DIY-pcap 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. data/bin/pcap +2 -62
  2. data/bin/rpcap +2 -63
  3. data/lib/diy/command.rb +80 -0
  4. data/lib/diy/device_finder.rb +1 -1
  5. data/lib/diy/dig.rb +3 -1
  6. data/lib/diy/live.rb +5 -0
  7. data/lib/diy/parser/mu/fixnum_ext.rb +7 -0
  8. data/lib/diy/parser/mu/pcap/ethernet.rb +148 -0
  9. data/lib/diy/parser/mu/pcap/header.rb +75 -0
  10. data/lib/diy/parser/mu/pcap/io_pair.rb +67 -0
  11. data/lib/diy/parser/mu/pcap/io_wrapper.rb +76 -0
  12. data/lib/diy/parser/mu/pcap/ip.rb +61 -0
  13. data/lib/diy/parser/mu/pcap/ipv4.rb +257 -0
  14. data/lib/diy/parser/mu/pcap/ipv6.rb +148 -0
  15. data/lib/diy/parser/mu/pcap/packet.rb +104 -0
  16. data/lib/diy/parser/mu/pcap/pkthdr.rb +155 -0
  17. data/lib/diy/parser/mu/pcap/reader.rb +61 -0
  18. data/lib/diy/parser/mu/pcap/reader/http_family.rb +170 -0
  19. data/lib/diy/parser/mu/pcap/sctp.rb +367 -0
  20. data/lib/diy/parser/mu/pcap/sctp/chunk.rb +123 -0
  21. data/lib/diy/parser/mu/pcap/sctp/chunk/data.rb +134 -0
  22. data/lib/diy/parser/mu/pcap/sctp/chunk/init.rb +100 -0
  23. data/lib/diy/parser/mu/pcap/sctp/chunk/init_ack.rb +68 -0
  24. data/lib/diy/parser/mu/pcap/sctp/parameter.rb +110 -0
  25. data/lib/diy/parser/mu/pcap/sctp/parameter/ip_address.rb +48 -0
  26. data/lib/diy/parser/mu/pcap/stream_packetizer.rb +72 -0
  27. data/lib/diy/parser/mu/pcap/tcp.rb +505 -0
  28. data/lib/diy/parser/mu/pcap/udp.rb +69 -0
  29. data/lib/diy/parser/mu/scenario/pcap.rb +172 -0
  30. data/lib/diy/parser/mu/scenario/pcap/fields.rb +50 -0
  31. data/lib/diy/parser/mu/scenario/pcap/rtp.rb +71 -0
  32. data/lib/diy/parser/pcap.rb +113 -0
  33. data/lib/diy/parser/readme.md +72 -0
  34. data/lib/diy/utils.rb +9 -1
  35. data/lib/diy/version.rb +1 -1
  36. data/lib/diy/worker.rb +3 -2
  37. data/lib/diy/worker_keeper.rb +6 -0
  38. data/spec/helper/tcp.dat +0 -0
  39. data/spec/live_spec.rb +9 -0
  40. data/spec/mu_parser_spec.rb +12 -0
  41. data/spec/utils_spec.rb +1 -1
  42. metadata +34 -3
