fix(ssh,ai): keep connections to idle LAN peers fast (#463)

* fix(ssh): retry and multiplex LAN connections
* fix(ai): shrink the neighbour-probe fallback to ~1s

Assisted-by: DeepSeek Harness 0.2.0-rc.2 (deepseek-flash)
This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-10-16 20:07:19 +08:00
1 parent 3266aad4a2
commit a0ff9f7fd7
4 files changed
+77 -15

No files matched your search

+1
View File
@@ -71,6 +71,7 @@ A known limitation with no fix yet.
| WA-025 | brcmfmac 4-way-handshake offload authentication — upstream kernel/wpa_supplicant, no code in this repo | Since hostap commit 416386060, wpa_supplicant 2.11+ expects `NL80211_CMD_PORT_AUTHORIZED` from drivers that offload the 4-way handshake; brcmfmac never reports it, so authentication intermittently fails on Apple Silicon (BRCM4378/4387/4388). nixpkgs already carries the Broadcom revert in wpa_supplicant 2.12; the proper fix, Janne Grunau's `wifi: brcmfmac: cfg80211: Report port_authorized for 4-way HS offload` (2026-09), is still under review. Entering the key again after such a failure only creates a duplicate profile, so cancel and retry instead | Fix lands via a nixpkgs kernel update (or by carrying the patch through `nixpkgs-patched`) | 2026-10-07 | on every kernel bump | active |
| WA-033 | USB-C/XHCI port `_PLD` ACPI noise — this board's DSDT, no code in this repo | every boot logs 16 `ACPI Error: Aborting method \_SB.PC00.TXHC.RHUB.SS01._PLD ... (AE_NOT_FOUND)` lines, plus `\_SB.PC02.XHCI.RHUB.HS01._PLD`; the ports work, and these messages are unrelated to the GPU's ATPX failure, which names `\_SB.PC00.GFX0.ATPX` instead | Drop if a BIOS update defines the `_PLD` descriptors | 2026-10-14 | on a BIOS update | active |
| WA-034 | IPv6-first resolution is dead on the wired link — `hosts/idols-aquamarine/default.nix` (`networking.getaddrinfo.precedence`) | the proxy gateway answers DNS in fake-IP mode and its IPv6 path is dead: an AAAA lookup returns a fake address whose connection fails at once (measured: `curl -6` fails in 8 ms, `curl -4` answers in 0.7 s). glibc clients are covered by the RFC 6724 table with the IPv4-mapped row raised; nix's libcurl and other non-glibc resolvers ignore `/etc/gai.conf`. Disabling IPv6 with `boot.kernel.sysctl` did not work here — networkd re-enables it on the wired link for `DHCP = "ipv6"`/`IPv6AcceptRA` — so that setting was dropped on 2026-10-14 | Drop once the gateway serves proxied names over IPv6 as well (the adjacent fix is `dns.ipv6: false` in mihomo, see WA-030) | 2026-10-14 | on a mihomo or gateway change | active |
| WA-035 | SSH retry + multiplexing to LAN peers, ARP fallback tuning — `home/base/tui/ssh.nix`, `hosts/idols-ai/neighbour-probe.nix` | the desktop reaches the LAN over a Mesh WiFi backhaul, which drops unicast to a peer whose MAC it has aged out of its forwarding table while still flooding broadcast: a capture shows seven SYNs from one socket addressed to the cached MAC with no answer, three unicast ARP probes unanswered, then a broadcast ARP answered in 0.4 ms after ~12 s of failed connects. The ssh settings retry a short connect and keep a master warm so the entry stays fresh; the neighbour-probe tunings walk the kernel to that broadcast fallback in ~1 s | Drop once the backhaul is wired or the mesh stops dropping unknown unicast | 2026-10-16 | when the backhaul changes | active |
## WIP
+38 -15
View File
@@ -3,8 +3,26 @@
lib,
pkgs,
mysecrets,
myvars,
...
}:
let
# WA-035: settings for the home LAN, which the desktop reaches over a Mesh
# WiFi backhaul.
lanHost = {
# A lost first SYN must not fail the connection; a LAN handshake is ~2 ms,
# so the connect is cut short and retried instead of hanging.
ConnectTimeout = 1;
ConnectionAttempts = 3;
# One persistent master per host keeps a session (and with it the mesh's
# forwarding entry for that peer) open, so later connections reuse it
# instead of resolving and handshaking again. Three missed keepalives reap a
# master whose path died.
ControlMaster = "auto";
ControlPersist = "30m";
ServerAliveInterval = 15;
};
in
{
home.file.".ssh/romantic.pub".source = "${mysecrets}/public/romantic.pub";
@@ -12,20 +30,23 @@
enable = true;
enableDefaultConfig = false;
settings."*" = {
ForwardAgent = false;
AddKeysToAgent = "yes";
Compression = true;
ServerAliveInterval = 0;
ServerAliveCountMax = 3;
HashKnownHosts = false;
UserKnownHostsFile = "~/.ssh/known_hosts";
ControlMaster = "no";
ControlPath = "~/.ssh/master-%r@%n:%p";
ControlPersist = "no";
};
# Keep this one definition: Nix merges a `settings."<pattern>"` attribute
# path into `settings = ...` only while that value stays an attrset literal,
# so the generated LAN patterns are merged inside it with `//` instead.
settings = {
"*" = {
ForwardAgent = false;
AddKeysToAgent = "yes";
Compression = true;
ServerAliveInterval = 0;
ServerAliveCountMax = 3;
HashKnownHosts = false;
UserKnownHostsFile = "~/.ssh/known_hosts";
ControlMaster = "no";
ControlPath = "~/.ssh/master-%r@%n:%p";
ControlPersist = "no";
};
"github.com" = {
# WA-028: pinned to IPv4 until the mihomo v6 fake-ip pool is proven here
AddressFamily = "inet";
@@ -37,8 +58,10 @@
"192.168.*" = {
ForwardAgent = true;
};
};
}
// lanHost;
}
// lib.genAttrs (builtins.attrNames myvars.networking.hostsAddr) (_: lanHost);
};
# Keep ~/.ssh/config a real, user-owned file rather than a /nix/store symlink.
+3
View File
@@ -35,6 +35,9 @@ in
./restic.nix
# WA-035: neighbour-probe fallback for the Mesh WiFi backhaul to the homelab.
./neighbour-probe.nix
# others
./ai
];
+35
View File
@@ -0,0 +1,35 @@
{
config,
myvars,
...
}:
let
# The wired link to the router, i.e. the one that leaves through the Mesh WiFi
# backhaul to the homelab. Taken from `hostsAddr` so a NIC rename cannot leave
# these tunings pointing at a table that no longer exists.
iface = myvars.networking.hostsAddr.${config.networking.hostName}.iface;
in
{
# WA-035: the mesh backhaul drops unicast to a peer whose MAC it has aged out
# while still flooding broadcast, so a connection to an idle peer waits for the
# kernel to give up on unicast ARP and fall back to broadcast. Walk that
# neighbour state machine in ~1s instead of ~10s, which fits inside ssh's 5s
# retry window (see `home/base/tui/ssh.nix` for the other half).
#
# Cost: while a peer is genuinely down, probes go out 5x faster for the same
# bounded count -- one unicast probe, then the unchanged broadcast attempts. The
# failed-neighbour re-probe pace (`interval_probe_time_ms`) is deliberately left
# alone, so a peer that stays down is not probed more often than before.
#
# `default` and the link's own table are both set: a device copies `default`
# when it is registered, so a later write to `default` does not reach a table
# that already exists.
boot.kernel.sysctl = {
"net.ipv4.neigh.default.delay_first_probe_time" = 1; # revalidate after 1s, not 5s
"net.ipv4.neigh.default.retrans_time_ms" = 200; # probe every 200ms, not 1s
"net.ipv4.neigh.default.ucast_solicit" = 1; # one unicast probe, then broadcast
"net.ipv4.neigh.${iface}.delay_first_probe_time" = 1;
"net.ipv4.neigh.${iface}.retrans_time_ms" = 200;
"net.ipv4.neigh.${iface}.ucast_solicit" = 1;
};
}