packetgen 3.2.0 → 3.2.2

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/bin/pgconsole +3 -3
  4. data/lib/packetgen/header/arp.rb +24 -13
  5. data/lib/packetgen/header/asn1_base.rb +2 -2
  6. data/lib/packetgen/header/base.rb +1 -1
  7. data/lib/packetgen/header/dhcpv6/duid.rb +6 -0
  8. data/lib/packetgen/header/dhcpv6/option.rb +11 -13
  9. data/lib/packetgen/header/dns/opt.rb +2 -2
  10. data/lib/packetgen/header/dns/rr.rb +61 -28
  11. data/lib/packetgen/header/dns.rb +13 -6
  12. data/lib/packetgen/header/dot11/management.rb +2 -5
  13. data/lib/packetgen/header/dot11.rb +1 -1
  14. data/lib/packetgen/header/eap.rb +1 -1
  15. data/lib/packetgen/header/eth.rb +1 -1
  16. data/lib/packetgen/header/http/headers.rb +16 -1
  17. data/lib/packetgen/header/http/response.rb +43 -23
  18. data/lib/packetgen/header/igmp.rb +1 -1
  19. data/lib/packetgen/header/ip/option.rb +36 -10
  20. data/lib/packetgen/header/ipv6/addr.rb +3 -0
  21. data/lib/packetgen/header/mdns.rb +19 -25
  22. data/lib/packetgen/header/mld.rb +1 -1
  23. data/lib/packetgen/header/ospfv2/lsa_header.rb +2 -4
  24. data/lib/packetgen/header/ospfv2.rb +1 -1
  25. data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +14 -5
  26. data/lib/packetgen/header/ospfv3/lsa.rb +1 -1
  27. data/lib/packetgen/header/ospfv3/lsa_header.rb +2 -4
  28. data/lib/packetgen/header/ospfv3.rb +1 -1
  29. data/lib/packetgen/header/snmp.rb +39 -16
  30. data/lib/packetgen/header/tcp/option.rb +1 -1
  31. data/lib/packetgen/header/tcp.rb +12 -5
  32. data/lib/packetgen/header/tftp.rb +15 -9
  33. data/lib/packetgen/inspect.rb +15 -9
  34. data/lib/packetgen/packet.rb +48 -2
  35. data/lib/packetgen/pcapng/file.rb +13 -13
  36. data/lib/packetgen/pcapng.rb +1 -0
  37. data/lib/packetgen/pcaprub_wrapper.rb +0 -4
  38. data/lib/packetgen/types/abstract_tlv.rb +1 -1
  39. data/lib/packetgen/types/array.rb +35 -13
  40. data/lib/packetgen/types/fields.rb +19 -19
  41. data/lib/packetgen/types/int.rb +7 -0
  42. data/lib/packetgen/types/oui.rb +1 -1
  43. data/lib/packetgen/types/tlv.rb +17 -9
  44. data/lib/packetgen/unknown_packet.rb +3 -2
  45. data/lib/packetgen/utils.rb +67 -24
  46. data/lib/packetgen/version.rb +1 -1
  47. data/lib/packetgen.rb +3 -3
  48. metadata +3 -3
@@ -62,7 +62,7 @@ module PacketGen
62
62
  HUMAN_SEPARATOR = ','
63
63
 
64
64
  # rubocop:disable Naming/AccessorMethodName
65
- class <<self
65
+ class << self
66
66
  # Get class set with {.set_of}.
67
67
  # @return [Class]
68
68
  # @since 3.0.0
@@ -154,20 +154,16 @@ module PacketGen
154
154
  self
155
155
  end
156
156
 
157
- # Populate object from a string
158
- # @param [String] str
157
+ # Populate object from a string or from an array of hashes
158
+ # @param [String, Array<Hash>] data
159
159
  # @return [self]
160
- def read(str)
160
+ def read(data)
161
161
  clear
162
- return self if str.nil?
163
- return self if @counter&.to_i&.zero?
164
-
165
- str = read_with_length_from(str)
166
- until str.empty?
167
- obj = create_object_from_str(str)
168
- @array << obj
169
- str.slice!(0, obj.sz)
170
- break if @counter && self.size == @counter.to_i
162
+ case data
163
+ when ::Array
164
+ read_from_array(data)
165
+ else
166
+ read_from_string(data)
171
167
  end
