packetgen 3.3.3 → 4.1.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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -22
  3. data/lib/packetgen/capture.rb +2 -2
  4. data/lib/packetgen/config.rb +0 -1
  5. data/lib/packetgen/deprecation.rb +14 -8
  6. data/lib/packetgen/header/arp.rb +17 -18
  7. data/lib/packetgen/header/asn1_base.rb +2 -1
  8. data/lib/packetgen/header/base.rb +42 -40
  9. data/lib/packetgen/header/bootp.rb +35 -37
  10. data/lib/packetgen/header/dhcp/option.rb +21 -21
  11. data/lib/packetgen/header/dhcp/options.rb +3 -3
  12. data/lib/packetgen/header/dhcp.rb +8 -9
  13. data/lib/packetgen/header/dhcpv6/duid.rb +16 -16
  14. data/lib/packetgen/header/dhcpv6/option.rb +83 -61
  15. data/lib/packetgen/header/dhcpv6/options.rb +4 -4
  16. data/lib/packetgen/header/dhcpv6/relay.rb +6 -5
  17. data/lib/packetgen/header/dhcpv6.rb +17 -18
  18. data/lib/packetgen/header/dns/name.rb +21 -16
  19. data/lib/packetgen/header/dns/opt.rb +5 -2
  20. data/lib/packetgen/header/dns/option.rb +14 -14
  21. data/lib/packetgen/header/dns/qdsection.rb +3 -3
  22. data/lib/packetgen/header/dns/question.rb +7 -8
  23. data/lib/packetgen/header/dns/rr.rb +56 -43
  24. data/lib/packetgen/header/dns/rrsection.rb +6 -6
  25. data/lib/packetgen/header/dns.rb +103 -90
  26. data/lib/packetgen/header/dot11/control.rb +12 -12
  27. data/lib/packetgen/header/dot11/data.rb +25 -24
  28. data/lib/packetgen/header/dot11/element.rb +4 -4
  29. data/lib/packetgen/header/dot11/management.rb +21 -18
  30. data/lib/packetgen/header/dot11/sub_mngt.rb +40 -53
  31. data/lib/packetgen/header/dot11.rb +117 -122
  32. data/lib/packetgen/header/dot1q.rb +12 -13
  33. data/lib/packetgen/header/dot1x.rb +13 -13
  34. data/lib/packetgen/header/eap/fast.rb +4 -4
  35. data/lib/packetgen/header/eap/md5.rb +16 -8
  36. data/lib/packetgen/header/eap/tls.rb +18 -19
  37. data/lib/packetgen/header/eap/ttls.rb +22 -21
  38. data/lib/packetgen/header/eap.rb +73 -48
  39. data/lib/packetgen/header/eth.rb +41 -27
  40. data/lib/packetgen/header/gre.rb +33 -11
  41. data/lib/packetgen/header/http/headers.rb +7 -6
  42. data/lib/packetgen/header/http/request.rb +38 -29
  43. data/lib/packetgen/header/http/response.rb +35 -27
  44. data/lib/packetgen/header/http/verbs.rb +1 -3
  45. data/lib/packetgen/header/icmp.rb +14 -14
  46. data/lib/packetgen/header/icmpv6.rb +10 -9
  47. data/lib/packetgen/header/igmp.rb +26 -15
  48. data/lib/packetgen/header/igmpv3/group_record.rb +18 -13
  49. data/lib/packetgen/header/igmpv3/mq.rb +16 -18
  50. data/lib/packetgen/header/igmpv3/mr.rb +5 -5
  51. data/lib/packetgen/header/igmpv3.rb +12 -11
  52. data/lib/packetgen/header/ip/addr.rb +19 -15
  53. data/lib/packetgen/header/ip/option.rb +47 -36
  54. data/lib/packetgen/header/ip/options.rb +1 -1
  55. data/lib/packetgen/header/ip.rb +77 -95
  56. data/lib/packetgen/header/ipv6/addr.rb +28 -27
  57. data/lib/packetgen/header/ipv6/extension.rb +13 -11
  58. data/lib/packetgen/header/ipv6/hop_by_hop.rb +32 -13
  59. data/lib/packetgen/header/ipv6.rb +42 -35
  60. data/lib/packetgen/header/llc.rb +28 -21
  61. data/lib/packetgen/header/mdns.rb +10 -3
  62. data/lib/packetgen/header/mld.rb +15 -13
  63. data/lib/packetgen/header/mldv2/mcast_address_record.rb +17 -12
  64. data/lib/packetgen/header/mldv2/mlq.rb +22 -24
  65. data/lib/packetgen/header/mldv2/mlr.rb +8 -8
  66. data/lib/packetgen/header/mldv2.rb +1 -1
  67. data/lib/packetgen/header/ospfv2/db_description.rb +17 -18
  68. data/lib/packetgen/header/ospfv2/hello.rb +18 -17
  69. data/lib/packetgen/header/ospfv2/ls_ack.rb +6 -7
  70. data/lib/packetgen/header/ospfv2/ls_request.rb +14 -13
  71. data/lib/packetgen/header/ospfv2/ls_update.rb +9 -9
  72. data/lib/packetgen/header/ospfv2/lsa.rb +79 -60
  73. data/lib/packetgen/header/ospfv2/lsa_header.rb +12 -11
  74. data/lib/packetgen/header/ospfv2.rb +49 -46
  75. data/lib/packetgen/header/ospfv3/db_description.rb +20 -22
  76. data/lib/packetgen/header/ospfv3/hello.rb +17 -16
  77. data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +22 -20
  78. data/lib/packetgen/header/ospfv3/ls_ack.rb +7 -8
  79. data/lib/packetgen/header/ospfv3/ls_request.rb +18 -18
  80. data/lib/packetgen/header/ospfv3/ls_update.rb +10 -10
  81. data/lib/packetgen/header/ospfv3/lsa.rb +62 -51
  82. data/lib/packetgen/header/ospfv3/lsa_header.rb +12 -11
  83. data/lib/packetgen/header/ospfv3.rb +54 -52
  84. data/lib/packetgen/header/sctp/chunk.rb +80 -56
  85. data/lib/packetgen/header/sctp/error.rb +174 -202
  86. data/lib/packetgen/header/sctp/padded32.rb +3 -3
  87. data/lib/packetgen/header/sctp/parameter.rb +89 -136
  88. data/lib/packetgen/header/sctp.rb +19 -8
  89. data/lib/packetgen/header/snmp.rb +108 -7
  90. data/lib/packetgen/header/tcp/option.rb +52 -39
  91. data/lib/packetgen/header/tcp/options.rb +13 -5
  92. data/lib/packetgen/header/tcp.rb +83 -65
  93. data/lib/packetgen/header/tftp.rb +31 -25
  94. data/lib/packetgen/header/udp.rb +21 -19
  95. data/lib/packetgen/header.rb +23 -18
  96. data/lib/packetgen/headerable.rb +21 -5
  97. data/lib/packetgen/inspect.rb +3 -8
  98. data/lib/packetgen/packet.rb +146 -71
  99. data/lib/packetgen/pcap.rb +15 -4
  100. data/lib/packetgen/pcapng/block.rb +20 -18
  101. data/lib/packetgen/pcapng/epb.rb +13 -15
  102. data/lib/packetgen/pcapng/file.rb +44 -111
  103. data/lib/packetgen/pcapng/idb.rb +11 -12
  104. data/lib/packetgen/pcapng/shb.rb +15 -16
  105. data/lib/packetgen/pcapng/spb.rb +9 -11
  106. data/lib/packetgen/pcapng/unknown_block.rb +6 -17
  107. data/lib/packetgen/pcapng.rb +6 -4
  108. data/lib/packetgen/pcaprub_wrapper.rb +17 -1
  109. data/lib/packetgen/proto.rb +5 -1
  110. data/lib/packetgen/unknown_packet.rb +5 -5
  111. data/lib/packetgen/utils/arp_spoofer.rb +18 -19
  112. data/lib/packetgen/utils.rb +4 -3
  113. data/lib/packetgen/version.rb +1 -1
  114. data/lib/packetgen.rb +12 -5
  115. metadata +29 -38
  116. data/lib/packetgen/types/abstract_tlv.rb +0 -278
  117. data/lib/packetgen/types/array.rb +0 -287
  118. data/lib/packetgen/types/cstring.rb +0 -109
  119. data/lib/packetgen/types/enum.rb +0 -171
  120. data/lib/packetgen/types/fieldable.rb +0 -66
  121. data/lib/packetgen/types/fields.rb +0 -622
  122. data/lib/packetgen/types/int.rb +0 -473
  123. data/lib/packetgen/types/int_string.rb +0 -102
  124. data/lib/packetgen/types/length_from.rb +0 -54
  125. data/lib/packetgen/types/oui.rb +0 -52
  126. data/lib/packetgen/types/string.rb +0 -97
  127. data/lib/packetgen/types/tlv.rb +0 -161
  128. data/lib/packetgen/types.rb +0 -26
