Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BFId — BFI-based Identity Inference

A reproduction of the pipeline described in the "BFId" line of research (WiFi 802.11ac Beamforming Feedback Information used for passive human identification via micro-Doppler-like body-movement signatures): capture → parse → windowed tensor → LSTM classifier.

BFI is MAC-layer, unencrypted Action-frame traffic, so unlike CSI it can be captured with an ordinary monitor-mode-capable NIC instead of specialized firmware/chipsets. That's also exactly why this pipeline needs to be built and used carefully — see Legal & ethical use below before you point it at anything.

What's real vs. what's simulated here

Everything runs today except live RF capture, which needs hardware this environment doesn't have:

Stage Status
bfid.simulate — synthetic BFI generator ✅ runs now, no hardware needed
bfid.preprocessing — windowing, session-aware split ✅ runs now, tested
bfid.model — LSTM classifier, train/infer ✅ runs now, tested
bfid.parsing — pcap → CSV (pcap/radiotap/802.11 framing) ✅ implemented, tested against synthetic pcaps
bfid.parsing.vht_beamforming — angle bit-unpacking ⚠️ implemented from the general standard structure, not yet validated against a real capture (no VHT hardware available here) — see the caveat in that file and in "Validating the parser" below
bfid.capture — monitor mode + tcpdump orchestration 🔧 needs your own monitor-mode NIC to run

The synthetic path exists so you can develop/trust the ML side independently of the hardware side: bfid.simulate.synthetic_bfi writes the exact same CSV schema bfid.parsing.extract_bfi produces, so every downstream stage — windowing, splitting, training, inference — is the same unmodified code whether the data came from the air or from a random-number generator.

Quickstart (no hardware required)

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

./scripts/run_synthetic_demo.sh

If python3 -m venv fails with an ensurepip is not available error (some minimal Linux images ship Python without it and without a way to install the missing OS package non-interactively), bootstrap pip manually instead:

python3 -m venv --without-pip .venv && source .venv/bin/activate
curl -sS -o /tmp/get-pip.py https://bootstrap.pypa.io/get-pip.py && python3 /tmp/get-pip.py
pip install -r requirements.txt

This generates 12 synthetic identities × 8 sessions × 500 packets, builds a (4416, 50, 740) windowed tensor, trains the LSTM with early stopping, and prints a per-identity precision/recall/F1 report on a held-out test split. On this synthetic data it reaches ~100% test accuracy — that's expected and not meaningful on its own: the synthetic signatures are, by construction, cleanly separable. It demonstrates the pipeline is wired correctly, not real-world identification accuracy. Real accuracy depends entirely on real captured data.

Run the test suite separately with python -m pytest tests/ -v.

Running against real captures

