packetgen 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f98c7d5b928554e3d6da92e42f96bf3ee0a397a8
4
- data.tar.gz: 994ede3f4bc303f98df8bd29e6bcb0b34932bf75
3
+ metadata.gz: 67bab87e209f3ad1ca3adecf77be2b19c1ba17a5
4
+ data.tar.gz: c5bd835c57160261677a22693e297c8b27ac3cbe
5
5
  SHA512:
6
- metadata.gz: d3a0d0198fda11d157d42ca626b6e59d0af792748b194d60dff4d0bd6a928e6b664eeb4d95d5ad70c94e3f38e690e8b616377eb95fffd9fe338158766235148c
7
- data.tar.gz: '09ba37f246d17b612f0ae247dab1408a799a9782d652e29ac3e798d8b46543225b506c983546b2e5acc7effdc52aca65f17f1fec1e3d32434f68edd535439b18'
6
+ metadata.gz: 466fadd588084aedad8c7e9fbc7ac6313f8f66d47594171fc7728c2056369ffc2e24fedb4794f4a37cd8fcc66ed4dff7db5339e1f19fefc3522d4906a8667953
7
+ data.tar.gz: c8bd3cb05a61ae939f152355d45fed6ff0e26b6760dd0bea98fddff4aa86263ec1eea27b24024325ae089246cb78baad3958b9df7a949771150d65c45cba7073
data/README.md CHANGED
@@ -4,17 +4,7 @@
4
4
 
5
5
  # PacketGen
6
6
 
7
- PacketGen provides simple ways to generate, send and capture network packets easily.
8
-
9
- ## Why PacketGen
10
- Why create PacketGen ? There is already PacketFu!
11
-
12
- Yes. But PacketFu is limited:
13
- * upper protocols use fixed layers: TCP always uses IPv4, IP and IPv6 always uses Ethernet as MAC,...
14
- * cannot handle tunneled packets (IP-in-IP, or deciphered ESP packets,...)
15
- * cannot easily encapsulate or decapsulate packets
16
- * parse packets top-down, and sometimes bad parse down layers
17
- * cannot send packet on wire at IP/IPv6 level (Ethernet header is mandatory)
7
+ PacketGen provides simple ways to generate, send and capture network packets.
18
8
 
19
9
  ## Installation
20
10
  Via RubyGems:
@@ -25,10 +15,11 @@ Or add it to a Gemfile:
25
15
  ```ruby
26
16
  gem 'packetgen'
27
17
  ```
18
+
28
19
  ## Usage
29
20
 
30
21
  ### Easily create packets
31
- ```
22
+ ```ruby
32
23
  PacketGen.gen('IP') # generate a IP packet object
33
24
  PacketGen.gen('TCP') # generate a TCP over IP packet object
34
25
  PacketGen.gen('IP').add('TCP') # the same
@@ -43,9 +34,7 @@ PacketGen.gen('IP').to_s
43
34
  ```
44
35
 
45
36
  ### Send packets on wire
46
- need PcapRub for Ethernet packets. Need a C extension (use of C socket API) for IP packets.
47
-
48
- ```
37
+ ```ruby
49
38
  # send Ethernet packet
50
39
  PacketGen.gen('Eth', src: '00:00:00:00:01', dst: '00:00:00:00:02').to_w
51
40
  # send IP packet
@@ -55,14 +44,12 @@ PacketGen.gen('Eth', src: '00:00:00:00:01', dst: '00:00:00:00:02').add('IP').to_
55
44
  ```
56
45
 
57
46
  ### Parse packets from binary data
58
- ```
47
+ ```ruby
59
48
  packet = PacketGen.parse(binary_data)
60
49
  ```
61
50
 
62
51
  ### Capture packets from wire
63
- need PCapRub.
64
-
65
- ```
52
+ ```ruby
66
53
  # Capture packets, action from a block
67
54
  PacketGen.capture('eth0') do |packet|
68
55
  do_stuffs_with_packet
@@ -76,7 +63,7 @@ packets = PacketGen.capture('eth0', filter: 'ip src 1.1.1.2', max: 1)
76
63
  ```
77
64
 
78
65
  ### Easily manipulate packets
79
- ```
66
+ ```ruby
80
67
  # access header fields
81
68
  pkt = PacketGen.gen('IP').add('TCP')
82
69
  pkt.ip.src = '192.168.1.1'
