commit 38fbce9061d163b929a78f448c7f16d640721ce1 Author: Robert McMahon Date: Thu Feb 12 13:26:17 2026 -0800 Initial commit: Linux wireless monitor for ESP32 verification A C program using GNU Automake for capturing and parsing 802.11 WiFi frame headers in monitor mode. Designed to verify ESP32 monitor code by comparing Linux vs ESP32 frame parsing. Features: - Monitor mode setup using libnl3 - Packet capture using libpcap - 802.11 frame parsing (RA, TA, duration, retry flag) - MAC address filtering (matches ESP32 filter behavior) - Output format matches ESP32 debug logs for easy comparison Co-authored-by: Cursor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d23bc06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Autotools generated files +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +config.h +config.h.in +config.log +config.status +configure +stamp-h1 +.deps/ +.dirstamp + +# Compiled files +*.o +*.lo +*.la +*.a +*.so +*.so.* + +# Executables +wireless_monitor +src/wireless_monitor + +# Distribution files +*.tar.gz +*.tar.bz2 +*.zip + +# Editor files +*~ +*.swp +*.swo +.vscode/ +.idea/ + +# Build directories +build/ +*.dSYM/ diff --git a/BUILD_PI5.md b/BUILD_PI5.md new file mode 100644 index 0000000..9efc569 --- /dev/null +++ b/BUILD_PI5.md @@ -0,0 +1,103 @@ +# Building on Raspberry Pi 5 + +This guide covers building the wireless monitor tool on Raspberry Pi 5 running Raspberry Pi OS (Debian-based). + +## Prerequisites + +### Install Build Tools and Dependencies + +```bash +# Update package list +sudo apt-get update + +# Install build essentials and autotools +sudo apt-get install -y \ + build-essential \ + autoconf \ + automake \ + libtool \ + pkg-config + +# Install WiFi monitoring libraries +sudo apt-get install -y \ + libpcap-dev \ + libnl-genl-3-dev \ + libnl-3-dev +``` + +## Building + +```bash +cd wireless-monitor-template + +# Generate configure script +./autogen.sh + +# Configure build +./configure + +# Build +make + +# Test (as root - required for monitor mode) +sudo ./src/wireless_monitor wlan0 11 +``` + +## Installation (Optional) + +```bash +# Install system-wide +sudo make install + +# Then run from anywhere +sudo wireless_monitor wlan0 11 +``` + +## Troubleshooting + +### Missing autotools + +If `autogen.sh` fails: +```bash +sudo apt-get install autoconf automake libtool +``` + +### Missing pkg-config + +If configure fails to find libraries: +```bash +sudo apt-get install pkg-config +``` + +### Library Not Found + +If you get linker errors: +```bash +# Verify libraries are installed +pkg-config --exists libpcap && echo "libpcap OK" || echo "libpcap missing" +pkg-config --exists libnl-genl-3.0 && echo "libnl-genl OK" || echo "libnl-genl missing" +pkg-config --exists libnl-3.0 && echo "libnl-3 OK" || echo "libnl-3 missing" +``` + +### Monitor Mode Permission Denied + +Must run as root: +```bash +sudo ./src/wireless_monitor wlan0 11 +``` + +## Cross-Compilation (from Linux PC) + +If you want to cross-compile from a Linux PC: + +```bash +# Install cross-compiler +sudo apt-get install gcc-aarch64-linux-gnu + +# Configure for cross-compilation +./autogen.sh +./configure --host=aarch64-linux-gnu \ + PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig + +make +``` diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..ad79a1c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = src + +dist_doc_DATA = README.md + +EXTRA_DIST = autogen.sh diff --git a/PUSH_TO_UMBER.md b/PUSH_TO_UMBER.md new file mode 100644 index 0000000..1487b21 --- /dev/null +++ b/PUSH_TO_UMBER.md @@ -0,0 +1,125 @@ +# Push to Umber Git Repository + +## Current Status + +✅ Git repository initialized +✅ All files staged +✅ Ready to commit and push + +## Steps to Push + +### 1. Create Repository on Umber Git Server + +First, create a new repository on your Umber Git server (Gitea/GitLab/etc.): + +- Repository name: `wireless-monitor` (or your preferred name) +- Visibility: Private or Public (your choice) +- **Do NOT** initialize with README (we already have one) + +### 2. Commit Locally + +```bash +cd /home/rjmcmahon/Code/esp32/esp32-iperf-shell/wireless-monitor-template + +# Create initial commit +git commit -m "Initial commit: Linux wireless monitor for ESP32 verification + +A C program using GNU Automake for capturing and parsing 802.11 WiFi +frame headers in monitor mode. Designed to verify ESP32 monitor code +by comparing Linux vs ESP32 frame parsing. + +Features: +- Monitor mode setup using libnl3 +- Packet capture using libpcap +- 802.11 frame parsing (RA, TA, duration, retry flag) +- MAC address filtering (matches ESP32 filter behavior) +- Output format matches ESP32 debug logs for easy comparison" +``` + +### 3. Add Remote Repository + +Replace `YOUR_REPO_URL` with your actual Umber Git repository URL: + +```bash +# Example URLs: +# https://git.umber.com/rjmcmahon/wireless-monitor.git +# git@git.umber.com:rjmcmahon/wireless-monitor.git + +git remote add origin YOUR_REPO_URL + +# Verify remote +git remote -v +``` + +### 4. Push to Repository + +```bash +# Set default branch name (if needed) +git branch -M main + +# Push to repository +git push -u origin main + +# If your default branch is 'master' instead: +# git branch -M master +# git push -u origin master +``` + +## Building on Raspberry Pi 5 After Clone + +Once pushed, you can clone and build on Raspberry Pi 5: + +```bash +# Clone repository +git clone https://git.umber.com/rjmcmahon/wireless-monitor.git +cd wireless-monitor + +# Build (automated) +./build_pi5.sh + +# Or manually +./autogen.sh +./configure +make + +# Run +sudo ./src/wireless_monitor wlan0 11 +``` + +## Troubleshooting + +### Authentication Issues + +If you get authentication errors: + +```bash +# For HTTPS (will prompt for username/password) +git remote set-url origin https://git.umber.com/rjmcmahon/wireless-monitor.git + +# For SSH (requires SSH key setup) +git remote set-url origin git@git.umber.com:rjmcmahon/wireless-monitor.git +``` + +### Branch Name Mismatch + +If you get "branch name mismatch" error: + +```bash +# Check what branch you're on +git branch + +# Rename if needed +git branch -M main # or 'master' +``` + +### Push Rejected + +If push is rejected because remote has content: + +```bash +# Pull first (if remote has initial commit) +git pull origin main --allow-unrelated-histories + +# Then push +git push -u origin main +``` diff --git a/QUICK_START_PI5.md b/QUICK_START_PI5.md new file mode 100644 index 0000000..ef448e5 --- /dev/null +++ b/QUICK_START_PI5.md @@ -0,0 +1,61 @@ +# Quick Start: Raspberry Pi 5 + +## 1. Clone Repository + +```bash +# On Raspberry Pi 5 +git clone https://git.umber.com/rjmcmahon/wireless-monitor.git +cd wireless-monitor +``` + +## 2. Build + +**Option A: Use automated script** +```bash +./build_pi5.sh +``` + +**Option B: Manual build** +```bash +# Install dependencies +sudo apt-get update +sudo apt-get install -y build-essential autoconf automake libtool \ + pkg-config libpcap-dev libnl-genl-3-dev libnl-3-dev + +# Build +./autogen.sh +./configure +make +``` + +## 3. Run + +```bash +# Run as root (required for monitor mode) +sudo ./src/wireless_monitor wlan0 11 + +# With MAC filter (to match ESP32) +sudo ./src/wireless_monitor wlan0 11 80:84:89:93:c4:b6 +``` + +## 4. Compare with ESP32 + +Run both simultaneously on the same channel and compare outputs: + +**ESP32:** +``` +monitor start -c 11 +monitor debug on +monitor filter 80:84:89:93:c4:b6 +``` + +**Raspberry Pi 5:** +```bash +sudo ./src/wireless_monitor wlan0 11 80:84:89:93:c4:b6 +``` + +Compare: +- Same TA/RA addresses? +- Same frame counts? +- Same durations? +- Same retry flags? diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ff06e8 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# Wireless Monitor + +A Linux C program for capturing and parsing 802.11 WiFi frame headers in monitor mode. This tool is designed to **verify and cross-check** the ESP32 monitor code by comparing what a Linux machine sees vs what the ESP32 sees when monitoring the same WiFi traffic. + +## Purpose + +This program captures 802.11 frames and displays: +- **RA (Receiver Address)** and **TA (Transmitter Address)** - same fields ESP32 extracts +- Frame type, size, duration (NAV) +- RSSI, MCS, spatial streams (when available from radiotap) +- Retry flag + +This allows you to verify that the ESP32's frame parsing matches what Linux sees, helping debug issues like: +- Missing frames +- Incorrect MAC address extraction +- Duration/NAV mismatches +- Frame type classification + +## Requirements + +- Linux kernel with nl80211 support +- libpcap development files +- libnl3 development files +- GNU autotools (autoconf, automake, libtool) + +### Install Dependencies + +**Ubuntu/Debian:** +```bash +sudo apt-get install build-essential autoconf automake libtool \ + libpcap-dev libnl-genl-3-dev libnl-3-dev pkg-config +``` + +**Fedora/RHEL:** +```bash +sudo dnf install gcc autoconf automake libtool \ + libpcap-devel libnl3-devel pkgconfig +``` + +## Building + +```bash +# Generate configure script +./autogen.sh + +# Configure build +./configure + +# Build +make + +# Install (optional) +sudo make install +``` + +## Usage + +```bash +# Run as root (required for monitor mode) +sudo ./src/wireless_monitor wlan0 11 + +# Or after installation +sudo wireless_monitor wlan1 36 + +# Example output (comparable to ESP32 debug logs): +# [1770775602.813] DATA: TA=80:84:89:93:c4:b6, RA=e0:46:ee:07:df:e1, Size=228 bytes, Dur=25038 us, RSSI=-94 dBm, Retry=YES +``` + +## Comparison with ESP32 + +The output format is designed to match ESP32's debug output format: +- **TA** = Transmitter Address (same as ESP32's `addr2`) +- **RA** = Receiver Address (same as ESP32's `addr1`) +- Frame type, size, duration, RSSI, retry flag + +You can run both simultaneously on the same channel and compare: +1. Are the same frames seen? +2. Do RA/TA match? +3. Do durations match? +4. Are retry flags consistent? + +## Project Structure + +``` +wireless-monitor/ +├── configure.ac # Autoconf configuration +├── Makefile.am # Top-level automake file +├── autogen.sh # Script to generate configure +├── README.md +└── src/ + ├── Makefile.am # Source automake file + ├── main.c # Main program + ├── monitor.c # Monitor mode setup (libnl3) + ├── monitor.h + ├── capture.c # Packet capture (libpcap) + ├── capture.h + ├── frame_parser.c # 802.11 frame parsing + └── frame_parser.h # Frame structures (matches ESP32) +``` diff --git a/SETUP_GIT.md b/SETUP_GIT.md new file mode 100644 index 0000000..91e6dfe --- /dev/null +++ b/SETUP_GIT.md @@ -0,0 +1,140 @@ +# Setting Up Git Repository + +This guide helps you set up this project in the Umber git repository. + +## Option 1: Create New Repository + +### On Your Git Server (Gitea/GitHub/etc.) + +1. Create a new repository named `wireless-monitor` (or your preferred name) +2. Note the repository URL (e.g., `https://git.umber.com/rjmcmahon/wireless-monitor.git`) + +### On Raspberry Pi 5 + +```bash +cd wireless-monitor-template + +# Initialize git (if not already done) +git init + +# Add all files +git add -A + +# Create initial commit +git commit -m "Initial commit: Linux wireless monitor for ESP32 verification" + +# Add remote repository +git remote add origin https://git.umber.com/rjmcmahon/wireless-monitor.git + +# Push to repository +git push -u origin main +# Or if your default branch is 'master': +# git push -u origin master +``` + +## Option 2: Add to Existing Repository + +If you want to add this as a subdirectory to an existing repository: + +```bash +# From the parent repository +cd /path/to/parent/repo + +# Copy the template +cp -r wireless-monitor-template wireless-monitor + +# Add to git +cd wireless-monitor +git init +git add -A +git commit -m "Add wireless monitor tool for ESP32 verification" + +# Add as submodule (if parent repo uses submodules) +# Or just add files directly to parent repo +``` + +## Option 3: Standalone Repository + +If you want this as a completely separate repository: + +```bash +cd wireless-monitor-template + +# Initialize git +git init + +# Create .gitignore (already included) +# Add all files +git add -A + +# Initial commit +git commit -m "Initial commit: Linux wireless monitor + +A C program using GNU Automake for capturing and parsing 802.11 WiFi +frame headers in monitor mode. Designed to verify ESP32 monitor code +by comparing Linux vs ESP32 frame parsing." + +# Add remote (replace with your actual repository URL) +git remote add origin https://git.umber.com/rjmcmahon/wireless-monitor.git + +# Push +git branch -M main # Or 'master' if that's your default +git push -u origin main +``` + +## Building on Raspberry Pi 5 + +After cloning the repository: + +```bash +# Clone repository +git clone https://git.umber.com/rjmcmahon/wireless-monitor.git +cd wireless-monitor + +# Run build script +./build_pi5.sh + +# Or manually: +./autogen.sh +./configure +make +``` + +## Repository Structure + +``` +wireless-monitor/ +├── .gitignore +├── configure.ac +├── Makefile.am +├── autogen.sh +├── build_pi5.sh # Automated build script for Pi 5 +├── README.md +├── BUILD_PI5.md # Build instructions +├── VERIFICATION_GUIDE.md # ESP32 verification guide +└── src/ + ├── Makefile.am + ├── main.c + ├── monitor.c + ├── monitor.h + ├── capture.c + ├── capture.h + ├── frame_parser.c + └── frame_parser.h +``` + +## Git Workflow + +```bash +# Make changes +# ... edit files ... + +# Stage changes +git add -A + +# Commit +git commit -m "Description of changes" + +# Push +git push origin main +``` diff --git a/VERIFICATION_GUIDE.md b/VERIFICATION_GUIDE.md new file mode 100644 index 0000000..43b0eca --- /dev/null +++ b/VERIFICATION_GUIDE.md @@ -0,0 +1,101 @@ +# ESP32 Monitor Verification Guide + +This Linux C program captures 802.11 frames and displays them in a format that matches ESP32's debug output, allowing you to verify that the ESP32 monitor code is correctly parsing frames. + +## Quick Start + +```bash +# Build +cd wireless-monitor-template +./autogen.sh +./configure +make + +# Run (as root) +sudo ./src/wireless_monitor wlan0 11 + +# With MAC filter (to match ESP32 filter) +sudo ./src/wireless_monitor wlan0 11 80:84:89:93:c4:b6 +``` + +## Comparison Workflow + +### 1. Setup ESP32 Monitor + +```bash +# On ESP32 console +monitor start -c 11 +monitor debug on +monitor filter 80:84:89:93:c4:b6 +``` + +### 2. Setup Linux Monitor (Same Channel) + +```bash +# On Linux machine (same channel!) +sudo ./src/wireless_monitor wlan0 11 80:84:89:93:c4:b6 +``` + +### 3. Generate Test Traffic + +```bash +# On another device (e.g., Raspberry Pi) +iperf -c -u -b 10M -t 60 +``` + +### 4. Compare Outputs + +**ESP32 Output:** +``` +[1770775602.813] I MONITOR: DATA: DATA, TA=80:84:89:93:c4:b6, Size=228 bytes, Rate=54 Mbps, MCS=0, SS=1, BW=20 MHz, RSSI=-94 dBm, Retry:YES +``` + +**Linux Output:** +``` +[1770775602.813] DATA: TA=80:84:89:93:c4:b6, RA=e0:46:ee:07:df:e1, Size=228 bytes, Dur=25038 us, RSSI=-94 dBm, Retry=YES +``` + +## What to Verify + +1. **Same Frames Seen**: Do both see the same number of frames? +2. **TA Matches**: Transmitter Address should be identical +3. **RA Present**: Linux shows RA, ESP32 extracts it as `addr1` +4. **Duration/NAV**: Duration field should match (for collapse detection) +5. **Retry Flag**: Should be consistent +6. **Frame Types**: Both should classify frames the same way + +## Troubleshooting + +### No Frames on Linux but ESP32 Sees Them + +- **Channel Mismatch**: Ensure both are on the same channel +- **Interface Issue**: Check `iw dev wlan0 info` shows monitor mode +- **Permissions**: Must run as root + +### Different TA/RA Values + +- **Address Order**: ESP32 uses `addr2`=TA, `addr1`=RA (correct) +- **To DS/From DS**: Address meanings change based on these bits +- **Frame Direction**: Client→AP vs AP→Client have different addressing + +### Duration Mismatches + +- **NAV Field**: Both should read the same Duration/ID field +- **Expected vs Actual**: ESP32 calculates expected duration, Linux shows actual +- **Collapse Detection**: Large mismatches indicate potential collisions + +## Example: Debugging Missing Frames + +If ESP32 shows `Filter: 80:84:89:93:c4:b6 (0 frames, 0.0 fps)` but Linux sees frames: + +1. **Check Channel**: `monitor status` on ESP32 vs `iw dev wlan0 info` on Linux +2. **Check MAC**: Verify the MAC address is correct +3. **Check Filter Logic**: ESP32 checks both TA and RA, Linux does the same +4. **Check Frame Types**: ESP32 might be filtering by frame type + +## Notes + +- Linux output format matches ESP32 debug logs for easy comparison +- Both tools filter on TA **and** RA to maximize matches +- Radiotap header parsing (RSSI, MCS) is simplified - full parsing would require radiotap library +- For accurate PHY info, consider using `tcpdump` or `wireshark` with radiotap support diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..1aac6e8 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Run this script to generate the configure script and Makefile.in files + +set -e + +echo "Running aclocal..." +aclocal + +echo "Running autoconf..." +autoconf + +echo "Running automake..." +automake --add-missing --copy + +echo "Done. Now run ./configure && make" diff --git a/build_pi5.sh b/build_pi5.sh new file mode 100755 index 0000000..4fbbadf --- /dev/null +++ b/build_pi5.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Build script for Raspberry Pi 5 +# This script installs dependencies and builds the wireless monitor tool + +set -e + +echo "=== Wireless Monitor - Raspberry Pi 5 Build ===" +echo "" + +# Check if running on Raspberry Pi (optional check) +if [ -f /proc/device-tree/model ] && grep -q "Raspberry Pi" /proc/device-tree/model 2>/dev/null; then + echo "Detected Raspberry Pi" + cat /proc/device-tree/model + echo "" +fi + +# Check for root (needed for some package checks, but not for building) +if [ "$EUID" -eq 0 ]; then + echo "Warning: Running as root. Building as regular user is recommended." + echo "" +fi + +# Install dependencies +echo "=== Installing Dependencies ===" +sudo apt-get update +sudo apt-get install -y \ + build-essential \ + autoconf \ + automake \ + libtool \ + pkg-config \ + libpcap-dev \ + libnl-genl-3-dev \ + libnl-3-dev + +echo "" +echo "=== Building ===" + +# Generate configure script +if [ ! -f configure ]; then + echo "Running autogen.sh..." + ./autogen.sh +fi + +# Configure +if [ ! -f Makefile ]; then + echo "Running configure..." + ./configure +fi + +# Build +echo "Running make..." +make + +echo "" +echo "=== Build Complete ===" +echo "" +echo "Binary location: ./src/wireless_monitor" +echo "" +echo "To test (requires root for monitor mode):" +echo " sudo ./src/wireless_monitor wlan0 11" +echo "" +echo "To install system-wide:" +echo " sudo make install" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..c040010 --- /dev/null +++ b/configure.ac @@ -0,0 +1,31 @@ +AC_PREREQ([2.69]) +AC_INIT([wireless-monitor], [1.0.0], [your-email@example.com]) +AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) +AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs +AC_PROG_CC +AC_PROG_CC_C99 +AM_PROG_AR + +# Checks for libraries +PKG_CHECK_MODULES([LIBPCAP], [libpcap]) +PKG_CHECK_MODULES([LIBNL], [libnl-genl-3.0 libnl-3.0]) + +# Checks for header files +AC_CHECK_HEADERS([sys/socket.h netinet/in.h linux/nl80211.h]) + +# Checks for typedefs, structures, and compiler characteristics +AC_TYPE_UINT8_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T + +# Checks for library functions +AC_CHECK_FUNCS([memset memcpy strncpy]) + +AC_CONFIG_FILES([ + Makefile + src/Makefile +]) +AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..938bf45 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,24 @@ +bin_PROGRAMS = wireless_monitor + +wireless_monitor_SOURCES = \ + main.c \ + monitor.c \ + capture.c \ + frame_parser.c + +wireless_monitor_HEADERS = \ + monitor.h \ + capture.h \ + frame_parser.h + +wireless_monitor_CFLAGS = \ + $(LIBPCAP_CFLAGS) \ + $(LIBNL_CFLAGS) \ + -I$(top_srcdir)/src + +wireless_monitor_LDADD = \ + $(LIBPCAP_LIBS) \ + $(LIBNL_LIBS) + +# Include headers in distribution +include_HEADERS = monitor.h capture.h frame_parser.h diff --git a/src/capture.c b/src/capture.c new file mode 100644 index 0000000..2f78c5d --- /dev/null +++ b/src/capture.c @@ -0,0 +1,71 @@ +#include "config.h" +#include "capture.h" +#include "frame_parser.h" +#include +#include +#include +#include +#include +#include +#include + +static pcap_t *pcap_handle = NULL; +static volatile int capture_running = 0; + +static void packet_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes) { + packet_callback_t callback = (packet_callback_t)user; + if (callback && h->caplen >= 24) { // Minimum 802.11 header size + callback(bytes, h->caplen, NULL); + } +} + +int start_capture(const char *interface, packet_callback_t callback, void *user_data) { + char errbuf[PCAP_ERRBUF_SIZE]; + + if (capture_running) { + fprintf(stderr, "Capture already running\n"); + return -1; + } + + // Open interface for capture + pcap_handle = pcap_open_live(interface, 65535, 1, 1000, errbuf); + if (!pcap_handle) { + fprintf(stderr, "Failed to open interface %s: %s\n", interface, errbuf); + return -1; + } + + // Set non-blocking mode + if (pcap_setnonblock(pcap_handle, 0, errbuf) < 0) { + fprintf(stderr, "Warning: Failed to set blocking mode: %s\n", errbuf); + } + + capture_running = 1; + + // Start capture loop + printf("Starting capture loop...\n"); + while (capture_running) { + int ret = pcap_dispatch(pcap_handle, 1, packet_handler, (u_char *)callback); + if (ret < 0) { + if (ret == PCAP_ERROR_BREAK) { + // Normal break + break; + } + fprintf(stderr, "Capture error: %s\n", pcap_geterr(pcap_handle)); + break; + } else if (ret == 0) { + // Timeout - continue + continue; + } + } + + return 0; +} + +void stop_capture(void) { + capture_running = 0; + if (pcap_handle) { + pcap_breakloop(pcap_handle); + pcap_close(pcap_handle); + pcap_handle = NULL; + } +} diff --git a/src/capture.h b/src/capture.h new file mode 100644 index 0000000..a48c9a3 --- /dev/null +++ b/src/capture.h @@ -0,0 +1,29 @@ +#ifndef CAPTURE_H +#define CAPTURE_H + +#include +#include + +/** + * @brief Packet callback function type + * @param packet Pointer to packet data + * @param len Packet length + * @param user_data User-provided data pointer + */ +typedef void (*packet_callback_t)(const uint8_t *packet, size_t len, void *user_data); + +/** + * @brief Start packet capture on interface + * @param interface Interface name + * @param callback Callback function for each packet + * @param user_data User data passed to callback + * @return 0 on success, -1 on error + */ +int start_capture(const char *interface, packet_callback_t callback, void *user_data); + +/** + * @brief Stop packet capture + */ +void stop_capture(void); + +#endif /* CAPTURE_H */ diff --git a/src/frame_parser.c b/src/frame_parser.c new file mode 100644 index 0000000..a9440ab --- /dev/null +++ b/src/frame_parser.c @@ -0,0 +1,161 @@ +#include "config.h" +#include "frame_parser.h" +#include +#include +#include + +// Radiotap header parsing (simplified) +static int skip_radiotap_header(const uint8_t *packet, size_t len, size_t *offset) { + if (len < 8) return -1; + + // Check for radiotap header (starts with version 0) + if (packet[0] == 0 && packet[1] == 0) { + // Radiotap header present + uint16_t rt_len = *(uint16_t *)&packet[2]; + if (rt_len > len || rt_len < 8) return -1; + + // Extract RSSI and rate from radiotap (simplified - full parsing is complex) + // For now, just skip the header + *offset = rt_len; + return 0; + } + + // No radiotap header + *offset = 0; + return 0; +} + +int parse_80211_frame(const uint8_t *packet, size_t len, wifi_frame_info_t *frame_info) { + size_t offset = 0; + + // Skip radiotap header if present + if (skip_radiotap_header(packet, len, &offset) < 0) { + return -1; + } + + if (len < offset + 24) { + return -1; // Too short for 802.11 header + } + + const uint8_t *frame = packet + offset; + + // Parse Frame Control (bytes 0-1) + frame_info->frame_control = frame[0] | (frame[1] << 8); + frame_info->type = (frame[0] >> 2) & 0x03; + frame_info->subtype = (frame[0] >> 4) & 0x0F; + frame_info->to_ds = (frame[1] >> 0) & 0x01; + frame_info->from_ds = (frame[1] >> 1) & 0x01; + frame_info->retry = (frame[1] >> 3) & 0x01; + + // Parse Duration/ID (bytes 2-3) + frame_info->duration_id = frame[2] | (frame[3] << 8); + + // Parse Addresses based on To DS / From DS bits + // addr1 = RA (Receiver Address) + // addr2 = TA (Transmitter Address) + // addr3 = BSSID/SA/DA + memcpy(frame_info->addr1, &frame[4], 6); // RA + memcpy(frame_info->addr2, &frame[10], 6); // TA + memcpy(frame_info->addr3, &frame[16], 6); // BSSID/SA/DA + + // Check for Address 4 (only if To DS and From DS both set) + frame_info->has_addr4 = frame_info->to_ds && frame_info->from_ds; + if (frame_info->has_addr4) { + if (len < offset + 30) return -1; + memcpy(frame_info->addr4, &frame[22], 6); + } + + // Parse Sequence Control (bytes 22-23 or 28-29) + uint16_t seq_offset = frame_info->has_addr4 ? 28 : 22; + if (len < offset + seq_offset + 2) return -1; + + frame_info->seq_ctrl = frame[seq_offset] | (frame[seq_offset + 1] << 8); + frame_info->fragment_num = frame_info->seq_ctrl & 0x0F; + frame_info->sequence_num = (frame_info->seq_ctrl >> 4) & 0x0FFF; + + // Initialize PHY info (would need radiotap parsing for accurate values) + frame_info->rssi = -100; // Default + frame_info->mcs = 0; + frame_info->spatial_streams = 1; + frame_info->bandwidth = 0; + frame_info->sgi = false; + frame_info->phy_rate_kbps = 0; + + frame_info->frame_len = len - offset; + + return 0; +} + +const char *get_frame_type_name(uint8_t type, uint8_t subtype) { + switch (type) { + case FRAME_TYPE_MANAGEMENT: + switch (subtype) { + case 0: return "ASSOC_REQ"; + case 1: return "ASSOC_RESP"; + case 2: return "REASSOC_REQ"; + case 3: return "REASSOC_RESP"; + case 4: return "PROBE_REQ"; + case 5: return "PROBE_RESP"; + case 8: return "BEACON"; + case 10: return "DISASSOC"; + case 11: return "AUTH"; + case 12: return "DEAUTH"; + default: return "MGMT_UNKNOWN"; + } + case FRAME_TYPE_CONTROL: + switch (subtype) { + case 11: return "RTS"; + case 12: return "CTS"; + case 13: return "ACK"; + default: return "CTRL_UNKNOWN"; + } + case FRAME_TYPE_DATA: + switch (subtype) { + case 0: return "DATA"; + case 1: return "DATA_CF_ACK"; + case 2: return "DATA_CF_POLL"; + case 3: return "DATA_CF_ACK_POLL"; + case 4: return "NULL"; + case 8: return "QOS_DATA"; + case 12: return "QOS_NULL"; + default: return "DATA_UNKNOWN"; + } + default: + return "UNKNOWN"; + } +} + +void print_frame_info(const wifi_frame_info_t *frame_info, uint64_t timestamp_us) { + const char *type_name = get_frame_type_name(frame_info->type, frame_info->subtype); + + printf("[%llu.%03llu] %s: ", + timestamp_us / 1000000, (timestamp_us / 1000) % 1000); + + printf("TA=%02x:%02x:%02x:%02x:%02x:%02x, ", + frame_info->addr2[0], frame_info->addr2[1], frame_info->addr2[2], + frame_info->addr2[3], frame_info->addr2[4], frame_info->addr2[5]); + + printf("RA=%02x:%02x:%02x:%02x:%02x:%02x, ", + frame_info->addr1[0], frame_info->addr1[1], frame_info->addr1[2], + frame_info->addr1[3], frame_info->addr1[4], frame_info->addr1[5]); + + printf("Size=%u bytes, ", frame_info->frame_len); + printf("Dur=%u us, ", frame_info->duration_id); + printf("RSSI=%d dBm", frame_info->rssi); + + if (frame_info->retry) { + printf(", Retry=YES"); + } else { + printf(", Retry=no"); + } + + if (frame_info->mcs > 0) { + printf(", MCS=%u", frame_info->mcs); + } + + if (frame_info->spatial_streams > 1) { + printf(", SS=%u", frame_info->spatial_streams); + } + + printf("\n"); +} diff --git a/src/frame_parser.h b/src/frame_parser.h new file mode 100644 index 0000000..f2e7b41 --- /dev/null +++ b/src/frame_parser.h @@ -0,0 +1,76 @@ +#ifndef FRAME_PARSER_H +#define FRAME_PARSER_H + +#include +#include +#include + +/** + * @brief 802.11 Frame types + */ +typedef enum { + FRAME_TYPE_MANAGEMENT = 0, + FRAME_TYPE_CONTROL = 1, + FRAME_TYPE_DATA = 2, + FRAME_TYPE_RESERVED = 3 +} wifi_frame_type_t; + +/** + * @brief Parsed 802.11 frame information (similar to ESP32 wifi_frame_info_t) + */ +typedef struct { + // Frame Control + uint16_t frame_control; + uint8_t type; + uint8_t subtype; + bool to_ds; + bool from_ds; + bool retry; + + // Duration/ID (NAV) + uint16_t duration_id; + + // MAC Addresses + uint8_t addr1[6]; // Receiver Address (RA) + uint8_t addr2[6]; // Transmitter Address (TA) + uint8_t addr3[6]; // BSSID/SA/DA + uint8_t addr4[6]; // Optional Address 4 + bool has_addr4; + + // Sequence Control + uint16_t seq_ctrl; + uint16_t fragment_num; + uint16_t sequence_num; + + // PHY info (from radiotap header if available) + int8_t rssi; + uint8_t mcs; + uint8_t spatial_streams; // NSS + uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz, 3=160MHz + bool sgi; // Short Guard Interval + uint32_t phy_rate_kbps; + + // Frame size + uint16_t frame_len; +} wifi_frame_info_t; + +/** + * @brief Parse 802.11 frame header + * @param packet Raw packet data (including radiotap header if present) + * @param len Packet length + * @param frame_info Output: parsed frame information + * @return 0 on success, -1 on error + */ +int parse_80211_frame(const uint8_t *packet, size_t len, wifi_frame_info_t *frame_info); + +/** + * @brief Get human-readable frame type name + */ +const char *get_frame_type_name(uint8_t type, uint8_t subtype); + +/** + * @brief Print frame information (for verification/comparison with ESP32) + */ +void print_frame_info(const wifi_frame_info_t *frame_info, uint64_t timestamp_us); + +#endif /* FRAME_PARSER_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..978f430 --- /dev/null +++ b/src/main.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "monitor.h" +#include "capture.h" +#include "frame_parser.h" + +static volatile int running = 1; +static uint64_t packet_count = 0; +static uint64_t data_frame_count = 0; +static uint8_t filter_mac[6] = {0}; +static bool filter_enabled = false; + +static void signal_handler(int sig) { + (void)sig; + running = 0; + stop_capture(); +} + +static uint64_t get_timestamp_us(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000ULL + (uint64_t)tv.tv_usec; +} + +static bool mac_match(const uint8_t *mac1, const uint8_t *mac2) { + return memcmp(mac1, mac2, 6) == 0; +} + +static void packet_callback(const uint8_t *packet, size_t len, void *user_data) { + (void)user_data; + wifi_frame_info_t frame_info; + uint64_t timestamp_us = get_timestamp_us(); + + packet_count++; + + // Parse 802.11 frame + if (parse_80211_frame(packet, len, &frame_info) == 0) { + // Only print data frames (for comparison with ESP32) + if (frame_info.type == FRAME_TYPE_DATA) { + // Apply MAC filter if enabled (check both TA and RA like ESP32) + bool should_print = true; + if (filter_enabled) { + should_print = mac_match(frame_info.addr2, filter_mac) || + mac_match(frame_info.addr1, filter_mac); + } + + if (should_print) { + data_frame_count++; + print_frame_info(&frame_info, timestamp_us); + } + } + } +} + +static int parse_mac(const char *mac_str, uint8_t *mac_out) { + int values[6]; + if (sscanf(mac_str, "%x:%x:%x:%x:%x:%x", + &values[0], &values[1], &values[2], + &values[3], &values[4], &values[5]) != 6) { + return -1; + } + for (int i = 0; i < 6; i++) { + if (values[i] < 0 || values[i] > 255) return -1; + mac_out[i] = (uint8_t)values[i]; + } + return 0; +} + +int main(int argc, char **argv) { + if (argc < 3) { + fprintf(stderr, "Usage: %s [filter_mac]\n", argv[0]); + fprintf(stderr, "Example: %s wlan0 11\n", argv[0]); + fprintf(stderr, "Example: %s wlan0 11 80:84:89:93:c4:b6\n", argv[0]); + fprintf(stderr, "\n"); + fprintf(stderr, "This tool captures 802.11 frames to verify ESP32 monitor code.\n"); + fprintf(stderr, "Output format matches ESP32 debug logs for easy comparison.\n"); + return 1; + } + + const char *interface = argv[1]; + int channel = atoi(argv[2]); + + if (channel < 1 || channel > 165) { + fprintf(stderr, "Invalid channel: %d (must be 1-165)\n", channel); + return 1; + } + + // Parse optional MAC filter + if (argc >= 4) { + if (parse_mac(argv[3], filter_mac) == 0) { + filter_enabled = true; + printf("Filter enabled: %02x:%02x:%02x:%02x:%02x:%02x\n", + filter_mac[0], filter_mac[1], filter_mac[2], + filter_mac[3], filter_mac[4], filter_mac[5]); + } else { + fprintf(stderr, "Invalid MAC address format: %s\n", argv[3]); + fprintf(stderr, "Expected format: XX:XX:XX:XX:XX:XX\n"); + return 1; + } + } + + // Setup signal handlers + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + printf("=== Wireless Monitor ===\n"); + printf("Interface: %s\n", interface); + printf("Channel: %d\n", channel); + printf("\n"); + + // Set monitor mode + if (set_monitor_mode(interface, channel) != 0) { + fprintf(stderr, "Failed to set monitor mode\n"); + return 1; + } + + printf("Monitor mode activated\n"); + printf("Capturing 802.11 frames... (Ctrl+C to stop)\n"); + printf("This output can be compared with ESP32 monitor debug logs\n"); + if (filter_enabled) { + printf("Filter: %02x:%02x:%02x:%02x:%02x:%02x (showing frames with matching TA or RA)\n", + filter_mac[0], filter_mac[1], filter_mac[2], + filter_mac[3], filter_mac[4], filter_mac[5]); + } + printf("\n"); + + // Start packet capture (blocks until stopped) + if (start_capture(interface, packet_callback, NULL) != 0) { + fprintf(stderr, "Failed to start capture\n"); + restore_managed_mode(interface); + return 1; + } + + printf("\n=== Capture Statistics ===\n"); + printf("Total packets: %llu\n", (unsigned long long)packet_count); + printf("Data frames: %llu\n", (unsigned long long)data_frame_count); + + restore_managed_mode(interface); + printf("Done\n"); + + return 0; +} diff --git a/src/monitor.c b/src/monitor.c new file mode 100644 index 0000000..92ad6b4 --- /dev/null +++ b/src/monitor.c @@ -0,0 +1,237 @@ +#include "config.h" +#include "monitor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct nl_sock *nl_sock = NULL; +static int nl80211_id = -1; + +static int nl80211_init(void) { + nl_sock = nl_socket_alloc(); + if (!nl_sock) { + fprintf(stderr, "Failed to allocate netlink socket\n"); + return -1; + } + + if (genl_connect(nl_sock) < 0) { + fprintf(stderr, "Failed to connect to netlink\n"); + nl_socket_free(nl_sock); + nl_sock = NULL; + return -1; + } + + nl80211_id = genl_ctrl_resolve(nl_sock, "nl80211"); + if (nl80211_id < 0) { + fprintf(stderr, "nl80211 not found\n"); + nl_close(nl_sock); + nl_socket_free(nl_sock); + nl_sock = NULL; + return -1; + } + + return 0; +} + +static void nl80211_cleanup(void) { + if (nl_sock) { + nl_close(nl_sock); + nl_socket_free(nl_sock); + nl_sock = NULL; + } +} + +int set_monitor_mode(const char *interface, int channel) { + struct nl_msg *msg; + struct nl_cb *cb; + int ifindex; + int err = 0; + + if (nl80211_init() < 0) { + return -1; + } + + ifindex = if_nametoindex(interface); + if (ifindex == 0) { + fprintf(stderr, "Interface %s not found\n", interface); + nl80211_cleanup(); + return -1; + } + + // Create message to set monitor mode + msg = nlmsg_alloc(); + if (!msg) { + fprintf(stderr, "Failed to allocate netlink message\n"); + nl80211_cleanup(); + return -1; + } + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl80211_id, 0, + NLM_F_REQUEST, NL80211_CMD_SET_INTERFACE, 0); + + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex); + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MONITOR); + + // Send message + cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) { + fprintf(stderr, "Failed to allocate netlink callback\n"); + nlmsg_free(msg); + nl80211_cleanup(); + return -1; + } + + err = nl_send_auto_complete(nl_sock, msg); + if (err < 0) { + fprintf(stderr, "Failed to send netlink message: %s\n", nl_geterror(err)); + nl_cb_put(cb); + nlmsg_free(msg); + nl80211_cleanup(); + return -1; + } + + nl_cb_put(cb); + nlmsg_free(msg); + + // Set channel + if (set_channel(interface, channel) < 0) { + nl80211_cleanup(); + return -1; + } + + nl80211_cleanup(); + return 0; + +nla_put_failure: + fprintf(stderr, "Failed to add netlink attribute\n"); + nlmsg_free(msg); + nl80211_cleanup(); + return -1; +} + +int restore_managed_mode(const char *interface) { + struct nl_msg *msg; + int ifindex; + int err = 0; + + if (nl80211_init() < 0) { + return -1; + } + + ifindex = if_nametoindex(interface); + if (ifindex == 0) { + fprintf(stderr, "Interface %s not found\n", interface); + nl80211_cleanup(); + return -1; + } + + msg = nlmsg_alloc(); + if (!msg) { + fprintf(stderr, "Failed to allocate netlink message\n"); + nl80211_cleanup(); + return -1; + } + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl80211_id, 0, + NLM_F_REQUEST, NL80211_CMD_SET_INTERFACE, 0); + + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex); + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_STATION); + + err = nl_send_auto_complete(nl_sock, msg); + if (err < 0) { + fprintf(stderr, "Failed to send netlink message: %s\n", nl_geterror(err)); + nlmsg_free(msg); + nl80211_cleanup(); + return -1; + } + + nlmsg_free(msg); + nl80211_cleanup(); + return 0; + +nla_put_failure: + fprintf(stderr, "Failed to add netlink attribute\n"); + nlmsg_free(msg); + nl80211_cleanup(); + return -1; +} + +// Convert WiFi channel number to frequency in MHz +static uint32_t channel_to_freq(int channel) { + if (channel >= 1 && channel <= 14) { + // 2.4 GHz: 2412 + (channel - 1) * 5 + return 2412 + (channel - 1) * 5; + } else if (channel >= 36 && channel <= 165) { + // 5 GHz: 5000 + channel * 5 + return 5000 + channel * 5; + } else { + // Invalid channel + return 0; + } +} + +int set_channel(const char *interface, int channel) { + struct nl_msg *msg; + int ifindex; + uint32_t freq_mhz; + int err = 0; + + // Convert channel to frequency + freq_mhz = channel_to_freq(channel); + if (freq_mhz == 0) { + fprintf(stderr, "Invalid channel: %d\n", channel); + return -1; + } + + if (nl80211_init() < 0) { + return -1; + } + + ifindex = if_nametoindex(interface); + if (ifindex == 0) { + fprintf(stderr, "Interface %s not found\n", interface); + nl80211_cleanup(); + return -1; + } + + msg = nlmsg_alloc(); + if (!msg) { + fprintf(stderr, "Failed to allocate netlink message\n"); + nl80211_cleanup(); + return -1; + } + + genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, nl80211_id, 0, + NLM_F_REQUEST, NL80211_CMD_SET_CHANNEL, 0); + + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex); + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq_mhz); + + err = nl_send_auto_complete(nl_sock, msg); + if (err < 0) { + fprintf(stderr, "Failed to set channel %d (freq %u MHz): %s\n", + channel, freq_mhz, nl_geterror(err)); + nlmsg_free(msg); + nl80211_cleanup(); + return -1; + } + + nlmsg_free(msg); + nl80211_cleanup(); + return 0; + +nla_put_failure: + fprintf(stderr, "Failed to add netlink attribute\n"); + nlmsg_free(msg); + nl80211_cleanup(); + return -1; +} diff --git a/src/monitor.h b/src/monitor.h new file mode 100644 index 0000000..8e88dff --- /dev/null +++ b/src/monitor.h @@ -0,0 +1,30 @@ +#ifndef MONITOR_H +#define MONITOR_H + +#include +#include + +/** + * @brief Set WiFi interface to monitor mode + * @param interface Interface name (e.g., "wlan0") + * @param channel Channel number (1-165) + * @return 0 on success, -1 on error + */ +int set_monitor_mode(const char *interface, int channel); + +/** + * @brief Restore WiFi interface to managed mode + * @param interface Interface name + * @return 0 on success, -1 on error + */ +int restore_managed_mode(const char *interface); + +/** + * @brief Set channel for monitor interface + * @param interface Interface name + * @param channel Channel number (1-165) + * @return 0 on success, -1 on error + */ +int set_channel(const char *interface, int channel); + +#endif /* MONITOR_H */