Create a specific function for ratiotap parsing
[wifihood] / wifiscanner / wiviz.py
index 5710393..7d0bbc6 100755 (executable)
@@ -10,86 +10,7 @@ promiscuous = False
 read_timeout = 100 # in milliseconds
 pc = pcapy.open_live(iface, max_bytes, promiscuous, read_timeout)
 
-# Removed leading IEEE80211_RADIOTAP_
-ratiotap_header_bits = (
-( "TSFT" , 0 , "q" , False ) ,
-( "FLAGS" , 1 , "B" , True ) ,
-( "RATE" , 2 , "B" , True ) ,
-( "CHANNEL" , 3 , "hh" , False ) ,
-( "FHSS" , 4 , "h" , False ) ,
-( "DBM_ANTSIGNAL" , 5 , "b" , True ) ,
-( "DBM_ANTNOISE" , 6 , "b" , True ) ,
-( "LOCK_QUALITY" , 7 , "h" , False ) ,
-( "TX_ATTENUATION" , 8 , "h" , False ) ,
-( "DB_TX_ATTENUATION" , 9 , "h" , False ) ,
-( "DBM_TX_POWER" , 10 , "b" , True ) ,
-( "ANTENNA" , 11 , "B" , True ) ,
-( "DB_ANTSIGNAL" , 12 , "B" , True ) ,
-( "DB_ANTNOISE" , 13 , "B" , True ) ,
-( "RX_FLAGS" , 14 , "h" , False ) ,
-( "TX_FLAGS" , 15 , "h" , False ) ,
-( "RTS_RETRIES" , 16 , "B" , True ) ,
-( "DATA_RETRIES" , 17 , "B" , True ) ,
-( "EXT" , 31 , "" , False )
-) 
-
-# Removed leading IEEE80211_CHAN_
-channel_flags = (
-( "TURBO"   , 0x0010 ) , # Turbo channel
-( "CCK"     , 0x0020 ) , # CCK channel
-( "OFDM"    , 0x0040 ) , # OFDM channel
-( "2GHZ"    , 0x0080 ) , # 2 GHz spectrum channel.
-( "5GHZ"    , 0x0100 ) , # 5 GHz spectrum channel
-( "PASSIVE" , 0x0200 ) , # Only passive scan allowed
-( "DYN"     , 0x0400 ) , # Dynamic CCK-OFDM channel
-( "GFSK"    , 0x0800 )   # GFSK channel (FHSS PHY)
-)
-# IEEE80211_CHAN_A  = IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM
-# IEEE80211_CHAN_B  = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_CCK
-# IEEE80211_CHAN_G  = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN
-# IEEE80211_CHAN_TA = IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM | IEEE80211_CHAN_TURBO
-# IEEE80211_CHAN_TG = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN  | IEEE80211_CHAN_TURBO
-
-# Removed leading IEEE80211_RADIOTAP_F_
-radiotap_flags = (
-( "CFP"      , 0x01 ) , # sent/received during CFP
-( "SHORTPRE" , 0x02 ) , # sent/received with short preamble
-( "WEP"      , 0x04 ) , # sent/received with WEP encryption
-( "FRAG"     , 0x08 ) , # sent/received with fragmentation
-( "FCS"      , 0x10 ) , # frame includes FCS
-( "DATAPAD"  , 0x20 )   # frame has padding between 802.11 header and payload (to 32-bit boundary)
-)
-
-# Removed leading IEEE80211_RADIOTAP_F_RX_
-radiotap_rx_flags = (
-( "BADFCS" , 0x0001 )   # frame failed crc check
-)
-
-# Removed leading IEEE80211_RADIOTAP_F_TX_
-radiotap_tx_flags = (
-( "FAIL" , 0x0001 ) , # failed due to excessive retries
-( "CTS"  , 0x0002 ) , # used cts 'protection'
-( "RTS"  , 0x0004 )   # used rts/cts handshake
-)
-
-
-# Unknown origin
-# Removed leading IEEE80211_STYPE_
-management_flags = (
-( "ASSOC_REQ"    , 0x0000 ) ,
-( "ASSOC_RESP"   , 0x0010 ) ,
-( "REASSOC_REQ"  , 0x0020 ) ,
-( "REASSOC_RESP" , 0x0030 ) ,
-( "PROBE_REQ"    , 0x0040 ) ,
-( "PROBE_RESP"   , 0x0050 ) ,
-( "BEACON"       , 0x0080 ) ,
-( "ATIM"         , 0x0090 ) ,
-( "DISASSOC"     , 0x00A0 ) ,
-( "AUTH"         , 0x00B0 ) ,
-( "DEAUTH"       , 0x00C0 ) ,
-( "ACTION"       , 0x00D0 )
-)
-
+from ieee80211 import *
 
 import time
 
