Compare commits

...

8 Commits

Author SHA1 Message Date
Miroslav Hutár
a86a7d3b02 add FTM support 2025-05-17 13:40:40 +02:00
Miroslav Hutár
7860485dbe set frequency only when enable inject/measure 2024-04-30 23:54:32 +02:00
Miroslav Hutár
0f94a0f1cb plot with cairo, remove gnuplot 2024-04-30 22:38:28 +02:00
Miroslav Hutár
2086412fbf async update plot
fix loosing csi packet caused by sync updating plot
2024-04-22 13:37:08 +02:00
Miroslav Hutár
9bb4fd74fa lower compiler optimization flag
higher optimization made segmentation fault on some systems
2024-04-22 10:54:11 +02:00
Miroslav Hutár
f628479edb add udp socket support 2024-02-15 15:55:24 +01:00
Miroslav Hutár
23c6aaee25 add support for selecting TX antenna 2024-02-05 17:10:06 +01:00
Miroslav Hutár
1975789026 update readme.md 2023-12-30 12:18:23 +01:00
32 changed files with 5163 additions and 3683 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
FEITCSI_VERSION=2.0.0

View File

@ -87,7 +87,7 @@
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkNotebook">
<object class="GtkNotebook" id="leftBar">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
@ -819,9 +819,16 @@
<property name="can-focus">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkSocket" id="chartSocket">
<object class="GtkBox" id="plotBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="resize">False</property>

View File

@ -1,3 +1,8 @@
include .env
# Variables
CDEFS += -DFEITCSI_VERSION="\"${FEITCSI_VERSION}"\"
# Target
BIN_DIR = bin
BIN = $(BIN_DIR)/app
@ -19,7 +24,7 @@ INCLUDE_LIB_DIRS =
INCLUDE_LIB = $(foreach includedir,$(INCLUDE_LIB_DIRS),-L$(includedir))
# Set compiler, preprocesor and linker flags
CXXFLAGS += -g -O3 -Wall -std=c++17 $(INCLUDE)
CXXFLAGS += -g -O1 -Wall -std=c++17 $(CDEFS) $(INCLUDE)
CPPFLAGS += `pkg-config --cflags gtkmm-3.0 libnl-3.0 libnl-genl-3.0 libpcap`
LDFLAGS += $(INCLUDE_LIB)
LDLIBS += `pkg-config --libs gtkmm-3.0 libnl-3.0 libnl-genl-3.0 libpcap`

120
include/Arguments.h Normal file
View File

@ -0,0 +1,120 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2024-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ARGUMENTS_PARSER_H
#define ARGUMENTS_PARSER_H
#include <string>
#include <cstdint>
#include <map>
#include <argp.h>
#include "main.h"
#define ETH_ALEN 6
struct Args
{
bool strict;
bool verbose;
uint16_t frequency;
bool gui = false;
bool udpSocket = false;
bool plot = false;
std::string bandwidth;
std::string outputFile;
uint8_t mcs;
uint16_t channelWidth;
uint8_t spatialStreams;
uint8_t txPower;
uint32_t antenna;
uint16_t guardInterval;
uint32_t injectDelay;
uint32_t injectRepeat;
std::string coding;
std::string format;
bool inject;
bool measure;
std::string mode;
std::string ltf;
uint32_t modeDelay;
bool ftm = false;
bool ftmResponder = false;
bool ftmAsap = false;
uint8_t ftmBurstExp;
uint8_t ftmPerBurst;
uint16_t ftmBurstPeriod;
uint8_t ftmBurstDuration;
uint8_t mac[ETH_ALEN];
uint8_t ftmTargetMac[ETH_ALEN];
std::string inputFile;
std::map<enum processor, bool> processors;
};
class Arguments
{
public:
inline static struct Args arguments;
static void init();
static void parse(int argc, char *argv[]);
static error_t parse_opt(int key, char *arg, argp_state *state);
private:
/* Program documentation. */
inline static char doc[] =
"FeitCSI - FeitCSI, the tool that enables CSI extraction and injection IEEE 802.11 frames";
/* A description of the arguments we accept. */
inline static char args_doc[] = "ARG1 ARG2";
/* The options we understand. */
inline static struct argp_option options[] = {
{"frequency", 'f', "FREQUENCY", 0, "Frequency to measure/inject CSI"},
{"channel-width", 'w', "CHANNELWIDTH", 0, "Channel width to measure/inject CSI. Possible values [20|40|HT40-|80|160]"},
{"output-file", 'o', "FILE", 0, "Output file where measurements should be stored."},
{"mcs", 'm', "MCS", 0, "Mcs index [0-11]"},
{"format", 'r', "FORMAT", 0, "Data frame format [NOHT|HT|VHT|HESU]"},
{"spatial-streams", 's', "SPATIALSTREAMS", 0, "Number of spatial streams [1|2]"},
{"guard-interval", 'g', "GUARDINTERVAL", 0, "Guard interval in ns [400|800]"},
{"ltf", 'l', "LTF", 0, "HE LTF [2xLTF+0.8|2xLTF+1.6|4xLTF+3.2|4xLTF+0.8]"},
{"coding", 'c', "CODING", 0, "Coding scheme [LDPC|BCC]"},
{"tx-power", 't', "TXPOWER", 0, "TX power of antenna in dBm [1-22]"},
{"antenna", 'a', "ANTENNA", 0, "Transmitting antenna 1, 2 or 12 for both"},
{"mode", 'i', "MODE", 0, "Mode of program[measure|inject|measureinject|ftm|ftmres|injectftmres|measureftm]"},
{"inject-delay", 'd', "INJECTDELAY", 0, "Delay between frame injections is us"},
{"inject-repeat", 'j', "INJECTREPEAT", 0, "How many times inject frame"},
{"verbose", 'v', 0, OPTION_ARG_OPTIONAL, "Produce verbose output"},
{"plot", 'p', 0, OPTION_ARG_OPTIONAL, "Plot CSI data"},
{"gui", 'x', 0, OPTION_ARG_OPTIONAL, "Run application in GUI"},
{"udp-socket", 'u', 0, OPTION_ARG_OPTIONAL, "Run application and listen to UDP"},
{"ftm-asap", 'b', 0, OPTION_ARG_OPTIONAL, "FTM asap mode"},
{"ftm-burst-exp", 'q', "FTMBURSTEXP", 0, "FTM burst exponent"},
{"ftm-per-burst", 'e', "FTMPERBURST", 0, "FTM samples per burst"},
{"ftm-burst-period", 'h', "FTMBURSTPERIOD", 0, "FTM burst period"},
{"ftm-burst-duration", 'k', "FTMBURTSDURATION", 0, "FTM burst duration"},
{"ftm-mac", 'n', "FTMMAC", 0, "FTM target MAC address xx:xx:xx:xx:xx:xx"},
{"mode-delay", 'y', "SWAPTIME", 0, "Delay in ms between inject and ftm responder or measure and ftm initiator when modes are injectftmres|measureftm"},
{"strict", 'z', 0, OPTION_ARG_OPTIONAL, "Strict mode: filter out values that do not contain a specific MCS"},
{"mac", '#', "MAC", 0, "Default NICs MAC will be change to providing MAC xx:xx:xx:xx:xx:xx"},
{0}};
};
#endif

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -23,15 +23,18 @@
#include <string>
#include <complex>
#include <vector>
#include "UdpSocket.h"
#define CSI_HEADER_LENGTH 272
struct RawHeaderData
struct __attribute__((__packed__)) RawHeaderData
{
uint32_t csiDataSize;
uint32_t space4;
uint32_t ftmClock;
uint8_t space12[34];
//uint8_t space12[34];
uint64_t timestamp;
uint8_t space20[26];
uint8_t numRx;
uint8_t numTx;
uint8_t space48[4];
@ -56,6 +59,7 @@ public:
void loadFromMemory(uint8_t *pHeader, uint8_t *rawCsiData);
void loadFromMemory(uint8_t *rawData);
void save();
void sendUDP(UdpSocket *udpSocket);
void backup();
void restore();
void magnitudePhaseToComplex();

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -23,18 +23,30 @@
#include "WiFIController.h"
#include "PacketInjector.h"
#include "gui/MainWindow.h"
#include "gui/Plot.h"
#include "Csi.h"
#include "UdpSocket.h"
#include <thread>
class MainController
{
public:
inline static UdpSocket *udpSocket = nullptr;
Plot *plotAmplitude;
Plot *plotPhase;
static MainController *getInstance();
static gint updatePlots();
void initPlots();
static void deleteInstance();
void runNoGui();
void runNoGui(bool detach = false);
void measureCsi(bool stop = false);
@ -42,7 +54,12 @@ public:
void runGui();
void runUdpSocket();
void initInterface();
void restoreState();
~MainController();
private:
@ -52,24 +69,43 @@ private:
inline static MainWindow *mainWindow = nullptr;
Glib::RefPtr<Gtk::Application> app;
inline static Csi *csiToPlot = nullptr;
guint updatePlotsSourceId = 0;
pthread_t measureCsiThread = 0;
pthread_t injectPacketThread = 0;
pthread_t ftmThread = 0;
pthread_t ftmResponderThread = 0;
PacketInjector packetInjector;
WiFIController wifiController;
std::vector<InterfaceInfo> bkpInterfaces;
static void *measureCsi(void *arg);
static void intHandler(int dummy);
static void *injectPackets(void *arg);
void restoreState();
static void *ftm(void *arg);
static void *ftmResponder(void *arg);
bool measuring = false;
bool injecting = false;
bool ftmEnabled = false;
bool ftmResponderEnabled = false;
};
#endif

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -22,6 +22,7 @@
#include <stdint.h>
#include "ieee80211_radiotap.h"
#include "rs.h"
#include <pcap.h>
#define BIT(n) (0x1U << (n))
@ -35,9 +36,9 @@ public:
void injectVHT();
void injectHE();
private:
void send(uint32_t rateNFlags);
pcap_t *ppcap = nullptr;
};
#endif

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2024 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,27 +16,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GNU_PLOT_H
#define GNU_PLOT_H
#ifndef UDP_SOCKET_H
#define UDP_SOCKET_H
#include <gtkmm.h>
#include <gtkmm/socket.h>
#include <Csi.h>
#include <sys/socket.h>
#include <string>
class GnuPlot
class UdpSocket
{
public:
void init();
void setWindow(uint64_t id);
void setBlank();
void reload();
void updateChart(Csi &csi);
void send(char *buf, int size);
private:
inline static std::string lastCmd;
inline static FILE *gnuPlotPipe;
int sfd;
bool running = false;
struct sockaddr_storage peer_addr;
socklen_t peer_addr_len;
};
#endif

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -64,28 +64,36 @@ class WiFIController : public Netlink
public:
void killNetworkProcesses();
void getInterfaces();
void getInterfaceInfo(const char* ifname);
void setTxPower();
void addMonitorDevice(const char *name, const nl80211_iftype type);
void addDevice(const char *name, const nl80211_iftype type);
int setInterfaceUpDown(const char *ifName, bool up);
void setFreq(uint16_t freq, const char *bw);
void removeInterface(const char *name, int ifIndex = 0);
void createMonitorInteface();
void createApInteface();
void getPhys();
int setApMode();
static ChanMode getChanMode(const char *width);
static uint16_t chanModeToWidth(ChanMode &chanMode);
std::vector<InterfaceInfo> interfaces;
std::vector<int64_t> phys;
InterfaceInfo currentInterfaceInfo = {};
private:
std::string macN2a(const unsigned char *arg);
int freqToCHannel(int freq);
int phyLookup(char *name);
std::string currentDeviceName;
static int getCf1(const ChanMode *chanmode, unsigned long freq);
static int processGetInterfacesHandler(nl_msg *msg, void *arg);
static int processGetInterfaceInfoHandler(nl_msg *msg, void *arg);
static int processGetPhysHandler(nl_msg *msg, void *arg);
static int processSetFreq(nl80211_state *state, nl_msg *msg, void *arg);
static int setTxPowerHandler(nl80211_state *state, nl_msg *msg, void *arg);
static int processAddMonitorDeviceHandler(nl80211_state *state, nl_msg *msg, void *arg);
static int processaddDeviceHandler(nl80211_state *state, nl_msg *msg, void *arg);
static int setApModeHandler(nl80211_state *state, nl_msg *msg, void *arg);
};
#endif

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,7 +20,9 @@
#define WIFI_CSI_CONTROLLER_H
#include "Netlink.h"
#include "GnuPlot.h"
#include "Csi.h"
#include <mutex>
#include <queue>
#define IWL_MVM_VENDOR_ATTR_CSI_HDR 0x4d
#define IWL_MVM_VENDOR_ATTR_CSI_DATA 0x4e
@ -32,14 +34,17 @@ class WiFiCsiController : public Netlink
public:
void init();
int listenToCsi();
void enableCsi(bool enable = true);
static void enableCsi(bool enable = true);
inline static std::mutex csiQueueMutex;
inline static std::queue<Csi*> csiQueue;
int64_t stopTime = 0;
~WiFiCsiController();
private:
static int listenToCsiHandler(nl80211_state *state, nl_msg *msg, void *arg);
static int processListenToCsiHandler(nl_msg *msg, void *arg);
GnuPlot gnuPlot;
static void printDetail(Csi *c);
};
#endif

View File

@ -0,0 +1,63 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WIFI_FTM_CONTROLLER_H
#define WIFI_FTM_CONTROLLER_H
#include "Netlink.h"
struct __attribute__((__packed__)) Ftm
{
uint32_t burstIndex;
uint32_t numFtmrAttempts;
uint32_t numFtmrSuccesses;
uint32_t numBurstsExp;
uint8_t burstDuration;
uint8_t ftmsPerBurst;
uint8_t rssiAvg;
uint32_t rssiSpread;
uint64_t rttAvg;
uint64_t rttVariance;
uint64_t rttSpread;
uint64_t distAvg;
uint64_t distVariance;
uint64_t distSpread;
uint64_t timestamp;
}; // size 79 bytes
#define FTM_SIZE sizeof(Ftm)
class WiFiFtmController : public Netlink
{
public:
void init();
int startInitiator();
int startResponder();
int setApMode();
uint64_t lastRttIsSuccess = false;
private:
static int ftmHandler(nl80211_state *state, nl_msg *msg, void *arg);
static int ftmResponderHandler(nl80211_state *state, nl_msg *msg, void *arg);
static int setApModeHandler(nl80211_state *state, nl_msg *msg, void *arg);
static int processFtmHandler(nl_msg *msg, void *arg);
};
#endif

View File

@ -21,7 +21,6 @@
#include <gtkmm.h>
#include "CsiProcessor.h"
#include "GnuPlot.h"
class CsiProcessingWindow
{
@ -55,7 +54,7 @@ private:
void processingSaveGtkButtonClicked();
CsiProcessor csiProcessor;
GnuPlot gnuPlot;
uint32_t currentIndex = 0;
};

View File

