packetgen 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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'