DIY-pcap 0.4.1 → 0.4.3

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 (35) hide show
  1. data/.gitignore +4 -4
  2. data/DIY-pcap.gemspec +17 -17
  3. data/Gemfile +3 -3
  4. data/Rakefile +1 -1
  5. data/lib/DIY-pcap.rb +2 -2
  6. data/lib/diy/command.rb +7 -1
  7. data/lib/diy/controller.rb +10 -15
  8. data/lib/diy/live.rb +9 -2
  9. data/lib/diy/parser/mu/pcap/ethernet.rb +148 -148
  10. data/lib/diy/parser/mu/pcap/header.rb +75 -75
  11. data/lib/diy/parser/mu/pcap/io_pair.rb +67 -67
  12. data/lib/diy/parser/mu/pcap/io_wrapper.rb +76 -76
  13. data/lib/diy/parser/mu/pcap/ip.rb +61 -61
  14. data/lib/diy/parser/mu/pcap/ipv4.rb +257 -257
  15. data/lib/diy/parser/mu/pcap/ipv6.rb +148 -148
  16. data/lib/diy/parser/mu/pcap/packet.rb +104 -104
  17. data/lib/diy/parser/mu/pcap/pkthdr.rb +155 -155
  18. data/lib/diy/parser/mu/pcap/reader.rb +61 -61
  19. data/lib/diy/parser/mu/pcap/reader/http_family.rb +170 -170
  20. data/lib/diy/parser/mu/pcap/sctp.rb +367 -367
  21. data/lib/diy/parser/mu/pcap/sctp/chunk.rb +123 -123
  22. data/lib/diy/parser/mu/pcap/sctp/chunk/data.rb +134 -134
  23. data/lib/diy/parser/mu/pcap/sctp/chunk/init.rb +100 -100
  24. data/lib/diy/parser/mu/pcap/sctp/chunk/init_ack.rb +68 -68
  25. data/lib/diy/parser/mu/pcap/sctp/parameter.rb +110 -110
  26. data/lib/diy/parser/mu/pcap/sctp/parameter/ip_address.rb +48 -48
  27. data/lib/diy/parser/mu/pcap/stream_packetizer.rb +72 -72
  28. data/lib/diy/parser/mu/pcap/tcp.rb +505 -505
  29. data/lib/diy/parser/mu/pcap/udp.rb +69 -69
  30. data/lib/diy/parser/mu/scenario/pcap.rb +172 -172
  31. data/lib/diy/parser/mu/scenario/pcap/fields.rb +50 -50
  32. data/lib/diy/parser/mu/scenario/pcap/rtp.rb +71 -71
  33. data/lib/diy/parser/pcap.rb +109 -109
  34. data/lib/diy/version.rb +1 -1
  35. metadata +7 -9
@@ -1,258 +1,258 @@
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
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
84
  #ipv4.payload = SCTP.from_bytes payload
