Add Raspberry Pi 5 monitor mode scripts for RA/TA capture

- Add rpi_monitor_setup.sh: Basic monitor mode setup script
- Add rpi_capture_ra_ta.sh: Bash script for capturing RA/TA addresses
- Add rpi_capture_ra_ta_python.py: Python script using scapy for detailed 802.11 frame parsing
- Add RPI_MONITOR_GUIDE.md: Comprehensive guide for monitor mode setup and usage
- Add RPI_INSTALL_SCAPY.md: Installation instructions for scapy on Raspberry Pi

These scripts help debug WiFi traffic by capturing and displaying Receiver Address (RA) and Transmitter Address (TA) from 802.11 frames, useful for comparing with ESP32 monitor mode captures.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Robert McMahon 2026-02-10 16:37:48 -08:00
parent bb6bd568ce
commit 8ba4bff040
5 changed files with 737 additions and 0 deletions

40
RPI_INSTALL_SCAPY.md Normal file
View File

@ -0,0 +1,40 @@
# Installing Scapy on Raspberry Pi 5
## Option 1: Install via apt (Recommended)
```bash
sudo apt-get install python3-scapy
```
This is the safest method and integrates with the system package manager.
## Option 2: Use --break-system-packages (For system tools)
Since this is a system-level monitoring tool that runs as root, you can use:
```bash
sudo pip3 install --break-system-packages scapy
```
## Option 3: Virtual Environment (Not recommended for root tools)
If you prefer a virtual environment (though less convenient for root tools):
```bash
python3 -m venv ~/scapy-env
source ~/scapy-env/bin/activate
pip install scapy
```
Then run the script with:
```bash
sudo ~/scapy-env/bin/python3 rpi_capture_ra_ta_python.py 11
```
## Quick Install Command
For this use case (system monitoring tool), Option 2 is acceptable:
```bash
sudo pip3 install --break-system-packages scapy
```

216
RPI_MONITOR_GUIDE.md Normal file
View File