@@ -7,50 +7,58 @@
7
7
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
8
8
  # This program is published under MIT license.
9
9
 
10
- # rubocop:disable Metrics/ClassLength
11
-
12
10
  module PacketGen
13
11
  # An object of type {Packet} handles a network packet. This packet may contain
14
12
  # multiple protocol headers, starting from MAC layer or from Network (OSI) layer.
15
13
  #
16
- # Creating a packet is fairly simple:
17
- # Packet.gen 'IP', src: '192.168.1.1', dst: '192.168.1.2'
14
+ # A Packet is created using {.gen} method. Headers may be added to this packet using {#add}.
15
+ # It may also be created from parsing a binary string, using {.parse} method.
16
+ #
17
+ # @example Very simple packet
18
+ # # Create a packet with a single IP header. IP source and destination addresses are set.
19
+ # pkt = PacketGen::Packet.gen('IP', src: '192.168.1.1', dst: '192.168.1.2')
18
20
  #
19
- # == Create a packet
20
- # Packets may be hand-made or parsed from a binary string:
21
- # Packet.gen('IP', src: '192.168.1.1', dst: '192.168.1.2').add('UDP', sport: 45000, dport: 23)
22
- # Packet.parse(binary_string)
21
+ # @example Parsing a binary string
22
+ # # a IP/ICMP packet, as binary string
23
+ # binary_string = "E\x00\x00\x1C\xAA`\x00\x00@\x01M-\xC0\xA8\x01\x01\xC0\xA8\x01\x02\x00\b;1abcd".b
24
+ # pkt = PacketGen::Packet.parse(binary_string)
25
+ # pkt.is?('IP') #=> true
26
+ # pkt.is?('ICMP') #=> true
23
27
  #
