# hive-network Host-side bridge + per-agent DNS resolver — the foundation that makes container netns isolation safe to land. Configured via `services.hyperhive.network.*`; off by default during rollout. ## Why ship before netns isolation If netns isolation lands first, agent containers lose `/etc/resolv.conf` propagation from the host and DNS breaks until a separate resolver is up. Inverting the sequence — bridge + dnsmasq first, netns flip second — makes the flag day boring: the resolver endpoint is already live, agents just discover it via veth instead of shared netns. ## v1 vs v2 | feature | v1 (this PR) | v2 (after netns isolation) | |---|---|---| | bridge interface | created on host, no slave NICs | per-agent veth pairs attach | | dnsmasq binding | bridge IP (reachable via host loopback in shared netns) | bridge IP (reachable via veth in private netns) | | agent container netns | shared host | private | | agent `/etc/resolv.conf` | unchanged (host DNS) | `nameserver ` | | `address` rules target | `` (works in both modes) | unchanged from v1 | The `address` rules ship pointing at the bridge IP from v1 so the DNS contract is fixed before any container actually depends on it — minimises the things that flip on netns day. ## Container shape (where dnsmasq lives) Co-located in the existing `hive-gateway` container — single front-door for both DNS and HTTP, saves a sibling container, single systemd-unit / state surface to monitor. The gateway shares host netns (`privateNetwork = false`) so dnsmasq's `bind-interfaces` listener on `bridgeIp` works without any veth gymnastics today; when agent containers flip to private netns the binding doesn't change (it's still on the host's bridge interface). ## Configuration ```nix { services.hyperhive = { enable = true; domain = "darkest.space"; network.enable = true; # opt in to bridge + DNS network.bridgeIp = "10.42.0.1"; # default network.upstreamDns = [ # default Cloudflare + Quad9 "1.1.1.1" "9.9.9.9" ]; }; } ``` Asserts `services.hyperhive.domain != null` (resolver needs a domain to be authoritative for) + `services.hyperhive.gateway.enable = true` (resolver lives in the gateway container). ## Bridge addressing Default subnet is `10.42.0.0/24`, host-side gateway at `10.42.0.1`. RFC 1918 space, unlikely to clash with operator's existing setup; override `bridgeIp` + `bridgePrefixLength` if a different range is already in use. `/24` gives 254 usable per-agent addresses — enough for any single-host hive; bigger swarms or tighter addressing schemes pick their own. ## Resolver behaviour dnsmasq is **authoritative** for the hive's own zones — answers ``, `forge.`, `matrix.` queries with the bridge IP (where nginx is reachable). Everything else gets forwarded to `upstreamDns`. Containers don't need to know the upstream — they query the bridge IP and dnsmasq does the right thing per-name. `bind-interfaces` + `interface = [ bridgeName "lo" ]` means the listener only accepts queries from the bridge interface (plus lo for container health-checks). External hosts can't reach it — no DNS-amplification surface even when the operator opens port 80 for gateway HTTP. `resolveLocalQueries = false` keeps dnsmasq out of the host's own resolution stack — the host's resolver (systemd-resolved, plain glibc nss, dnscrypt-proxy, etc.) keeps doing whatever the operator configured. The hive resolver is purely for inbound queries from agent containers. ## Firewall posture `networking.firewall.interfaces..allowedUDPPorts = [ 53 ]` + `allowedTCPPorts = [ 53 ]` opens the resolver on the bridge interface only. Other interfaces stay closed. The hive resolver isn't an external-facing service. ## Cross-references - `docs/gateway.md` — vhost map + the gateway container's other duties