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:
@@ -150,6 +150,17 @@ func (m *darwinRouteMon) skipInterfaceAddrMessage(msg *route.InterfaceAddrMessag
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *darwinRouteMon) skipRouteMessage(msg *route.RouteMessage) bool {
|
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() {
|
if ip := ipOfAddr(addrType(msg.Addrs, unix.RTAX_DST)); ip.IsLinkLocalUnicast() {
|
||||||
// Skip those like:
|
// Skip those like:
|
||||||
// dst = fe80::b476:66ff:fe30:c8f6%15
|
// dst = fe80::b476:66ff:fe30:c8f6%15
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/net/route"
|
"golang.org/x/net/route"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIssue1416RIB(t *testing.T) {
|
func TestIssue1416RIB(t *testing.T) {
|
||||||
@@ -25,3 +26,31 @@ func TestIssue1416RIB(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Logf("Got: %#v", msgs)
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user