@@ -101,7 +88,7 @@ pkt2.decapsulate(pkt2.ip) # pkt2 is now inner IP/TCP packet
101
88
  ```
102
89
 
103
90
  ### Read/write PcapNG files
104
- ```
91
+ ```ruby
105
92
  # read a PcapNG file, containing multiple packets
106
93
  packets = PacketGen.read('file.pcapng')
107
94
  packets.first.udp.sport = 65535
@@ -112,44 +99,34 @@ PacketGen.write('more_packets.pcapng', packets)
112
99
  ```
113
100
 
114
101
  ### Add custom header/protocol
115
- Since v1.1.0, PacketGen permits adding you own header classes.
116
- First, define the new header class. By example:
102
+ Since v1.1.0, PacketGen permits adding your own header classes.
103
+ First, define the new header class. For example:
117
104
 
118
105
  ```ruby
119
106
  module MyModule
120
- class MyHeader < Struct.new(:field1, :field2)
121
- include PacketGen::StructFu
122
- include PacketGen::Header::HeaderMethods
123
- extend PacketGen::Header::HeaderClassMethods
124
-
125
- def initialize(options={})
126
- super Int32.new(options[:field1]), Int32.new(options[:field2])
127
- end
128
-
129
- def read(str)
130
- self[:field1].read str[0, 4]
131
- self[:field2].read str[4, 4]
132
- end
107
+ class MyHeader < PacketGen::Header::Base
108
+ define_field :field1, PacketGen::Types::Int32
109
+ define_field :field2, PacketGen::Types::Int32
133
110
  end
134
111
  end
135
112
  ```
136
113
 
137
114
  Then, class must be declared to PacketGen:
138
115
 
139
- ```
116
+ ```ruby
140
117
  PacketGen::Header.add_class MyModule::MyHeader
141
118
  ```
142
119
 
143
120
  Finally, bindings must be declared:
144
121
 
145
- ```
146
- # bind MyHeader as IP protocol number 254 (needed by Packet#parse)
122
+ ```ruby
123
+ # bind MyHeader as IP protocol number 254 (needed by Packet#parse and Packet#add)
147
124
  PacketGen::Header::IP.bind_header MyModule::MyHeader, protocol: 254
148
125
  ```
149
126
 
150
127
  And use it:
151
128
 
152
- ```
129
+ ```ruby
153
130
  pkt = Packet.gen('IP').add('MyHeader', field1: 0x12345678)
154
131
  pkt.myheader.field2.read 0x01
