mirror of
https://gitlab.com/prpl-foundation/prplmesh/stationsniffer.git
synced 2025-12-20 02:20:31 +08:00
Add station.cpp, station.h, rssi_measurement.h, radiotap_*.[h|cpp]
Signed-off-by: Tucker Polomik <t.polomik@cablelabs.com>
This commit is contained in:
321
main.cpp
321
main.cpp
@@ -10,22 +10,28 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
// rt stuff
|
||||
#include "radiotap-library/platform.h"
|
||||
#include "radiotap-library/radiotap.h"
|
||||
#include "radiotap-library/radiotap_iter.h"
|
||||
// our stuff
|
||||
#include "radiotap_parse.h"
|
||||
#include "station.h"
|
||||
|
||||
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
|
||||
#define MACSTRFMT "%02x:%02x:%02x:%02x:%02x:%02x"
|
||||
|
||||
constexpr size_t ETH_ALEN = 6;
|
||||
// to kill pcap loop on OS signal
|
||||
static bool stay_alive = true;
|
||||
|
||||
// yoinked from linux/ether.h
|
||||
// addr1 - destination addr
|
||||
// addr2 - source addr
|
||||
// addr3 - bssid
|
||||
// addr4 - optional, just there for padding.
|
||||
struct ieee80211_hdr {
|
||||
unsigned short frame_control;
|
||||
unsigned short duration_id;
|
||||
@@ -36,6 +42,18 @@ struct ieee80211_hdr {
|
||||
unsigned short addr4[ETH_ALEN];
|
||||
} __attribute__((packed));
|
||||
|
||||
void printMacToStream(std::ostream &os, unsigned char MACData[])
|
||||
{
|
||||
char oldFill = os.fill('0');
|
||||
|
||||
os << std::setw(2) << std::hex << static_cast<unsigned int>(MACData[0]);
|
||||
for (uint i = 1; i < 6; ++i) {
|
||||
os << ':' << std::setw(2) << std::hex << static_cast<unsigned int>(MACData[i]);
|
||||
}
|
||||
|
||||
os.fill(oldFill);
|
||||
}
|
||||
|
||||
template <typename Callback> static void print_usage_and(Callback cb)
|
||||
{
|
||||
static constexpr std::string_view usage_str = "Usage: ./pcap <device> <packet_wait_time (mS)>";
|
||||
@@ -45,93 +63,54 @@ template <typename Callback> static void print_usage_and(Callback cb)
|
||||
|
||||
static const std::chrono::milliseconds STATION_KEEPALIVE_TIMEOUT = std::chrono::milliseconds(5000);
|
||||
|
||||
struct radiotap_fields {
|
||||
int8_t rssi; // dBm
|
||||
uint16_t channel_number;
|
||||
uint16_t channel_frequency; // mHz
|
||||
bool bad_fcs; // bad frame control sequence, indicates that this is bogus crap.
|
||||
struct packet_capture_params {
|
||||
// how often do we process a packet?
|
||||
uint packet_cadence_ms;
|
||||
// the name of the interface that we're collecting on.
|
||||
std::string device_name;
|
||||
};
|
||||
|
||||
struct rssi_measurement {
|
||||
int8_t m_rssi_value;
|
||||
uint16_t m_measurement_channel;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_measurement_timestamp;
|
||||
};
|
||||
|
||||
class station {
|
||||
radiotap_fields m_rt_fields;
|
||||
std::array<uint8_t, ETH_ALEN> m_mac;
|
||||
uint32_t m_rssi_wma;
|
||||
// the end of this vector will contain the most recent instantaneous measurement.
|
||||
std::vector<rssi_measurement> m_rssi_measurements;
|
||||
|
||||
public:
|
||||
station(uint8_t mac[ETH_ALEN])
|
||||
{
|
||||
for (int i = 0; i < ETH_ALEN; i++)
|
||||
m_mac[i] = mac[i];
|
||||
}
|
||||
station(const std::array<uint8_t, ETH_ALEN> &mac) { m_mac = mac; }
|
||||
virtual ~station() = default;
|
||||
std::array<uint8_t, ETH_ALEN> get_mac() const { return m_mac; }
|
||||
int8_t get_rssi() const { return m_rt_fields.rssi; }
|
||||
// second nibble of MSB of MAC being 2, E, A or 6 seems to indicate mac randomization (which is canonically only implemented by mobile stations)
|
||||
bool is_potentially_mobile() const
|
||||
{
|
||||
return ((get_mac()[0] & 0x0f) == 0x0A) || ((get_mac()[0] & 0x0f) == 0x0E) ||
|
||||
((get_mac()[0] & 0x0f) == 0x06) || ((get_mac()[0] & 0x0f) == 0x02);
|
||||
}
|
||||
void update_rt_fields(const radiotap_fields &rt_f)
|
||||
{
|
||||
m_rt_fields = rt_f;
|
||||
m_rssi_measurements.push_back(
|
||||
{m_rt_fields.rssi, m_rt_fields.channel_number, std::chrono::system_clock::now()});
|
||||
}
|
||||
int16_t get_frequency() const { return m_rt_fields.channel_frequency; }
|
||||
uint16_t get_channel() const { return m_rt_fields.channel_number; }
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> get_last_rssi_meas_time() const
|
||||
{
|
||||
return m_rssi_measurements.back().m_measurement_timestamp;
|
||||
}
|
||||
bool is_timed_out_ms(std::chrono::milliseconds timeout_ms) const
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::chrono::duration<double, std::milli> elapsed = now - get_last_rssi_meas_time();
|
||||
return elapsed > timeout_ms;
|
||||
}
|
||||
bool operator==(const station &other) const
|
||||
{
|
||||
return std::memcmp(this->get_mac().data(), other.get_mac().data(), ETH_ALEN) == 0;
|
||||
}
|
||||
// sum (x_1*w_1) + ... + (xn*wn) / sum(w_i)
|
||||
void calculate_wma()
|
||||
{
|
||||
static std::chrono::milliseconds reference_time = std::chrono::milliseconds(5000);
|
||||
auto now = std::chrono::system_clock::now();
|
||||
|
||||
int32_t rssi_sum = 0;
|
||||
int i = 0;
|
||||
int denom = 0;
|
||||
|
||||
for (const auto &measurement : m_rssi_measurements) {
|
||||
std::chrono::duration<double, std::milli> time_diff =
|
||||
now - measurement.m_measurement_timestamp;
|
||||
auto weight = (reference_time - time_diff).count();
|
||||
if (weight < 0)
|
||||
continue;
|
||||
rssi_sum += (measurement.m_rssi_value * weight);
|
||||
denom += weight;
|
||||
}
|
||||
if (denom != 0)
|
||||
rssi_sum /= denom;
|
||||
std::cout << "wma " << rssi_sum << std::endl;
|
||||
}
|
||||
};
|
||||
// list of macs that we want to measure for.
|
||||
static std::vector<uint8_t *> whitelisted_macs;
|
||||
|
||||
static std::vector<station> stations;
|
||||
|
||||
static void stop_collecting_metrics(uint8_t mac[ETH_ALEN])
|
||||
{
|
||||
bool found = std::find_if(stations.begin(), stations.end(), [&mac](const station &s) {
|
||||
return std::memcmp(mac, s.get_mac().data(), ETH_ALEN);
|
||||
}) != stations.end();
|
||||
|
||||
// this is not an error case -- we should return true here.
|
||||
if (!found) {
|
||||
std::cout << "Station " << std::hex << mac
|
||||
<< " not found, ignoring request to stop collecting metrics." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::remove_if(whitelisted_macs.begin(), whitelisted_macs.end(),
|
||||
[&mac](const uint8_t *whitelisted_mac) {
|
||||
return std::memcmp(whitelisted_mac, mac, ETH_ALEN) == 0;
|
||||
});
|
||||
|
||||
std::remove_if(stations.begin(), stations.end(), [&mac](const station &s) {
|
||||
return std::memcmp(s.get_mac().data(), mac, ETH_ALEN) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
static void register_station_of_interest(uint8_t mac[ETH_ALEN])
|
||||
{
|
||||
for (const station &s : stations) {
|
||||
if (std::memcmp(mac, s.get_mac().data(), ETH_ALEN) == 0) {
|
||||
std::cout << " Station " << std::hex << mac
|
||||
<< " already known, ignoring register request";
|
||||
}
|
||||
}
|
||||
whitelisted_macs.push_back(mac);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Call 'cb' on every station in the station list
|
||||
* @brief Call 'cb' on every station in the station list (immutable).
|
||||
*
|
||||
* @tparam Callback
|
||||
* @param cb the callback to call with a station passed in.
|
||||
@@ -142,11 +121,18 @@ template <typename Callback> void station_for_each(Callback cb)
|
||||
cb(sta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Call 'cb' on every station in the station list (mutable).
|
||||
*
|
||||
* @tparam Callback
|
||||
* @param cb the callback to call with a station passed in.
|
||||
*/
|
||||
template <typename Callback> void station_for_each_mutable(Callback cb)
|
||||
{
|
||||
for (station &s : stations)
|
||||
cb(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Walks every station and checks if they've been seen in at least timeout_ms milliseconds.
|
||||
*
|
||||
@@ -170,86 +156,6 @@ static void station_keepalive_check(std::vector<station> &station_list,
|
||||
stations.end());
|
||||
}
|
||||
|
||||
// only works for sub wifi 6 -- IEEE dudes are wrapping chan # back to 1 for 6GHz : - )
|
||||
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);
|
||||
}
|
||||
|
||||
static 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!
|
||||
int err;
|
||||
while (!(err = ieee80211_radiotap_iterator_next(&iter))) {
|
||||
switch (iter.this_arg_index) {
|
||||
case IEEE80211_RADIOTAP_TSFT:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_FLAGS:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_RATE:
|
||||
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_FHSS:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_DBM_ANTSIGNAL:
|
||||
rt_fields.rssi = (int8_t)*iter.this_arg;
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_DBM_ANTNOISE:
|
||||
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:
|
||||
case IEEE80211_RADIOTAP_DB_ANTSIGNAL:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_DB_ANTNOISE:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_TX_FLAGS:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_RTS_RETRIES:
|
||||
case IEEE80211_RADIOTAP_DATA_RETRIES:
|
||||
break;
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_F_BADFCS:
|
||||
rt_fields.bad_fcs = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (err != -ENOENT) {
|
||||
printf("malformed radiotap data\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *,
|
||||
// const u_char *);
|
||||
static void packet_cb(u_char *args, const struct pcap_pkthdr *pcap_hdr, const u_char *packet)
|
||||
@@ -257,6 +163,8 @@ static void packet_cb(u_char *args, const struct pcap_pkthdr *pcap_hdr, const u_
|
||||
int err;
|
||||
int i;
|
||||
struct ieee80211_radiotap_iterator iter;
|
||||
if (!stay_alive)
|
||||
pcap_breakloop((pcap_t *)args);
|
||||
|
||||
err = ieee80211_radiotap_iterator_init(&iter, (ieee80211_radiotap_header *)packet, 2014, NULL);
|
||||
if (err) {
|
||||
@@ -265,21 +173,31 @@ static void packet_cb(u_char *args, const struct pcap_pkthdr *pcap_hdr, const u_
|
||||
}
|
||||
const size_t eth_hdr_offset = iter._max_length;
|
||||
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(packet + eth_hdr_offset);
|
||||
if (std::find_if(std::begin(stations), std::end(stations), [&hdr](const station &sta) {
|
||||
return std::memcmp(sta.get_mac().data(), hdr->addr2, ETH_ALEN) == 0;
|
||||
}) == stations.end()) {
|
||||
stations.push_back(hdr->addr2);
|
||||
}
|
||||
auto it =
|
||||
std::find_if(whitelisted_macs.begin(), whitelisted_macs.end(), [&hdr](const uint8_t *mac) {
|
||||
return std::memcmp(hdr->addr2, mac, ETH_ALEN) == 0;
|
||||
});
|
||||
if (it != whitelisted_macs.end()) {
|
||||
|
||||
stations.erase(std::remove_if(stations.begin(), stations.end(),
|
||||
[](const station &s) { return !s.is_potentially_mobile(); }),
|
||||
stations.end());
|
||||
// check if it's already accounted for.
|
||||
auto station_it = std::find_if(stations.begin(), stations.end(), [&hdr](const station &s) {
|
||||
return std::memcmp(hdr->addr2, s.get_mac().data(), ETH_ALEN) == 0;
|
||||
});
|
||||
// if it's not already being tracked, and we care about it, add it to the station list.
|
||||
if (station_it == stations.end()) {
|
||||
stations.push_back(hdr->addr2);
|
||||
}
|
||||
|
||||
} else {
|
||||
// no work to be done, it's not a packet from a station we care about. bail.
|
||||
return;
|
||||
}
|
||||
|
||||
radiotap_fields rt_fields;
|
||||
parse_radiotap_buf(iter, (uint8_t *)packet, 2014, rt_fields);
|
||||
|
||||
if (rt_fields.bad_fcs) {
|
||||
std::cout << "This radiotap sample is clapped." << std::endl;
|
||||
std::cout << "Malformed radiotap header." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -291,37 +209,60 @@ static void packet_cb(u_char *args, const struct pcap_pkthdr *pcap_hdr, const u_
|
||||
return;
|
||||
sta_it->update_rt_fields(rt_fields);
|
||||
|
||||
// DEBUG
|
||||
station_for_each([](const station &s) {
|
||||
printf("Station " MACSTRFMT ":", MAC2STR(s.get_mac().data()));
|
||||
std::cout << " RSSI: " << (int)s.get_rssi() << " ChannelNumber: " << s.get_channel()
|
||||
<< " ChannelFreq: " << s.get_frequency() << std::endl;
|
||||
});
|
||||
|
||||
// do some housekeeping
|
||||
station_keepalive_check(stations, STATION_KEEPALIVE_TIMEOUT);
|
||||
|
||||
station_for_each_mutable([](station &s) { s.calculate_wma(); });
|
||||
|
||||
station_for_each([](const station &s) {
|
||||
std::cout << "Station ";
|
||||
printMacToStream(std::cout, s.get_mac().data());
|
||||
std::cout << std::dec << std::endl
|
||||
<< " RSSI " << (int)s.get_rssi() << " WMA RSSI " << (int)s.get_wma_rssi()
|
||||
<< " Channel " << s.get_channel() << " (" << s.get_frequency() << " mHz)"
|
||||
<< std::endl;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int begin_packet_loop(pcap_t *pcap_handle, const packet_capture_params &pcap_params,
|
||||
pcap_handler callback, int mode)
|
||||
{
|
||||
return pcap_loop(pcap_handle, mode, callback, (u_char *)pcap_handle);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
std::cout << "Welcome to " << argv[0] << std::endl;
|
||||
if (argc < 3)
|
||||
print_usage_and([]() { exit(1); });
|
||||
std::string dev = argv[1];
|
||||
int packet_cadence_ms = std::stoi(argv[2], 0, 10);
|
||||
const int signals_of_interest[2] = {
|
||||
SIGINT,
|
||||
SIGTERM,
|
||||
};
|
||||
packet_capture_params pcap_params{(uint)std::stoi(argv[2], 0, 10), argv[1]};
|
||||
char err[PCAP_ERRBUF_SIZE];
|
||||
auto pcap_handle = pcap_open_live(dev.c_str(), BUFSIZ, 1, packet_cadence_ms, err);
|
||||
auto pcap_handle = pcap_open_live(pcap_params.device_name.c_str(), BUFSIZ, 1,
|
||||
pcap_params.packet_cadence_ms, err);
|
||||
if (!pcap_handle) {
|
||||
std::cout << "Could not get a pcap handle on device '" << dev << "', pcap error: " << err
|
||||
<< std::endl;
|
||||
std::cout << "Could not get a pcap handle on device '" << pcap_params.device_name
|
||||
<< "', pcap error: " << err << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Got a handle to device '" << dev << "'" << std::endl;
|
||||
pcap_loop(pcap_handle, 0, packet_cb, (u_char *)pcap_handle);
|
||||
std::cout << "Got a handle to device '" << pcap_params.device_name << "'" << std::endl;
|
||||
|
||||
for (int sig : signals_of_interest) {
|
||||
std::signal(sig, [](int signum) { stay_alive = false; });
|
||||
}
|
||||
// DEBUG
|
||||
uint8_t station_of_interest[ETH_ALEN] = {0x88, 0xf0, 0x31, 0x79, 0xdc, 0x52};
|
||||
register_station_of_interest(station_of_interest);
|
||||
int loop_status = begin_packet_loop(pcap_handle, pcap_params, packet_cb, 0);
|
||||
if (loop_status != PCAP_ERROR_BREAK) {
|
||||
std::cout << "Unexpected pcap_loop exit code: " << loop_status << std::endl;
|
||||
}
|
||||
pcap_close(pcap_handle);
|
||||
std::cout << "Bye!" << std::endl;
|
||||
std::cout << "Done sniffing on '" << pcap_params.device_name << "', bye!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
9
radiotap_fields.h
Normal file
9
radiotap_fields.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
struct radiotap_fields {
|
||||
int8_t rssi; // dBm
|
||||
uint16_t channel_number;
|
||||
uint16_t channel_frequency; // mHz
|
||||
bool bad_fcs; // bad frame control sequence, indicates that this is bogus data.
|
||||
};
|
||||
88
radiotap_parse.cpp
Normal file
88
radiotap_parse.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "radiotap_parse.h"
|
||||
#include "radiotap_fields.h"
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
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!
|
||||
int err;
|
||||
while (!(err = ieee80211_radiotap_iterator_next(&iter))) {
|
||||
switch (iter.this_arg_index) {
|
||||
case IEEE80211_RADIOTAP_TSFT:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_FLAGS:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_RATE:
|
||||
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_FHSS:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_DBM_ANTSIGNAL:
|
||||
rt_fields.rssi = (int8_t)*iter.this_arg;
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_DBM_ANTNOISE:
|
||||
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:
|
||||
case IEEE80211_RADIOTAP_DB_ANTSIGNAL:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_DB_ANTNOISE:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_TX_FLAGS:
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_RTS_RETRIES:
|
||||
case IEEE80211_RADIOTAP_DATA_RETRIES:
|
||||
break;
|
||||
break;
|
||||
case IEEE80211_RADIOTAP_F_BADFCS:
|
||||
rt_fields.bad_fcs = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (err != -ENOENT) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
24
radiotap_parse.h
Normal file
24
radiotap_parse.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// rt stuff
|
||||
#include "radiotap-library/platform.h"
|
||||
#include "radiotap-library/radiotap.h"
|
||||
#include "radiotap-library/radiotap_iter.h"
|
||||
|
||||
struct radiotap_fields;
|
||||
|
||||
/**
|
||||
* @brief Parse the radiotap header of a packet.
|
||||
*
|
||||
*
|
||||
* If `rt_fields` has `bad_fcs` set, then the output should be ignored.
|
||||
*
|
||||
* @param iter The initialized radiotap iterator.
|
||||
* @param buf The radiotap header.
|
||||
* @param buflen The length of the radiotap header.
|
||||
* @param[out] rt_fields Radiotap fields of interest.
|
||||
*/
|
||||
void parse_radiotap_buf(struct ieee80211_radiotap_iterator &iter, const uint8_t *buf, size_t buflen,
|
||||
radiotap_fields &rt_fields);
|
||||
22
rssi_measurement.h
Normal file
22
rssi_measurement.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <chrono>
|
||||
/**
|
||||
* @brief Struct to hold metadata about an RSSI measurement.
|
||||
*/
|
||||
struct rssi_measurement {
|
||||
/**
|
||||
* @brief The RSSI value (usually, log-scale negative dBm)
|
||||
*
|
||||
*/
|
||||
int8_t m_rssi_value;
|
||||
/**
|
||||
* @brief The channel number on which the RSSI measurement was made.
|
||||
*
|
||||
*/
|
||||
uint16_t m_measurement_channel;
|
||||
/**
|
||||
* @brief The timestamp of this measurement.
|
||||
*
|
||||
*/
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_measurement_timestamp;
|
||||
};
|
||||
64
station.cpp
Normal file
64
station.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "station.h"
|
||||
#include <cstring>
|
||||
|
||||
station::station(uint8_t mac[ETH_ALEN])
|
||||
{
|
||||
for (int i = 0; i < ETH_ALEN; i++)
|
||||
m_mac[i] = mac[i];
|
||||
}
|
||||
|
||||
station::station(const std::array<uint8_t, ETH_ALEN> &mac) { m_mac = mac; }
|
||||
std::array<uint8_t, ETH_ALEN> station::get_mac() const { return m_mac; }
|
||||
int8_t station::get_rssi() const { return m_rt_fields.rssi; }
|
||||
// second nibble of MSB of MAC being 2, E, A or 6 seems to indicate mac randomization (which is canonically only implemented by mobile stations)
|
||||
bool station::is_potentially_mobile() const
|
||||
{
|
||||
return ((get_mac()[0] & 0x0f) == 0x0A) || ((get_mac()[0] & 0x0f) == 0x0E) ||
|
||||
((get_mac()[0] & 0x0f) == 0x06) || ((get_mac()[0] & 0x0f) == 0x02);
|
||||
}
|
||||
void station::update_rt_fields(const radiotap_fields &rt_f)
|
||||
{
|
||||
m_rt_fields = rt_f;
|
||||
m_rssi_measurements.push_back(
|
||||
{m_rt_fields.rssi, m_rt_fields.channel_number, std::chrono::system_clock::now()});
|
||||
}
|
||||
int16_t station::get_frequency() const { return m_rt_fields.channel_frequency; }
|
||||
uint16_t station::get_channel() const { return m_rt_fields.channel_number; }
|
||||
int8_t station::get_wma_rssi() const { return m_rssi_wma; }
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> station::get_last_rssi_meas_time() const
|
||||
{
|
||||
return m_rssi_measurements.back().m_measurement_timestamp;
|
||||
}
|
||||
bool station::is_timed_out_ms(std::chrono::milliseconds timeout_ms) const
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::chrono::duration<double, std::milli> elapsed = now - get_last_rssi_meas_time();
|
||||
return elapsed > timeout_ms;
|
||||
}
|
||||
bool station::operator==(const station &other) const
|
||||
{
|
||||
return std::memcmp(this->get_mac().data(), other.get_mac().data(), ETH_ALEN) == 0;
|
||||
}
|
||||
// sum (x_1*w_1) + ... + (xn*wn) / sum(w_i)
|
||||
void station::calculate_wma()
|
||||
{
|
||||
static std::chrono::milliseconds reference_time = std::chrono::milliseconds(5000);
|
||||
auto now = std::chrono::system_clock::now();
|
||||
|
||||
int32_t rssi_sum = 0;
|
||||
int i = 0;
|
||||
int denom = 0;
|
||||
|
||||
for (const auto &measurement : m_rssi_measurements) {
|
||||
std::chrono::duration<double, std::milli> time_diff =
|
||||
now - measurement.m_measurement_timestamp;
|
||||
auto weight = (reference_time - time_diff).count();
|
||||
if (weight < 0)
|
||||
continue;
|
||||
rssi_sum += (measurement.m_rssi_value * weight);
|
||||
denom += weight;
|
||||
}
|
||||
if (denom != 0)
|
||||
rssi_sum /= denom;
|
||||
m_rssi_wma = rssi_sum;
|
||||
}
|
||||
84
station.h
Normal file
84
station.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "radiotap_fields.h"
|
||||
#include "rssi_measurement.h"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief The length of a MAC address, in bytes.
|
||||
*
|
||||
*/
|
||||
constexpr size_t ETH_ALEN = 6;
|
||||
|
||||
class station {
|
||||
radiotap_fields m_rt_fields;
|
||||
std::array<uint8_t, ETH_ALEN> m_mac;
|
||||
int8_t m_rssi_wma;
|
||||
// the end of this vector will contain the most recent instantaneous measurement.
|
||||
std::vector<rssi_measurement> m_rssi_measurements;
|
||||
|
||||
public:
|
||||
station(uint8_t mac[ETH_ALEN]);
|
||||
|
||||
station(const std::array<uint8_t, ETH_ALEN> &mac);
|
||||
virtual ~station() = default;
|
||||
/**
|
||||
* @brief Get this station's MAC address.
|
||||
*
|
||||
* @return std::array<uint8_t, ETH_ALEN> - the MAC.
|
||||
*/
|
||||
std::array<uint8_t, ETH_ALEN> get_mac() const;
|
||||
/**
|
||||
* @brief Get the most recent RSSI measurement that has been made for this station.
|
||||
*
|
||||
* @return int8_t the RSSI
|
||||
*/
|
||||
int8_t get_rssi() const;
|
||||
|
||||
/**
|
||||
* @brief Returns whether or not this station is _likely_ a mobile station (i.e. a cell phone, a laptop, tablet, etc)
|
||||
*
|
||||
* The second nibble of the MSB of a station's MAC address being one of [0x2, 0xE, 0xA, 0x6] seems to indicate MAC randomization on the station.
|
||||
* Canonically, MAC randomization is only implemented for mobile stations, mostly phones.
|
||||
*
|
||||
* @return true if this station is likely a mobile station.
|
||||
* @return false otherwise.
|
||||
*/
|
||||
bool is_potentially_mobile() const;
|
||||
|
||||
/**
|
||||
* @brief Update this station's parsed radiotap fields
|
||||
*
|
||||
* @param rt_f The radiotap fields to read from.
|
||||
*/
|
||||
void update_rt_fields(const radiotap_fields &rt_f);
|
||||
|
||||
int16_t get_frequency() const;
|
||||
uint16_t get_channel() const;
|
||||
int8_t get_wma_rssi() const;
|
||||
/**
|
||||
* @brief Get the measurement time of the most recent RSSI reading. Used to check if a station is 'alive' still.
|
||||
*
|
||||
* @return std::chrono::time_point<std::chrono::high_resolution_clock> the timestamp.
|
||||
*/
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> get_last_rssi_meas_time() const;
|
||||
/**
|
||||
* @brief Determine if a station is alive still.
|
||||
*
|
||||
* @param timeout_ms The timeout value (milliseconds) threshold. If there has not been a new RSSI measurement made for this station in `timeout_ms`, then it is considered timedout.
|
||||
* @return true if the station is timed out.
|
||||
* @return false otherwise.
|
||||
*/
|
||||
bool is_timed_out_ms(std::chrono::milliseconds timeout_ms) const;
|
||||
bool operator==(const station &other) const;
|
||||
/**
|
||||
* @brief Computes the weighted average of RSSI values over this station's RSSI measurement list.
|
||||
*
|
||||
* More recent measurements are weighed higher.
|
||||
*
|
||||
* (x_1*w_1) + ... + (x_n*w_n) / sum(w_i) -- where x_i is an RSSI measurement value in dBm, and w_i is it's weight.
|
||||
*/
|
||||
void calculate_wma();
|
||||
};
|
||||
Reference in New Issue
Block a user