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 规则格式:
此规则允许 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% 直接连接率。
- STUN 发现:查询公网 ip:port。
- 防火墙打洞:同时发送 UDP 探针,预开状态表。
- 生日悖论优化:EDM NAT 下,256 端口 × 随机探针,20s 内 99.9% 成功。
- 端口映射:协商公网端口,绕过 NAT 限制。
- DERP 中继:HTTP 加密中继,作为 hairpin/CGNAT 后备。
- 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(新一代)支持应用层控制:
实际配置示例
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 字)