packetgen 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 21f7a146ddb2ba2b7e8b86933360a97d95344fef
4
- data.tar.gz: e26c103bc3ef15ca5d7064163cd747716215a71b
3
+ metadata.gz: dfd9d08f263a51dd186442e4b8f8395be3a964a2
4
+ data.tar.gz: f68b293509bf734b7aa1760ba976b437ce3fba4c
5
5
  SHA512:
6
- metadata.gz: 304723a5e11714b3092606fd7f7529fd1a4d6ec0b427e364d3603b63cca20fbfc410a996d80a1a3cfb58106e855dcf5fbc76dd957b589eee6b97c1fa70379312
7
- data.tar.gz: d95e4b3fa118e33ae9296626fd2855d6d3eaad9a4d2e0fd4605cfca5bdfd193b4397fd4411164a018dfbbf37868a35026134acf01cd2fdb52729cb90c05bc7fd
6
+ metadata.gz: 024c502d411c3ae81f7272bb5476964421738e3357d6a5e8b6308600408a499805f0015497d31ca792278857dc23609f2352c26a23011ef17c6edc40fe44e726
7
+ data.tar.gz: 968a82ca1231067f5bc68fefae05c80842ff4b058fb17ff9c269320662e7eb6acb5db4c54435be479294857cadc41fee0d7fcb7d795437b982a30ae2fba17bed
@@ -4,11 +4,15 @@ rvm:
4
4
  - 2.1
5
5
  - 2.2
6
6
  - 2.3.3
7
+ - 2.4.0
7
8
 
8
9
  install:
9
10
  - sudo apt-get update -qq
10
11
  - sudo apt-get install libpcap-dev -qq
11
12
  - bundler install --path vendor/bundle --jobs=3 --retry=3
13
+ before_script:
14
+ - openssl version
15
+ - gem list openssl
12
16
  script:
13
17
  - bundler exec rake
14
18
  - rvmsudo bundle exec rake spec:sudo
@@ -42,10 +42,7 @@ module PacketGen
42
42
  # @return [Array<Class>]
43
43
  def self.all
44
44
  return @header_classes if @header_classes
45
-
46
- @builtin ||= constants.map { |sym| const_get sym }.
47
- select { |klass| klass < Struct && klass < HeaderMethods }
48
- @header_classes = @builtin + @added_header_classes.values
45
+ @header_classes = @added_header_classes.values
49
46
  end
50
47
 
51
48
  # Add a foreign header class to known header classes. This is
@@ -95,3 +92,4 @@ require_relative 'header/ipv6'
95
92
  require_relative 'header/icmpv6'
96
93
  require_relative 'header/udp'
97
94
  require_relative 'header/tcp'
95
+ require_relative 'header/esp'
@@ -184,6 +184,8 @@ module PacketGen
184
184
  alias :dst_ip= :tpa=
185
185
  end
186
186
 
187
+ self.add_class ARP
188
+
187
189
  Eth.bind_header ARP, ethertype: 0x806
188
190
  end
189
191
  end