@ -20,7 +20,7 @@
#define MAIN_WINDOW_H
#include <gtkmm.h>
#include "gui/GnuPlot.h"
#include "gui/Plot.h"
#include "gui/CsiProcessingWindow.h"
class MainWindow : public Gtk::ApplicationWindow
@ -31,6 +31,7 @@ public:
MainWindow(BaseObjectType *obj, Glib::RefPtr<Gtk::Builder> const &builder);
void fatalError(std::string msg);
Glib::RefPtr<Gtk::Builder> builder;
private:
int chartWidth = 0;
@ -44,7 +45,6 @@ private:
std::map<std::string, std::string> errors;
Glib::RefPtr<Gtk::Adjustment> consoleScrollbarAdjustment;
Glib::RefPtr<Gtk::Builder> builder;
Glib::RefPtr<Gtk::MessageDialog> errorDialog;
Glib::RefPtr<Gtk::Button> measureButton;
@ -67,8 +67,7 @@ private:
Glib::RefPtr<Gtk::TextView> console;
Glib::RefPtr<Gtk::Label> errorMessages;
Glib::RefPtr<Gtk::Socket> chartSocket;
GnuPlot gnuPlot;
Plot plot;
void closeButton();

56
include/gui/Plot.h Normal file
View File

@ -0,0 +1,56 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2024 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PLOT_H
#define PLOT_H
#include "Csi.h"
#include <future>
#include <sigc++/sigc++.h>
#include <string>
#include <gtkmm.h>
#include <mutex>
class Plot : public Gtk::DrawingArea
{
public:
void init();
void init(Glib::RefPtr<Gtk::Box> box);
void updateData(Csi *csi, std::vector<double> *data);
double yTicksMax = 200;
double yTicksMin = 0;
std::string yLabel = "";
std::string xLabel = "Subcarrier index";
std::string title = "";
private:
const double colors[4][3] = {
{ 1.0, 0.0, 0.0 },
{ 0.0, 0.0, 1.0 },
{ 1.0, 0.7, 0.0 },
{ 0.0, 1.0, 0.0 },
};
Csi *csi;
std::vector<double> *data;
std::mutex updateDataMutex;
double yTicks;
bool on_draw(const Cairo::RefPtr<Cairo::Context> &cr);
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,11 +19,8 @@
#ifndef MAIN_H
#define MAIN_H
#include <string>
#include <cstdint>
#include <map>
#define MONITOR_INTERFACE_NAME "mon0"
#define MONITOR_INTERFACE_NAME "FeitCSImon"
#define AP_INTERFACE_NAME "FeitCSIap"
enum processor
{
@ -33,33 +30,4 @@ enum processor
phaseCalibrationLinearTransform,
};
struct Arguments
{
bool verbose;
uint16_t frequency;
bool gui = false;
bool plot = false;
std::string bandwidth;
std::string outputFile;
uint8_t mcs;
uint16_t channelWidth;
uint8_t spatialStreams;
uint8_t txPower;
uint16_t guardInterval;
uint32_t injectDelay;
uint32_t injectRepeat;
std::string coding;
std::string format;
bool inject;
bool measure;
std::string mode;
std::string ltf;
std::string inputFile;
std::map<enum processor, bool> processors;
};
/* Get the input argument from argp_parse, which we
know is a pointer to our arguments structure. */
extern struct Arguments arguments;
#endif

View File

@ -1,20 +1,23 @@
# FeitCSI, the 802.11 CSI tool
Visit [https://feitcsi.kuskosoft.com](https://feitcsi.kuskosoft.com) to view the full documentation.
FeitCSI is the **first open-source** tool that enables CSI extraction and injection frames for **all formats (802.11a/g/n/ac/ax)** in **all bandwidths (20/40/80/160 MHz)** from commodity intel NIC **AX200, AX210** (There is high possibility that newest intel NIC is also supported). The aim of FeitCSI tool is:
: - **easy to use**, providing a tool in different formats source, binary, and also live distribution with a tool for virtual environments or live USB and other discs
- **easy to use**, providing a tool in different formats source, binary, and also live distribution with a tool for virtual environments or live USB and other discs
- **high availability** on different devices (e.g. x86_64, arm)
- **open-source**, which allows the community to contribute and improve this tool for purpose of future researches and next activities
---
## Functionality
FeitCSI allows you to **extract CSI** and **inject frames** for **all frames 802.11a/g/n/ac/ax** and **all bandwidths (20/40/80/160 MHz) with NO LIMITS**. Based on the NIC used also **6 GHz band is supported** (5945 - 7125 MHz). The main tool provides for users [Command line interface](command_line_interface.md) where can set up all parameters. For users less proficient at the command line and for comfortable tuning parameters [Graphical user interface](graphical_user_interface.md) is provided.
## Functionality
!!! Warning
FeitCSI is unlocked for all supported bands, channels, etc. You are responsible for following the laws of the country, frequency, and power levels in which the device is used.
FeitCSI allows you to **extract CSI** and **inject frames** for **all frames 802.11a/g/n/ac/ax** and **all bandwidths (20/40/80/160 MHz) with NO LIMITS**. Based on the NIC used also **6 GHz band is supported** (5945 - 7125 MHz). The main tool provides for users [Command line interface](https://feitcsi.kuskosoft.com/command_line_interface/) where can set up all parameters. For users less proficient at the command line and for comfortable tuning parameters [Graphical user interface](https://feitcsi.kuskosoft.com/graphical_user_interface/) is provided.
Are you prepared? Read [Getting started](getting_started.md) guide.
> [!WARNING]
> FeitCSI is unlocked for all supported bands, channels, etc. You are responsible for following the laws of the country, frequency, and power levels in which the device is used.
Are you prepared? Read [Getting started](https://feitcsi.kuskosoft.com/getting_started/) guide.
---
@ -38,9 +41,9 @@ citations to the FeitCSI project:
## Competition
There are some platforms, that are used to extract CSI from commodity hardware. However, they don't support current standards or injection packets. The only one competition is PicoScenes. It is a robust and good platform but FeitCSI has several advantages against it:
* **All for free without limits** (Some of the functionality (full 6GHz spectrum, packet injection in 11ac/ax format with 80/160MHz channel bandwidth, ...) are allowed after payment for the license in PicoScenes)
* **All for free without limits**
* **No restriction to specific version of Linux OS and computer architecture** (PicoScenes work only on CPU with SSE4.2 or AVX2 and you need to have Ubuntu 20.04 LTS or its variants. **FeitCSI will work on every computer architecture** where can be installed Linux (e.g. x86-64, ARM). But of course, you have to plug NICs via PCI.)
* **Support virtual environment** (If you don't want to install software on your computer or you have trouble, just download our prepared image of FeitCSI and enjoy FeitCSI in the virtual environment. Follow the steps in [Getting started - Virtual environment](getting_started.md#virtual-environment))
* **Support virtual environment** (If you don't want to install software on your computer or you have trouble, just download our prepared image of FeitCSI and enjoy FeitCSI in the virtual environment. Follow the steps in [Getting started - Virtual environment](https://feitcsi.kuskosoft.com/getting_started/#virtual-environment))
* **Live medium** (Our image can be also used in live medium (e.g. USB), not only in the virtual environment)
* **No data collection** (PicoScenes has in eula that you agree to fill and share real information about your affiliation and research team also they collected and use statistics on selected information on your computer, including but not limited to computer hardware information, non-personal files on your computer and data or files generated by its software. Based on the last words your CSI data aren't safe. Looks a bit like spyware to me)
* **User friendly GUI application** (We provide a GUI application where you can fast-tune parameters by filling in fields in the form)

386
src/Arguments.cpp Normal file
View File

@ -0,0 +1,386 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2024-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Arguments.h"
#include "WiFIController.h"
#include "rs.h"
const std::string VERSION = (std::string("FeitCSI ") + FEITCSI_VERSION);
const char *argp_program_version = VERSION.c_str();
const char *argp_program_bug_address = "https://github.com/KuskoSoft/FeitCSI/issues";
void Arguments::init()
{
Arguments::arguments = {
.strict = false,
.verbose = false,
.frequency = 2412,
.gui = false,
.udpSocket = false,
.plot = false,
.bandwidth = "20",
.mcs = 0,
.channelWidth = 20,
.spatialStreams = 1,
.txPower = 10,
.antenna = RATE_MCS_ANT_A_MSK,
.guardInterval = 400,
.injectDelay = 100000,
.injectRepeat = 0,
.coding = "LDPC",
.format = "HT",
.inject = false,
.measure = true,
.mode = "measure",
.ltf = "1xLTF+0.8",
.modeDelay = 3000,
.ftm = false,
.ftmResponder = false,
.ftmAsap = false,
.ftmBurstExp = 0,
.ftmPerBurst = 0,
.ftmBurstPeriod = 0,
.ftmBurstDuration = 0,
.mac = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}
};
}
void Arguments::parse(int argc, char *argv[])
{
static struct argp argp = {options, parse_opt, args_doc, doc};
argp_parse(&argp, argc, argv, 0, 0, &arguments);
}
error_t Arguments::parse_opt(int key, char *arg, struct argp_state *state)
{
/* Get the input argument from argp_parse, which we
know is a pointer to our arguments structure. */
struct Args *args = (struct Args *)state->input;
switch (key)
{
case 'v':
args->verbose = true;
break;
case 'z':
args->strict = true;
break;
case 'x':
args->gui = true;
break;
case 'u':
args->udpSocket = true;
break;
case 'p':
args->plot = true;
break;
case 'i':
{
args->mode.assign(arg);
if (args->mode == "measure")
{
args->measure = true;
}
else if (args->mode == "inject")
{
args->inject = true;
args->measure = false;
}
else if (args->mode == "measureinject")
{
args->measure = true;
args->inject = true;
}
else if (args->mode == "measureftm")
{
args->measure = true;
args->ftm = true;
}
else if (args->mode == "ftm")
{
args->measure = false;
args->ftm = true;
}
else if (args->mode == "ftmres")
{
args->measure = false;
args->ftmResponder = true;
}
else if (args->mode == "injectftmres")
{
args->measure = false;
args->ftmResponder = true;
args->inject = true;
}
else
{
argp_failure(state, 1, 0, "Bad mode. Possible values [measure|inject|measureinject|ftm]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'm':
{
int mcs = std::atoi(arg);
if (mcs < 0 || mcs > 11)
{
argp_failure(state, 1, 0, "Bad MCS index. Possible values [0-11]");
exit(ARGP_ERR_UNKNOWN);
}
args->mcs = (uint8_t)mcs;
break;
}
case 'r':
{
args->format.assign(arg);
if (args->format == "NOHT" || args->format == "HT" || args->format == "VHT" || args->format == "HESU")
{
}
else
{
argp_failure(state, 1, 0, "Bad format. Possible values [NOHT|HT|VHT|HESU]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'c':
{
args->coding.assign(arg);
if (args->coding == "LDPC" || args->coding == "BCC")
{
}
else
{
argp_failure(state, 1, 0, "Bad coding. Possible values [LDPC|BCC]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'l':
{
args->ltf.assign(arg);
if (args->ltf == "1xLTF+0.8" || args->ltf == "2xLTF+0.8" || args->ltf == "2xLTF+1.6" || args->ltf == "4xLTF+3.2" || args->ltf == "4xLTF+0.8")
{
}
else
{
argp_failure(state, 1, 0, "Bad LTF. Possible values [2xLTF+0.8|2xLTF+1.6|4xLTF+3.2|4xLTF+0.8]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'y':
{
int modeDelay = std::atoi(arg);
if (modeDelay <= 0)
{
argp_failure(state, 1, 0, "Mode delay is not correct number");
exit(ARGP_ERR_UNKNOWN);
}
args->modeDelay = (uint32_t)modeDelay;
break;
}
case 'g':
{
int gi = std::atoi(arg);
if (gi == 400 || gi == 800)
{
args->guardInterval = (uint16_t)gi;
}
else
{
argp_failure(state, 1, 0, "Bad guard interval. Possible values [400|800]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'd':
{
int injd = std::atoi(arg);
if (injd <= 0)
{
argp_failure(state, 1, 0, "Inject delay is not correct number");
exit(ARGP_ERR_UNKNOWN);
}
args->injectDelay = (uint32_t)injd;
break;
}
case 'j':
{
int injr = std::atoi(arg);
if (injr <= 0)
{
argp_failure(state, 1, 0, "Inject repeat is not correct number");
exit(ARGP_ERR_UNKNOWN);
}
args->injectRepeat = (uint32_t)injr;
break;
}
case 's':
{
int ss = std::atoi(arg);
if (ss < 1 || ss > 2)
{
argp_failure(state, 1, 0, "Bad spatial stream. Possible values [1|2]");
exit(ARGP_ERR_UNKNOWN);
}
args->spatialStreams = (uint8_t)ss;
break;
}
case 't':
{
int tx = std::atoi(arg);
if (tx < 1 || tx > 22)
{
argp_failure(state, 1, 0, "Bad tx power. Possible values [1-22]");
exit(ARGP_ERR_UNKNOWN);
}
args->txPower = (uint8_t)tx;
break;
}
case 'a':
{
int a = std::atoi(arg);
if (a == 1)
{
args->antenna = RATE_MCS_ANT_A_MSK;
}
else if (a == 2)
{
args->antenna = RATE_MCS_ANT_B_MSK;
}
else if (a == 12)
{
args->antenna = RATE_MCS_ANT_AB_MSK;
}
else
{
argp_failure(state, 1, 0, "Bad transmitting antenna value. Possible values 1, 2 or 12 for both");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'f':
{
int f = std::atoi(arg);
if (f <= 0)
{
argp_failure(state, 1, 0, "Frequency is not correct");
exit(ARGP_ERR_UNKNOWN);
}
args->frequency = (uint16_t)f;
break;
}
case 'w':
{
struct ChanMode chMode = WiFIController::getChanMode(arg);
if (chMode.width == 0)
{
argp_failure(state, 1, 0, "Bad bandwidth. Possible values of bandwidth are [20|40|HT40-|80|160]");
exit(ARGP_ERR_UNKNOWN);
}
args->bandwidth = arg;
args->channelWidth = WiFIController::chanModeToWidth(chMode);
break;
}
case 'o':
args->outputFile = arg;
break;
case 'b':
args->ftmAsap = true;
break;
case 'q':
{
int f = std::atoi(arg);
if (f <= 0)
{
argp_failure(state, 1, 0, "FTM burst exponent is not correct");
exit(ARGP_ERR_UNKNOWN);
}
args->ftmBurstExp = (uint8_t)f;
break;
}
case 'e':
{
int f = std::atoi(arg);
if (f <= 0)
{
argp_failure(state, 1, 0, "FTM per burst is not correct");
exit(ARGP_ERR_UNKNOWN);
}
args->ftmPerBurst = (uint8_t)f;
break;
}
case 'h':
{
int f = std::atoi(arg);
if (f <= 0)
{
argp_failure(state, 1, 0, "FTM burst period is not correct");
exit(ARGP_ERR_UNKNOWN);
}
args->ftmBurstPeriod = (uint16_t)f;
break;
}
case 'k':
{
int f = std::atoi(arg);
if (f <= 0)
{
argp_failure(state, 1, 0, "FTM burst duration is not correct");
exit(ARGP_ERR_UNKNOWN);
}
args->ftmBurstDuration = (uint8_t)f;
break;
}
case '#':
{
int res = sscanf(arg, "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", &args->mac[0], &args->mac[1], &args->mac[2], &args->mac[3], &args->mac[4], &args->mac[5]);
if (res != ETH_ALEN)
{
argp_failure(state, 1, 0, "Bad mac address");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'n':
{
int res = sscanf(arg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&args->ftmTargetMac[0], &args->ftmTargetMac[1], &args->ftmTargetMac[2], &args->ftmTargetMac[3], &args->ftmTargetMac[4], &args->ftmTargetMac[5]);
if (res != ETH_ALEN)
{
argp_failure(state, 1, 0, "FTM target mac address is not correct");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case ARGP_KEY_ARG:
case ARGP_KEY_END:
if (args->frequency == 0 ||
args->bandwidth.empty())
{
argp_failure(state, 1, 0, "Fill required arguments -f -b . See --help for more information");
exit(ARGP_ERR_UNKNOWN);
}
return 0;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -24,9 +24,9 @@
#include <vector>
#include <iostream>
#include <filesystem>
#include "main.h"
#include "rs.h"
#include "Logger.h"
#include "Arguments.h"
Csi::Csi()
{
@ -58,6 +58,7 @@ void Csi::loadFromMemory(uint8_t *pHeader, uint8_t *pRawCsiData)
memcpy(&this->rawHeaderData, pHeader, CSI_HEADER_LENGTH);
this->rawCsiData = new uint8_t[this->rawHeaderData.csiDataSize];
memcpy(this->rawCsiData, pRawCsiData, this->rawHeaderData.csiDataSize);
//this->rawHeaderData.timestamp = (uint64_t)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
this->processRawCsi();
}
@ -73,7 +74,7 @@ void Csi::loadFromMemory(uint8_t *rawData)
void Csi::save()
{
std::ofstream outfile;
outfile.open(arguments.outputFile, std::ios_base::app | std::ios::binary);
outfile.open(Arguments::arguments.outputFile, std::ios_base::app | std::ios::binary);
if (outfile.fail())
{
throw std::ios_base::failure("Open file failed: " + std::string(std::strerror(errno)));
@ -81,7 +82,16 @@ void Csi::save()
outfile.write(reinterpret_cast<char *>(&this->rawHeaderData), sizeof(RawHeaderData));
outfile.write(reinterpret_cast<char *>(this->rawCsiData), this->rawHeaderData.csiDataSize);
outfile.close();
std::filesystem::permissions(arguments.outputFile, std::filesystem::perms::all & ~(std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec), std::filesystem::perm_options::add);
std::filesystem::permissions(Arguments::arguments.outputFile, std::filesystem::perms::all & ~(std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec), std::filesystem::perm_options::add);
}
void Csi::sendUDP(UdpSocket *udpSocket)
{
int size = CSI_HEADER_LENGTH + this->rawHeaderData.csiDataSize;
char data[size];
memcpy(data, &this->rawHeaderData, CSI_HEADER_LENGTH);
memcpy(&data[CSI_HEADER_LENGTH], this->rawCsiData, this->rawHeaderData.csiDataSize);
udpSocket->send(data, size);
}
void Csi::fixCsiBug()
@ -196,57 +206,6 @@ void Csi::processRawCsi()
this->magnitude.push_back(std::abs(c));
this->phase.push_back(std::arg(c));
}
//this->unwrapPhase();
if (arguments.verbose)
{
Logger::log(info) << "Subcarrier count: " << this->rawHeaderData.numSubCarriers << ", ";
Logger::log(info, true) << "RX: " << +this->rawHeaderData.numRx << ", ";
Logger::log(info, true) << "TX: " << +this->rawHeaderData.numTx << ", ";
switch (channelWidth)
{
case RATE_MCS_CHAN_WIDTH_20:
Logger::log(info, true) << "Channel width: 20, ";
break;
case RATE_MCS_CHAN_WIDTH_40:
Logger::log(info, true) << "Channel width: 40, ";
break;
case RATE_MCS_CHAN_WIDTH_80:
Logger::log(info, true) << "Channel width: 80, ";
break;
case RATE_MCS_CHAN_WIDTH_160:
Logger::log(info, true) << "Channel width: 160, ";
break;
}
switch (format)
{
case RATE_MCS_CCK_MSK: // VERY OLD FORMAT
Logger::log(info, true) << "Format: CCK\n";
break;
case RATE_MCS_LEGACY_OFDM_MSK:
Logger::log(info, true) << "Format: LEGACY_OFDM\n";
break;
break;
case RATE_MCS_HT_MSK:
Logger::log(info, true) << "Format: HT\n";
break;
break;
case RATE_MCS_VHT_MSK:
Logger::log(info, true) << "Format: VHT\n";
break;
break;
case RATE_MCS_HE_MSK:
Logger::log(info, true) << "Format: HE\n";
break;
break;
case RATE_MCS_EHT_MSK:
Logger::log(info, true) << "Format: EHT\n";
break;
break;
}
}
}
void Csi::backup()

View File

@ -19,8 +19,8 @@
#include "CsiProcessor.h"
#include "main.h"
#include "Logger.h"
#include "GnuPlot.h"
#include "interpolation.h"
#include "Arguments.h"
#include <fstream>
#include <numeric>
@ -30,7 +30,7 @@
bool CsiProcessor::loadCsi()
{
this->clearState();
std::ifstream ifs(arguments.inputFile, std::ios::binary);
std::ifstream ifs(Arguments::arguments.inputFile, std::ios::binary);
ifs.seekg (0, ifs.end);
int length = ifs.tellg();
@ -60,7 +60,7 @@ bool CsiProcessor::loadCsi()
void CsiProcessor::saveCsi()
{
std::ofstream outfile;
outfile.open(arguments.outputFile, std::ios_base::app | std::ios::binary);
outfile.open(Arguments::arguments.outputFile, std::ios_base::app | std::ios::binary);
if (outfile.fail())
{
throw std::ios_base::failure("Open file failed: " + std::string(std::strerror(errno)));
@ -74,7 +74,7 @@ void CsiProcessor::saveCsi()
outfile.write(reinterpret_cast<char *>(c->csi.data()), c->rawHeaderData.csiDataSize);
}
outfile.close();
std::filesystem::permissions(arguments.outputFile, std::filesystem::perms::all & ~(std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec), std::filesystem::perm_options::add);
std::filesystem::permissions(Arguments::arguments.outputFile, std::filesystem::perms::all & ~(std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec), std::filesystem::perm_options::add);
}
CsiProcessor::~CsiProcessor()
@ -132,20 +132,20 @@ void CsiProcessor::process(Csi &csi)
csi.backup();
csi.restore();
if (arguments.processors[processor::interpolateLinear])
if (Arguments::arguments.processors[processor::interpolateLinear])
{
this->interpolate(csi, processor::interpolateLinear);
}
else if (arguments.processors[processor::interpolateCubic])
else if (Arguments::arguments.processors[processor::interpolateCubic])
{
this->interpolate(csi, processor::interpolateCubic);
}
else if (arguments.processors[processor::interpolateCosine])
else if (Arguments::arguments.processors[processor::interpolateCosine])
{
this->interpolate(csi, processor::interpolateCosine);
}
if (arguments.processors[processor::phaseCalibrationLinearTransform])
if (Arguments::arguments.processors[processor::phaseCalibrationLinearTransform])
{
this->phaseCalibLinearTransform(csi);
}

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,10 +17,11 @@
*/
#include "MainController.h"
#include "main.h"
#include "Arguments.h"
#include "Logger.h"
#include "gui/MainWindow.h"
#include "layout.h"
#include "WiFiFtmController.h"
#include <iostream>
#include <chrono>
#include <thread>
@ -39,42 +40,187 @@ MainController *MainController::getInstance()
return INSTANCE;
}
gint MainController::updatePlots()
{
WiFiCsiController::csiQueueMutex.lock();
if (!WiFiCsiController::csiQueue.empty())
{
if (csiToPlot)
{
delete csiToPlot;
}
csiToPlot = WiFiCsiController::csiQueue.front();
WiFiCsiController::csiQueue.pop();
}
//clear old values
while (!WiFiCsiController::csiQueue.empty()) {
delete WiFiCsiController::csiQueue.front();
WiFiCsiController::csiQueue.pop();
}
WiFiCsiController::csiQueueMutex.unlock();
if (!csiToPlot)
{
return (TRUE);
}
MainController *mainController = MainController::getInstance();
mainController->plotAmplitude->updateData(csiToPlot, &csiToPlot->magnitude);
mainController->plotPhase->updateData(csiToPlot, &csiToPlot->phase);
// force refresh, not wait on next frame redraw
/* GtkAllocation reg;
gtk_widget_get_allocation((GtkWidget *)mainController->plotAmplitude->get_parent()->gobj(), &reg);
cairo_region_t *creg = cairo_region_create_rectangle(&reg);
GdkWindow *pUnderlyingWindow = gtk_widget_get_window((GtkWidget *)mainController->plotAmplitude->get_parent()->gobj());
cairo_t *cr = gdk_cairo_create(pUnderlyingWindow);
gdk_cairo_region(cr, creg);
cairo_clip(cr);
gtk_widget_draw((GtkWidget *)mainController->plotAmplitude->get_parent()->gobj(), cr);
cairo_destroy(cr);
cairo_region_destroy(creg); */
return (TRUE);
}
void MainController::initPlots()
{
Glib::RefPtr<Gtk::Box> plotBox = Glib::RefPtr<Gtk::Box>::cast_dynamic(this->mainWindow->builder->get_object("plotBox"));
MainController *mainController = MainController::getInstance();
mainController->plotAmplitude->yLabel = "Amplitude";
mainController->plotAmplitude->title = "Amplitude";
mainController->plotAmplitude->init(plotBox);
mainController->plotPhase->yLabel = "Phase (rad)";
mainController->plotPhase->title = "Phase";
mainController->plotPhase->yTicksMin = -4;
mainController->plotPhase->yTicksMax = 4;
mainController->plotPhase->init(plotBox);
this->updatePlotsSourceId = gdk_threads_add_idle((GSourceFunc)MainController::updatePlots, nullptr);
}
void MainController::deleteInstance()
{
MainController *mainController = MainController::INSTANCE;
if (mainController->measuring)
{
MainController::INSTANCE->measureCsi(true);
}
if (mainController->injecting)
{
MainController::INSTANCE->injectPackets(true);
}
if (Arguments::arguments.plot)
{
MainController::INSTANCE->app->quit();
}
delete MainController::INSTANCE;
}
void MainController::runNoGui()
void MainController::runNoGui(bool detach)
{
this->initInterface();
if (arguments.measure)
if (Arguments::arguments.plot)
{
gtk_init(NULL, NULL);
Glib::init();
this->plotAmplitude = new Plot();
this->plotPhase = new Plot();
this->app = Gtk::Application::create("com.kuskosoft.feitcsi");
std::string layout((char *)MainWindow_glade);
//auto builder = Gtk::Builder::create_from_file("MainWindow.glade");
auto builder = Gtk::Builder::create_from_string(layout);
builder->get_widget_derived("MainWindow", this->mainWindow);
this->initPlots();
if (Arguments::arguments.measure && !Arguments::arguments.ftm)
{
this->measureCsi();
}
if (Arguments::arguments.inject && !Arguments::arguments.ftmResponder)
{
this->injectPackets();
}
this->app->run(*this->mainWindow);
return;
}
if (Arguments::arguments.measure && !Arguments::arguments.ftm)
{
this->measuring = true;
pthread_create(&this->measureCsiThread, NULL, &MainController::measureCsi, NULL);
}
if (arguments.inject)
if (Arguments::arguments.inject && !Arguments::arguments.ftmResponder)
{
this->injecting = true;
pthread_create(&this->injectPacketThread, NULL, &MainController::injectPackets, NULL);
}
if (arguments.measure)
if (Arguments::arguments.ftm)
{
this->ftmEnabled = true;
pthread_create(&this->ftmThread, NULL, &MainController::ftm, NULL);
}
if (Arguments::arguments.ftmResponder)
{
this->ftmResponderEnabled = true;
pthread_create(&this->ftmResponderThread, NULL, &MainController::ftmResponder, NULL);
}
if (Arguments::arguments.measure && !Arguments::arguments.ftm)
{
if (detach) {
pthread_detach(this->measureCsiThread);
} else {
pthread_join(this->measureCsiThread, NULL);
}
if (arguments.inject)
}
if (Arguments::arguments.inject && !Arguments::arguments.ftmResponder)
{
if (detach) {
pthread_detach(this->injectPacketThread);
} else {
pthread_join(this->injectPacketThread, NULL);
}
this->deleteInstance();
}
if (Arguments::arguments.ftm)
{
if (detach)
{
pthread_detach(this->ftmThread);
}
else
{
pthread_join(this->ftmThread, NULL);
}
}
if (Arguments::arguments.ftmResponder)
{
if (detach)
{
pthread_detach(this->ftmResponderThread);
}
else
{
pthread_join(this->ftmResponderThread, NULL);
}
}
}
void MainController::measureCsi(bool stop)
{
this->wifiController.setFreq(arguments.frequency, arguments.bandwidth.c_str());
if (stop)
{
this->measuring = false;
pthread_cancel(this->measureCsiThread);
}
else
{
this->wifiController.setFreq(Arguments::arguments.frequency, Arguments::arguments.bandwidth.c_str());
this->measuring = true;
pthread_create(&this->measureCsiThread, NULL, &MainController::measureCsi, NULL);
pthread_detach(this->measureCsiThread);
}
@ -82,14 +228,15 @@ void MainController::measureCsi(bool stop)
void MainController::injectPackets(bool stop)
{
this->wifiController.setTxPower();
this->wifiController.setFreq(arguments.frequency, arguments.bandwidth.c_str());
if (stop)
{
this->injecting = false;
pthread_cancel(this->injectPacketThread);
}
else
{
this->wifiController.setFreq(Arguments::arguments.frequency, Arguments::arguments.bandwidth.c_str());
this->injecting = true;
pthread_create(&this->injectPacketThread, NULL, &MainController::injectPackets, NULL);
pthread_detach(this->injectPacketThread);
}
@ -97,17 +244,29 @@ void MainController::injectPackets(bool stop)
void MainController::runGui()
{
arguments.plot = true;
arguments.verbose = true;
arguments.measure = false;
arguments.inject = false;
this->initInterface();
Arguments::arguments.plot = true;
Arguments::arguments.verbose = true;
Arguments::arguments.measure = false;
Arguments::arguments.inject = false;
gtk_init(NULL, NULL);
Glib::init();
auto app = Gtk::Application::create("com.kuskosoft.feitcsi");
this->plotAmplitude = new Plot();
this->plotPhase = new Plot();
this->app = Gtk::Application::create("com.kuskosoft.feitcsi");
std::string layout((char*)MainWindow_glade);
//auto builder = Gtk::Builder::create_from_file("MainWindow.glade");
auto builder = Gtk::Builder::create_from_string(layout);
builder->get_widget_derived("MainWindow", this->mainWindow);
app->run(*this->mainWindow);
this->initPlots();
//g_signal_connect(app, "delete-event", G_CALLBACK(MainController::deleteInstance), NULL);
this->app->run(*this->mainWindow);
}
void MainController::runUdpSocket()
{
this->udpSocket = new UdpSocket();
udpSocket->init();
}
void MainController::initInterface()
@ -121,22 +280,17 @@ void MainController::initInterface()
// delete actual interfaces
for (InterfaceInfo interface : this->wifiController.interfaces)
{
if (arguments.verbose)
if (Arguments::arguments.verbose)
{
Logger::log(info) << "Remove interface " << interface.ifName << "\n";
}
this->bkpInterfaces.push_back(interface);
this->wifiController.removeInterface(NULL, interface.ifIndex);
}
this->wifiController.addMonitorDevice(MONITOR_INTERFACE_NAME, NL80211_IFTYPE_MONITOR);
this->wifiController.setInterfaceUpDown(MONITOR_INTERFACE_NAME, true);
this->wifiController.setFreq(arguments.frequency, arguments.bandwidth.c_str());
if (!MainController::mainWindow)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500)); //wait on init then set power and go next
this->wifiController.setTxPower();
}
this->wifiController.createMonitorInteface();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
this->wifiController.createApInteface();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
catch(const std::exception& e)
{
@ -157,6 +311,8 @@ void *MainController::measureCsi(void *arg)
{
try
{
MainController::getInstance()->wifiController.setInterfaceUpDown(AP_INTERFACE_NAME, false);
MainController::getInstance()->wifiController.setInterfaceUpDown(MONITOR_INTERFACE_NAME, true);
WiFiCsiController wcs;
wcs.init();
wcs.listenToCsi();
@ -176,17 +332,171 @@ void *MainController::measureCsi(void *arg)
return 0;
}
void *MainController::ftm(void *arg)
{
bool firstRun = true;
try
{
MainController::getInstance()->wifiController.setInterfaceUpDown(AP_INTERFACE_NAME, true);
WiFiFtmController wft;
wft.init();
if (Arguments::arguments.measure)
{
uint64_t startFtmTime = 0;
while (1)
{
std::this_thread::sleep_for(std::chrono::microseconds(Arguments::arguments.injectDelay));
try
{
wft.startInitiator();
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
}
if (wft.lastRttIsSuccess && firstRun)
{
firstRun = false;
startFtmTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
if ((startFtmTime + (Arguments::arguments.modeDelay / 2)) > now)
{
continue;
}
if (!wft.lastRttIsSuccess && !firstRun)
{
MainController::getInstance()->measureCsi(false);
std::this_thread::sleep_for(std::chrono::milliseconds(Arguments::arguments.modeDelay));
MainController::getInstance()->measureCsi(true);
WiFiCsiController::enableCsi(false);
firstRun = true;
MainController::getInstance()->wifiController.setInterfaceUpDown(MONITOR_INTERFACE_NAME, false);
MainController::getInstance()->wifiController.setInterfaceUpDown(AP_INTERFACE_NAME, true);
}
}
}
else
{
if (Arguments::arguments.injectRepeat)
{
for (uint32_t i = 0; i < Arguments::arguments.injectRepeat; i++)
{
wft.startInitiator();
std::this_thread::sleep_for(std::chrono::microseconds(Arguments::arguments.injectDelay));
}
}
else
{
while (true)
{
wft.startInitiator();
std::this_thread::sleep_for(std::chrono::microseconds(Arguments::arguments.injectDelay));
}
}
}
}
catch (const std::exception &e)
{
if (MainController::mainWindow)
{
MainController::mainWindow->fatalError(e.what());
}
else
{
Logger::log(error) << e.what() << '\n';
}
}
return 0;
}
void *MainController::ftmResponder(void *arg)
{
WiFiFtmController wft;
wft.init();
try
{
if (Arguments::arguments.inject)
{
while (true)
{
MainController::getInstance()->injectPackets(false);
std::this_thread::sleep_for(std::chrono::milliseconds(Arguments::arguments.modeDelay));
MainController::getInstance()->injectPackets(true);
MainController::getInstance()->wifiController.setInterfaceUpDown(MONITOR_INTERFACE_NAME, false);
MainController::getInstance()->wifiController.setInterfaceUpDown(AP_INTERFACE_NAME, true);
MainController::getInstance()->wifiController.getInterfaceInfo(AP_INTERFACE_NAME);
while (MainController::getInstance()->wifiController.currentInterfaceInfo.freq == 0)
{
try
{
wft.startResponder();
}
catch (const std::exception &e)
{
// Just ignore.. wait on network up
}
MainController::getInstance()->wifiController.getInterfaceInfo(AP_INTERFACE_NAME);
}
std::this_thread::sleep_for(std::chrono::milliseconds(Arguments::arguments.modeDelay));
}
} else
{
while(MainController::getInstance()->wifiController.currentInterfaceInfo.freq == 0) {
try
{
wft.startResponder();
}
catch(const std::exception& e)
{
//Just ignore.. wait on network up
}
MainController::getInstance()->wifiController.getInterfaceInfo(AP_INTERFACE_NAME);
}
if (Arguments::arguments.verbose)
{
Logger::log(info) << "FTM responder was started\n";
}
}
while (1)
{
sleep(1);
}
}
catch (const std::exception &e)
{
if (MainController::mainWindow)
{
MainController::mainWindow->fatalError(e.what());
}
else
{
Logger::log(error) << e.what() << '\n';
}
}
return 0;
}
void *MainController::injectPackets(void *arg)
{
try
{
MainController::getInstance()->wifiController.setInterfaceUpDown(AP_INTERFACE_NAME, false);
MainController::getInstance()->wifiController.setInterfaceUpDown(MONITOR_INTERFACE_NAME, true);
PacketInjector pi;
if (arguments.injectRepeat)
if (Arguments::arguments.injectRepeat)
{
for (uint32_t i = 0; i < arguments.injectRepeat; i++)
for (uint32_t i = 0; i < Arguments::arguments.injectRepeat; i++)
{
pi.inject();
std::this_thread::sleep_for(std::chrono::microseconds(arguments.injectDelay));
std::this_thread::sleep_for(std::chrono::microseconds(Arguments::arguments.injectDelay));
}
}
else
@ -194,7 +504,7 @@ void *MainController::injectPackets(void *arg)
while (true)
{
pi.inject();
std::this_thread::sleep_for(std::chrono::microseconds(arguments.injectDelay));
std::this_thread::sleep_for(std::chrono::microseconds(Arguments::arguments.injectDelay));
}
}
}
@ -227,16 +537,19 @@ void MainController::restoreState()
}
mainController->wifiController.removeInterface(MONITOR_INTERFACE_NAME);
mainController->wifiController.removeInterface(AP_INTERFACE_NAME);
for (InterfaceInfo interface : mainController->bkpInterfaces)
{
if (arguments.verbose)
if (Arguments::arguments.verbose)
{
Logger::log(info) << "Recovering interface " << interface.ifName << "\n";
}
mainController->wifiController.addMonitorDevice(interface.ifName.c_str(), (nl80211_iftype)interface.ifType);
mainController->wifiController.addDevice(interface.ifName.c_str(), (nl80211_iftype)interface.ifType);
}
mainController->bkpInterfaces.clear();
mainController->wifiController.interfaces.clear();
if (arguments.verbose)
if (Arguments::arguments.verbose)
{
Logger::log(info) << "Exiting recovery state...\n";
}
@ -245,10 +558,17 @@ void MainController::restoreState()
MainController::~MainController()
{
this->restoreState();
if (udpSocket) {
delete udpSocket;
}
if (csiToPlot)
{
delete csiToPlot;
}
}
void MainController::intHandler(int dummy)
{
delete MainController::INSTANCE;
MainController::INSTANCE->deleteInstance();
exit(0);
}

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -127,6 +127,7 @@ int Netlink::nlExecCommand(Cmd &cmd)
nl_socket_set_cb(this->nlstate.nl_sock, s_cb);
err = nl_send_auto_complete(this->nlstate.nl_sock, msg);
if (err < 0)
goto out;
@ -145,7 +146,6 @@ int Netlink::nlExecCommand(Cmd &cmd)
{
errMsg = "command failed: " + std::string(strerror(-err)) + std::to_string(err) + "\n";
}
out:
nl_cb_put(cb);
nl_cb_put(s_cb);

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,8 +19,8 @@
#include "PacketInjector.h"
#include "main.h"
#include "Logger.h"
#include "Arguments.h"
#include <string.h>
#include <pcap.h>
#include <iostream>
#define SPATIAL_STREAM 16
@ -32,25 +32,25 @@ uint8_t ieee80211Body[] = {};
void PacketInjector::inject()
{
if (arguments.verbose)
if (Arguments::arguments.verbose)
{
Logger::log(info) << "Injecting " << arguments.format << "\n";
Logger::log(info) << "Injecting " << Arguments::arguments.format << "\n";
}
if (arguments.format == "NOHT")
if (Arguments::arguments.format == "NOHT")
{
this->injectNoHT();
}
else if (arguments.format == "HT")
else if (Arguments::arguments.format == "HT")
{
this->injectHT();
}
else if (arguments.format == "VHT")
else if (Arguments::arguments.format == "VHT")
{
this->injectVHT();
}
else if (arguments.format == "HESU")
else if (Arguments::arguments.format == "HESU")
{
this->injectHE();
}
@ -59,11 +59,11 @@ void PacketInjector::inject()
void PacketInjector::injectNoHT()
{
uint8_t mcs = 0;
if (RATE_LEGACY_RATE_MSK >= arguments.mcs)
if (RATE_LEGACY_RATE_MSK >= Arguments::arguments.mcs)
{
mcs = RATE_LEGACY_RATE_MSK & arguments.mcs;
mcs = RATE_LEGACY_RATE_MSK & Arguments::arguments.mcs;
}
uint32_t rateNFlags = RATE_MCS_LEGACY_OFDM_MSK | mcs;
uint32_t rateNFlags = RATE_MCS_LEGACY_OFDM_MSK | mcs | Arguments::arguments.antenna;
this->send(rateNFlags);
}
@ -71,63 +71,65 @@ void PacketInjector::injectNoHT()
void PacketInjector::injectHT()
{
uint8_t mcs = 0;
if (RATE_HT_MCS_CODE_MSK >= arguments.mcs)
if (RATE_HT_MCS_CODE_MSK >= Arguments::arguments.mcs)
{
mcs = RATE_HT_MCS_CODE_MSK & arguments.mcs;
mcs = RATE_HT_MCS_CODE_MSK & Arguments::arguments.mcs;
}
uint32_t rateNFlags =
RATE_MCS_HT_MSK |
mcs |
(arguments.channelWidth == 40 ? RATE_MCS_CHAN_WIDTH_40 : 0) |
(arguments.spatialStreams == 2 ? SPATIAL_STREAM : 0) |
(arguments.spatialStreams == 2 ? RATE_MCS_ANT_AB_MSK : 0) |
(arguments.guardInterval == 400 ? RATE_MCS_SGI_MSK : 0) |
(arguments.coding == "LDPC" ? RATE_MCS_LDPC_MSK : 0);
Arguments::arguments.antenna |
(Arguments::arguments.channelWidth == 40 ? RATE_MCS_CHAN_WIDTH_40 : 0) |
(Arguments::arguments.spatialStreams == 2 ? SPATIAL_STREAM : 0) |
(Arguments::arguments.spatialStreams == 2 ? RATE_MCS_ANT_AB_MSK : 0) |
(Arguments::arguments.guardInterval == 400 ? RATE_MCS_SGI_MSK : 0) |
(Arguments::arguments.coding == "LDPC" ? RATE_MCS_LDPC_MSK : 0);
this->send(rateNFlags);
}
void PacketInjector::injectVHT()
{
uint8_t mcs = 0;
if (RATE_MCS_CODE_MSK >= arguments.mcs)
if (RATE_MCS_CODE_MSK >= Arguments::arguments.mcs)
{
mcs = RATE_MCS_CODE_MSK & arguments.mcs;
mcs = RATE_MCS_CODE_MSK & Arguments::arguments.mcs;
}
uint32_t rateNFlags =
RATE_MCS_VHT_MSK |
mcs |
(arguments.channelWidth == 40 ? RATE_MCS_CHAN_WIDTH_40 : 0) |
(arguments.channelWidth == 80 ? RATE_MCS_CHAN_WIDTH_80 : 0) |
(arguments.channelWidth == 160 ? RATE_MCS_CHAN_WIDTH_160 : 0) |
(arguments.spatialStreams == 2 ? SPATIAL_STREAM : 0) |
(arguments.spatialStreams == 2 ? RATE_MCS_ANT_AB_MSK : 0) |
(arguments.guardInterval == 400 ? RATE_MCS_SGI_MSK : 0) |
(arguments.coding == "LDPC" ? RATE_MCS_LDPC_MSK : 0);
Arguments::arguments.antenna |
(Arguments::arguments.channelWidth == 40 ? RATE_MCS_CHAN_WIDTH_40 : 0) |
(Arguments::arguments.channelWidth == 80 ? RATE_MCS_CHAN_WIDTH_80 : 0) |
(Arguments::arguments.channelWidth == 160 ? RATE_MCS_CHAN_WIDTH_160 : 0) |
(Arguments::arguments.spatialStreams == 2 ? SPATIAL_STREAM : 0) |
(Arguments::arguments.spatialStreams == 2 ? RATE_MCS_ANT_AB_MSK : 0) |
(Arguments::arguments.guardInterval == 400 ? RATE_MCS_SGI_MSK : 0) |
(Arguments::arguments.coding == "LDPC" ? RATE_MCS_LDPC_MSK : 0);
this->send(rateNFlags);
}
void PacketInjector::injectHE()
{
uint8_t mcs = 0;
if (RATE_MCS_CODE_MSK >= arguments.mcs)
if (RATE_MCS_CODE_MSK >= Arguments::arguments.mcs)
{
mcs = RATE_MCS_CODE_MSK & arguments.mcs;
mcs = RATE_MCS_CODE_MSK & Arguments::arguments.mcs;
}
uint32_t ltf = 1;
if (arguments.ltf == "2xLTF+0.8")
if (Arguments::arguments.ltf == "2xLTF+0.8")
{
ltf = 1;
}
else if (arguments.ltf == "2xLTF+1.6")
else if (Arguments::arguments.ltf == "2xLTF+1.6")
{
ltf = 2;
}
else if (arguments.ltf == "4xLTF+3.2")
else if (Arguments::arguments.ltf == "4xLTF+3.2")
{
ltf = 3;
}
else if (arguments.ltf == "4xLTF+0.8")
else if (Arguments::arguments.ltf == "4xLTF+0.8")
{
ltf = 4;
}
@ -137,12 +139,13 @@ void PacketInjector::injectHE()
RATE_MCS_HE_MSK |
RATE_MCS_LDPC_MSK |
mcs |
Arguments::arguments.antenna |
ltf |
(arguments.channelWidth == 40 ? RATE_MCS_CHAN_WIDTH_40 : 0) |
(arguments.channelWidth == 80 ? RATE_MCS_CHAN_WIDTH_80 : 0) |
(arguments.channelWidth == 160 ? RATE_MCS_CHAN_WIDTH_160 : 0) |
(arguments.spatialStreams == 2 ? SPATIAL_STREAM : 0) |
(arguments.spatialStreams == 2 ? RATE_MCS_ANT_AB_MSK : 0);
(Arguments::arguments.channelWidth == 40 ? RATE_MCS_CHAN_WIDTH_40 : 0) |
(Arguments::arguments.channelWidth == 80 ? RATE_MCS_CHAN_WIDTH_80 : 0) |
(Arguments::arguments.channelWidth == 160 ? RATE_MCS_CHAN_WIDTH_160 : 0) |
(Arguments::arguments.spatialStreams == 2 ? SPATIAL_STREAM : 0) |
(Arguments::arguments.spatialStreams == 2 ? RATE_MCS_ANT_AB_MSK : 0);
this->send(rateNFlags);
}
@ -167,15 +170,20 @@ void PacketInjector::send(uint32_t rateNFlags)
memcpy(&sendBuffer[rthdr.it_len], ieee80211Header, sizeof(ieee80211Header));
memcpy(&sendBuffer[rthdr.it_len + sizeof(ieee80211Header)], ieee80211Body, sizeof(ieee80211Body));
uint16_t totalSize = rthdr.it_len + sizeof(ieee80211Header) + sizeof(ieee80211Body);
int totalSize = rthdr.it_len + sizeof(ieee80211Header) + sizeof(ieee80211Body);
char szErrbuf[500];
pcap_t *ppcap = pcap_open_live("mon0", 800, 1, 20, szErrbuf);
if (ppcap == nullptr) {
ppcap = pcap_open_live(MONITOR_INTERFACE_NAME, 800, 1, 20, szErrbuf);
}
int r = pcap_inject(ppcap, sendBuffer, totalSize);
if (r > 0)
if (r != totalSize)
{
//pcap_perror(ppcap, "Failed to inject packet");
pcap_close(ppcap);
ppcap = nullptr;
//throw std::ios_base::failure("Failed to inject packet\n");
}

152
src/UdpSocket.cpp Normal file
View File

@ -0,0 +1,152 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2024 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "UdpSocket.h"
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include "Logger.h"
#include "Arguments.h"
#include "MainController.h"
#define PORT "8008"
#define BUF_SIZE 1024
void UdpSocket::init()
{
MainController *mainController = MainController::getInstance();
struct addrinfo hints;
struct addrinfo *result, *rp;
ssize_t nread;
int s;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
s = getaddrinfo(NULL, PORT, &hints, &result);
if (s != 0)
{
Logger::log(error) << "getaddrinfo: " << gai_strerror(s) << "\n";
exit(1);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully bind(2).
If socket(2) (or bind(2)) fails, we (close the socket
and) try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next)
{
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1)
continue;
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
break; /* Success */
close(sfd);
}
if (rp == NULL)
{ /* No address succeeded */
Logger::log(error) << "Error bind socket \n";
exit(1);
}
freeaddrinfo(result); /* No longer needed */
/* Read datagrams and echo them back to sender */
while (1)
{
char buf[BUF_SIZE] = {0};
peer_addr_len = sizeof(struct sockaddr_storage);
nread = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *)&peer_addr, &peer_addr_len);
if (nread == -1)
continue; /* Ignore failed request */
char host[NI_MAXHOST], service[NI_MAXSERV];
s = getnameinfo(
(struct sockaddr *)&peer_addr,
peer_addr_len,
host,
NI_MAXHOST,
service,
NI_MAXSERV,
NI_NUMERICSERV);
if (s == 0) {
if (strncmp(buf, "stop", 4) == 0) {
if (this->running) {
mainController->restoreState();
this->running = false;
}
} else {
char *args[128];
std::istringstream iss(buf);
std::string token;
int index = 0;
while (iss >> token)
{
char *arg = new char[token.size() + 1];
copy(token.begin(), token.end(), arg);
arg[token.size()] = '\0';
args[index] = arg;
index++;
}
Arguments::parse(index, &args[0]);
for (int i = 0; i < index - 1; i++)
delete[] args[i];
mainController->runNoGui(true);
this->running = true;
}
}
else {
Logger::log(error) << "Error getnameinfo " << gai_strerror(s) << "\n";
}
}
}
void UdpSocket::send(char *buf, int size)
{
if (
sendto(
sfd,
buf,
size,
0,
(struct sockaddr *)&peer_addr,
peer_addr_len) != size)
{
Logger::log(error) << "Error sending response \n";
}
}

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -33,6 +33,7 @@
#include <thread>
#include "main.h"
#include "Logger.h"
#include "Arguments.h"
const char *IF_MODES[NL80211_IFTYPE_MAX + 1] = {
"unspecified",
@ -106,20 +107,34 @@ void WiFIController::getInterfaces()
this->nlExecCommand(cmd);
}
void WiFIController::getInterfaceInfo(const char *ifname)
{
Cmd cmd{
.id = NL80211_CMD_GET_INTERFACE,
.idby = CIB_NETDEV,
.nlFlags = 0,
.device = if_nametoindex(ifname),
.handler = NULL,
.valid_handler = this->processGetInterfaceInfoHandler,
};
this->nlExecCommand(cmd);
}
void WiFIController::setTxPower()
{
Cmd cmd{
.id = NL80211_CMD_SET_WIPHY,
.idby = CIB_NETDEV,
.nlFlags = 0,
.device = if_nametoindex(MONITOR_INTERFACE_NAME),
.device = if_nametoindex(this->currentDeviceName.c_str()),
.handler = this->setTxPowerHandler,
};
this->nlExecCommand(cmd);
}
void WiFIController::addMonitorDevice(const char *name, const nl80211_iftype type)
void WiFIController::addDevice(const char *name, const nl80211_iftype type)
{
if (this->phys.empty())
{
@ -134,7 +149,7 @@ void WiFIController::addMonitorDevice(const char *name, const nl80211_iftype typ
.idby = CIB_PHY,
.nlFlags = 0,
.device = this->phys[0],
.handler = this->processAddMonitorDeviceHandler,
.handler = this->processaddDeviceHandler,
.valid_handler = NULL,
.args = settings,
};
@ -165,6 +180,7 @@ int WiFIController::setInterfaceUpDown(const char *ifName, bool up)
if (up)
{
ifr.ifr_flags |= IFF_UP;
this->currentDeviceName = ifName;
}
else
{
@ -180,7 +196,7 @@ int WiFIController::setInterfaceUpDown(const char *ifName, bool up)
// Close the socket
close(sockfd);
if (arguments.verbose)
if (Arguments::arguments.verbose)
{
Logger::log(info) << "Interface " << ifName << " has been brought " << (up ? "up" : "down") << "\n";
}
@ -195,7 +211,7 @@ void WiFIController::setFreq(uint16_t freq, const char *bw)
.id = NL80211_CMD_SET_WIPHY,
.idby = CIB_NETDEV,
.nlFlags = 0,
.device = if_nametoindex(MONITOR_INTERFACE_NAME),
.device = if_nametoindex(this->currentDeviceName.c_str()),
.handler = processSetFreq,
.valid_handler = NULL,
.args = settings,
@ -216,6 +232,48 @@ void WiFIController::removeInterface(const char *name, int ifIndex)
this->nlExecCommand(cmd);
}
void WiFIController::createMonitorInteface()
{
this->addDevice(MONITOR_INTERFACE_NAME, NL80211_IFTYPE_MONITOR);
while (this->currentInterfaceInfo.freq != Arguments::arguments.frequency)
{
try
{
this->setInterfaceUpDown(MONITOR_INTERFACE_NAME, true);
this->setFreq(Arguments::arguments.frequency, Arguments::arguments.bandwidth.c_str());
}
catch (const std::exception &e)
{
// Just skip
}
this->getInterfaceInfo(MONITOR_INTERFACE_NAME);
}
this->setTxPower();
}
void WiFIController::createApInteface()
{
this->addDevice(AP_INTERFACE_NAME, NL80211_IFTYPE_MONITOR);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
while (this->currentInterfaceInfo.ifType != NL80211_IFTYPE_AP)
{
try
{
this->setInterfaceUpDown(MONITOR_INTERFACE_NAME, false);
this->setApMode();
}
catch (const std::exception &e)
{
// Just skip
}
this->getInterfaceInfo(AP_INTERFACE_NAME);
}
this->setInterfaceUpDown(AP_INTERFACE_NAME, true);
this->setTxPower();
}
void WiFIController::getPhys()
{
Cmd cmd{
@ -347,7 +405,7 @@ int WiFIController::processSetFreq(struct nl80211_state *state, struct nl_msg *m
uint16_t settingsFreq = *(uint16_t *)(settings[0]);
char *settingsWidth = (char *)(settings[1]);
if (arguments.verbose)
if (Arguments::arguments.verbose)
{
Logger::log(info) << "Setting frequency " << settingsFreq << " channel width " << settingsWidth << "\n";
}
@ -416,7 +474,7 @@ nla_put_failure:
int WiFIController::setTxPowerHandler(nl80211_state *state, nl_msg *msg, void *arg)
{
enum nl80211_tx_power_setting type = NL80211_TX_POWER_FIXED;
int mbm = arguments.txPower * 100; //dBm to mbm *100
int mbm = Arguments::arguments.txPower * 100; // dBm to mbm *100
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_TX_POWER_SETTING, type);
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_TX_POWER_LEVEL, mbm);
@ -427,7 +485,7 @@ nla_put_failure:
return -ENOBUFS;
}
int WiFIController::processAddMonitorDeviceHandler(struct nl80211_state *state, struct nl_msg *msg, void *arg)
int WiFIController::processaddDeviceHandler(struct nl80211_state *state, struct nl_msg *msg, void *arg)
{
void **settings = (void **)arg;
const char *name = (const char *)settings[0];
@ -435,6 +493,8 @@ int WiFIController::processAddMonitorDeviceHandler(struct nl80211_state *state,
NLA_PUT_STRING(msg, NL80211_ATTR_IFNAME, name);
NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, *type);
NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, Arguments::arguments.mac);
return 0;
nla_put_failure:
return -ENOBUFS;
@ -591,6 +651,104 @@ int WiFIController::processGetInterfacesHandler(struct nl_msg *msg, void *arg)
return NL_OK;
}
int WiFIController::processGetInterfaceInfoHandler(struct nl_msg *msg, void *arg)
{
struct genlmsghdr *gnlh = (genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
WiFIController *wc = (WiFIController *)arg;
if (tb_msg[NL80211_ATTR_IFNAME])
{
wc->currentInterfaceInfo.ifName = nla_get_string(tb_msg[NL80211_ATTR_IFNAME]);
}
else
{
wc->currentInterfaceInfo.ifName = "Unnamed/non-netdev interface";
return NL_OK;
}
if (tb_msg[NL80211_ATTR_IFINDEX])
{
wc->currentInterfaceInfo.ifIndex = nla_get_u32(tb_msg[NL80211_ATTR_IFINDEX]);
}
else
{
wc->currentInterfaceInfo.ifIndex = 0;
}
if (tb_msg[NL80211_ATTR_WDEV])
{
wc->currentInterfaceInfo.wdev = (uint64_t)nla_get_u64(tb_msg[NL80211_ATTR_WDEV]);
}
else
{
wc->currentInterfaceInfo.wdev = 0;
}
if (tb_msg[NL80211_ATTR_MAC])
{
wc->currentInterfaceInfo.mac = wc->macN2a((const unsigned char *)nla_data(tb_msg[NL80211_ATTR_MAC]));
}
else
{
wc->currentInterfaceInfo.mac = "";
}
if (tb_msg[NL80211_ATTR_SSID])
{
wc->currentInterfaceInfo.ssid = (const char *)nla_data(tb_msg[NL80211_ATTR_SSID]);
}
else
{
wc->currentInterfaceInfo.ssid = "";
}
if (tb_msg[NL80211_ATTR_IFTYPE])
{
wc->currentInterfaceInfo.ifType = nla_get_u32(tb_msg[NL80211_ATTR_IFTYPE]);
}
else
{
wc->currentInterfaceInfo.ifType = 0;
}
if (tb_msg[NL80211_ATTR_WIPHY])
{
wc->currentInterfaceInfo.wiphy = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY]);
}
else
{
wc->currentInterfaceInfo.wiphy = 0;
}
if (tb_msg[NL80211_ATTR_WIPHY_FREQ])
{
wc->currentInterfaceInfo.freq = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_FREQ]);
wc->currentInterfaceInfo.channel = wc->freqToCHannel(wc->currentInterfaceInfo.freq);
}
else
{
wc->currentInterfaceInfo.freq = 0;
wc->currentInterfaceInfo.channel = 0;
}
if (tb_msg[NL80211_ATTR_WIPHY_TX_POWER_LEVEL])
{
int32_t txp = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_TX_POWER_LEVEL]);
wc->currentInterfaceInfo.txdBm = txp % 100;
wc->currentInterfaceInfo.txdBm += txp / 100;
}
else
{
wc->currentInterfaceInfo.txdBm = 0;
}
return NL_OK;
}
std::string WiFIController::macN2a(const unsigned char *arg)
{
int i, l;
@ -659,3 +817,25 @@ int WiFIController::phyLookup(char *name)
close(fd);
return atoi(buf);
}
int WiFIController::setApMode()
{
Cmd cmd{
.id = NL80211_CMD_SET_INTERFACE,
.idby = CIB_NETDEV,
.nlFlags = 0,
.device = if_nametoindex(AP_INTERFACE_NAME),
.handler = this->setApModeHandler,
.valid_handler = NULL,
};
return this->nlExecCommand(cmd);
}
int WiFIController::setApModeHandler(struct nl80211_state *state, struct nl_msg *msg, void *arg)
{
NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP);
return 0;
nla_put_failure:
return -ENOBUFS;
}

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,7 +18,8 @@
#include "WiFiCsiController.h"
#include "Csi.h"
#include "GnuPlot.h"
#include "MainController.h"
#include "Arguments.h"
#include <errno.h>
#include <netlink/genl/genl.h>
@ -42,8 +43,6 @@ void WiFiCsiController::init()
int WiFiCsiController::listenToCsi()
{
gnuPlot.init();
Cmd cmd{
.id = NL80211_CMD_VENDOR,
.idby = CIB_NETDEV,
@ -67,7 +66,6 @@ nla_put_failure:
int WiFiCsiController::processListenToCsiHandler(struct nl_msg *msg, void *arg)
{
WiFiCsiController *instance = (WiFiCsiController *)arg;
struct nlattr *attrs[MAX_CMD + 1];
struct nlmsghdr *nlh = nlmsg_hdr(msg);
@ -91,39 +89,107 @@ int WiFiCsiController::processListenToCsiHandler(struct nl_msg *msg, void *arg)
uint8_t *dataCsi = (uint8_t *)nla_data(attrs[IWL_MVM_VENDOR_ATTR_CSI_DATA]);
memcpy(rawCsi, dataCsi, dataLength);
Csi c;
c.loadFromMemory(header, dataCsi);
instance->gnuPlot.updateChart(c);
Csi *c = new Csi();
c->loadFromMemory(header, dataCsi);
if (
(c.channelWidth == RATE_MCS_CHAN_WIDTH_20 && arguments.channelWidth == 20) ||
(c.channelWidth == RATE_MCS_CHAN_WIDTH_40 && arguments.channelWidth == 40) ||
(c.channelWidth == RATE_MCS_CHAN_WIDTH_80 && arguments.channelWidth == 80) ||
(c.channelWidth == RATE_MCS_CHAN_WIDTH_160 && arguments.channelWidth == 160)
(c->channelWidth == RATE_MCS_CHAN_WIDTH_20 && Arguments::arguments.channelWidth == 20) ||
(c->channelWidth == RATE_MCS_CHAN_WIDTH_40 && Arguments::arguments.channelWidth == 40) ||
(c->channelWidth == RATE_MCS_CHAN_WIDTH_80 && Arguments::arguments.channelWidth == 80) ||
(c->channelWidth == RATE_MCS_CHAN_WIDTH_160 && Arguments::arguments.channelWidth == 160)
)
{
if (
(c.format == RATE_MCS_LEGACY_OFDM_MSK && arguments.format == "NOHT") ||
(c.format == RATE_MCS_HT_MSK && arguments.format == "HT") ||
(c.format == RATE_MCS_VHT_MSK && arguments.format == "VHT") ||
(c.format == RATE_MCS_HE_MSK && arguments.format == "HESU") ||
(c.format == RATE_MCS_EHT_MSK && arguments.format == "EHT")
(c->format == RATE_MCS_LEGACY_OFDM_MSK && Arguments::arguments.format == "NOHT") ||
(c->format == RATE_MCS_HT_MSK && Arguments::arguments.format == "HT") ||
(c->format == RATE_MCS_VHT_MSK && Arguments::arguments.format == "VHT") ||
(c->format == RATE_MCS_HE_MSK && Arguments::arguments.format == "HESU") ||
(c->format == RATE_MCS_EHT_MSK && Arguments::arguments.format == "EHT")
)
{
c.save();
printf("masm csi\n");
if (!Arguments::arguments.strict || (Arguments::arguments.strict && (c->rawHeaderData.rateNflag & RATE_LEGACY_RATE_MSK) == Arguments::arguments.mcs))
{
if (Arguments::arguments.verbose) {
printDetail(c);
}
if ( MainController::getInstance()->udpSocket ) {
c->sendUDP(MainController::getInstance()->udpSocket);
} else {
c->save();
}
if (Arguments::arguments.plot)
{
WiFiCsiController::csiQueueMutex.lock();
WiFiCsiController::csiQueue.push(c);
WiFiCsiController::csiQueueMutex.unlock();
}
}
}
}
}
}
WiFiCsiController *wcc = (WiFiCsiController*) arg;
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
if (wcc->stopTime != 0 && wcc->stopTime < now)
{
return NL_OK;
}
return NL_SKIP;
}
void WiFiCsiController::printDetail(Csi *c)
{
Logger::log(info) << "Subcarrier count: " << c->rawHeaderData.numSubCarriers << ", ";
Logger::log(info, true) << "RX: " << +c->rawHeaderData.numRx << ", ";
Logger::log(info, true) << "TX: " << +c->rawHeaderData.numTx << ", ";
switch (c->channelWidth)
{
case RATE_MCS_CHAN_WIDTH_20:
Logger::log(info, true) << "Channel width: 20, ";
break;
case RATE_MCS_CHAN_WIDTH_40:
Logger::log(info, true) << "Channel width: 40, ";
break;
case RATE_MCS_CHAN_WIDTH_80:
Logger::log(info, true) << "Channel width: 80, ";
break;
case RATE_MCS_CHAN_WIDTH_160:
Logger::log(info, true) << "Channel width: 160, ";
break;
}
switch (c->format)
{
case RATE_MCS_CCK_MSK: // VERY OLD FORMAT
Logger::log(info, true) << "Format: CCK\n";
break;
case RATE_MCS_LEGACY_OFDM_MSK:
Logger::log(info, true) << "Format: LEGACY_OFDM\n";
break;
case RATE_MCS_HT_MSK:
Logger::log(info, true) << "Format: HT\n";
break;
case RATE_MCS_VHT_MSK:
Logger::log(info, true) << "Format: VHT\n";
break;
case RATE_MCS_HE_MSK:
Logger::log(info, true) << "Format: HE\n";
break;
case RATE_MCS_EHT_MSK:
Logger::log(info, true) << "Format: EHT\n";
break;
}
}
void WiFiCsiController::enableCsi(bool enable)
{
if (arguments.verbose)
if (Arguments::arguments.verbose)
{
if (enable)
{

339
src/WiFiFtmController.cpp Normal file
View File

@ -0,0 +1,339 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2025 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "WiFiFtmController.h"
#include "main.h"
#include "Arguments.h"
#include <net/if.h>
#include "Logger.h"
#include "MainController.h"
#include <netlink/genl/genl.h>
#include <fstream>
#include <filesystem>
#include <cstring>
uint8_t beaconHeader[] = {0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xea, 0xb9, 0xcc, 0xc2, 0x4a, 0xaf, 0xea, 0xb9, 0xcc, 0xc2, 0x4a, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x04, 0x00, 0x07, 0x46, 0x65, 0x69, 0x74, 0x43, 0x53, 0x49, 0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x03, 0x01, 0x01};
void WiFiFtmController::init()
{
Netlink::init();
}
int WiFiFtmController::startInitiator()
{
Cmd cmd{
.id = NL80211_CMD_PEER_MEASUREMENT_START,
.idby = CIB_NETDEV,
.nlFlags = 0,
.device = if_nametoindex(AP_INTERFACE_NAME),
.handler = this->ftmHandler,
.valid_handler = this->processFtmHandler,
};
return this->nlExecCommand(cmd);
}
int WiFiFtmController::startResponder()
{
Cmd cmd{
.id = NL80211_CMD_NEW_BEACON,
.idby = CIB_NETDEV,
.nlFlags = 0,
.device = if_nametoindex(AP_INTERFACE_NAME),
.handler = this->ftmResponderHandler,
.valid_handler = NULL,
};
return this->nlExecCommand(cmd);
}
int WiFiFtmController::setApMode()
{
Cmd cmd{
.id = NL80211_CMD_SET_INTERFACE,
.idby = CIB_NETDEV,
.nlFlags = 0,
.device = if_nametoindex(AP_INTERFACE_NAME),
.handler = this->setApModeHandler,
.valid_handler = NULL,
};
return this->nlExecCommand(cmd);
}
int WiFiFtmController::ftmHandler(struct nl80211_state *state, struct nl_msg *msg, void *arg)
{
struct nlattr *pmsr, *peers, *peer, *req, *reqdata, *ftm, *chan;
pmsr = nla_nest_start(msg, NL80211_ATTR_PEER_MEASUREMENTS);
peers = nla_nest_start(msg, NL80211_PMSR_ATTR_PEERS);
peer = nla_nest_start(msg, 1); // TODO multiple peers
NLA_PUT(msg, NL80211_PMSR_PEER_ATTR_ADDR, ETH_ALEN, Arguments::arguments.ftmTargetMac);
req = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_REQ);
reqdata = nla_nest_start(msg, NL80211_PMSR_REQ_ATTR_DATA);
ftm = nla_nest_start(msg, NL80211_PMSR_TYPE_FTM);
if (Arguments::arguments.ftmBurstExp)
{
NLA_PUT_U8(msg, NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP, Arguments::arguments.ftmBurstExp);
}
if (Arguments::arguments.ftmBurstPeriod)
{
NLA_PUT_U16(msg, NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD, Arguments::arguments.ftmBurstPeriod);
}
if (Arguments::arguments.ftmBurstDuration)
{
NLA_PUT_U8(msg, NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION, Arguments::arguments.ftmBurstDuration);
}
if (Arguments::arguments.ftmPerBurst)
{
NLA_PUT_U8(msg, NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST, Arguments::arguments.ftmPerBurst);
}
if (Arguments::arguments.ftmAsap)
{
NLA_PUT_FLAG(msg, NL80211_PMSR_FTM_REQ_ATTR_ASAP);
}
if (Arguments::arguments.format == "NOHT")
{
NLA_PUT_U32(msg, NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE, NL80211_PREAMBLE_LEGACY);
}
else if (Arguments::arguments.format == "HT")
{
NLA_PUT_U32(msg, NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE, NL80211_PREAMBLE_HT);
}
else if (Arguments::arguments.format == "VHT")
{
NLA_PUT_U32(msg, NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE, NL80211_PREAMBLE_VHT);
}
else if (Arguments::arguments.format == "HESU")
{
NLA_PUT_U32(msg, NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE, NL80211_PREAMBLE_HE);
}
nla_nest_end(msg, ftm);
nla_nest_end(msg, reqdata);
nla_nest_end(msg, req);
chan = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_CHAN);
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, (uint32_t)Arguments::arguments.frequency);
switch (Arguments::arguments.channelWidth)
{
case 20:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_20);
break;
case 40:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_40);
break;
case 80:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_80);
break;
case 160:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_160);
break;
case 320:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_320);
break;
}
nla_nest_end(msg, chan);
nla_nest_end(msg, peer);
nla_nest_end(msg, peers);
nla_nest_end(msg, pmsr);
return 0;
nla_put_failure:
return -ENOBUFS;
}
int WiFiFtmController::ftmResponderHandler(struct nl80211_state *state, struct nl_msg *msg, void *arg)
{
NLA_PUT(msg, NL80211_ATTR_BEACON_HEAD, 58, beaconHeader);
NLA_PUT_U32(msg, NL80211_ATTR_BEACON_INTERVAL, 100);
NLA_PUT_U32(msg, NL80211_ATTR_DTIM_PERIOD, 2);
NLA_PUT(msg, NL80211_ATTR_SSID, 7, "FeitCSI");
NLA_PUT_U32(msg, NL80211_ATTR_HIDDEN_SSID, NL80211_HIDDEN_SSID_NOT_IN_USE);
NLA_PUT_U32(msg, NL80211_ATTR_AUTH_TYPE, NL80211_AUTHTYPE_OPEN_SYSTEM);
struct nlattr *ftm;
ftm = nla_nest_start(msg, NL80211_ATTR_FTM_RESPONDER);
NLA_PUT_FLAG(msg, NL80211_FTM_RESP_ATTR_ENABLED);
nla_nest_end(msg, ftm);
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, (uint32_t)Arguments::arguments.frequency);
switch (Arguments::arguments.channelWidth)
{
case 20:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_20);
break;
case 40:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_40);
break;
case 80:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_80);
break;
case 160:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_160);
break;
case 320:
NLA_PUT_U32(msg, NL80211_ATTR_CHANNEL_WIDTH, NL80211_CHAN_WIDTH_320);
break;
}
NLA_PUT_FLAG(msg, NL80211_ATTR_SOCKET_OWNER);
// nl_msg_dump(msg, stdout);
return 0;
nla_put_failure:
return -ENOBUFS;
}
int WiFiFtmController::setApModeHandler(struct nl80211_state *state, struct nl_msg *msg, void *arg)
{
NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP);
return 0;
nla_put_failure:
return -ENOBUFS;
}
int WiFiFtmController::processFtmHandler(struct nl_msg *msg, void *arg)
{
struct genlmsghdr *gnlh = (genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb[NL80211_ATTR_MAX + 1];
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
if (gnlh->cmd == NL80211_CMD_PEER_MEASUREMENT_COMPLETE)
{
return NL_OK;
}
if (gnlh->cmd == NL80211_CMD_PEER_MEASUREMENT_RESULT)
{
if (!tb[NL80211_ATTR_PEER_MEASUREMENTS])
{
return NL_OK;
}
struct nlattr *pmsr[NL80211_PMSR_ATTR_MAX + 1];
nla_parse_nested(pmsr, NL80211_PMSR_ATTR_MAX, tb[NL80211_ATTR_PEER_MEASUREMENTS], NULL);
if (!pmsr[NL80211_PMSR_ATTR_PEERS])
{
return NL_OK;
}
struct nlattr *peer = (nlattr *)nla_data(pmsr[NL80211_PMSR_ATTR_PEERS]);
struct nlattr *tbpeer[NL80211_PMSR_PEER_ATTR_MAX + 1];
struct nlattr *resp[NL80211_PMSR_RESP_ATTR_MAX + 1];
struct nlattr *data[NL80211_PMSR_TYPE_MAX + 1];
nla_parse_nested(tbpeer, NL80211_PMSR_PEER_ATTR_MAX, peer, NULL);
if (!tbpeer[NL80211_PMSR_PEER_ATTR_ADDR])
{
return NL_OK;
}
uint8_t mac[ETH_ALEN];
memcpy(mac, nla_data(tbpeer[NL80211_PMSR_PEER_ATTR_ADDR]), ETH_ALEN);
if (!tbpeer[NL80211_PMSR_PEER_ATTR_ADDR])
{
return NL_OK;
}
nla_parse_nested(resp, NL80211_PMSR_RESP_ATTR_MAX, tbpeer[NL80211_PMSR_PEER_ATTR_RESP], NULL);
if (!resp[NL80211_PMSR_RESP_ATTR_DATA])
{
return NL_OK;
}
nla_parse_nested(data, NL80211_PMSR_TYPE_MAX, resp[NL80211_PMSR_RESP_ATTR_DATA], NULL);
if (!data[NL80211_PMSR_TYPE_FTM])
{
return NL_OK;
}
struct nlattr *ftm[NL80211_PMSR_FTM_RESP_ATTR_MAX + 1];
nla_parse_nested(ftm, NL80211_PMSR_FTM_RESP_ATTR_MAX, data[NL80211_PMSR_TYPE_FTM], NULL);
WiFiFtmController *wfc = (WiFiFtmController *)arg;
if (ftm[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON])
{
uint32_t reason = nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]);
if (Arguments::arguments.verbose)
{
Logger::log(info) << "FTM failed to measure " << reason << "\n";
}
wfc->lastRttIsSuccess = false;
return NL_OK;
}
Ftm ftmData{
.burstIndex = (uint32_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_BURST_INDEX] ? nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_BURST_INDEX]) : 0),
.numFtmrAttempts = (uint32_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS] ? nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS]) : 0),
.numFtmrSuccesses = (uint32_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES] ? nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES]) : 0),
.numBurstsExp = (uint32_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP] ? nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP]) : 0),
.burstDuration = (uint8_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_BURST_DURATION] ? nla_get_u8(ftm[NL80211_PMSR_FTM_RESP_ATTR_BURST_DURATION]) : 0),
.ftmsPerBurst = (uint8_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_FTMS_PER_BURST] ? nla_get_u8(ftm[NL80211_PMSR_FTM_RESP_ATTR_FTMS_PER_BURST]) : 0),
.rssiAvg = (uint8_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_RSSI_AVG] ? nla_get_u8(ftm[NL80211_PMSR_FTM_RESP_ATTR_RSSI_AVG]) : 0),
.rssiSpread = (uint32_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_RSSI_SPREAD] ? nla_get_u32(ftm[NL80211_PMSR_FTM_RESP_ATTR_RSSI_SPREAD]) : 0),
.rttAvg = (uint64_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG] ? nla_get_u64(ftm[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG]) : 0),
.rttVariance = (uint64_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_RTT_VARIANCE] ? nla_get_u64(ftm[NL80211_PMSR_FTM_RESP_ATTR_RTT_VARIANCE]) : 0),
.rttSpread = (uint64_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_RTT_SPREAD] ? nla_get_u64(ftm[NL80211_PMSR_FTM_RESP_ATTR_RTT_SPREAD]) : 0),
.distAvg = (uint64_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_DIST_AVG] ? nla_get_u64(ftm[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG]) : 0),
.distVariance = (uint64_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_DIST_VARIANCE] ? nla_get_u64(ftm[NL80211_PMSR_FTM_RESP_ATTR_DIST_VARIANCE]) : 0),
.distSpread = (uint64_t)(ftm[NL80211_PMSR_FTM_RESP_ATTR_DIST_SPREAD] ? nla_get_u64(ftm[NL80211_PMSR_FTM_RESP_ATTR_DIST_SPREAD]) : 0),
.timestamp = (uint64_t)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count(),
};
if (Arguments::arguments.verbose)
{
Logger::log(info) << "FTM average RTT: " << ftmData.rttAvg << "ps\n";
}
if (MainController::getInstance()->udpSocket)
{
MainController::getInstance()->udpSocket->send(reinterpret_cast<char *>(&ftmData), FTM_SIZE);
} else {
std::ofstream outfile;
std::string filename = std::string("FTM_") + Arguments::arguments.outputFile;
outfile.open(filename, std::ios_base::app | std::ios::binary);
if (outfile.fail())
{
throw std::ios_base::failure("Open file failed: " + std::string(std::strerror(errno)));
}
outfile.write(reinterpret_cast<char *>(&ftmData), FTM_SIZE);
outfile.close();
std::filesystem::permissions(filename, std::filesystem::perms::all & ~(std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec), std::filesystem::perm_options::add);
}
wfc->lastRttIsSuccess = true;
return NL_OK;
}
return NL_OK;
}

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2024 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,9 +17,9 @@
*/
#include "gui/CsiProcessingWindow.h"
#include "GnuPlot.h"
#include "main.h"
#include "Logger.h"
#include "Arguments.h"
#include "CsiProcessingWindow.h"
void CsiProcessingWindow::init(Glib::RefPtr<Gtk::Builder> &builder)
@ -53,7 +53,7 @@ void CsiProcessingWindow::init(Glib::RefPtr<Gtk::Builder> &builder)
void CsiProcessingWindow::inputFileChange()
{
arguments.inputFile = this->inputFile->get_filename();
Arguments::arguments.inputFile = this->inputFile->get_filename();
csiProcessor.loadCsi();
this->refresh();
}
@ -64,10 +64,10 @@ void CsiProcessingWindow::refresh()
this->currentDataCount->set_text(indexLabelText);
if (!this->csiProcessor.csiData.empty())
/* if (!this->csiProcessor.csiData.empty())
{
gnuPlot.updateChart(*this->csiProcessor.csiData[this->currentIndex]);
}
gnuPlot.updateChartAsync(this->csiProcessor.csiData[this->currentIndex]);
} */
}
@ -95,7 +95,7 @@ void CsiProcessingWindow::interpolationLinearRadioButtonClicked()
{
if (!this->csiProcessor.csiData.empty())
{
arguments.processors[processor::interpolateLinear] = this->interpolationLinearRadioButton->get_active();
Arguments::arguments.processors[processor::interpolateLinear] = this->interpolationLinearRadioButton->get_active();
this->csiProcessor.process(*this->csiProcessor.csiData[this->currentIndex]);
this->refresh();
}
@ -105,7 +105,7 @@ void CsiProcessingWindow::interpolationCubicRadioButtonClicked()
{
if (!this->csiProcessor.csiData.empty())
{
arguments.processors[processor::interpolateCubic] = this->interpolationCubicRadioButton->get_active();
Arguments::arguments.processors[processor::interpolateCubic] = this->interpolationCubicRadioButton->get_active();
this->csiProcessor.process(*this->csiProcessor.csiData[this->currentIndex]);
this->refresh();
}
@ -115,7 +115,7 @@ void CsiProcessingWindow::interpolationCosineButtonClicked()
{
if (!this->csiProcessor.csiData.empty())
{
arguments.processors[processor::interpolateCosine] = this->interpolationCosineButton->get_active();
Arguments::arguments.processors[processor::interpolateCosine] = this->interpolationCosineButton->get_active();
this->csiProcessor.process(*this->csiProcessor.csiData[this->currentIndex]);
this->refresh();
}
@ -125,7 +125,7 @@ void CsiProcessingWindow::phaseLinearTransformCheckButtonClicked()
{
if (!this->csiProcessor.csiData.empty())
{
arguments.processors[processor::phaseCalibrationLinearTransform] = this->phaseLinearTransformCheckButton->get_active();
Arguments::arguments.processors[processor::phaseCalibrationLinearTransform] = this->phaseLinearTransformCheckButton->get_active();
this->csiProcessor.process(*this->csiProcessor.csiData[this->currentIndex]);
this->refresh();
}
@ -135,7 +135,7 @@ void CsiProcessingWindow::processingSaveGtkButtonClicked()
{
if (!this->csiProcessor.csiData.empty())
{
arguments.outputFile = "processedCsi.bin";
Arguments::arguments.outputFile = "processedCsi.bin";
this->csiProcessor.saveCsi();
}
}

View File

@ -1,138 +0,0 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/GnuPlot.h"
#include "main.h"
#include "GnuPlot.h"
void GnuPlot::init()
{
if (!arguments.plot || gnuPlotPipe)
{
return;
}
this->gnuPlotPipe = popen("gnuplot > NUL 2>&1", "w");
fprintf(this->gnuPlotPipe, "set term x11 noraise\n");
fflush(this->gnuPlotPipe);
}
void GnuPlot::setWindow(uint64_t id)
{
this->gnuPlotPipe = popen("gnuplot > NUL 2>&1", "w");
fprintf(this->gnuPlotPipe, "set term x11 noraise\n");
fprintf(this->gnuPlotPipe, "set terminal x11 window \"%#lx\"\n", id);
fflush(this->gnuPlotPipe);
}
void GnuPlot::setBlank()
{
const char *cmd = R"(
set multiplot layout 2,1
set ylabel "Magnitude"
set xlabel "Subcarrier Index"
set title "Magnitude (Abs) "
set autoscale x
set xrange [0:1]
set yrange [0:1]
plot 1/0
set ylabel "Phase (rad) "
set xlabel "Subcarrier Index"
set title "Phase "
set autoscale
set xrange [0:1]
set yrange [0:1]
plot 1/0
)";
this->lastCmd = cmd;
fprintf(this->gnuPlotPipe, cmd);
fflush(this->gnuPlotPipe);
}
void GnuPlot::updateChart(Csi &csi)
{
if (!arguments.plot)
{
return;
}
std::stringstream ss;
// ss << "set multiplot layout 2,1";
ss << R"(
set multiplot layout 2,1
set ylabel "Magnitude"
set xlabel "Subcarrier Index"
set title "Magnitude (Abs) "
set autoscale x
)";
std::stringstream plotCmd;
plotCmd << "set xrange [1:" << csi.numSubCarriers << "]\n";
plotCmd << "plot";
for (uint32_t tx = 0; tx < csi.numTx; tx++)
{
for (uint32_t rx = 0; rx < csi.numRx; rx++)
{
plotCmd << " '-' with lines title \"RX" << (rx + 1) << "TX" << (tx + 1) << "\", ";
}
}
plotCmd << "\n";
ss << plotCmd.str();
std::stringstream dataMagnitude;
std::stringstream dataPhase;
uint32_t index = 0;
for (uint32_t rx = 0; rx < csi.numRx; rx++)
{
for (uint32_t tx = 0; tx < csi.numTx; tx++)
{
for (uint32_t n = 0; n < csi.numSubCarriers; n++)
{
dataMagnitude << (n + 1) << " " << csi.magnitude[index] << "\n";
dataPhase << (n + 1) << " " << csi.phase[index] << "\n";
index++;
}
dataMagnitude << "e\n";
dataPhase << "e\n";
}
}
ss << dataMagnitude.str();
ss << R"(
set ylabel "Phase (rad) "
set xlabel "Subcarrier Index"
set title "Phase "
set autoscale
)";
ss << plotCmd.str();
ss << dataPhase.str();
ss << "unset multiplot\n";
this->lastCmd = ss.str();
fprintf(this->gnuPlotPipe, ss.str().c_str());
fflush(this->gnuPlotPipe);
}
void GnuPlot::reload()
{
fprintf(this->gnuPlotPipe, this->lastCmd.c_str());
fflush(this->gnuPlotPipe);
}

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2024 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,18 +17,28 @@
*/
#include "gui/MainWindow.h"
#include "main.h"
#include <iostream>
#include "MainWindow.h"
#include "MainController.h"
#include "WiFIController.h"
#include "Logger.h"
#include "Arguments.h"
MainWindow::MainWindow(BaseObjectType *obj, Glib::RefPtr<Gtk::Builder> const &builder) : Gtk::ApplicationWindow(obj), builder{builder}
{
set_title("FeitCSI");
set_default_size(1000, 800);
this->signal_delete_event().connect(sigc::mem_fun(*this, &MainWindow::onWindowDelete));
if (!Arguments::arguments.gui && Arguments::arguments.plot)
{
Glib::RefPtr<Gtk::ScrolledWindow>::cast_dynamic(this->builder->get_object("consoleScrollbar"))->hide();
Glib::RefPtr<Gtk::Notebook>::cast_dynamic(this->builder->get_object("leftBar"))->hide();
return;
}
this->measureButton = Glib::RefPtr<Gtk::Button>::cast_dynamic(this->builder->get_object("measureButton"));
this->measureButton->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::measureButtonClicked));
@ -80,8 +90,6 @@ MainWindow::MainWindow(BaseObjectType *obj, Glib::RefPtr<Gtk::Builder> const &bu
this->errorMessages = Glib::RefPtr<Gtk::Label>::cast_dynamic(this->builder->get_object("errorMessages"));
this->chartSocket = Glib::RefPtr<Gtk::Socket>::cast_dynamic(this->builder->get_object("chartSocket"));
this->console = Glib::RefPtr<Gtk::TextView>::cast_dynamic(this->builder->get_object("console"));
Glib::RefPtr<Gtk::ScrolledWindow> consoleScrollbar = Glib::RefPtr<Gtk::ScrolledWindow>::cast_dynamic(this->builder->get_object("consoleScrollbar"));
this->consoleScrollbarAdjustment = consoleScrollbar->get_vadjustment();
@ -92,12 +100,9 @@ MainWindow::MainWindow(BaseObjectType *obj, Glib::RefPtr<Gtk::Builder> const &bu
Logger::guiOutput = this->console->get_buffer();
this->gnuPlot.setWindow(this->chartSocket->get_id());
this->signal_check_resize().connect_notify(sigc::mem_fun(*this, &MainWindow::onResize));
this->signal_show().connect(sigc::mem_fun(*this, &MainWindow::onWindowInit));
this->signal_delete_event().connect(sigc::mem_fun(*this, &MainWindow::onWindowDelete));
this->csiProcessingWindow.init(this->builder);
}
@ -118,16 +123,14 @@ bool MainWindow::onWindowDelete(GdkEventAny *event)
{
MainController *mainController = MainController::getInstance();
mainController->deleteInstance();
return 0;
return true;
}
void MainWindow::onWindowInit()
{
Glib::RefPtr<Gtk::Paned> socketConsolePaned = Glib::RefPtr<Gtk::Paned>::cast_dynamic(this->builder->get_object("socketConsolePaned"));
socketConsolePaned->set_position(socketConsolePaned->get_allocated_height() * 0.8);
MainController *mainController = MainController::getInstance();
mainController->initInterface();
this->gnuPlot.setBlank();
}
void MainWindow::onResize()
@ -137,91 +140,76 @@ void MainWindow::onResize()
this->consoleScrollbarAdjustment->set_value(this->consoleScrollbarAdjustment->get_upper());
return;
}
int pHeight = this->chartSocket->get_allocated_height();
int pWidth = this->chartSocket->get_allocated_width();
if (pHeight < 10 && pWidth < 10)
{
return;
}
if (pWidth != this->chartWidth || pHeight != this->chartHeight)
{
this->gnuPlot.reload();
this->chartWidth = pWidth;
this->chartHeight = pHeight;
}
}
void MainWindow::frequencyChange()
{
int v = std::atoi(this->frequency->get_text().raw().c_str());
arguments.frequency = (uint16_t)v;
Arguments::arguments.frequency = (uint16_t)v;
}
void MainWindow::channelWidthChange()
{
arguments.bandwidth = this->channelWidth->get_active_text().raw();
struct ChanMode chMode = WiFIController::getChanMode(arguments.bandwidth.c_str());
arguments.channelWidth = WiFIController::chanModeToWidth(chMode);
Arguments::arguments.bandwidth = this->channelWidth->get_active_text().raw();
struct ChanMode chMode = WiFIController::getChanMode(Arguments::arguments.bandwidth.c_str());
Arguments::arguments.channelWidth = WiFIController::chanModeToWidth(chMode);
}
void MainWindow::outputFileChange()
{
arguments.outputFile = this->outputFile->get_filename();
this->filePath->set_text(arguments.outputFile);
Arguments::arguments.outputFile = this->outputFile->get_filename();
this->filePath->set_text(Arguments::arguments.outputFile);
}
void MainWindow::formatChange()
{
arguments.format = this->format->get_active_text().raw();
Arguments::arguments.format = this->format->get_active_text().raw();
}
void MainWindow::mcsChange()
{
int v = std::atoi(this->mcs->get_active_text().raw().c_str());
arguments.mcs = (uint8_t)v;
Arguments::arguments.mcs = (uint8_t)v;
}
void MainWindow::spatialStreamsChange()
{
int v = std::atoi(this->spatialStreams->get_active_text().raw().c_str());
arguments.spatialStreams = (uint8_t)v;
Arguments::arguments.spatialStreams = (uint8_t)v;
}
void MainWindow::ltfChange()
{
arguments.ltf = this->ltf->get_active_text().raw();
Arguments::arguments.ltf = this->ltf->get_active_text().raw();
}
void MainWindow::guardIntervalChange()
{
int v = std::atoi(this->guardInterval->get_active_text().raw().c_str());
arguments.guardInterval = (uint16_t)v;
Arguments::arguments.guardInterval = (uint16_t)v;
}
void MainWindow::txPowerChange()
{
int v = std::atoi(this->txPower->get_active_text().raw().c_str());
arguments.txPower = (uint8_t)v;
Arguments::arguments.txPower = (uint8_t)v;
}
void MainWindow::codingChange()
{
arguments.coding = this->coding->get_active_text().raw();
Arguments::arguments.coding = this->coding->get_active_text().raw();
}
void MainWindow::injectDelayChange()
{
int v = std::atoi(this->injectDelay->get_text().raw().c_str());
arguments.injectDelay = (uint32_t)v;
Arguments::arguments.injectDelay = (uint32_t)v;
}
void MainWindow::injectRepeatChange()
{
int v = std::atoi(this->injectRepeat->get_text().raw().c_str());
arguments.injectRepeat = (uint32_t)v;
Arguments::arguments.injectRepeat = (uint32_t)v;
}
void MainWindow::updateErrorMessages()

