Implemented detection of packet direction and improved handling of data frames
[wifihood] / wifiscanner / wiviz.py
1 #!/usr/bin/python
2
3 import pcapy
4 import struct
5
6 iface = 'wlan0'
7
8 max_bytes = 1024
9 promiscuous = False
10 read_timeout = 100 # in milliseconds
11 pc = pcapy.open_live(iface, max_bytes, promiscuous, read_timeout)
12
13 # Removed leading IEEE80211_RADIOTAP_
14 ratiotap_header_bits = (
15 ( "TSFT" , 0 , "q" , False ) ,
16 ( "FLAGS" , 1 , "B" , True ) ,
17 ( "RATE" , 2 , "B" , True ) ,
18 ( "CHANNEL" , 3 , "hh" , False ) ,
19 ( "FHSS" , 4 , "h" , False ) ,
20 ( "DBM_ANTSIGNAL" , 5 , "b" , True ) ,
21 ( "DBM_ANTNOISE" , 6 , "b" , True ) ,
22 ( "LOCK_QUALITY" , 7 , "h" , False ) ,
23 ( "TX_ATTENUATION" , 8 , "h" , False ) ,
24 ( "DB_TX_ATTENUATION" , 9 , "h" , False ) ,
25 ( "DBM_TX_POWER" , 10 , "b" , True ) ,
26 ( "ANTENNA" , 11 , "B" , True ) ,
27 ( "DB_ANTSIGNAL" , 12 , "B" , True ) ,
28 ( "DB_ANTNOISE" , 13 , "B" , True ) ,
29 ( "RX_FLAGS" , 14 , "h" , False ) ,
30 ( "TX_FLAGS" , 15 , "h" , False ) ,
31 ( "RTS_RETRIES" , 16 , "B" , True ) ,
32 ( "DATA_RETRIES" , 17 , "B" , True ) ,
33 ( "EXT" , 31 , "" , False )
34
35
36 # Removed leading IEEE80211_CHAN_
37 channel_flags = (
38 ( "INDOOR"  , 0x0004 ) , # This channel can be used indoor
39 ( "OUTDOOR" , 0x0008 ) , # This channel can be used outdoor
40 ( "TURBO"   , 0x0010 ) , # Turbo channel
41 ( "CCK"     , 0x0020 ) , # CCK channel
42 ( "OFDM"    , 0x0040 ) , # OFDM channel
43 ( "2GHZ"    , 0x0080 ) , # 2 GHz spectrum channel.
44 ( "5GHZ"    , 0x0100 ) , # 5 GHz spectrum channel
45 ( "PASSIVE" , 0x0200 ) , # Only passive scan allowed
46 ( "DYN"     , 0x0400 ) , # Dynamic CCK-OFDM channel
47 ( "GFSK"    , 0x0800 ) , # GFSK channel (FHSS PHY)
48 ( "RADAR"   , 0x1000 ) , # Radar found on channel
49 ( "STURBO"  , 0x2000 ) , # 11a static turbo channel only
50 ( "HALF"    , 0x4000 ) , # Half rate channel
51 ( "QUARTER" , 0x8000 ) , # Quarter rate channel
52 )
53 # IEEE80211_CHAN_A  = IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM
54 # IEEE80211_CHAN_B  = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_CCK
55 # IEEE80211_CHAN_G  = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN
56 # IEEE80211_CHAN_TA = IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM | IEEE80211_CHAN_TURBO
57 # IEEE80211_CHAN_TG = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN  | IEEE80211_CHAN_TURBO
58
59 # Removed leading IEEE80211_RADIOTAP_F_
60 radiotap_flags = (
61 ( "CFP"      , 0x01 ) , # sent/received during CFP
62 ( "SHORTPRE" , 0x02 ) , # sent/received with short preamble
63 ( "WEP"      , 0x04 ) , # sent/received with WEP encryption
64 ( "FRAG"     , 0x08 ) , # sent/received with fragmentation
65 ( "FCS"      , 0x10 ) , # frame includes FCS
66 ( "DATAPAD"  , 0x20 )   # frame has padding between 802.11 header and payload (to 32-bit boundary)
67 )
68
69 # Removed leading IEEE80211_RADIOTAP_F_RX_
70 radiotap_rx_flags = (
71 ( "BADFCS" , 0x0001 )   # frame failed crc check
72 )
73
74 # Removed leading IEEE80211_RADIOTAP_F_TX_
75 radiotap_tx_flags = (
76 ( "FAIL" , 0x0001 ) , # failed due to excessive retries
77 ( "CTS"  , 0x0002 ) , # used cts 'protection'
78 ( "RTS"  , 0x0004 )   # used rts/cts handshake
79 )
80
81
82 # without IEEE80211_FC0_TYPE_
83 frame_type = (
84 ( "MGT"               , 0x00 ) ,
85 ( "CTL"               , 0x04 ) ,
86 ( "DATA"              , 0x08 )  
87 )
88 # without IEEE80211_FC0_SUBTYPE_
89 management_subtypes = (
90 ( "ASSOC_REQ"    , 0x00 ) ,
91 ( "ASSOC_RESP"   , 0x10 ) ,
92 ( "REASSOC_REQ"  , 0x20 ) ,
93 ( "REASSOC_RESP" , 0x30 ) ,
94 ( "PROBE_REQ"    , 0x40 ) ,
95 ( "PROBE_RESP"   , 0x50 ) ,
96 ( "BEACON"       , 0x80 ) ,
97 ( "ATIM"         , 0x90 ) ,
98 ( "DISASSOC"     , 0xA0 ) ,
99 ( "AUTH"         , 0xB0 ) ,
100 ( "DEAUTH"       , 0xC0 ) ,
101 ( "ACTION"       , 0xD0 )  
102 )
103 # without IEEE80211_FC0_SUBTYPE_
104 control_subtypes = (
105 ( "PS_POLL"        , 0xa0 ) ,
106 ( "RTS"            , 0xb0 ) ,
107 ( "CTS"            , 0xc0 ) ,
108 ( "ACK"            , 0xd0 ) ,
109 ( "CF_END"         , 0xe0 ) ,
110 ( "CF_END_ACK"     , 0xf0 )  
111 )
112 # without IEEE80211_FC0_SUBTYPE_
113 data_subtypes = (
114 ( "DATA"           , 0x00 ) ,
115 ( "CF_ACK"         , 0x10 ) ,
116 ( "CF_POLL"        , 0x20 ) ,
117 ( "CF_ACPL"        , 0x30 ) ,
118 ( "NULL"           , 0x40 ) ,
119 ( "CFACK"          , 0x50 ) ,
120 ( "CFPOLL"         , 0x60 ) ,
121 ( "CF_ACK_CF_ACK"  , 0x70 ) ,
122 ( "QOS"            , 0x80 ) ,
123 ( "QOS_NULL"       , 0xc0 )  
124 )
125
126 # without IEEE80211_FC1_DIR_
127 directions = (
128 ( "NODS"               , 0x00 ) , # STA->STA
129 ( "TODS"               , 0x01 ) , # STA->AP
130 ( "FROMDS"             , 0x02 ) , # AP ->STA
131 ( "DSTODS"             , 0x03 )   # AP ->AP
132 )
133
134 import time
135
136 max_time = 1
137 tstamp = time.time()
138 discovered = []
139
140 def dealWithPacket ( hdr , data ) :
141
142     if hdr.getlen() != hdr.getcaplen() :
143         print "Error in header : %d vs. %d" % ( hdr.getlen() , hdr.getcaplen() )
144         return
145     if len(data) != hdr.getlen() :
146         print "Data lenght does not match"
147         return
148
149     it_version , it_len , it_present = struct.unpack("<Bxhl",data[:8])
150     if it_version != 0 :
151         print "Bad version (%s), it is probably not radiotap header" % it_version
152         return
153     if it_len <= 0 :
154         print "Bad length on radiotap header"
155         return
156
157     radiotap = data[:it_len]
158     payload = data[it_len:]
159
160     format , padstr = "<" , ""
161     fields = []
162     for name,bit,fmt,pad in ratiotap_header_bits :
163 # What about  'it_present & ( 0x1 << bit )' ??
164         if it_present & pow(2,bit) == pow(2,bit) :
165             if not fmt :
166                 print "Unknown bit %s (%d) set" % ( name , bit )
167                 return
168             fields.append( name )
169             if fmt == "hh" :
170                 fields.append( "CHANNEL_BITMAP" )
171             if pad :
172                 padstr += "x"
173             format += fmt
174     values = struct.unpack(format+padstr,radiotap[8:])
175
176     flags = []
177     for name,value in radiotap_flags :
178         if values[1] & value == value :
179             flags.append( name )
180
181     channel = []
182     for name,value in channel_flags :
183         if values[4] & value == value :
184             channel.append( name )
185
186     pointer = 0
187     pcktlen = len(payload)
188
189     frame_ctl , frame_subtype , duration_id = struct.unpack("BBh",payload[:4])
190     pointer += 4
191     pcktlen -= 4
192
193     for name,value in frame_type :
194         if frame_ctl & 0x0c == value :
195             type = name
196             break
197     else :
198         print "Unknown frame type %s" % ( frame_ctl & 0x0c , )
199         return
200
201     if type == "MGT" :
202         for name,value in management_subtypes :
203             if frame_ctl & 0xf0 == value :
204                 subtype = name
205                 break
206         else :
207             print "Unknown MGT subtype %s" % ( frame_ctl & 0xf0 , )
208             return
209
210     elif type == "CTL" :
211         for name,value in control_subtypes :
212             if frame_ctl & 0xf0 == value :
213                 subtype = name
214                 break
215         else :
216             print "Unknown CTL subtype %s" % ( frame_ctl & 0xf0 , )
217             return
218
219     elif type == "DATA" :
220         _subtype = []
221         for name,value in data_subtypes :
222             if frame_ctl & 0xf0 == value :
223                 _subtype.append( name )
224         subtype = "-".join( _subtype )
225
226     for name,value in directions :
227         if frame_subtype & 0x03 == value :
228             direction = name
229             break
230     else :
231         print "Unknown direction %s" % ( frame_subtype & 0x03 , )
232         return
233
234
235     mac_str = "BBBBBB" # is leading '<' required
236     mac_fmt = "%02X:%02X:%02X:%02X:%02X:%02X"
237
238     maclist = []
239     for i in range(3) :
240         maclist.append( mac_fmt % struct.unpack( mac_str , payload[pointer:pointer+6] ) )
241         pointer += 6
242         pcktlen -= 6
243         # FIXME : only CTL/ACK have single address ???
244         # FIXME : Is this the same than pcktlen == 2
245         if pcktlen < 6 :
246             break
247
248     sequence = struct.unpack("BB",payload[pointer:pointer+2])
249     pointer += 2
250     pcktlen -= 2
251
252 #    print fields
253 #    print values
254 #    print "Radiotap flags : %s" % " ".join(flags)
255 #    print "Channel %d (%s)" % ( values[3] , " ".join(channel) )
256 ##    print "Control info",frame_ctl,frame_subtype,duration_id,"seq",sequence
257 #    print type,":",maclist,"->",pcktlen
258 #    print
259
260     global tstamp,max_time,discovered
261     for mac in maclist :
262         if not mac in discovered :
263             tstamp = time.time()
264             discovered.append( mac )
265     if time.time()-tstamp > max_time :
266         fd = open( "discovered.list" , "a" )
267         for mac in discovered :
268             fd.write( "%s\n" % mac )
269         fd.close()
270         raise Exception( "Neighborhoud scan completed" )
271
272     print "%4s %13s %6s %4d %4d from %4d" % (type,subtype,direction,values[3],pcktlen,len(payload))," ; %4d %4d "%sequence,":"+" %s"*len(maclist) % tuple(maclist)
273
274 packet_limit = -1 # infinite
275 pc.loop( packet_limit , dealWithPacket )
276