@@ -0,0 +1,474 @@
1
+ module PacketGen
2
+ module Header
3
+
4
+ # Error about enciphering/deciphering was encountered
5
+ class CipherError < Error;end
6
+
7
+ # A ESP header consists of:
8
+ # * a Security Parameters Index (#{spi}, {Int32} type),
9
+ # * a Sequence Number ({#sn}, +Int32+ type),
10
+ # * a {#body} (variable length),
11
+ # * an optional TFC padding ({#tfc}, variable length),
12
+ # * an optional {#padding} (to align ESP on 32-bit boundary, variable length),
13
+ # * a {#pad_length} ({Int8}),
14
+ # * a Next header field ({#next}, +Int8+),
15
+ # * and an optional Integrity Check Value ({#icv}, variable length).
16
+ #
17
+ # == Create an ESP header
18
+ # # standalone
19
+ # esp = PacketGen::Header::ESP.new
20
+ # # in a packet
21
+ # pkt = PacketGen.gen('IP').add('ESP')
22
+ # # access to ESP header
23
+ # pkt.esp # => PacketGen::Header::ESP
24
+ #
25
+ # == Examples
26
+ # === Create an enciphered UDP packet (ESP transport mode), using CBC mode
27
+ # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
28
+ # add('ESP', spi: 0xff456e01, sn: 12345678).
29
+ # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
30
+ # cipher = OpenSSL::Cipher.new('aes-128-cbc')
31
+ # cipher.encrypt
32
+ # cipher.key = 16bytes_key
33
+ # iv = 16bytes_iv
34
+ # esp.esp.encrypt! cipher, iv
35
+ #
36
+ # === Create a ESP packet tunneling a UDP one, using GCM combined mode
37
+ # # create inner UDP packet
38
+ # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
39
+ # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
40
+ #
41
+ # # create outer ESP packet
42
+ # esp = PacketGen.gen('IP', src '198.76.54.32', dst: '1.2.3.4').add('ESP')
43
+ # esp.esp.spi = 0x87654321
44
+ # esp.esp.sn = 0x123
45
+ # esp.esp.icv_length = 16
46
+ # # encapsulate ICMP packet in ESP one
47
+ # esp.encapsulate icmp
48
+ #
49
+ # # encrypt ESP payload
50
+ # cipher = OpenSSL::Cipher.new('aes-128-gcm')
51
+ # cipher.encrypt
52
+ # cipher.key = 16bytes_key
53
+ # iv = 8bytes_iv
54
+ # esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt
55
+ #
56
+ # === Decrypt a ESP packet using CBC mode and HMAC-SHA-256
57
+ # cipher = OpenSSL::Cipher.new('aes-128-cbc')
58
+ # cipher.decrypt
59
+ # cipher.key = 16bytes_key
60
+ #
61
+ # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
62
+ #
63
+ # pkt.esp.decrypt! cipher, intmode: hmac # => true if ICV check OK
64
+ # @author Sylvain Daubert
65
+ class ESP < Struct.new(:spi, :sn, :body, :tfc, :padding,
66
+ :pad_length, :next, :icv)
67
+ include StructFu
68
+ include HeaderMethods
69
+ extend HeaderClassMethods
70
+
71
+ # IP protocol number for ESP
72
+ IP_PROTOCOL = 50
73
+
74
+ # Well-known UDP port for ESP
75
+ UDP_PORT = 4500
76
+
77
+ # ICV (Integrity Check Value) length
78
+ # @return [Integer]
79
+ attr_accessor :icv_length
80
+
81
+ # @param [Hash] options
82
+ # @option options [Integer] :icv_length ICV length
83
+ # @option options [Integer] :spi Security Parameters Index
84
+ # @option options [Integer] :sn Sequence Number
85
+ # @option options [::String] :body ESP payload data
86
+ # @option options [::String] :tfc Traffic Flow Confidentiality, random padding
87
+ # up to MTU
88
+ # @option options [::String] :padding ESP padding to align ESP on 32-bit
89
+ # boundary
90
+ # @option options [Integer] :pad_length padding length
91
+ # @option options [Integer] :next Next Header field
92
+ # @option options [::String] :icv Integrity Check Value
93
+ def initialize(options={})
94
+ @icv_length = options[:icv_length] || 0
95
+ super Int32.new(options[:spi]),
96
+ Int32.new(options[:sn]),
97
+ StructFu::String.new.read(options[:body]),
98
+ StructFu::String.new.read(options[:tfc]),
99
+ StructFu::String.new.read(options[:padding]),
100
+ Int8.new(options[:pad_length]),
101
+ Int8.new(options[:next]),
102
+ StructFu::String.new.read(options[:icv])
103
+ end
104
+
105
+ # Read a ESP packet from string.
106
+ #
107
+ # {#padding} and {#tfc} are not set as they are enciphered (impossible
108
+ # to guess their respective size). {#pad_length} and {#next} are also
109
+ # enciphered.
110
+ # @param [String] str
111
+ # @return [self]
112
+ def read(str)
113
+ return self if str.nil?
114
+ raise ParseError, 'string too short for ESP' if str.size < self.sz
115
+ force_binary str
116
+ self[:spi].read str[0, 4]
117
+ self[:sn].read str[4, 4]
118
+ self[:body].read str[8...-@icv_length-2]
119
+ self[:tfc].read ''
120
+ self[:padding].read ''
121
+ self[:pad_length].read str[-@icv_length-2, 1]
122
+ self[:next].read str[-@icv_length-1, 1]
123
+ self[:icv].read str[-@icv_length, @icv_length] if @icv_length
124
+ self
125
+ end
126
+
127
+ # Getter for SPI attribute
128
+ # @return [Integer]
129
+ def spi
130
+ self[:spi].to_i
131
+ end
132
+
133
+ # Setter for SPI attribute
134
+ # @param [Integer] val
135
+ # @return [Integer]
136
+ def spi=(val)
137
+ typecast val
138
+ end
139
+
140
+ # Getter for SN attribute
141
+ # @return [Integer]
142
+ def sn
143
+ self[:sn].to_i
144
+ end
145
+
146
+ # Setter for SN attribute
147
+ # @param [Integer] val
148
+ # @return [Integer]
149
+ def sn=(val)
150
+ typecast val
151
+ end
152
+
153
+ # Getter for +pad_length+ attribute
154
+ # @return [Integer]
155
+ def pad_length
156
+ self[:pad_length].to_i
157
+ end
158
+
159
+ # Setter for +pad_length+ attribute
160
+ # @param [Integer] val
161
+ # @return [Integer]
162
+ def pad_length=(val)
163
+ typecast val
164
+ end
165
+
166
+ # Getter for +next+ attribute
167
+ # @return [Integer]
168
+ def next
169
+ self[:next].to_i
170
+ end
171
+
172
+ # Setter for +next+ attribute
173
+ # @param [Integer] val
174
+ # @return [Integer]
175
+ def next=(val)
176
+ typecast val
177
+ end
178
+
179
+ # Encrypt in-place ESP payload and trailer.
180
+ #
181
+ # This method removes all data from +tfc+ and +padding+ fields, as their
182
+ # enciphered values are concatenated into +body+.
183
+ #
184
+ # It also removes headers under ESP from packet, as they are enciphered in
185
+ # ESP body, and then are no more accessible.
186
+ # @param [OpenSSL::Cipher] cipher keyed cipher.
187
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
188
+ # cipher to add integrity, use +:intmode+ option.
189
+ # @param [String] iv full IV for encryption
190
+ # * CTR and GCM modes: +iv+ is 8-bytes long.
191
+ # @param [Hash] options
192
+ # @option options [String] :salt salt value for CTR and GCM modes
193
+ # @option options [Boolean] :tfc
194
+ # @option options [Fixnum] :tfc_size ESP body size used for TFC
195
+ # (default 1444, max size for a tunneled IPv4/ESP packet).
196
+ # This is the maximum size for ESP packet (without IP header
197
+ # nor Eth one).
198
+ # @option options [Fixnum] :esn 32 high-orber bits of ESN
199
+ # @option options [Fixnum] :pad_length set a padding length
200
+ # @option options [String] :padding set a padding. No check with
201
+ # +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
202
+ # length is shortened to correct padding length
203
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
204
+ # confidentiality-only cipher. Only HMAC are supported.
205
+ # @return [self]
206
+ def encrypt!(cipher, iv, options={})
207
+ opt = { salt: '', tfc_size: 1444 }.merge(options)
208
+
209
+ set_crypto cipher, opt[:intmode]
210
+
211
+ real_iv = force_binary(opt[:salt]) + force_binary(iv)
212
+ real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
213
+ cipher.iv = real_iv
214
+
215
+ authenticate_esp_header_if_needed options, iv
216
+
217
+ case confidentiality_mode
218
+ when 'cbc'
219
+ cipher_len = self.body.sz + 2
220
+ self.pad_length = (16 - (cipher_len % 16)) % 16
221
+ else
222
+ mod4 = to_s.size % 4
223
+ self.pad_length = 4 - mod4 if mod4 > 0
224
+ end
225
+
226
+ if opt[:pad_length]
227
+ self.pad_length = opt[:pad_length]
228
+ padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack("C*"))
229
+ self[:padding].read padding
230
+ else
231
+ padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack("C*"))
232
+ self[:padding].read padding[0...self.pad_length]
233
+ end
234
+
235
+ tfc = ''
236
+ if opt[:tfc]
237
+ tfc_size = opt[:tfc_size] - body.sz
238
+ if tfc_size > 0
239
+ case confidentiality_mode
240
+ when 'cbc'
241
+ tfc_size = (tfc_size / 16) * 16
242
+ else
243
+ tfc_size = (tfc_size / 4) * 4
244
+ end
245
+ tfc = force_binary("\0" * tfc_size)
246
+ end
247
+ end
248
+
249
+ msg = self.body.to_s + tfc
250
+ msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
251
+ enc_msg = encipher(msg)
252
+ # as padding is used to pad for CBC mode, this is unused
253
+ cipher.final
254
+
255
+ self[:body] = StructFu::String.new(iv) << enc_msg[0..-3]
256
+ self[:pad_length].read enc_msg[-2]
257
+ self[:next].read enc_msg[-1]
258
+
259
+ # reset padding field as it has no sense in encrypted ESP
260
+ self[:padding].read ''
261
+
262
+ set_esp_icv_if_needed
263
+
264
+ # Remove enciphered headers from packet
265
+ id = header_id(self)
266
+ if id < packet.headers.size - 1
267
+ (packet.headers.size-1).downto(id+1) do |index|
268
+ packet.headers.delete_at index
269
+ end
270
+ end
271
+
272
+ self
273
+ end
274
+
275
+ # Decrypt in-place ESP payload and trailer.
276
+ # @param [OpenSSL::Cipher] cipher keyed cipher
277
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
278
+ # cipher to add integrity, use +:intmode+ option.
279
+ # @param [Hash] options
280
+ # @option options [Boolean] :parse parse deciphered payload to retrieve
281
+ # headers (default: +true+)
282
+ # @option options [Fixnum] :icv_length ICV length for captured packets,
283
+ # or read from PCapNG files
284
+ # @option options [String] :salt salt value for CTR and GCM modes
285
+ # @option options [Fixnum] :esn 32 high-orber bits of ESN
286
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
287
+ # confidentiality-only cipher. Only HMAC are supported.
288
+ # @return [Boolean] +true+ if ESP packet is authenticated
289
+ def decrypt!(cipher, options={})
290
+ opt = { :salt => '', parse: true }.merge(options)
291
+
292
+ set_crypto cipher, opt[:intmode]
293
+
294
+ case confidentiality_mode
295
+ when 'gcm'
296
+ iv = self.body.slice!(0, 8)
297
+ real_iv = opt[:salt] + iv
298
+ when 'cbc'
299
+ cipher.padding = 0
300
+ real_iv = iv = self.body.slice!(0, 16)
301
+ when 'ctr'
302
+ iv = self.body.slice!(0, 8)
303
+ real_iv = opt[:salt] + iv + [1].pack('N')
304
+ else
305
+ real_iv = iv = self.body.slice!(0, 16)
306
+ end
307
+ cipher.iv = real_iv
308
+
309
+ if authenticated? and (@icv_length == 0 or opt[:icv_length])
310
+ raise ParseError, 'unknown ICV size' unless opt[:icv_length]
311
+ @icv_length = opt[:icv_length].to_i
312
+ # reread ESP to handle new ICV size
313
+ msg = self.body.to_s + self[:pad_length].to_s
314
+ msg += self[:next].to_s
315
+ self[:icv].read msg.slice!(-@icv_length, @icv_length)
316
+ self[:body].read msg[0..-3]
317
+ self[:pad_length].read msg[-2]
318
+ self[:next].read msg[-1]
319
+ end
320
+
321
+ authenticate_esp_header_if_needed options, iv, self[:icv]
322
+ private_decrypt cipher, opt
323
+ end
324
+
325
+ private
326
+
327
+ def set_crypto(conf, intg)
328
+ @conf, @intg = conf, intg
329
+ end
330
+
331
+ def confidentiality_mode
332
+ mode = @conf.name.match(/-([^-]*)$/)[1]
333
+ raise CipherError, 'unknown cipher mode' if mode.nil?
334
+ mode.downcase
335
+ end
336
+
337
+ def authenticated?
338
+ @conf.authenticated? or !!@intg
339
+ end
340
+
341
+ def authenticate!
342
+ @conf.final
343
+ if @intg
344
+ @intg.update @esn.to_s if @esn
345
+ @intg.digest[0, @icv_length] == @icv
346
+ else
347
+ true
348
+ end
349
+ rescue OpenSSL::Cipher::CipherError
350
+ false
351
+ end
352
+
353
+ def encipher(data)
354
+ enciphered_data = @conf.update(data)
355
+ @intg.update(enciphered_data) if @intg
356
+ enciphered_data
357
+ end
358
+
359
+ def decipher(data)
360
+ @intg.update(data) if @intg
361
+ @conf.update(data)
362
+ end
363
+
364
+ def get_auth_data(opt)
365
+ ad = self[:spi].to_s
366
+ if opt[:esn]
367
+ @esn = StructFu::Int32.new(opt[:esn])
368
+ ad << @esn.to_s if @conf.authenticated?
369
+ end
370
+ ad << self[:sn].to_s
371
+ end
372
+
373
+ def authenticate_esp_header_if_needed(opt, iv, icv=nil)
374
+ if @conf.authenticated?
375
+ @conf.auth_tag = icv if icv
376
+ @conf.auth_data = get_auth_data(opt)
377
+ elsif @intg
378
+ @intg.reset
379
+ @intg.update get_auth_data(opt)
380
+ @intg.update iv
381
+ @icv = icv
382
+ else
383
+ @icv = nil
384
+ end
385
+ end
386
+
387
+ def set_esp_icv_if_needed
388
+ return unless authenticated?
389
+ if @conf.authenticated?
390
+ self[:icv].read @conf.auth_tag[0, @icv_length]
391
+ else
392
+ self[:icv].read @intg.digest[0, @icv_length]
393
+ end
394
+ end
395
+
396
+ def private_decrypt(cipher, options)
397
+ # decrypt
398
+ msg = self.body.to_s
399
+ msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
400
+ plain_msg = decipher(msg)
401
+
402
+ # check authentication tag
403
+ if authenticated?
404
+ return false unless authenticate!
405
+ end
406
+
407
+ # Set ESP fields
408
+ self[:body].read plain_msg[0..-3]
409
+ self[:pad_length].read plain_msg[-2]
410
+ self[:next].read plain_msg[-1]
411
+
412
+ # Set padding
413
+ if self.pad_length > 0
414
+ len = self.pad_length
415
+ self[:padding].read self.body.slice!(-len, len)
416
+ end
417
+
418
+ # Set TFC padding
419
+ encap_length = 0
420
+ pkt = nil
421
+ case self.next
422
+ when 4 # IPv4
423
+ pkt = Packet.parse(body, first_header: 'IP')
424
+ encap_length = pkt.ip.length
425
+ when 41 # IPv6
426
+ pkt = Packet.parse(body, first_header: 'IPv6')
427
+ encap_length = pkt.ipv6.length + pkt.ipv6.sz
428
+ when ICMP::IP_PROTOCOL
429
+ pkt = Packet.parse(body, first_header: 'ICMP')
430
+ # no size field. cannot recover TFC padding
431
+ encap_length = body.sz
432
+ when UDP::IP_PROTOCOL
433
+ pkt = Packet.parse(body, first_header: 'UDP')
434
+ encap_length = pkt.udp.length
435
+ when TCP::IP_PROTOCOL
436
+ # No length in TCP header, so TFC may not be used.
437
+ # Or underlayer protocol should have a size information...
438
+ pkt = Packet.parse(body, first_header: 'TCP')
439
+ encap_length = pkt.sz
440
+ when ICMPv6::IP_PROTOCOL
441
+ pkt = Packet.parse(body, first_header: 'ICMPv6')
442
+ # no size field. cannot recover TFC padding
443
+ encap_length = body.sz
444
+ else
445
+ # Unmanaged encapsulated protocol
446
+ encap_length = body.sz
447
+ end
448
+
449
+ if encap_length < body.sz
450
+ tfc_len = body.sz - encap_length
451
+ self[:tfc].read self.body.slice!(encap_length, tfc_len)
452
+ end
453
+
454
+ if options[:parse]
455
+ packet.encapsulate pkt unless pkt.nil?
456
+ end
457
+
458
+ true
459
+ end
460
+ end
461
+
462
+ self.add_class ESP
463
+
464
+ IP.bind_header ESP, protocol: ESP::IP_PROTOCOL
465
+ IPv6.bind_header ESP, next: ESP::IP_PROTOCOL
466
+ UDP.bind_header ESP, dport: ESP::UDP_PORT, sport: ESP::UDP_PORT
467
+ ESP.bind_header IP, next: 4
468
+ ESP.bind_header IPv6, next: 41
469
+ ESP.bind_header TCP, next: TCP::IP_PROTOCOL
470
+ ESP.bind_header UDP, next: TCP::IP_PROTOCOL
471
+ ESP.bind_header ICMP, next: ICMP::IP_PROTOCOL
472
+ ESP.bind_header ICMPv6, next: ICMPv6::IP_PROTOCOL
473
+ end
474
+ end
@@ -176,5 +176,7 @@ module PacketGen
176
176
  pcap.inject self.to_s