155
132
  ```
@@ -166,5 +143,5 @@ Copyright © 2016 Sylvain Daubert
166
143
  ### Other sources
167
144
  All original code maintains its copyright from its original authors and licensing.
168
145
 
169
- This is mainly for PcapNG (copied from [PacketFu](https://github.com/packetfu/packetfu),
146
+ This is mainly for PcapNG (originally copied from [PacketFu](https://github.com/packetfu/packetfu),
170
147
  but i am the original author).
data/lib/packetgen.rb CHANGED
@@ -85,6 +85,6 @@ end
85
85
 
86
86
  require 'packetgen/types'
87
87
  require 'packetgen/inspect'
88
+ require 'packetgen/pcapng'
88
89
  require 'packetgen/packet'
89
90
  require 'packetgen/capture'
90
- require 'packetgen/pcapng'
@@ -11,20 +11,14 @@ module PacketGen
11
11
  # First, define the new header class. By example:
12
12
  # module MyModule
13
13
  # class MyHeader < PacketGen::Header::Base
14
- # def initialize(options={})
15
- # super Int32.new(options[:field1]), Int32.new(options[:field2])
16
- # end
17
- #
18
- # def read(str)
19
- # self[:field1].read str[0, 4]
20
- # self[:field2].read str[4, 4]
21
- # end
14
+ # define_field :field1, PacketGen::Types::Int32
15
+ # define_field :field2, PacketGen::Types::Int32
22
16
  # end
23
17
  # end
24
18
  # Then, class must be declared to PacketGen:
25
19
  # PacketGen::Header.add_class MyModule::MyHeader
26
20
  # Finally, bindings must be declared:
27
- # # bind MyHeader as IP protocol number 254 (needed by Packet#parse)
21
+ # # bind MyHeader as IP protocol number 254 (needed by Packet#parse and Packet#add)
28
22
  # PacketGen::Header::IP.bind_header MyModule::MyHeader, protocol: 254
29
23
  # And use it:
30
24
  # pkt = Packet.gen('IP').add('MyHeader', field1: 0x12345678)
@@ -32,6 +26,13 @@ module PacketGen
32
26
  # @author Sylvain Daubert
33
27
  module Header
34
28
 
29
+ # @private snap length for PCAPRUB
30
+ PCAP_SNAPLEN = 0xffff
31
+ # @private promiscuous (or not) for PCAPRUB
32
+ PCAP_PROMISC = false
33
+ # @private timeout for PCAPRUB
34
+ PCAP_TIMEOUT = 1
35
+
35
36
  @added_header_classes = {}
36
37
 
37
38
  # Get known header classes
@@ -79,6 +80,10 @@ end
79
80
 
80
81
  require_relative 'header/base'
81
82
  require_relative 'header/eth'
83
+ require_relative 'header/dot11'
84
+ require_relative 'header/llc'
85
+ require_relative 'header/dot1q'
86
+ require_relative 'header/dot1x'
82
87
  require_relative 'header/ip'
83
88
  require_relative 'header/icmp'
84
89
  require_relative 'header/arp'
@@ -116,6 +116,7 @@ module PacketGen
116
116
  self.add_class ARP
117
117
 
118
118
  Eth.bind_header ARP, ethertype: 0x806
119
+ Dot1q.bind_header ARP, ethertype: 0x806
119
120
  end
120
121
  end
121
122
 
@@ -86,7 +86,7 @@ module PacketGen
86
86
  def check?(fields)
87
87
  case @op
88
88
  when :or
89
- @bindings.any? { |binding| binding.check?(fields) }
89
+ empty? || @bindings.any? { |binding| binding.check?(fields) }
90
90
  when :and
91
91
  @bindings.all? { |binding| binding.check?(fields) }
92
92
  end
@@ -100,6 +100,41 @@ module PacketGen
100
100
  end
101
101
  end
102
102
 
103
+ # @api private
104
+ # Class to handle header associations
105
+ class Bindings
106
+ include Enumerable
107
+
108
+ # op type
109
+ # @return [:or,:and]
110
+ attr_accessor :op
111
+ # @return [Array<Binding>]
112
+ attr_accessor :bindings
113
+
114
+ # @param [:or, :and] op
115
+ def initialize(op)
116
+ @op = op
117
+ @bindings = []
118
+ end
119
+
120
+ # @param [Object] arg
121
+ # @return [Bindings] self
122
+ def <<(arg)
123
+ @bindings << arg
124
+ end
125
+
126
+ # each iterator
127
+ # @return [void]
128
+ def each
129
+ @bindings.each { |b| yield b }
130
+ end
131
+
132
+ # @return [Boolean]
133
+ def empty?
134
+ @bindings.empty?
135
+ end
136
+ end
137
+
103
138
  # @api private
104
139
  # Reference on packet which owns this header
105
140
  attr_accessor :packet
@@ -144,6 +179,20 @@ module PacketGen
144
179
  @known_headers
145
180
  end
146
181
 
182
+ # Return header protocol name
183
+ # @return [String]
184
+ def protocol_name
185
+ self.class.to_s.sub(/.*::/, '')
186
+ end
187
+
188
+ # @abstract Should be redefined by subclasses. This method should check invariant
189
+ # fields from header.
190
+ # Call by {Packet#parse} when guessing first header to check if header is correct
191
+ # @return [Boolean]
192
+ def parse?
193
+ true
194
+ end
195
+
147
196
  # @api private
148
197
  # Get +header+ id in packet headers array
149
198
  # @param [Header] header
@@ -0,0 +1,349 @@
1
+ # coding: utf-8
2
+ # This file is part of PacketGen
3
+ # See https://github.com/sdaubert/packetgen for more informations
4
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
5
+ # This program is published under MIT license.
6
+ require 'zlib'
7
+
8
+ module PacketGen
9
+ module Header
10
+
11
+ # PPI (Per-Packet Information) packet
12
+ #@author Sylvain Daubert
13
+ class PPI < Base
14
+ # @!attribute version
15
+ # @return [Integer] 8-bit PPI version
16
+ define_field :version, Types::Int8, default: 0
17
+ # @!attribute flags
18
+ # @return [Integer] 8-bit PPI flags
19
+ define_field :flags, Types::Int8
20
+ # @!attribute length
21
+ # @return [Integer] 16-bit PPI header length
22
+ define_field :length, Types::Int16le, default: 8
23
+ # @!attribute dlt
24
+ # @return [Integer] 32-bit PPI data link type
25
+ define_field :dlt, Types::Int32le
26
+ # @!attribute ppi_fields
27
+ # @return [Type::String] concatenation of PPI fields
28
+ define_field :ppi_fields, Types::String
29
+ # @!attribute body
30
+ # @return [Type::String]
31
+ define_field :body, Types::String
32
+ # @!attribute align
33
+ # @return [Boolean] align flag from {#flags} attribute
34
+ define_bit_fields_on :flags, :reserved, 7, :align
35
+
36
+ # @param [String] str
37
+ # @return [PPI] self
38
+ def read(str)
39
+ return self if str.nil?
40
+ force_binary str
41
+ self[:version].read str[0, 1]
42
+ self[:flags].read str[1, 1]
43
+ self[:length].read str[2, 2]
44
+ self[:dlt].read str[4, 4]
45
+ self[:ppi_fields].read str[8, length - 8]
46
+ self[:body].read str[length, str.size]
47
+ self
48
+ end
49
+
50
+ # Check version field
51
+ # @see [Base#parse?]
52
+ def parse?
53
+ version == 0
54
+ end
55
+
56
+ # send PPI packet on wire. Dot11 FCS trailer should be set.
57
+ # @param [String] iface interface name
58
+ # @return [void]
59
+ def to_w(iface)
60
+ pcap = PCAPRUB::Pcap.open_live(iface, PCAP_SNAPLEN, PCAP_PROMISC,
61
+ PCAP_TIMEOUT)
62
+ pcap.inject self.to_s
63
+ end
64
+ end
65
+ self.add_class PPI
66
+
67
+ # Radiotap header
68
+ # @author Sylvain Daubert
69
+ class RadioTap < Base
70
+ # @!attribute version
71
+ # @return [Integer] 8-bit version
72
+ define_field :version, Types::Int8, default: 0
73
+ # @!attribute pad
74
+ # @return [Integer] 8-bit pad
75
+ define_field :pad, Types::Int8, default: 0
76
+ # @!attribute length
77
+ # @return [Integer] 16-bit RadioTap header length
78
+ define_field :length, Types::Int16le
79
+ # @!attribute present_flags
80
+ # @return [Integer] 32-bit integer
81
+ define_field :present_flags, Types::Int32le
82
+ # @!attribute radio_fields
83
+ # @return [Type::String] concatenation of RadioTap fields
84
+ define_field :radio_fields, Types::String
85
+ # @!attribute body
86
+ # @return [Type::String]
87
+ define_field :body, Types::String
88
+
89
+ # @param [String] str
90
+ # @return [RadioTap] self
91
+ def read(str)
92
+ return self if str.nil?
93
+ force_binary str
94
+ self[:version].read str[0, 1]
95
+ self[:pad].read str[1, 1]
96
+ self[:length].read str[2, 2]
97
+ self[:present_flags].read str[4, 4]
98
+ self[:radio_fields].read str[8, length - 8]
99
+ self[:body].read str[length, str.size]
100
+ self
101
+ end
102
+
103
+ # Check version field
104
+ # @see [Base#parse?]
105
+ def parse?
106
+ version == 0
107
+ end
108
+
109
+ # send RadioTap packet on wire. Dot11 FCS trailer should be set.
110
+ # @param [String] iface interface name
111
+ # @return [void]
112
+ def to_w(iface)
113
+ pcap = PCAPRUB::Pcap.open_live(iface, PCAP_SNAPLEN, PCAP_PROMISC,
114
+ PCAP_TIMEOUT)
115
+ pcap.inject self.to_s
116
+ end
117
+ end
118
+ self.add_class RadioTap
119
+
120
+ # IEEE 802.11 header
121
+ # @abstract This is a base class to demultiplex different IEEE 802.11 frames when
122
+ # parsing.
123
+ # A IEEE 802.11 header may consists of at least:
124
+ # * a {#frame_ctrl} ({Types::Int16}),
125
+ # * a {#id}/duration ({Types::Int16le}),
126
+ # * and a {#mac1} ({Eth::MacAddr}).
127
+ # Depending on frame type and subtype, it may also contains:
128
+ # * a {#mac2} ({Eth::MacAddr}),
129
+ # * a {#mac3} ({Eth::MacAddr}),
130
+ # * a {#sequence_ctrl} ({Types::Int16}),
131
+ # * a {#mac4} ({Eth::MacAddr}),
132
+ # * a {#qos_ctrl} ({Types::Int16}),
133
+ # * a {#ht_ctrl} ({Types::Int32}),
134
+ # * a {#body} (a {Types::String} or another {Base} class),
135
+ # * a Frame check sequence ({#fcs}, of type {Types::Int32le})
136
+ # @author Sylvain Daubert
137
+ class Dot11 < Base
138
+
139
+ # Frame types
140
+ TYPES = %w(Management Control Data Reserved).freeze
141
+
142
+ class << self
143
+ # Set a flag for parsing Dot11 packets. If set to +true+, parse FCS field,
144
+ # else don't. Default is +true+.
145
+ # @return [Boolean]
146
+ attr_accessor :has_fcs
147
+ end
148
+ Dot11.has_fcs = true
149
+
150
+ # @!attribute frame_ctrl
151
+ # @return [Integer] 16-bit frame control word
152
+ define_field :frame_ctrl, Types::Int16, default: 0
153
+ # @!attribute id
154
+ # @return [Integer] 16-bit ID/Duration word
155
+ define_field :id, Types::Int16le, default: 0
156
+ # @!attribute mac1
157
+ # @return [Eth::MacAddr]
158
+ define_field :mac1, Eth::MacAddr
159
+ # @!attribute mac2
160
+ # @return [Eth::MacAddr]
161
+ define_field :mac2, Eth::MacAddr
162
+ # @!attribute mac3
163
+ # @return [Eth::MacAddr]
164
+ define_field :mac3, Eth::MacAddr
165
+ # @!attribute sequence_ctrl
166
+ # @return [Integer] 16-bit sequence control word
167
+ define_field :sequence_ctrl, Types::Int16le, default: 0
168
+ # @!attribute mac4
169
+ # @return [Eth::MacAddr]
170
+ define_field :mac4, Eth::MacAddr
171
+ # @!attribute qos_ctrl
172
+ # @return [Integer] 16-bit QoS control word
173
+ define_field :qos_ctrl, Types::Int16
174
+ # @!attribute ht_ctrl
175
+ # @return [Integer] 16-bit HT control word
176
+ define_field :ht_ctrl, Types::Int32
177
+ # @!attribute body
178
+ # @return [Types::String]
179
+ define_field :body, Types::String
180
+ # @!attribute fcs
181
+ # @return [Types::Int32le]
182
+ define_field :fcs, Types::Int32le
183
+
184
+ # @!attribute subtype
185
+ # @return [Integer] 4-bit frame subtype from {#frame_ctrl}
186
+ # @!attribute type
187
+ # @return [Integer] 2-bit frame type from {#frame_ctrl}
188
+ # @!attribute proto_version
189
+ # @return [Integer] 2-bit protocol version from {#frame_ctrl}
190
+ # @!attribute order
191
+ # @return [Boolean] order flag from {#frame_ctrl}
192
+ # @!attribute wep
193
+ # @return [Boolean] wep flag from {#frame_ctrl}
194
+ # @!attribute md
195
+ # @return [Boolean] md flag from {#frame_ctrl}
196
+ # @!attribute pwmngt
197
+ # @return [Boolean] pwmngt flag from {#frame_ctrl}
198
+ # @!attribute retry
199
+ # @return [Boolean] retry flag from {#frame_ctrl}
200
+ # @!attribute mf
201
+ # @return [Boolean] mf flag from {#frame_ctrl}
202
+ # @!attribute from_ds
203
+ # @return [Boolean] from_ds flag from {#frame_ctrl}
204
+ # @!attribute to_ds
205
+ # @return [Boolean] to_ds flag from {#frame_ctrl}
206
+ define_bit_fields_on :frame_ctrl, :subtype, 4, :type, 2, :proto_version, 2,
207
+ :order, :wep, :md, :pwmngt, :retry, :mf, :from_ds, :to_ds
208
+
209
+ alias duration id
210
+ # @private
211
+ alias old_fields fields
212
+
213
+ # @param [Hash] options
214
+ # @see Base#initialize
215
+ def initialize(options={})
216
+ super
217
+ @applicable_fields = old_fields
218
+ end
219
+
220
+ # Get all used field names
221
+ # @return [Array<Symbol>]
222
+ def fields
223
+ @applicable_fields
224
+ end
225
+
226
+ # @private
227
+ alias old_read read
228
+
229
+ # Populate object from a binary string
230
+ # @param [String] str
231
+ # @return [Dot11] may return a subclass object if a more specific class
232
+ # may be determined
233
+ def read(str)
234
+ has_fcs = Dot11.has_fcs
235
+
236
+ if self.class == Dot11
237
+ return self if str.nil?
238
+ force_binary str
239
+ self[:frame_ctrl].read str[0, 2]
240
+
241
+ case type
242
+ when 0
243
+ Dot11::Management.new.read str
244
+ when 1
245
+ Dot11::Control.new.read str
246
+ when 2
247
+ Dot11::Data.new.read str
248
+ else
249
+ private_read str, has_fcs
250
+ end
251
+ else
252
+ private_read str, has_fcs
253
+ end
254
+ end
255
+
256
+ # Compute checksum and set +fcs+ field
257
+ # @return [Integer]
258
+ def calc_checksum
259
+ fcs = Zlib.crc32(to_s[0...-4])
260
+ self.fcs = fcs
261
+ fcs
262
+ end
263
+
264
+ # @return [String]
265
+ def to_s
266
+ define_applicable_fields
267
+ @applicable_fields.map { |f| force_binary @fields[f].to_s }.join
268
+ end
269
+
270
+ # Get human readable type
271
+ # @return [String]
272
+ def human_type
273
+ TYPES[type]
274
+ end
275
+
276
+ # @return [String]
277
+ def inspect
278
+ str = if self.class == Dot11
279
+ Inspect.dashed_line("#{self.class} #{human_type}", 2)
280
+ elsif self.respond_to? :human_subtype
281
+ Inspect.dashed_line("#{self.class} #{human_subtype}", 2)
282
+ else
283
+ Inspect.dashed_line("#{self.class}", 2)
284
+ end
285
+ define_applicable_fields
286
+ @applicable_fields.each do |attr|
287
+ next if attr == :body
288
+ str << Inspect.inspect_attribute(attr, @fields[attr], 2)
289
+ end
290
+ str
291
+ end
292
+
293
+ # send Dot11 packet on wire.
294
+ # @param [String] iface interface name
295
+ # @return [void]
296
+ def to_w(iface)
297
+ pcap = PCAPRUB::Pcap.open_live(iface, PCAP_SNAPLEN, PCAP_PROMISC,
298
+ PCAP_TIMEOUT)
299
+ str = self.to_s
300
+ pcap.inject str << [crc32].pack('V')
301
+ end
302
+
303
+ private
304
+
305
+ def define_applicable_fields
306
+ if to_ds? and from_ds?
307
+ @applicable_fields[6, 0] = :mac4 unless @applicable_fields.include? :mac4
308
+ else
309
+ @applicable_fields -= %i(mac4)
310
+ end
311
+ if order?
312
+ unless @applicable_fields.include? :ht_ctrl
313
+ idx = @applicable_fields.index(:body)
314
+ @applicable_fields[idx, 0] = :ht_ctrl
315
+ end
316
+ else
317
+ @applicable_fields -= %i(ht_ctrl)
318
+ end
319
+ if Dot11.has_fcs
320
+ @applicable_fields << :fcs unless @applicable_fields.include? :fcs
321
+ else
322
+ @applicable_fields -= %i(fcs)
323
+ end
324
+ end
325
+
326
+ def private_read(str, has_fcs)
327
+ self[:frame_ctrl].read str[0, 2]
328
+ define_applicable_fields
329
+ if has_fcs
330
+ old_read str[0...-4]
331
+ self[:fcs].read str[-4..-1]
332
+ else
333
+ old_read str
334
+ end
335
+ self
336
+ end
337
+ end
338
+
339
+ self.add_class Dot11
340
+ PPI.bind_header Dot11, dlt: PcapNG::LINKTYPE_IEEE802_11
341
+ RadioTap.bind_header Dot11
342
+ end
343
+ end
344
+
345
+ require_relative 'dot11/element'
346
+ require_relative 'dot11/management'
347
+ require_relative 'dot11/sub_mngt'
348
+ require_relative 'dot11/control'
349
+ require_relative 'dot11/data'