Summary
On Debian 13, standalone tailscaled runs alongside sing-box 1.14.0. Tailscale's locally generated UDP traffic enters the sing-box TUN despite a process_name rule with action: bypass.
A temporary nftables rule restoring Tailscale's routing mark after sing-box's output processing restored direct connectivity.
Could you help determine whether this is a configuration issue, a process pre-match limitation, a conntrack interaction, or a bug?
This report contains configuration excerpts and observed packet traces. It is not yet a standalone minimal reproducer, and the exact root cause has not been established. IP addresses below are anonymized example addresses and cannot be used directly for reproduction; ports, lengths, interfaces and observed results are preserved.
Environment
- Debian 13, Linux amd64.
- sing-box and standalone tailscaled run as systemd services on the same host.
- sing-box: 1.14.0
- sing-box revision: 0b89958
- Build environment: go1.26.7 linux/amd64, CGO enabled.
- Tailscale: 1.102.3-t9329c3677-ga522f65e9
- Tailscale provides subnet routing and an exit node; no upstream exit node is selected.
- Physical interface: ens18; sing-box TUN: tun0.
- Main default route points to a separate LAN gateway.
- ss -uapne confirms tailscaled listens on UDP 41641 with socket fwmark:0x80000.
Relevant configuration
The following is an excerpt, not a complete runnable configuration:
{
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"address": "172.19.0.1/30",
"auto_route": true,
"auto_redirect": true,
"strict_route": true,
"route_exclude_address": [
"192.168.6.0/24",
"100.64.0.0/10"
]
}
],
"route": {
"rules": [
{
"process_name": [
"tailscale",
"tailscaled"
],
"action": "bypass",
"outbound": "direct-out"
}
]
}
}
In the actual configuration, the bypass rule is at index 3. The preceding three rules only match socks-in. direct-out is a direct outbound.
The full configuration passes:
sing-box check -C /etc/sing-box
Observed behavior
While running tailscale ping, packet captures show public peer discovery traffic entering tun0:
tun0 Out 10.0.0.3:41641 -> 198.51.100.20:32506 UDP length 124
STUN traffic shows the following path:
tun0 Out 10.0.0.3:41641 -> 203.0.113.30:3478 UDP length 40
ens18 Out 10.0.0.3:43460 -> 203.0.113.30:3478 UDP length 40
ens18 In 203.0.113.30:3478 -> 10.0.0.3:43460 UDP length 32
tun0 In 203.0.113.30:3478 -> 10.0.0.3:41641 UDP length 32
Conntrack entries for the original UDP 41641 flows have mark=8227 (0x2023). The re-originated UDP 43460 flow has mark=8228 (0x2024).
Restarting tailscaled alone did not resolve the issue. The new process's UDP probes still entered TUN. Conntrack was not flushed, so this does not rule out existing connection-state involvement.
Connectivity remained relayed through DERP, typically around 327 ms or higher, with some tests timing out.
Relevant routing and nftables behavior
Existing Tailscale policy rules include:
5210: fwmark 0x80000/0xff0000 lookup main
5230: fwmark 0x80000/0xff0000 lookup default
5250: fwmark 0x80000/0xff0000 unreachable
5270: lookup 52
Sing-box policy rules include:
9000: fwmark 0x2024 goto 9002
9001: fwmark 0x2023 lookup 2022
9002: nop
The generated sing-box chains show:
- output_prematch at priority -149, with early returns for connection marks 0x2023 and 0x2024, followed by UDP queuing to NFQUEUE 100.
- output_udp_icmp at priority -148, with bypass checks and a final action setting packet and connection marks to 0x2023.
An existing iptables-nft OUTPUT chain at priority -150 also saves the packet's nonzero 0xff0000 bits into the connection mark for new connections. Its PREROUTING counterpart restores those bits for related/established connections.
The exact packet verdict sequence and the ownership of these additional mangle rules have not yet been traced.
Tested workaround
The following independent nftables rule restores the Tailscale mark after sing-box's output UDP processing. 10.0.0.3 is a substituted example address.
table inet tailscale_bypass {
chain output {
type route hook output priority -147; policy accept;
ip saddr 10.0.0.3 meta skuid 0 udp sport 41641 counter meta mark set 0x80000 ct mark set 0x80000
}
}
Results:
- Peer discovery and STUN packets leave ens18 with source port 41641.
- Five consecutive pings establish direct connectivity at 5, 5, 5, 6, 5 ms.
- After persistently loading the rule and restarting tailscaled, five further pings are direct at 4, 6, 5, 5, 20 ms.
- A 10-second capture on tun0, filtered to the local address and UDP source port 41641, captures zero packets.
- No upstream gateway/NAT configuration changes were made during these tests.
This demonstrates an effective workaround in this environment; it does not establish why the process-based bypass failed. The workaround only covers locally generated IPv4 UDP. Full VM reboot and phone exit-node application behavior have not yet been tested.
Questions
- Should process_name with action: bypass handle standalone tailscaled UDP sockets carrying SO_MARK=0x80000 in this setup?
- Is there a supported sing-box configuration that avoids the extra nftables rule?
- Which targeted debug logs or nftables traces would best distinguish process lookup failure from pre-match/conntrack behavior?
Related issue
I checked #4463, which concerns forwarded traffic and reports that locally originated traffic works there. This report concerns locally originated tailscaled UDP, so I have not assumed the same underlying cause.
Summary
On Debian 13, standalone tailscaled runs alongside sing-box 1.14.0. Tailscale's locally generated UDP traffic enters the sing-box TUN despite a process_name rule with action: bypass.
A temporary nftables rule restoring Tailscale's routing mark after sing-box's output processing restored direct connectivity.
Could you help determine whether this is a configuration issue, a process pre-match limitation, a conntrack interaction, or a bug?
This report contains configuration excerpts and observed packet traces. It is not yet a standalone minimal reproducer, and the exact root cause has not been established. IP addresses below are anonymized example addresses and cannot be used directly for reproduction; ports, lengths, interfaces and observed results are preserved.
Environment
Relevant configuration
The following is an excerpt, not a complete runnable configuration:
In the actual configuration, the bypass rule is at index 3. The preceding three rules only match socks-in. direct-out is a direct outbound.
The full configuration passes:
Observed behavior
While running tailscale ping, packet captures show public peer discovery traffic entering tun0:
STUN traffic shows the following path:
Conntrack entries for the original UDP 41641 flows have mark=8227 (0x2023). The re-originated UDP 43460 flow has mark=8228 (0x2024).
Restarting tailscaled alone did not resolve the issue. The new process's UDP probes still entered TUN. Conntrack was not flushed, so this does not rule out existing connection-state involvement.
Connectivity remained relayed through DERP, typically around 327 ms or higher, with some tests timing out.
Relevant routing and nftables behavior
Existing Tailscale policy rules include:
Sing-box policy rules include:
The generated sing-box chains show:
An existing iptables-nft OUTPUT chain at priority -150 also saves the packet's nonzero 0xff0000 bits into the connection mark for new connections. Its PREROUTING counterpart restores those bits for related/established connections.
The exact packet verdict sequence and the ownership of these additional mangle rules have not yet been traced.
Tested workaround
The following independent nftables rule restores the Tailscale mark after sing-box's output UDP processing. 10.0.0.3 is a substituted example address.
Results:
This demonstrates an effective workaround in this environment; it does not establish why the process-based bypass failed. The workaround only covers locally generated IPv4 UDP. Full VM reboot and phone exit-node application behavior have not yet been tested.
Questions
Related issue
I checked #4463, which concerns forwarded traffic and reports that locally originated traffic works there. This report concerns locally originated tailscaled UDP, so I have not assumed the same underlying cause.