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 +4 -4
- data/.travis.yml +4 -0
- data/lib/packetgen/header.rb +2 -4
- data/lib/packetgen/header/arp.rb +2 -0
- data/lib/packetgen/header/esp.rb +474 -0
- data/lib/packetgen/header/eth.rb +2 -0
- data/lib/packetgen/header/header_class_methods.rb +8 -4
- data/lib/packetgen/header/icmp.rb +2 -0
- data/lib/packetgen/header/icmpv6.rb +2 -0
- data/lib/packetgen/header/ip.rb +2 -0
- data/lib/packetgen/header/ipv6.rb +2 -0
- data/lib/packetgen/header/tcp.rb +3 -1
- data/lib/packetgen/header/udp.rb +2 -0
- data/lib/packetgen/inspect.rb +1 -1
- data/lib/packetgen/packet.rb +25 -13
- data/lib/packetgen/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfd9d08f263a51dd186442e4b8f8395be3a964a2
|
4
|
+
data.tar.gz: f68b293509bf734b7aa1760ba976b437ce3fba4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 024c502d411c3ae81f7272bb5476964421738e3357d6a5e8b6308600408a499805f0015497d31ca792278857dc23609f2352c26a23011ef17c6edc40fe44e726
|
7
|
+
data.tar.gz: 968a82ca1231067f5bc68fefae05c80842ff4b058fb17ff9c269320662e7eb6acb5db4c54435be479294857cadc41fee0d7fcb7d795437b982a30ae2fba17bed
|
data/.travis.yml
CHANGED
@@ -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
|
data/lib/packetgen/header.rb
CHANGED
@@ -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'
|
data/lib/packetgen/header/arp.rb
CHANGED
@@ -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
|
data/lib/packetgen/header/eth.rb
CHANGED
@@ -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
|
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
|
-
|
22
|
-
|
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:
|
30
|
+
# @return [Hash] keys: header classes, values: array of {Binding}
|
27
31
|
def known_headers
|
28
32
|
@known_headers ||= {}
|
29
33
|
end
|
data/lib/packetgen/header/ip.rb
CHANGED
data/lib/packetgen/header/tcp.rb
CHANGED
@@ -96,7 +96,7 @@ module PacketGen
|
|
96
96
|
end
|
97
97
|
|
98
98
|
# @!attribute data_offset
|
99
|
-
# @return [Integer] 4-bit data
|
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
|
data/lib/packetgen/header/udp.rb
CHANGED
data/lib/packetgen/inspect.rb
CHANGED
data/lib/packetgen/packet.rb
CHANGED
@@ -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,
|
79
|
-
|
80
|
-
|
81
|
-
|
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,
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
331
|
-
if
|
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[
|
350
|
+
prev_header[bindings.first.key].read bindings.first.value
|
339
351
|
prev_header.body = header
|
340
352
|
end
|
341
353
|
header.packet = self
|
data/lib/packetgen/version.rb
CHANGED
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.
|
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:
|
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.
|
158
|
+
rubygems_version: 2.6.8
|
158
159
|
signing_key:
|
159
160
|
specification_version: 4
|
160
161
|
summary: Network packet generator and analyzor
|