From 40477575c1a5900d5a1f21c7b5a473c2a55d2818 Mon Sep 17 00:00:00 2001 From: swares Date: Sun, 6 Sep 2026 09:44:53 -0600 Subject: [PATCH] fix(h4): pin enp2s0 as the preferred route so the source address stops floating (2.9) Both interfaces are on the same /24 and both take DHCP, so both installed a default route at metric 100. Equal metric means the kernel picks one and nothing pins the choice -- it had picked enp1s0, so the H4 was sourcing LAN traffic from 192.168.1.156, an address referenced nowhere in this repo. Found in Vault audit logs, where the H4's own requests arrived from an address no document knew about. This matters beyond tidiness: 2.9's firewall allowlist would have been built from the repo, which says the H4 is .160. It would have worked until a reboot flipped the source address and locked out the machine that runs every playbook, including the one needed to undo the rule -- a failure arriving hours or weeks later, looking unrelated to the change. Hung off h4_dns_link, the variable that already decides which link is primary, so the resolver link and the preferred-route link are the same link by construction. Two independent knobs would eventually disagree in a way nothing would notice. route-metric: 200 on the non-primary link. It does not remove .156 or down the link, only which route is preferred -- and nothing is bound to .156 any more, since the unconfigured dnsmasq holding it was disabled in 2.14, found during this same investigation. Applying stays manual, as this playbook has always been: it writes the override and stops. --- BACKLOG.md | 25 +++++++++++++++--- ansible/playbooks/h4-dns-resolvers.yml | 36 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index 2e1838b..fda8095 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -1640,10 +1640,27 @@ rules", and of doing that discovery with an audit device rather than a grep. The found 19 files and could not have found this, because **the fact is not in the files** — it is a property of the running host. -**Pin the address before writing rules.** An allowlist naming both is correct but papers -over the cause; the real fix is deciding what `enp1s0` is for and either giving it a -losing route metric or taking it off that subnet. See §4.12 — same configuration, and -this is its second distinct symptom. +**PINNED 2026-09-06, not yet applied.** `h4-dns-resolvers.yml` now emits +`route-metric: 200` on the non-primary link, so `enp2s0` (.160) wins deterministically +and the H4's source address stops depending on what the kernel felt like. + +**Hung off the variable that already decides which link is primary.** `h4_dns_link: +enp2s0` governed the resolver list; it now governs the route metric too. The resolver +link and the preferred-route link must be the *same* link, and two independent knobs +would eventually disagree in a way nothing would notice. + +**It does not remove `.156` or down the link** — only which route is preferred. And +nothing is bound to `.156` any more in any case: the unconfigured dnsmasq holding it was +disabled in §2.14, found during this same investigation. + +**Applying is still manual**, as this playbook has always been: it writes the override +and stops. `netplan try` gives a 120-second auto-revert, and unlike the N150s it should +work here — that refusal was specific to a bridge carrying `parameters:`, and the H4 has +plain ethernets. Confirm it actually offers the prompt rather than assuming; the +`systemd-run` revert timer from `kvm-netplan-fix.yml` is the fallback if it refuses. + +An allowlist naming both addresses would still have been *correct*; this makes it +unnecessary. See §4.12 — same configuration, and this was its second distinct symptom. ### 2.10 `.claude/settings.json` is a MicroShift-era artifact `docs/REVIEW-2026-07-24.md:310` (H30) diff --git a/ansible/playbooks/h4-dns-resolvers.yml b/ansible/playbooks/h4-dns-resolvers.yml index c395323..bfa6e12 100644 --- a/ansible/playbooks/h4-dns-resolvers.yml +++ b/ansible/playbooks/h4-dns-resolvers.yml @@ -99,8 +99,36 @@ # its applied line. Right servers, wrong count. # # enp2s0 is the primary: it carries 192.168.1.160 and the kube-vip VIPs .200/.201. + # + # THIS VARIABLE NOW GOVERNS ROUTING AS WELL AS DNS, deliberately. The resolver link + # and the preferred-route link must be the same link; two independent knobs would + # eventually disagree, and the disagreement would be invisible. h4_dns_link: enp2s0 + # ---- route metric — BACKLOG §2.9, added 2026-09-06 ----------------------- + # Both interfaces are on the SAME /24 and both take DHCP, so both installed a + # default route at metric 100: + # + # default via 192.168.1.1 dev enp1s0 proto dhcp src 192.168.1.156 metric 100 + # default via 192.168.1.1 dev enp2s0 proto dhcp src 192.168.1.160 metric 100 + # + # Equal metric means the kernel picks one and nothing pins the choice. It had + # picked enp1s0, so the H4 was sourcing LAN traffic from **192.168.1.156** — an + # address referenced nowhere in this repo. Found in Vault audit logs (§2.6), where + # the H4's own requests arrived from an address no document knew about. + # + # WHY THIS MATTERS BEYOND TIDINESS. §2.9's firewall allowlist would have been built + # from the repo, which says the H4 is .160. It would have worked — until a reboot + # flipped the source address and locked out the machine that runs every playbook, + # including the one needed to undo the rule. A failure arriving hours or weeks + # after the change, looking unrelated to it. + # + # A higher metric loses. 200 on the secondary leaves enp2s0's 100 winning, and does + # NOT remove .156 or take the link down — it only decides which route is preferred. + # Nothing is bound to .156 any more in any case: the unconfigured dnsmasq that held + # it was disabled in §2.14, found during this same investigation. + h4_secondary_route_metric: 200 + tasks: - name: Confirm the expected links exist before touching anything ansible.builtin.command: "ip -o link show {{ item }}" @@ -265,6 +293,14 @@ dhcp4: true dhcp4-overrides: use-dns: false + {% if link != h4_dns_link %} + # Both links are on the same /24 and both take DHCP, so both + # installed a default route at metric 100 and the kernel picked + # whichever it liked — which made the H4's source address for LAN + # traffic non-deterministic. A higher metric loses. This does not + # remove the address or down the link. BACKLOG §2.9. + route-metric: {{ h4_secondary_route_metric }} + {% endif %} {% if link == h4_dns_link %} # Resolver list lives on this link only. Repeating it on the other # link doubles the entries in resolv.conf — resolved does not dedupe