@ -0,0 +1,216 @@
# Raspberry Pi 5 WiFi Monitor Mode Guide
## Quick Setup
1. **Copy the setup script to your Raspberry Pi:**
```bash
scp rpi_monitor_setup.sh pi@your-pi-ip:~/
```
2. **SSH into your Raspberry Pi and run:**
```bash
sudo ./rpi_monitor_setup.sh [channel]
```
Example for channel 11:
```bash
sudo ./rpi_monitor_setup.sh 11
```
## Manual Setup (Alternative)
If you prefer to run commands manually:
```bash
# 1. Check available interfaces
iw dev
# 2. Bring down the interface
sudo ip link set wlan0 down
# 3. Set to monitor mode
sudo iw dev wlan0 set type monitor
# 4. Bring up the interface
sudo ip link set wlan0 up
# 5. Set channel (e.g., channel 11)
sudo iw dev wlan0 set channel 11
# 6. Verify monitor mode
iw dev wlan0 info
```
## Capturing Packets
Once monitor mode is active, you can capture packets:
### Using tcpdump (simple)
```bash
# View packets in real-time
sudo tcpdump -i wlan0 -n
# Save to file
sudo tcpdump -i wlan0 -w capture.pcap
# Filter by MAC address (e.g., your Pi's MAC)
sudo tcpdump -i wlan0 -n ether host 80:84:89:93:c4:b6
# Filter by channel (if using multiple interfaces)
sudo tcpdump -i wlan0 -n -c 100 # Capture 100 packets
```
### Using airodump-ng (advanced, requires aircrack-ng)
```bash
# Install aircrack-ng if needed
sudo apt-get update
sudo apt-get install aircrack-ng
# Capture on specific channel
sudo airodump-ng wlan0 -c 11
# Save to file
sudo airodump-ng wlan0 -c 11 -w capture
```
### Using Wireshark (GUI)
```bash
# Install wireshark if needed
sudo apt-get install wireshark
# Run wireshark (may need to add user to wireshark group)
sudo wireshark -i wlan0
```
## Capturing RA/TA Addresses
### Quick Capture Script (Recommended)
Use the provided Python script for best results:
```bash
# Install scapy if needed
sudo apt-get install python3-pip
sudo pip3 install scapy
# Capture on channel 11 (shows all frames with RA/TA)
sudo python3 rpi_capture_ra_ta_python.py 11
# Capture and filter by specific MAC address
sudo python3 rpi_capture_ra_ta_python.py 11 80:84:89:93:c4:b6
```
The script will:
- Automatically set monitor mode
- Parse 802.11 frames correctly
- Display RA (Receiver Address) and TA (Transmitter Address)
- Show frame type, RSSI, length, and QoS info
- Provide statistics when stopped (Ctrl+C)
### Alternative: Bash Script
For a simpler bash-based solution:
```bash
# Capture on channel 11
sudo ./rpi_capture_ra_ta.sh 11
# Capture and filter by MAC
sudo ./rpi_capture_ra_ta.sh 11 80:84:89:93:c4:b6
```
## Monitoring Specific Traffic
### Filter by MAC address (TA/RA)
```bash
# Capture frames from specific transmitter (TA)
sudo tcpdump -i wlan0 -n ether src 80:84:89:93:c4:b6
# Capture frames to specific receiver (RA)
sudo tcpdump -i wlan0 -n ether dst e0:46:ee:07:df:e1
# Capture frames involving either address
sudo tcpdump -i wlan0 -n "ether host 80:84:89:93:c4:b6 or ether host e0:46:ee:07:df:e1"
```
### Filter by frame type
```bash
# Data frames only
sudo tcpdump -i wlan0 -n "type wlan type data"
# Management frames (beacons, probes, etc.)
sudo tcpdump -i wlan0 -n "type wlan type mgt"
# Control frames (RTS, CTS, ACK)
sudo tcpdump -i wlan0 -n "type wlan type ctl"
```
## Restoring Normal WiFi
To restore normal WiFi operation:
```bash
# Bring down interface
sudo ip link set wlan0 down
# Set back to managed mode
sudo iw dev wlan0 set type managed
# Bring up interface
sudo ip link set wlan0 up
# Reconnect to your network (use NetworkManager, wpa_supplicant, etc.)
sudo nmcli device wifi connect "YourSSID" password "YourPassword"
# OR
sudo wpa_supplicant -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf &
sudo dhclient wlan0
```
## Troubleshooting
### Interface not found
```bash
# List all network interfaces
ip link show
# Check WiFi interfaces specifically
iw dev
```
### Permission denied
- Make sure you're using `sudo` for all monitor mode commands
- Some distributions require adding your user to specific groups
### Can't set monitor mode
- Some WiFi adapters don't support monitor mode
- Check adapter capabilities: `iw phy | grep -A 10 "Supported interface modes"`
- Raspberry Pi 5 built-in WiFi should support monitor mode
### Channel not changing
- Make sure the interface is up: `sudo ip link set wlan0 up`
- Try bringing it down first, then setting channel, then bringing it up
## Useful Commands
```bash
# Check current interface status
iw dev wlan0 info
# Scan for networks (won't work in monitor mode, but useful before switching)
iw dev wlan0 scan
# Check signal strength and link info (before switching to monitor mode)
iw dev wlan0 link
# Monitor channel activity
watch -n 1 "iw dev wlan0 info | grep channel"
```
## Comparing with ESP32 Monitor
When comparing captures between your ESP32 and Raspberry Pi:
1. **Ensure same channel**: Both devices must monitor the same channel
2. **Time sync**: Consider using NTP for accurate timestamp comparison
3. **MAC filtering**: Use tcpdump filters to match your ESP32's filter settings
4. **Frame types**: Both should capture the same frame types (data, management, control)

101
rpi_capture_ra_ta.sh Executable file
View File