@@ -97,28 +18,10 @@ max_time = 1
 tstamp = time.time()
 discovered = []
 
-def dealWithPacket ( hdr , data ) :
+def parse_radiotap( radiotap , it_present ) :
 
-    if hdr.getlen() != hdr.getcaplen() :
-        print "Error in header : %d vs. %d" % ( hdr.getlen() , hdr.getcaplen() )
-        return
-    if len(data) != hdr.getlen() :
-        print "Data lenght does not match"
-        return
-
-    it_version , it_len , it_present = struct.unpack("<Bxhl",data[:8])
-    if it_version != 0 :
-        print "Bad version (%s), it is probably not radiotap header" % it_version
-        return
-    if it_len <= 0 :
-        print "Bad length on radiotap header"
-        return
-
-    radiotap = data[:it_len]
-    payload = data[it_len:]
-
-    format , padstr = "<" , ""
     fields = []
+    format , padstr = "<" , ""
     for name,bit,fmt,pad in ratiotap_header_bits :
 # What about  'it_present & ( 0x1 << bit )' ??
         if it_present & pow(2,bit) == pow(2,bit) :
@@ -128,64 +31,141 @@ def dealWithPacket ( hdr , data ) :
             fields.append( name )
             if fmt == "hh" :
                 fields.append( "CHANNEL_BITMAP" )
+            format += fmt
             if pad :
                 padstr += "x"
-            format += fmt
-    values = struct.unpack(format+padstr,radiotap[8:])
+    values = struct.unpack(format+padstr,radiotap])
+
+    radio_hdr = {}
+    for i in range(len(fields)) :
+        radio_hdr[fields[i]] = values[i]
 
     flags = []
     for name,value in radiotap_flags :
-        if values[1] & value == value :
+        if radio_hdr['FLAGS'] & value == value :
             flags.append( name )
+    radio_hdr['_flags'] = flags
+    if radio_hdr['FLAGS'] != 16 and radio_hdr['FLAGS'] != 18 :
+        # 16 - FCS
+        # 18 - SHORTPRE , FCS
+        print 'WARNING : Unexpected flags : (%s) %s' % ( radio_hdr['FLAGS'] , " , ".join( flags ) )
 
     channel = []
     for name,value in channel_flags :
-        if values[4] & value == value :
+        if radio_hdr['CHANNEL_BITMAP'] & value == value :
             channel.append( name )
+    if radio_hdr['CHANNEL_BITMAP'] != 160 and radio_hdr['CHANNEL_BITMAP'] != 192 :
+        # 160 - CCK , 2GHZ
+        # 192 - OFDM , 2GHZ
+        print 'WARNING : Unexpected channel flags : (%s) %s' % ( radio_hdr['CHANNEL_BITMAP'] , " , ".join( channel ) )
+    radio_hdr['_channel_bitmap'] = channel
+
+    return radio_hdr
+
+
+def dealWithPacket ( hdr , data ) :
+
+    if hdr.getlen() != hdr.getcaplen() :
+        print "Error in header : %d vs. %d" % ( hdr.getlen() , hdr.getcaplen() )
+        return
+    if len(data) != hdr.getlen() :
+        print "Data lenght does not match"
+        return
+
+    it_version , it_len , it_present = struct.unpack("<Bxhl",data[:8])
+    if it_version != 0 :
+        print "Bad version (%s), it is probably not radiotap header" % it_version
+        return
+    if it_len <= 0 :
+        print "Bad length on radiotap header"
+        return
+
+    radio_hdr = parse_radiotap( data[8:it_len] , it_present )
+    if not radio_hdr :
+        return
+
+    payload = data[it_len:]
+
 
     pointer = 0
     pcktlen = len(payload)
 
