Tailscale 出口网关:出站流量策略与 Hairpin 路由深度解析

出口网关概念

Tailscale 的出口网关(Egress Gateway,也称为 Exit Node)是一种高级功能,允许 tailnet 中的设备将所有非 Tailscale 流量(即 0.0.0.0/0 和 ::/0)路由通过指定的出口节点。这类似于传统 VPN 的全隧道模式,但 Tailscale 通过 WireGuard® 协议实现零信任安全连接。

出口网关的核心作用包括:

  • 地理位置伪装:通过位于特定地区的出口节点访问受地域限制的服务。
  • 安全出站:在不信任网络(如公共 Wi-Fi)中,所有流量经由可信出口节点加密转发。
  • 合规性:满足企业要求的所有流量必须经过集中审计的场景。

配置出口网关需满足前提:

  • 出口节点设备运行 Tailscale v1.20+(Linux、macOS、Windows、Android 或 tvOS)。
  • 通过 tailscale up --advertise-exit-node 广告出口角色。
  • 在 admin console 中批准(Machines 页面 > Edit route settings > Use as exit node)。

使用时,客户端通过 tailscale up --exit-node=<节点 IP 或名称> 指定出口,或使用自动建议节点(--exit-node=auto:any)。

出站流量策略

Tailscale 的出站流量策略主要通过访问控制列表(ACLs)和新一代 Grants 实现 deny-by-default 原则。默认策略允许所有 tailnet 内通信,但出口使用需显式授权 autogroup:internet

ACL 结构

ACL 规则格式:

{ } " ] a c { } l s " " " " a s d : c r s t c t [ i " " o : : n " [ [ : " " g a " r u a o t c u o c p g e : r p e o t n u " g p , i : n i e n e t r e s r " n ] e , t : * " ]

此规则允许 engineers 组使用任意出口节点访问互联网。

策略组件 描述 示例
src 来源选择器(用户、组、标签、IP) group:dev, tag:prod, autogroup:admin
dst 目标(autogroup:internet 表示出口) autogroup:internet:, 100.64.0.0/10:
proto 协议(TCP/UDP 等) tcp, udp, 6
端口 目标端口范围 *:80,443, 1000-2000

Hairpin NAT 路由问题

Hairpin NAT(也称 NAT Loopback 或 NAT Reflection)指内部设备使用公共 IP 访问同一 NAT 后的另一内部设备时,流量需“折返”(hairpin)通过 NAT 设备。

在 Tailscale 出口场景中常见问题:

  • CGNAT(Carrier-Grade NAT):ISP 级 NAT,双层 NAT 导致 hairpin 失败。客户端 A 和 B 在同一 CGNAT 后,STUN 发现相同公网 IP,但无 hairpin 支持时,A 到 B 的流量被丢弃。
  • 企业 NAT 网关:Symmetric NAT(EDM)变异端口,导致 STUN 公网端口无效。
  • 防火墙状态超时:UDP 会话 30s 无流量即过期,需心跳保持。

问题表现:客户端间直接 P2P 失败,回退 DERP 中继;出口流量 hairpin 时,内部服务不可达。

NAT 类型 Mapping 类型 Hairpin 支持 Tailscale 影响
Endpoint-Independent (EIM) 固定端口 易穿越,STUN 有效
Endpoint-Dependent (EDM) 动态端口 否/部分 需生日悖论探针或中继
CGNAT 多层 EDM 常见失败 DERP 必备,生日攻击辅助
NAT64 IPv6→IPv4 依赖 CLAT IPv6 优先,NAT64 探测

Tailscale 的解决方案

Tailscale 采用 ICE(Interactive Connectivity Establishment)算法,结合 STUN、端口映射(UPnP/NAT-PMP/PCP)和 DERP 中继,实现 >90% 直接连接率。

  1. STUN 发现:查询公网 ip:port。
  2. 防火墙打洞:同时发送 UDP 探针,预开状态表。
  3. 生日悖论优化:EDM NAT 下,256 端口 × 随机探针,20s 内 99.9% 成功。
  4. 端口映射:协商公网端口,绕过 NAT 限制。
  5. DERP 中继:HTTP 加密中继,作为 hairpin/CGNAT 后备。
  6. IPv6 优先:无 NAT,简化遍历。

出口特有:--exit-node-allow-lan-access 允许 LAN 直连,避免 hairpin;状态过滤(--stateful-filtering)防非预期入站。

基于 ACL 的出口控制

使用 tailnet policy file(HuJSON 格式)精细控制:

{
  "acls": [
    // 允许 dev 组使用 tag:egress-exit 出口
    {
      "action": "accept",
      "src": ["group:dev"],
      "proto": "any",
      "dst": ["tag:egress-exit:0.0.0.0/0"]
    },
    // 仅 HTTP/HTTPS 出站
    {
      "action": "accept",
      "src": ["autogroup:internet"],
      "proto": "tcp",
      "dst": ["autogroup:internet:80,443"]
    }
  ],
  "tagOwners": {
    "tag:egress-exit": ["group:ops"]
  },
  "autoApprovers": {
    "exit-node": ["tag:egress-exit"]
  }
}

测试规则:

{
  "tests": [
    {
      "src": ["user:[email protected]"],
      "accept": [],
      "deny": ["autogroup:internet:*"]
    }
  ]
}

Grants(新一代)支持应用层控制:

g ] r a a } n l t l t p s o a o : w r r g t [ g e r t 0 o . u t 0 p a . : g 0 d : . e e 0 v g / r 0 { e s s - e x i t

实际配置示例

1. 部署出口节点(Linux)

# 广告出口
sudo tailscale up --advertise-exit-node --advertise-routes=0.0.0.0/0 --stateful-filtering

# 批准:admin console > Machines > 编辑 > Use as exit node

2. 客户端使用

tailscale up --exit-node=100.64.1.2 --exit-node-allow-lan-access
tailscale status  # 验证

3. 策略应用

上传 policy file 到 admin console(Access controls),测试 tailscale test acls

场景 ACL 示例 预期效果
部门隔离 src: group:sales → dst: autogroup:internet:* 仅销售组出站
端口限制 dst: autogroup:internet:80,443 仅 Web 流量
高可用 autoApprovers: exit-node: [tag:ha-exit] 自动批准 HA 标签

参考文献

(本文约 3500 字)