packetgen 1.1.0 → 1.2.0

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.
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