24
- # == Access packet information
25
- # pkt = Packet.gen('IP').add('UDP')
26
- # # read information
27
- # pkt.udp.sport
28
- # pkt.ip.ttl
29
- # # set information
30
- # pkt.udp.dport = 2323
31
- # pkt.ip.ttl = 1
32
- # pkt.ip(ttl: 1, id: 1234)
28
+ # Information for each level is accessible through the associated header. Header is accessible through a
29
+ # method defined with its name at packet level (i.e. +#ip+ for a IP header).
33
30
  #
34
- # == Save a packet to a file
35
- # pkt.write('file.pcapng')
31
+ # @example Accessing header information
32
+ # pkt = PacketGen::Packet.gen('IP').add('UDP')
33
+ # # read information
34
+ # pkt.udp.sport #=> 0
35
+ # pkt.ip.ttl #=> 64
36
+ # # set information
37
+ # pkt.udp.dport = 2323
38
+ # pkt.ip.ttl = 1
39
+ # # Set multiple attributes at once
40
+ # pkt.ip(ttl: 1, id: 1234)
36
41
  #
37
- # == Get packets
38
- # Packets may be captured from wire:
39
- # Packet.capture do |packet|
40
- # do_some_stuffs
41
- # end
42
- # packets = Packet.capture(iface: 'eth0', max: 5) # get 5 packets from eth0
42
+ # Packets may be written to files using {#write}:
43
+ # pkt = PacketGen::Packet.gen('Eth')
44
+ # pkt.write('file.pcapng')
43
45
  #
44
- # Packets may also be read from a file:
45
- # packets = Packet.read(file.pcapng)
46
+ # Packets may be captured from network interfaces:
47
+ # # Capture all packets from default interface. Never end.
48
+ # PacketGen::Packet.capture do |packet|
49
+ # do_some_stuffs
50
+ # end
46
51
  #
47
- # == Save packets to a file
48
- # Packet.write 'file.pcapng', packets
52
+ # # Get 5 packets from eth0 interface
53
+ # packets = Packet.capture(iface: 'eth0', max: 5)
49
54
  #
55
+ # Finally, packets may also be read from a file:
56
+ # packets = Packet.read(file.pcapng)
50
57
  # @author Sylvain Daubert
58
+ # @author LemonTree55
51
59
  class Packet
52
60
  # Get packet headers, ordered as they appear in the packet.
53
- # @return [Array<Header::Base>]
61
+ # @return [Array<Headerable>]
54
62
  attr_reader :headers