172
168
  self
173
169
  end
@@ -198,6 +194,28 @@ module PacketGen
198
194
 
199
195
  private
200
196
 
197
+ # rubocop:disable Metrics/CyclomaticComplexity
198
+
199
+ def read_from_string(str)
200
+ return self if str.nil? || @counter&.to_i&.zero?
201
+
202
+ str = read_with_length_from(str)
203
+ until str.empty? || (@counter && self.size == @counter.to_i)
204
+ obj = create_object_from_str(str)
205
+ @array << obj
206
+ str.slice!(0, obj.sz)
207
+ end
208
+ end
209
+ # rubocop:enable Metrics/CyclomaticComplexity
210
+
211
+ def read_from_array(ary)
212
+ return self if ary.empty?
213
+
214
+ ary.each do |hsh|
215
+ self << hsh
216
+ end
217
+ end
218
+
201
219
  def record_from_hash(hsh)
202
220
  obj_klass = self.class.set_of_klass
203
221
  raise NotImplementedError, 'class should define #record_from_hash or declare type of elements in set with .set_of' unless obj_klass
@@ -228,18 +246,22 @@ module PacketGen
228
246
  class ArrayOfInt8 < Array
229
247
  set_of Int8
230
248
  end
249
+
231
250
  # Specialized array to handle serie of {Int16}.
232
251
  class ArrayOfInt16 < Array
233
252
  set_of Int16
234
253
  end
254
+
235
255
  # Specialized array to handle serie of {Int16le}.
236
256
  class ArrayOfInt16le < Array
237
257
  set_of Int16le
238
258
  end
259
+
239
260
  # Specialized array to handle serie of {Int32}.
240
261
  class ArrayOfInt32 < Types::Array
241
262
  set_of Types::Int32
242
263
  end
264
+
243
265
  # Specialized array to handle serie of {Int32le}.
244
266
  class ArrayOfInt32le < Types::Array
245
267
  set_of Types::Int32le
@@ -115,7 +115,7 @@ module PacketGen
115
115
  # @private bit field definitions
116
116
  @bit_fields = {}
117
117
 
118
- class <<self
118
+ class << self
119
119
  # Get field definitions for this class.
120
120
  # @return [Hash]
121
121
  # @since 3.1.0
@@ -282,7 +282,7 @@ module PacketGen
282
282
  type = field_defs[attr].type
283
283
  raise TypeError, "#{attr} is not a PacketGen::Types::Int" unless type < Types::Int
284
284
 
285
- total_size = type.new.width * 8
285
+ total_size = type.new.nbits
286
286
  idx = total_size - 1
287
287
 
288
288
  until args.empty?
@@ -358,15 +358,15 @@ module PacketGen
358
358
  clear_mask = compute_clear_mask(total_size, field_mask)
359
359
 
360
360
  class_eval <<-METHODS