@ -0,0 +1,101 @@
#!/bin/bash
# Raspberry Pi 5 WiFi Monitor Mode - Capture RA/TA Addresses
# This script sets up monitor mode and captures 802.11 frames showing RA and TA
set -e
WIFI_INTERFACE="wlan0"
CHANNEL="${1:-11}" # Default to channel 11, or pass as argument
FILTER_MAC="${2:-}" # Optional: filter by MAC address (e.g., 80:84:89:93:c4:b6)
echo "=== Raspberry Pi 5 WiFi Monitor - RA/TA Capture ==="
echo "Interface: $WIFI_INTERFACE"
echo "Channel: $CHANNEL"
if [ -n "$FILTER_MAC" ]; then
echo "Filter: $FILTER_MAC"
fi
echo ""
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root (use sudo)"
exit 1
fi
# Check if interface exists
if ! ip link show "$WIFI_INTERFACE" &>/dev/null; then
echo "Error: Interface $WIFI_INTERFACE not found"
exit 1
fi
# Check current mode
CURRENT_MODE=$(iw dev "$WIFI_INTERFACE" info 2>/dev/null | grep "type" | awk '{print $2}' || echo "unknown")
if [ "$CURRENT_MODE" != "monitor" ]; then
echo "Setting $WIFI_INTERFACE to monitor mode..."
ip link set "$WIFI_INTERFACE" down
iw dev "$WIFI_INTERFACE" set type monitor
ip link set "$WIFI_INTERFACE" up
iw dev "$WIFI_INTERFACE" set channel "$CHANNEL"
echo "Monitor mode activated on channel $CHANNEL"
else
echo "Already in monitor mode, setting channel to $CHANNEL..."
iw dev "$WIFI_INTERFACE" set channel "$CHANNEL"
fi
echo ""
echo "=== Starting Capture (showing RA/TA addresses) ==="
echo "Press Ctrl+C to stop"
echo ""
# Build tcpdump filter
TCPDUMP_FILTER=""
if [ -n "$FILTER_MAC" ]; then
# Remove colons from MAC for tcpdump
MAC_CLEAN=$(echo "$FILTER_MAC" | tr -d ':')
TCPDUMP_FILTER="ether host $FILTER_MAC"
fi
# Use tcpdump with verbose output to show MAC addresses
# -e shows link-level headers (includes MAC addresses)
# -n prevents DNS resolution
# -v increases verbosity
if [ -n "$TCPDUMP_FILTER" ]; then
tcpdump -i "$WIFI_INTERFACE" -e -n -v "$TCPDUMP_FILTER" 2>&1 | \
grep -E "(ether|RA|TA|SA|DA|BSSID)" | \
awk '
{
# Extract MAC addresses from tcpdump output
# tcpdump shows: ether src/dst MAC
if (match($0, /ether (src|dst) ([0-9a-f:]{17})/, arr)) {
direction = arr[1]
mac = arr[2]
print "[" direction "] " mac
}
# Also show full frame info
print $0
}'
else
# Show all frames with MAC address extraction
tcpdump -i "$WIFI_INTERFACE" -e -n -v 2>&1 | \
while IFS= read -r line; do
# Extract and highlight MAC addresses
if echo "$line" | grep -q "ether"; then
# Extract source MAC (TA in 802.11)
if echo "$line" | grep -q "ether src"; then
TA=$(echo "$line" | grep -oP 'ether src \K[0-9a-f:]{17}' || echo "")
if [ -n "$TA" ]; then
echo "TA (Transmitter): $TA"
fi
fi
# Extract destination MAC (RA in 802.11)
if echo "$line" | grep -q "ether dst"; then
RA=$(echo "$line" | grep -oP 'ether dst \K[0-9a-f:]{17}' || echo "")
if [ -n "$RA" ]; then
echo "RA (Receiver): $RA"
fi
fi
fi
echo "$line"
done
fi

307
rpi_capture_ra_ta_python.py Executable file
View File

