mirror of
https://github.com/ruvnet/RuView.git
synced 2026-08-31 20:45:58 +00:00
feat(privshield): E2E hardware program — validated C core + multi-provider firmware scaffolds
Take VEIL from the synthetic Rust reference model toward real WiFi silicon
across multiple hardware providers, around one shared, host-validated core.
Answers the questions "can OpenWRT / open WiFi software implement this?" and
"can ESP32 help scramble signals?" with an honest per-platform feasibility map.
Portable C shield core (firmware/privshield/core/) — VALIDATED (host test):
- veil_shield.{h,c}: keyed Givens-rotation obfuscation of the identity-bearing
"fine" subspace, C99, no malloc / no libc I/O, only <math.h>. SplitMix64 key
schedule byte-identical to the Rust crate, so on-air behavior is consistent
everywhere and every adapter links the same math.
- make test passes: energy conservation (orthogonal => "not jamming"),
reversibility (recover inverts apply), wrong-key-fails, and PRNG stream parity
with the Rust crate. This is build/host evidence, NOT silicon.
Per-provider adapters (all SYNTHETIC / L0, build-only, TODO(hw) markers):
- openwifi/ grade B (ceiling A, effort D): only open PHY/MAC (FPGA) that can
host the full keyed rotation + inverse; needs new HDL + 2nd TX chain. Carries
the P5 measurement protocol (MEASUREMENT.md) for the first MEASURED result.
- openwrt/ grade C: per-packet keyed unitary is blob-blocked on commodity APs;
coarse compliant knobs (TX antenna map, sounding-cadence jitter) reachable
from userspace/hostapd; ath9k is the one credible driver-patch route.
- nexmon/ grade C: reading the compressed-BF angles is solved (nexmon_csi /
Wi-BFI); shaping the transmitted report is research-grade (D11 ucode-adjacent).
- esp32/ grade F (self) / B (supporting): cannot shape its own BF feedback
(closed esp-phy-lib blob); legitimate as a sensing detector and external-RIS
controller — the honest way ESP32 "helps scramble", via an external surface.
Docs:
- firmware/privshield/README.md: architecture, layout, and the feasibility matrix.
- ADR-290: the E2E hardware program, PROOF discipline, and per-provider decision;
added to docs/adr/README.md index.
Compliant waveform controls only, never jamming. No adapter has run on silicon;
no MEASURED claim is made (that is roadmap P5, gated on a captured log).
Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01WEXNqzs7UsfNFBcP5yW21p
This commit is contained in:
95
firmware/privshield/openwrt/INTEGRATION.md
Normal file
95
firmware/privshield/openwrt/INTEGRATION.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# VEIL ↔ `mac80211` / driver integration map
|
||||
|
||||
> **`SYNTHETIC / L0` — BUILD-ONLY, UNTESTED ON HARDWARE.** These are hook-point
|
||||
> designs derived from public API/source, not validated on silicon. Function and
|
||||
> attribute names are real (verified against in-tree `linux/nl80211.h` and public
|
||||
> hostapd/driver docs); where a hook does **not** exist upstream it is marked
|
||||
> `TODO(hw)` with what a patch would have to add. Compliant controls only.
|
||||
|
||||
Legend: **US** = userspace-reachable today · **DP** = needs driver patch ·
|
||||
**FW** = needs firmware patch (blob-blocked).
|
||||
|
||||
---
|
||||
|
||||
## 1. TX antenna-map perturbation — **US** (feasible)
|
||||
|
||||
- **Daemon:** `veil_set_tx_antenna_mask()` in `veil_shieldd.c`.
|
||||
- **Kernel path:** `nl80211` → `cfg80211_ops.set_antenna()` → driver
|
||||
`.set_antenna` (e.g. `mt7915_set_antenna`, `ath9k` `set_antenna`).
|
||||
- **Attributes:** `NL80211_CMD_SET_WIPHY`, `NL80211_ATTR_WIPHY_ANTENNA_TX`,
|
||||
`NL80211_ATTR_WIPHY_ANTENNA_RX`.
|
||||
- **Constraints:** many drivers require the phy DOWN and accept only symmetric
|
||||
masks; validate per driver. Coarse static spatial-mapping change, not the keyed
|
||||
rotation. Fully standards-compliant.
|
||||
|
||||
## 2. NDP sounding-cadence jitter — **US (indirect)**
|
||||
|
||||
- **Daemon:** `veil_randomize_sounding_cadence()` / `veil_next_cadence_ms()`.
|
||||
The schedule is derived from the session key via the core SplitMix64 so the
|
||||
paired receiver can anticipate it (not random spraying).
|
||||
- **Real lever:** hostapd `ctrl_iface` (UNIX socket `/var/run/hostapd/<iface>`):
|
||||
`SET he_su_beamformer …` / rewrite `vht_capab` `[SOUNDING-DIMENSION-n]` /
|
||||
toggle `[SU-BEAMFORMER]`, then `RECONFIGURE`. Config keys documented in
|
||||
`hostapd.conf`.
|
||||
- **`TODO(hw)`:** there is **no** `nl80211` "set sounding interval" command; the
|
||||
per-NDP timer is in driver/firmware. We can only jitter the *offered* cadence.
|
||||
The `ctrl_iface` write itself is not yet wired (function currently only
|
||||
computes `ms`).
|
||||
|
||||
## 3. MU-MIMO group shuffling — **FW** (blob-blocked)
|
||||
|
||||
- **Daemon:** `veil_shuffle_mumimo_groups()` — explicit `-ENOTSUP` no-op.
|
||||
- **Where it lives:** MU group formation + per-group steering matrices are
|
||||
computed in the WiFi MCU firmware on mt76 (mt7915) and all ath1x parts.
|
||||
- **`TODO(hw)`:** would require `NL80211_CMD_VENDOR` with a driver-specific
|
||||
`NL80211_ATTR_VENDOR_ID` / `NL80211_ATTR_VENDOR_SUBCMD` /
|
||||
`NL80211_ATTR_VENDOR_DATA` that upstream mt76/ath do **not** define, plus a
|
||||
firmware change to honor an externally supplied grouping. Not reachable without
|
||||
both a driver and firmware patch.
|
||||
|
||||
## 4. Per-packet keyed unitary (the core VEIL transform) — **FW** (blob-blocked)
|
||||
|
||||
- **Daemon:** `veil_apply_keyed_rotation()` → `veil_shield_apply(fine, n, key,
|
||||
passes)` from the portable core. Orthogonal / energy-preserving (the
|
||||
"not jamming" invariant, checked via `veil_l2_norm` before/after).
|
||||
- **What a full path must touch:**
|
||||
- **mt76 (mt7915):** the MCU firmware stage that builds the compressed
|
||||
beamforming report (φ/ψ angles) or applies the steering/precoder Q to the
|
||||
LTF spatial mapping. A firmware patch would call the rotation on the fine
|
||||
subspace *before* the report is emitted / precoder applied. The driver
|
||||
(`mt7915/mcu.c`) would ferry the key/passes down via a new MCU command.
|
||||
- **ath9k (DP, best open case):** the static spatial-mapping matrix is set via
|
||||
`AR_PHY_*` registers in the open PHY init; a driver patch could apply a keyed
|
||||
*static* Q there. This is coarser than a true per-packet report edit but is
|
||||
the most credible OpenWRT-adjacent route (older 802.11n hardware only).
|
||||
- **ath10k/ath11k/ath12k:** report generation + precoder are entirely
|
||||
firmware-side with no open firmware (ath11k/ath12k) — not patchable.
|
||||
- **`TODO(hw)`:** on OpenWRT there is **no** userspace/`mac80211` hook that hands
|
||||
the pre-precoder V/steering buffer to the daemon before TX. Reaching it needs
|
||||
the driver+firmware patch above, or use the **openwifi (FPGA)** / **Nexmon
|
||||
(Broadcom)** adapters, which expose the datapath. The daemon only proves the
|
||||
math is invariant; nothing goes on air.
|
||||
|
||||
## 5. Sensing-solicitation (NDPA) detection — **US/DP** (partial)
|
||||
|
||||
- **Daemon:** `veil_event_cb()` on `NL80211_CMD_FRAME`.
|
||||
- **Real path:** `NL80211_CMD_REGISTER_FRAME` to subscribe to specific
|
||||
management action categories, delivered as `NL80211_CMD_FRAME` with
|
||||
`NL80211_ATTR_FRAME`. Classify VHT/HE compressed beamforming action
|
||||
(categories 21 / 30) and NDP Announcement to measure cadence.
|
||||
- **`TODO(hw)`:** commodity drivers do **not** forward raw NDPA to userspace by
|
||||
default; honest external-solicitation detection needs monitor-mode capture or a
|
||||
driver notification that is not guaranteed upstream. Frame parsing is stubbed.
|
||||
|
||||
---
|
||||
|
||||
## Summary of the effort boundary
|
||||
|
||||
| Control | Effort to reach full VEIL fidelity |
|
||||
|---|---|
|
||||
| TX antenna map | Ready now (US), coarse only |
|
||||
| Sounding cadence jitter | Wire hostapd `ctrl_iface` (US), coarse only |
|
||||
| Static spatial Q | ath9k driver patch (DP) |
|
||||
| MU grouping | driver vendor subcmd + firmware (FW) |
|
||||
| Per-packet keyed rotation | mt76/ath **firmware** patch, or openwifi/Nexmon adapter (FW) |
|
||||
| NDPA detection | frame registration + likely driver patch (US/DP) |
|
||||
44
firmware/privshield/openwrt/Makefile
Normal file
44
firmware/privshield/openwrt/Makefile
Normal file
@@ -0,0 +1,44 @@
|
||||
# SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
#
|
||||
# Host build-CHECK for the OpenWRT/mac80211 VEIL adapter.
|
||||
# STATUS: SYNTHETIC / L0 — build-only, UNTESTED ON HARDWARE.
|
||||
#
|
||||
# Two targets:
|
||||
# make core - compile+link the portable core only (always works,
|
||||
# no libnl needed) — proves the rotation math builds.
|
||||
# make daemon - build veil_shieldd against libnl-genl-3 (needs the
|
||||
# dev headers: `pkg-config libnl-genl-3.0`). On OpenWRT
|
||||
# the package build uses libnl-tiny instead (see openwrt.mk).
|
||||
#
|
||||
# This Makefile does NOT flash, run on, or validate any radio.
|
||||
|
||||
CC ?= cc
|
||||
COREDIR := ../core
|
||||
CFLAGS ?= -std=c99 -Wall -Wextra -O2 -I$(COREDIR)
|
||||
LDLIBS ?= -lm
|
||||
|
||||
NL_CFLAGS := $(shell pkg-config --cflags libnl-genl-3.0 2>/dev/null)
|
||||
NL_LIBS := $(shell pkg-config --libs libnl-genl-3.0 2>/dev/null)
|
||||
|
||||
.PHONY: all core daemon clean
|
||||
all: core
|
||||
|
||||
# Always-buildable: the core object, no netlink dependency.
|
||||
core: $(COREDIR)/veil_shield.c $(COREDIR)/veil_shield.h
|
||||
$(CC) $(CFLAGS) -c $(COREDIR)/veil_shield.c -o veil_shield.o
|
||||
@echo "core built (rotation math OK). Nothing was run on hardware."
|
||||
|
||||
# Full daemon: requires libnl-genl-3 dev headers on the host.
|
||||
daemon: veil_shieldd.c core
|
||||
ifeq ($(strip $(NL_LIBS)),)
|
||||
@echo "SKIP daemon: libnl-genl-3.0 not found (pkg-config)."
|
||||
@echo " Install libnl-3-dev + libnl-genl-3-dev, or build via openwrt.mk."
|
||||
@exit 0
|
||||
else
|
||||
$(CC) $(CFLAGS) $(NL_CFLAGS) -o veil_shieldd \
|
||||
veil_shieldd.c veil_shield.o $(NL_LIBS) $(LDLIBS)
|
||||
@echo "veil_shieldd linked (BUILD-ONLY; untested on silicon)."
|
||||
endif
|
||||
|
||||
clean:
|
||||
rm -f veil_shield.o veil_shieldd
|
||||
112
firmware/privshield/openwrt/README.md
Normal file
112
firmware/privshield/openwrt/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# VEIL — OpenWRT / Linux `mac80211` adapter
|
||||
|
||||
> **STATUS: `SYNTHETIC / L0` — BUILD-ONLY, UNTESTED ON HARDWARE.**
|
||||
> No radio was driven, no CSI captured, no log produced on silicon. Every
|
||||
> claim below is a design/feasibility statement, not a `MEASURED` result. This
|
||||
> adapter uses **compliant waveform controls only** — it never jams and emits
|
||||
> no denial energy.
|
||||
|
||||
This directory is the OpenWRT/`mac80211` platform adapter for the VEIL privacy
|
||||
shield. It links the validated portable core
|
||||
(`../core/veil_shield.{h,c}` — the keyed Givens rotation over the identity-bearing
|
||||
"fine" subspace of 802.11 compressed beamforming feedback) and drives the subset
|
||||
of controls that Linux userspace/`mac80211` can actually reach on commodity APs.
|
||||
|
||||
---
|
||||
|
||||
## Feasibility grade: **C** (partial — coarse compliant controls only)
|
||||
|
||||
**Why C, not higher.** VEIL's defining action is a *per-packet keyed unitary* on
|
||||
the compressed beamforming-feedback angles (equivalently, a keyed Q on the LTF
|
||||
spatial mapping / precoder). On every mainstream OpenWRT AP chipset
|
||||
(Qualcomm ath10k/ath11k/ath12k, MediaTek mt76 / mt7915), that report is generated
|
||||
and the precoder applied **inside the WiFi MCU firmware blob** — userspace and the
|
||||
open driver never touch the pre-transmit V matrix. So the full keyed-rotation path
|
||||
is **blob-blocked** from OpenWRT. What remains reachable is a set of *coarse*
|
||||
compliant knobs that perturb, but do not cryptographically obfuscate, the CSI a
|
||||
sensor observes. That is a real, honest defense-in-depth layer — hence C, not D —
|
||||
but it is not the full VEIL transform.
|
||||
|
||||
**Why not D.** Some controls genuinely work from userspace (TX antenna map;
|
||||
hostapd-mediated sounding/beamformer capability), and one chipset family
|
||||
(**ath9k**) is open enough at the register level that a *driver patch* could reach
|
||||
the static spatial-mapping matrix — a credible route to B on that specific,
|
||||
older hardware. openwifi (FPGA) and Nexmon (Broadcom) are the routes to the full
|
||||
A-grade keyed rotation, but those are **separate adapters**, not OpenWRT.
|
||||
|
||||
---
|
||||
|
||||
## What is FEASIBLE vs. BLOB-BLOCKED from OpenWRT
|
||||
|
||||
| VEIL control | Reachable from OpenWRT? | Mechanism (real API / knob) | Notes |
|
||||
|---|---|---|---|
|
||||
| **TX antenna-map perturbation** | ✅ Feasible | `NL80211_CMD_SET_WIPHY` + `NL80211_ATTR_WIPHY_ANTENNA_TX` / `_RX` | Coarse static spatial-mapping change. Many drivers require phy DOWN and symmetric masks. Compliant. |
|
||||
| **NDP sounding-cadence jitter** | 🟡 Indirect | hostapd `ctrl_iface` (rewrite `SOUNDING-DIMENSION`, toggle `[SU-BEAMFORMER]`, `RECONFIGURE`) | No `nl80211` "set sounding interval" exists; the per-NDP timer lives in driver/firmware. We can only jitter the *offered* capability. |
|
||||
| **Beamformer/beamformee capability toggle** | ✅ Feasible | hostapd `vht_capab` / `he_su_beamformer` etc. | Standards-compliant advertisement. Coarse on/off, not per-packet. |
|
||||
| **Spatial-stream → antenna mapping (static Q)** | 🟡 Driver-patch (ath9k only) | ath9k PHY spatial-mapping registers (`AR_PHY_*`) | Open enough to patch on ath9k; opaque/firmware on ath10k+/mt76. Not a stock userspace knob. |
|
||||
| **MU-MIMO group shuffling** | ❌ Blob-blocked | would need `NL80211_CMD_VENDOR` subcmd that upstream mt76/ath do **not** expose | Group formation + steering matrices computed in MCU firmware. |
|
||||
| **Per-packet keyed unitary on LTF / precoder** | ❌ Blob-blocked | — | The core VEIL transform. Lives in firmware on all commodity AP parts. Requires firmware patch, or use openwifi / Nexmon adapters. |
|
||||
| **Compressed-BF-report angle edit (φ/ψ)** | ❌ Blob-blocked | — | Report is generated in firmware/PHY; not exposed pre-TX on OpenWRT. |
|
||||
| **External sensing-solicitation detection (NDPA cadence)** | 🟡 Partial | `NL80211_CMD_FRAME` + `NL80211_CMD_REGISTER_FRAME`, or monitor-mode capture | Commodity drivers do not forward raw NDPA to userspace by default. |
|
||||
|
||||
---
|
||||
|
||||
## Best candidate chipsets / drivers
|
||||
|
||||
- **ath9k (Atheros 802.11n)** — *best open target for a driver-side patch.* The
|
||||
most transparent open driver (no per-packet firmware for the datapath), with a
|
||||
long history of PHY register access and the Atheros CSI Tool ecosystem. A
|
||||
static spatial-mapping perturbation and CSI observation are realistic here;
|
||||
full HT beamforming-feedback editing still is not in open code. 802.11n-only.
|
||||
- **mt76 (MediaTek mt7915 / mt7622-mt7615)** — *best-maintained modern open
|
||||
driver* and the most likely place upstream would eventually accept a vendor
|
||||
hook, but beamforming/sounding/MU grouping run in the MCU firmware today, so
|
||||
the keyed path needs a firmware patch (blob-blocked out of the box).
|
||||
- **ath10k / ath11k / ath12k (Qualcomm)** — most capable radios but the most
|
||||
closed: regulatory + beamforming + sounding all firmware-side. ath11k/ath12k
|
||||
have **no open firmware** at all. Worst target for the keyed path.
|
||||
- **openwifi (FPGA SDR) / Nexmon (Broadcom)** — the only routes to the full
|
||||
A-grade keyed rotation; handled by the sibling `../openwifi/` and `../nexmon/`
|
||||
adapters, **not** this OpenWRT one.
|
||||
|
||||
**Recommendation:** for OpenWRT specifically, target **ath9k** for a
|
||||
driver-patch proof-of-concept (spatial-mapping + CSI), and **mt76/mt7915** as the
|
||||
strategic modern platform pending a firmware/vendor-subcmd hook.
|
||||
|
||||
---
|
||||
|
||||
## Build (host, build-only)
|
||||
|
||||
```bash
|
||||
make core # always works: compiles+links the portable core, no libnl needed
|
||||
make daemon # builds veil_shieldd IF libnl-genl-3.0 dev headers are present
|
||||
make clean
|
||||
```
|
||||
|
||||
`make daemon` cleanly **skips** (does not fail) when `libnl-genl-3.0` is absent,
|
||||
printing the required dev packages. On an OpenWRT buildroot use `openwrt.mk`
|
||||
(rename to `Makefile` under `package/utils/veil-shieldd/`), which builds against
|
||||
`libnl-tiny`. See `INTEGRATION.md` for the per-control hook points and exactly
|
||||
what a driver/firmware patch would need to touch.
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- Linux `nl80211.h` (in-tree, this host): `NL80211_CMD_SET_WIPHY`,
|
||||
`NL80211_ATTR_WIPHY_ANTENNA_TX` / `_RX`, `NL80211_CMD_VENDOR`,
|
||||
`NL80211_CMD_FRAME` / `NL80211_CMD_REGISTER_FRAME`.
|
||||
- ath10k configuration (beamforming only via hostapd `vht_capab`, no debugfs
|
||||
sounding control): <https://wireless.docs.kernel.org/en/latest/en/users/drivers/ath10k/configuration.html>
|
||||
- hostapd beamforming/sounding knobs (`[SU-BEAMFORMER]`, `[MU-BEAMFORMER]`,
|
||||
`[SOUNDING-DIMENSION-4]`, `he_su_beamformer`):
|
||||
<https://w1.fi/cgit/hostap/tree/hostapd/hostapd.conf> and
|
||||
<https://github.com/morrownr/USB-WiFi/blob/main/home/AP_Mode/hostapd-WiFi6.conf>
|
||||
- mt76 beamforming lives in firmware (mt7622/mt7615 performance/beamforming
|
||||
discussion): <https://github.com/openwrt/mt76/issues/863>
|
||||
- Qualcomm firmware closedness (ath11k/ath12k no open firmware; regulatory +
|
||||
features firmware-enforced): ath10k mailing-list thread
|
||||
<https://ath10k.infradead.narkive.com/6bdEJZih/qca99xx-with-mu-mimo-and-beamforming>
|
||||
and CodeLinaro ath firmware <https://git.codelinaro.org/clo/ath-firmware/ath11k-firmware>
|
||||
- ath11k reports VHT beamformee spatial streams *from firmware*:
|
||||
<https://lkml.iu.edu/2210.2/09619.html>
|
||||
60
firmware/privshield/openwrt/openwrt.mk
Normal file
60
firmware/privshield/openwrt/openwrt.mk
Normal file
@@ -0,0 +1,60 @@
|
||||
# SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
#
|
||||
# OpenWRT package Makefile STUB for veil_shieldd.
|
||||
# STATUS: SYNTHETIC / L0 — package skeleton, UNTESTED ON HARDWARE / not in any feed.
|
||||
#
|
||||
# Drop this (renamed to `Makefile`) into a package dir such as
|
||||
# `package/utils/veil-shieldd/` in an OpenWRT buildroot, alongside the copied
|
||||
# core (veil_shield.{c,h}) and veil_shieldd.c under ./src/. It builds against
|
||||
# libnl-tiny (the OpenWRT netlink lib) — the same nl80211 API surface, smaller.
|
||||
#
|
||||
# This stub does NOT prove the daemon works on a device; it only wires the
|
||||
# build. No hardware validation is implied.
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=veil-shieldd
|
||||
PKG_VERSION:=0.0.0-l0
|
||||
PKG_RELEASE:=1
|
||||
PKG_LICENSE:=MIT OR Apache-2.0
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/veil-shieldd
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=VEIL compliant-waveform privacy shield (mac80211 adapter, L0)
|
||||
# libnl-tiny provides nl80211/genl; hostapd for the ctrl_iface cadence path.
|
||||
DEPENDS:=+libnl-tiny +hostapd-common
|
||||
URL:=https://github.com/ruvnet/RuView
|
||||
endef
|
||||
|
||||
define Package/veil-shieldd/description
|
||||
BUILD-ONLY / UNTESTED-ON-HARDWARE userspace adapter that drives the
|
||||
standards-compliant subset of VEIL controls reachable from OpenWRT
|
||||
(TX antenna map, hostapd-mediated sounding cadence) and links the portable
|
||||
keyed-rotation core. The full per-packet keyed rotation is blob-blocked on
|
||||
commodity Qualcomm/MediaTek parts and requires a driver/firmware patch.
|
||||
This is NOT a jammer and emits no denial energy.
|
||||
endef
|
||||
|
||||
# Build flags: point at libnl-tiny headers and the copied core.
|
||||
TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include/libnl-tiny -I$(PKG_BUILD_DIR)/src
|
||||
TARGET_LDFLAGS += -lnl-tiny -lm
|
||||
|
||||
define Build/Compile
|
||||
$(TARGET_CC) $(TARGET_CFLAGS) -std=c99 -Wall -Wextra \
|
||||
-o $(PKG_BUILD_DIR)/veil_shieldd \
|
||||
$(PKG_BUILD_DIR)/src/veil_shieldd.c \
|
||||
$(PKG_BUILD_DIR)/src/veil_shield.c \
|
||||
$(TARGET_LDFLAGS)
|
||||
endef
|
||||
|
||||
define Package/veil-shieldd/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/veil_shieldd $(1)/usr/sbin/veil_shieldd
|
||||
# TODO(hw): ship a procd init script that reads the session key from a
|
||||
# secure store (never a world-readable config) and passes -i <ifindex>.
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,veil-shieldd))
|
||||
294
firmware/privshield/openwrt/veil_shieldd.c
Normal file
294
firmware/privshield/openwrt/veil_shieldd.c
Normal file
@@ -0,0 +1,294 @@
|
||||
/* SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
*
|
||||
* veil_shieldd — OpenWRT / Linux mac80211 userspace adapter for the VEIL
|
||||
* compliant-waveform privacy shield (ADR-288 / ADR-290).
|
||||
*
|
||||
* ============================= HONESTY BANNER ==============================
|
||||
* STATUS: SYNTHETIC / L0 — BUILD-ONLY SCAFFOLD, UNTESTED ON HARDWARE.
|
||||
*
|
||||
* This daemon compiles and links the portable veil_shield core, and it issues
|
||||
* REAL nl80211/libnl calls for the small set of controls that Linux actually
|
||||
* exposes to userspace (antenna TX mask, station/BSS observation). Everything
|
||||
* that would edit the per-packet spatial mapping / precoder or the compressed
|
||||
* beamforming-feedback angles is BLOB-BLOCKED on commodity Qualcomm/MediaTek
|
||||
* parts and is marked `TODO(hw)` at the exact call site — see README.md and
|
||||
* INTEGRATION.md. Nothing here has been run against a radio. Do not read any
|
||||
* comment in this file as evidence that VEIL obfuscation reaches the air.
|
||||
*
|
||||
* COMPLIANCE: every control below is a standards-compliant configuration or
|
||||
* observation action. This daemon never transmits energy to deny a channel;
|
||||
* it only shapes/observes our own compliant frames. It is NOT a jammer.
|
||||
* ==========================================================================
|
||||
*
|
||||
* Build deps (OpenWRT: libnl-tiny; desktop: libnl-3 + libnl-genl-3):
|
||||
* pkg-config --cflags --libs libnl-genl-3.0
|
||||
* See Makefile (host build-check) and openwrt.mk (package stub).
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Real libnl / nl80211 headers. On OpenWRT these resolve to libnl-tiny; on a
|
||||
* desktop to libnl-3. If the toolchain lacks them the host Makefile still
|
||||
* builds the core object so the rotation math is validated in isolation. */
|
||||
#include <netlink/netlink.h>
|
||||
#include <netlink/genl/genl.h>
|
||||
#include <netlink/genl/ctrl.h>
|
||||
#include <linux/nl80211.h>
|
||||
|
||||
#include "veil_shield.h"
|
||||
|
||||
/* ---- Tunables (compliant, conservative defaults) ---------------------- */
|
||||
#define VEIL_DEFAULT_PASSES 96u /* matches core default (ADR-290) */
|
||||
#define VEIL_CADENCE_JITTER_MIN_MS 20 /* NDP sounding cadence jitter floor */
|
||||
#define VEIL_CADENCE_JITTER_MAX_MS 400 /* ... and ceiling (stays in-spec) */
|
||||
|
||||
/* ---- Daemon context --------------------------------------------------- */
|
||||
struct veil_ctx {
|
||||
struct nl_sock *sock; /* generic-netlink socket to nl80211 */
|
||||
int family; /* resolved "nl80211" genl family id */
|
||||
int ifindex;/* target AP interface (e.g. phy0-ap0) */
|
||||
uint64_t key; /* shared session key for the keyed rotation */
|
||||
size_t passes; /* Givens passes */
|
||||
volatile sig_atomic_t running;
|
||||
};
|
||||
|
||||
static struct veil_ctx g_ctx;
|
||||
|
||||
static void on_signal(int sig) { (void)sig; g_ctx.running = 0; }
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* nl80211 bring-up — all REAL libnl-genl-3 API names. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int veil_nl_connect(struct veil_ctx *c) {
|
||||
c->sock = nl_socket_alloc();
|
||||
if (!c->sock) {
|
||||
fprintf(stderr, "veil: nl_socket_alloc failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (genl_connect(c->sock)) {
|
||||
fprintf(stderr, "veil: genl_connect failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
c->family = genl_ctrl_resolve(c->sock, "nl80211");
|
||||
if (c->family < 0) {
|
||||
fprintf(stderr, "veil: genl_ctrl_resolve(nl80211) failed: %d\n",
|
||||
c->family);
|
||||
return c->family;
|
||||
}
|
||||
/* Observe MLME events (auth/assoc, and — where the driver forwards them —
|
||||
* action-frame notifications). Real multicast group name is "mlme". */
|
||||
int grp = genl_ctrl_resolve_grp(c->sock, "nl80211", "mlme");
|
||||
if (grp >= 0) {
|
||||
(void)nl_socket_add_membership(c->sock, grp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* CONTROL 1 (FEASIBLE): TX antenna-map perturbation. */
|
||||
/* Rotating the allowed TX antenna bitmap changes the static spatial */
|
||||
/* mapping the PHY uses, coarsely perturbing the CSI a sensor observes. */
|
||||
/* This is a genuinely userspace-reachable, compliant knob. */
|
||||
/* NL80211_CMD_SET_WIPHY + NL80211_ATTR_WIPHY_ANTENNA_TX / _RX */
|
||||
/* NOTE: many drivers only accept this while the phy is DOWN, and only on */
|
||||
/* symmetric masks — validate per driver. Coarse, not the keyed rotation. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int veil_set_tx_antenna_mask(struct veil_ctx *c,
|
||||
uint32_t tx_mask, uint32_t rx_mask) {
|
||||
struct nl_msg *msg = nlmsg_alloc();
|
||||
if (!msg) return -ENOMEM;
|
||||
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, c->family, 0, 0,
|
||||
NL80211_CMD_SET_WIPHY, 0);
|
||||
/* wiphy is addressed via the interface index on most drivers. */
|
||||
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, (uint32_t)c->ifindex);
|
||||
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_ANTENNA_TX, tx_mask);
|
||||
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_ANTENNA_RX, rx_mask);
|
||||
int ret = nl_send_auto(c->sock, msg);
|
||||
nlmsg_free(msg);
|
||||
if (ret < 0) return ret;
|
||||
return nl_recvmsgs_default(c->sock); /* consume ACK/ERR */
|
||||
nla_put_failure:
|
||||
nlmsg_free(msg);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* CONTROL 2 (FEASIBLE, indirect): NDP sounding-cadence randomization. */
|
||||
/* mac80211/driver decides when to send NDP Announcement + NDP. There is */
|
||||
/* NO stable nl80211 attribute to set the sounding period directly, so the */
|
||||
/* compliant lever from userspace is hostapd's advertised sounding */
|
||||
/* capability and dimensions, toggled/rewritten over the hostapd ctrl */
|
||||
/* interface (RECONFIGURE / SET). We jitter the *offered* cadence. */
|
||||
/* */
|
||||
/* TODO(hw): there is no nl80211 "set sounding interval" command. Confirm */
|
||||
/* against hostapd ctrl_iface docs; the direct per-NDP timer lives in */
|
||||
/* driver/firmware. See INTEGRATION.md §2. Cite: */
|
||||
/* https://w1.fi/cgit/hostap/tree/hostapd/hostapd.conf */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static unsigned veil_next_cadence_ms(struct veil_ctx *c) {
|
||||
/* Derive jitter deterministically from the session key stream so the
|
||||
* paired receiver can anticipate the schedule (compliant, not random
|
||||
* spraying). Reuses the core SplitMix64 for byte-identical behavior. */
|
||||
static veil_rng r;
|
||||
static int seeded = 0;
|
||||
if (!seeded) { veil_rng_seed(&r, c->key ^ 0xCADE11CEULL); seeded = 1; }
|
||||
unsigned span = VEIL_CADENCE_JITTER_MAX_MS - VEIL_CADENCE_JITTER_MIN_MS;
|
||||
return VEIL_CADENCE_JITTER_MIN_MS +
|
||||
(unsigned)(veil_rng_next_f32(&r) * (float)span);
|
||||
}
|
||||
|
||||
static int veil_randomize_sounding_cadence(struct veil_ctx *c) {
|
||||
unsigned ms = veil_next_cadence_ms(c);
|
||||
/* TODO(hw): push `ms` into the offered sounding cadence. On OpenWRT the
|
||||
* realistic path is the hostapd ctrl_iface (UNIX socket at
|
||||
* /var/run/hostapd/<iface>): rewrite he/vht sounding-dimension or toggle
|
||||
* beamformer capability and RECONFIGURE. mac80211 has no direct knob.
|
||||
* This function currently only computes the schedule. */
|
||||
fprintf(stderr, "veil: [feasible/indirect] next sounding jitter = %u ms "
|
||||
"(TODO(hw): apply via hostapd ctrl_iface)\n", ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* CONTROL 3 (MOSTLY BLOB-BLOCKED): MU-MIMO group shuffling. */
|
||||
/* The MU group definition + steering matrices are computed and applied in */
|
||||
/* the WiFi MCU firmware on mt76 (mt7915) and all ath1x parts. There is no */
|
||||
/* generic nl80211 command to reshuffle MU groups. Only a vendor subcmd */
|
||||
/* (NL80211_CMD_VENDOR) on a driver that chose to expose one could do it. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int veil_shuffle_mumimo_groups(struct veil_ctx *c) {
|
||||
(void)c;
|
||||
/* TODO(hw): requires NL80211_CMD_VENDOR + a driver-specific
|
||||
* NL80211_ATTR_VENDOR_ID / _SUBCMD / _DATA that does not exist upstream
|
||||
* for mt76/ath. Without a driver+firmware patch this is unreachable.
|
||||
* See INTEGRATION.md §3. Left as an explicit no-op, not a fake success. */
|
||||
fprintf(stderr, "veil: [blob-blocked] MU-MIMO group shuffle needs a "
|
||||
"vendor subcmd / firmware patch (TODO(hw))\n");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* CONTROL 4 (BLOB-BLOCKED on commodity AP silicon): the keyed rotation. */
|
||||
/* This is the actual VEIL transform — a keyed Givens rotation on the fine */
|
||||
/* subspace of the compressed beamforming feedback (the phi/psi angles), */
|
||||
/* or equivalently a unitary Q on the LTF spatial mapping. On mt76/ath the */
|
||||
/* feedback report is generated and the precoder applied inside firmware, */
|
||||
/* so userspace cannot edit it. This function shows WHERE the core plugs */
|
||||
/* in for the platforms that CAN reach the buffer (openwifi FPGA datapath, */
|
||||
/* Nexmon Broadcom patch) — it operates on a caller-supplied fine block. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int veil_apply_keyed_rotation(struct veil_ctx *c,
|
||||
float *fine, size_t n) {
|
||||
if (!fine || n < 2) return -EINVAL;
|
||||
/* Pure, orthogonal, energy-preserving (the "not jamming" invariant). */
|
||||
float before = veil_l2_norm(fine, n);
|
||||
veil_shield_apply(fine, n, c->key, c->passes);
|
||||
float after = veil_l2_norm(fine, n);
|
||||
/* TODO(hw): on OpenWRT there is NO userspace/mac80211 hook that hands us
|
||||
* this buffer before TX. Reaching it requires a driver+firmware patch
|
||||
* (mt76 MCU / ath) to expose the pre-precoder V/steering matrix, OR use
|
||||
* the openwifi (FPGA) or Nexmon adapters. See INTEGRATION.md §4.
|
||||
* We only prove the math is invariant here; nothing goes on air. */
|
||||
fprintf(stderr, "veil: [blob-blocked path] rotated %zu coeffs, "
|
||||
"L2 %.6f -> %.6f (delta %.2e; must be ~0)\n",
|
||||
n, before, after, (double)(after - before));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Event loop: watch for sensing-solicitation cadence. */
|
||||
/* We register interest in MLME/frame events. On commodity drivers the raw */
|
||||
/* NDP Announcement is NOT forwarded to userspace, so honest detection of */
|
||||
/* an *external* sensing solicitation needs monitor-mode capture or a */
|
||||
/* driver notification that does not exist upstream — marked TODO(hw). */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int veil_event_cb(struct nl_msg *msg, void *arg) {
|
||||
struct veil_ctx *c = (struct veil_ctx *)arg;
|
||||
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
|
||||
switch (gnlh->cmd) {
|
||||
case NL80211_CMD_FRAME:
|
||||
/* TODO(hw): parse NL80211_ATTR_FRAME; classify VHT/HE compressed
|
||||
* beamforming action (category 21/30) or NDPA to measure solicitation
|
||||
* cadence. Requires the driver to forward these frames (registered via
|
||||
* NL80211_CMD_REGISTER_FRAME / monitor). Not guaranteed upstream. */
|
||||
(void)veil_randomize_sounding_cadence(c);
|
||||
break;
|
||||
case NL80211_CMD_NEW_STATION:
|
||||
case NL80211_CMD_DEL_STATION:
|
||||
/* Membership churn changes MU grouping surface. */
|
||||
(void)veil_shuffle_mumimo_groups(c);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NL_SKIP;
|
||||
}
|
||||
|
||||
static void usage(const char *p) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s -i <ifindex> [-k <key_hex>] [-p <passes>]\n"
|
||||
" BUILD-ONLY / UNTESTED-ON-HARDWARE. See README.md.\n", p);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
memset(&g_ctx, 0, sizeof(g_ctx));
|
||||
g_ctx.key = 0xA5A5A5A5A5A5A5A5ULL; /* placeholder; real key from keystore */
|
||||
g_ctx.passes = VEIL_DEFAULT_PASSES;
|
||||
g_ctx.ifindex = -1;
|
||||
g_ctx.running = 1;
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "i:k:p:h")) != -1) {
|
||||
switch (opt) {
|
||||
case 'i': g_ctx.ifindex = atoi(optarg); break;
|
||||
case 'k': g_ctx.key = strtoull(optarg, NULL, 16); break;
|
||||
case 'p': g_ctx.passes = (size_t)strtoul(optarg, NULL, 10); break;
|
||||
case 'h': default: usage(argv[0]); return (opt == 'h') ? 0 : 2;
|
||||
}
|
||||
}
|
||||
if (g_ctx.ifindex < 0) { usage(argv[0]); return 2; }
|
||||
|
||||
fprintf(stderr, "veil_shieldd: SYNTHETIC/L0 build-only scaffold — "
|
||||
"no RF is emitted, nothing is validated on silicon.\n");
|
||||
|
||||
signal(SIGINT, on_signal);
|
||||
signal(SIGTERM, on_signal);
|
||||
|
||||
if (veil_nl_connect(&g_ctx)) return 1;
|
||||
|
||||
/* Install the event callback (valid-message path). */
|
||||
nl_socket_modify_cb(g_ctx.sock, NL_CB_VALID, NL_CB_CUSTOM,
|
||||
veil_event_cb, &g_ctx);
|
||||
nl_socket_disable_seq_check(g_ctx.sock); /* required for multicast events */
|
||||
|
||||
/* Self-check the one genuinely feasible active control at startup. Comment
|
||||
* this out on a live AP; it may bounce the radio depending on the driver.
|
||||
* (void)veil_set_tx_antenna_mask(&g_ctx, 0x3, 0x3); */
|
||||
(void)veil_set_tx_antenna_mask;
|
||||
|
||||
/* Prove the linked core is byte-consistent (no radio involved). */
|
||||
{
|
||||
float demo[8] = {1,0,0,0,0,0,0,0};
|
||||
(void)veil_apply_keyed_rotation(&g_ctx, demo, 8);
|
||||
veil_shield_recover(demo, 8, g_ctx.key, g_ctx.passes);
|
||||
fprintf(stderr, "veil: recover round-trip demo[0]=%.6f (expect ~1.0)\n",
|
||||
(double)demo[0]);
|
||||
}
|
||||
|
||||
while (g_ctx.running) {
|
||||
int r = nl_recvmsgs_default(g_ctx.sock);
|
||||
if (r < 0 && r != -NLE_AGAIN) {
|
||||
fprintf(stderr, "veil: nl_recvmsgs_default: %d\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nl_socket_free(g_ctx.sock);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user