1. Hardware & environment

  • Linux, a NIC + driver that supports monitor mode at 802.11ac (VHT) rates — e.g. RTL8812AU/RTL8814AU with the aircrack-ng fork of the driver. A NIC that only does monitor mode at 802.11n rates will not see VHT sounding frames.
  • tshark, tcpdump, iw on PATH; airmon-ng optional (scripts fall back to iw/ip if it's missing).
  • An access point and at least one associated station under your control, both configured for 802.11ac with beamforming/sounding enabled (this is usually on by default on modern APs).

Confirmed NOT to work: Intel AX200/AX210 (iwlwifi). Tested directly — iw accepts monitor mode and any channel/width on these chips without error (iw dev <iface> info reports the config as applied), but 5GHz reception is silently empty: zero packets, not even beacons, across multiple channels and widths, while the identical setup on 2.4GHz works immediately (100+ packets in under a minute). This matches widely-reported iwlwifi community issues, not a config problem on our end — see Troubleshooting for how we isolated it. If your machine's only radio is one of these, the capture stage needs a dedicated USB adapter (RTL8812AU/RTL8814AU); nothing else in this pipeline needs different hardware.

2. Capture

# second arg is a channel number, or an SSID to auto-resolve its current
# channel via a live scan (channels drift with AP auto-selection/DFS events,
# so this is the more reliable option if the target isn't yours to configure)
sudo bfid/capture/setup_monitor.sh wlan1 "MyNetwork5G" 80MHz
sudo bfid/capture/capture_bfi.sh wlan1 data/raw/run01.pcap 120 &

# in another shell, generate traffic to YOUR OWN test device so the AP
# sounds it more often (see the script's docstring — this only works on
# devices/networks you control):
python -m bfid.capture.induce_traffic 192.168.50.42 --duration 120

If your NIC is managed by NetworkManager (true for most desktop/laptop installs), setup_monitor.sh explicitly tells it to stop managing the interface before switching to monitor mode, and prints the exact commands to hand it back afterward. Skipping that handoff is not theoretical: in testing, NetworkManager silently reassociated a monitor-mode interface to a saved profile within ~15 seconds, and tcpdump kept writing the resulting ordinary Ethernet-framed traffic under the already-declared radiotap linktype header, producing a capture that looked like 802.11 frames but wasn't. If a capture comes back with 0 packets, or with frames that decode as nonsense ("PV1", garbled addresses, wrong protocol version), that mismatch — or a stale channel number — is the first thing to check, not the VHT parser.

3. Parse

python -m bfid.parsing.extract_bfi data/raw/run01.pcap data/parsed/run01.csv \
    --session-id run01 --feature-dim 740

If this writes 0 rows, see the troubleshooting checklist it prints — the most common causes are FCS presence mismatch (--no-fcs) or a capture that didn't actually contain VHT sounding traffic.

4. Label

BFI carries no identity — you supply that from knowing who was carrying which MAC during which session, which only works because you ran a controlled, consenting collection in the first place:

sta_mac,identity
aa:bb:cc:dd:ee:ff,alice
11:22:33:44:55:66,bob

Concatenate multiple extract_bfi.py runs (one per session, distinct --session-ids) into a single CSV before the next step, or build/merge tensors per-run and concatenate the .npz arrays — either works as long as session_id stays unique per continuous recording.

5. Build tensor, train, infer

python -m bfid.preprocessing.build_tensor data/parsed/all_sessions.csv data/labels.csv data/tensors.npz
python -m bfid.model.train data/tensors.npz --out checkpoints/model.pt
python -m bfid.model.infer checkpoints/model.pt data/parsed/new_capture.csv

Validating the parser

bfid/parsing/vht_beamforming.py decodes the MIMO Control field mechanically (low risk) and the compressed-beamforming angle bitstream from the general published structure of the Givens-rotation encoding (higher risk — field ordering varies more than field presence across sources). Before trusting output from real hardware:

  1. python -m pytest tests/test_vht_beamforming.py -v — proves the pack/unpack code is internally consistent. Passing this does not prove it matches what your NIC actually transmits.
  2. Capture a few real frames, unpack them, and check the recovered angles look like a real signal: in-range and smoothly varying frame-to-frame. Bit-misalignment usually looks like noise jumping between min/max every packet, not a subtly-wrong-but-smooth signal.
  3. If it looks wrong, --no-fcs is the first thing to try, then the phi/psi ordering inside unpack_beamforming_angles (grouped per-column as phi-then-psi; some captures may need all-phi-then-all-psi instead).

Feature dimension

features.feature_dim: 740 in configs/default.yaml matches the original BFId paper's capture setup (bandwidth × antenna config × grouping — it is not a universal constant). build_tensor.py tells you the real column count from your CSV and uses that instead if it disagrees, but set this correctly in the config once you know your setup so padding/truncation is intentional rather than silent.

Troubleshooting real captures

Everything below was hit and diagnosed while validating this pipeline against real hardware. Keeping it here so the next run doesn't have to re-discover it from scratch.

Capture comes back with 0 packets, or with garbled/nonsense frames ("PV1", wrong protocol version, addresses that don't look like real MACs): NetworkManager (or another network daemon) silently reconnected the interface out of monitor mode mid-capture — confirmed via journalctl -u NetworkManager showing it reassociating the device to a saved profile seconds after the mode switch. tcpdump kept writing the resulting ordinary Ethernet-framed traffic under the already-declared radiotap linktype header, which is what produces the nonsense decode. setup_monitor.sh now tells NetworkManager to back off (nmcli device set <iface> managed no) before switching modes and prints the exact command to hand it back afterward.

0 packets on 5GHz specifically, but 2.4GHz works fine on the same script: see the Intel AX200/AX210 note under Hardware above — this looks like a real firmware/driver limitation, not a config problem. Confirm with a clean control test: identical script and flow, but target a 2.4GHz SSID instead, and check you get something (beacons alone should give dozens of packets in under a minute). If 2.4GHz also comes back empty, look at NetworkManager interference or a dead capture path first — don't assume it's the same 5GHz-specific issue.

iw rejects the bandwidth argument: its width keywords are NOHT, HT20, HT40+, HT40-, 5MHz, 10MHz, 80MHz, 160MHz, 320MHz — note a plain 20MHz channel is HT20, not 20MHz (only the wider VHT/EHT widths use the NNMHz spelling). setup_monitor.sh validates this up front and refuses to touch the interface if it's wrong, rather than failing partway through a mode switch and leaving the radio on whatever channel it was on before.

tshark: You don't have permission to read the file ... on a file you own: not a real file-permission problem. Ubuntu ships tshark with an AppArmor profile (/etc/apparmor.d/tshark) that only grants unrestricted .pcap read access to its dumpcap child profile — not to arbitrary paths for the outer tshark process reading a saved file directly. Confirm with journalctl -k | grep -i 'apparmor.*denied.*tshark'. The fix that doesn't touch security policy: copy the file under /tmp/ first (covered by the user-tmp AppArmor abstraction) and run tshark/extract_bfi.py against that copy. Adding yourself to the wireshark group (the fix you'll find in most general guides) is for live capture via dumpcap and doesn't help with this offline -r read — and either way, group membership changes don't apply to already-running shells, only genuinely new login sessions.

Legal & ethical use

This pipeline's entire premise is that BFI is unencrypted MAC-layer traffic readable by anyone in monitor mode — no compromise, no network credentials, no cooperation from the target device required. That's what makes it useful for the legitimate research use case (studying what an unencrypted WiFi standard leaks about the people near it) and also what makes it usable for non-consensual tracking if pointed at people who haven't agreed to it.

  • Get informed consent from anyone whose movement data you capture and label. The induce_traffic.py / labeling workflow above is designed around a lab setup you control with participants who know they're in it — that's not incidental, it's the only setup that gives you ground-truth labels anyway.
  • Capturing wireless traffic and building biometric-identification models from it is independently regulated in a lot of places — wiretap/ interception statutes (their carve-outs for "readily accessible" radio communications vary by jurisdiction and are genuinely unsettled for this kind of traffic), and biometric-privacy law where movement-derived identification counts as biometric data (e.g. BIPA in Illinois, GDPR Art. 9 in the EU/UK). This is not legal advice; check what applies where you are before deploying beyond your own lab.
  • Only run bfid.capture.* against networks/devices you own or are explicitly authorized to test.

Layout

bfid/
  capture/        monitor-mode setup, tcpdump capture, own-device traffic induction
  parsing/         pcap -> 802.11 action frames -> VHT angle CSV
  simulate/        synthetic BFI generator (same CSV schema as parsing output)
  preprocessing/   CSV -> windowed tensor, session-aware train/val/test split
  model/           LSTM classifier, train/infer
configs/default.yaml   all tunables in one place
scripts/run_synthetic_demo.sh   generate -> window -> train, no hardware needed
tests/           bit-parser round-trip, pcap framing, tensor/split correctness

Config reference (configs/default.yaml)

  • capture.* — interface/channel/bandwidth passed to the capture scripts
  • parsing.has_fcs — whether captured frames carry a trailing 4-byte FCS
  • features.feature_dim/window_size/window_stride — padded feature length, timesteps per training window, hop between windows
  • model.* — LSTM hidden size/layers/dropout/bidirectional
  • train.* — batch size, epochs, LR, val/test fractions, early stopping
  • simulate.* — synthetic dataset size/shape

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages