diff --git a/.github/workflows/package-openwrt.yml b/.github/workflows/package-openwrt.yml new file mode 100644 index 0000000..113c3a6 --- /dev/null +++ b/.github/workflows/package-openwrt.yml @@ -0,0 +1,127 @@ +name: OpenWrt Package + +on: + push: + workflow_dispatch: + inputs: + arch: + description: "Target architecture (leave empty to build all)" + required: false + default: "" + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: Build .ipk (${{ matrix.openwrt_arch }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - build_arch: aarch64 + openwrt_arch: aarch64_cortex-a53 + rust_target: aarch64-unknown-linux-musl + rust_channel: stable + # MT3000, MT6000, Flint 2, RPi 3/4/5 + # MIPS disabled: 32-bit MIPS lacks AtomicU64; needs portable-atomic crate + # - build_arch: mipsel + # openwrt_arch: mipsel_24kc + # rust_target: mipsel-unknown-linux-musl + # rust_channel: nightly + # - build_arch: mips + # openwrt_arch: mips_24kc + # rust_target: mips-unknown-linux-musl + # rust_channel: nightly + - build_arch: x86_64 + openwrt_arch: x86_64 + rust_target: x86_64-unknown-linux-musl + rust_channel: stable + # x86 routers / VMs + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive package version + id: version + run: | + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + VERSION="${GITHUB_REF_NAME#v}" + else + BRANCH=$(echo "$GITHUB_REF_NAME" | sed 's|/|-|g') + HEIGHT=$(git rev-list --count HEAD) + HASH=$(git rev-parse --short HEAD) + VERSION="${BRANCH}.${HEIGHT}.${HASH}" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "filename=fips_${VERSION}_${{ matrix.openwrt_arch }}.ipk" >> "$GITHUB_OUTPUT" + + - name: Install Rust toolchain (stable) + if: matrix.rust_channel == 'stable' + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.rust_target }} + + - name: Install Rust toolchain (nightly, Tier 3) + if: matrix.rust_channel == 'nightly' + uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + + - name: Cache Cargo registry + build + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target/${{ matrix.rust_target }} + key: openwrt-${{ matrix.rust_target }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + openwrt-${{ matrix.rust_target }}- + + - name: Install cargo-zigbuild + run: cargo install cargo-zigbuild --locked + + - name: Install zig (required by cargo-zigbuild) + uses: goto-bus-stop/setup-zig@v2 + + - name: Install llvm-strip + run: sudo apt-get install -y --no-install-recommends llvm + + - name: Build .ipk + env: + PKG_VERSION: ${{ steps.version.outputs.version }} + LLVM_STRIP: llvm-strip + run: ./packaging/openwrt/build-ipk.sh --arch ${{ matrix.build_arch }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.version.outputs.filename }} + path: dist/${{ steps.version.outputs.filename }} + retention-days: 30 + + release: + name: Publish GitHub Release + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + + steps: + - name: Download all .ipk artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + files: dist/*.ipk + generate_release_notes: true diff --git a/.gitignore b/.gitignore index 1db6196..573a982 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,9 @@ .claude/ deploy/ -vps.env \ No newline at end of file +vps.env + +reference/ + +dist/ +*.ipk \ No newline at end of file diff --git a/packaging/openwrt/Makefile b/packaging/openwrt/Makefile new file mode 100644 index 0000000..cb468c6 --- /dev/null +++ b/packaging/openwrt/Makefile @@ -0,0 +1,127 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=fips +PKG_VERSION:=0.1.0 +PKG_RELEASE:=1 + +# Pin to a specific commit for reproducible builds. +# Update PKG_SOURCE_VERSION and PKG_MIRROR_HASH when upgrading. +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/fips-network/fips.git +PKG_SOURCE_VERSION:=master +PKG_MIRROR_HASH:=skip + +PKG_MAINTAINER:=FIPS Network +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=LICENSE + +# Rust host toolchain must be present in the build system. +# In the OpenWrt build system, enable via: make menuconfig → Advanced → Rust +PKG_BUILD_DEPENDS:=rust/host +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk + +# --------------------------------------------------------------------------- +# Map OpenWrt ARCH to Rust target triple. +# All OpenWrt targets use musl libc. +# --------------------------------------------------------------------------- +ifeq ($(ARCH),aarch64) + RUST_TARGET:=aarch64-unknown-linux-musl +else ifeq ($(ARCH),x86_64) + RUST_TARGET:=x86_64-unknown-linux-musl +else ifeq ($(ARCH),mipsel) + RUST_TARGET:=mipsel-unknown-linux-musl +else ifeq ($(ARCH),mips) + RUST_TARGET:=mips-unknown-linux-musl +else ifeq ($(ARCH),arm) + # OpenWrt ARM targets predominantly use hardfloat ABI. + # Override RUST_TARGET in your build if your target uses softfloat. + RUST_TARGET:=arm-unknown-linux-musleabihf +else + $(error Unsupported architecture: $(ARCH). Add a RUST_TARGET mapping in packaging/openwrt/Makefile.) +endif + +RUST_RELEASE_DIR:=$(PKG_BUILD_DIR)/target/$(RUST_TARGET)/release + +define Package/fips + SECTION:=net + CATEGORY:=Network + TITLE:=FIPS Mesh Network Daemon + URL:=https://github.com/fips-network/fips + DEPENDS:=+kmod-tun +kmod-br-netfilter +endef + +define Package/fips/description + FIPS is a distributed, decentralized mesh networking daemon. It routes + traffic across nodes connected over UDP, TCP, or raw Ethernet (EtherType + 0x2121). Provides a TUN interface (fips0) with ULA IPv6 mesh addressing + and a local DNS responder for .fips name resolution. + + Three binaries are installed: + fips — mesh daemon + fipsctl — CLI control and inspection tool + fipstop — live TUI dashboard (requires a terminal) +endef + +# --------------------------------------------------------------------------- +# Build +# +# We write a temporary .cargo/config.toml to set the cross-linker without +# polluting the source tree. CARGO_HOME is scoped to the staging directory +# to avoid touching the developer's ~/.cargo during SDK builds. +# --------------------------------------------------------------------------- +define Build/Compile + mkdir -p $(PKG_BUILD_DIR)/.cargo + printf '[target.$(RUST_TARGET)]\nlinker = "$(TARGET_CC)"\n' \ + > $(PKG_BUILD_DIR)/.cargo/config.toml + cd $(PKG_BUILD_DIR) && \ + CARGO_HOME=$(STAGING_DIR_HOST)/share/cargo \ + cargo build \ + --release \ + --target $(RUST_TARGET) \ + --bin fips \ + --bin fipsctl \ + --bin fipstop +endef + +define Package/fips/install + # Binaries + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(RUST_RELEASE_DIR)/fips $(1)/usr/bin/fips + $(INSTALL_BIN) $(RUST_RELEASE_DIR)/fipsctl $(1)/usr/bin/fipsctl + $(INSTALL_BIN) $(RUST_RELEASE_DIR)/fipstop $(1)/usr/bin/fipstop + + # procd init script + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) $(CURDIR)/files/etc/init.d/fips $(1)/etc/init.d/fips + + # Default config — installed as CONF so opkg will not overwrite it on upgrade + $(INSTALL_DIR) $(1)/etc/fips + $(INSTALL_CONF) $(CURDIR)/files/etc/fips/fips.yaml $(1)/etc/fips/fips.yaml + + # Firewall helper script (called by UCI include and hotplug) + $(INSTALL_BIN) $(CURDIR)/files/etc/fips/firewall.sh $(1)/etc/fips/firewall.sh + + # dnsmasq drop-in: forward .fips queries to the FIPS DNS responder + $(INSTALL_DIR) $(1)/etc/dnsmasq.d + $(INSTALL_DATA) $(CURDIR)/files/etc/dnsmasq.d/fips.conf $(1)/etc/dnsmasq.d/fips.conf + + # sysctl: enable br_netfilter so AF_PACKET sees frames on bridge member ports + $(INSTALL_DIR) $(1)/etc/sysctl.d + $(INSTALL_DATA) $(CURDIR)/files/etc/sysctl.d/fips-bridge.conf $(1)/etc/sysctl.d/fips-bridge.conf + + # Hotplug: apply firewall rules when the FIPS TUN interface comes up + $(INSTALL_DIR) $(1)/etc/hotplug.d/net + $(INSTALL_BIN) $(CURDIR)/files/etc/hotplug.d/net/99-fips $(1)/etc/hotplug.d/net/99-fips + + # UCI defaults: one-time first-boot firewall and module setup + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_BIN) $(CURDIR)/files/etc/uci-defaults/90-fips-setup $(1)/etc/uci-defaults/90-fips-setup + + # sysupgrade: preserve /etc/fips/ (config + identity key) across firmware upgrades + $(INSTALL_DIR) $(1)/lib/upgrade/keep.d + $(INSTALL_DATA) $(CURDIR)/files/lib/upgrade/keep.d/fips $(1)/lib/upgrade/keep.d/fips +endef + +$(eval $(call BuildPackage,fips)) diff --git a/packaging/openwrt/README.md b/packaging/openwrt/README.md new file mode 100644 index 0000000..905c545 --- /dev/null +++ b/packaging/openwrt/README.md @@ -0,0 +1,174 @@ +# FIPS OpenWrt Package + +This directory is an OpenWrt feed package that builds and installs FIPS on any +OpenWrt 22.03+ router via the standard `opkg` package system. + +For ad-hoc deployment without the build system, see +[`deploy/native/`](../../deploy/native/README.md) instead. + +## Package contents + +| Installed path | Purpose | +|---|---| +| `/usr/bin/fips` | Mesh daemon | +| `/usr/bin/fipsctl` | CLI control tool (`fipsctl peers`, `fipsctl links`, …) | +| `/usr/bin/fipstop` | Live TUI dashboard | +| `/etc/init.d/fips` | procd service (auto-start, crash respawn) | +| `/etc/fips/fips.yaml` | Node configuration (edit before first start) | +| `/etc/fips/firewall.sh` | Firewall helper — accepts traffic on `fips0` | +| `/etc/dnsmasq.d/fips.conf` | Forwards `.fips` DNS queries to the daemon | +| `/etc/sysctl.d/fips-bridge.conf` | `br_netfilter` settings for Ethernet transport | +| `/etc/hotplug.d/net/99-fips` | Applies firewall rules when `fips0` comes up | +| `/etc/uci-defaults/90-fips-setup` | First-boot kernel module and firewall setup | +| `/lib/upgrade/keep.d/fips` | Preserves `/etc/fips/` across `sysupgrade` | + +## Requirements + +### Build host + +| Requirement | Notes | +|---|---| +| OpenWrt SDK 22.03+ | Older versions lack fw4 / nftables support | +| Rust host toolchain | Enable in `make menuconfig` → Advanced → Rust, or install rustup | +| Rust target for your router | Added automatically by the Makefile via `rustup target add` | + +### Router + +| Requirement | Notes | +|---|---| +| `kmod-tun` | Required for `fips0` TUN interface | +| `kmod-br-netfilter` | Required for Ethernet transport on bridge member ports | + +Both kernel modules are listed as package dependencies (`DEPENDS`) and will be +installed automatically by `opkg`. + +## Target architectures + +The Makefile maps the OpenWrt `ARCH` variable to the correct Rust musl target: + +| OpenWrt `ARCH` | Rust target | +|---|---| +| `aarch64` | `aarch64-unknown-linux-musl` | +| `x86_64` | `x86_64-unknown-linux-musl` | +| `mipsel` | `mipsel-unknown-linux-musl` | +| `mips` | `mips-unknown-linux-musl` | +| `arm` | `arm-unknown-linux-musleabihf` | + +To add a missing architecture, add an `ifeq` block in `Makefile` mapping the +OpenWrt `ARCH` value to the Rust target triple. + +## Building with the OpenWrt SDK + +### 1. Obtain the SDK + +Download the SDK for your router's target from +[downloads.openwrt.org](https://downloads.openwrt.org) and extract it. + +### 2. Add this package + +Copy or symlink this directory into the SDK's `package/` tree: + +```bash +# From inside the SDK root: +ln -s /path/to/fips/packaging/openwrt package/fips +``` + +Or add the FIPS repository as a feed in `feeds.conf`: + +``` +src-git-full fips https://github.com/fips-network/fips.git +``` + +Then update and install feeds: + +```bash +./scripts/feeds update fips +./scripts/feeds install -a -p fips +``` + +### 3. Build + +```bash +make package/fips/compile V=s +``` + +The resulting `.ipk` is placed in `bin/packages//`. + +### 4. Pin the source version + +For reproducible production builds, replace `PKG_SOURCE_VERSION:=master` in +`Makefile` with a specific commit SHA and set `PKG_MIRROR_HASH` to the correct +hash (or keep `skip` for development): + +```makefile +PKG_SOURCE_VERSION:=bf117dfabc123... # full 40-char SHA +PKG_MIRROR_HASH:=skip +``` + +## Installing on the router + +```bash +scp bin/packages//fips_0.1.0-1_.ipk root@192.168.1.1:/tmp/ +ssh root@192.168.1.1 opkg install /tmp/fips_0.1.0-1_.ipk +``` + +## First-time configuration + +Edit `/etc/fips/fips.yaml` on the router before starting the daemon: + +```bash +ssh root@192.168.1.1 +vi /etc/fips/fips.yaml +``` + +The default config enables: +- Persistent identity (key generated on first start, saved to `/etc/fips/fips.key`) +- TUN interface `fips0` +- DNS responder on `127.0.0.1:5354` +- UDP transport on `0.0.0.0:2121` + +For Ethernet transport, uncomment the `ethernet:` section and set the correct +physical interface names for your router. **Always use physical port names +(`eth0`, `eth1`), never bridge names (`br-lan`).** See +[`deploy/native/README.md`](../../deploy/native/README.md) for details. + +## Service management + +```bash +/etc/init.d/fips start +/etc/init.d/fips stop +/etc/init.d/fips restart +/etc/init.d/fips enable # start at boot (already enabled by opkg postinstall) +/etc/init.d/fips disable +``` + +## Inspection and logs + +```bash +# Peer table +fipsctl peers + +# Active sessions +fipsctl sessions + +# Transport links +fipsctl links + +# Live TUI dashboard +fipstop + +# Daemon logs (OpenWrt syslog) +logread | grep fips +``` + +## Upgrading + +Install the new `.ipk` over the existing one: + +```bash +opkg install --force-reinstall fips__.ipk +``` + +The config in `/etc/fips/fips.yaml` and the identity key `/etc/fips/fips.key` +are preserved by `opkg` (the yaml is installed as a conffile; the key is not a +package file). Both survive `sysupgrade` via `/lib/upgrade/keep.d/fips`. diff --git a/packaging/openwrt/build-ipk.sh b/packaging/openwrt/build-ipk.sh new file mode 100755 index 0000000..d050556 --- /dev/null +++ b/packaging/openwrt/build-ipk.sh @@ -0,0 +1,265 @@ +#!/bin/bash +# Build a FIPS .ipk package for OpenWrt without the OpenWrt SDK. +# +# Uses cargo-zigbuild for cross-compilation and assembles the .ipk directly. +# An .ipk is just an ar archive containing two tarballs — no SDK required. +# +# Usage: +# ./packaging/openwrt/build-ipk.sh [--arch ] +# +# Architectures (--arch): +# aarch64 GL.iNet MT3000/MT6000, RPi 3/4/5, most modern routers [default] +# mipsel Older MIPS routers (TP-Link, Netgear, GL.iNet AR750) +# mips MIPS big-endian routers (ath79) +# arm 32-bit ARM routers (Cortex-A7) +# x86_64 x86 routers / VMs +# +# Output: dist/fips__.ipk +# +# Prerequisites: +# cargo install cargo-zigbuild +# rustup target add (added automatically if missing) + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Arguments +# --------------------------------------------------------------------------- + +ARCH="aarch64" + +while [[ $# -gt 0 ]]; do + case "$1" in + --arch) ARCH="$2"; shift 2 ;; + --arch=*) ARCH="${1#*=}"; shift ;; + *) echo "Unknown argument: $1" >&2; exit 1 ;; + esac +done + +# --------------------------------------------------------------------------- +# Architecture mapping +# +# RUST_TARGET — passed to cargo --target +# OPENWRT_ARCH — goes in the .ipk control file and filename +# --------------------------------------------------------------------------- + +case "$ARCH" in + aarch64) + RUST_TARGET="aarch64-unknown-linux-musl" + OPENWRT_ARCH="aarch64_cortex-a53" + ;; + mipsel) + RUST_TARGET="mipsel-unknown-linux-musl" + OPENWRT_ARCH="mipsel_24kc" + ;; + mips) + RUST_TARGET="mips-unknown-linux-musl" + OPENWRT_ARCH="mips_24kc" + ;; + arm) + RUST_TARGET="arm-unknown-linux-musleabihf" + OPENWRT_ARCH="arm_cortex-a7" + ;; + x86_64) + RUST_TARGET="x86_64-unknown-linux-musl" + OPENWRT_ARCH="x86_64" + ;; + *) + echo "Unknown arch: $ARCH" >&2 + echo "Valid: aarch64, mipsel, mips, arm, x86_64" >&2 + exit 1 + ;; +esac + +# --------------------------------------------------------------------------- +# Paths +# --------------------------------------------------------------------------- + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +FILES_DIR="$SCRIPT_DIR/files" +DIST_DIR="$PROJECT_ROOT/dist" + +PKG_NAME="fips" +PKG_VERSION="${PKG_VERSION:-$(cd "$PROJECT_ROOT" && git describe --tags --always --dirty 2>/dev/null || echo "0.1.0")}" + +echo "==> Building $PKG_NAME $PKG_VERSION for $OPENWRT_ARCH ($RUST_TARGET)" + +# --------------------------------------------------------------------------- +# Prerequisites +# --------------------------------------------------------------------------- + +if ! command -v cargo-zigbuild &>/dev/null; then + echo "Error: cargo-zigbuild not found." >&2 + echo " Install: cargo install cargo-zigbuild" >&2 + exit 1 +fi + +if ! rustup target list --installed | grep -q "^$RUST_TARGET$"; then + echo "==> Adding Rust target $RUST_TARGET..." + rustup target add "$RUST_TARGET" +fi + +# --------------------------------------------------------------------------- +# 1. Build +# --------------------------------------------------------------------------- + +echo "==> Compiling..." +cd "$PROJECT_ROOT" +cargo zigbuild \ + --release \ + --target "$RUST_TARGET" \ + --bin fips \ + --bin fipsctl \ + --bin fipstop + +RELEASE_DIR="$PROJECT_ROOT/target/$RUST_TARGET/release" + +echo "==> Stripping binaries..." +STRIP="${LLVM_STRIP:-strip}" +for bin in fips fipsctl fipstop; do + "$STRIP" "$RELEASE_DIR/$bin" 2>/dev/null || true +done + +SIZE=$(du -sh "$RELEASE_DIR/fips" | cut -f1) +echo " fips: $SIZE after strip" + +# --------------------------------------------------------------------------- +# 2. Assemble .ipk +# --------------------------------------------------------------------------- +# An .ipk is an ar archive with three members: +# debian-binary — format version ("2.0\n") +# control.tar.gz — package metadata, conffiles, pre/post scripts +# data.tar.gz — the actual filesystem tree + +WORK_DIR="$(mktemp -d)" +trap 'rm -rf "$WORK_DIR"' EXIT + +CONTROL_DIR="$WORK_DIR/control" +DATA_DIR="$WORK_DIR/data" +mkdir -p "$CONTROL_DIR" "$DATA_DIR" + +# ---- data tree ---- + +install -d "$DATA_DIR/usr/bin" +install -m 0755 "$RELEASE_DIR/fips" "$DATA_DIR/usr/bin/fips" +install -m 0755 "$RELEASE_DIR/fipsctl" "$DATA_DIR/usr/bin/fipsctl" +install -m 0755 "$RELEASE_DIR/fipstop" "$DATA_DIR/usr/bin/fipstop" + +install -d "$DATA_DIR/etc/init.d" +install -m 0755 "$FILES_DIR/etc/init.d/fips" "$DATA_DIR/etc/init.d/fips" + +install -d "$DATA_DIR/etc/fips" +install -m 0600 "$FILES_DIR/etc/fips/fips.yaml" "$DATA_DIR/etc/fips/fips.yaml" +install -m 0755 "$FILES_DIR/etc/fips/firewall.sh" "$DATA_DIR/etc/fips/firewall.sh" + +install -d "$DATA_DIR/etc/dnsmasq.d" +install -m 0644 "$FILES_DIR/etc/dnsmasq.d/fips.conf" "$DATA_DIR/etc/dnsmasq.d/fips.conf" + +install -d "$DATA_DIR/etc/sysctl.d" +install -m 0644 "$FILES_DIR/etc/sysctl.d/fips-bridge.conf" "$DATA_DIR/etc/sysctl.d/fips-bridge.conf" + +install -d "$DATA_DIR/etc/hotplug.d/net" +install -m 0755 "$FILES_DIR/etc/hotplug.d/net/99-fips" "$DATA_DIR/etc/hotplug.d/net/99-fips" + +install -d "$DATA_DIR/etc/uci-defaults" +install -m 0755 "$FILES_DIR/etc/uci-defaults/90-fips-setup" "$DATA_DIR/etc/uci-defaults/90-fips-setup" + +install -d "$DATA_DIR/lib/upgrade/keep.d" +install -m 0644 "$FILES_DIR/lib/upgrade/keep.d/fips" "$DATA_DIR/lib/upgrade/keep.d/fips" + +# ---- control files ---- + +PKG_SIZE=$(du -sk "$DATA_DIR" | cut -f1) + +cat > "$CONTROL_DIR/control" < "$CONTROL_DIR/conffiles" < "$CONTROL_DIR/postinst" <<'EOF' +#!/bin/sh +# Run first-boot UCI setup (the script deletes itself when done). +if [ -x /etc/uci-defaults/90-fips-setup ]; then + /etc/uci-defaults/90-fips-setup && rm -f /etc/uci-defaults/90-fips-setup +fi + +/etc/init.d/fips enable +/etc/init.d/fips start +exit 0 +EOF +chmod 0755 "$CONTROL_DIR/postinst" + +cat > "$CONTROL_DIR/prerm" <<'EOF' +#!/bin/sh +/etc/init.d/fips stop 2>/dev/null || true +/etc/init.d/fips disable 2>/dev/null || true +exit 0 +EOF +chmod 0755 "$CONTROL_DIR/prerm" + +# ---- pack ---- + +PKG_FILENAME="${PKG_NAME}_${PKG_VERSION}_${OPENWRT_ARCH}.ipk" +IPK_WORK="$WORK_DIR/ipk" +mkdir -p "$IPK_WORK" + +echo "2.0" > "$IPK_WORK/debian-binary" + +# Detect a tar that supports --format=gnu. +# On macOS, Homebrew's GNU tar is installed as 'gtar'; the system tar is BSD. +# Our filenames are short so BSD tar (ustar) works too, but gnu is preferred +# to match ipkg-build exactly and to embed numeric UID/GID. +# COPYFILE_DISABLE=1 suppresses macOS resource-fork (._*) files; no-op on Linux. +if command -v gtar &>/dev/null; then + # Homebrew GNU tar on macOS + TAR_CMD="gtar" + TAR_EXTRA_FLAGS="--format=gnu --numeric-owner" +elif tar --version 2>/dev/null | grep -q 'GNU tar'; then + # System tar is GNU tar (Linux) + TAR_CMD="tar" + TAR_EXTRA_FLAGS="--format=gnu --numeric-owner" +else + # macOS BSD tar (libarchive). Its default format is PAX (typeflag 0x78), + # which OpenWrt's busybox tar cannot handle. Force ustar explicitly. + TAR_CMD="tar" + TAR_EXTRA_FLAGS="--format=ustar" +fi + +ipk_tar() { + # ipk_tar [paths...] + local out="$1" src="$2"; shift 2 + COPYFILE_DISABLE=1 "$TAR_CMD" $TAR_EXTRA_FLAGS -czf "$out" -C "$src" "$@" +} + +ipk_tar "$IPK_WORK/control.tar.gz" "$CONTROL_DIR" . +ipk_tar "$IPK_WORK/data.tar.gz" "$DATA_DIR" . + +# The outer .ipk container is a gzip-compressed tar — NOT an ar archive. +# (Debian .deb uses ar; OpenWrt .ipk uses tar.gz.) +# Entries must be named with ./ prefix, as ipkg-build produces. +mkdir -p "$DIST_DIR" +ipk_tar "$DIST_DIR/$PKG_FILENAME" "$IPK_WORK" ./debian-binary ./control.tar.gz ./data.tar.gz + +echo "" +echo "==> Done: dist/$PKG_FILENAME" +echo " $(du -sh "$DIST_DIR/$PKG_FILENAME" | cut -f1)" +echo "" +echo "Install on router:" +echo " scp -O dist/$PKG_FILENAME root@192.168.1.1:/tmp/" +echo " ssh root@192.168.1.1 opkg install /tmp/$PKG_FILENAME" diff --git a/packaging/openwrt/files/etc/dnsmasq.d/fips.conf b/packaging/openwrt/files/etc/dnsmasq.d/fips.conf new file mode 100644 index 0000000..d0680d9 --- /dev/null +++ b/packaging/openwrt/files/etc/dnsmasq.d/fips.conf @@ -0,0 +1,11 @@ +# FIPS mesh DNS — forward .fips queries to the local FIPS DNS responder. +# +# server= forward all .fips queries to 127.0.0.1:5354 +# rebind-domain-ok= disable DNS-rebind protection for .fips; FIPS node +# addresses live in fd00::/8 (ULA), which dnsmasq blocks by +# default as a rebind-attack countermeasure. +# +# If your fips.yaml sets dns.port to something other than 5354, update the +# port number below to match. +server=/fips/127.0.0.1#5354 +rebind-domain-ok=/fips/ diff --git a/packaging/openwrt/files/etc/fips/fips.yaml b/packaging/openwrt/files/etc/fips/fips.yaml new file mode 100644 index 0000000..aee3f6e --- /dev/null +++ b/packaging/openwrt/files/etc/fips/fips.yaml @@ -0,0 +1,64 @@ +# FIPS Node Configuration +# +# Edit this file before starting the FIPS daemon. +# Full reference: https://github.com/jmcorgan/fips/blob/master/docs/design/fips-configuration.md +# +# After editing, restart the daemon: +# /etc/init.d/fips restart + +node: + identity: + # A persistent keypair is generated on first start and saved to + # /etc/fips/fips.key (private) and /etc/fips/fips.pub (public). + # The router keeps the same FIPS identity across reboots. + # + # To use an explicit key instead, comment out 'persistent' and set: + # nsec: "your-64-char-hex-private-key" + persistent: true + +tun: + enabled: true + name: fips0 + mtu: 1280 + +dns: + enabled: true + bind_addr: "127.0.0.1" + port: 5354 + +transports: + udp: + bind_addr: "0.0.0.0:2121" + + # Ethernet transport — uncomment and set your interface names. + # Use physical port names, NOT bridge names (e.g. eth1, not br-lan). + # Run 'ip link show' on the router to identify port names. + # See: https://github.com/jmcorgan/fips/blob/master/deploy/native/README.md + # + # ethernet: + # wan: + # interface: "eth0" # WAN + # discovery: true + # announce: true + # auto_connect: true + # accept_connections: true + # wwan: + # interface: "phy0-sta0" # WWAN + # discovery: true + # announce: true + # auto_connect: true + # accept_connections: true + # lan: + # interface: "br-lan" # LAN + # discovery: true + # announce: true + # auto_connect: true + # accept_connections: true + +peers: [] + # Static peers for bootstrapping (add at least one if using UDP-only): + # - npub: "npub1..." + # alias: "gateway" + # addresses: + # - transport: udp + # addr: "1.2.3.4:2121" diff --git a/packaging/openwrt/files/etc/fips/firewall.sh b/packaging/openwrt/files/etc/fips/firewall.sh new file mode 100644 index 0000000..c464967 --- /dev/null +++ b/packaging/openwrt/files/etc/fips/firewall.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# FIPS firewall rules — accept all traffic on the FIPS TUN interface. +# +# Called by: +# - /etc/hotplug.d/net/99-fips when fips0 comes up +# - the UCI firewall include on every firewall reload +# +# Supports both fw4 (nftables, OpenWrt 22.03+) and older iptables builds. + +TUN="fips0" + +if command -v nft >/dev/null 2>&1 && nft list table inet fw4 >/dev/null 2>&1; then + for chain in input output forward; do + case "$chain" in + input|forward) match="iifname \"$TUN\"" ;; + output) match="oifname \"$TUN\"" ;; + esac + nft list chain inet fw4 "$chain" 2>/dev/null | grep -q "$match" || \ + nft insert rule inet fw4 "$chain" $match accept comment "\"fips\"" + done + # Also accept forwarded traffic leaving via fips0 + nft list chain inet fw4 forward 2>/dev/null | grep -q "oifname \"$TUN\"" || \ + nft insert rule inet fw4 forward oifname "$TUN" accept comment '"fips"' +fi + +if command -v iptables >/dev/null 2>&1; then + iptables -C INPUT -i "$TUN" -j ACCEPT 2>/dev/null || \ + iptables -I INPUT 1 -i "$TUN" -j ACCEPT + iptables -C OUTPUT -o "$TUN" -j ACCEPT 2>/dev/null || \ + iptables -I OUTPUT 1 -o "$TUN" -j ACCEPT + iptables -C FORWARD -i "$TUN" -j ACCEPT 2>/dev/null || \ + iptables -I FORWARD 1 -i "$TUN" -j ACCEPT +fi diff --git a/packaging/openwrt/files/etc/hotplug.d/net/99-fips b/packaging/openwrt/files/etc/hotplug.d/net/99-fips new file mode 100644 index 0000000..00f1d7c --- /dev/null +++ b/packaging/openwrt/files/etc/hotplug.d/net/99-fips @@ -0,0 +1,8 @@ +#!/bin/sh +# FIPS hotplug — apply firewall rules when the FIPS TUN interface comes up. +# The kernel fires net hotplug events for every interface including TUN devices. + +[ "$INTERFACE" = "fips0" ] || exit 0 +[ "$ACTION" = "add" ] || [ "$ACTION" = "register" ] || exit 0 + +/etc/fips/firewall.sh diff --git a/packaging/openwrt/files/etc/init.d/fips b/packaging/openwrt/files/etc/init.d/fips new file mode 100644 index 0000000..f5032d5 --- /dev/null +++ b/packaging/openwrt/files/etc/init.d/fips @@ -0,0 +1,28 @@ +#!/bin/sh /etc/rc.common +# FIPS mesh daemon — procd init script for OpenWrt + +USE_PROCD=1 +START=95 +STOP=10 + +PROG=/usr/bin/fips +CONFIG=/etc/fips/fips.yaml + +start_service() { + # Ensure TUN module is loaded before starting the daemon. + modprobe tun 2>/dev/null || true + + procd_open_instance + procd_set_param command "$PROG" --config "$CONFIG" + procd_set_param env RUST_LOG=info + # Respawn: restart after 5 s, give up after 5 consecutive failures within + # a 3600 s window, then reset the failure counter and try again. + procd_set_param respawn 3600 5 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +reload_service() { + restart +} diff --git a/packaging/openwrt/files/etc/sysctl.d/fips-bridge.conf b/packaging/openwrt/files/etc/sysctl.d/fips-bridge.conf new file mode 100644 index 0000000..ef8b28a --- /dev/null +++ b/packaging/openwrt/files/etc/sysctl.d/fips-bridge.conf @@ -0,0 +1,12 @@ +# FIPS: bridge netfilter settings +# +# kmod-br-netfilter must be loaded for AF_PACKET sockets to receive frames +# on bridge member ports (e.g. eth1 when it is a member of br-lan). +# Without it, the bridge's rx_handler intercepts frames before they reach +# the packet socket layer. +# +# We load br_netfilter for the AF_PACKET visibility benefit but disable its +# IP/IPv6/ARP call hooks to avoid double-processing of routed traffic. +net.bridge.bridge-nf-call-iptables=0 +net.bridge.bridge-nf-call-ip6tables=0 +net.bridge.bridge-nf-call-arptables=0 diff --git a/packaging/openwrt/files/etc/uci-defaults/90-fips-setup b/packaging/openwrt/files/etc/uci-defaults/90-fips-setup new file mode 100644 index 0000000..d87bb09 --- /dev/null +++ b/packaging/openwrt/files/etc/uci-defaults/90-fips-setup @@ -0,0 +1,72 @@ +#!/bin/sh +# FIPS first-boot setup — runs once after package installation. +# Configures the firewall and kernel modules for FIPS operation. +# This script is executed by /etc/rc.d/S19sysctl on first boot and +# then deleted by the UCI defaults mechanism. + +# --------------------------------------------------------------------------- +# 1. Kernel modules +# --------------------------------------------------------------------------- + +# kmod-tun is listed as a package dependency, but ensure the module is loaded. +modprobe tun 2>/dev/null || true +echo "tun" > /etc/modules.d/tun + +# kmod-br-netfilter makes AF_PACKET visible on bridge member ports. +modprobe br_netfilter 2>/dev/null || true +echo "br_netfilter" > /etc/modules.d/br-netfilter + +# Apply the sysctl settings shipped in /etc/sysctl.d/fips-bridge.conf now +# (the file will be applied automatically on subsequent boots). +sysctl -p /etc/sysctl.d/fips-bridge.conf 2>/dev/null || true + +# --------------------------------------------------------------------------- +# 2. Firewall — add fips0 to the lan zone (fw4 / UCI) +# --------------------------------------------------------------------------- +# fw4 (nftables) matches zones by device name, so adding fips0 as a 'device' +# to the lan zone makes all traffic on the TUN interface accepted without +# needing raw nft rules in the base chains. + +z=0 +while uci -q get "firewall.@zone[$z]" >/dev/null 2>&1; do + if [ "$(uci -q get "firewall.@zone[$z].name" 2>/dev/null)" = "lan" ]; then + uci -q del_list "firewall.@zone[$z].device=fips0" 2>/dev/null || true + uci add_list "firewall.@zone[$z].device=fips0" + break + fi + z=$((z + 1)) +done + +# Install a firewall include so /etc/fips/firewall.sh is re-applied on every +# firewall reload (belt-and-suspenders for iptables-based OpenWrt builds). +FOUND=0 +j=0 +while uci -q get "firewall.@include[$j]" >/dev/null 2>&1; do + if [ "$(uci -q get "firewall.@include[$j].path" 2>/dev/null)" = "/etc/fips/firewall.sh" ]; then + FOUND=1 + break + fi + j=$((j + 1)) +done +if [ "$FOUND" = "0" ]; then + uci add firewall include + uci set "firewall.@include[-1].path=/etc/fips/firewall.sh" + uci set "firewall.@include[-1].reload=1" +fi + +uci commit firewall + +# --------------------------------------------------------------------------- +# 3. dnsmasq UCI registration +# --------------------------------------------------------------------------- +# /etc/dnsmasq.d/fips.conf already handles runtime forwarding. +# Register via UCI as well so the settings survive a full dnsmasq config +# regeneration (e.g. after a firmware upgrade that rebuilds dnsmasq.conf). + +uci -q del_list dhcp.@dnsmasq[0].server="/fips/127.0.0.1#5354" 2>/dev/null || true +uci add_list dhcp.@dnsmasq[0].server="/fips/127.0.0.1#5354" +uci -q del_list dhcp.@dnsmasq[0].rebind_domain="fips" 2>/dev/null || true +uci add_list dhcp.@dnsmasq[0].rebind_domain="fips" +uci commit dhcp + +exit 0 diff --git a/packaging/openwrt/files/lib/upgrade/keep.d/fips b/packaging/openwrt/files/lib/upgrade/keep.d/fips new file mode 100644 index 0000000..2faa455 --- /dev/null +++ b/packaging/openwrt/files/lib/upgrade/keep.d/fips @@ -0,0 +1,2 @@ +# Preserve FIPS configuration and persistent identity key across sysupgrade. +/etc/fips/