85
- ipv4.payload = payload
86
- else
87
- ipv4.payload = payload
88
- end
89
- rescue ParseError => e
90
- Pcap.warning e
91
- end
92
- else
93
- ipv4.payload = payload
94
- end
95
- return ipv4
96
- end
97
-
98
- def write io
99
- if @payload.is_a? String
100
- payload = @payload
101
- else
102
- string_io = StringIO.new
103
- @payload.write string_io, self
104
- payload = string_io.string
105
- end
106
- length = 20 + payload.length
107
- if length > 65535
108
- Pcap.warning "IPv4 payload is too large"
109
- end
110
-
111
- src = HTON[@src] ||= IPAddr.new(@src).hton
112
- dst = HTON[@dst] ||= IPAddr.new(@dst).hton
113
- fields = [0x45, @dscp << 2, length, @ip_id, @offset, @ttl, @proto, 0, src, dst]
114
- header = fields.pack(FMT_HEADER)
115
- fields[7] = IP.checksum(header)
116
- header = fields.pack(FMT_HEADER)
117
- io.write header
118
- io.write payload
119
- end
120
-
121
- FMT_PSEUDO_HEADER = 'a4a4CCn'
122
- def pseudo_header payload_length
123
- src = HTON[@src] ||= IPAddr.new(@src).hton
124
- dst = HTON[@dst] ||= IPAddr.new(@dst).hton
125
- return [src, dst, 0, @proto, payload_length].pack(FMT_PSEUDO_HEADER)
126
- end
127
-
128
- def fragment?
129
- return (@offset & (IP_OFFMASK | IP_MF) != 0)
130
- end
131
-
132
- # Check that IP or TCP options are valid. Do nothing if they are valid.
133
- # Both IP and TCP options are 8-bit TLVs with an inclusive length. Both
134
- # have one byte options 0 and 1.
135
- def self.check_options options, label='IPv4'
136
- while not options.empty?
137
- type = options.slice!(0, 1)[0].ord
138
- if type == 0 or type == 1
139
- next
140
- end
141
- Pcap.assert !options.empty?,
142
- "#{label} option #{type} is missing the length field"
143
- length = options.slice!(0, 1)[0].ord
144
- Pcap.assert length >= 2,
145
- "#{label} option #{type} has invalid length: #{length}"
146
- Pcap.assert length - 2 <= options.length,
147
- "#{label} option #{type} has truncated data"
148
- options.slice! 0, length - 2
149
- end
150
- end
151
-
152
- ReassembleState = ::Struct.new :packets, :bytes, :mf, :overlap
153
-
154
- # Reassemble fragmented IPv4 packets
155
- def self.reassemble packets
156
- reassembled_packets = []
157
- flow_id_to_state = {}
158
- packets.each do |packet|
159
- if not packet.is_a?(Ethernet) or not packet.payload.is_a?(IPv4)
160
- # Ignore non-IPv4 packet
161
- elsif not packet.payload.fragment?
162
- # Ignore non-fragments
163
- else
164
- # Get reassembly state
165
- ip = packet.payload
166
- flow_id = [ip.ip_id, ip.proto, ip.src, ip.dst]
167
- state = flow_id_to_state[flow_id]
168
- if not state
169
- state = ReassembleState.new [], [], true, false
170
- flow_id_to_state[flow_id] = state
171
- end
172
- state.packets << packet
173
-
174
- # Clear the more-fragments flag if no more fragments
175
- if ip.offset & IP_MF == 0
176
- state.mf = false
177
- end
178
-
179
- # Add the bytes
180
- start = (ip.offset & IP_OFFMASK) * 8
181
- finish = start + ip.payload.length
182
- state.bytes.fill nil, start, finish - start
183
- start.upto(finish-1) do |i|
184
- if not state.bytes[i]
185
- byte = ip.payload[i - start].chr
186
- state.bytes[i] = byte
187
- elsif not state.overlap
188
- name = "%s:%s:%d" % [ip.src, ip.dst, ip.proto]
189
- Pcap.warning \
190
- "IPv4 flow #{name} contains overlapping fragements"
191
- state.overlap = true
192
- end
193
- end
194
-
195
- # We're done if we've received a fragment without the
196
- # more-fragments flag and all the bytes in the buffer have been
197
- # set.
198
- if not state.mf and state.bytes.all?
199
- # Remove fragments from reassembled_packets
200
- state.packets.each do |packet|
201
- reassembled_packets.delete_if do |reassembled_packet|
202
- packet.object_id == reassembled_packet.object_id
203
- end
204
- end
205
- # Remove state
206
- flow_id_to_state.delete flow_id
207
- # Create new packet
208
- packet = state.packets[0].deepdup
209
- ipv4 = packet.payload
210
- ipv4.offset = 0
211
- ipv4.payload = state.bytes.join
212
- # Decode
213
- begin
214
- case ipv4.proto
215
- when IPPROTO_TCP
216
- ipv4.payload = TCP.from_bytes ipv4.payload
217
- when IPPROTO_UDP
218
- ipv4.payload = UDP.from_bytes ipv4.payload
219
- when IPPROTO_SCTP
220
- ipv4.payload = SCTP.from_bytes ipv4.payload
221
- end
222
- rescue ParseError => e
223
- Pcap.warning e
224
- end
225
- end
226
- end
227
- reassembled_packets << packet
228
- end
229
- if !flow_id_to_state.empty?
230
- Pcap.warning \
231
- "#{flow_id_to_state.length} flow(s) have IPv4 fragments " \
232
- "that can't be reassembled"
233
- end
234
-
235
- return reassembled_packets
236
- end
237
-
238
- def to_s
239
- if @payload.is_a? String
240
- payload = @payload.inspect
241
- else
242
- payload = @payload.to_s
243
- end
244
- return "ipv4(%d, %s, %s, %s)" % [@proto, @src, @dst, payload]
245
- end
246
-
247
- def == other
248
- return super &&
249
- self.proto == other.proto &&
250
- self.ip_id == other.ip_id &&
251
- self.offset == other.offset &&
252
- self.ttl == other.ttl &&
253
- self.dscp == other.dscp
254
- end
255
- end
256
-
257
- end
258
- end
85
+ ipv4.payload = payload
86
+ else
87
+ ipv4.payload = payload
88
+ end
89
+ rescue ParseError => e
90
+ Pcap.warning e
91
+ end
92
+ else
93
+ ipv4.payload = payload
94
+ end
95
+ return ipv4
96
+ end
97
+
98
+ def write io
99
+ if @payload.is_a? String
100
+ payload = @payload
101
+ else
102
+ string_io = StringIO.new
103
+ @payload.write string_io, self
104
+ payload = string_io.string
105
+ end
106
+ length = 20 + payload.length
107
+ if length > 65535
108
+ Pcap.warning "IPv4 payload is too large"
109
+ end
110
+
111
+ src = HTON[@src] ||= IPAddr.new(@src).hton
112
+ dst = HTON[@dst] ||= IPAddr.new(@dst).hton
113
+ fields = [0x45, @dscp << 2, length, @ip_id, @offset, @ttl, @proto, 0, src, dst]
114
+ header = fields.pack(FMT_HEADER)
115
+ fields[7] = IP.checksum(header)
116
+ header = fields.pack(FMT_HEADER)
117
+ io.write header
118
+ io.write payload
119
+ end
120
+
121
+ FMT_PSEUDO_HEADER = 'a4a4CCn'
122
+ def pseudo_header payload_length
123
+ src = HTON[@src] ||= IPAddr.new(@src).hton
124
+ dst = HTON[@dst] ||= IPAddr.new(@dst).hton
125
+ return [src, dst, 0, @proto, payload_length].pack(FMT_PSEUDO_HEADER)
126
+ end
127
+
128
+ def fragment?
129
+ return (@offset & (IP_OFFMASK | IP_MF) != 0)
130
+ end
131
+
132
+ # Check that IP or TCP options are valid. Do nothing if they are valid.
133
+ # Both IP and TCP options are 8-bit TLVs with an inclusive length. Both
134
+ # have one byte options 0 and 1.
135
+ def self.check_options options, label='IPv4'
136
+ while not options.empty?
137
+ type = options.slice!(0, 1)[0].ord
138
+ if type == 0 or type == 1
139
+ next
140
+ end
141
+ Pcap.assert !options.empty?,
142
+ "#{label} option #{type} is missing the length field"
143
+ length = options.slice!(0, 1)[0].ord
144
+ Pcap.assert length >= 2,
145
+ "#{label} option #{type} has invalid length: #{length}"
146
+ Pcap.assert length - 2 <= options.length,
147
+ "#{label} option #{type} has truncated data"
148
+ options.slice! 0, length - 2
149
+ end
150
+ end
151
+
152
+ ReassembleState = ::Struct.new :packets, :bytes, :mf, :overlap
153
+
154
+ # Reassemble fragmented IPv4 packets
155
+ def self.reassemble packets
156
+ reassembled_packets = []
157
+ flow_id_to_state = {}
158
+ packets.each do |packet|
159
+ if not packet.is_a?(Ethernet) or not packet.payload.is_a?(IPv4)
160
+ # Ignore non-IPv4 packet
161
+ elsif not packet.payload.fragment?
162
+ # Ignore non-fragments
163
+ else
164
+ # Get reassembly state
165
+ ip = packet.payload
166
+ flow_id = [ip.ip_id, ip.proto, ip.src, ip.dst]
167
+ state = flow_id_to_state[flow_id]
168
+ if not state
169
+ state = ReassembleState.new [], [], true, false
170
+ flow_id_to_state[flow_id] = state
171
+ end
172
+ state.packets << packet
173
+
174
+ # Clear the more-fragments flag if no more fragments
175
+ if ip.offset & IP_MF == 0
176
+ state.mf = false
177
+ end
178
+
179
+ # Add the bytes
180
+ start = (ip.offset & IP_OFFMASK) * 8
181
+ finish = start + ip.payload.length
182
+ state.bytes.fill nil, start, finish - start
183
+ start.upto(finish-1) do |i|
184
+ if not state.bytes[i]
185
+ byte = ip.payload[i - start].chr
186
+ state.bytes[i] = byte
187
+ elsif not state.overlap
188
+ name = "%s:%s:%d" % [ip.src, ip.dst, ip.proto]
189
+ Pcap.warning \
190
+ "IPv4 flow #{name} contains overlapping fragements"
191
+ state.overlap = true
192
+ end
193
+ end
194
+
195
+ # We're done if we've received a fragment without the
196
+ # more-fragments flag and all the bytes in the buffer have been
197
+ # set.
198
+ if not state.mf and state.bytes.all?
199
+ # Remove fragments from reassembled_packets
200
+ state.packets.each do |packet|
201
+ reassembled_packets.delete_if do |reassembled_packet|
202
+ packet.object_id == reassembled_packet.object_id
203
+ end
204
+ end
205
+ # Remove state
206
+ flow_id_to_state.delete flow_id
207
+ # Create new packet
208
+ packet = state.packets[0].deepdup
209
+ ipv4 = packet.payload
210
+ ipv4.offset = 0
211
+ ipv4.payload = state.bytes.join
212
+ # Decode
213
+ begin
214
+ case ipv4.proto
215
+ when IPPROTO_TCP
216
+ ipv4.payload = TCP.from_bytes ipv4.payload
217
+ when IPPROTO_UDP
218
+ ipv4.payload = UDP.from_bytes ipv4.payload
219
+ when IPPROTO_SCTP
220
+ ipv4.payload = SCTP.from_bytes ipv4.payload
221
+ end
222
+ rescue ParseError => e
223
+ Pcap.warning e
224
+ end
225
+ end
226
+ end
227
+ reassembled_packets << packet
228
+ end
229
+ if !flow_id_to_state.empty?
230
+ Pcap.warning \
231
+ "#{flow_id_to_state.length} flow(s) have IPv4 fragments " \
232
+ "that can't be reassembled"
233
+ end
234
+
235
+ return reassembled_packets
236
+ end
237
+
238
+ def to_s
239
+ if @payload.is_a? String
240
+ payload = @payload.inspect
241
+ else
242
+ payload = @payload.to_s
243
+ end
244
+ return "ipv4(%d, %s, %s, %s)" % [@proto, @src, @dst, payload]
245
+ end
246
+
247
+ def == other
248
+ return super &&
249
+ self.proto == other.proto &&
250
+ self.ip_id == other.ip_id &&
251
+ self.offset == other.offset &&
252
+ self.ttl == other.ttl &&
253
+ self.dscp == other.dscp
254
+ end
255
+ end
256
+
257
+ end
258
+ end