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,67 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module Mu
6
+ class Pcap
7
+
8
+ # For emulating of a pair of connected sockets. Bytes written
9
+ # with #write to one side are returned by a subsequent #read on
10
+ # the other side.
11
+ #
12
+ # Use Pair.stream_pair to get a pair with stream semantics.
13
+ # Use Pair.packet_pair to get a pair with packet semantics.
14
+ class IOPair
15
+ attr_reader :read_queue
16
+ attr_accessor :other
17
+
18
+ def initialize
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def self.stream_pair
23
+ io1 = Stream.new
24
+ io2 = Stream.new
25
+ io1.other = io2
26
+ io2.other = io1
27
+ return io1, io2
28
+ end
29
+
30
+ def self.packet_pair
31
+ io1 = Packet.new
32
+ io2 = Packet.new
33
+ io1.other = io2
34
+ io2.other = io1
35
+ return io1, io2
36
+ end
37
+
38
+ def write bytes
39
+ @other.read_queue << bytes
40
+ bytes.size
41
+ end
42
+
43
+ class Stream < IOPair
44
+ def initialize
45
+ @read_queue = ""
46
+ end
47
+
48
+ def read n=nil
49
+ n ||= @read_queue.size
50
+ @read_queue.slice!(0,n)
51
+ end
52
+ end
53
+
54
+ class Packet < IOPair
55
+ def initialize
56
+ @read_queue = []
57
+ end
58
+
59
+ def read
60
+ @read_queue.shift
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,76 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ require 'mu/pcap/io_pair'
6
+
7
+ module Mu
8
+ class Pcap
9
+ class IOWrapper
10
+ attr_reader :ios, :unread, :state
11
+
12
+ def initialize ios, reader
13
+ @ios = ios
14
+ @reader = reader
15
+ # parse state for reader
16
+ @state = {}
17
+ # read off of underlying io but not yet processed by @reader
18
+ @unread = ""
19
+ end
20
+
21
+ # Impose upper limit to protect against memory exhaustion.
22
+ MAX_RECEIVE_SIZE = 1048576 # 1MB
23
+
24
+ # Returns next higher level protocol message.
25
+ def read
26
+ until message = @reader.read_message!(@unread, @state)
27
+ bytes = @ios.read
28
+ if bytes and not bytes.empty?
29
+ @unread << bytes
30
+ else
31
+ return nil
32
+ end
33
+ if @unread.size > MAX_RECEIVE_SIZE
34
+ raise "Maximum message size (#{MAX_RECEIVE_SIZE}) exceeded"
35
+ end
36
+ end
37
+
38
+ return message
39
+ end
40
+
41
+ # Parser may need to see requests to understand responses.
42
+ def record_write bytes
43
+ @reader.record_write bytes, @state
44
+ end
45
+
46
+ def write bytes, *args
47
+ w = @ios.write bytes, *args
48
+ record_write bytes
49
+ w
50
+ end
51
+
52
+ def write_to bytes, *args
53
+ w = @ios.write_to bytes, *args
54
+ record_write bytes
55
+ w
56
+ end
57
+
58
+ def open
59
+ if block_given?
60
+ @ios.open { yield }
61
+ else
62
+ @ios.open
63
+ end
64
+ end
65
+
66
+ def open?
67
+ @ios.open?
68
+ end
69
+
70
+ def close
71
+ @ios.close
72
+ end
73
+
74
+ end
75
+ end
76
+ 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 IP < Packet
9
+ IPPROTO_TCP = 6
10
+ IPPROTO_UDP = 17
11
+ IPPROTO_HOPOPTS = 0
12
+ IPPROTO_ROUTING = 43
13
+ IPPROTO_FRAGMENT = 44
14
+ IPPROTO_AH = 51
15
+ IPPROTO_NONE = 59
16
+ IPPROTO_DSTOPTS = 60
17
+ IPPROTO_SCTP = 132
18
+
19
+ attr_accessor :src, :dst
20
+
21
+ def initialize src=nil, dst=nil
22
+ super()
23
+ @src = src
24
+ @dst = dst
25
+ end
26
+
27
+ def v4?
28
+ return false
29
+ end
30
+
31
+ def v6?
32
+ return false
33
+ end
34
+
35
+ def proto
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def pseudo_header payload_length
40
+ raise NotImplementedError
41
+ end
42
+
43
+ def == other
44
+ return super &&
45
+ self.src == other.src &&
46
+ self.dst == other.dst
47
+ end
48
+
49
+ def self.checksum bytes
50
+ if bytes.size & 1 == 1
51
+ bytes = bytes + "\0"
52
+ end
53
+ sum = 0
54
+ bytes.unpack("n*").each {|n| sum += n }
55
+ sum = (sum & 0xffff) + (sum >> 16 & 0xffff)
56
+ ~sum & 0xffff
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,257 @@
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 IPv4 < IP
11
+ IP_RF = 0x8000 # Reserved
12
+ IP_DF = 0x4000 # Don't fragment
13
+ IP_MF = 0x2000 # More fragments
14
+ IP_OFFMASK = 0x1fff
15
+
16
+ FMT_HEADER = 'CCnnnCCna4a4'
17
+
18
+ attr_accessor :ip_id, :offset, :ttl, :proto, :src, :dst, :dscp
19
+
20
+ def initialize src=nil, dst=nil, ip_id=0, offset=0, ttl=64, proto=0, dscp=0
21
+ super()
22
+ @ip_id = ip_id
23
+ @offset = offset
24
+ @ttl = ttl
25
+ @proto = proto
26
+ @src = src
27
+ @dst = dst
28
+ @dscp = dscp
29
+ end
30
+
31
+ def v4?
32
+ return true
33
+ end
34
+
35
+ def flow_id
36
+ if not @payload or @payload.is_a? String
37
+ return [:ipv4, @proto, @src, @dst]
38
+ else
39
+ return [:ipv4, @src, @dst, @payload.flow_id]
40
+ end
41
+ end
42
+
43
+ NTOP = {} # Network to human cache
44
+ HTON = {} # Human to network cache
45
+
46
+ def self.from_bytes bytes
47
+ bytes.length >= 20 or
48
+ raise ParseError, "Truncated IPv4 header: expected at least 20 bytes, got #{bytes.length} bytes"
49
+
50
+ vhl, tos, length, id, offset, ttl, proto, checksum, src, dst = bytes[0,20].unpack FMT_HEADER
51
+ version = vhl >> 4
52
+ hl = (vhl & 0b1111) * 4
53
+
54
+ version == 4 or
55
+ raise ParseError, "Wrong IPv4 version: got (#{version})"
56
+ hl >= 20 or
57
+ raise ParseError, "Bad IPv4 header length: expected at least 20 bytes raise ParseError, got #{hl} bytes"
58
+ bytes.length >= hl or
59
+ raise ParseError, "Truncated IPv4 header: expected #{hl} bytes raise ParseError, got #{bytes.length} bytes"
60
+ length >= 20 or
61
+ raise ParseError, "Bad IPv4 packet length: expected at least 20 bytes raise ParseError, got #{length} bytes"
62
+ bytes.length >= length or
63
+ raise ParseError, "Truncated IPv4 packet: expected #{length} bytes raise ParseError, got #{bytes.length} bytes"
64
+
65
+ if hl != 20
66
+ IPv4.check_options bytes[20, hl-20]
67
+ end
68
+
69
+ src = NTOP[src] ||= IPAddr.ntop(src)
70
+ dst = NTOP[dst] ||= IPAddr.ntop(dst)
71
+ dscp = tos >> 2
72
+ ipv4 = IPv4.new(src, dst, id, offset, ttl, proto, dscp)
73
+ ipv4.payload_raw = bytes[hl..-1]
74
+
75
+ payload = bytes[hl...length]
76
+ if offset & (IP_OFFMASK | IP_MF) == 0
77
+ begin
78
+ case proto
79
+ when IPPROTO_TCP
80
+ ipv4.payload = TCP.from_bytes payload
81
+ when IPPROTO_UDP
82
+ ipv4.payload = UDP.from_bytes payload
83
+ when IPPROTO_SCTP
84
+ ipv4.payload = SCTP.from_bytes payload
85
+ else
86
+ ipv4.payload = payload
87
+ end
88
+ rescue ParseError => e
89
+ Pcap.warning e
90
+ end
91
+ else
92
+ ipv4.payload = payload
93
+ end
94
+ return ipv4
95
+ end
96
+
97
+ def write io
98
+ if @payload.is_a? String
99
+ payload = @payload
100
+ else
101
+ string_io = StringIO.new
102
+ @payload.write string_io, self
103
+ payload = string_io.string
104
+ end
105
+ length = 20 + payload.length
106
+ if length > 65535
107
+ Pcap.warning "IPv4 payload is too large"
108
+ end
109
+
110
+ src = HTON[@src] ||= IPAddr.new(@src).hton
111
+ dst = HTON[@dst] ||= IPAddr.new(@dst).hton
112
+ fields = [0x45, @dscp << 2, length, @ip_id, @offset, @ttl, @proto, 0, src, dst]
113
+ header = fields.pack(FMT_HEADER)
114
+ fields[7] = IP.checksum(header)
115
+ header = fields.pack(FMT_HEADER)
116
+ io.write header
117
+ io.write payload
118
+ end
119
+
120
+ FMT_PSEUDO_HEADER = 'a4a4CCn'
121
+ def pseudo_header payload_length
122
+ src = HTON[@src] ||= IPAddr.new(@src).hton
123
+ dst = HTON[@dst] ||= IPAddr.new(@dst).hton
124
+ return [src, dst, 0, @proto, payload_length].pack(FMT_PSEUDO_HEADER)
125
+ end
126
+
127
+ def fragment?
128
+ return (@offset & (IP_OFFMASK | IP_MF) != 0)
129
+ end
130
+
131
+ # Check that IP or TCP options are valid. Do nothing if they are valid.
132
+ # Both IP and TCP options are 8-bit TLVs with an inclusive length. Both
133
+ # have one byte options 0 and 1.
134
+ def self.check_options options, label='IPv4'
135
+ while not options.empty?
136
+ type = options.slice!(0, 1)[0].ord
137
+ if type == 0 or type == 1
138
+ next
139
+ end
140
+ Pcap.assert !options.empty?,
141
+ "#{label} option #{type} is missing the length field"
142
+ length = options.slice!(0, 1)[0].ord
143
+ Pcap.assert length >= 2,
144
+ "#{label} option #{type} has invalid length: #{length}"
145
+ Pcap.assert length - 2 <= options.length,
146
+ "#{label} option #{type} has truncated data"
147
+ options.slice! 0, length - 2
148
+ end
149
+ end
150
+
151
+ ReassembleState = ::Struct.new :packets, :bytes, :mf, :overlap
152
+
153
+ # Reassemble fragmented IPv4 packets
154
+ def self.reassemble packets
155
+ reassembled_packets = []
156
+ flow_id_to_state = {}
157
+ packets.each do |packet|
158
+ if not packet.is_a?(Ethernet) or not packet.payload.is_a?(IPv4)
159
+ # Ignore non-IPv4 packet
160
+ elsif not packet.payload.fragment?
161
+ # Ignore non-fragments
162
+ else
163
+ # Get reassembly state
164
+ ip = packet.payload
165
+ flow_id = [ip.ip_id, ip.proto, ip.src, ip.dst]
166
+ state = flow_id_to_state[flow_id]
167
+ if not state
168
+ state = ReassembleState.new [], [], true, false
169
+ flow_id_to_state[flow_id] = state
170
+ end
171
+ state.packets << packet
172
+
173
+ # Clear the more-fragments flag if no more fragments
174
+ if ip.offset & IP_MF == 0
175
+ state.mf = false
176
+ end
177
+
178
+ # Add the bytes
179
+ start = (ip.offset & IP_OFFMASK) * 8
180
+ finish = start + ip.payload.length
181
+ state.bytes.fill nil, start, finish - start
182
+ start.upto(finish-1) do |i|
183
+ if not state.bytes[i]
184
+ byte = ip.payload[i - start].chr
185
+ state.bytes[i] = byte
186
+ elsif not state.overlap
187
+ name = "%s:%s:%d" % [ip.src, ip.dst, ip.proto]
188
+ Pcap.warning \
189
+ "IPv4 flow #{name} contains overlapping fragements"
190
+ state.overlap = true
191
+ end
192
+ end
193
+
194
+ # We're done if we've received a fragment without the
195
+ # more-fragments flag and all the bytes in the buffer have been
196
+ # set.
197
+ if not state.mf and state.bytes.all?
198
+ # Remove fragments from reassembled_packets
199
+ state.packets.each do |packet|
200
+ reassembled_packets.delete_if do |reassembled_packet|
201
+ packet.object_id == reassembled_packet.object_id
202
+ end
203
+ end
204
+ # Remove state
205
+ flow_id_to_state.delete flow_id
206
+ # Create new packet
207
+ packet = state.packets[0].deepdup
208
+ ipv4 = packet.payload
209
+ ipv4.offset = 0
210
+ ipv4.payload = state.bytes.join
211
+ # Decode
212
+ begin
213
+ case ipv4.proto
214
+ when IPPROTO_TCP
215
+ ipv4.payload = TCP.from_bytes ipv4.payload
216
+ when IPPROTO_UDP
217
+ ipv4.payload = UDP.from_bytes ipv4.payload
218
+ when IPPROTO_SCTP
219
+ ipv4.payload = SCTP.from_bytes ipv4.payload
220
+ end
221
+ rescue ParseError => e
222
+ Pcap.warning e
223
+ end
224
+ end
225
+ end
226
+ reassembled_packets << packet
227
+ end
228
+ if !flow_id_to_state.empty?
229
+ Pcap.warning \
230
+ "#{flow_id_to_state.length} flow(s) have IPv4 fragments " \
231
+ "that can't be reassembled"
232
+ end
233
+
234
+ return reassembled_packets
235
+ end
236
+
237
+ def to_s
238
+ if @payload.is_a? String
239
+ payload = @payload.inspect
240
+ else
241
+ payload = @payload.to_s
242
+ end
243
+ return "ipv4(%d, %s, %s, %s)" % [@proto, @src, @dst, payload]
244
+ end
245
+
246
+ def == other
247
+ return super &&
248
+ self.proto == other.proto &&
249
+ self.ip_id == other.ip_id &&
250
+ self.offset == other.offset &&
251
+ self.ttl == other.ttl &&
252
+ self.dscp == other.dscp
253
+ end
254
+ end
255
+
256
+ end
257
+ end