Leadaxe 记录 singbox-launcher 的 Tailscale 出口节点流量不被 sniff,域名规则对该类客户端不生效

Leadaxe 于 2026-09-29 08:06:22Z 在自己的仓库 singbox-launcher 提交 issue #139(目前仍 open):远程机器(Linux,内核 1.14.2-lx.7)以 tailscale endpoint 且 advertise_exit_node: true 作为出口节点、手机(LxBox)以其为 exit_node 时,手机流量会绕过该机器上全部基于域名的路由规则——模板把 sniff 与 resolve 限定在启动器自身的入站(tun-in、proxy-in),经 tailscale endpoint 进入的连接不会被 sniff,只带目标 IP,域名规则与域名规则集都不匹配,全部落到 route.final。他给出同一时刻的实测对照:经 tun-in 的局域网客户端被识别为 api.anthropic.com 并命中 AI 规则集、走专用选择器;经 Tailscale 出口节点的手机只见裸 IP、未命中任何规则而走 proxy-out,导致该服务以「不支持地区」拒绝手机。成因在 bin/wizard_template.json 的 route.rules:sniff 与 hijack-dns 之后的 resolve,其 inbound 只由 @tun(tun-in)与 @enable_proxy_in(proxy-in)构建,tailscale 等可接收入站连接的 endpoint 不在其中。他期望 tailscale endpoint 接收的连接同样经过 sniff/resolve,并列出两种改法:去掉这两条规则的 inbound 限制(对所有入站生效),或在 endpoint 宣告出口节点时把其标签加入两条规则的 inbound 列表。该缺陷尚未修复,也未随任何版本发出(最新发布为 09-28 的 v2.3.3)。

作者原文

Summary

Traffic from Tailscale exit-node clients bypasses all domain-based routing rules on the exit node. The template restricts sniff and resolve to the launcher's own inbounds (tun-in, proxy-in), so connections arriving through a tailscale endpoint are never sniffed. They carry only a destination IP, domain rules and domain rule-sets never match, and everything falls through to route.final.

Setup

  • Remote machine (Linux, core 1.14.2-lx.7) with a tailscale endpoint, advertise_exit_node: true.
  • A phone (LxBox) uses that machine as exit_node.
  • On the exit node, a rule sends a domain rule-set (AI services) to a dedicated selector; route.final is proxy-out.

Observed

Generated route.rules on the exit node:

{"action":"sniff","inbound":["tun-in"],"timeout":"1s"}
{"action":"hijack-dns","protocol":"dns"}
{"action":"resolve","inbound":["tun-in"],"strategy":"prefer_ipv4"}

Live connections on the same machine at the same time:

| Client | host seen by core | matched rule | chain |
|---|---|---|---|
| LAN client via tun-in | api.anthropic.com | rule_set=[user:AI-SRS …] | Ai → selected node |
| Phone via Tailscale exit node | bare IP | none | proxy-out → final |

The service behind the domain rule rejects the phone ("unsupported region") because the phone's traffic leaves through route.final instead of the dedicated selector.

Cause

bin/wizard_template.json, route.rules: the sniff rule (and the resolve rule after hijack-dns) build inbound only from @tun → tun-in and @enable_proxy_in → proxy-in. Tailscale endpoints (and any other endpoint that accepts inbound connections) are not in the list.

Expected

Connections accepted by a tailscale endpoint go through the same sniff/resolve steps as tun-in, so the exit node applies its routing rules to exit-node clients exactly as to LAN clients.

Possible fixes

  1. Drop the inbound restriction from sniff and resolve (apply to all inbounds).
  2. Add the tags of tailscale endpoints to the inbound list of both rules when an endpoint advertises an exit node.