DIY-pcap 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
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'