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