Files
stationsniffer/radiotap_parse.cpp
2023-07-18 14:53:54 -06:00

169 lines
6.3 KiB
C++

#include "radiotap_parse.h"
#include "radiotap_fields.h"
#include <cassert>
#include <map>
#include <vector>
#define BIT(n) (1 << n)
#define STBC_BIT BIT(0)
/**
* @brief Convert a sub-WiFi 6 frequency (MHz) to channel number.
*
* Note: only works for sub-WiFi 6, as the IEEE folks are wrapping channel numbers back to 1 for the 6 GHz band.
*
* @param freq_ Frequency, in megahertz (MHz)
* @return int the channel number. Will be zero if the frequency conversion is unknown.
*/
static int frequency_to_channel_number(int freq_)
{
uint16_t channel = 0;
// Channels 1 - 13
if ((freq_ >= 2412) && (freq_ <= 2472)) {
channel = (1 + ((freq_ - 2412) / 5));
}
// Channels 36 - 64
else if ((freq_ >= 5170) && (freq_ <= 5320)) {
channel = (34 + ((freq_ - 5170) / 5));
}
// Channels 100 - 144
else if ((freq_ >= 5500) && (freq_ <= 5720)) {
channel = (100 + ((freq_ - 5500) / 5));
}
// Channels 149 - 161
else if ((freq_ >= 5745) && (freq_ <= 5805)) {
channel = (149 + ((freq_ - 5745) / 5));
}
// Channel 165
else if (freq_ == 5825) {
channel = 165;
}
return (channel);
}
bandwidth_metadata parse_radiotap_bandwidth(uint8_t radiotap_bw_data)
{
// static data, just gen once for fast lookup
static std::map<int, bandwidth_metadata> lookup_table;
lookup_table[0] = {20, -1, -1};
lookup_table[1] = {40, -1, -1};
lookup_table[2] = {40, 20, 0};
lookup_table[3] = {40, 20, 1};
lookup_table[4] = {80, -1, -1};
lookup_table[5] = {80, 40, 0};
lookup_table[6] = {80, 40, 1};
lookup_table[7] = {80, 20, 0};
lookup_table[8] = {80, 20, 1};
lookup_table[9] = {80, 20, 2};
lookup_table[10] = {80, 20, 3};
lookup_table[11] = {160, -1, -1};
lookup_table[12] = {160, 80, 0};
lookup_table[13] = {160, 80, 1};
lookup_table[14] = {160, 40, 0};
lookup_table[15] = {160, 40, 1};
lookup_table[16] = {160, 40, 2};
lookup_table[17] = {160, 40, 3};
lookup_table[18] = {160, 20, 0};
lookup_table[19] = {160, 20, 1};
lookup_table[20] = {160, 20, 2};
lookup_table[21] = {160, 20, 3};
lookup_table[22] = {160, 20, 4};
lookup_table[23] = {160, 20, 5};
lookup_table[24] = {160, 20, 6};
lookup_table[25] = {160, 20, 7};
assert(radiotap_bw_data < lookup_table.size());
return lookup_table[radiotap_bw_data];
}
vht_mcs_nss parse_radiotap_vht_mcs_nss(uint8_t radiotap_vht_mcs_nss, uint8_t flags)
{
vht_mcs_nss parsed{};
parsed.nss = radiotap_vht_mcs_nss & 0x0f;
// Radiotap encodes a decimal MCS index as the high nibble in the MCS/NSS hex field.
parsed.mcs = (radiotap_vht_mcs_nss & 0xf0) >> 4;
// the number of space-time streams (NSTS) for a user can be calculated from the NSS for
// that user and the STBC flag:
(flags & STBC_BIT) ? (parsed.nsts = (2 * parsed.nss)) : (parsed.nsts = parsed.nss);
return parsed;
}
void parse_radiotap_buf(struct ieee80211_radiotap_iterator &iter, const uint8_t *buf, size_t buflen,
radiotap_fields &rt_fields)
{
// be careful of unaligned access!
// typically, you only want the measurement made on the first antenna
// for example, a 4 antenna phone will see something like:
// ant1: -43 dBm, ant2: -45dBm, ant3: -99 dBm, ant4: -99 dBm
int current_ant = 0;
int err;
while (!(err = ieee80211_radiotap_iterator_next(&iter))) {
switch (iter.this_arg_index) {
case IEEE80211_RADIOTAP_FLAGS:
rt_fields.bad_fcs = (uint8_t)*iter.this_arg & IEEE80211_RADIOTAP_F_BADFCS;
break;
case IEEE80211_RADIOTAP_CHANNEL:
rt_fields.channel_frequency = le16_to_cpu(*(uint16_t *)iter.this_arg);
rt_fields.channel_number = frequency_to_channel_number(rt_fields.channel_frequency);
break;
case IEEE80211_RADIOTAP_DBM_ANTSIGNAL:
// Only want the first measurement per packet (antenna 1, 0-indexed).
if (0 == current_ant)
rt_fields.rssi = (int8_t)*iter.this_arg;
break;
case IEEE80211_RADIOTAP_RATE:
// The rate field reports the rx/tx data rate in units of 500 kbps
// This field is generally considered unreliable.
// The radiotap header reports ~1.6 Mbps on a fully saturated 802.11ac stream
// See: https://gitlab.com/wireshark/wireshark/-/issues/5280
// Don't use this metric for any serious work.
break;
case IEEE80211_RADIOTAP_MCS: {
[[maybe_unused]] uint8_t known = iter.this_arg[0];
[[maybe_unused]] uint8_t flags = iter.this_arg[1];
[[maybe_unused]] uint8_t mcs = iter.this_arg[2];
} break;
case IEEE80211_RADIOTAP_VHT: {
std::vector<vht_mcs_nss> vht_mcs_nss_list;
// u16 known, u8 flags, u8 bandwidth, u8 vht_mcs_nss[4], u8 coding, u8 group_id, u16 partial_aid
uint8_t bw = iter.this_arg[3] & 0x1f;
rt_fields.bw_metadata = parse_radiotap_bandwidth(bw);
uint8_t flags = iter.this_arg[2];
for (int i = 4; i < 8; i++) {
vht_mcs_nss_list.push_back(parse_radiotap_vht_mcs_nss(iter.this_arg[i], flags));
}
for (int i = 0; i < (int)vht_mcs_nss_list.size(); i++) {
// If MCS index is zero, there's no data there.
if (vht_mcs_nss_list[i].mcs > 0) {
rt_fields.vht_mcs_nss_ = vht_mcs_nss_list[i];
}
}
break;
}
case IEEE80211_RADIOTAP_TSFT:
case IEEE80211_RADIOTAP_FHSS:
case IEEE80211_RADIOTAP_DBM_ANTNOISE:
rt_fields.ant_noise = (uint8_t)iter.this_arg[0];
break;
case IEEE80211_RADIOTAP_LOCK_QUALITY:
case IEEE80211_RADIOTAP_TX_ATTENUATION:
case IEEE80211_RADIOTAP_DB_TX_ATTENUATION:
case IEEE80211_RADIOTAP_DBM_TX_POWER:
case IEEE80211_RADIOTAP_ANTENNA: {
current_ant = iter.this_arg[0];
} break;
case IEEE80211_RADIOTAP_DB_ANTSIGNAL:
case IEEE80211_RADIOTAP_DB_ANTNOISE:
case IEEE80211_RADIOTAP_TX_FLAGS:
case IEEE80211_RADIOTAP_RTS_RETRIES:
case IEEE80211_RADIOTAP_DATA_RETRIES:
default:
break;
}
}
if (err != -ENOENT) {
return;
}
}