问题描述
出于某种原因,我似乎不断从 c/c++ 中的 pcap 获取 10 字节 802.11 MAC 标头,但我不知道为什么。
一些介绍细节:
-
是的,我处于监控模式
-
是的,我正在使用
wlan1mon
-
我检查过
pcap_open_live
返回非空 -
我检查了
pcap_datalink
返回了 127(802.11 w/ radiotap 标头) -
我很难找到 802.11 MAC 标头的良好参考。以太网标头、IPv4 标头等都有非常 很好的参考资料,其中详细介绍了每个字段的所有必要细节,以及您如何知道它是否存在和/或有效...但是没有人甚至说如果不需要 addr4 是否完全省略或者它只是 0 填充/未填充。或者不同类型/子类型给出了报头的排列方式(one site 建议,有时,帧只是用于确认的控制、持续时间和 MAC,但没有其他站点我发现也一样)。
以下代码段的快速参考:我制作了一个宏 Assert
(我通常给出符合规范的更长的名称,但现在仅此而已)它有一个条件作为第一个参数,并且,如果失败,它使用一个 stringstream
来构造一个字符串,如果它是假的,则抛出一个 runtime_error
。这让我可以制作非常具有描述性的错误消息,必要时包含局部变量值。
好的,这是我目前所在的位置。请注意,这是我第一个使用 pcap 的程序,我完全在 RaspBerry Pi 上使用 vim 通过 ssh 从 Windows 的 git-bash 编写它,所以我并不完全处于格式化的理想环境中。当我试图让愚蠢的事情不发生时,事情也会变得混乱
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,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,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,handlePacket,/*user*/0 ) ;
cout << "Exiting" << endl ;
return 0 ;
}
但是我很快得到一个错误,即 802.11 帧(在 radiotap 之后)是 10 字节长(这是从代码中的最后一个 Assert
开始,在 to/from DS 之后)。
我尝试了几种读取帧控件的组合(一次读取整个 little-endian uint16_t,或逐字节、b0-first 或 b0-last 等),因为似乎没有好的规范关于它是如何安排的。我最常遇到 STA->STA
帧(由此代码检测/报告),鉴于我的环境,这似乎不太可能。
我错过了什么?显然我遗漏了一些东西。
作为参考,radiotap it_len
始终是此代码报告的 18 字节或 21 字节,这似乎也很奇怪。
解决方法
我很难找到 802.11 MAC 标头的良好参考。
如果“好”是指“简单”,不幸的是这是不可能的,因为标头并不像 802.3 以太网标头那样简单。 :-)
总有IEEE Std 802.11-2016;请参阅第 9.2 节“MAC 帧格式”。
(一个站点建议,有时,帧只是用于确认的控制、持续时间和 MAC,但我发现没有其他站点表示相同)。
帧控制、持续时间、接收器地址和 CRC。就是这样 - 那是 14 个八位字节,CRC 是最后 4 个八位字节,所以如果数据包中不 CRC,那将是 10 个八位字节。