[GH-ISSUE #5126] Policy routing rule for network routes disappears, breaking DNS forwarding #10625

Closed
opened 2026-08-05 01:26:40 -04:00 by saavagebueno · 1 comment
Owner

Originally created by @anagrius on GitHub (Jan 18, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/5126

Description

When NetBird is configured with network routes (e.g., 10.43.0.0/16 for Kubernetes service CIDR), the route is correctly added to a custom routing table (7120), but the policy routing rule that directs traffic to use that table disappears over time.

This causes DNS resolution to fail for domains configured to use upstream DNS servers reachable only via the NetBird tunnel (e.g., svc.cluster.local forwarded to CoreDNS at 10.43.0.10:53).

Environment

  • OS: Arch Linux (kernel 6.17.9)
  • NetBird version: 0.60.7
  • Interface type: Kernel (wt0)
  • Network manager: systemd-networkd

Symptoms

  1. netbird status shows everything connected:

    • Peers connected
    • Nameservers: [10.43.0.10:53] for [svc.cluster.local] is Available
    • Network route: 10.43.0.0/16 via peer with status "Selected"
  2. But DNS queries fail with SERVFAIL:

    $ resolvectl query ui.bzrk.svc.cluster.local
    ui.bzrk.svc.cluster.local: resolve call failed: SERVFAIL
    
  3. The route exists in table 7120:

    $ ip route show table 7120
    10.43.0.0/16 dev wt0
    
  4. But the policy rule is missing:

    $ ip rule show
    0:     from all lookup local
    32766: from all lookup main
    32767: from all lookup default
    # No rule for table 7120!
    
  5. Traffic to 10.43.0.0/16 goes out the wrong interface:

    $ ip route get 10.43.0.10
    10.43.0.10 via 192.168.0.1 dev wlan0  # Wrong! Should be dev wt0
    

Workaround

Manually adding the rule fixes it immediately:

sudo ip rule add to 10.43.0.0/16 lookup 7120 priority 100

After adding the rule:

$ ip route get 10.43.0.10
10.43.0.10 dev wt0 table 7120 src 100.84.x.x  # Correct!

$ resolvectl query ui.bzrk.svc.cluster.local
ui.bzrk.svc.cluster.local: 10.43.100.142  # Works!

Root Cause Hypothesis

The policy routing rule appears to be removed when systemd-networkd reconfigures the network (e.g., DHCP lease renewal, interface up/down events). Running netbird down && netbird up restores the rule, suggesting NetBird adds it on startup but doesn't monitor/restore it afterward.

Expected Behavior

NetBird should either:

  1. Monitor and restore the policy routing rule if it gets removed
  2. Use a routing mechanism that persists across network reconfigurations
  3. Hook into systemd-networkd's dispatcher to re-add rules after network changes

Temporary Workaround

Created a systemd timer that checks and restores the rule every minute:

# /etc/systemd/system/netbird-route-fix.service
[Unit]
Description=Fix NetBird routing rule for k8s CIDR
After=network.target

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'ip rule show | grep -q "to 10.43.0.0/16 lookup 7120" || ip rule add to 10.43.0.0/16 lookup 7120 priority 100'

# /etc/systemd/system/netbird-route-fix.timer
[Unit]
Description=Periodically check NetBird routing rule

[Timer]
OnBootSec=30
OnUnitActiveSec=1min

[Install]
WantedBy=timers.target
Originally created by @anagrius on GitHub (Jan 18, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/5126 ## Description When NetBird is configured with network routes (e.g., `10.43.0.0/16` for Kubernetes service CIDR), the route is correctly added to a custom routing table (7120), but the policy routing rule that directs traffic to use that table disappears over time. This causes DNS resolution to fail for domains configured to use upstream DNS servers reachable only via the NetBird tunnel (e.g., `svc.cluster.local` forwarded to CoreDNS at `10.43.0.10:53`). ## Environment - **OS**: Arch Linux (kernel 6.17.9) - **NetBird version**: 0.60.7 - **Interface type**: Kernel (wt0) - **Network manager**: systemd-networkd ## Symptoms 1. `netbird status` shows everything connected: - Peers connected - Nameservers: `[10.43.0.10:53] for [svc.cluster.local] is Available` - Network route: `10.43.0.0/16` via peer with status "Selected" 2. But DNS queries fail with `SERVFAIL`: ``` $ resolvectl query ui.bzrk.svc.cluster.local ui.bzrk.svc.cluster.local: resolve call failed: SERVFAIL ``` 3. The route exists in table 7120: ``` $ ip route show table 7120 10.43.0.0/16 dev wt0 ``` 4. But the policy rule is missing: ``` $ ip rule show 0: from all lookup local 32766: from all lookup main 32767: from all lookup default # No rule for table 7120! ``` 5. Traffic to `10.43.0.0/16` goes out the wrong interface: ``` $ ip route get 10.43.0.10 10.43.0.10 via 192.168.0.1 dev wlan0 # Wrong! Should be dev wt0 ``` ## Workaround Manually adding the rule fixes it immediately: ```bash sudo ip rule add to 10.43.0.0/16 lookup 7120 priority 100 ``` After adding the rule: ``` $ ip route get 10.43.0.10 10.43.0.10 dev wt0 table 7120 src 100.84.x.x # Correct! $ resolvectl query ui.bzrk.svc.cluster.local ui.bzrk.svc.cluster.local: 10.43.100.142 # Works! ``` ## Root Cause Hypothesis The policy routing rule appears to be removed when systemd-networkd reconfigures the network (e.g., DHCP lease renewal, interface up/down events). Running `netbird down && netbird up` restores the rule, suggesting NetBird adds it on startup but doesn't monitor/restore it afterward. ## Expected Behavior NetBird should either: 1. Monitor and restore the policy routing rule if it gets removed 2. Use a routing mechanism that persists across network reconfigurations 3. Hook into systemd-networkd's dispatcher to re-add rules after network changes ## Temporary Workaround Created a systemd timer that checks and restores the rule every minute: ```ini # /etc/systemd/system/netbird-route-fix.service [Unit] Description=Fix NetBird routing rule for k8s CIDR After=network.target [Service] Type=oneshot ExecStart=/bin/bash -c 'ip rule show | grep -q "to 10.43.0.0/16 lookup 7120" || ip rule add to 10.43.0.0/16 lookup 7120 priority 100' # /etc/systemd/system/netbird-route-fix.timer [Unit] Description=Periodically check NetBird routing rule [Timer] OnBootSec=30 OnUnitActiveSec=1min [Install] WantedBy=timers.target ```
Author
Owner

@anagrius commented on GitHub (Feb 5, 2026):

Closing as this is a duplicate of #4578.

Root cause: systemd-networkd defaults to ManageForeignRoutingPolicyRules=yes, which removes routing policy rules it didn't create (like NetBird's rule for table 7120) during DHCP renewals, sleep/wake, or network reconfigurations.

Why this affected me: I installed NetBird via the Arch Linux package (pacman), which doesn't create the /etc/systemd/networkd.conf.d/99-netbird.conf drop-in that the official NetBird installer script creates.

Solution: Create /etc/systemd/networkd.conf.d/99-netbird.conf:

[Network]
ManageForeignRoutes=no
ManageForeignRoutingPolicyRules=no

Then restart systemd-networkd: sudo systemctl restart systemd-networkd

This should probably be documented for users who install via distro packages rather than the official installer.

<!-- gh-comment-id:3851790022 --> @anagrius commented on GitHub (Feb 5, 2026): Closing as this is a duplicate of #4578. **Root cause:** `systemd-networkd` defaults to `ManageForeignRoutingPolicyRules=yes`, which removes routing policy rules it didn't create (like NetBird's rule for table 7120) during DHCP renewals, sleep/wake, or network reconfigurations. **Why this affected me:** I installed NetBird via the Arch Linux package (`pacman`), which doesn't create the `/etc/systemd/networkd.conf.d/99-netbird.conf` drop-in that the official NetBird installer script creates. **Solution:** Create `/etc/systemd/networkd.conf.d/99-netbird.conf`: ```ini [Network] ManageForeignRoutes=no ManageForeignRoutingPolicyRules=no ``` Then restart systemd-networkd: `sudo systemctl restart systemd-networkd` This should probably be documented for users who install via distro packages rather than the official installer.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#10625