net/netmon: skip RTM_MISS route messages on darwin (#20050)

macOS 26.4 emits RTM_MISS on the routing socket for every failed route
lookup. skipRouteMessage never inspected the message type, so each miss
woke the monitor as a link change and triggered a netcheck. On networks
without an IPv6 default route the netcheck's IPv6 DERP probes fail and
emit more RTM_MISS messages, sustaining the loop indefinitely: netchecks
run at roughly 40x the intended rate, with sustained probe traffic and
corresponding CPU and battery cost.

RTM_MISS scales with traffic volume, not network state, and is never
the leading signal for a topology change: route withdrawals emit
RTM_DELETE synchronously before any subsequent lookup can miss, so
ignoring it loses no signal. Other routing daemons (bird, dhcpcd, frr)
ignore it as well.

Same fix as coder/tailscale@e956a95074.

Fixes #19324

Signed-off-by: Doug Bryant <dougbryant@anthropic.com>
This commit is contained in:
Doug Bryant
2026-06-08 10:45:13 -07:00
committed by GitHub
parent 4b1408f4a5
commit 2767100bc2
2 changed files with 40 additions and 0 deletions
+11
View File
@@ -150,6 +150,17 @@ func (m *darwinRouteMon) skipInterfaceAddrMessage(msg *route.InterfaceAddrMessag
}
func (m *darwinRouteMon) skipRouteMessage(msg *route.RouteMessage) bool {
// RTM_MISS fires on every failed route lookup (no matching entry in the
// routing table). It scales with traffic volume, not network-state
// changes, and is never the leading signal for a topology change: route
// withdrawals emit RTM_DELETE synchronously before any subsequent lookup
// can miss. Letting these through causes netmon to report spurious
// link changes, which trigger a re-STUN/netcheck loop when a probe
// destination is unreachable (e.g. IPv6 DERP probes on a network with
// no IPv6 default route), as each failed probe emits another RTM_MISS.
if msg.Type == unix.RTM_MISS {
return true
}
if ip := ipOfAddr(addrType(msg.Addrs, unix.RTAX_DST)); ip.IsLinkLocalUnicast() {
// Skip those like:
// dst = fe80::b476:66ff:fe30:c8f6%15
+29
View File
@@ -9,6 +9,7 @@ import (
"testing"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
)
func TestIssue1416RIB(t *testing.T) {
@@ -25,3 +26,31 @@ func TestIssue1416RIB(t *testing.T) {
}
t.Logf("Got: %#v", msgs)
}
func TestSkipRouteMessage(t *testing.T) {
m := &darwinRouteMon{logf: t.Logf}
dst := &route.Inet6Addr{IP: [16]byte{0x26, 0x07}} // 2607:: (global unicast)
tests := []struct {
name string
msg *route.RouteMessage
want bool
}{
{
name: "rtm_miss",
msg: &route.RouteMessage{Type: unix.RTM_MISS, Addrs: []route.Addr{unix.RTAX_DST: dst}},
want: true,
},
{
name: "rtm_add",
msg: &route.RouteMessage{Type: unix.RTM_ADD, Addrs: []route.Addr{unix.RTAX_DST: dst}},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := m.skipRouteMessage(tt.msg); got != tt.want {
t.Errorf("skipRouteMessage = %v; want %v", got, tt.want)
}
})
}
}