LAN BAT / kestrel

rust-rewrite branch v0.2.1 · GPL-3.0 · OpenWrt stable (opkg) and snapshots (apk)

Trust zones for your router. Now one native package.

kestrel splits your WiFi into isolated networks for guests, IoT gadgets and everything you don't trust, then gives you a say over every device that joins and every domain it reaches. The Rust rewrite turns the original shell toolkit into a single .apk or .ipk: a fast CGI binary for the router UI and a parallel blocklist resolver, cross-compiled for most router CPUs.

ntfy · kestrelnow

New device on untrusted

Espressif 7c:df:a1 asked to join. Looks like it was “kitchen plug” before its MAC changed.

ApproveDeny
ntfy · kestrel2m

“kitchen plug” wants api.tuya.com

Blocked until you allow it. Route via WAN or a VPN tier.

An illustration of the approval flow, not a screenshot.

Why rewrite it in Rust?

The shell toolkit on main still works. The rewrite moves the router UI and blocklist handling into compiled Rust, ships them as a real OpenWrt package, and adds features the shell version never had.

main · shell toolkitrust-rewrite · native package
Installgit clone + install scriptsapk add / opkg install, then the same scripts for your networks
Router UIShell CGI scriptskestreld, one compiled CGI binary with typed templates
BlocklistsShell resolutionnft-resolve: eight formats, parallel DNS, atomic nft updates
DevicesKnown by MAC addressRecognised across privacy-MAC rotation by fingerprint
VPN routingBy domain listBy domain list, or per device and per rule from the device page
TestingOn the routerUnit tests plus a QEMU OpenWrt VM with virtual WiFi radios

No daemon. No extra port. No reverse proxy.

kestreld is a CGI binary that uhttpd already knows how to run. Each request starts it fresh, so it uses no memory while idle, and uhttpd keeps handling HTTPS and authentication exactly as it does for LuCI.

clientYour browserdashboard, device pages, approval links from notifications
routeruhttpdHTTPS and auth as usual; /www/cgi-bin/* symlinks
per requestkestreldRust, askama templates; status page cached for 5 s
systemfw4 · dnsmasq · hostapdnft counters, WireGuard peers, DHCP leases, neighbour table

Routes served by kestreld

RouteWhat it does
GET /cgi-bin/statusLive dashboard: WiFi clients, traffic counters, WireGuard peers, DHCP leases
GET /cgi-bin/networkDetail page for one isolated network
GET|POST /cgi-bin/deviceLabel, rate-limit, approve or revoke rules, delete a device
GET /cgi-bin/identityFingerprint and label history for a device across MAC rotations
GET|POST /cgi-bin/approve-joinApprove, deny or label a device waiting to join
GET|POST /cgi-bin/approve-accessGrant temporary LAN ↔ isolated access
GET /cgi-bin/qrWiFi QR code as SVG
POST /cgi-bin/rotate-passwordRotate the WiFi password and regenerate the QR

For local development, kestreld 8080 runs the same pages as a standalone HTTP server.

What you get

networks

Isolated WiFi networks

  • One config file per network deploys its own subnet, firewall zone, DNS policy and rate limit
  • Join approval: new devices stay offline until you tap Approve on a push notification
  • Device fingerprinting from DHCP, WiFi and mDNS signals recognises a device after its MAC rotates. It only suggests a match; you confirm it
  • Per-device outbound control: each approved device must be allowed every domain or IP it reaches
  • Approval in both directions between LAN and isolated networks, with rules that expire on their own
  • Access hours, bandwidth alerts, temporary port forwards, password rotation with a live QR code
  • WPA3/WPA2, dual-band, IPv6, VLAN trunking
split-routing

Domain-based VPN routing

  • Send chosen domains or categories through WireGuard without moving the default gateway
  • Several independent VPN tiers, each with its own categories, local lists and remote sources
  • Route one device's approved domain through a tier straight from its device page
  • Isolated networks never use the tunnel: their bridges are excluded from the VPN mark chain
nft-resolve

Blocklist resolver

  • Reads Adblock, dnsmasq, hosts, RPZ, Unbound, ipset, clash and plain-domain lists
  • Resolves in parallel and applies the result to nftables sets atomically
alerts

Know what changed

  • ntfy.sh push notifications for nearly every state change, plus a daily digest
  • Per-device DNS history, flagged against your adblock list
  • Pending connections flagged against banIP threat-intelligence feeds
  • Live dashboard of devices, traffic, VPN health, pending approvals and port forwards
  • A workaround for the MediaTek Filogic WiFi driver bug that drops virtual APs

Install from a release

You need an OpenWrt router with fw4 / nftables and SSH access as root. Pick the package for your CPU from the latest release; apk info --print-arch or opkg print-architecture tells you which.

  1. Install the package

    Snapshot builds use apk; stable releases use opkg.

    # OpenWrt snapshot
    apk add --allow-untrusted /tmp/kestrel-*.aarch64.apk
    
    # OpenWrt stable
    opkg install /tmp/kestrel_*_aarch64_cortex-a53.ipk
  2. Clone the network scripts onto the router

    git clone -b rust-rewrite https://github.com/lanbat/openwrt-kestrel /root/openwrt-kestrel
    cd /root/openwrt-kestrel
  3. Describe and install a network

    Set at least WIFI_KEY, SSID and SUBNET. Repeat with untrusted.conf for IoT.

    cp networks/configs/guest.conf.example networks/configs/guest.conf
    vi networks/configs/guest.conf
    sh networks/install.sh networks/configs/guest.conf
  4. Open the dashboard

    It is served by the uhttpd you already have, at https://<router>/cgi-bin/status. Optionally, put VPN tiers in /etc/kestrel/split-routing/ and run sh split-routing/install.sh.

Prebuilt packages

Package archTypical hardware
aarch64MediaTek Filogic, Qualcomm IPQ50xx/60xx/807x, Rockchip, mvebu
x86_64PC Engines APU, Protectli, VMs
arm_cortex-a7IPQ40xx, IPQ806x and other ARMv7 boards
arm_arm1176jzf-sOlder ARMv6 boards
riscv64SiFive and StarFive boards

MIPS (ath79, ramips) can be built from source with make package CROSS_TARGET=mipsel-unknown-linux-musl …; prebuilt MIPS packages are best effort. 32-bit PowerPC is not supported.

Coming from the old extra-networks package? Remove it first with apk del extra-networks or opkg remove extra-networks.

Hack on it without a router

The test rig boots a real OpenWrt image in QEMU with mac80211_hwsim virtual radios, so hostapd, fw4, dnsmasq and kestreld run their real code paths against a virtual WiFi client.

Boot the test router

Needs QEMU with KVM, Docker and cross.

test/qemu/setup.sh
test/qemu/provision.sh
test/qemu/deploy.sh test/qemu/configs/guest.conf \
                    test/qemu/configs/untrusted.conf
# then open http://127.0.0.1:8080/cgi-bin/status

Build and ship to a real router

Cross-compiles both binaries, packages them and installs over SSH, detecting apk or opkg.

make build                      # aarch64 by default
make package                    # .apk and .ipk
make deploy ROUTER=192.168.1.1

Read CONTRIBUTING.md for the testing hierarchy and shell-to-Rust porting conventions, and test/qemu for what the VM does and does not cover.

Try the rewrite, tell us what breaks

The rust-rewrite branch is where kestrel's releases come from today. Issues and pull requests are welcome, especially from people with hardware we don't have.

Open an issue