55
63
  # Activaye or deactivate header cache (activated by default)
56
64
  # @return [Boolean]
@@ -61,20 +69,20 @@ module PacketGen
61
69
  # @param [Hash] options specific options for +protocol+
62
70
  # @return [Packet]
63
71
  def self.gen(protocol, options={})
64
- self.new.add protocol, options
72
+ self.new.add(protocol, options)
65
73
  end
66
74
 
67
75
  # Parse a binary string and generate a Packet from it.
68
76
  # # auto-detect first header
69
- # Packet.parse str
77
+ # Packet.parse(str)
70
78
  # # force decoding a Ethernet header for first header
71
- # Packet.parse str, first_header: 'Eth'
79
+ # Packet.parse(str, first_header: 'Eth')
72
80
  # @param [String] binary_str
73
81
  # @param [String,nil] first_header First protocol header. +nil+ means discover it!
74
82
  # @return [Packet]
75
83
  # @raise [ArgumentError] +first_header+ is an unknown header
76
84
  def self.parse(binary_str, first_header: nil)
77
- new.parse binary_str, first_header: first_header
85
+ new.parse(binary_str, first_header: first_header)
78
86
  end
79
87
 
80
88
  # Capture packets from wire.
@@ -112,16 +120,23 @@ module PacketGen
112
120
  Pcap.read(filename)
113
121
  end
114
122
 
115
- # Write packets to +filename+
123
+ # Write packets to +filename+, as pcap or PcapNG file
116
124
  #
117
125
  # For more options, see {PcapNG::File}.
118
126
  # @param [String] filename
119
127
  # @param [Array<Packet>] packets packets to write
120
128
  # @return [void]
129
+ # @since 4.0.0 write a pcap file if filename extension is +.pcap+
130
+ # @author Sylvain Daubert
131
+ # @author LemonTree55 (pcap)
121
132
  def self.write(filename, packets)
122
- pf = PcapNG::File.new
123
- pf.array_to_file packets
124
- pf.to_f filename
133
+ if filename.end_with?('.pcap')
134
+ Pcap.write(filename, packets)
135
+ else
136
+ pf = PcapNG::File.new
137
+ pf.read_array(packets)
138
+ pf.to_f(filename)
139
+ end
125
140
  end
126
141
 
127
142
  # @private
@@ -135,14 +150,22 @@ module PacketGen
135
150
  # @param [String] protocol
136
151
  # @param [Hash] options protocol specific options
137
152
  # @return [self]
138
- # @raise [BindingError] unknown protocol
153
+ # @raise [ArgumentError] unknown protocol
154
+ # @raise [BindingError] unknown binding
155
+ # @see #<<
156
+ # @example
157
+ # pkt = PacketGen::Packet.gen('Eth')
158
+ # # Add a IP header
159
+ # pkt.add('IP')
160
+ # # Add a TCP header, with some attributes and body set
161
+ # pkt.add('TCP', dport: 80, seqnum: 123456, body: "abcd".b)
139
162
  def add(protocol, options={})
140
163
  klass = check_protocol(protocol)
141
164
 
142
165
  # options[:packet]= self is speedier than options.merge(packet: self)
143
166
  options[:packet] = self
144
167
  header = klass.new(options)
145
- add_header header
168
+ add_header(header)
146
169
  self
147
170
  end
148
171
 
@@ -159,7 +182,7 @@ module PacketGen
159
182
  # options[:packet]= self is speedier than options.merge(packet: self)
160
183
  options[:packet] = self
161
184
  header = klass.new(options)
162
- add_header header, previous_header: prev
185
+ add_header(header, previous_header: prev)
163
186
  idx = headers.index(prev) + 1
164
187
  headers[idx, 0] = header
165
188
  header[:body] = nxt
@@ -167,13 +190,14 @@ module PacketGen
167
190
  end
168
191
 
169
192
  # Check if a protocol header is embedded in packet.
193
+ # @example
170
194
  # pkt = PacketGen.gen('IP').add('UDP')
171
195
  # pkt.is?('IP') #=> true
172
196
  # pkt.is?('TCP') #=> false
173
197
  # @return [Boolean]
174
198
  # @raise [ArgumentError] unknown protocol
175
199
  def is?(protocol)
176
- klass = check_protocol protocol
200
+ klass = check_protocol(protocol)
177
201
  headers.any?(klass)
178
202
  end
179
203
 
@@ -181,7 +205,7 @@ module PacketGen
181
205
  # @return [void]
182
206
  def calc_checksum
183
207
  headers.reverse_each do |header|
184
- header.calc_checksum if header.respond_to? :calc_checksum
208
+ header.calc_checksum if header.respond_to?(:calc_checksum)
185
209
  end
186
210
  end
187
211
 
@@ -189,27 +213,36 @@ module PacketGen
189
213
  # @return [void]
190
214
  def calc_length
191
215
  headers.reverse_each do |header|
192
- header.calc_length if header.respond_to? :calc_length
216
+ header.calc_length if header.respond_to?(:calc_length)
193
217
  end
194
218
  end
195
219
 
196
220
  # Recalculate all calculatable fields (for now: length and checksum)
197
221
  # @return [void]
222
+ # @author LemonTree55
198
223
  def calc
199
- calc_length
200
- calc_checksum
224
+ headers.reverse_each do |header|
225
+ header.calc_length if header.respond_to?(:calc_length)
226
+ header.calc_checksum if header.respond_to?(:calc_checksum)
227
+ end
201
228
  end
202
229
 
203
- # Get packet body
204
- # @return [Types]
230
+ # Get packet body. If packet (i.e. last header) has no +:body+ field, return +nil+.
231
+ # @return [Headerable,BinStruct::String,nil]
205
232
  def body
206
- last_header[:body] if last_header.respond_to? :body
233
+ last_header[:body] if last_header.respond_to?(:body)
207
234
  end
208
235
 
209
236
  # Set packet body
210
- # @param [String] str
237
+ # @param [String] str Binary string
211
238
  # @return [void]
239
+ # @raise [Error] Packet (i.e. last header) has no +:body+ field.
240
+ # @note To set a {Headerable} object, prefer #{<<}
241
+ # @see #<<
242
+ # @since 4.1.0 raise {Error} if no body on packet
212
243
  def body=(str)
244
+ raise Error, 'no body in last headeré' unless last_header.respond_to?(:body)
245
+
213
246
  last_header.body = str
214
247
  end
215
248
 
@@ -229,7 +262,7 @@ module PacketGen
229
262
  alias write to_f
230
263
 
231
264
  # Send packet on wire. Use first header +#to_w+ method.
232
- # @param [String] iface interface name. Default to first non-loopback interface
265
+ # @param [String,nil] iface interface name. Default to first non-loopback interface
233
266
  # @param [Boolean] calc if +true+, call {#calc} on packet before sending it.
234
267
  # @param [Integer] number number of times to send the packets
235
268
  # @param [Integer,Float] interval time, in seconds, between sending 2 packets
@@ -239,12 +272,12 @@ module PacketGen
239
272
  def to_w(iface=nil, calc: true, number: 1, interval: 1)
240
273
  iface ||= PacketGen.default_iface
241
274
 
242
- if first_header.respond_to? :to_w
275
+ if first_header.respond_to?(:to_w)
243
276
  self.calc if calc
244
277
 
245
278
  number.times do
246
279
  first_header.to_w(iface)
247
- sleep interval if number > 1
280
+ sleep(interval) if number > 1
248
281
  end
249
282
  else
250
283
  type = first_header.protocol_name
@@ -257,12 +290,19 @@ module PacketGen
257
290
  # @param [Boolean] parsing set to +true+ to not update last current header field
258
291
  # from binding with first other's one. Use only when current header field
259
292
  # has its value set accordingly.
260
- # @return [self] +self+ with new headers from +other+
293
+ # @return [self] +self+ updated with new headers from +other+
261
294
  # @raise [BindingError] do not known how to encapsulate
262
295
  # @since 1.1.0
296
+ # @example
297
+ # # Create a first IP packet
298
+ # ip1 = PacketGen::Packet.gen('IP', id: 1)
299
+ # # Create second IP packet, to encapsulate in first
300
+ # ip2 = PacketGen.gen('IP', id: 2)
301
+ # ip1.encapsulate(ip2)
302
+ # ip1.ip(2) == ip2.ip
263
303
  def encapsulate(other, parsing: false)
264
304
  other.headers.each_with_index do |h, i|
265
- add_header h, parsing: i.positive? || parsing
305
+ add_header(h, parsing: i.positive? || parsing)
266
306
  end
267
307
  end
268
308
 
@@ -272,6 +312,12 @@ module PacketGen
272
312
  # @raise [FormatError] any headers not in +self+
273
313
  # @raise [BindingError] removed headers result in an unknown binding
274
314
  # @since 1.1.0
315
+ # @example
316
+ # # IP/IP encapsulation
317
+ # pkt = PacketGen::Packet.gen('IP', id: 1).add('IP', id:2)
318
+ # # Remove outer IP header
319
+ # pkt.decapsulate(pkt.ip(1))
320
+ # pkt.ip.id #=> 2
275
321
  def decapsulate(*hdrs)
276
322
  hdrs.each do |hdr|
277
323
  prev_hdr = previous_header(hdr)
@@ -280,14 +326,15 @@ module PacketGen
280
326
  add_header(next_hdr, previous_header: prev_hdr) if prev_hdr && next_hdr
281
327
  end
282
328
  invalidate_header_cache
329
+ self
283
330
  rescue ArgumentError => e
284
331
  raise FormatError, e.message
285
332
  end
286
333
 
287
334
  # Parse a binary string and populate Packet from it.
288
335
  # @param [String] binary_str
289
- # @param [String,nil] first_header First protocol header. +nil+ means discover it!
290
- # @return [Packet] self
336
+ # @param [String,nil] first_header First protocol header name. +nil+ means discover it!
337
+ # @return [self]
291
338
  # @raise [ParseError] +first_header+ is an unknown header
292
339
  # @raise [BindingError] unknwon binding between some headers
293
340
  def parse(binary_str, first_header: nil)
@@ -299,7 +346,7 @@ module PacketGen
299
346
  raise ParseError, "cannot identify first header in string: #{binary_str.inspect}" if first_header.nil?
300
347
  end
301
348
 
302
- add first_header
349
+ add(first_header)
303
350
  headers[-1, 1] = last_header.read(binary_str)
304
351
 
305
352
  # Decode upper headers recursively
@@ -317,12 +364,14 @@ module PacketGen
317
364
  str << Inspect.inspect_body(body)
318
365
  end
319
366
 
367
+ # Check equality at binary level
320
368
  # @param [Packet] other
321
369
  # @return [Boolean]
322
370
  def ==(other)
323
371
  to_s == other.to_s
324
372
  end
325
373
 
374
+ # +true+ if {#==} is +true+ with another packet, or if +other+ is a protocol name String, whose protocol is in Packet.
326
375
  # @param [Packet] other
327
376
  # @return [Boolean]
328
377
  # @since 3.1.2
@@ -331,14 +380,15 @@ module PacketGen
331
380
  when PacketGen::Packet
332
381
  self == other
333
382
  when String
334
- is? other
383
+ is?(other)
335
384
  else
336
385
  false
337
386
  end
338
387
  end
339
388
 
340
- # Invert all possible fields in packet to create a reply.
389
+ # Invert all possible attributes in packet to create a reply.
341
390
  # @return [self]
391
+ # @note Only modify headers responding to +#reply!+.
342
392
  # @since 2.7.0
343
393
  def reply!
344
394
  headers.each do |header|
@@ -350,12 +400,32 @@ module PacketGen
350
400
  # Forge a new packet from current one with all possible fields
351
401
  # inverted. The new packet may be a reply to current one.
352
402
  # @return [Packet]
403
+ # @note Only modify headers responding to +#reply!+.
353
404
  # @since 2.7.0
354
405
  def reply
355
406
  pkt = dup
356
407
  pkt.reply!
357
408
  end
358
409
 
410
+ # Append an already defined header to packet
411
+ # @param [Headerable] header
412
+ # @return [self]
413
+ # @raise [ArgumentError] unknown protocol
414
+ # @raise [BindingError] unknown binding
415
+ # @example
416
+ # pkt = PacketGen.gen('Eth')
417
+ # # Add a new header from its type
418
+ # pkt.add('IP')
419
+ # # Add a new pre-generated header
420
+ # pkt << PacketGen::Header::TCP.new
421
+ # @see #add
422
+ # @since 4.1.0
423
+ # @author LemonTree55
424
+ def <<(header)
425
+ add_header(header)
426
+ self
427
+ end
428
+
359
429
  private
360
430
 
361
431
  # Dup +@headers+ instance variable. Internally used by +#dup+ and +#clone+
@@ -363,7 +433,7 @@ module PacketGen
363
433
  def initialize_copy(_other)
364
434
  @headers = headers.map(&:dup)
365
435
  headers.each do |header|
366
- add_magic_header_method header
436
+ add_magic_header_method(header)
367
437
  end
