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.
- data/bin/pcap +2 -62
- data/bin/rpcap +2 -63
- data/lib/diy/command.rb +80 -0
- data/lib/diy/device_finder.rb +1 -1
- data/lib/diy/dig.rb +3 -1
- data/lib/diy/live.rb +5 -0
- data/lib/diy/parser/mu/fixnum_ext.rb +7 -0
- data/lib/diy/parser/mu/pcap/ethernet.rb +148 -0
- data/lib/diy/parser/mu/pcap/header.rb +75 -0
- data/lib/diy/parser/mu/pcap/io_pair.rb +67 -0
- data/lib/diy/parser/mu/pcap/io_wrapper.rb +76 -0
- data/lib/diy/parser/mu/pcap/ip.rb +61 -0
- data/lib/diy/parser/mu/pcap/ipv4.rb +257 -0
- data/lib/diy/parser/mu/pcap/ipv6.rb +148 -0
- data/lib/diy/parser/mu/pcap/packet.rb +104 -0
- data/lib/diy/parser/mu/pcap/pkthdr.rb +155 -0
- data/lib/diy/parser/mu/pcap/reader.rb +61 -0
- data/lib/diy/parser/mu/pcap/reader/http_family.rb +170 -0
- data/lib/diy/parser/mu/pcap/sctp.rb +367 -0
- data/lib/diy/parser/mu/pcap/sctp/chunk.rb +123 -0
- data/lib/diy/parser/mu/pcap/sctp/chunk/data.rb +134 -0
- data/lib/diy/parser/mu/pcap/sctp/chunk/init.rb +100 -0
- data/lib/diy/parser/mu/pcap/sctp/chunk/init_ack.rb +68 -0
- data/lib/diy/parser/mu/pcap/sctp/parameter.rb +110 -0
- data/lib/diy/parser/mu/pcap/sctp/parameter/ip_address.rb +48 -0
- data/lib/diy/parser/mu/pcap/stream_packetizer.rb +72 -0
- data/lib/diy/parser/mu/pcap/tcp.rb +505 -0
- data/lib/diy/parser/mu/pcap/udp.rb +69 -0
- data/lib/diy/parser/mu/scenario/pcap.rb +172 -0
- data/lib/diy/parser/mu/scenario/pcap/fields.rb +50 -0
- data/lib/diy/parser/mu/scenario/pcap/rtp.rb +71 -0
- data/lib/diy/parser/pcap.rb +113 -0
- data/lib/diy/parser/readme.md +72 -0
- data/lib/diy/utils.rb +9 -1
- data/lib/diy/version.rb +1 -1
- data/lib/diy/worker.rb +3 -2
- data/lib/diy/worker_keeper.rb +6 -0
- data/spec/helper/tcp.dat +0 -0
- data/spec/live_spec.rb +9 -0
- data/spec/mu_parser_spec.rb +12 -0
- data/spec/utils_spec.rb +1 -1
- 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'
|