361
- def #{name}?
362
- val = (self[:#{attr}].to_i & #{field_mask}) >> #{shift}
363
- val != 0
364
- end
365
- def #{name}=(v)
366
- val = v ? 1 : 0
367
- self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask}
368
- self[:#{attr}].value |= val << #{shift}
369
- end
361
+ def #{name}? # def bit?
362
+ val = (self[:#{attr}].to_i & #{field_mask}) >> #{shift} # val = (self[:attr}].to_i & 1}) >> 1
363
+ val != 0 # val != 0
364
+ end # end
365
+ def #{name}=(v) # def bit=(v)
366
+ val = v ? 1 : 0 # val = v ? 1 : 0
367
+ self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfffd
368
+ self[:#{attr}].value |= val << #{shift} # self[:attr].value |= val << 1
369
+ end # end
370
370
  METHODS
371
371
  end
372
372
 
@@ -375,13 +375,13 @@ module PacketGen
375
375
  clear_mask = compute_clear_mask(total_size, field_mask)
376
376
 
377
377
  class_eval <<-METHODS
378
- def #{name}
379
- (self[:#{attr}].to_i & #{field_mask}) >> #{shift}
380
- end
381
- def #{name}=(v)
382
- self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask}
383
- self[:#{attr}].value |= (v & #{2**size - 1}) << #{shift}
384
- end
378
+ def #{name} # def multibit
379
+ (self[:#{attr}].to_i & #{field_mask}) >> #{shift} # (self[:attr].to_i & 6) >> 1
380
+ end # end
381
+ def #{name}=(v) # def multibit=(v)
382
+ self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfff9
383
+ self[:#{attr}].value |= (v & #{2**size - 1}) << #{shift} # self[:attr].value |= (v & 3) << 1
384
+ end # end
385
385
  METHODS
386
386
  end
387
387
 
@@ -523,7 +523,7 @@ module PacketGen
523
523
  # Return object as a hash
524
524
  # @return [Hash] keys: attributes, values: attribute values
525
525
  def to_h
526
- Hash[fields.map { |f| [f, @fields[f].to_human] }]
526
+ fields.map { |f| [f, @fields[f].to_human] }.to_h
527
527
  end
528
528
 
529
529
  # Get offset of given field in {Fields} structure.
@@ -89,6 +89,13 @@ module PacketGen
89
89
  format_str % [to_i.to_s, to_i]
90
90
  end
91
91
 
92
+ # Return the number of bits used to encode this Int
93
+ # @return [Integer]
94
+ # @since 3.2.1
95
+ def nbits
96
+ width * 8
97
+ end
98
+
92
99
  private
93
100
 
94
101
  def format_str
@@ -32,7 +32,7 @@ module PacketGen
32
32
  def from_human(str)
33
33
  return self if str.nil?
34
34
 
35
- bytes = str.split(/:/)
35
+ bytes = str.split(':')
36
36
  raise ArgumentError, 'not a OUI' unless bytes.size == 3
37
37
 
38
38
  self[:b2].read(bytes[0].to_i(16))
@@ -56,12 +56,8 @@ module PacketGen
56
56
  def initialize(options={})
57
57
  Deprecation.deprecated_class(self.class, AbstractTLV)
58
58
  super
59
- self[:type] = options[:t].new(self.type) if options[:t]
60
- self[:length] = options[:l].new(self.length) if options[:l]
61
- self[:value] = options[:v].new if options[:v]
62
- self.type = options[:type] if options[:type]
63
- self.value = options[:value] if options[:value]
64
- self.length = options[:length] if options[:length]
59
+ initialize_types(options)
60
+ initialize_values(options)
65
61
  end
66
62
 
67
63
  # Populate object from a binary string
@@ -137,9 +133,9 @@ module PacketGen
137
133
  else
138
134
  '%s'
139
135
  end
140
- @lenstr ||= "%-#{(2**(self[:length].width * 8) - 1).to_s.size}u"
141
- "#{name} type:#{@typestr} length:#{@lenstr} value:#{value.inspect}" % [human_type,
142
- length]
136
+ lenstr = "%-#{(2**self[:length].nbits - 1).to_s.size}u"
137
+ "#{name} type:#{@typestr} length:#{lenstr} value:#{value.inspect}" % [human_type,
138
+ length]
143
139
  end
144
140
 
145
141
  private
@@ -147,6 +143,18 @@ module PacketGen
147
143
  def human_types?
148
144
  self.class.const_defined? :TYPES
149
145
  end
146
+
147
+ def initialize_types(options)
148
+ self[:type] = options[:t].new(self.type) if options[:t]
149
+ self[:length] = options[:l].new(self.length) if options[:l]
150
+ self[:value] = options[:v].new if options[:v]
151
+ end
152
+
153
+ def initialize_values(options)
154
+ self.type = options[:type] if options[:type]
155
+ self.value = options[:value] if options[:value]
156
+ self.length = options[:length] if options[:length]
157
+ end
150
158
  end
151
159
  end
152
160
  end
@@ -61,7 +61,8 @@ module PacketGen
61
61
 
62
62
  # @return [String]
63
63
  def inspect
64
- # TODO
64
+ str = Inspect.dashed_line(self.class)
65
+ str << Inspect.inspect_body(body)
65
66
  end
66
67
 
67
68
  # equality if {#to_s} is equal
@@ -74,7 +75,7 @@ module PacketGen
74
75
  # @return [Boolean]
75
76
  def ===(other)
76
77
  case other
77
- when UnknwonPacket
78
+ when UnknownPacket
78
79
  self == other
79
80
  else
80
81
  false
@@ -16,21 +16,62 @@ module PacketGen
16
16
  # @author Sylvain Daubert
17
17
  # @since 2.1.3
18
18
  module Utils
19
+ # @private
20
+ ARP_FILTER = 'arp src %<ipaddr>s and ether dst %<hwaddr>s'
21
+ # @private
22
+ MITM_FILTER = '((ip src %<target1>s and not ip dst %<local_ip>s) or' \
23
+ ' (ip src %<target2>s and not ip dst %<local_ip>s) or' \
24
+ ' (ip dst %<target1>s and not ip src %<local_ip>s) or' \
25
+ ' (ip dst %<target2>s and not ip src %<local_ip>s))' \
26
+ ' and ether dst %<local_mac>s'
27
+
28
+ # @private
29
+ ARP_PATH = '/usr/sbin/arp'
30
+ # @private
31
+ IP_PATH = '/usr/bin/ip'
32
+ # @private
33
+ ARP_LINE_RE = /\((\d+\.\d+\.\d+\.\d+)\) at (([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2})(?: \[ether\])? on (\w+)/.freeze
34
+ # @private
35
+ IP_LINE_RE = /^(\d+\.\d+\.\d+\.\d+) dev (\w+) lladdr (([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2})/.freeze
36
+
19
37
  # Get local ARP cache
20
38
  # @return [Hash] key: IP address, value: array containing MAC address and
21
39
  # interface name
22
40
  def self.arp_cache
23
- raw_cache = `/usr/sbin/arp -an`
41
+ return self.cache_from_arp_command if File.exist?(ARP_PATH)
42
+ return self.cache_from_ip_command if File.exist?(IP_PATH)
43
+
44
+ {}
45
+ end
46
+
47
+ # @private
48
+ def self.cache_from_arp_command(raw_cache=nil)
49
+ raw_cache ||= `#{ARP_PATH} -an`
24
50
 
25
51
  cache = {}
26
52
  raw_cache.split("\n").each do |line|
27
- match = line.match(/\((\d+\.\d+\.\d+\.\d+)\) at (([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2})(?: \[ether\])? on (\w+)/)
53
+ match = line.match(ARP_LINE_RE)
28
54
  cache[match[1]] = [match[2], match[4]] if match
29
55
  end
30
56
 
31
57
  cache
32
58
  end
33
59
 
60
+ # @private
61
+ def self.cache_from_ip_command(raw_cache=nil)
62
+ raw_cache ||= `#{IP_PATH} neigh`
63
+
64
+ cache = {}
65
+ raw_cache.split("\n").each do |line|
66
+ match = line.match(IP_LINE_RE)
67
+ cache[match[1]] = [match[3], match[2]] if match
68
+ end
69
+
70
+ cache
71
+ end
72
+
73
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
74
+
34
75
  # Get MAC address from an IP address, or nil if this IP address is unknown
35
76
  # on local network.
36
77
  # @param [String] ipaddr dotted-octet IP address
@@ -40,7 +81,7 @@ module PacketGen
40
81
  # @option options [Boolean] :no_cache if +true+, do not query local ARP
41
82
  # cache and always send an ARP request on wire. Default to +false+
42
83
  # @option options [Integer] :timeout timeout in seconds before stopping
43
- # request. Default to 2.
84
+ # request. Default to 1.
44
85
  # @return [String,nil]
45
86
  # @raise [RuntimeError] user don't have permission to capture packets on network device.
46
87
  def self.arp(ipaddr, options={})
@@ -57,10 +98,10 @@ module PacketGen
57
98
  spa: Config.instance.ipaddr(iface),
58
99
  tpa: ipaddr)
59
100
 
60
- capture = Capture.new(iface: iface, timeout: timeout, max: 1,
61
- filter: "arp src #{ipaddr} and ether dst #{my_hwaddr}")
101
+ capture = Capture.new(iface: iface, timeout: timeout, max: 1, filter: ARP_FILTER % { ipaddr: ipaddr, hwaddr: my_hwaddr })
62
102
  cap_thread = Thread.new { capture.start }
63
103
 
104
+ sleep 0.1
64
105
  arp_pkt.to_w(iface)
65
106
  cap_thread.join
66
107
 
@@ -70,6 +111,7 @@ module PacketGen
70
111
  break pkt.arp.sha.to_s if pkt.arp.spa.to_s == ipaddr
71
112
  end
72
113
  end
114
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize
73
115
 
74
116
  # Do ARP spoofing on given IP address. Call to this method blocks.
75
117
  # @note This method is provided for test purpose.
@@ -122,39 +164,40 @@ module PacketGen
122
164
  # end
123
165
  # @since 2.2.0
124
166
  # @raise [RuntimeError] user don't have permission to capture packets on network device.
125
- def self.mitm(target1, target2, options={})
167
+ def self.mitm(target1, target2, options={}, &block)
126
168
  options = { iface: PacketGen.default_iface }.merge(options)
127
169
 
128
- mac1 = arp(target1)
129
- mac2 = arp(target2)
130
-
131
170
  spoofer = Utils::ARPSpoofer.new(options)
132
171
  spoofer.add target1, target2, options
133
172
  spoofer.add target2, target1, options
134
173
 
135
- my_mac = Config.instance.hwaddr(options[:iface])
136
- my_ip = Config.instance.ipaddr(options[:iface])
174
+ cfg = Config.instance
175
+ my_mac = cfg.hwaddr(options[:iface])
137
176
  capture = Capture.new(iface: options[:iface],
138
- filter: "((ip src #{target1} and not ip dst #{my_ip}) or" \
139
- " (ip src #{target2} and not ip dst #{my_ip}) or" \
140
- " (ip dst #{target1} and not ip src #{my_ip}) or" \
141
- " (ip dst #{target2} and not ip src #{my_ip}))" \
142
- " and ether dst #{my_mac}")
177
+ filter: MITM_FILTER % { target1: target1, target2: target2, local_ip: cfg.ipaddr(options[:iface]), local_mac: my_mac })
143
178
 
144
179
  spoofer.start_all
180
+ mitm_core(capture, target1, target2, my_mac, &block)
181
+ spoofer.stop_all
182
+ end
183
+
184
+ # @private
185
+ def self.mitm_core(capture, target1, target2, my_mac)
186
+ mac1 = arp(target1)
187
+ mac2 = arp(target2)
188
+
145
189
  capture.start do |pkt|
146
190
  modified_pkt = yield pkt
147
191
  iph = modified_pkt.ip
148
192
  l2 = modified_pkt.is?('Dot11') ? modified_pkt.dot11 : modified_pkt.eth
149
193
 
150
- if (iph.src == target1) || (iph.dst == target2)
151
- l2.dst = mac2
152
- elsif (iph.src == target2) || (iph.dst == target1)
153
- l2.dst = mac1
154
- else
155
- next
156
- end
157
- modified_pkt.to_w(options[:iface])
194
+ l2.src = my_mac
195
+ l2.dst = if (iph.src == target1) || (iph.dst == target2)
196
+ mac2
197
+ else # (iph.src == target2) || (iph.dst == target1)
198
+ mac1
199
+ end
200
+ modified_pkt.to_w(capture.iface)
158
201
  end
159
202
  end
160
203
  end
@@ -10,5 +10,5 @@
10
10
  # @author Sylvain Daubert
11
11
  module PacketGen
12
12
  # PacketGen version
13
- VERSION = '3.2.0'
13
+ VERSION = '3.2.2'
14
14
  end
data/lib/packetgen.rb CHANGED
@@ -39,9 +39,9 @@ module PacketGen
39
39
 
40
40
  def message
41
41
  "#{prev_hdr.class} knowns no layer association with #{hdr.protocol_name}. " \
42
- "Try #{prev_hdr.class}.bind_layer(#{hdr.class}, " \
43
- "#{prev_hdr.method_name}_proto_field: " \
44
- "<value_for_#{hdr.method_name}>)"
42
+ "Try #{prev_hdr.class}.bind_layer(#{hdr.class}, " \
43
+ "#{prev_hdr.method_name}_proto_field: " \
44
+ "<value_for_#{hdr.method_name}>)"
45
45
  end
46
46
  end
47
47
 
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: 3.2.0
4
+ version: 3.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sylvain Daubert
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-20 00:00:00.000000000 Z
11
+ date: 2022-12-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: interfacez
@@ -260,7 +260,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
260
260
  requirements:
261
261
  - - ">="
262
262
  - !ruby/object:Gem::Version
263
- version: 2.4.0
263
+ version: 2.5.0
264
264
  required_rubygems_version: !ruby/object:Gem::Requirement
265
265
  requirements:
266
266
  - - ">="