Skip to content

Repository files navigation

netprov

BLE-provisioned network configuration for headless embedded Linux. A systemd service (netprovd) advertises a GATT service that a paired client can use to list interfaces, read IP configuration, scan Wi-Fi, and set DHCP / static IPv4 / Wi-Fi credentials. Written in Rust, talks to NetworkManager over D-Bus.

The companion netprov CLI speaks the same protocol over TCP (loopback, for dev) or BLE (for production).

Architecture

flowchart TD
    Client["BLE client (netprov CLI)"]
    NM["NetworkManager (system)"]

    subgraph Daemon["netprovd (systemd service, runs as root)"]
        direction LR
        GATT["BLE GATT Server"]
        Session["Auth & Session"]
        Facade["Network Facade"]
        GATT --> Session --> Facade
    end

    Client -->|"GATT over LE (Just Works-encrypted link + app-layer HMAC auth)"| GATT
    Facade -->|D-Bus| NM
Loading

Security posture: the sensitive characteristics (ChallengeNonce, AuthResponse, Request/Response — everything but Info) require an encrypted link (encrypt_authenticated_read/encrypt_authenticated_write in crates/server/src/ble/gatt.rs). Since netprovd runs headless with no display or keyboard, it registers a no-IO-capability bluer::agent::Agent (crates/server/src/ble/server.rs), so BlueZ negotiates Just Works pairing on first access. Just Works establishes encryption but provides no man-in-the-middle protection by itself; the app-layer HMAC challenge (keyed by the pre-shared PSK) is what actually authorizes commands, so an active MITM that completes Just Works pairing still cannot issue commands without the PSK. The residual risk is that an active MITM present during pairing could potentially observe traffic before authorization completes (e.g. the ChallengeNonce). Wi-Fi credentials (Op::ConnectWifi) are only ever transmitted after the encrypted link and HMAC auth are both established. The Info characteristic (model + protocol version only, per spec §11) stays unauthenticated and unencrypted.

Five Rust crates in one workspace:

Crate Role
netprov-protocol Wire format: CBOR messages, framing, HMAC auth helpers. Transport-agnostic.
netprov-server netprovd daemon. BLE GATT driver, session state machine, NetworkFacade (mock + nmrs).
netprov-sdk Transport-agnostic ProvisioningClient trait plus BLE/TCP transport implementations, shared by the CLI and desktop app.
netprov-client netprov CLI. Connects over BLE (via --ble-peer) or TCP behind the dev-tcp feature.
netprov-app Dioxus desktop UI, gated behind the desktop feature.

Install (from deb)

Builds for amd64 and aarch64 are produced by CI as artifacts. On a target box:

sudo dpkg -i netprov_<version>_arm64.deb
sudo netprovd keygen --install        # generate and install a production PSK
sudo systemctl enable --now netprovd  # start at boot + now

The deb does not auto-start — the admin must install a production key first. packaging/netprovd.service ships with NETPROV_PRODUCTION=1 set, so the daemon refuses to start without a real key at /etc/netprov/key rather than silently falling back to the embedded dev key (the committed PSK under packaging/, used only by serve-tcp for local dev/loopback tests, where it logs a loud warning every 60 seconds).

Dev quickstart (no BLE, no NetworkManager)

The full request/response surface is exercised end-to-end over TCP against an in-memory MockFacade:

# one terminal
cp packaging/dev-key.bin /tmp/netprov-key.bin && chmod 600 /tmp/netprov-key.bin
cargo run -p netprov-server --bin netprovd -- serve-tcp --listen 127.0.0.1:9600

# another terminal
cargo run -p netprov-client --features dev-tcp --bin netprov -- \
  --key-path /tmp/netprov-key.bin --endpoint 127.0.0.1:9600 list

list, ip <iface>, wifi-status, wifi-scan, wifi-connect, set-dhcp, set-static all work against the mock.

BLE smoke test

See packaging/SMOKE-TEST.md for the two-box hardware-in-the-loop runbook.

Build matrix

cargo test --workspace                                 # default: no BLE, no NM
cargo build -p netprov-server --features live-ble      # + BLE GATT server
cargo build -p netprov-server --features live-nm       # + real NetworkManager
cargo deb -p netprov-server                            # build the .deb

live-ble implies live-nm (production BLE needs NM). live-nm-destructive gates the mutating NmrsFacade integration tests that are unsafe to run in CI.

Desktop app dev setup

The Dioxus desktop app is gated behind the desktop feature because it needs native GTK/WebKit development libraries on Linux. On Ubuntu/Debian:

sudo apt-get install -y \
  pkg-config \
  libgtk-3-dev \
  libwebkit2gtk-4.1-dev \
  libayatana-appindicator3-dev \
  libxdo-dev

Then run the BLE-first desktop app with:

cargo run -p netprov-app --features desktop

The app talks to target devices over BLE; the TCP transport remains a dev/test path for protocol regression coverage.

Testing tiers

Tier In CI? Covers
Unit tests Yes Protocol: CBOR round-trips, framing, HMAC, bounded strings. Plus proptests.
Session-layer Yes Session state machine against MockFacade.
Client↔server loopback Yes Full request/response surface via tokio::io::duplex.
Live NetworkManager Opt-in --features live-nm -- --ignored. Requires NM on the system bus.
Live BLE e2e Opt-in --features live-ble + two BLE-equipped boxes.

Spec and plans

License

MIT OR Apache-2.0.

About

BLE-provisioned network configuration for headless embedded Linux

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages