DIY-pcap 0.4.1 → 0.4.3

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