177
177
  end
178
178
  end
179
+
180
+ self.add_class Eth
179
181
  end
180
182
  end
@@ -8,22 +8,26 @@ module PacketGen
8
8
 
9
9
  module HeaderClassMethods
10
10
 
11
+ # @api private
11
12
  # Simple class to handle header association
12
13
  Binding = Struct.new(:key, :value)
13
14
 
14
15
  # Bind a upper header to current class
15
16
  # @param [Class] header_klass header class to bind to current class
16
- # @param [Hash] args current class field and its value when +header_klass+
17
+ # @param [Hash] args current class fields and their value when +header_klass+
17
18
  # is embedded in current class
18
19
  # @return [void]
19
20
  def bind_header(header_klass, args={})
20
21
  @known_headers ||= {}
21
- key = args.keys.first
22
- @known_headers[header_klass] = Binding.new(key, args[key])
22
+ @known_headers[header_klass] ||= []
23
+ args.each do |key, value|
24
+ @known_headers[header_klass] << Binding.new(key, value)
25
+ end
23
26
  end
24
27
 
28
+ # @api private
25
29
  # Get knwon headers
26
- # @return [Hash] keys: header classes, values: struct with methods #key and #value
30
+ # @return [Hash] keys: header classes, values: array of {Binding}
27
31
  def known_headers
