Files
fips/packaging/freebsd/README.md
T
fr34akyandJohnathan Corgan d0dcb40958 FreeBSD support: daemon, TUN datapath, .fips DNS, and native pkg packaging
Adds FreeBSD as a supported platform. The daemon, fipsctl, TUN datapath and
DNS integration build and run there, with a native pkg and an rc.d service.

The one piece of genuinely new datapath logic is the TUN framing. FreeBSD's
tun rejects every non-IPv4 packet with EAFNOSUPPORT unless TUNSIFHEAD is set,
so nothing IPv6 can be sent at all; with it set, every frame carries a 4-byte
network-order address-family prefix the way macOS utun does. The ioctl is
issued at device creation and the prefix is stripped on read, which gives
callers the same raw-IP contract as Linux and macOS. A frame carrying only
the header reads as zero bytes and the reader loops treat it as nothing to
do. The address family is now taken from libc rather than hardcoded, because
AF_INET6 is 30 on Darwin and 28 on FreeBSD.

The reader shutdown path, the writer's address-family header and the
supervisor's shutdown pipe were all macOS-only and are now shared with
FreeBSD, since neither platform wakes a blocked read when the interface goes
down. Linux continues to rely on interface deletion.

mdns-sd moves from 0.19 to 0.20 for socket-pktinfo 0.4.1, the first release
that builds on FreeBSD, which uses IP_RECVDSTADDR and IP_RECVIF instead of
Linux-style IP_PKTINFO. This is the only change here that affects every
platform rather than just the new one.

The config, ACL, hosts and keygen path constants now treat FreeBSD the same
as macOS, since both install under /usr/local/etc/fips. Those constants
arrived separately on maint and are merged here rather than duplicated: the
predicates widen to cover FreeBSD, the platform-gated tests widen with them,
and keygen keeps reading the shared SYSTEM_CONFIG_DIR constant rather than
reintroducing a literal.

Co-authored-by: Johnathan Corgan <johnathan@corganlabs.com>
2026-08-09 13:51:20 +00:00

4.0 KiB

FIPS FreeBSD packaging

Builds a native FreeBSD .pkg shipping fips, fipsctl, fipstop, rc.d services, and .fips DNS integration. fips-gateway is excluded (its NAT backend is nftables, Linux-only).

Platform notes: the Ethernet and BLE transports are not available on FreeBSD (UDP, TCP, Tor, and Nym are). The UDP datapath deliberately uses the portable single-packet receive loop — FreeBSD's recvmmsg(2) is a libc loop over recvmsg, not a kernel batch, so the Linux/macOS batched arm would gain nothing — and the connected-UDP fast path is not compiled pending FreeBSD-specific SO_REUSEPORT validation.

Build

./packaging/freebsd/build-pkg.sh            # cargo build --release + pkg create
./packaging/freebsd/build-pkg.sh --no-build # package existing release binaries

Output: deploy/fips-<version>-freebsd-<arch>.pkg (e.g. fips-0.5.0.dev-freebsd-amd64.pkg — pkg versions cannot contain -).

Install

pkg add ./deploy/fips-0.5.0.dev-freebsd-amd64.pkg
cp /usr/local/etc/fips/fips.yaml.sample /usr/local/etc/fips/fips.yaml  # then edit
sysrc fips_enable=YES fips_dns_enable=YES
service fips start
service fips_dns start
fipsctl show status

Config installs sample-style (@sample semantics via manifest scripts), so an edited fips.yaml survives upgrade/removal. fips.yaml is installed 0600 — it may hold the node's private key (nsec:). The daemon runs under daemon(8) with pidfile /var/run/fips/fips.pid and logs to /var/log/fips.log (rc.conf knobs: fips_config, fips_flags, fips_logfile).

The package creates a fips group; members can run fipsctl and fipstop without root (pw groupmod fips -m <user>, then re-login). On pkg upgrade the services are stopped before the binaries are replaced and started again afterwards if enabled; on pkg delete they are stopped and the .fips resolver drop-in is removed.

.fips DNS integration

The daemon answers .fips queries on [::1]:5354. fips_dns points the system resolver's fips. zone there; backends tried in order: base local_unbound (drop-in /var/unbound/conf.d/fips.conf), pkg unbound, pkg dnsmasq. The unbound drop-in must (and does) set:

  • do-not-query-localhost: no — unbound's default silently refuses loopback forwarders, SERVFAILing every .fips query.
  • do-ip6: yes — the daemon binds ::1 only.
  • domain-insecure: "fips." — the zone is unsigned.

One-time host resolver setup (NOT automated)

The package configures the fips. zone only; making the local resolver the system resolver is an operator decision. On a typical box:

sysrc local_unbound_enable=YES
local-unbound-setup 1.1.1.1 9.9.9.9   # explicit upstreams — see below
service local_unbound restart

Field-tested caveats (fips-dns-setup detects and warns about each):

  • /etc/resolv.conf must list a loopback nameserver (ideally only 127.0.0.1), or nothing ever queries unbound and .fips cannot resolve.
  • Do not use a home-router DNS proxy as unbound's upstream. Many CPE forwarders are EDNS-broken; unbound always sends EDNS, so every public query SERVFAILs. Forward to real resolvers (ISP or public).
  • Remove options edns0 from /etc/resolv.conf if present, and set resolv_conf_options="" in /etc/resolvconf.conf so resolvconf(8) does not re-add it — against an EDNS-broken router it breaks libc resolution outright.
  • Never run local-unbound-setup with no arguments while resolv.conf already points at 127.0.0.1 — it snapshots that as upstream and forwards unbound to itself.

Debugging

drill -p 5354 <npub>.fips @::1 AAAA   # daemon directly (bypasses unbound)
drill <npub>.fips AAAA                # full chain; SERVER: must be 127.0.0.1
cat /var/run/fips/dns-backend         # which backend fips_dns configured

The TUN interface gets a kernel-assigned name (tun0, tun1, ...), like utun on macOS, and is destroyed automatically when the daemon exits. ifconfig <name> prints Opened by PID <n> for the process holding a tun device.