368
438
  invalidate_header_cache
369
439
  end
@@ -410,17 +480,20 @@ module PacketGen
410
480
  end
411
481
 
412
482
  # @overload header(klass, layer=1)
483
+ # Get a header given its class and its layer (example: IP-in-IP encapsulation)
413
484
  # @param [Class] klass
414
485
  # @param [Integer] layer
415
486
  # @overload header(klass, options={})
487
+ # Get a header given its class, and set some attributes
416
488
  # @param [String] klass
417
- # @param [Hash] options
418
- # @raise [ArgumentError] unknown option
419
- # @return [Header::Base]
489
+ # @param [Hash] options attributes to set
490
+ # @raise [ArgumentError] unknown attribute
491
+ # @return [Header::Base,nil]
420
492
  def header(klass, arg)
421
493
  layer = arg.is_a?(Integer) ? arg : 1
422
494
  header = find_header(klass, layer)
423
- return header unless arg.is_a? Hash
495
+ return nil if header.nil?
496
+ return header unless arg.is_a?(Hash)
424
497
 
425
498
  arg.each do |key, value|
426
499
  raise ArgumentError, "unknown #{key} attribute for #{klass}" unless header.respond_to?(:"#{key}=")
@@ -434,12 +507,14 @@ module PacketGen
434
507
  # Get header from cache, or find it in packet
435
508
  # @param [Class] klass
436
509
  # @param [Integer] layer
437
- # @return [Header::Base]
510
+ # @return [Header::Base,nil]
438
511
  def find_header(klass, layer)
439
512
  header = fetch_header_from_cache(klass, layer)
440
513
  return header if header
441
514
 
442
- header = headers.select { |h| h.is_a? klass }[layer - 1]
515
+ header = headers.select { |h| h.is_a?(klass) }[layer - 1]
516
+ return nil if header.nil?
517
+
443
518
  add_header_to_cache(header, klass, layer)
444
519
  header
445
520
  end
@@ -468,9 +543,9 @@ module PacketGen
468
543
  header.packet = self
469
544
  headers << header unless previous_header
470
545
 
471
- return if respond_to? header.method_name
546
+ return if respond_to?(header.method_name)
472
547
 
473
- add_magic_header_method header
548
+ add_magic_header_method(header)
474
549
  end
475
550
 
476
551
  # Bind +header+ to +prev_header+.
@@ -497,7 +572,7 @@ module PacketGen
497
572
 
498
573
  # Try to guess header from +binary_str+
499
574
  # @param [String] binary_str
500
- # @return [String] header/protocol name
575
+ # @return [String,nil] header/protocol name, or nil if cannot be guessed
501
576
  def guess_first_header(binary_str)
502
577
  first_header = nil
503
578
  Header.all.each do |hklass|
@@ -529,7 +604,7 @@ module PacketGen
529
604
  nheader = nheader.read(last_known_hdr.body)
530
605
  next unless nheader.parse?
531
606
 
532
- add_header nheader, parsing: true
607
+ add_header(nheader, parsing: true)
533
608
  break if last_header == last_known_hdr
534
609
  end
535
610
  end
@@ -8,8 +8,7 @@
8
8
  require_relative 'pcaprub_wrapper'
9
9
 
10
10
  module PacketGen
11
- # Module to read PCAP files
12
- # @author Sylvain Daubert
11
+ # Module to read/write PCAP files
13
12
  # @api private
14
13
  # @since 3.1.4
15
14
  module Pcap
@@ -17,14 +16,26 @@ module PacketGen
17
16
  # @param [String] filename
18
17
  # @return [Array<Packet>]
19
18
  # @author Kent Gruber
19
+ # @author LemonTree55
20
20
  def self.read(filename)
21
21
  packets = []
22
- PCAPRUBWrapper.read_pcap(filename: filename) do |packet|
23
- next unless (packet = PacketGen.parse(packet.to_s))
22
+ PCAPRUBWrapper.read_pcap(filename: filename) do |raw_packet|
23
+ packet = PacketGen.parse(raw_packet.to_s)
24
+ next if packet.nil?
24
25
 
25
26
  packets << packet
26
27
  end
27
28
  packets
28
29
  end
30
+
31
+ # Write binary packets to a PCAP file
32
+ # @param [String] filename
33
+ # @param [Array<String>] packets
34
+ # @return [void]
35
+ # @since 4.0.0
36
+ # @author LemonTree55
37
+ def self.write(filename, packets)
38
+ PCAPRUBWrapper.pcap_write(filename, packets.map(&:to_s))
39
+ end
29
40
  end