28
32
  @known_headers ||= {}
29
33
  end
@@ -115,6 +115,8 @@ module PacketGen
115
115
  end
116
116
  end
117
117
 
118
+ self.add_class ICMP
119
+
118
120
  IP.bind_header ICMP, protocol: ICMP::IP_PROTOCOL
119
121
  end
120
122
  end
@@ -51,6 +51,8 @@ module PacketGen
51
51
  end
52
52
  end
53
53
 
54
+ self.add_class ICMPv6
55
+
54
56
  IPv6.bind_header ICMPv6, next: ICMPv6::IP_PROTOCOL
55
57
  end
56
58
  end
@@ -380,6 +380,8 @@ module PacketGen
380
380
  end
381
381
  end
382
382
 
383
+ self.add_class IP
384
+
383
385
  Eth.bind_header IP, ethertype: 0x800
384
386
  IP.bind_header IP, protocol: 4
385
387
  end
@@ -305,6 +305,8 @@ module PacketGen
305
305
  end
306
306
  end
307
307
 
308
+ self.add_class IPv6
309
+
308
310
  Eth.bind_header IPv6, ethertype: 0x86DD
309
311
  IP.bind_header IPv6, protocol: 41 # 6to4
310
312
  end
@@ -96,7 +96,7 @@ module PacketGen
96
96
  end
97
97
 
98
98
  # @!attribute data_offset
99
- # @return [Integer] 4-bit data offsetfrom {#u16}
99
+ # @return [Integer] 4-bit data offset from {#u16}
100
100
  # @!attribute reserved
101
101
  # @return [Integer] 3-bit reserved from {#u16}
102
102
  # @!attribute flags
@@ -293,6 +293,8 @@ module PacketGen
293
293
  end
294
294
  end
295
295
 
296
+ self.add_class TCP
297
+
296
298
  IP.bind_header TCP, protocol: TCP::IP_PROTOCOL
297
299
  IPv6.bind_header TCP, next: TCP::IP_PROTOCOL
298
300
  end
@@ -151,6 +151,8 @@ module PacketGen
151
151
  end
152
152
  end
153
153
 
154
+ self.add_class UDP
155
+
154
156
  IP.bind_header UDP, protocol: UDP::IP_PROTOCOL
155
157
  IPv6.bind_header UDP, next: UDP::IP_PROTOCOL
156
158
  end
@@ -49,7 +49,7 @@ module PacketGen
49
49
  elsif value.respond_to? :to_human
50
50
  value.to_human
51
51
  else
52
- value.to_s
52
+ value.to_s.inspect
53
53
  end
54
54
  str << INSPECT_FMT_ATTR % [value.class.to_s.sub(/.*::/, ''), attr, val]
55
55
  end
@@ -75,10 +75,13 @@ module PacketGen
75
75
  # First header is found when:
76
76
  # * for one known header,
77
77
  # * it exists a known binding with a upper header
78
- hklass.known_headers.each do |nh, binding|
79
- if hdr.send(binding.key) == binding.value
80
- first_header = hklass.to_s.gsub(/.*::/, '')
81
- break
78
+ hklass.known_headers.each do |nh, bindings|
79
+ bindings.each do |binding|
80
+ if hdr.send(binding.key) == binding.value
81
+ first_header = hklass.to_s.gsub(/.*::/, '')
82
+ break
83
+ end
84
+ break unless first_header.nil?
82
85
  end
83
86
  end
84
87
  break unless first_header.nil?
@@ -95,13 +98,16 @@ module PacketGen
95
98
  decode_packet_bottom_up = true
96
99
  while decode_packet_bottom_up do
97
100
  last_known_hdr = pkt.headers.last
98
- last_known_hdr.class.known_headers.each do |nh, binding|
99
- if last_known_hdr.send(binding.key) == binding.value
100
- str = last_known_hdr.body
101
- pkt.add nh.to_s.gsub(/.*::/, '')
102
- pkt.headers.last.read str
103
- break
101
+ last_known_hdr.class.known_headers.each do |nh, bindings|
102
+ bindings.each do |binding|
103
+ if last_known_hdr.send(binding.key) == binding.value
104
+ str = last_known_hdr.body
105
+ pkt.add nh.to_s.gsub(/.*::/, '')
106
+ pkt.headers.last.read str
107
+ break
108
+ end
104
109
  end