175
src/gui/Plot.cpp Normal file
View File

@ -0,0 +1,175 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2024 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Plot.h"
#include "Arguments.h"
#include "Logger.h"
#include "WiFiCsiController.h"
#include <chrono>
#include <thread>
#include <iostream>
void Plot::init()
{
signal_draw().connect(sigc::mem_fun(*this, &Plot::on_draw));
}
void Plot::init(Glib::RefPtr<Gtk::Box> box)
{
this->yTicks = this->yTicksMin >= 0 ? this->yTicksMax : this->yTicksMax + (this->yTicksMin * -1);
signal_draw().connect(sigc::mem_fun(*this, &Plot::on_draw));
box->pack_start(*this);
show();
}
void Plot::updateData(Csi *csi, std::vector<double> *data)
{
this->csi = csi;
this->data = data;
queue_draw();
}
bool Plot::on_draw(const Cairo::RefPtr<Cairo::Context> &cr)
{
Gtk::Allocation allocation = get_allocation();
const int width = allocation.get_width();
const int height = allocation.get_height();
const double offset = 50;
const double xTicks = csi ? csi->numSubCarriers : 56;
bool redraw = false;
// Set background color
cr->set_source_rgb(1.0, 1.0, 1.0); // White
cr->paint();
// Draw x and y axis
cr->set_source_rgb(0.0, 0.0, 0.0); // Black
cr->set_line_width(1.0);
cr->move_to(offset, offset);
cr->line_to(offset, height - offset);
cr->line_to(width - offset, height - offset);
cr->stroke();
// Calculate the scaling factor for data points to fit in the drawing area
double xScale = (width - 2 * offset) / (xTicks - 1);
double yScale = (height - 2 * offset) / yTicks;
// Draw tick text
const int perXTick = xTicks / 8;
for (int i = 0; i < xTicks; i = i + perXTick)
{
double x = i * xScale + offset;
cr->set_source_rgb(0, 0, 0);
cr->move_to(x, height - 40);
cr->show_text(std::to_string((i + 1)));
// Draw grid lines
cr->set_source_rgb(0.8, 0.7, 0.7); // Light gray
cr->move_to(x, offset);
cr->line_to(x, height - offset);
cr->stroke();
}
const int perYTick = yTicks > 8 ? yTicks / 8 : 1;
for (int i = 0; i <= yTicks; i = i + perYTick)
{
double y = height - offset - i * (height - 100) / yTicks;
cr->set_source_rgb(0, 0, 0);
cr->move_to(30, y);
cr->show_text(std::to_string((int)(this->yTicksMin < 0 ? i + this->yTicksMin : i)));
// Draw grid lines
cr->set_source_rgb(0.8, 0.8, 0.8); // Light gray
cr->move_to(offset, y);
cr->line_to(width - offset, y);
cr->stroke();
}
if (csi)
{
// Draw the line chart
int colorNumber = 0;
uint32_t index = 0;
for (uint32_t rx = 0; rx < csi->numRx; rx++)
{
for (uint32_t tx = 0; tx < csi->numTx; tx++)
{
cr->set_source_rgb(colors[colorNumber][0], colors[colorNumber][1], colors[colorNumber][2]); // Black color
cr->set_line_width(2.0);
cr->move_to(offset, (height - offset) - (this->yTicksMin < 0 ? ((this->yTicksMin * -1) + (*this->data)[0]) : (*this->data)[0]) * yScale);
for (uint32_t n = 0; n < csi->numSubCarriers; n++)
{
double x = n * xScale + offset;
double y = (height - offset) - (this->yTicksMin < 0 ? ((this->yTicksMin * -1) + (*this->data)[index]) : (*this->data)[index]) * yScale;
if ((*this->data)[index] > this->yTicks)
{
this->yTicks = (*this->data)[index];
redraw = true;
}
cr->line_to(x, y);
index++;
}
cr->stroke();
colorNumber++;
}
}
if (redraw)
{
return this->on_draw(cr);
}
// Draw legend
colorNumber = 0;
cr->move_to(offset * 2, offset - 10);
for (uint32_t rx = 0; rx < csi->numRx; rx++)
{
for (uint32_t tx = 0; tx < csi->numTx; tx++)
{
cr->set_source_rgb(colors[colorNumber][0], colors[colorNumber][1], colors[colorNumber][2]); // Black color
cr->show_text(std::string("RX" + std::to_string(rx + 1)) + std::string("TX" + std::to_string(tx + 1)));
cr->show_text(" ");
colorNumber++;
}
}
}
// Draw x label
cr->set_source_rgb(0.0, 0.0, 0.0); // Black color
cr->move_to(width / 2, (offset / 2));
cr->show_text(this->title);
// Draw x label
cr->set_source_rgb(0.0, 0.0, 0.0); // Black color
cr->move_to(width / 2, height - (offset / 2));
cr->show_text(this->xLabel);
// Draw y label
cr->move_to((offset / 2), height / 2);
cr->rotate(-M_PI / 2); // Rotate text for y label
cr->show_text(this->yLabel);
// Draw x label
cr->set_source_rgb(0.0, 0.0, 0.0); // Black color
cr->set_font_size(30);
cr->move_to(width / 2, (offset / 2));
cr->show_text(this->title);
return true;
}

