Resolving Netbird and Mihomo (Tun Mode) Coexistence Issues on Linux

Running NetBird (mesh networking) and Mihomo (Tun Mode) on Linux means both tools try to control routing. NetBird may connect but stay in Relayed mode, unable to establish point-to-point (P2P) direct connections. In some cases, even the relay connection fails.

Problem Background

  • OS: Arch Linux
  • NetBird: Using default Kernel Mode
  • Mihomo: TUN mode enabled, hijacking system traffic
  • Symptoms: netbird status -d shows the connection type as Relayed, ICE negotiation fails, resulting in high latency and failed NAT hole punching.

Gathering Key Information

Before implementing the solution, you must confirm the actual WireGuard port NetBird is listening on.

To find the NetBird listening port:

sudo cat /var/lib/netbird/default.json | grep WgPort

The default is usually 51820. If it shows 0 or another value, it is recommended to manually edit this file, fix it to 51820, and restart the service.

Additionally, if you have wireguard-tools installed, you can verify the actual port of the kernel interface:

sudo wg show wt0 listen-port

The Final Solution

Core Routing Logic

  1. Mark: Identify UDP packets originating from source port 51820 in the OUTPUT chain and tag them with mark 0x100.
  2. Route: Use policy routing to force packets marked with 0x100 to use the main routing table, effectively bypassing Mihomo.
  3. Masquerade: (The most critical step) Perform source address masquerading in the POSTROUTING chain for marked packets. This fixes the source IP (which might have been altered by Mihomo) and ensures the packet appears to originate from the physical network interface.

Implementation Steps

Use a Systemd override to persist these rules. Execute the following command:

sudo mkdir -p /etc/systemd/system/netbird.service.d/
echo "[Service]
# --- 1. Environment ---
# Since we are using kernel mode, no specific environment variables (like NB_WG_KERNEL_DISABLED) are needed here.

# --- 2. Core Fix: Mark NetBird Traffic (Mangle Output) ---
# Purpose: Identify all encrypted packets sent from port 51820 and apply the 0x100 mark.
ExecStartPost=/usr/bin/iptables -t mangle -A OUTPUT -p udp --sport 51820 -j MARK --set-mark 0x100
ExecStartPost=/usr/bin/ip6tables -t mangle -A OUTPUT -p udp --sport 51820 -j MARK --set-mark 0x100

# --- 3. Core Fix: Policy Routing ---
# Purpose: Force packets with the 0x100 mark to use the physical interface (main table), bypassing Mihomo.
ExecStartPost=/usr/bin/ip rule add fwmark 0x100 lookup main prio 50
ExecStartPost=/usr/bin/ip -6 rule add fwmark 0x100 lookup main prio 50

# --- 4. Core Fix: NAT Masquerade ---
# Purpose: Fix the source IP modified by Mihomo (e.g., 198.18...), reverting it to the physical interface's real IP to ensure P2P return packets are received.
ExecStartPost=/usr/bin/iptables -t nat -A POSTROUTING -m mark --mark 0x100 -j MASQUERADE
ExecStartPost=/usr/bin/ip6tables -t nat -A POSTROUTING -m mark --mark 0x100 -j MASQUERADE

# --- 5. Cleanup on Stop ---
# Purpose: Maintain system cleanliness and prevent rule accumulation.
ExecStopPost=/usr/bin/iptables -t mangle -D OUTPUT -p udp --sport 51820 -j MARK --set-mark 0x100
ExecStopPost=/usr/bin/ip6tables -t mangle -D OUTPUT -p udp --sport 51820 -j MARK --set-mark 0x100
ExecStopPost=/usr/bin/ip rule del fwmark 0x100 lookup main prio 50
ExecStopPost=/usr/bin/ip -6 rule del fwmark 0x100 lookup main prio 50
ExecStopPost=/usr/bin/iptables -t nat -D POSTROUTING -m mark --mark 0x100 -j MASQUERADE
ExecStopPost=/usr/bin/ip6tables -t nat -D POSTROUTING -m mark --mark 0x100 -j MASQUERADE
" | sudo tee /etc/systemd/system/netbird.service.d/override.conf

sudo systemctl daemon-reload
sudo systemctl restart netbird

Additionally, you need to configure Mihomo. Add the following to your tun section:

tun:
  exclude-interface:
    - wt0
  endpoint-independent-nat: true
  inet4-route-exclude-address:
    - 100.64.0.0/10
  inet6-route-exclude-address:
    - fd00::/8

Restart Mihomo to apply changes.

Verification

Wait a moment and check the status:

sudo netbird status -d

You should now see:

  • Interface type: Kernel
  • Connection type: P2P (no longer Relayed)