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,51 @@
1
+ module Dot11
2
+ class MACAddress
3
+ include Comparable
4
+
5
+ attr_accessor :prefix_length
6
+
7
+ def initialize(address)
8
+ if address.kind_of?(Integer)
9
+ @address = [address].pack("Q").unpack("C8")[0, 6].reverse
10
+ elsif address.kind_of?(String) && address =~ /^(.*?)(?:\/(.*))?$/
11
+ @prefix_length = $2.to_i
12
+
13
+ @address = $1.split(":").map {|octet| octet.to_i(16)}
14
+ elsif address.kind_of?(Array)
15
+ @address = address
16
+ end
17
+ end
18
+
19
+ def to_i
20
+ ("\x00\x00" + @address.pack("C6")).reverse.unpack("Q")[0]
21
+ end
22
+
23
+ def to_s
24
+ @address.map{|byte| "%02x" % byte}.join(":") + (@prefix_length == 0 ? "" : "/#{@prefix_length}")
25
+ end
26
+
27
+ def inspect
28
+ to_s
29
+ end
30
+
31
+ def to_arr
32
+ @address
33
+ end
34
+
35
+ def [](*index)
36
+ @address[*index]
37
+ end
38
+
39
+ def eql?(other)
40
+ to_i == other.to_i
41
+ end
42
+
43
+ def ==(other)
44
+ eql?(other)
45
+ end
46
+
47
+ def hash
48
+ to_i.hash
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'packetset'))
2
+
3
+ module Dot11
4
+ class Packet
5
+ class <<self
6
+ alias older_new new
7
+
8
+ def new(parameters = {})
9
+ # Maybe we shouldn't do it this way, but it looks prettier
10
+ if !parameters.kind_of?(String) && parameters.values.any? {|v| v.kind_of?(Array) || v.kind_of?(Range)}
11
+ PacketSet.new(self, parameters)
12
+ else
13
+ older_new(parameters)
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(parameters = {})
19
+ if parameters.kind_of?(String)
20
+ dissect(parameters)
21
+ elsif parameters.kind_of?(Hash)
22
+ parameters.each_pair do |key, value|
23
+ send((key.to_s + "=").intern, value)
24
+ end
25
+ end
26
+ end
27
+
28
+ def =~(other)
29
+ if other.kind_of?(Hash)
30
+ other.each_pair do |key, value|
31
+ return false if self.send(key) != value
32
+ end
33
+
34
+ return true
35
+ end
36
+ end
37
+
38
+ def to_filter
39
+ PacketSet.new(self.class, self.instance_variables.inject({}) do |accum, var|
40
+ accum[var[1..-1].intern] = self.instance_variable_get(var).kind_of?(MACAddress) ? self.instance_variable_get(var).to_s : self.instance_variable_get(var).to_s
41
+ accum
42
+ end).to_filter
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,253 @@
1
+ module Dot11
2
+ class PacketSet
3
+ Pair = Struct.new(:name, :value)
4
+
5
+ attr_accessor :packet_class
6
+ attr_reader :fields
7
+
8
+ def initialize(klass, parameters)
9
+ @fields = []
10
+
11
+ @packet_class = klass
12
+
13
+ parameters.each_pair do |key, value|
14
+ @fields << Pair.new(key, value)
15
+ end
16
+
17
+ @field_sizes = @fields.map do |field|
18
+ if field.value.respond_to?(:size) && !(field.value.kind_of?(String) || field.value.kind_of?(Numeric))
19
+ field.value.size
20
+ elsif field.value.respond_to?(:entries)
21
+ field.value.entries.size
22
+ else
23
+ field.value = [field.value]
24
+ 1
25
+ end
26
+ end
27
+ end
28
+
29
+ def [](index)
30
+ indices = @field_sizes.inject([]) do |accumulator, size|
31
+ remainder = index % size
32
+ index /= size # yeah textmate isn't perfect and that's why I need this slash: /
33
+ accumulator << remainder
34
+ end
35
+
36
+ field_hash = {}
37
+
38
+ indices.each_with_index do |index, i|
39
+ value = @fields[i].value
40
+
41
+ if value.kind_of?(Array) or value.kind_of?(Range)
42
+ field_hash[@fields[i].name] = @fields[i].value.entries[index]
43
+ else
44
+ field_hash[@fields[i].name] = @fields[i].value
45
+ end
46
+ end
47
+
48
+ out = @packet_class.new(field_hash)
49
+
50
+ out
51
+ end
52
+
53
+ def size
54
+ @field_sizes.inject(1) { |product, size| size * product }
55
+ end
56
+
57
+ def each(prefix = [])
58
+ size.times do |i|
59
+ yield self[i]
60
+ end
61
+ end
62
+
63
+ def each_with_index
64
+ size.times do |i|
65
+ yield self[i], i
66
+ end
67
+ end
68
+
69
+ def include?(packet)
70
+ # This is horrrrrribly inefficient. FIXME
71
+ each do |pkt|
72
+ return true if packet == pkt
73
+ end
74
+
75
+ false
76
+ end
77
+
78
+ def bitrange_to_filter(offset, size, bitrange, desired, negated = false)
79
+ # TODO: Take bitrange.exclude_end? into account
80
+ mask = ((2 << (bitrange.end - bitrange.begin)) - 1) << bitrange.begin
81
+
82
+ value = "(ether[#{offset}:#{size}] & #{'%#x' % mask}) >> #{bitrange.begin}"
83
+
84
+ case desired
85
+ when Range
86
+ "(#{value} >= #{'%#x' % desired.begin} && #{value} #{desired.exclude_end? ? "<" : "<="} #{'%#x' % desired.end})"
87
+ when Array
88
+ # TODO: deal with arrays of ranges
89
+ '(' + desired.map {|d| "#{value} = #{'%#x' % d}"}.join(" || ") + ')'
90
+ when Numeric, String
91
+ "#{value} #{negated ? '!' : ''}= #{'%#x' % desired.to_i}"
92
+ else
93
+ raise "unknown"
94
+ end
95
+ end
96
+
97
+ def int_to_filter(offset, size, desired, negated = false)
98
+ value = "ether[#{offset}:#{size}]"
99
+
100
+ case desired
101
+ when Range
102
+ "(#{value} >= #{'%#x' % desired.begin} && #{value} #{desired.exclude_end? ? "<" : "<="} #{'%#x' % desired.end})"
103
+ when Array
104
+ # TODO: deal with arrays of ranges
105
+ desired.map {|d| "#{value} #{negated ? '!' : ''}= #{'%#x' % d}"}.join(" || ")
106
+ when Numeric
107
+ "#{value} #{negated ? '!' : ''}= #{'%#x' % desired}"
108
+ else
109
+ raise "unknown desired: #{desired.inspect}"
110
+ end
111
+ end
112
+
113
+ def mac_to_filter(offset, size, desired, negated = false)
114
+ case desired
115
+ when Range
116
+ raise "Range of MAC addresses in filter not yet implemented"
117
+ when Array
118
+ '(' + desired.map do |d|
119
+ desired = MACAddress.new(desired) if desired.kind_of?(String)
120
+ first_four = desired[0, 4].pack("CCCC").unpack("N")[0]
121
+ second_two = desired[4, 2].pack("CC").unpack("n")[0]
122
+
123
+ "(ether[#{offset}:2] = #{'%#x' % first_four} && ether[#{offset + 4}:2] = #{'%#x' % second_two})"
124
+ end.join(" || ") + ')'
125
+ when Numeric
126
+
127
+ when MACAddress, String
128
+ desired = MACAddress.new(desired) if desired.kind_of?(String)
129
+ first_four = desired[0, 4].pack("CCCC").unpack("N")[0]
130
+ second_two = desired[4, 2].pack("CC").unpack("n")[0]
131
+
132
+
133
+ if desired.prefix_length == 32
134
+ "ether[#{offset}:4] = #{'%#x' % first_four}"
135
+ elsif desired.prefix_length == 48 || desired.prefix_length == 0
136
+ "(ether[#{offset}:4] = #{'%#x' % first_four} && ether[#{offset + 4}:2] = #{'%#x' % second_two})"
137
+ else
138
+ raise "unsupport prefix length #{desired.prefix_length}"
139
+ end
140
+ else
141
+ raise "unknown"
142
+ end
143
+ end
144
+
145
+ def decode_condition(fields, condition)
146
+ case condition
147
+ when Array
148
+ condition.map{|x| decode_condition(fields, x)}.join(" || ")
149
+ when Hash
150
+ condition.map do |key, value|
151
+ # Decode the nasty negation syntax
152
+ negated, value = value.kind_of?(Array) && value.size == 1 ? [true, value[0]] : [false, value]
153
+
154
+ field_info = fields.find{|x| key == x[0]}[1]
155
+
156
+ case field_info[:type]
157
+ when :int
158
+ if field_info.has_key?(:bitrange)
159
+ bitrange_to_filter(field_info[:offset], field_info[:size], field_info[:bitrange], value, negated)
160
+ else
161
+ int_to_filter(field_info[:offset], field_info[:size], value, negated)
162
+ end
163
+ when :mac
164
+ mac_to_filter(field_info[:offset], field_info[:size], value, negated)
165
+ else
166
+ raise "unknown"
167
+ end
168
+ end.join(" && ")
169
+ end
170
+ end
171
+
172
+ def to_filter
173
+ fields = @packet_class.fields
174
+
175
+ @fields.map do |field|
176
+ '(' + begin
177
+ field_info = fields.find{|x| field.name == x[0]}
178
+
179
+ if field_info.nil?
180
+ raise "nil field_info"
181
+ end
182
+
183
+ field_info = field_info[1]
184
+
185
+ # We don't really want to do this
186
+ if field_info.nil?
187
+ raise "nil field_info 2"
188
+ end
189
+
190
+ if field_info.has_key?(:condition)
191
+ if !field_info[:offset].kind_of?(Numeric)
192
+ field_info[:offset].map do |key, value|
193
+ '(' + begin
194
+ if key == :else
195
+ field_info[:offset].keys.reject{|k| k == :else}.map do |k|
196
+ '!(' + decode_condition(fields, k) + ')'
197
+ end.join(" && ")
198
+ else
199
+ '(' + decode_condition(fields, key) + ') && (' + begin
200
+ case field_info[:type]
201
+ when :int
202
+ if field_info.has_key?(:bitrange)
203
+ bitrange_to_filter(value, field_info[:size], field_info[:bitrange], field.value)
204
+ else
205
+ int_to_filter(value, field_info[:size], field.value)
206
+ end
207
+ when :mac
208
+ mac_to_filter(value, field_info[:size], field.value)
209
+ else
210
+ raise "unknown"
211
+ end
212
+ end
213
+ end
214
+ end + ')'
215
+ end.join(" && ")
216
+ else
217
+ '((' + decode_condition(fields, field_info[:condition]) +') && ('+ case field_info[:type]
218
+ when :int
219
+ if field_info.has_key?(:bitrange)
220
+ bitrange_to_filter(field_info[:offset], field_info[:size], field_info[:bitrange], field.value)
221
+ else
222
+ int_to_filter(field_info[:offset], field_info[:size], field.value)
223
+ end
224
+ when :mac
225
+ mac_to_filter(field_info[:offset], field_info[:size], field.value)
226
+ else
227
+ raise "unknown"
228
+ end + '))'
229
+ end
230
+ else
231
+ if !field_info[:offset].kind_of?(Numeric)
232
+ p "moo"
233
+ "can"
234
+ else
235
+ case field_info[:type]
236
+ when :int
237
+ if field_info.has_key?(:bitrange)
238
+ bitrange_to_filter(field_info[:offset], field_info[:size], field_info[:bitrange], field.value)
239
+ else
240
+ int_to_filter(field_info[:offset], field_info[:size], field.value)
241
+ end
242
+ when :mac
243
+ mac_to_filter(field_info[:offset], field_info[:size], field.value)
244
+ else
245
+ raise "unknown"
246
+ end
247
+ end
248
+ end
249
+ end + ')'
250
+ end.join(" && ")
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'packet'))
2
+
3
+ module Dot11
4
+ class Radiotap < Packet
5
+ attr_accessor :revision, :pad, :stuff_length, :stuff
6
+
7
+ def data
8
+ raise "This space intentionally left blank"
9
+ end
10
+
11
+ def to_s
12
+ "Radiotap\n" +
13
+ "-------------\n" +
14
+ "payload:\n" +
15
+ payload.to_s.indent(6)
16
+ end
17
+
18
+ def payload
19
+ return @payload if @payload
20
+
21
+ @payload = Dot11.new(@rest)
22
+ end
23
+
24
+ private
25
+
26
+ def dissect(data)
27
+ fields = data.unpack("CCv")
28
+
29
+ @data = data
30
+ @revision = fields[0]
31
+ @pad = fields[1]
32
+ @stuff_length = fields[2]
33
+
34
+ @rest = data[@stuff_length..-1]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'packet'))
2
+
3
+ module Dot11
4
+ class Raw < Packet
5
+ attr_accessor :data
6
+
7
+ def to_s
8
+ "Raw\n" +
9
+ "------\n" +
10
+ "data: #{data.inspect}\n"
11
+ end
12
+
13
+ private
14
+
15
+ def dissect(data)
16
+ @data = data
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,133 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'dot11', 'dot11'))
2
+
3
+ include Dot11
4
+
5
+ def test(klass, raw, packet, test_desc)
6
+ dissector = (klass.new(raw) == packet)
7
+ builder = (packet.data == raw)
8
+
9
+ puts "#{klass} dissector #{dissector ? 'passed' : 'failed'} on #{test_desc}"
10
+ puts "#{klass} builder #{builder ? 'passed' : 'failed'} on #{test_desc}"
11
+ end
12
+
13
+ #####################################################################################
14
+
15
+ beacon_raw = "\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\xe3\x41\xbd\x6e\x00\x01\xe3\x41\xbd\x6e\x00\x0e"
16
+ beacon = Dot11.new(:type => 0,
17
+ :subtype => 8,
18
+ :version => 0,
19
+ :flags => 0,
20
+ :duration => 0,
21
+ :addr1 => "ff:ff:ff:ff:ff:ff",
22
+ :addr2 => "00:01:e3:41:bd:6e",
23
+ :addr3 => "00:01:e3:41:bd:6e",
24
+ :sc => 0x0e00)
25
+
26
+ test(Dot11, beacon_raw, beacon, "beacon frame")
27
+
28
+ #####################################################################################
29
+
30
+ data_raw = "\x08\x42\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\xe3\x41\xbd\x6e\x00\x01\xe3\x42\x9e\x2b\x40\x07"
31
+ data = Dot11.new(:type => 2,
32
+ :subtype => 0,
33
+ :version => 0,
34
+ :flags => 0x42,
35
+ :duration => 0,
36
+ :addr1 => "ff:ff:ff:ff:ff:ff",
37
+ :addr2 => "00:01:e3:41:bd:6e",
38
+ :addr3 => "00:01:e3:42:9e:2b",
39
+ :sc => 0x0740)
40
+
41
+ test(Dot11, data_raw, data, "data frame")
42
+
43
+ #####################################################################################
44
+
45
+ complete_beacon_raw = "\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\xe3\x41\xbd\x6e" +
46
+ "\x00\x01\xe3\x41\xbd\x6e\xf0\x02\x86\xf1\x1b\x6a\x02\x00\x00\x00" +
47
+ "\x64\x00\x11\x04\x00\x09\x6d\x61\x72\x74\x69\x6e\x65\x74\x33\x01" +
48
+ "\x08\x82\x84\x8b\x96\x24\x30\x48\x6c\x03\x01\x0b\x05\x04\x00\x01" +
49
+ "\x00\x00\x2a\x01\x04\x2f\x01\x04\x32\x04\x0c\x12\x18\x60\xdd\x06" +
50
+ "\x00\x10\x18\x01\x01\x00\xdd\x16\x00\x50\xf2\x01\x01\x00\x00\x50" +
51
+ "\xf2\x02\x01\x00\x00\x50\xf2\x02\x01\x00\x00\x50\xf2\x02"
52
+ complete_beacon = Dot11.new(:type => 0,
53
+ :subtype => 8,
54
+ :version => 0,
55
+ :flags => 0,
56
+ :duration => 0,
57
+ :addr1 => "ff:ff:ff:ff:ff:ff",
58
+ :addr2 => "00:01:e3:41:bd:6e",
59
+ :addr3 => "00:01:e3:41:bd:6e",
60
+ :sc => 0x02f0) /
61
+ Dot11Beacon.new(:timestamp => 0x26a1bf186,
62
+ :beacon_interval => 0x64,
63
+ :capabilities => 0x1104) /
64
+ Dot11Elt.new(:id => 0,
65
+ :info_length => 9,
66
+ :info => "martinet3") /
67
+ Dot11Elt.new(:id => 1,
68
+ :info_length => 8,
69
+ :info => "\x82\x84\x8b\x96\x24\x30\x48\x6c") /
70
+ Dot11Elt.new(:id => 3,
71
+ :info_length => 1,
72
+ :info => "\x0b") /
73
+ Dot11Elt.new(:id => 5,
74
+ :info_length => 4,
75
+ :info => "\x00\x01\x00\x00") /
76
+ Dot11Elt.new(:id => 42,
77
+ :info_length => 1,
78
+ :info => "\x04") /
79
+ Dot11Elt.new(:id => 47,
80
+ :info_length => 1,
81
+ :info => "\x04") /
82
+ Dot11Elt.new(:id => 50,
83
+ :info_length => 4,
84
+ :info => "\x0c\x12\x18\x60") /
85
+ Dot11Elt.new(:id => 221,
86
+ :info_length => 6,
87
+ :info => "\x00\x10\x18\x01\x01\x00") /
88
+ Dot11Elt.new(:id => 221,
89
+ :info_length => 22,
90
+ :info => "\x00\x50\xf2\x01\x01\x00\x00\x50\xf2\x02\x01\x00\x00\x50\xf2\x02\x01\x00\x00\x50\xf2\x02")
91
+
92
+ test(Dot11, complete_beacon_raw, complete_beacon, "complete beacon frame")
93
+ p complete_beacon
94
+
95
+ #####################################################################################
96
+
97
+ acknowledgement_raw = "\xd4\x00\x00\x00\x00\x15\x00\x34\x18\x52"
98
+ acknowledgement = Dot11.new(:type => 1,
99
+ :subtype => 13,
100
+ :version => 0,
101
+ :flags => 0,
102
+ :duration => 0,
103
+ :addr1 => "00:15:00:34:18:52")
104
+
105
+ test(Dot11, acknowledgement_raw, acknowledgement, "acknowledgement frame")
106
+ puts("Passed to_i test") if acknowledgement.addr1.to_i == 0x1500341852
107
+ p acknowledgement
108
+
109
+ #####################################################################################
110
+
111
+ probereq_raw = "\x40\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x16\xbc\x3d\xaa\x57\xff" +
112
+ "\xff\xff\xff\xff\xff\xb0\x00\x00\x09\x6d\x61\x72\x74\x69\x6e\x65\x74" +
113
+ "\x33\x01\x08\x82\x84\x8b\x96\x0c\x12\x18\x24\x03\x01\x09\x32\x04\x30" +
114
+ "\x48\x60\x6c"
115
+ probereq = Dot11.new(:type => 0,
116
+ :subtype => 4,
117
+ :version => 0,
118
+ :flags => 0,
119
+ :duration => 0,
120
+ :addr1 => "ff:ff:ff:ff:ff:ff",
121
+ :addr2 => "00:16:bc:3d:aa:57",
122
+ :addr3 => "ff:ff:ff:ff:ff:ff",
123
+ :sc => 0x00b0) /
124
+ Dot11ProbeReq.new() /
125
+ Dot11Elt.new(:id => 0, :info_length => 9, :info => "martinet3") /
126
+ Dot11Elt.new(:id => 1, :info_length => 8, :info => "\x82\x84\x8b\x96\x0c\x12\x18\x24") /
127
+ Dot11Elt.new(:id => 3, :info_length => 1, :info => "\x09") /
128
+ Dot11Elt.new(:id => 50, :info_length => 4, :info => "\x30\x48\x60\x6c")
129
+
130
+ test(Dot11, probereq_raw, probereq, "probe request frame")
131
+ p probereq
132
+
133
+ #####################################################################################