Skip to content

Linux auto_redirect: local tailscaled UDP enters TUN despite process_name bypass (1.14.0) #4505

Description

@kinist

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

  1. Should process_name with action: bypass handle standalone tailscaled UDP sockets carrying SO_MARK=0x80000 in this setup?
  2. Is there a supported sing-box configuration that avoids the extra nftables rule?
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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