30
41
  end
@@ -10,63 +10,65 @@ module PacketGen
10
10
  module PcapNG
11
11
  # @abstract Base class for all block types
12
12
  # @author Sylvain Daubert
13
- class Block < Types::Fields
13
+ # @author LemonTree55
14
+ class Block < BinStruct::Struct
14
15
  # @return [:little, :big]
15
16
  attr_accessor :endian
16
17
 
17
18
  # @!attribute type
18
19
  # 32-bit block type
19
20
  # @return [Integer]
20
- define_field :type, Types::Int32
21
+ define_attr :type, BinStruct::Int32
21
22
  # @!attribute block_len
22
23
  # 32-bit block length
23
24
  # @return [Integer]
24
- define_field :block_len, Types::Int32
25
- # @!attribute block_len
25
+ define_attr :block_len, BinStruct::Int32
26
+ # @!attribute block_len2
26
27
  # 32-bit block length
27
28
  # @return [Integer]
28
- define_field :block_len2, Types::Int32
29
+ define_attr :block_len2, BinStruct::Int32
29
30
 
30
31
  def initialize(options={})
31
32
  super
33
+ endianness(options[:endian] || :little)
34
+ recalc_block_len
32
35
  end
33
36
 
34
37
  # Has this block option?
35
38
  # @return [Boolean]
36
39
  # @since 2.7.0
37
40
  def options?
38
- @fields.key?(:options) && @fields[:options].sz.positive?
41
+ @attributes.key?(:options) && @attributes[:options].sz.positive?
39
42
  end
40
43
 
41
- # Calculate block length and update :block_len and block_len2 fields
44
+ # Calculate block length and update +block_len+ and +block_len2+ fields
42
45
  # @return [void]
43
46
  def recalc_block_len
44
- len = fields.map { |f| @fields[f].to_s }.join.size
47
+ len = attributes.map { |f| @attributes[f].to_s }.join.size
45
48
  self.block_len = self.block_len2 = len
46
49
  end
47
50
 
48
51
  # Pad given field to 32 bit boundary, if needed
49
- # @param [Array<Symbol>] fields block fields to pad
52
+ # @param [Array<Symbol>] fields fields to pad
50
53
  # @return [void]
54
+ # @author LemonTree55
51
55
  def pad_field(*fields)
52
56
  fields.each do |field|
53
- obj = @fields[field]
54
- pad_size = (obj.sz % 4).zero? ? 0 : (4 - (obj.sz % 4))
55
- obj << "\x00" * pad_size
57
+ obj = @attributes[field]
58
+ obj << "\x00" * -(obj.sz % -4)
56
59
  end
57
60
  end
58
61
 
59
62
  private
60
63
 
61
64
  # Set the endianness for the various Int classes handled by self.
62
- # Must be called by all subclass #initialize method.
63
65
  # @param [:little, :big] endian
64
66
  # @return [:little, :big] returns endian
65
67
  def endianness(endian)
66
68
  raise ArgumentError, "unknown endianness for #{self.class}" unless %i[little big].include?(endian)
67
69
 
68
70
  @endian = endian
69
- @fields.each_value { |v| v.endian = endian if v.is_a?(Types::Int) }
71
+ @attributes.each_value { |v| v.endian = endian if v.is_a?(BinStruct::Int) }
70
72
  endian
71
73
  end
72
74
 
@@ -75,19 +77,19 @@ module PacketGen
75
77
  end
76
78
 
77
79
  def to_io(str_or_io)
78
- return str_or_io if str_or_io.respond_to? :read
80
+ return str_or_io if str_or_io.respond_to?(:read)
79
81
 
80
- StringIO.new(force_binary(str_or_io.to_s))
82
+ StringIO.new(str_or_io.to_s.b)
81
83
  end
82
84
 
83
85
  def remove_padding(io, data_len)
84
86
  data_pad_len = (4 - (data_len % 4)) % 4
85
- io.read data_pad_len
87
+ io.read(data_pad_len)
86
88
  data_pad_len
87
89
  end
88
90
 
89
91
  def read_blocklen2_and_check(io)
90
- self[:block_len2].read io.read(4)
92
+ self[:block_len2].read(io.read(4))
91
93
  check_len_coherency
92
94
  end
93
95
  end