-    frame_ctl , duration_id = struct.unpack("<hh",payload[:4])
+    frame_ctl , frame_subtype , duration_id = struct.unpack("BBh",payload[:4])
     pointer += 4
     pcktlen -= 4
-    for name,value in management_flags :
-        if frame_ctl == value :
+
+    for name,value in frame_type :
+        if frame_ctl & 0x0c == value :
             type = name
             break
     else :
-        type = "unknown_%s" % frame_ctl
+        print "Unknown frame type %s" % ( frame_ctl & 0x0c , )
+        return
+
+    if type == "MGT" :
+        for name,value in management_subtypes :
+            if frame_ctl & 0xf0 == value :
+                subtype = name
+                break
+        else :
+            print "Unknown MGT subtype %s" % ( frame_ctl & 0xf0 , )
+            return
+
+    elif type == "CTL" :
+        for name,value in control_subtypes :
+            if frame_ctl & 0xf0 == value :
+                subtype = name
+                break
+        else :
+            print "Unknown CTL subtype %s" % ( frame_ctl & 0xf0 , )
+            return
+
+    elif type == "DATA" :
+        _subtype = []
+        for name,value in data_subtypes :
+            if frame_ctl & 0xf0 == value :
+                _subtype.append( name )
+        subtype = "-".join( _subtype )
+
+    for name,value in directions :
+        if frame_subtype & 0x03 == value :
+            direction = name
+            break
+    else :
+        print "Unknown direction %s" % ( frame_subtype & 0x03 , )
+        return
 
-    mac_str = "<BBBBBB"
+
+    mac_str = "BBBBBB" # is leading '<' required
     mac_fmt = "%02X:%02X:%02X:%02X:%02X:%02X"
 
     maclist = []
-    for i in range(4) :
+    for i in range(3) :
         maclist.append( mac_fmt % struct.unpack( mac_str , payload[pointer:pointer+6] ) )
         pointer += 6
         pcktlen -= 6
+        # FIXME : only CTL/ACK have single address ???
+        # FIXME : Is this the same than pcktlen == 2
         if pcktlen < 6 :
-            unknowns = { 'unknown_212':12 , 'unknown_180':20 , 'unknown_196':12 , 'unknown_148':32 , 'unknown_4308':12 , 'unknown_4260':20 }
-            if type in unknowns.keys() :
-                if len(payload) == unknowns[type] :
-                    break
-            raise Exception( "Unhandled type %s with length %d" % ( type , len(payload) ) )
+            break
+
+    sequence = struct.unpack("BB",payload[pointer:pointer+2])
+    pointer += 2
+    pcktlen -= 2
 
 #    print fields
 #    print values
 #    print "Radiotap flags : %s" % " ".join(flags)
 #    print "Channel %d (%s)" % ( values[3] , " ".join(channel) )
-##    print "Control info",frame_ctl,duration_id
+##    print "Control info",frame_ctl,frame_subtype,duration_id,"seq",sequence
 #    print type,":",maclist,"->",pcktlen
 #    print
 
-    print "%20s %4d %5d" % (type,values[3],len(payload)),":"+" %s"*len(maclist) % tuple(maclist)
     global tstamp,max_time,discovered
     for mac in maclist :
-        if not type.startswith( "unknown_" ) and not mac in discovered :
-            if mac.startswith( "00:" ) :
-                tstamp = time.time()
-                discovered.append( mac )
+        if not mac in discovered :
+            tstamp = time.time()
+            discovered.append( mac )
     if time.time()-tstamp > max_time :
         fd = open( "discovered.list" , "a" )
         for mac in discovered :
@@ -193,6 +173,7 @@ def dealWithPacket ( hdr , data ) :
         fd.close()
         raise Exception( "Neighborhoud scan completed" )
 
+    print "%4s %13s %6s %4d %4d from %4d" % (type,subtype,direction,values[3],pcktlen,len(payload))," ; %4d %4d "%sequence,":"+" %s"*len(maclist) % tuple(maclist)
 
 packet_limit = -1 # infinite
 pc.loop( packet_limit , dealWithPacket )