View File

@ -1,6 +1,6 @@
/*
* FeitCSI is the tool for extracting CSI information from supported intel NICs.
* Copyright (C) 2023 Miroslav Hutar.
* Copyright (C) 2023-2024 Miroslav Hutar.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,7 +18,6 @@
#include <iostream>
#include <thread>
#include <argp.h>
#include <chrono>
#include "Csi.h"
#include "WiFIController.h"
@ -28,285 +27,38 @@
#include "PacketInjector.h"
#include "MainController.h"
#include "Logger.h"
struct Arguments arguments;
const char *argp_program_version =
"argp-ex3 1.0";
const char *argp_program_bug_address =
"<bug-gnu-utils@gnu.org>";
/* Program documentation. */
static char doc[] =
"Argp example #3 -- a program with options and arguments using argp";
/* A description of the arguments we accept. */
static char args_doc[] = "ARG1 ARG2";
/* The options we understand. */
static struct argp_option options[] = {
{"frequency", 'f', "FREQUENCY", 0, "Frequency to measure/inject CSI"},
{"channel-width", 'w', "CHANNELWIDTH", 0, "Channel width to measure/inject CSI. Possible values [20|40|HT40-|80|160]"},
{"output-file", 'o', "FILE", 0, "Output file where measurements should be stored."},
{"mcs", 'm', "MCS", 0, "Mcs index [0-11]"},
{"format", 'r', "FORMAT", 0, "Data frame format [NOHT|HT|VHT|HESU]"},
{"spatial-streams", 's', "SPATIALSTREAMS", 0, "Number of spatial streams [1|2]"},
{"guard-interval", 'g', "GUARDINTERVAL", 0, "Guard interval in ns [400|800]"},
{"ltf", 'l', "LTF", 0, "HE LTF [2xLTF+0.8|2xLTF+1.6|4xLTF+3.2|4xLTF+0.8]"},
{"coding", 'c', "CODING", 0, "Coding scheme [LDPC|BCC]"},
{"tx-power", 't', "TXPOWER", 0, "TX power of antenna in dBm [1-22]"},
{"mode", 'i', "MODE", 0, "Mode of program[measure|inject|measureinject]"},
{"inject-delay", 'd', "INJECTDELAY", 0, "Delay between frame injections is us"},
{"inject-repeat", 'j', "INJECTREPEAT", 0, "How many times inject frame"},
{"verbose", 'v', 0, OPTION_ARG_OPTIONAL, "Produce verbose output"},
{"plot", 'p', 0, OPTION_ARG_OPTIONAL, "Plot CSI data"},
{"gui", 'x', 0, OPTION_ARG_OPTIONAL, "Run application in GUI"},
{0}};
/* Used by main to communicate with parse_opt. */
/* Parse a single option. */
static error_t
parse_opt(int key, char *arg, struct argp_state *state)
{
/* Get the input argument from argp_parse, which we
know is a pointer to our arguments structure. */
struct Arguments *args = (struct Arguments *)state->input;
switch (key)
{
case 'v':
args->verbose = true;
break;
case 'x':
args->gui = true;
break;
case 'p':
args->plot = true;
break;
case 'i':
{
args->mode.assign(arg);
if (args->mode == "measure")
{
args->measure = true;
}
else if (args->mode == "inject")
{
args->inject = true;
args->measure = false;
}
else if (args->mode == "measureinject")
{
args->measure = true;
args->inject = true;
}
else
{
argp_failure(state, 1, 0, "Bad mode. Possible values [measure|inject|measureinject]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'm':
{
int mcs = std::atoi(arg);
if (mcs < 0 || mcs > 11)
{
argp_failure(state, 1, 0, "Bad MCS index. Possible values [0-11]");
exit(ARGP_ERR_UNKNOWN);
}
args->mcs = (uint8_t)mcs;
break;
}
case 'r':
{
args->format.assign(arg);
if (args->format == "NOHT" || args->format == "HT" || args->format == "VHT" || args->format == "HESU")
{
} else
{
argp_failure(state, 1, 0, "Bad format. Possible values [NOHT|HT|VHT|HESU]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'c':
{
args->coding.assign(arg);
if (args->coding == "LDPC" || args->coding == "BCC")
{
}
else
{
argp_failure(state, 1, 0, "Bad coding. Possible values [LDPC|BCC]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'l':
{
args->ltf.assign(arg);
if (args->ltf == "1xLTF+0.8" || args->ltf == "2xLTF+0.8" || args->ltf == "2xLTF+1.6" || args->ltf == "4xLTF+3.2" || args->ltf == "4xLTF+0.8")
{
} else
{
argp_failure(state, 1, 0, "Bad LTF. Possible values [2xLTF+0.8|2xLTF+1.6|4xLTF+3.2|4xLTF+0.8]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'g':
{
int gi = std::atoi(arg);
if (gi == 400 || gi == 800)
{
args->guardInterval = (uint16_t)gi;
}
else
{
argp_failure(state, 1, 0, "Bad guard interval. Possible values [400|800]");
exit(ARGP_ERR_UNKNOWN);
}
break;
}
case 'd':
{
int injd = std::atoi(arg);
if (injd <= 0)
{
argp_failure(state, 1, 0, "Inject delay is not correct number");
exit(ARGP_ERR_UNKNOWN);
}
args->injectDelay = (uint32_t)injd;
break;
}
case 'j':
{
int injr = std::atoi(arg);
if (injr <= 0)
{
argp_failure(state, 1, 0, "Inject repeat is not correct number");
exit(ARGP_ERR_UNKNOWN);
}
args->injectRepeat = (uint32_t)injr;
break;
}
case 's':
{
int ss = std::atoi(arg);
if (ss < 1 || ss > 2)
{
argp_failure(state, 1, 0, "Bad spatial stream. Possible values [1|2]");
exit(ARGP_ERR_UNKNOWN);
}
args->spatialStreams = (uint8_t)ss;
break;
}
case 't':
{
int tx = std::atoi(arg);
if (tx < 1 || tx > 22)
{
argp_failure(state, 1, 0, "Bad tx power. Possible values [1-22]");
exit(ARGP_ERR_UNKNOWN);
}
args->txPower = (uint8_t)tx;
break;
}
case 'f':
{
int f = std::atoi(arg);
if (f <= 0)
{
argp_failure(state, 1, 0, "Frequency is not correct");
exit(ARGP_ERR_UNKNOWN);
}
args->frequency = (uint16_t)f;
break;
}
case 'w':
{
struct ChanMode chMode = WiFIController::getChanMode(arg);
if (chMode.width == 0)
{
argp_failure(state, 1, 0, "Bad bandwidth. Possible values of bandwidth are [20|40|HT40-|80|160]");
exit(ARGP_ERR_UNKNOWN);
}
args->bandwidth = arg;
args->channelWidth = WiFIController::chanModeToWidth(chMode);
break;
}
case 'o':
args->outputFile = arg;
break;
case ARGP_KEY_ARG:
case ARGP_KEY_END:
if (args->frequency == 0 ||
args->bandwidth.empty())
{
argp_failure(state, 1, 0, "Fill required arguments -f -b . See --help for more information");
exit(ARGP_ERR_UNKNOWN);
}
return 0;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
/* Our argp parser. */
static struct argp argp = {options, parse_opt, args_doc, doc};
#include "Arguments.h"
int main(int argc, char *argv[])
{
struct Arguments defValues = {
.verbose = false,
.frequency = 2412,
.gui = false,
.plot = false,
.bandwidth = "20",
.mcs = 0,
.channelWidth = 20,
.spatialStreams = 1,
.txPower = 10,
.guardInterval = 400,
.injectDelay = 100000,
.injectRepeat = 0,
.coding = "LDPC",
.format = "HT",
.inject = false,
.measure = true,
.mode = "measure",
.ltf = "1xLTF+0.8",
};
Arguments args;
args.init();
args.parse(argc, argv);
arguments = defValues;
if (arguments.outputFile.empty())
if (Arguments::arguments.outputFile.empty())
{
const auto t = std::chrono::system_clock::now();
int64_t tInt = std::chrono::duration_cast<std::chrono::seconds>(t.time_since_epoch()).count();
arguments.outputFile = "FeitCSI_" + std::to_string(tInt) + ".dat";
Arguments::arguments.outputFile = "FeitCSI_" + std::to_string(tInt) + ".dat";
}
/* Default values. */
argp_parse(&argp, argc, argv, 0, 0, &arguments);
// all arguments ok and sanitized go next
MainController *mainController = MainController::getInstance();
if (!arguments.gui)
if (Arguments::arguments.gui)
{
mainController->runNoGui();
} else
{
arguments = defValues;
mainController->runGui();
}
else if (Arguments::arguments.udpSocket)
{
mainController->runUdpSocket();
}
else
{
mainController->runNoGui();
}
if (arguments.verbose)
if (Arguments::arguments.verbose)
{
Logger::log(info) << "Exiting...\n";
}