110
+ break unless last_known_hdr == pkt.headers.last
105
111
  end
106
112
  decode_packet_bottom_up = (pkt.headers.last != last_known_hdr)
107
113
  end
@@ -285,6 +291,12 @@ module PacketGen
285
291
 
286
292
  private
287
293
 
294
+ # Dup +@headers+ instance variable. Internally used by +#dup+ and +#clone+
295
+ # @return [void]
296
+ def initialize_copy(other)
297
+ @headers = @headers.dup
298
+ end
299
+
288
300
  # @overload header(klass, layer=1)
289
301
  # @param [Class] klass
290
302
  # @param [Integer] layer
@@ -327,15 +339,15 @@ module PacketGen
327
339
  protocol = header.protocol_name
328
340
  prev_header = previous_header || @headers.last
329
341
  if prev_header
330
- binding = prev_header.class.known_headers[header.class]
331
- if binding.nil?
342
+ bindings = prev_header.class.known_headers[header.class]
343
+ if bindings.nil? or bindings.empty?
332
344
  msg = "#{prev_header.class} knowns no layer association with #{protocol}. "
333
345
  msg << "Try #{prev_header.class}.bind_layer(PacketGen::Header::#{protocol}, "
334
346
  msg << "#{prev_header.protocol_name.downcase}_proto_field: "
335
347
  msg << "value_for_#{protocol.downcase})"
336
348
  raise ArgumentError, msg
337
349
  end
338
- prev_header[binding.key].read binding.value
350
+ prev_header[bindings.first.key].read bindings.first.value
339
351
  prev_header.body = header
340
352
  end
341
353
  header.packet = self
@@ -8,5 +8,5 @@
8
8
  # @author Sylvain Daubert
9
9
  module PacketGen
10
10
  # PacketGen version
11
- VERSION = "1.1.0"
11
+ VERSION = "1.2.0"
12
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: packetgen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sylvain Daubert
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-28 00:00:00.000000000 Z
11
+ date: 2017-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pcaprub
@@ -111,6 +111,7 @@ files:
111
111
  - lib/packetgen/capture.rb
112
112
  - lib/packetgen/header.rb
113
113
  - lib/packetgen/header/arp.rb
114
+ - lib/packetgen/header/esp.rb
114
115
  - lib/packetgen/header/eth.rb
115
116
  - lib/packetgen/header/header_class_methods.rb
116
117
  - lib/packetgen/header/header_methods.rb
@@ -154,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
155
  version: '0'
155
156
  requirements: []
156
157
  rubyforge_project:
157
- rubygems_version: 2.5.2
158
+ rubygems_version: 2.6.8
158
159
  signing_key:
159
160
  specification_version: 4
160
161
  summary: Network packet generator and analyzor