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,170 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ require 'mu/pcap/reader'
6
+ require 'stringio'
7
+ require 'zlib'
8
+
9
+ module Mu
10
+ class Pcap
11
+ class Reader
12
+
13
+ # Reader for HTTP family of protocols (HTTP/SIP/RTSP).
14
+ # Handles message boundaries and decompressing/dechunking payloads.
15
+ class HttpFamily < Reader
16
+ FAMILY = :http
17
+ FAMILY_TO_READER[FAMILY] = self
18
+ CRLF = "\r\n"
19
+ def family
20
+ FAMILY
21
+ end
22
+
23
+ def do_record_write bytes, state=nil
24
+ return if not state
25
+ if bytes =~ RE_REQUEST_LINE
26
+ method = $1
27
+ requests = state[:requests] ||= []
28
+ requests << method
29
+ end
30
+ end
31
+ private :do_record_write
32
+
33
+ RE_CONTENT_ENCODING = /^content-encoding:\s*(gzip|deflate)/i
34
+ RE_CHUNKED = /Transfer-Encoding:\s*chunked/i
35
+ RE_HEADERS_COMPLETE = /.*?\r\n\r\n/m
36
+ # Request line e.g. GET /index.html HTTP/1.1
37
+ RE_REQUEST_LINE = /\A([^ \t\r\n]+)[ \t]+([^ \t\r\n]+)[ \t]+(HTTP|SIP|RTSP)\/[\d.]+.*\r\n/
38
+ # Status line e.g. SIP/2.0 404 Authorization required
39
+ RE_STATUS_LINE = /\A((HTTP|SIP|RTSP)\/[\d.]+[ \t]+(\d+))\b.*\r\n/
40
+
41
+ RE_CONTENT_LENGTH = /^(Content-Length)(:\s*)(\d+)\r\n/i
42
+ RE_CONTENT_LENGTH_SIP = /^(Content-Length|l)(:\s*)(\d+)\r\n/i
43
+
44
+
45
+ def do_read_message! bytes, state=nil
46
+ case bytes
47
+ when RE_REQUEST_LINE
48
+ proto = $3
49
+ when RE_STATUS_LINE
50
+ proto = $2
51
+ status = $3.to_i
52
+ if state
53
+ requests = state[:requests] ||= []
54
+ if requests[0] == "HEAD"
55
+ reply_to_head = true
56
+ end
57
+ if status > 199
58
+ # We have a final response. Forget about request.
59
+ requests.shift
60
+ end
61
+ end
62
+ else
63
+ return nil # Not http family.
64
+ end
65
+
66
+ # Read headers
67
+ if bytes =~ RE_HEADERS_COMPLETE
68
+ headers = $&
69
+ rest = $'
70
+ else
71
+ return nil
72
+ end
73
+ message = [headers]
74
+
75
+ # Read payload.
76
+ if proto == 'SIP'
77
+ re_content_length = RE_CONTENT_LENGTH_SIP
78
+ else
79
+ re_content_length = RE_CONTENT_LENGTH
80
+ end
81
+ if reply_to_head
82
+ length = 0
83
+ elsif headers =~ RE_CHUNKED
84
+ # Read chunks, dechunking in runtime case.
85
+ raw, dechunked = get_chunks(rest)
86
+ if raw
87
+ length = raw.length
88
+ payload = raw
89
+ else
90
+ return nil # Last chunk not received.
91
+ end
92
+ elsif headers =~ re_content_length
93
+ length = $3.to_i
94
+ if rest.length >= length
95
+ payload = rest.slice(0,length)
96
+ else
97
+ return nil # Not enough bytes.
98
+ end
99
+ else
100
+ # XXX. When there is a payload and no content-length
101
+ # header HTTP RFC says to read until connection close.
102
+ length = 0
103
+ end
104
+
105
+ message << payload
106
+
107
+ # Consume message from input bytes.
108
+ message_len = headers.length + length
109
+ if bytes.length >= message_len
110
+ bytes.slice!(0, message_len)
111
+ return message.join
112
+ else
113
+ return nil # Not enough bytes.
114
+ end
115
+ end
116
+ private :do_read_message!
117
+
118
+ # Returns array containing raw and dechunked payload. Returns nil
119
+ # if payload cannot be completely read.
120
+ RE_CHUNK_SIZE_LINE = /\A([[:xdigit:]]+)\r\n?/
121
+ def get_chunks bytes
122
+ raw = []
123
+ dechunked = []
124
+ io = StringIO.new bytes
125
+ until io.eof?
126
+ # Read size line
127
+ size_line = io.readline
128
+ raw << size_line
129
+ if size_line =~ RE_CHUNK_SIZE_LINE
130
+ chunk_size = $1.to_i(16)
131
+ else
132
+ # Malformed chunk size line
133
+ $stderr.puts "malformed size line : #{size_line.inspect}"
134
+ return nil
135
+ end
136
+
137
+ # Read chunk data
138
+ chunk = io.read(chunk_size)
139
+ if chunk.size < chunk_size
140
+ # malformed/incomplete
141
+ $stderr.puts "malformed/incomplete #{chunk_size}"
142
+ return nil
143
+ end
144
+ raw << chunk
145
+ dechunked << chunk
146
+ # Get end-of-chunk CRLF
147
+ crlf = io.read(2)
148
+ if crlf == CRLF
149
+ raw << crlf
150
+ else
151
+ # CRLF has not arrived or, if this is the last chunk,
152
+ # we might be looking at the first two bytes of a trailer
153
+ # and we don't support trailers (see rfc 2616 sec3.6.1).
154
+ return nil
155
+ end
156
+
157
+ if chunk_size == 0
158
+ # Done. Return raw and dechunked payloads.
159
+ return raw.join, dechunked.join
160
+ end
161
+ end
162
+
163
+ # EOF w/out reaching last chunk.
164
+ return nil
165
+ end
166
+ end
167
+
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,367 @@
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 SCTP < Packet
9
+ attr_accessor :src_port, :dst_port, :verify_tag, :checksum
10
+
11
+ # SCTP chunk types
12
+ CHUNK_DATA = 0x00
13
+ CHUNK_INIT = 0x01
14
+ CHUNK_INIT_ACK = 0x02
15
+ CHUNK_SACK = 0x03
16
+ CHUNK_HEARTBEAT = 0x04
17
+ CHUNK_HEARTBEAT_ACK = 0x05
18
+ CHUNK_ABORT = 0x06
19
+ CHUNK_SHUTDOWN = 0x07
20
+ CHUNK_SHUTDOWN_ACK = 0x08
21
+ CHUNK_ERROR = 0x09
22
+ CHUNK_COOKIE_ECHO = 0x0A
23
+ CHUNK_COOKIE_ACK = 0x0B
24
+ CHUNK_ECNE = 0x0C
25
+ CHUNK_CWR = 0x0D
26
+ CHUNK_SHUTDOWN_COMPLETE = 0x0E
27
+ CHUNK_AUTH = 0x0F
28
+ CHUNK_ASCONF_ACK = 0x80
29
+ CHUNK_PADDING = 0x84
30
+ CHUNK_FORWARD_TSN = 0xC0
31
+ CHUNK_ASCONF = 0xC1
32
+
33
+ # SCTP parameter types
34
+ PARAM_IPV4 = 0x0005
35
+ PARAM_IPV6 = 0x0006
36
+ PARAM_STATE_COOKIE = 0x0007
37
+ PARAM_COOKIE_PRESERVATIVE = 0x0009
38
+ PARAM_HOST_NAME_ADDR = 0x000B
39
+ PARAM_SUPPORTED_ADDR_TYPES = 0x000C
40
+ PARAM_ECN = 0x8000
41
+ PARAM_RANDOM = 0x8002
42
+ PARAM_CHUNK_LIST = 0x8003
43
+ PARAM_HMAC_ALGORITHM = 0x8004
44
+ PARAM_PADDING = 0x8005
45
+ PARAM_SUPPORTED_EXTENSIONS = 0x8006
46
+ PARAM_FORWARD_TSN = 0xC000
47
+ PARAM_SET_PRIMARY_ADDR = 0xC004
48
+ PARAM_ADAPTATION_LAYER_INDICATION = 0xC006
49
+
50
+ def initialize
51
+ super
52
+
53
+ @src_port = 0
54
+ @dst_port = 0
55
+ @verify_tag = 0
56
+ @checksum = 0
57
+ @payload = []
58
+ end
59
+
60
+ def flow_id
61
+ return [:sctp, @src_port, @dst_port, @verify_tag]
62
+ end
63
+
64
+ def reverse_flow_id
65
+ return [:sctp, @dst_port, @src_port, @checksum]
66
+ end
67
+
68
+ # Creates SCTP packet from the payload
69
+ def self.from_bytes bytes
70
+ # Basic packet validation
71
+ Pcap.assert(bytes.length >= 12,
72
+ "Truncated SCTP header: 12 > #{bytes.length}")
73
+ Pcap.assert(bytes.length >= 16,
74
+ "Truncated SCTP packet: got only #{bytes.length} bytes")
75
+
76
+ # Read SCTP header
77
+ sport, dport, vtag, cksum = bytes.unpack('nnNN')
78
+
79
+ # Create SCTP packet and populate SCTP header fields
80
+ sctp = SCTP.new
81
+ sctp.src_port = sport
82
+ sctp.dst_port = dport
83
+ sctp.verify_tag = vtag
84
+ sctp.checksum = cksum
85
+
86
+ # Initialize the counter
87
+ length = 12
88
+
89
+ # Collect the chunks
90
+ while length < bytes.length
91
+ # Parse new chunk from the bytes
92
+ chunk = Chunk.from_bytes(bytes[length..-1])
93
+
94
+ # Get chunk size with padding
95
+ length += chunk.padded_size
96
+
97
+ # Add chunk to the list
98
+ sctp << chunk
99
+ end
100
+
101
+ # Sync the payload
102
+ sctp.sync_payload
103
+
104
+ # Return the result
105
+ return sctp
106
+ end
107
+
108
+ class ReorderError < StandardError ; end
109
+
110
+ # Reorders SCTP packets, if necessary
111
+ def self.reorder packets
112
+ # Initialize
113
+ tsns = {}
114
+ init_packets = {}
115
+ init_ack_packets = {}
116
+ reordered_packets = []
117
+
118
+ # Iterate over each packet
119
+ while not packets.empty?
120
+ # Get next packet
121
+ packet = packets.shift
122
+
123
+ # Do not reorder non-SCTP packets
124
+ if not sctp?(packet)
125
+ reordered_packets << packet
126
+ else
127
+ # Get SCTP portion
128
+ sctp = packet.payload.payload
129
+
130
+ # Sanity checks and packet filtering/preprocessing
131
+ if 0 == sctp.verify_tag and not sctp.init?
132
+ # Non-Init packet with 0 verify tag
133
+ raise ReorderError, "Non-Init packet with zero verify tag"
134
+ elsif sctp.init_or_ack? and 1 < sctp.chunk_count
135
+ # Init/InitAck packet with more with one chunk
136
+ raise ReorderError, "Init/Ack packet with more than 1 chunk"
137
+ elsif sctp.init?
138
+ # Use checksum to save reverse verify tag in the Init packet
139
+ sctp.checksum = sctp[0].init_tag
140
+
141
+ # Save orphaned Init packets until we find the Ack
142
+ init_packets[sctp.reverse_flow_id] = sctp
143
+
144
+ # Add packet for further processing
145
+ reordered_packets << packet
146
+ elsif sctp.init_ack?
147
+ # Lookup Init packet and construct it's flow it
148
+ init_packet = init_packets.delete(sctp.flow_id)
149
+
150
+ # Did we find anything?
151
+ if init_packet
152
+ # Set verify tag in the Init packet
153
+ init_packet.verify_tag = sctp[0].init_tag
154
+
155
+ # Set reverse verify tag in the InitAck packet
156
+ sctp.checksum = init_packet.verify_tag
157
+
158
+ # Re-insert INIT packet keyed by its flow id
159
+ init_packets[init_packet.flow_id] = init_packet
160
+ else
161
+ Pcap.warning("Orphaned SCTP INIT_ACK packet")
162
+ end
163
+
164
+ # Save InitAck packet
165
+ init_ack_packets[sctp.flow_id] = sctp
166
+
167
+ # Add packet for further processing
168
+ reordered_packets << packet
169
+ elsif sctp.has_data?
170
+ # SCTP packet with user data; lookup Init or InitAck packet
171
+ init_packet = init_packets[sctp.flow_id]
172
+ init_ack_packet = init_ack_packets[sctp.flow_id]
173
+
174
+ # It should belong to either one flow id or the other
175
+ if init_packet
176
+ # Set reverse verify tag from Init packet
177
+ sctp.checksum = init_packet.checksum
178
+ elsif init_ack_packet
179
+ # Set reverse flow id from InitAck packet
180
+ sctp.checksum = init_ack_packet.checksum
181
+ else
182
+ # Orphaned SCTP packet -- not very good
183
+ Pcap.warning("Orphaned SCTP DATA packet detected")
184
+ end
185
+
186
+ # If we have just one chunk we are done
187
+ if 1 == sctp.chunk_count and not tsns.member?(sctp[0].tsn)
188
+ # Save TSN
189
+ tsns[sctp[0].tsn] = sctp[0]
190
+
191
+ # sync the payload
192
+ sctp.sync_payload
193
+
194
+ # Add packet for further processing
195
+ reordered_packets << packet
196
+ else
197
+ # Split each data chunk in a separate SCTP packet
198
+ sctp.chunk_count.times do
199
+ # Get next chunk
200
+ chunk = sctp.shift
201
+
202
+ # Is it data?
203
+ if CHUNK_DATA == chunk.type
204
+ # Yes, check for duplicate TSNs
205
+ if not tsns.member?(chunk.tsn)
206
+ # Not a duplicate; create new SCTP packet
207
+ packet_new = packet.deepdup
208
+
209
+ # Create new SCTP payload
210
+ sctp_new = SCTP.new
211
+ sctp_new.src_port = sctp.src_port
212
+ sctp_new.dst_port = sctp.dst_port
213
+ sctp_new.verify_tag = sctp.verify_tag
214
+ sctp_new.checksum = sctp.checksum
215
+
216
+ # Add the chunk
217
+ sctp_new << chunk
218
+
219
+ # Add SCTP payload to the new packet
220
+ packet_new.payload.payload = sctp_new
221
+
222
+ # Save TSN
223
+ tsns[chunk.tsn] = chunk
224
+
225
+ # Sync the payload
226
+ sctp_new.sync_payload
227
+
228
+ # Add packet for further processing
229
+ reordered_packets << packet_new
230
+ else
231
+ Pcap.warning("Duplicate chunk: #{chunk.tsn}")
232
+ end
233
+ else
234
+ Pcap.warning("Non-data chunk: #{chunk.type}")
235
+ end
236
+ end
237
+ end
238
+ else
239
+ # Other SCTP packet; we are not interested at this time
240
+ end
241
+ end
242
+ end
243
+
244
+ # Return the result
245
+ return reordered_packets
246
+ end
247
+
248
+ def write io, ip
249
+ # Give a warning if packet size exceeds maximum allowed
250
+ if @payload_raw and @payload_raw.length + 20 > 65535
251
+ Pcap.warning("SCTP payload is too large")
252
+ end
253
+
254
+ # Calculate CRC32 checksum on the packet; temporarily removed due to a
255
+ # hack that uses checksum to link forward and reverse SCTP flow IDs.
256
+ #header = [@src_port, @dst_port, @verify_tag, 0].pack('nnNN')
257
+ #checksum = SCTP.crc32(header + @payload_raw)
258
+ header = [@src_port, @dst_port, @verify_tag, @checksum].pack('nnNN')
259
+
260
+ # Write SCTP header followed by each chunk
261
+ io.write(header)
262
+
263
+ # Write each chunks' data
264
+ @payload.each do |chunk|
265
+ chunk.write(io, ip)
266
+ end
267
+ end
268
+
269
+ def sync_payload
270
+ # Reset raw bytes
271
+ @payload_raw = ''
272
+
273
+ # Iterate over each chunk
274
+ @payload.each do |chunk|
275
+ @payload_raw << chunk.payload_raw
276
+ end
277
+
278
+ # Reset raw payload if it's empty
279
+ @payload_raw = nil if @payload_raw == ''
280
+ end
281
+
282
+ def self.crc32 bytes
283
+ r = 0xFFFFFFFF
284
+
285
+ bytes.each_byte do |b|
286
+ r ^= b
287
+
288
+ 8.times do
289
+ r = (r >> 1) ^ (0xEDB88320 * (r & 1))
290
+ end
291
+ end
292
+
293
+ return r ^ 0xFFFFFFFF
294
+ end
295
+
296
+ def self.sctp? packet
297
+ return packet.is_a?(Ethernet) &&
298
+ packet.payload.is_a?(IP) &&
299
+ packet.payload.payload.is_a?(SCTP)
300
+ end
301
+
302
+ def << chunk
303
+ @payload << chunk
304
+ end
305
+
306
+ def shift
307
+ return @payload.shift
308
+ end
309
+
310
+ def [] index
311
+ return @payload[index]
312
+ end
313
+
314
+ def chunk_count
315
+ return @payload.size
316
+ end
317
+
318
+ def has_data?
319
+ return @payload.any? do |chunk|
320
+ CHUNK_DATA == chunk.type
321
+ end
322
+ end
323
+
324
+ def to_s
325
+ return "sctp(%d, %d, %d, %s)" % [@src_port,
326
+ @dst_port,
327
+ @verify_tag,
328
+ @payload.join(", ")]
329
+ end
330
+
331
+ def == other
332
+ return super &&
333
+ self.src_port == other.src_port &&
334
+ self.dst_port == other.dst_port &&
335
+ self.verify_tag == other.verify_tag &&
336
+ self.payload.size == other.payload.size
337
+ end
338
+
339
+ def init?
340
+ if CHUNK_INIT == @payload[0].type
341
+ return true
342
+ else
343
+ return false
344
+ end
345
+ end
346
+
347
+ def init_ack?
348
+ if CHUNK_INIT_ACK == @payload[0].type
349
+ return true
350
+ else
351
+ return false
352
+ end
353
+ end
354
+
355
+ def init_or_ack?
356
+ if CHUNK_INIT == @payload[0].type or CHUNK_INIT_ACK == @payload[0].type
357
+ return true
358
+ else
359
+ return false
360
+ end
361
+ end
362
+ end # class SCTP
363
+
364
+ end # class Pcap
365
+ end # module Mu
366
+
367
+ require 'mu/pcap/sctp/chunk'