dronewatch
Passive multi-sensor drone detection — WiFi OUI + OpenDroneID RemoteID decoder
v0.1.1Quick Start
Install via jcli (recommended once shipped)
jcli install dronewatch
Decode a captured pcap (no root, no monitor mode)
dronewatch decode capture.pcap
# emits one NDJSON line per contact_update on stdout
Live capture on a monitor-mode interface
# put your WiFi card in monitor mode first
sudo iw dev wlan0 interface add wlan0mon type monitor
sudo ip link set wlan0mon up
# then run dronewatch — needs CAP_NET_RAW
sudo dronewatch run --wifi-iface wlan0mon --alert-min LOW
sudo dronewatch run --wifi-iface wlan0mon --output json --log-dir /var/log/dronewatch
What it does
dronewatch is a passive, listen-only drone detection CLI. It opens a
monitor-mode WiFi interface (or replays an offline pcap), parses 802.11 management frames,
and surfaces any drone signal it finds — either by drone-vendor OUI / drone-shaped SSID, or
by the OpenDroneID broadcast that EU 2021/664 and FAA Part 89 mandate. Each detection is
fused into a single Contact by a correlator keyed on MAC and UAS-ID, then
written as a one-line alert (terminal / syslog-friendly) or NDJSON (machine-friendly).
- Passive only. No transmission, no deauthentication, no jamming, no active probing. The radio listens; nothing is emitted.
- Pure-Rust OpenDroneID decoder. Decodes the WiFi vendor-IE path of
ASTM F3411-22a directly — no
opendroneid-core-cFFI. - Deterministic NDJSON. One JSON object per line per
contact_update; safe to pipe into jq, journald, or any SIEM ingest. - Suite-native. Same install layout, signify-signed release path, and tagline footer as every other tool at cli.johlem.net.
Subcommands
| Command | What it does |
|---|---|
dronewatch run | Live detection loop on a monitor-mode WiFi interface. --output = plain | json. Optional --log-dir for daily-rotated NDJSON. |
dronewatch scan | One-shot timed capture; prints packets_seen / sensor_events / rule_hits summary and exits. Useful as a smoke-test. |
dronewatch decode <pcap> | Offline replay of a .pcap or .pcapng file through the same pipeline as run. No root required. |
dronewatch list-hw | Enumerates /sys/class/net; flags interfaces that look wireless-capable. |
dronewatch version | Prints version + tagline. |
Detection scope (v0.1)
v0.1 ships the highest-confidence detection paths first, deliberately. Other vectors arrive in later phases as the spec phases them out.
| Sensor | v0.1 | Notes |
|---|---|---|
| WiFi OUI / SSID | ✓ | DJI / Parrot / Autel / Skydio / Yuneec OUIs + drone-named SSIDs. |
| OpenDroneID over WiFi | ✓ | Pure-Rust decoder, ASTM F3411-22a Basic ID + Location + Operator ID. |
| Bluetooth LE | v0.2 | Including OpenDroneID-over-BLE Long Range. |
| ADS-B Mode-S | v0.2 | Beast TCP from dump1090 for manned-aircraft deconfliction. |
| SDR FFT + FHSS | v0.3 | SoapySDR + rule-based RF pattern classifier. |
| MAVLink, acoustic | v0.5 | Feature-gated; both optional at build time. |
| ONNX RF classifier | v1.0 | ort-backed; falls back to rule engine if model file missing. |
NDJSON event shape
{
"ts": "2026-06-20T14:32:07.123Z",
"event": "contact_update",
"contact": {
"id": "<uuid>",
"first_seen": "2026-06-20T14:32:01.501Z",
"last_seen": "2026-06-20T14:32:07.123Z",
"confidence": 0.87,
"threat_level": "MEDIUM",
"label": "DJI drone (WiFi)",
"sources": ["wifi", "remoteid"],
"mac": "60:60:1f:aa:bb:cc",
"uas_id": "FIN1234567890",
"position": { "lat": 49.6116, "lon": 6.1319 },
"altitude_m": 78.5,
"speed_mps": 12.3,
"rssi_dbm": -62,
"raw_events": ["...","..."]
}
}
Exit codes
| Code | Meaning |
|---|---|
0 | Ok |
2 | Usage error (clap or bad --alert-min) |
3 | Sensor / capture / integrity failure |
Touches / Produces / Gates
- Touches (read): a monitor-mode WiFi interface (live
run+scan) or a.pcapfile (decode). Read-only on both — the radio never transmits. - Touches (write): only the daily-rotated NDJSON in
--log-dirif set. Stdout otherwise. - Produces: stdout — one-line alerts (
--output plain) or NDJSON (--output json). Stderr — capture-window status lines. - Gates:
--i-am-authorizedis not required — passive RF monitoring is permissible in EU/Luxembourg for security research. Live capture does need CAP_NET_RAW (or root) and a monitor-mode-capable WiFi card. Confirm with your jurisdiction's regulator before operational deployment.
Build from source
cd tools/dronewatch/rust
# libpcap-dev required for the wifi feature (default)
cargo build --release
cargo test # 25 unit + 5 integration tests
Toolchain pin: Rust 1.85.0. Dependencies: clap, serde,
serde_json, chrono, uuid, pcap,
anyhow, thiserror — all in the suite-permitted allowlist.
Release artefacts & provenance
Every signed release ships with checksum + signature sidecars plus a CycloneDX SBOM. Enterprise procurement can pin third-party components without unpacking the binary.
-
manifest.json— pinned SHA-256 +.sigURL + SBOM URL per platform -
johlem-release.pub— Ed25519 public key used bysignify -V(fingerprint:4532510f…6748, pinned injcli verify) CHANGELOG.md— semver-tagged release notes- Security contact:
security@johlem.net