Skip to content

Network monitor generates constant IPv6 NoRoutes on systems without IPv6 default route (probe to [2001:db8::1]:9) #3303

Description

@tekert

Diagnostic output

sudo /config/dnscrypt-proxy/dnscrypt-proxy -version
2.1.18

What's happening?

Since upgrading from v2.1.7 to v2.1.18, I'm seeing a constant stream of OutNoRoutes IPv6 errors on my router (EdgeRouter X running EdgeOS), caused by the new network monitor probing [2001:db8::1]:9 every 5 seconds. On systems that have IPv6 forwarding enabled but no IPv6 default route (very common on IPv4-only ISPs), each probe fails with ENETUNREACH and increments the kernel's ipSystemStatsOutNoRoutes counter.

Environment

  • dnscrypt-proxy version: v2.1.18 (upgraded from v2.1.7)
  • OS: EdgeOS on EdgeRouter X
  • IPv6: forwarding enabled (net.ipv6.conf.all.forwarding = 1), but no IPv6 default route and no IPv6 internet connectivity
  • Config: ipv6_servers = false, block_ipv6 = true, listen_addresses = ['[::]:5353']

The problem

The new network monitor introduced in netmon.go calls discoverNetworkMonitorLocalIPs() every 5 seconds (defaultNetworkMonitorInterval), which performs a UDP "dial" to two probe addresses to discover the local IP:

// netmon.go
probeAddrs := []string{"192.0.2.1:9", "[2001:db8::1]:9"}

On a system without an IPv6 default route, the net.DialTimeout("udp", "[2001:db8::1]:9", 1s) call fails immediately with ENETUNREACH. The kernel counts this as an OutNoRoutes event on the IPv6 stats.

This happens every 5 seconds, forever, generating a constant ~0.2 NoRoutes/s that never stops — even when dnscrypt-proxy is otherwise idle and no user queries are being made.

Steps to reproduce

  1. Set up a Linux system with IPv6 forwarding enabled but no IPv6 default route:
    echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
    ip -6 route show default   # should be empty
  2. Start dnscrypt-proxy v2.1.18 with any config (the issue is independent of config).
  3. Monitor the kernel IPv6 NoRoutes counter:
    # Using SNMP stats or /proc/net/snmp6:
    watch -n 1 'grep OutNoRoutes /proc/net/snmp6'
  4. Observe the counter increasing by ~1 every 5 seconds, constantly, with no DNS queries being made.

Evidence

I noticed the issue via SNMP monitoring (VictoriaMetrics scraping the router's ipSystemStatsOutNoRoutes counter). The rate is rock-steady at exactly 0.2/s (= 1 probe / 5 seconds), which matches the defaultNetworkMonitorInterval:

Time window OutNoRoutes IPv6 rate
Before v2.1.18 upgrade ~0.003/s (baseline noise)
After v2.1.18 upgrade 0.20/s constant

The rate does not correlate with DNS query volume, LAN traffic, or time of day — it's a fixed 1 probe every 5 seconds, confirming it's the network monitor and not user traffic.

A tcpdump on the router confirms dnscrypt-proxy itself is not sending any IPv6 DNS traffic (all queries are IPv4/UDP to the upstream resolvers). The NoRoutes are purely from the internal probe.

Impact

  • Pollutes the kernel IPv6 stats with noise, making it harder to spot real routing problems.
  • On routers/edge devices monitored via SNMP, this triggers false-positive alerts on "IPv6 NoRoute" counters.
  • The probe serves no useful purpose on a system with no IPv6 connectivity — it will always fail, every 5 seconds, forever.

Expected behavior

The network monitor should avoid (or skip) the IPv6 probe when the system has no IPv6 default route. A few possible approaches:

  1. Check for an IPv6 default route before probing — if ip -6 route show default is empty (or ::/0 is not in the routing table), skip the [2001:db8::1]:9 probe.
  2. Cache the probe failure — if the IPv6 probe fails with ENETUNREACH / EHOSTUNREACH, back off and retry much less frequently (or not at all) instead of hammering it every 5 seconds.
  3. Make the IPv6 probe opt-in — only probe IPv6 if ipv6_servers = true or if IPv6 connectivity is detected.
  4. Catch and ignore ENETUNREACH — at minimum, don't let the failed probe increment kernel stats (though this may not be controllable from userspace).

Suggested fix (rough idea)

In discoverNetworkMonitorLocalIPs(), something like:

probeAddrs := []string{"192.0.2.1:9"}
// Only add IPv6 probe if the system has an IPv6 default route
if hasIPv6DefaultRoute() {
    probeAddrs = append(probeAddrs, "[2001:db8::1]:9")
}

Or, in networkMonitor.check(), if the IPv6 probe has failed N times in a row with a network-unreachable error, stop including it in the fingerprint until a network change is detected.


Made the AI write this so clarify the issue in depth after some hours trying to find unrelated case, so it's verified by me (yeah IA made it verbose as always but extra info doesn't hurt).

Happy to test a patch if needed. Thanks for the great work on dnscrypt-proxy!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions