A unicast DHCP renewal reply currently reaches dhcpcd only by matching the firewall's ESTABLISHED,RELATED conntrack rule against the outbound request. When that conntrack entry has already expired the reply is dropped silently, with no log line anywhere. The client's broadcast paths (DISCOVER, rebind) bypass netfilter entirely via a raw BPF socket and never depend on this state — only the unicast renewal path does. This removes that dependency by accepting DHCP client traffic unconditionally, gated on the firewall being enabled at all. It does not identify or claim to fix the cause of any particular observed renewal failure. Refs #3389
92 lines
4.5 KiB
Nix
92 lines
4.5 KiB
Nix
# In-container network plumbing: DHCP on the bridge veth, resolvconf
|
|
# taken out of the loop, and the oneshot that points resolv.conf at
|
|
# the hive bridge resolver.
|
|
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
{
|
|
# Take resolvconf + dhcpcd out of the /etc/resolv.conf loop so the
|
|
# bridge resolver the oneshot below writes actually sticks. At their
|
|
# NixOS defaults, resolvconf regenerates resolv.conf from host-tracking
|
|
# *after* the oneshot has pointed it at the bridge (dhcpcd re-triggers
|
|
# that when the veth comes up under isolation) — silently clobbering the
|
|
# bridge nameserver back to the host resolver, which isn't authoritative
|
|
# for the hive's own zones, so `forge.<domain>` stops resolving. We
|
|
# disable resolvconf and tell dhcpcd not to touch resolv.conf (without
|
|
# disabling dhcpcd itself, so the veth still gets its address); then
|
|
# the hyperhive-isolated-dns oneshot owns resolv.conf. (Same "take
|
|
# resolvconf out of the loop" approach the matrix container uses.)
|
|
# All agent containers receive their bridge IP via DHCP from the hive
|
|
# dnsmasq pool (see nix/host-modules/hive-gateway/dnsmasq.nix). useDHCP runs dhcpcd
|
|
# on every interface (just eth0 in practice — the nspawn bridge veth).
|
|
config = {
|
|
networking.useDHCP = true;
|
|
networking.resolvconf.enable = false;
|
|
networking.dhcpcd.extraConfig = "nohook resolv.conf";
|
|
|
|
# A unicast DHCP renewal reply otherwise reaches dhcpcd only by
|
|
# matching the firewall's ESTABLISHED,RELATED conntrack rule against
|
|
# the outbound request; when that conntrack entry has already expired
|
|
# the reply is dropped with no log line anywhere. Accept it
|
|
# unconditionally instead, removing that dependency. Not a fix for any
|
|
# particular observed failure — the reply path just shouldn't depend
|
|
# on conntrack state in the first place.
|
|
networking.firewall.allowedUDPPorts = lib.mkIf config.networking.firewall.enable [ 68 ];
|
|
|
|
# Point resolv.conf at the hive bridge resolver when the container is
|
|
# network-isolated. nixos-container copies the *host's* /etc/resolv.conf
|
|
# into the container at every start — but the host resolver (e.g.
|
|
# 127.0.0.53) is unreachable from a private netns and isn't
|
|
# authoritative for the hive's own zones (forge.<domain> etc.). The
|
|
# bridge dnsmasq (gateway IP) is. hive-priv drops the marker
|
|
# `/etc/hyperhive-bridge-dns` (containing the gateway IP) since
|
|
# isolation is always on; the oneshot reads it and rewrites
|
|
# resolv.conf on every boot. Ordered before the first DNS consumer
|
|
# (tea-login) and the network targets so name resolution works for
|
|
# the very first turn.
|
|
systemd.services.hyperhive-isolated-dns = {
|
|
description = "point resolv.conf at the hive bridge resolver (isolated containers)";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "local-fs.target" ];
|
|
# Ordered before every network consumer that does DNS on first
|
|
# boot. `hive-agent` (the harness) is the load-bearing one: its
|
|
# first-turn api.anthropic.com lookup must not race the resolv.conf
|
|
# rewrite (it only declares `after network.target`, so without this
|
|
# edge the harness can start before we've fixed resolv.conf and the
|
|
# first turn errors — self-heals next turn, but better not to flap).
|
|
# `hive-matrix-daemon` likewise syncs over the network; the `before`
|
|
# is a harmless no-op when matrix is disabled (the unit is absent).
|
|
before = [
|
|
"network-online.target"
|
|
"tea-login.service"
|
|
"hive-agent.service"
|
|
"hive-matrix-daemon.service"
|
|
];
|
|
unitConfig.ConditionPathExists = "/etc/hyperhive-bridge-dns";
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
# Pin the journal identity; without it systemd derives it from the
|
|
# generated `script` store-path wrapper (an opaque `<hash>-…-start`).
|
|
SyslogIdentifier = "hyperhive-isolated-dns";
|
|
};
|
|
path = [ pkgs.coreutils ];
|
|
script = ''
|
|
set -eu
|
|
gw=$(tr -d '[:space:]' < /etc/hyperhive-bridge-dns)
|
|
if [ -z "$gw" ]; then
|
|
echo "hyperhive-isolated-dns: empty marker; leaving resolv.conf as-is"
|
|
exit 0
|
|
fi
|
|
# resolv.conf is a regular file copied from the host by
|
|
# nixos-container; replace it (rm first in case it's a symlink).
|
|
rm -f /etc/resolv.conf
|
|
printf 'nameserver %s\n' "$gw" > /etc/resolv.conf
|
|
echo "hyperhive-isolated-dns: resolv.conf -> nameserver $gw"
|
|
'';
|
|
};
|
|
};
|
|
}
|