@@ -0,0 +1,148 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ require 'ipaddr'
6
+
7
+ module Mu
8
+ class Pcap
9
+
10
+ class IPv6 < IP
11
+ FORMAT = 'NnCCa16a16'
12
+
13
+ attr_accessor :next_header, :hop_limit
14
+
15
+ def initialize
16
+ super
17
+ @next_header = 0
18
+ @hop_limit = 64
19
+ end
20
+
21
+ def v6?
22
+ return true
23
+ end
24
+
25
+ alias :proto :next_header
26
+ alias :ttl :hop_limit
27
+
28
+ def flow_id
29
+ if not @payload or @payload.is_a? String
30
+ return [:ipv6, @next_header, @src, @dst]
31
+ else
32
+ return [:ipv6, @src, @dst, @payload.flow_id]
33
+ end
34
+ end
35
+
36
+ def self.from_bytes bytes
37
+ Pcap.assert bytes.length >= 40, 'Truncated IPv6 header: ' +
38
+ "expected at least 40 bytes, got #{bytes.length} bytes"
39
+
40
+ vcl, length, next_header, hop_limit, src, dst =
41
+ bytes[0, 40].unpack FORMAT
42
+ version = vcl >> 28 & 0x0f
43
+ traffic_class = vcl >> 20 & 0xff
44
+ flow_label = vcl & 0xfffff
45
+
46
+ Pcap.assert version == 6, "Wrong IPv6 version: got (#{version})"
47
+ Pcap.assert bytes.length >= (40 + length), 'Truncated IPv6 header: ' +
48
+ "expected #{length + 40} bytes, got #{bytes.length} bytes"
49
+
50
+ ipv6 = IPv6.new
51
+ ipv6.next_header = next_header
52
+ ipv6.hop_limit = hop_limit
53
+ ipv6.src = IPAddr.new_ntoh(src).to_s
54
+ ipv6.dst = IPAddr.new_ntoh(dst).to_s
55
+
56
+ ipv6.payload_raw = bytes[40..-1]
57
+ ipv6.next_header, ipv6.payload =
58
+ payload_from_bytes ipv6, ipv6.next_header, bytes[40...40+length]
59
+
60
+ return ipv6
61
+ end
62
+
63
+ # Parse bytes and returns next_header and payload. Skips extension
64
+ # headers.
65
+ def self.payload_from_bytes ipv6, next_header, bytes
66
+ begin
67
+ case next_header
68
+ when IPPROTO_TCP
69
+ payload = TCP.from_bytes bytes
70
+ when IPPROTO_UDP
71
+ payload = UDP.from_bytes bytes
72
+ when IPPROTO_SCTP
73
+ payload = SCTP.from_bytes bytes
74
+ when IPPROTO_HOPOPTS
75
+ next_header, payload = eight_byte_header_from_bytes(ipv6,
76
+ bytes, 'hop-by-hop options')
77
+ when IPPROTO_ROUTING
78
+ next_header, payload = eight_byte_header_from_bytes(ipv6,
79
+ bytes, 'routing')
80
+ when IPPROTO_DSTOPTS
81
+ next_header, payload = eight_byte_header_from_bytes(ipv6,
82
+ bytes, 'destination options')
83
+ when IPPROTO_FRAGMENT
84
+ Pcap.assert bytes.length >= 8,
85
+ "Truncated IPv6 fragment header"
86
+ Pcap.assert false, 'IPv6 fragments are not supported'
87
+ when IPPROTO_AH
88
+ next_header, payload = ah_header_from_bytes(ipv6,
89
+ bytes, 'authentication header')
90
+ when IPPROTO_NONE
91
+ payload = ''
92
+ else
93
+ payload = bytes
94
+ end
95
+ rescue ParseError => e
96
+ Pcap.warning e
97
+ payload = bytes
98
+ end
99
+ return [next_header, payload]
100
+ end
101
+
102
+ # Parse extension header that's a multiple of 8 bytes
103
+ def self.eight_byte_header_from_bytes ipv6, bytes, name
104
+ Pcap.assert bytes.length >= 8, "Truncated IPv6 #{name} header"
105
+ length = (bytes[1].ord + 1) * 8
106
+ Pcap.assert bytes.length >= length, "Truncated IPv6 #{name} header"
107
+ return payload_from_bytes(ipv6, bytes[0].ord, bytes[length..-1])
108
+ end
109
+
110
+ # Parse authentication header (whose length field is interpeted differently)
111
+ def self.ah_header_from_bytes ipv6, bytes, name
112
+ Pcap.assert bytes.length >= 8, "Truncated IPv6 #{name} header"
113
+ length = (bytes[1].ord + 2) * 4
114
+ Pcap.assert bytes.length >= length, "Truncated IPv6 #{name} header"
115
+ return payload_from_bytes(ipv6, bytes[0].ord, bytes[length..-1])
116
+ end
117
+
118
+ def write io
119
+ if @payload.is_a? String
120
+ payload = @payload
121
+ else
122
+ string_io = StringIO.new
123
+ @payload.write string_io, self
124
+ payload = string_io.string
125
+ end
126
+ src = IPAddr.new(@src, Socket::AF_INET6).hton
127
+ dst = IPAddr.new(@dst, Socket::AF_INET6).hton
128
+ header = [0x60000000, payload.length, @next_header, @hop_limit,
129
+ src, dst].pack FORMAT
130
+ io.write header
131
+ io.write payload
132
+ end
133
+
134
+ def pseudo_header payload_length
135
+ return IPAddr.new(@src, Socket::AF_INET6).hton +
136
+ IPAddr.new(@dst, Socket::AF_INET6).hton +
137
+ [payload_length, '', @next_header].pack('Na3C')
138
+ end
139
+
140
+ def == other
141
+ return super &&
142
+ self.next_header == other.next_header &&
143
+ self.hop_limit == other.hop_limit
144
+ end
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,104 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module Mu
6
+ class Pcap
7
+
8
+ class Packet
9
+ attr_accessor :payload, :payload_raw
10
+
11
+ def initialize
12
+ @payload = ''
13
+ @payload_raw = ''
14
+ end
15
+
16
+ # Get payload as bytes. If the payload is a parsed object, returns
17
+ # raw payload. Otherwise return unparsed bytes.
18
+ def payload_bytes
19
+ if @payload.is_a? String
20
+ return @payload
21
+ end
22
+ return @payload_raw
23
+ end
24
+
25
+ def deepdup
26
+ dup = self.dup
27
+ if @payload.respond_to? :deepdup
28
+ dup.payload = @payload.deepdup
29
+ else
30
+ dup.payload = @payload.dup
31
+ end
32
+ return dup
33
+ end
34
+
35
+ def flow_id
36
+ raise NotImplementedError
37
+ end
38
+
39
+ # Reassemble, reorder, and merge packets.
40
+ def self.normalize packets
41
+ begin
42
+ packets = TCP.reorder packets
43
+ rescue TCP::ReorderError => e
44
+ Pcap.warning e
45
+ end
46
+
47
+ begin
48
+ packets = SCTP.reorder packets
49
+ rescue SCTP::ReorderError => e
50
+ Pcap.warning e
51
+ end
52
+
53
+ begin
54
+ packets = TCP.merge packets
55
+ rescue TCP::MergeError => e
56
+ Pcap.warning e
57
+ end
58
+ return packets
59
+ end
60
+
61
+ # Remove non-L7/DNS/DHCP traffic if there is L7 traffic. Returns
62
+ # original packets if there is no L7 traffic.
63
+ IGNORE_UDP_PORTS = [
64
+ 53, # DNS
65
+ 67, 68, # DHCP
66
+ 546, 547 # DHCPv6
67
+ ]
68
+ def self.isolate_l7 packets
69
+ cleaned_packets = []
70
+ packets.each do |packet|
71
+ if TCP.tcp? packet
72
+ cleaned_packets << packet
73
+ elsif UDP.udp? packet
74
+ src_port = packet.payload.payload.src_port
75
+ dst_port = packet.payload.payload.dst_port
76
+ if not IGNORE_UDP_PORTS.member? src_port and
77
+ not IGNORE_UDP_PORTS.member? dst_port
78
+ cleaned_packets << packet
79
+ end
80
+ elsif SCTP.sctp? packet
81
+ cleaned_packets << packet
82
+ end
83
+ end
84
+ if cleaned_packets.empty?
85
+ return packets
86
+ end
87
+ return cleaned_packets
88
+ end
89
+
90
+ def to_bytes
91
+ io = StringIO.new
92
+ write io
93
+ io.close
94
+ return io.string
95
+ end
96
+
97
+ def == other
98
+ return self.class == other.class && self.payload == other.payload &&
99
+ self.payload_raw == other.payload_raw
100
+ end
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,155 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module Mu
6
+ class Pcap
7
+
8
+ class Pkthdr
9
+ attr_accessor :endian, :ts_sec, :ts_usec, :caplen, :len, :pkt, :pkt_raw
10
+
11
+ def initialize endian=BIG_ENDIAN, ts_sec=0, ts_usec=0, caplen=0, len=0, pkt=nil
12
+ @endian = endian
13
+ @ts_sec = ts_sec
14
+ @ts_usec = ts_usec
15
+ @caplen = caplen
16
+ @len = len
17
+ @pkt = pkt
18
+ @pkt_raw = pkt
19
+ end
20
+
21
+ FMT_NNNN = 'NNNN'
22
+ FMT_VVVV = 'VVVV'
23
+ def self.read io, endian=BIG_ENDIAN
24
+ if endian == BIG_ENDIAN
25
+ format = FMT_NNNN
26
+ elsif endian == LITTLE_ENDIAN
27
+ format = FMT_VVVV
28
+ end
29
+ bytes = io.read 16
30
+ if not bytes
31
+ raise EOFError, 'Missing PCAP packet header'
32
+ end
33
+ if not bytes.length == 16
34
+ raise ParseError, "Truncated PCAP packet header: expected 16 bytes, got #{bytes.length} bytes"
35
+ end
36
+ ts_sec, ts_usec, caplen, len = bytes.unpack format
37
+ pkt = io.read(caplen)
38
+ if not pkt
39
+ raise ParseError, 'Missing PCAP packet header packet'
40
+ end
41
+ if not pkt.length == caplen
42
+ raise ParseError, "Truncated PCAP packet header: expected #{pkthdr.caplen} bytes, got #{pkthdr.pkt.length} bytes"
43
+ end
44
+ pkthdr = Pkthdr.new endian, ts_sec, ts_usec, caplen, len, pkt
45
+ return pkthdr
46
+ end
47
+
48
+ def write io
49
+ if @pkt.is_a? String
50
+ pkt = @pkt
51
+ else
52
+ string_io = StringIO.new
53
+ @pkt.write string_io
54
+ pkt = string_io.string
55
+ end
56
+ len = pkt.length
57
+ bytes = [@ts_sec, @ts_usec, len, len].pack FMT_NNNN
58
+ io.write bytes
59
+ io.write pkt
60
+ end
61
+
62
+ def decode! endian, linktype
63
+ @pkt = case linktype
64
+ when DLT_NULL; Pkthdr.decode_null endian, @pkt
65
+ when DLT_EN10MB; Pkthdr.decode_en10mb @pkt
66
+ when DLT_RAW; raise NotImplementedError
67
+ when DLT_LINUX_SLL; Pkthdr.decode_linux_sll @pkt
68
+ else raise ParseError, "Unknown PCAP linktype: #{linktype}"
69
+ end
70
+ end
71
+
72
+ # See http://wiki.wireshark.org/NullLoopback
73
+ # and epan/aftypes.h in wireshark code.
74
+ BSD_AF_INET6 = [
75
+ OPENBSD_AF_INET6 = 24,
76
+ FREEBSD_AF_INET6 = 28,
77
+ DARWIN_AF_INET6 = 30
78
+ ]
79
+
80
+ def self.decode_null endian, bytes
81
+ Pcap.assert bytes.length >= 4, 'Truncated PCAP packet header: ' +
82
+ "expected at least 4 bytes, got #{bytes.length} bytes"
83
+ if endian == BIG_ENDIAN
84
+ format = 'N'
85
+ elsif endian == LITTLE_ENDIAN
86
+ format = 'V'
87
+ end
88
+ family = bytes[0, 4].unpack(format)[0]
89
+ bytes = bytes[4..-1]
90
+ ethernet = Ethernet.new
91
+ ethernet.src = '00:01:01:00:00:01'
92
+ ethernet.dst = '00:01:01:00:00:02'
93
+ ethernet.payload = ethernet.payload_raw = bytes
94
+ if family != Socket::AF_INET and family != Socket::AF_INET6 and
95
+ not BSD_AF_INET6.include?(family)
96
+ raise ParseError, "Unknown PCAP packet header family: #{family}"
97
+ end
98
+ begin
99
+ case family
100
+ when Socket::AF_INET
101
+ ethernet.type = Ethernet::ETHERTYPE_IP
102
+ ethernet.payload = IPv4.from_bytes ethernet.payload
103
+ when Socket::AF_INET6, FREEBSD_AF_INET6, OPENBSD_AF_INET6, DARWIN_AF_INET6
104
+ ethernet.type = Ethernet::ETHERTYPE_IP6
105
+ ethernet.payload = IPv6.from_bytes ethernet.payload
106
+ else
107
+ raise NotImplementedError
108
+ end
109
+ rescue ParseError => e
110
+ Pcap.warning e
111
+ end
112
+ return ethernet
113
+ end
114
+
115
+ def self.decode_en10mb bytes
116
+ return Ethernet.from_bytes(bytes)
117
+ end
118
+
119
+ def self.decode_linux_sll bytes
120
+ Pcap.assert bytes.length >= 16, 'Truncated PCAP packet header: ' +
121
+ "expected at least 16 bytes, got #{bytes.length} bytes"
122
+ packet_type, link_type, addr_len = bytes.unpack('nnn')
123
+ type = bytes[14, 2].unpack('n')[0]
124
+ bytes = bytes[16..-1]
125
+ ethernet = Ethernet.new
126
+ ethernet.type = type
127
+ ethernet.src = '00:01:01:00:00:01'
128
+ ethernet.dst = '00:01:01:00:00:02'
129
+ ethernet.payload = ethernet.payload_raw = bytes
130
+ begin
131
+ case type
132
+ when Ethernet::ETHERTYPE_IP
133
+ ethernet.payload = IPv4.from_bytes ethernet.payload
134
+ when Ethernet::ETHERTYPE_IP6
135
+ ethernet.payload = IPv6.from_bytes ethernet.payload
136
+ end
137
+ rescue ParseError => e
138
+ Pcap.warning e
139
+ end
140
+ return ethernet
141
+ end
142
+
143
+ def == other
144
+ return self.class == other.class &&
145
+ self.endian == other.endian &&
146
+ self.ts_sec == other.ts_sec &&
147
+ self.ts_usec == other.ts_usec &&
148
+ self.caplen == other.caplen &&
149
+ self.len == other.len &&
150
+ self.pkt == other.pkt
151
+ end
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,61 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module Mu
6
+ class Pcap
7
+
8
+ class Reader
9
+ attr_accessor :pcap2scenario
10
+
11
+ FAMILY_TO_READER = {}
12
+
13
+ # Returns a reader instance of specified family. Returns nil when family is :none.
14
+ def self.reader family
15
+ if family == :none
16
+ return nil
17
+ end
18
+
19
+ if klass = FAMILY_TO_READER[family]
20
+ return klass.new
21
+ end
22
+
23
+ raise ArgumentError, "Unknown protocol family: '#{family}'"
24
+ end
25
+
26
+ # Returns family name
27
+ def family
28
+ raise NotImplementedError
29
+ end
30
+
31
+ # Notify parser of bytes written. Parser may update state
32
+ # to serve as a hint for subsequent reads.
33
+ def record_write bytes, state=nil
34
+ begin
35
+ do_record_write bytes, state
36
+ rescue
37
+ nil
38
+ end
39
+ end
40
+
41
+ # Returns next complete message from byte stream or nil.
42
+ def read_message bytes, state=nil
43
+ read_message! bytes.dup, state
44
+ end
45
+
46
+ # Mutating form of read_message. Removes a complete message
47
+ # from input stream. Returns the message or nil if there.
48
+ # is not a complete message.
49
+ def read_message! bytes, state=nil
50
+ begin
51
+ do_read_message! bytes, state
52
+ rescue
53
+ nil
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+
61
+ require 'mu/pcap/reader/http_family'