dot11 0.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.
@@ -0,0 +1,4 @@
1
+ dot11
2
+ =======
3
+
4
+ A simple 802.11 packet parser.
@@ -0,0 +1,17 @@
1
+ class String
2
+ def indent(depth)
3
+ indented = ""
4
+ self.each_line do |line|
5
+ indented += " " * depth + line
6
+ end
7
+
8
+ indented
9
+ end
10
+ end
11
+
12
+ require File.expand_path(File.join(File.dirname(__FILE__), 'dot11', 'packet'))
13
+ require File.expand_path(File.join(File.dirname(__FILE__), 'dot11', 'packetset'))
14
+ require File.expand_path(File.join(File.dirname(__FILE__), 'dot11', 'macaddress'))
15
+ require File.expand_path(File.join(File.dirname(__FILE__), 'dot11', 'dot11'))
16
+ require File.expand_path(File.join(File.dirname(__FILE__), 'dot11', 'raw'))
17
+ require File.expand_path(File.join(File.dirname(__FILE__), 'dot11', 'radiotap'))
@@ -0,0 +1,974 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'macaddress'))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), 'packet'))
3
+ require File.expand_path(File.join(File.dirname(__FILE__), 'raw'))
4
+
5
+ module Dot11
6
+ class Dot11 < Packet
7
+ @@TYPENAMES = [["association request", "association response", "reassociation request", "reassociation response", "probe request", "probe response", "reserved0", "reserved1", "beacon", "ATIM", "disassociation", "authorization", "deauthorization", "reserved2", "reserved3", "reserved4"],
8
+ ["reserved0", "reserved1", "reserved2", "reserved3", "reserved4", "reserved5", "reserved6", "reserved7", "reserved8", "PS-poll", "RTS", "CTS", "ACK", "CF-end", "CF-end + CF-ack"],
9
+ ["data", "data + CF-ack", "data + CF-poll", "data + CF-ack + CF-poll", "null function (no data)", "CF-ack (no data)", "CF-poll (no data)", "CF-ack + CF-poll (no data)", "reserved0", "reserved1", "reserved2", "reserved3", "reserved4", "reserved5", "reserved6", "reserved7"],
10
+ ["reserved0", "reserved1", "reserved2", "reserved3", "reserved4", "reserved5", "reserved6", "reserved7", "reserved8", "reserved9", "reserved10", "reserved11", "reserved12", "reserved13", "reserved14", "reserved15"]]
11
+
12
+ def subtype
13
+ @subtype ||= 0
14
+ end
15
+
16
+ def subtype=(other)
17
+ @subtype = other
18
+ end
19
+
20
+ def type
21
+ @type ||= 0
22
+ end
23
+
24
+ def type=(other)
25
+ @type = other
26
+ end
27
+
28
+ def version
29
+ @version ||= 0
30
+ end
31
+
32
+ def version=(other)
33
+ @version = other
34
+ end
35
+
36
+ def flags
37
+ @flags ||= 0
38
+ end
39
+
40
+ def flags=(other)
41
+ @flags = other
42
+ end
43
+
44
+ def duration
45
+ @duration ||= 0
46
+ end
47
+
48
+ def duration=(other)
49
+ @duration = other
50
+ end
51
+
52
+ def addr1
53
+ @addr1 ||= 0
54
+ end
55
+
56
+ def addr1=(other)
57
+ if other.kind_of?(Integer) || other.kind_of?(String)
58
+ @addr1 = MACAddress.new(other)
59
+ elsif other.kind_of?(MACAddress)
60
+ @addr1 = other
61
+ else
62
+ raise "Unrecognized addr #{other.inspect}"
63
+ end
64
+ end
65
+
66
+ def addr2
67
+ @addr2 ||= 0
68
+ end
69
+
70
+ def addr2=(other)
71
+ if other.kind_of?(Integer) || other.kind_of?(String)
72
+ @addr2 = MACAddress.new(other)
73
+ elsif other.kind_of?(MACAddress)
74
+ @addr2 = other
75
+ else
76
+ raise "Unrecognized addr #{other.inspect}"
77
+ end
78
+ end
79
+
80
+ def addr3
81
+ @addr3 ||= 0
82
+ end
83
+
84
+ def addr3=(other)
85
+ if other.kind_of?(Integer) || other.kind_of?(String)
86
+ @addr3 = MACAddress.new(other)
87
+ elsif other.kind_of?(MACAddress)
88
+ @addr3 = other
89
+ else
90
+ raise "Unrecognized addr #{other.inspect}"
91
+ end
92
+ end
93
+
94
+ def sc
95
+ @sc ||= 0
96
+ end
97
+
98
+ def sc=(other)
99
+ @sc = other
100
+ end
101
+
102
+ def addr4
103
+ @addr4 ||= 0
104
+ end
105
+
106
+ def addr4=
107
+ if other.kind_of?(Integer) || other.kind_of?(String)
108
+ @addr4 = MACAddress.new(other)
109
+ elsif other.kind_of?(MACAddress)
110
+ @addr4 = other
111
+ else
112
+ raise "Unrecognized addr #{other.inspect}"
113
+ end
114
+ end
115
+
116
+ def payload=(other)
117
+ @payload = other
118
+ end
119
+
120
+ def data
121
+ buffer = [(subtype << 4) | (type << 2) | version, flags, duration].concat(addr1.to_arr).pack("CCSC6")
122
+
123
+ if (type == 1 && [0x0a, 0x0b, 0x0e, 0x0f].include?(subtype)) || (type != 1)
124
+ buffer += addr2.to_arr.pack("C6")
125
+ end
126
+
127
+ if [0, 2].include?(type)
128
+ buffer += addr3.to_arr.pack("C6")
129
+ end
130
+
131
+ if type != 1
132
+ buffer += [sc].pack("v")
133
+ end
134
+
135
+ if type == 2 && flags & 0x03 == 0x03
136
+ buffer += addr4.to_arr.pack("C6")
137
+ end
138
+
139
+ if payload
140
+ buffer += payload.data
141
+ end
142
+
143
+ buffer
144
+ end
145
+
146
+ def ==(other)
147
+ eql?(other)
148
+ end
149
+
150
+ def eql?(other)
151
+ return false unless other.kind_of?(Dot11)
152
+
153
+ basics = subtype.eql?(other.subtype) && type.eql?(other.type) && version.eql?(other.version) &&
154
+ flags.eql?(other.flags) && duration.eql?(other.duration) && addr1.eql?(other.addr1)
155
+
156
+ return false unless basics
157
+
158
+ if (type == 1 && [0x0a, 0x0b, 0x0e, 0x0f].include?(subtype)) || (type != 1)
159
+ return false unless addr2.eql?(other.addr2)
160
+ end
161
+
162
+ if [0, 2].include?(type)
163
+ return false unless addr3.eql?(other.addr3)
164
+ end
165
+
166
+ if type != 1
167
+ return false unless sc.eql?(other.sc)
168
+ end
169
+
170
+ if type == 2 && flags & 0x03 == 0x03
171
+ return false unless addr4.eql?(other.addr4)
172
+ end
173
+
174
+ return true
175
+ end
176
+
177
+ def /(other)
178
+ if @payload.nil?
179
+ @payload = other
180
+ return self
181
+ end
182
+
183
+ if @payload.respond_to?(:elements)
184
+ @payload.elements << other
185
+ end
186
+
187
+ self
188
+ end
189
+
190
+ alias to_s data
191
+
192
+ def inspect
193
+ binary_flags = flags.to_s(2)
194
+ flag_names = ['to-DS', 'from-DS', 'MF', 'retry', 'pw-mgt', 'MD', 'wep', 'order']
195
+ set_flags = []
196
+
197
+ 8.times do |i|
198
+ set_flags << flag_names[i] if binary_flags[7 - i] == ?1
199
+ end
200
+
201
+ "Dot11\n" + if @corrupt then " (corrupt)" else "" end +
202
+ "----------------\n" +
203
+ "type: ...... #{type} (#{%w(management control data reserved)[type]})\n" +
204
+ "subtype: ... #{subtype} (#{@@TYPENAMES[type][subtype]})\n" +
205
+ "version: ... #{version}\n" +
206
+ "flags: ..... #{"%#02x" % flags} (#{"0" * (8 - flags.to_s(2).length) + flags.to_s(2)}#{if set_flags.size > 0 then ' : ' + set_flags.join(', ') else '' end})\n" +
207
+ "duration: .. #{duration}\n" +
208
+ "addr1: ..... #{addr1}\n" +
209
+ (if addr2 then "addr2: ..... #{addr2}\n" else "" end) +
210
+ (if addr3 then "addr3: ..... #{addr3}\n" else "" end) +
211
+ (if sc then "sc: ........ #{"%#02x" % sc} (fragment: #{sc & 0x0F}; sequence: #{(sc & 0xFFF0) >> 4})\n" else "" end) +
212
+ (if addr4 then "addr4: ..... #{addr4}\n" else "" end) +
213
+ (if payload then "payload:\n#{payload.to_s.indent(6)}" else "" end)
214
+ end
215
+
216
+ # Lazily dissect the payload
217
+ def payload
218
+ return @payload if @payload
219
+
220
+ payload_class = if (@flags & 0x40) == 0x40
221
+ Dot11WEP
222
+ elsif @type == 0
223
+ [ Dot11AssoReq, Dot11AssoResp, Dot11ReassoReq, Dot11ReassoResp, Dot11ProbeReq, Dot11ProbeResp, nil, nil,
224
+ Dot11Beacon, Dot11ATIM, Dot11Disas, Dot11Auth, Dot11Deauth, nil, nil, nil,
225
+ nil, nil, nil, nil, nil, nil, nil, nil,
226
+ nil, nil, nil, nil, nil, nil, nil ][@subtype]
227
+ elsif @type == 2
228
+ if @subtype == 0
229
+ Dot11Data
230
+ elsif @subtype == 4
231
+ Dot11NullData
232
+ end
233
+ end
234
+
235
+ return nil if payload_class.nil?
236
+
237
+ @payload = payload_class.new(@rest) unless (payload_class == Dot11NullData || @rest.nil? || @rest.empty?)
238
+ end
239
+
240
+ def self.fields()
241
+ # Why can't ruby have ordered maps??
242
+ # The offsets are unnecessary but removing them would have been even more complicated
243
+ # TODO: remove offsets and compute possible offsets for each field automatically
244
+ [[:subtype, {:type => :int, :offset => 0, :size => 1, :bitrange => 4..7}],
245
+ [:type, {:type => :int, :offset => 0, :size => 1, :bitrange => 2..3}],
246
+ [:version, {:type => :int, :offset => 0, :size => 1, :bitrange => 0..1}],
247
+ [:flags, {:type => :int, :offset => 1, :size => 1}],
248
+ [:duration, {:type => :int, :offset => 2, :size => 2}],
249
+ [:addr1, {:type => :mac, :offset => 4, :size => 6}],
250
+ # Ugly syntax! TODO: come up with a better way to represent negation than a one-element array
251
+ [:addr2, {:type => :mac, :offset => 10, :size => 6, :condition => [{:subtype => [0x0a, 0x0b, 0x0e, 0x0f]}, {:type => [1]}]}],
252
+ [:addr3, {:type => :mac, :offset => {[{:subtype => [0x0a, 0x0b, 0x0e, 0x0f]}, {:type => [1]}] => 16, :else => 10}, :size => 6, :condition => {:type => [0, 2]}}],
253
+ #[:sc, {:type => :int, :size => 2, :offset => {}, => :condition => {:type => [1]}}]
254
+ ]
255
+ end
256
+
257
+ private
258
+
259
+ def dissect(data)
260
+ fields = data.unpack("CCSC6")
261
+
262
+ @subtype = (fields[0] & 0xF0) >> 4
263
+ @type = (fields[0] & 0x0C) >> 2
264
+ @version = fields[0] & 0x03
265
+
266
+ @flags = fields[1]
267
+ @duration = fields[2]
268
+
269
+ # The array2mac calculations could be lazy if we really needed speed
270
+ @addr1 = MACAddress.new(fields[3..-1])
271
+
272
+ @rest = data[10..-1]
273
+
274
+ if (@type == 1 && [0x0a, 0x0b, 0x0e, 0x0f].include?(@subtype)) || (@type != 1)
275
+ if !@rest || @rest.empty?
276
+ @corrupt = true
277
+ return
278
+ end
279
+
280
+ @addr2 = MACAddress.new(@rest.unpack("C6"))
281
+ @rest = @rest[6..-1]
282
+ end
283
+
284
+ if [0, 2].include?(@type)
285
+ if !@rest || @rest.empty?
286
+ @corrupt = true
287
+ return
288
+ end
289
+
290
+ @addr3 = MACAddress.new(@rest.unpack("C6"))
291
+ @rest = @rest[6..-1]
292
+ end
293
+
294
+ if @type != 1
295
+ if !@rest || @rest.empty?
296
+ @corrupt = true
297
+ return
298
+ end
299
+
300
+ @sc = @rest.unpack("v")[0]
301
+ @rest = @rest[2..-1]
302
+ end
303
+
304
+ if @type == 2 && @flags & 0x03 == 0x03
305
+ if !@rest || @rest.empty?
306
+ @corrupt = true
307
+ return
308
+ end
309
+
310
+ @addr4 = MACAddress.new(@rest.unpack("C6"))
311
+ @rest = @rest[6..-1]
312
+ end
313
+ end
314
+
315
+ class Dot11Elt < Packet
316
+ attr_accessor :id, :info_length, :info
317
+
318
+ def Dot11Elt.register_element(id, klass)
319
+ @@registered_elements ||= {}
320
+
321
+ @@registered_elements[id] = klass
322
+ end
323
+
324
+ def data
325
+ buffer = [id, info_length].pack("CC")
326
+
327
+ buffer += info
328
+ end
329
+
330
+ def to_s
331
+ "Dot11Elt\n" +
332
+ "------------\n" +
333
+ "id: ............ #{id}\n" +
334
+ "info_length: ... #{info_length}\n" +
335
+ "info: .......... #{info.inspect}\n"
336
+ end
337
+
338
+ private
339
+
340
+ def dissect(data)
341
+ fields = data.unpack("CC")
342
+
343
+ @id = fields[0]
344
+ @info_length = fields[1]
345
+
346
+ @info = data[2, @info_length]
347
+
348
+ @rest = data[2 + @info_length..-1]
349
+ end
350
+
351
+ class << self
352
+ # Hook into new to "subclass on the fly"
353
+ alias old_new new
354
+
355
+ def new(parameters)
356
+ return old_new(parameters) if self != Dot11Elt
357
+
358
+ if parameters.kind_of?(String)
359
+ elt_id = parameters.unpack("C")[0]
360
+
361
+ if @@registered_elements && @@registered_elements[elt_id]
362
+ return @@registered_elements[elt_id].new(parameters)
363
+ else
364
+ elt = Dot11Elt.allocate
365
+ elt.send(:initialize, parameters)
366
+
367
+ return elt
368
+ end
369
+
370
+ elsif parameters.kind_of?(Hash)
371
+
372
+ if @@registered_elements && @@registered_elements[parameters[:id]]
373
+ return @@registered_elements[parameters[:id]].new(parameters)
374
+ else
375
+ elt = Dot11Elt.allocate
376
+ elt.send(:initialize, parameters)
377
+
378
+ return elt
379
+ end
380
+ end
381
+
382
+ end
383
+
384
+ def element_id(id)
385
+ @id = id
386
+ Dot11Elt.register_element(id, self)
387
+ end
388
+ end
389
+ end
390
+
391
+ class Dot11EltSSID < Dot11Elt
392
+ element_id 0
393
+
394
+ def essid
395
+ return @info
396
+ end
397
+
398
+ def to_s
399
+ "Dot11EltSSID\n" +
400
+ "-------------\n" +
401
+ "id: ............ 0\n" +
402
+ "info_length: ... #{info_length}\n" +
403
+ "essid: ......... #{info.inspect} (#{essid})\n"
404
+ end
405
+ end
406
+
407
+ class Dot11EltRates < Dot11Elt
408
+ element_id 1
409
+
410
+ def rates
411
+ return @rates if @rates
412
+
413
+ @rates = []
414
+
415
+ @info.each_byte do |b|
416
+ @rates << (b & 0x7f) / 2
417
+ end
418
+
419
+ @rates
420
+ end
421
+
422
+ def to_s
423
+ "Dot11EltRates\n" +
424
+ "------------------\n" +
425
+ "id: ............ 1\n" +
426
+ "info_length: ... #{info_length}\n" +
427
+ "rates: ......... #{info.inspect} (#{rates.join(', ')})\n"
428
+ end
429
+ end
430
+
431
+ class Dot11EltESR < Dot11Elt
432
+ element_id 50
433
+
434
+ def rates
435
+ return @rates if @rates
436
+
437
+ @rates = []
438
+
439
+ @info.each_byte do |b|
440
+ @rates << (b & 0x7f) / 2
441
+ end
442
+
443
+ @rates
444
+ end
445
+
446
+ def to_s
447
+ "Dot11EltESR\n" +
448
+ "------------------\n" +
449
+ "id: ............ 50\n" +
450
+ "info_length: ... #{info_length}\n" +
451
+ "rates: ......... #{info.inspect} (#{rates.join(', ')})\n"
452
+ end
453
+ end
454
+
455
+ module Dot11EltContainer
456
+ def elements_by_id
457
+ hash = {}
458
+
459
+ elements.each do |element|
460
+ hash[element.id] = element
461
+ end
462
+
463
+ hash
464
+ end
465
+
466
+ def elements
467
+ if @elements.nil?
468
+ @elements = []
469
+
470
+ if @rest
471
+ dissect_elements(@rest)
472
+ end
473
+
474
+ return @elements
475
+ end
476
+
477
+ @elements
478
+ end
479
+
480
+ def element_data
481
+ buffer = ""
482
+
483
+ elements.each do |element|
484
+ buffer += element.data
485
+ end
486
+
487
+ buffer
488
+ end
489
+
490
+ def element_to_s
491
+ buffer = ""
492
+
493
+ elements.each do |element|
494
+ buffer += element.to_s + "\n"
495
+ end
496
+
497
+ buffer
498
+ end
499
+
500
+ def /(other)
501
+ elements << other
502
+
503
+ self
504
+ end
505
+
506
+ private
507
+
508
+ def dissect_elements(data)
509
+ @elements = []
510
+
511
+ current_pos = 0
512
+
513
+ while current_pos < data.length
514
+ info_length = data[current_pos, 2].unpack("xC")[0]
515
+ total_elt_length = 2 + info_length
516
+
517
+ @elements << Dot11Elt.new(data[current_pos, total_elt_length])
518
+
519
+ current_pos += total_elt_length
520
+ end
521
+
522
+ end
523
+ end
524
+
525
+ class Dot11Beacon < Packet
526
+ attr_accessor :timestamp, :beacon_interval, :capabilities
527
+
528
+ include Dot11EltContainer
529
+
530
+ def data
531
+ buffer = [timestamp & 0xFFFFFFFF, (timestamp & 0xFFFFFFFF00000000) >> 32, beacon_interval, capabilities].pack("V2vn")
532
+
533
+ buffer += element_data
534
+ end
535
+
536
+ def to_s
537
+ binary_caps = capabilities.to_s(2)
538
+ cap_names = ['ESS', 'IBSS', 'CF Pollable', 'CF Poll Request', 'Privacy', 'Reserved5', 'Reserved6', 'Reserved7', 'Reserved8', 'Reserved9', 'Reserved10', 'Reserved11', 'Reserved12', 'Reserved13', 'Reserved14', 'Reserved15', ]
539
+ set_caps = []
540
+
541
+ 16.times do |i|
542
+ set_caps << cap_names[i] if binary_caps[15 -i] == ?1
543
+ end
544
+
545
+ "Dot11Beacon\n" +
546
+ "-------------------------\n" +
547
+ "timestamp: ......... #{timestamp}\n" +
548
+ "beacon_interval: ... #{beacon_interval} (#{beacon_interval * 0.001024} seconds)\n" +
549
+ "capabilities: ...... #{capabilities} (#{"0" * (16 - binary_caps.length) + binary_caps}#{if set_caps.size > 0 then ' : ' + set_caps.join(', ') else '' end})\n" +
550
+ "elements:\n" +
551
+ element_to_s.indent(7)
552
+ end
553
+
554
+ private
555
+
556
+ def dissect(data)
557
+ fields = data.unpack("V2vn")
558
+
559
+ p fields
560
+
561
+ @timestamp = (fields[1] << 32) | fields[0]
562
+ @beacon_interval = fields[2]
563
+ @capabilities = fields[3]
564
+
565
+ @rest = data[12..-1]
566
+ end
567
+ end
568
+
569
+ class Dot11ATIM < Packet
570
+ def dissect(data)
571
+ raise "Not implemented"
572
+ end
573
+ end
574
+
575
+ class Dot11Disas < Packet
576
+ attr_accessor :reason
577
+
578
+ def data
579
+ [reason].pack("v")
580
+ end
581
+
582
+ def to_s
583
+ "Dot11Disas\n" +
584
+ "-------------\n" +
585
+ "reason: #{reason}\n"
586
+ end
587
+
588
+ private
589
+
590
+ def dissect(data)
591
+ @reason = data.unpack("v")[0]
592
+ end
593
+ end
594
+
595
+ class Dot11AssoReq < Packet
596
+ attr_accessor :capabilities, :listen_interval
597
+
598
+ include Dot11EltContainer
599
+
600
+ def data
601
+ buffer = [capabilities, listen_interval].pack("nv")
602
+
603
+ buffer += element_data
604
+ end
605
+
606
+ def to_s
607
+ "Dot11AssoReq\n" +
608
+ "---------------\n" +
609
+ "capabilities: ...... #{capabilities}\n" +
610
+ "listen_interval: ... #{listen_interval}\n" +
611
+ "elements:\n" +
612
+ element_to_s.indent(7)
613
+ end
614
+
615
+ private
616
+
617
+ def dissect(data)
618
+ fields = data.unpack("nv")
619
+
620
+ @capabilities = fields[0]
621
+ @listen_interval = fields[1]
622
+
623
+ @rest = data[4..-1]
624
+ end
625
+ end
626
+
627
+ class Dot11AssoResp < Packet
628
+ attr_accessor :capabilities, :status, :aid
629
+
630
+ include Dot11EltContainer
631
+
632
+ def data
633
+ buffer = [capabilities, status, aid].pack("nvv")
634
+
635
+ buffer += element_data
636
+ end
637
+
638
+ def to_s
639
+ "Dot11AssoResp\n" +
640
+ "---------------\n" +
641
+ "capabilities: ... #{capabilities}\n" +
642
+ "status: ......... #{status}\n" +
643
+ "aid: ............ #{aid}\n" +
644
+ "elements:\n" +
645
+ element_to_s.indent(7)
646
+ end
647
+
648
+ private
649
+
650
+ def dissect(data)
651
+ fields = data.unpack("nvv")
652
+
653
+ @capabilities = fields[0]
654
+ @status = fields[1]
655
+ @aid = fields[2]
656
+
657
+ @rest = data[6..-1]
658
+ end
659
+ end
660
+
661
+ class Dot11ReassoReq < Packet
662
+ attr_accessor :capabilities, :current_ap, :listen_interval
663
+
664
+ include Dot11EltContainer
665
+
666
+ def data
667
+ buffer = [capabilities].concat(mac2array(current_ap)).concat([listen_interval]).pack("nC6v")
668
+
669
+ buffer += element_data
670
+ end
671
+
672
+ def to_s
673
+ "Dot11ReassoReq\n" +
674
+ "---------------\n" +
675
+ "capabilities: ...... #{capabilities}\n" +
676
+ "current_ap: ........ #{current_ap}\n" +
677
+ "listen_interval: ... #{listen_interval}\n" +
678
+ "elements:\n" +
679
+ element_to_s.indent(7)
680
+ end
681
+
682
+ private
683
+
684
+ def dissect(data)
685
+ fields = data.unpack("nC6v")
686
+
687
+ @capabilities = fields[0]
688
+ @current_ap = Packet.array2mac(fields[1, 6])
689
+ @listen_interval = fields[7]
690
+
691
+ @rest = data[10..-1]
692
+ end
693
+ end
694
+
695
+ class Dot11ReassoResp < Packet
696
+ include Dot11EltContainer
697
+
698
+ def data
699
+ element_data
700
+ end
701
+
702
+ def to_s
703
+ "Dot11ReassoResp\n" +
704
+ "---------------\n" +
705
+ "elements:\n" +
706
+ element_to_s.indent(7)
707
+ end
708
+
709
+ private
710
+
711
+ def dissect(data)
712
+ @rest = data
713
+ end
714
+ end
715
+
716
+ class Dot11ProbeReq < Packet
717
+ include Dot11EltContainer
718
+
719
+ def data
720
+ element_data
721
+ end
722
+
723
+ def to_s
724
+ "Dot11ProbeReq\n" +
725
+ "---------------\n" +
726
+ "elements:\n" +
727
+ element_to_s.indent(7)
728
+ end
729
+
730
+ private
731
+
732
+ def dissect(data)
733
+ @rest = data
734
+ end
735
+ end
736
+
737
+ class Dot11ProbeResp < Packet
738
+ attr_accessor :timestamp, :beacon_interval, :capabilities
739
+
740
+ include Dot11EltContainer
741
+
742
+ def data
743
+ buffer = [timestamp & 0xFFFFFFFF, (timestamp & 0xFFFFFFFF00000000) >> 32, beacon_interval, capabilities].pack("V2vn")
744
+
745
+ buffer += element_data
746
+ end
747
+
748
+ def to_s
749
+ binary_caps = capabilities.to_s(2)
750
+ cap_names = ['ESS', 'IBSS', 'CF Pollable', 'CF Poll Request', 'Privacy', 'Reserved5', 'Reserved6', 'Reserved7', 'Reserved8', 'Reserved9', 'Reserved10', 'Reserved11', 'Reserved12', 'Reserved13', 'Reserved14', 'Reserved15', ]
751
+ set_caps = []
752
+
753
+ 16.times do |i|
754
+ set_caps << cap_names[i] if binary_caps[15 -i] == ?1
755
+ end
756
+
757
+ "Dot11ProbeResp\n" +
758
+ "-------------------------\n" +
759
+ "timestamp: ......... #{timestamp}\n" +
760
+ "beacon_interval: ... #{beacon_interval} (#{beacon_interval * 0.001024} seconds)\n" +
761
+ "capabilities: ...... #{capabilities} (#{"0" * (16 - binary_caps.length) + binary_caps}#{if set_caps.size > 0 then ' : ' + set_caps.join(', ') else '' end})\n" +
762
+ "elements:\n" +
763
+ element_to_s.indent(7)
764
+ end
765
+
766
+ private
767
+
768
+ def dissect(data)
769
+ fields = data.unpack("V2vn")
770
+
771
+ @timestamp = (fields[1] << 32) | fields[0]
772
+ @beacon_interval = fields[2]
773
+ @capabilities = fields[3]
774
+
775
+ @rest = data[12..-1]
776
+ end
777
+ end
778
+
779
+ class Dot11Auth < Packet
780
+ attr_accessor :algo, :seqnum, :status
781
+
782
+ include Dot11EltContainer
783
+
784
+ def data
785
+ buffer = [algo, seqnum, status].pack("vvv")
786
+
787
+ buffer += element_data
788
+ end
789
+
790
+ def to_s
791
+ "Dot11Auth\n" +
792
+ "-------------\n" +
793
+ "algo: #{algo}\n" +
794
+ "seqnum: #{seqnum}\n" +
795
+ "status: #{status}\n"
796
+ end
797
+
798
+ private
799
+
800
+ def dissect(data)
801
+ fields = data.unpack("vvv")
802
+
803
+ @algo = fields[0] || 0
804
+ @seqnum = fields[1] || 0
805
+ @status = fields[2] || 0
806
+
807
+ @rest = data[6..-1]
808
+ end
809
+ end
810
+
811
+ class Dot11Deauth < Packet
812
+ attr_accessor :reason
813
+
814
+ def data
815
+ [reason].pack("v")
816
+ end
817
+
818
+ def to_s
819
+ "Dot11Deauth\n" +
820
+ "-------------\n" +
821
+ "reason: #{reason}\n"
822
+ end
823
+
824
+ private
825
+
826
+ def dissect(data)
827
+ @reason = data.unpack("v")[0]
828
+ end
829
+ end
830
+
831
+ class Dot11Data < Packet
832
+ attr_accessor :payload
833
+
834
+ def data
835
+ payload.data
836
+ end
837
+
838
+ def to_s
839
+ "Dot11Data\n" +
840
+ "-------------\n" +
841
+ "payload: \n#{payload.to_s.indent(6)}\n"
842
+ end
843
+
844
+ def payload
845
+ return @payload if @payload
846
+
847
+ @payload = LLC.new(@rest)
848
+ end
849
+
850
+ def /(other)
851
+ @payload = other
852
+ self
853
+ end
854
+
855
+ private
856
+
857
+ def dissect(data)
858
+ @rest = data
859
+ end
860
+ end
861
+
862
+ class Dot11NullData < Packet
863
+ def data
864
+ ""
865
+ end
866
+
867
+ def to_s
868
+ "Dot11NullData\n"
869
+ end
870
+
871
+ private
872
+
873
+ def dissect(data)
874
+ @rest = data
875
+ end
876
+ end
877
+
878
+ class Dot11WEP < Packet
879
+ def data
880
+
881
+ end
882
+
883
+ def to_s
884
+ "Dot11WEP\n" +
885
+ "-------------\n" +
886
+ "unknown\n"
887
+ end
888
+
889
+ private
890
+
891
+ def dissect(data)
892
+ @rest = data
893
+ end
894
+ end
895
+
896
+ class LLC < Packet
897
+ attr_accessor :dsap, :ssap, :control, :payload
898
+
899
+ def data
900
+ [dsap, ssap, control].pack("CCC") + payload.data
901
+ end
902
+
903
+ def to_s
904
+ "LLC\n" +
905
+ "-------------\n" +
906
+ "dsap: #{dsap}\n" +
907
+ "ssap: #{ssap}\n" +
908
+ "control: #{control}\n" +
909
+ (if payload then "payload:\n#{payload.to_s.indent(6)}" else "" end)
910
+ end
911
+
912
+ def payload
913
+ return @payload if @payload
914
+
915
+ @payload = SNAP.new(@rest)
916
+ end
917
+
918
+ def /(other)
919
+ @payload = other
920
+ self
921
+ end
922
+
923
+ private
924
+
925
+ def dissect(data)
926
+ fields = data.unpack("CCC")
927
+
928
+ @dsap = fields[0]
929
+ @ssap = fields[1]
930
+ @control = fields[2]
931
+
932
+ @rest = data[3..-1]
933
+ end
934
+ end
935
+
936
+ class SNAP < Packet
937
+ attr_accessor :oui, :code, :payload
938
+
939
+ def data
940
+ [oui, code].pack("QXv") + payload.data
941
+ end
942
+
943
+ def to_s
944
+ "SNAP\n" +
945
+ "-------\n" +
946
+ "oui: #{oui}\n" +
947
+ "code: #{code}\n" +
948
+ (if payload then "payload:\n#{payload.to_s.indent(6)}" else "" end)
949
+ end
950
+
951
+ def payload
952
+ return @payload if @payload
953
+
954
+ return @payload = Raw.new(@rest)
955
+ end
956
+
957
+ def /(other)
958
+ @payload = other
959
+ self
960
+ end
961
+
962
+ private
963
+
964
+ def dissect(data)
965
+ fields = data.unpack("CCCv")
966
+
967
+ @oui = fields[0] << 16 | fields[1] << 8 | fields[2]
968
+ @code = fields[3]
969
+
970
+ @rest = data[4..-1]
971
+ end
972
+ end
973
+ end
974
+ end