A real-time CLI + web Wi-Fi and LAN threat monitor for Raspberry Pi. Displays a live dashboard (uses TMUX) showing all activity: deauth attacks, probe floods, nearby access points, connected devices, management frame activity, and active LAN threat detection (ARP scanning, ARP poisoning/MITM, port scanning, DHCP starvation).
- How It Works
- Hardware Requirements
- Recommended Wi-Fi Dongles
- Raspberry Pi OS Setup
- SSH Setup
- System Dependencies
- Python Dependencies
- Configuration
- Running Manually
- Running as a systemd Service
- tmux Setup and Attaching
- Dashboard Reference
- Log Files
- Security Notes Before Pushing to Git
- Troubleshooting
WIDS uses two separate Wi-Fi interfaces simultaneously:
| Interface | Role | Mode |
|---|---|---|
wlan0 |
Built-in RPi Wi-Fi | Managed — stays connected to your home AP for LAN monitoring and nmcli scanning |
wlan1 |
USB Wi-Fi dongle | Monitor mode — passively captures all 802.11 management frames in the air |
Why two interfaces?
A Wi-Fi adapter in monitor mode cannot simultaneously be associated with an access point. It just listens to raw 802.11 frames. If you put your only adapter into monitor mode, you lose your network connection and can no longer scan for IPs, run arp-scan, or get security metadata from NetworkManager. You need one adapter locked to your home network (wlan0) and a second one free to sniff the air (wlan1).
Data sources:
tcpdumponwlan1— captures beacon, probe, deauth, disassoc, auth, assoc, and reassoc frames. Provides RSSI (signal strength) for APs on the same channel.tsharkonwlan1— captures EAPOL (WPA handshake) frames.nmclionwlan0— periodic full scan providing SSID, channel, security type, and signal percentage for all visible APs (including those on other channels).tcpdumponwlan0— watches live IP traffic for ARP scans, ARP poisoning, TCP SYN port scans, and DHCP starvation.arp-scanonwlan0— periodic sweep to list active IP/MAC pairs on your LAN.
- Raspberry Pi — any model with USB ports. Tested on RPi 4 and RPi 3B+. RPi Zero W will work but is slow.
- MicroSD card — 8 GB minimum, 16 GB recommended (logs grow over time).
- Power supply — official RPi power supply recommended. Underpowered supplies cause USB instability which will crash your monitor-mode dongle.
- USB Wi-Fi adapter that supports monitor mode (see next section).
- Internet connection during setup (Ethernet cable or using wlan0 temporarily).
Not all Wi-Fi adapters support monitor mode. Many cheap adapters advertise it but the Linux driver does not implement it. The following chipsets are well-tested on Raspberry Pi OS:
| Adapter | Chipset | Monitor Mode | Packet Injection | Notes |
|---|---|---|---|---|
| Alfa AWUS036ACM | MediaTek MT7612U | ✓ | ✓ | Best overall. Dual-band. Works out of box. |
| Alfa AWUS036ACHM | MediaTek MT7610U | ✓ | ✓ | Good 2.4/5 GHz support. |
| Alfa AWUS036NHA | Atheros AR9271 | ✓ | ✓ | 2.4 GHz only. Very reliable. Classic choice. |
| TP-Link TL-WN722N v1 | Atheros AR9271 | ✓ | ✓ | v1 only — v2/v3 use Realtek and do NOT support monitor mode. Check the version. |
| Panda PAU09 | Ralink RT5572 | ✓ | ✓ | Dual-band, good RPi compatibility. |
- Realtek RTL8188 / RTL8192 / RTL8811 series — monitor mode support is unreliable or broken on many kernel versions.
- Any adapter that does not explicitly list the chipset — almost certainly a Realtek.
Plug in the dongle and run:
iw list | grep -A 10 "Supported interface modes"You should see monitor in the list. If it is not there, the driver does not support it and the dongle will not work for WIDS.
Download Raspberry Pi OS Lite (64-bit) from https://www.raspberrypi.com/software/
Use Raspberry Pi Imager (https://www.raspberrypi.com/software/) to flash to your SD card.
In Imager, before flashing, click the gear icon (⚙) or press Ctrl+Shift+X to open advanced options:
- Enable SSH — check this box
- Set username and password — choose a username (e.g.
sud) and a strong password - Configure Wi-Fi — enter your home SSID and password so the Pi connects on first boot
- Set locale/timezone — set your country and timezone
Flash, insert the SD card into the Pi, and power on.
From your router's admin page, or run this from another machine on the same network:
nmap -sn 192.168.1.0/24 | grep -i raspberryOr use the hostname (if mDNS works on your network):
ping raspberrypi.localssh sud@192.168.1.XReplace 192.168.1.X with your Pi's actual IP. Accept the host key fingerprint when prompted.
On your local machine:
# Generate a key pair if you don't have one
ssh-keygen -t ed25519 -C "wids-rpi"
# Copy your public key to the Pi
ssh-copy-id sud@192.168.1.XNow you can SSH without a password. You should then disable password authentication on the Pi for security:
sudo nano /etc/ssh/sshd_configFind and set:
PasswordAuthentication no
PubkeyAuthentication yes
sudo systemctl restart sshAdd to your local ~/.ssh/config:
Host rpi
HostName 192.168.1.X
User sud
ServerAliveInterval 60
ServerAliveCountMax 3
Now you can connect with just ssh rpi.
Update the system first:
sudo apt update && sudo apt upgrade -yInstall all required tools:
sudo apt install -y \
tcpdump \
tshark \
iw \
wireless-tools \
network-manager \
arp-scan \
tmux \
python3 \
python3-pip \
gitDuring tshark installation you will be asked whether non-root users should be able to capture packets. Select Yes (but WIDS runs as root anyway).
which tcpdump tshark iw nmcli arp-scan tmux python3All should return a path. If any are missing, install them individually.
NetworkManager must be in control of wlan0 for nmcli scanning to work:
nmcli device statusYou should see wlan0 listed with state connected. If it shows unmanaged, run:
sudo nmcli device set wlan0 managed yesWIDS requires only one Python library beyond the standard library:
pip3 install rich --break-system-packagesThe --break-system-packages flag is required on Raspberry Pi OS Bookworm (Debian 12) and later, which use externally managed Python environments.
Verify:
python3 -c "from rich import box; print('rich OK')"cd ~
git clone https://github.com/YOURUSERNAME/wids.git
cd widsDo not put your Wi-Fi password in wids.py directly. Instead, create a local config file that is excluded from git:
nano ~/wids/wids_local.confAdd:
HOME_SSID=Hell`s WiFi
HOME_PASSWORD=rectum_obliterator_666
MONITOR_IFACE=wlan1 # use the wifi interface that support monitor mode
SCAN_IFACE=wlan0 # can be any wifi interface, even the RPi's stock iface is okPlug in your USB Wi-Fi dongle, then:
ip link showYou will see something like:
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
3: wlan1: <BROADCAST,MULTICAST> ...
wlan0 is typically the built-in RPi Wi-Fi. wlan1 is the USB dongle. Confirm which is which:
iw devThis shows each interface with its PHY (physical radio), MAC address, and current mode.
If your interfaces are named differently (e.g. wlx00c0ca...), update monitor_iface and scan_iface in the config accordingly.
sudo iw dev wlan1 set type monitor
sudo ip link set wlan1 up
iw dev wlan1 info | grep typeIf you see type monitor, the dongle works. Reset it after testing:
sudo ip link set wlan1 down
sudo iw dev wlan1 set type managed
sudo ip link set wlan1 upWIDS sets monitor mode automatically at startup — this is just a pre-flight check.
WIDS must run as root because it uses raw packet capture:
sudo python3 ~/wids/wids.pyYou should see startup output:
──────────── WIDS v2 — Wi-Fi Intrusion Detection ────────────
monitor: wlan1 scan: wlan0 SSID: YourNetwork
Setting wlan1 -> monitor mode …
✓ wlan1 in monitor mode
✓ wlan0 already connected to 'YourNetwork'
After about 5–10 seconds the full dashboard will appear. Press Ctrl+C to stop.
Running WIDS as a service means it starts automatically on boot and restarts if it crashes.
WIDS runs inside a tmux session so you can attach and detach from the dashboard. Create the service wrapper:
sudo nano /usr/local/bin/wids-attachPaste:
#!/bin/bash
SESSION="wids"
if tmux has-session -t "$SESSION" 2>/dev/null; then
tmux attach-session -t "$SESSION"
else
STATUS=$(systemctl is-active wids 2>/dev/null)
echo ""
echo "[wids] Session not running."
echo " Status: $STATUS"
echo " Start: sudo systemctl start wids"
echo ""
fisudo chmod +x /usr/local/bin/wids-attachNow you can type wids-attach from anywhere to open the dashboard.
sudo nano /etc/systemd/system/wids.servicePaste (adjust paths and environment variables to match your setup):
[Unit]
Description=WIDS — Wi-Fi Intrusion Detection System
After=network-online.target NetworkManager.service
Wants=network-online.target
[Service]
Type=forking
User=root
WorkingDirectory=/opt/wids
# Load local config as environment variables
EnvironmentFile=-/opt/wids/wids_local.conf
ExecStart=/usr/bin/tmux new-session -d -s wids -x 220 -y 55 \
/usr/bin/python3 /opt/wids/wids.py
ExecStop=/usr/bin/tmux kill-session -t wids
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.targetsudo mkdir -p /opt/wids
sudo cp ~/wids/wids.py /opt/wids/wids.py
sudo cp ~/wids/wids_local.conf /opt/wids/wids_local.conf
sudo chmod 600 /opt/wids/wids_local.conf # protect the passwordsudo systemctl daemon-reload
sudo systemctl enable wids
sudo systemctl start widssudo systemctl status widsYou should see Active: active (running). Then attach to the dashboard:
wids-attachsudo systemctl start wids # start
sudo systemctl stop wids # stop
sudo systemctl restart wids # restart
sudo systemctl status wids # check status
sudo journalctl -u wids -f # follow service logsWIDS runs inside a tmux session. tmux is a terminal multiplexer — it keeps the dashboard running in the background even when you disconnect your SSH session.
| Action | Keys |
|---|---|
| Detach (leave dashboard running) | Ctrl+B then D |
| Scroll up in panel | Ctrl+B then [ then arrow keys, Q to exit |
| Kill session | Ctrl+B then :kill-session |
wids-attach
# or directly:
sudo tmux attach -t widsCreate ~/.tmux.conf:
set -g mouse on
set -g history-limit 5000
set -g default-terminal "screen-256color"
This enables mouse scrolling inside the dashboard.
The dashboard is divided into six panels:
┌─────────────────────────────────────────────────────────────┐
│ Status Bar — pulse, interfaces, channel, AP count, uptime │
├──────────────────────┬──────────────────┬───────────────────┤
│ RF Radar │ Alert Feed │ Network Stats │
│ Access Points table │ RF/WIDS alerts │ Frame histogram │
│ BSSID/SSID/Ch/ │ ── divider ── │ Alert breakdown │
│ Signal/Security/ │ LAN Threats │ Counters │
│ PMF/Rep │ │ │
├──────────────────────┬──────────────────┬───────────────────┤
│ Device Registry │ Live Event │ Connected │
│ Probing clients, │ Stream │ Clients │
│ MAC/vendor/rand/ │ Real-time frame │ IP/MAC/vendor │
│ EAPOL/failures │ log │ via arp-scan │
└──────────────────────┴──────────────────┴───────────────────┘
| Column | Meaning |
|---|---|
| ★ | Home AP marker |
| BSSID | MAC address of the access point |
| SSID | Network name. <hidden> means the AP does not broadcast its SSID |
| Ch | Wi-Fi channel |
| Signal | RSSI in dBm. Green ≥ -60, yellow ≥ -75, red below |
| Security | WPA2-PSK / WPA3-SAE / WEP / Open / ? (not yet scanned) |
| PMF | 802.11w Protected Management Frames. ✓ = enabled, ✗ = not |
| Rep | Trust score 0–100. Decreases on suspicious activity |
APs are sorted strongest signal first. APs on the same channel as your home AP get live RSSI from beacons. APs on other channels get estimated RSSI from nmcli (updated every 60 seconds).
Split into two sections:
RF / WIDS — 802.11 layer threats:
| Alert | Meaning |
|---|---|
| DEAUTH_FLOOD | Someone is sending rapid deauthentication frames — classic Wi-Fi jamming/kickoff attack |
| DISASSOC_FLOOD | Similar to deauth flood |
| PROBE_STORM | A device is probing for many different SSIDs rapidly — possible scanner |
| EAPOL_STORM | Repeated WPA handshakes from one device — possible deauth+reconnect loop |
LAN Threats — IP layer threats:
| Alert | Meaning |
|---|---|
| ARP_SCAN | A device sent many ARP requests in a short window — possible network reconnaissance (e.g. nmap -sn, arp-scan) |
| ARP_POISON | An IP address is claiming a different MAC than previously seen — possible MITM/ARP spoofing attack |
| PORT_SCAN | A device hit many different destination ports in a short window — possible port scan |
| DHCP_STARVE | Many different MACs sending DHCP DISCOVER simultaneously — possible DHCP starvation attack |
| Icon | Level | Meaning |
|---|---|---|
| ℹ | INFO | Informational, no action needed |
| ⚠ | WARN | Suspicious, worth investigating |
| ✖ | ALERT | Likely malicious activity |
| ☠ | CRITICAL | Active attack or system failure |
All Wi-Fi clients seen probing or associating. Columns:
| Column | Meaning |
|---|---|
| Rand | Whether the MAC address is randomised (locally administered bit set) |
| Probes | Number of unique SSIDs this device has probed for |
| EAPOL | WPA handshake frame count |
| Fails | Authentication failure count |
| NEW | Badge shown on first appearance this session |
Devices actively on your LAN right now, discovered via arp-scan. Shows IP address, MAC, and vendor. Refreshes every dashboard cycle (~1 second) but arp-scan itself takes a few seconds so there is slight lag.
All logs are written to /var/log/wids/:
| File | Contents |
|---|---|
wids.log |
All alerts with timestamp and severity |
tcpdump_mgmt.log |
Raw tcpdump stderr from the management frame capture on wlan1 |
tcpdump_lan.log |
Raw tcpdump stderr from the LAN monitor on wlan0 |
tshark_eapol.log |
tshark stderr from the EAPOL capture |
wids_YYYYMMDD_HHMMSS.json |
Full snapshot export every 30 minutes and on shutdown |
wids_YYYYMMDD_HHMMSS_alerts.csv |
CSV of all alerts at time of export |
Log rotation is not built in. To prevent disk fill on long-running deployments, set up logrotate:
sudo nano /etc/logrotate.d/wids/var/log/wids/*.log {
daily
rotate 7
compress
missingok
notifempty
}
- Your Wi-Fi password — move it to an environment variable or external config file (see Configuration section above)
- Log files — contain your home BSSID, all neighbour BSSIDs, and MAC addresses of every device on your network
- Export snapshots —
.jsonand.csvfiles contain the same sensitive data
# Local config with secrets
wids_local.conf
*.conf
# Log and export files
*.log
*.json
*.csv
# Python cache
__pycache__/
*.pyc
*.pyowids.py— after removing hardcoded password and SSID (replace withos.environ.get(...))wids.service— contains no secrets if you useEnvironmentFileREADME.md.gitignore
git diff --staged | grep -iE "password|ssid|secret|key|token"Run this before every commit. If it returns anything, do not push.
The USB dongle may not be recognised. Check:
lsusb
dmesg | tail -30Look for the dongle's USB vendor/product ID in lsusb and any kernel messages about loading firmware. If the dongle is listed but monitor mode fails, the driver may not support it — see Recommended Wi-Fi Dongles.
Run directly to see the error:
sudo python3 /opt/wids/wids.pyCommon causes:
tcpdumportsharknot installed- wlan1 does not support monitor mode
- Running without root (
sudo)
All APs show <hidden> SSID and ? security
The nmcli scan thread has not run yet (it waits 5 seconds on startup) or wlan0 is not connected. Check:
nmcli device status
nmcli dev wifi list ifname wlan0If nmcli dev wifi list returns nothing, wlan0 is not scanning. Ensure NetworkManager is managing it:
sudo nmcli device set wlan0 managed yes
sudo nmcli device wifi rescan ifname wlan0Expected behaviour for APs on channels other than your home AP's channel. wlan1 in monitor mode is locked to a single channel — it only receives beacons from APs on that channel. APs on other channels get estimated signal from nmcli (shown after the first 60-second scan cycle). This is a hardware limitation, not a bug.
A critical capture process (management frame reader or EAPOL reader) has exited. Check the logs:
sudo tail -50 /var/log/wids/tcpdump_mgmt.log
sudo tail -50 /var/log/wids/tshark_eapol.logCommon cause: wlan1 was disconnected, NetworkManager grabbed it back out of monitor mode, or another process bound to the interface. The reader will auto-restart every 5 seconds.
WIDS ignores ARP traffic originating from the Pi itself, but if your home IP changed (e.g. after DHCP renewal) the cached own_ips set will be stale. Restart the service:
sudo systemctl restart widsThe IP cache is rebuilt at startup.
Check:
sudo tail -20 /var/log/wids/tcpdump_lan.log
iw dev wlan0 info | grep typeIf wlan0 shows type monitor, something has flipped it into monitor mode (unlikely but possible). WIDS will detect this and skip the LAN monitor with a LAN_SKIP alert until it is resolved.
On a RPi 3B+ with a busy network, CPU can reach 30–40% during peak probe activity. This is normal. If it consistently exceeds 80%, reduce the dashboard refresh rate in CFG:
"dashboard_refresh": 2.0, # was 1.0The service may still be starting. Wait 15–20 seconds after boot then try wids-attach again. If it consistently fails, check:
sudo systemctl status wids
sudo journalctl -u wids -n 30