@ -0,0 +1,307 @@
#!/usr/bin/env python3
"""
Raspberry Pi 5 WiFi Monitor - Capture RA/TA Addresses
Uses scapy to parse 802.11 frames and display RA/TA addresses clearly
Requirements:
sudo apt-get install python3-pip
sudo pip3 install scapy
Usage:
sudo python3 rpi_capture_ra_ta_python.py [channel] [filter_mac]
Example:
sudo python3 rpi_capture_ra_ta_python.py 11
sudo python3 rpi_capture_ra_ta_python.py 11 80:84:89:93:c4:b6
"""
import sys
import os
import subprocess
import signal
from datetime import datetime
from scapy.all import *
from scapy.layers.dot11 import Dot11, Dot11QoS
# Configuration
WIFI_INTERFACE = "wlan0"
CHANNEL = int(sys.argv[1]) if len(sys.argv) > 1 else 11
FILTER_MAC = sys.argv[2] if len(sys.argv) > 2 else None
def setup_monitor_mode():
"""Set WiFi interface to monitor mode"""
print(f"=== Setting up monitor mode on {WIFI_INTERFACE} ===")
# Check current mode
try:
result = subprocess.run(
["iw", "dev", WIFI_INTERFACE, "info"],
capture_output=True,
text=True,
check=True
)
if "type monitor" in result.stdout:
print(f"Already in monitor mode")
else:
print(f"Setting {WIFI_INTERFACE} to monitor mode...")
subprocess.run(["ip", "link", "set", WIFI_INTERFACE, "down"], check=True)
subprocess.run(["iw", "dev", WIFI_INTERFACE, "set", "type", "monitor"], check=True)
subprocess.run(["ip", "link", "set", WIFI_INTERFACE, "up"], check=True)
print("Monitor mode activated")
except subprocess.CalledProcessError as e:
print(f"Error setting monitor mode: {e}")
sys.exit(1)
# Set channel
try:
subprocess.run(["iw", "dev", WIFI_INTERFACE, "set", "channel", str(CHANNEL)], check=True)
print(f"Channel set to {CHANNEL}")
except subprocess.CalledProcessError as e:
print(f"Error setting channel: {e}")
sys.exit(1)
def get_frame_type_name(dot11):
"""Get human-readable frame type name"""
type_names = {
0: { # Management
0: "Association Request",
1: "Association Response",
2: "Reassociation Request",
3: "Reassociation Response",
4: "Probe Request",
5: "Probe Response",
8: "Beacon",
10: "Disassociation",
11: "Authentication",
12: "Deauthentication"
},
1: { # Control
10: "RTS",
11: "CTS",
12: "ACK",
13: "CF-End",
14: "CF-End+CF-Ack"
},
2: { # Data
0: "Data",
1: "Data+CF-Ack",
2: "Data+CF-Poll",
3: "Data+CF-Ack+CF-Poll",
4: "Null",
8: "QoS Data",
9: "QoS Data+CF-Ack",
10: "QoS Data+CF-Poll",
11: "QoS Data+CF-Ack+CF-Poll",
12: "QoS Null"
}
}
return type_names.get(dot11.type, {}).get(dot11.subtype,
f"Type{dot11.type}/Subtype{dot11.subtype}")
def parse_80211_frame(pkt):
"""Parse 802.11 frame and extract RA/TA addresses"""
if not pkt.haslayer(Dot11):
return None
dot11 = pkt[Dot11]
# 802.11 addressing:
# For Data frames (To DS=1, From DS=1):
# addr1 = RA (Receiver Address) = Next hop destination
# addr2 = TA (Transmitter Address) = Transmitting station
# addr3 = DA (Destination Address) = Final destination
# addr4 = SA (Source Address) = Original source
# For Data frames (To DS=0, From DS=1): AP to STA
# addr1 = RA = DA (Destination STA)
# addr2 = TA = SA (Source AP)
# addr3 = BSSID
# For Data frames (To DS=1, From DS=0): STA to AP
# addr1 = RA = BSSID (AP)
# addr2 = TA = SA (Source STA)
# addr3 = DA (Destination)
frame_type = dot11.type
frame_subtype = dot11.subtype
# Get addresses
addr1 = dot11.addr1 if dot11.addr1 else "N/A"
addr2 = dot11.addr2 if dot11.addr2 else "N/A"
addr3 = dot11.addr3 if dot11.addr3 else "N/A"
addr4 = dot11.addr4 if hasattr(dot11, 'addr4') and dot11.addr4 else None
# Extract To DS and From DS flags
to_ds = dot11.FCfield & 0x1
from_ds = (dot11.FCfield >> 1) & 0x1
# Determine RA and TA based on frame type
if frame_type == 2: # Data frame
# For data frames:
# addr1 is always RA (receiver)
# addr2 is always TA (transmitter)
ra = addr1
ta = addr2
# Additional context based on To DS / From DS
if to_ds and from_ds:
# WDS frame: addr3=DA, addr4=SA
context = f"WDS: DA={addr3}, SA={addr4 if addr4 else 'N/A'}"
elif to_ds and not from_ds:
# STA to AP: addr3=DA
context = f"STA→AP: DA={addr3}"
elif not to_ds and from_ds:
# AP to STA: addr3=BSSID
context = f"AP→STA: BSSID={addr3}"
else:
# Ad-hoc: addr3=BSSID
context = f"Ad-hoc: BSSID={addr3}"
else:
# For management/control frames, addr1=DA, addr2=SA
ra = addr1 # Receiver/Destination
ta = addr2 # Transmitter/Source
context = f"BSSID={addr3}" if addr3 != "N/A" else ""
# Get RSSI if available
rssi = "N/A"
if hasattr(pkt, "dBm_AntSignal"):
rssi = f"{pkt.dBm_AntSignal} dBm"
elif hasattr(pkt, "notdecoded"):
# Try to extract from radiotap header if present
pass
# Check for QoS data
is_qos = pkt.haslayer(Dot11QoS)
qos_info = ""
if is_qos:
qos = pkt[Dot11QoS]
tid = qos.TID if hasattr(qos, 'TID') else "N/A"
qos_info = f", TID={tid}"
return {
"type": frame_type,
"subtype": frame_subtype,
"name": get_frame_type_name(dot11),
"ra": ra,
"ta": ta,
"bssid": addr3,
"context": context,
"rssi": rssi,
"len": len(pkt),
"qos": is_qos,
"qos_info": qos_info,
"retry": bool(dot11.FCfield & 0x8) if hasattr(dot11, 'FCfield') else False
}
# Statistics
stats = {
"total": 0,
"by_ta": {},
"by_ra": {},
"by_type": {}
}
def packet_handler(pkt):
"""Handle captured packets"""
frame_info = parse_80211_frame(pkt)
if not frame_info:
return
# Apply MAC filter if specified
if FILTER_MAC:
filter_mac_clean = FILTER_MAC.lower().replace(":", "").replace("-", "")
ta_clean = frame_info["ta"].replace(":", "").replace("-", "").lower() if frame_info["ta"] != "N/A" else ""
ra_clean = frame_info["ra"].replace(":", "").replace("-", "").lower() if frame_info["ra"] != "N/A" else ""
if filter_mac_clean.lower() not in ta_clean and filter_mac_clean.lower() not in ra_clean:
return # Skip this frame
# Update statistics
stats["total"] += 1
if frame_info["ta"] != "N/A":
stats["by_ta"][frame_info["ta"]] = stats["by_ta"].get(frame_info["ta"], 0) + 1
if frame_info["ra"] != "N/A":
stats["by_ra"][frame_info["ra"]] = stats["by_ra"].get(frame_info["ra"], 0) + 1
frame_type_key = f"{frame_info['name']}"
stats["by_type"][frame_type_key] = stats["by_type"].get(frame_type_key, 0) + 1
# Print frame information
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
retry_str = " [RETRY]" if frame_info["retry"] else ""
print(f"\n[{timestamp}] {frame_info['name']}{retry_str}")
print(f" RA (Receiver): {frame_info['ra']}")
print(f" TA (Transmitter): {frame_info['ta']}")
if frame_info['bssid'] != "N/A":
print(f" BSSID: {frame_info['bssid']}")
if frame_info['context']:
print(f" Context: {frame_info['context']}")
print(f" RSSI: {frame_info['rssi']}")
print(f" Length: {frame_info['len']} bytes{frame_info['qos_info']}")
def signal_handler(sig, frame):
"""Handle Ctrl+C gracefully"""
print("\n\nStopping capture...")
sys.exit(0)
def main():
"""Main function"""
print(f"=== Raspberry Pi 5 WiFi Monitor - RA/TA Capture ===")
print(f"Interface: {WIFI_INTERFACE}")
print(f"Channel: {CHANNEL}")
if FILTER_MAC:
print(f"Filter: {FILTER_MAC}")
print("")
# Check if running as root
if os.geteuid() != 0:
print("Error: This script must be run as root (use sudo)")
sys.exit(1)
# Setup monitor mode
setup_monitor_mode()
# Register signal handler
signal.signal(signal.SIGINT, signal_handler)
print("\n=== Starting Capture (showing RA/TA addresses) ===")
print("Press Ctrl+C to stop\n")
# Build filter
bpf_filter = None
if FILTER_MAC:
bpf_filter = f"ether host {FILTER_MAC}"
# Start capturing
try:
sniff(
iface=WIFI_INTERFACE,
prn=packet_handler,
filter=bpf_filter,
store=False
)
except KeyboardInterrupt:
print("\n\n" + "="*60)
print("Capture stopped by user")
print("="*60)
print(f"\nStatistics:")
print(f" Total frames captured: {stats['total']}")
print(f"\n Top 5 TAs (Transmitters):")
sorted_tas = sorted(stats['by_ta'].items(), key=lambda x: x[1], reverse=True)[:5]
for ta, count in sorted_tas:
print(f" {ta}: {count} frames")
print(f"\n Top 5 RAs (Receivers):")
sorted_ras = sorted(stats['by_ra'].items(), key=lambda x: x[1], reverse=True)[:5]
for ra, count in sorted_ras:
print(f" {ra}: {count} frames")
print(f"\n Frame types:")
for ftype, count in sorted(stats['by_type'].items(), key=lambda x: x[1], reverse=True):
print(f" {ftype}: {count}")
except Exception as e:
print(f"\nError during capture: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

73
rpi_monitor_setup.sh Executable file
View File

@ -0,0 +1,73 @@
#!/bin/bash
# Raspberry Pi 5 WiFi Monitor Mode Setup Script
# Run this script with sudo on your Raspberry Pi
set -e
# Configuration
WIFI_INTERFACE="wlan0" # Change if your interface is different
MONITOR_INTERFACE="mon0" # Monitor mode interface name
CHANNEL="${1:-11}" # Default to channel 11, or pass as argument: ./rpi_monitor_setup.sh 36
echo "=== Raspberry Pi 5 WiFi Monitor Mode Setup ==="
echo "Interface: $WIFI_INTERFACE"
echo "Monitor interface: $MONITOR_INTERFACE"
echo "Channel: $CHANNEL"
echo ""
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root (use sudo)"
exit 1
fi
# Check if interface exists
if ! ip link show "$WIFI_INTERFACE" &>/dev/null; then
echo "Error: Interface $WIFI_INTERFACE not found"
echo "Available interfaces:"
ip link show | grep -E "^[0-9]+:" | awk '{print $2}' | sed 's/://'
exit 1
fi
# Check if monitor interface already exists
if ip link show "$MONITOR_INTERFACE" &>/dev/null; then
echo "Monitor interface $MONITOR_INTERFACE already exists. Removing..."
iw dev "$MONITOR_INTERFACE" del
fi
# Bring down the interface
echo "Bringing down $WIFI_INTERFACE..."
ip link set "$WIFI_INTERFACE" down
# Set monitor mode
echo "Setting $WIFI_INTERFACE to monitor mode..."
iw dev "$WIFI_INTERFACE" set type monitor
# Bring up the monitor interface
echo "Bringing up monitor interface..."
ip link set "$WIFI_INTERFACE" up
# Set channel
echo "Setting channel to $CHANNEL..."
iw dev "$WIFI_INTERFACE" set channel "$CHANNEL"
# Verify monitor mode
echo ""
echo "=== Verification ==="
iw dev "$WIFI_INTERFACE" info
echo ""
echo "=== Monitor mode is now active! ==="
echo "Interface: $WIFI_INTERFACE"
echo "Channel: $CHANNEL"
echo ""
echo "To capture packets, you can use:"
echo " sudo tcpdump -i $WIFI_INTERFACE -n"
echo " sudo tcpdump -i $WIFI_INTERFACE -w capture.pcap"
echo " sudo wireshark -i $WIFI_INTERFACE"
echo ""
echo "To stop monitor mode and restore normal WiFi:"
echo " sudo ip link set $WIFI_INTERFACE down"
echo " sudo iw dev $WIFI_INTERFACE set type managed"
echo " sudo ip link set $WIFI_INTERFACE up"
echo " # Then reconnect to your network"