Search code examples
wifilibpcap

pcap/monitor-mode w/ radiotap: packet size seems perpetually small?


For some reason, it seems like I keep getting 10-Byte 802.11 MAC headers from pcap in c/c++ and I don't know why.

Some intro details:

  • Yes, I'm in monitor mode

  • Yes, I'm using wlan1mon

  • I checked that pcap_open_live returned non-null

  • I checked that pcap_datalink returned 127 (802.11 w/ radiotap header)

  • I have had a really hard time finding a good reference for the 802.11 MAC header. The Ethernet header, IPv4 header, etc have all had really good references which went into all the necessary detail about every field and how you know if it is/isn't present and/or valid... but nobody even says whether addr4 is omitted entirely if unnecessary or if it's just 0-filled/unfilled. Or what the arrangement of the header is given the different types/subtypes (one site suggested that, sometimes, the frame is just the control, duration, and MAC for acknowledgements, but no other site I've found says the same).

A quick reference for code segments below: I made a macro Assert (which I usually give longer names that conform to the spec, but for now it's just that) which has a condition as the first argument and, if it fails, it uses a stringstream to construct a string and throws a runtime_error if it's false. This lets me make very descriptive error messages which include local variable values when necessary.

Ok, here're where I currently am. Note that this is my first program with pcap and I'm writing it entirely on a Raspberry Pi in vim over ssh from git-bash from Windows, so I'm not exactly in ideal circumstances for formatting. Also things get messy when I try to make the stupid thing not

namespace
{
    struct ieee80211_radiotap_header {
            u_int8_t        it_version;     /* set to 0 */
            u_int8_t        it_pad;
            u_int16_t       it_len;         /* entire length */
            u_int32_t       it_present;     /* fields present */
    } __attribute__((__packed__));
    static_assert ( sizeof(ieee80211_radiotap_header)==8 , "Bad packed structure" ) ;

    /* Presence bits */
    enum {
            RADIOTAP_TSFT                           = 0 ,
            RADIOTAP_FLAGS                          = 1 ,
            RADIOTAP_RATE                           = 2 ,
            RADIOTAP_CHANNEL                = 3 ,
            RADIOTAP_FHSS                           = 4 ,
            RADIOTAP_ANTENNA_SIGNAL         = 5 ,
            RADIOTAP_ANTENNA_NOISE          = 6 ,
            RADIOTAP_LOCK_QUALITY           = 7 ,
            RADIOTAP_TX_ATTENUATION         = 8 ,
            RADIOTAP_DB_TX_ATTENUATION  = 9 ,
            RADIOTAP_DBM_TX_POWER           = 10 ,
            RADIOTAP_ANTENNA                = 11 ,
            RADIOTAP_DB_ANTENNA_SIGNAL  = 12 ,
    } ;


    typedef array<uint8_t,6>        MAC ;
    static_assert ( is_pod<MAC>::value , "MAC is not a POD type" ) ;
    static_assert ( sizeof(MAC)==6 , "MAC is not 6-Bytes" ) ;
    string MAC2String ( MAC const& m ) {
            string rval = "__:__:__:__" ;
            for ( auto iByte(0) ; iByte<6 ; ++iByte ) {
                    static char const * const hex = "0123456789abcdef" ;
                    rval[3*iByte]   =       hex [ ( m[iByte] & 0xF0 ) >> 4 ] ;
                    rval[3*iByte+1] =       hex [ m[iByte] & 0x0F ] ;
            }
            return rval ;
    }

    void handlePacket ( u_char * args , pcap_pkthdr const * header , u_char const * packet ) {
            static_assert ( sizeof(u_char)==1 , "Huh?" ) ;

            //cout << "Packet; " << header->caplen << " of " << header->len << " captured" << endl ;

            size_t  expectedSize    =       sizeof(ieee80211_radiotap_header) ;

            Assert ( header->caplen>=expectedSize , "Capture is not big enough; expecting " << expectedSize << " so far, but only have " << header->caplen ) ;

            uint8_t const*  radioHeader             =       packet ;
            auto    rx              =       reinterpret_cast<ieee80211_radiotap_header const*> ( radioHeader ) ;

            expectedSize += rx->it_len - sizeof(ieee80211_radiotap_header) ; // add the radiotap body length
            Assert ( header->caplen>=expectedSize , "Capture is not big enough; expecting " << expectedSize << " so far, but only have " << header->caplen ) ;

            // Look at the 802.11 Radiotap Header




            if ( header->caplen == expectedSize ) {
                    cout << "Packet contains ONLY " << expectedSize << "-Byte RadioTap header" << endl ;
                    return ;
            }

            // From this point forward, all error messages should subtract rx->it_len so that the error reflects the 802.11 frame onward
            // and does not include the radiotap header.


            // Look at the 802.11 MAC Header

            expectedSize += 2+2+2+4 ; // everything but the four addresses
            Assert ( header->caplen>=expectedSize , "Frame is not big enough; expecting " << expectedSize-rx->it_len << " so far, but only have " << header->caplen-rx->it_len ) ;

            uint8_t const*  frameHeader             =       radioHeader + rx->it_len ;

            uint8_t version         =       frameHeader[0] & 3 ;
            Assert ( version==0 , "Bad 802.11 MAC version: " << int(version) ) ;

            uint8_t frameType       =       ( frameHeader[0] >> 2 ) & 0x03 ;
            uint8_t frameSubtype=   ( frameHeader[0] >> 4 ) & 0x0F ;
            bool    toDS            =       frameHeader[1] & 0x01 ;
            bool    fromDS          =       frameHeader[1] & 0x02 ;
            bool    isWEP           =       frameHeader[1] & (1<<6) ;

            MAC const*              addr1   =       reinterpret_cast<MAC const*> ( frameHeader + 4 ) ;
            MAC const*              addr2   =       reinterpret_cast<MAC const*> ( frameHeader + 10 ) ;
            MAC const*              addr3   =       reinterpret_cast<MAC const*> ( frameHeader + 16 ) ;
            MAC const*              addr4   =       reinterpret_cast<MAC const*> ( frameHeader + 24 ) ;

            MAC const*      bssid (0) ;
            MAC const*      da ;
            MAC const*      sa ;
            MAC const*      ra ;
            MAC const*      ta ;
            char const*     desc ;

            // Table found here: https://www.rfwireless-world.com/Articles/WLAN-MAC-layer-protocol.html
            if ( !toDS && !fromDS ) {
                    desc = "STA->STA" ;
                    da      =       addr1 ;
                    sa      =       addr2 ;
                    bssid=  addr3 ;
                    // inferred
                    ta      =       sa ;
                    ra      =       da ;
                    expectedSize += 6+6+6 ;
            } else if ( !toDS && fromDS ) {
                    desc = "Base->STA" ;
                    da      =       addr1 ;
                    bssid=  addr2 ;
                    sa      =       addr3 ;
                    // inferred
                    ta      =       bssid ;
                    ra      =       da ;
                    expectedSize += 6+6+6 ;
            } else if ( toDS && !fromDS ) {
                    desc = "STA->Base" ;
                    bssid=  addr1 ;
                    sa      =       addr2 ;
                    da      =       addr3 ;
                    // inferred
                    ta      =       sa ;
                    ra      =       bssid ;
                    expectedSize += 6+6+6 ;
            } else if ( toDS && fromDS ) {
                    desc = "Base->Base" ;
                    ra      =       addr1 ;
                    ta      =       addr2 ;
                    da      =       addr3 ;
                    sa      =       addr4 ;
                    expectedSize += 6+6+6+6 ;
            }
            Assert ( header->caplen>=expectedSize , "Frame is not big enough; expecting " << expectedSize-rx->it_len << " so far, but only have " << header->caplen-rx->it_len << " for a " << desc << " frame of type " << int(frameType) << '/' << int(frameSubtype) ) ;



            cout << desc << "\t" << MAC2String(*ta) << '/' << MAC2String(*sa) << "  -->  " << MAC2String(*ra) << '/' << MAC2String(*da) << endl ;

            //cout << MAC2String(addr1) << " / " << MAC2String(addr2) << "  -->  " << MAC2String(addr3) << " / " << MAC2String(addr4) << endl ;

            //cout << "   " << int(frameType) << " / " << int(frameSubtype) << endl ;
    }
}




int main ( int , char const* * )
{
    cout << "Hello, world" << endl ;

    auto    devName                 =       "wlan1mon" ;

    char    errBuf[PCAP_ERRBUF_SIZE] ;
    auto    maxTime                 =       0 ; // ms
    auto    maxPacketCount  =       0 ;

    auto    dev                     =       pcap_open_live ( devName , BUFSIZ , maxPacketCount , maxTime , errBuf ) ;
    Assert ( dev!=0 , "Failed to open pcap: " << errBuf ) ;

    auto    linkType                =       pcap_datalink ( dev ) ;
    // IEEE 802.11 link type is 105, from tcpdump.org/linktypes.html
    // 127 is the 802.11 radiotap which is even better
    Assert ( linkType==127 , "Link type was " << linkType << ", but expected " << 127 ) ;

    pcap_loop ( dev , maxPacketCount , handlePacket , /*user*/0 ) ;

    cout << "Exiting" << endl ;

    return 0 ;
}

But what I get very quickly is an error that the 802.11 frame (after the radiotap) is 10-Bytes long (this is from the last Assert in the code, after the to/from DS).

I've tried a few combinations of reading the Frame Control (whole little-endian uint16_t at a time, or byte-by-byte, b0-first or b0-last, etc) because there seems to be no good spec on how it is arranged. I do run into STA->STA frames most often (as detected/reported by this code), which seems unlikely given my environment.

What am I missing? Clearly I am missing something.

For reference, the radiotap it_len is always either 18 Bytes or 21 Bytes as reported by this code, which also seems odd.


Solution

  • I have had a really hard time finding a good reference for the 802.11 MAC header.

    If by "good" you mean "simple", unfortunately that's impossible, because the header isn't simple the way the 802.3 Ethernet header is. :-)

    There's always IEEE Std 802.11-2016; see section 9.2 "MAC frame formats".

    (one site suggested that, sometimes, the frame is just the control, duration, and MAC for acknowledgements, but no other site I've found says the same).

    Frame control, duration, receiver address, and CRC. That's it - and that's 14 octets, with the CRC being the last 4 octets, so if the CRC isn't present in the packet, that would be 10 octets.