Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TUIC 前端接入(QUIC)

rove 除了 TCP 上的 HTTP CONNECT / SOCKS5,还可以开一个 TUIC v5 前端入口。它跑在 QUIC(UDP + TLS 1.3)上,面向移动端与实时应用:单 UDP 口即可同时隧道 TCP 与 UDP。客户端参数见 客户端接入

节点的核心角色不变:本地完成认证、策略分流、限速、连接数限制,失败即拒绝。TUIC 只是多了一扇 QUIC 前门,出口仍复用现有能力。

参考实现:TUIC v5 协议规范

拓扑

TUIC 客户端 ──QUIC──▶ rove(TUIC 监听)
                         │ authenticate(uuid + token)
                         │ decide() 分流 · 限速 · 连接数
                         ├─ Connect(TCP) ─▶ 现有出口(direct / http / socks5 / reverse)
                         └─ Packet(UDP)  ─▶ 反向 hop UDP 出口(reverse/2)─▶ 媒体/游戏服务器
  • TCP(Connect 直接复用 outbound 出口选择,按用户限速、计连接数、写访问日志,和 HTTP/SOCKS5 完全一致。
  • UDP(Packet,native datagram 模式)reverse/2 UDP 数据面不限速(与反向 hop 的 TCP splice 一致),逐包执行策略。

认证模型(UUID + Token,独立于登录密码)

TUIC 用 uuid + token 而非用户名/密码:

  1. 客户端在一条单向流上发 Authenticate{ uuid(16B), token(32B) }
  2. token 由客户端用 TLS Keying Material Exporter(RFC 5705)在当前 TLS 会话上导出:label = 原始 uuidcontext = TUIC 密码,长度 32 字节。
  3. 节点用同一 TLS 会话、同样的 label/context 重新导出,常量时间比对。这把认证绑定到当前 TLS 握手,天然防重放。

节点侧只做查表归属:快照在编译期建立 uuid → 用户名 索引(见数据模型)。TUIC 凭据独立于登录密码,泄露前端凭据不会暴露账号登录口令;同一个 uuid 被两个用户占用会导致快照编译失败(认证必须无歧义)。

失败即拒绝:未知 uuid、token 不匹配、uuid/token 长度非法、账号过期 —— 一律认证失败并关闭连接;连接在超时时间内未完成认证也会被关闭。

配置

[[tuic_listeners]]
name   = "tuic-in"
listen = "0.0.0.0:8443"        # QUIC 的 UDP ip:port(记得放行 UDP!)
cert   = "./certs/server.crt"  # QUIC 强制 TLS 1.3
key    = "./certs/server.key"
alpn   = ["h3"]                # 必须与客户端配置的 ALPN 一致
[tuic_listeners.sniff]
enabled = true                 # 可选;默认 false
mode = "route"                 # observe | route
max_bytes = 16384
timeout_ms = 500
  • [[tuic_listeners]][[listeners]](TCP 的 HTTP/SOCKS5)相互独立,可以只开其一、都开、或都不开。
  • cert/key 必填(QUIC 强制 TLS)。缺 listen/cert/key 会在启动时 fail-closed 报错,绝不半配置启动。
  • alpn 默认 ["h3"];改了要让客户端一并改。
  • [tuic_listeners.sniff] 只处理 TCP Connect 的首包并提取 TLS SNI / HTTP Host,不处理 UDP Packetobserve 只记录;route 在拨号前让识别域名参与策略,但绝不改写 requested 目标。
  • route 中 requested/sniffed 任一命中 block 都拒绝;只有 requested 是 IP 时,sniffed 域名才能命中 egress route 选择出口。识别失败或超时回退 requested 决策,已读取字节原样回放。

要让某个用户能用 TUIC,控制面需要在快照里给该用户配 frontends.tuic(uuid + password):

{
  "schema_version": 1,
  "version": 42,
  "users": {
    "alice": {
      "password": "login-only-secret",   // 登录密码(HTTP/SOCKS5)
      "policy": "media",
      "frontends": {                     // 按协议命名空间的前端凭据
        "tuic": {
          "uuid": "550e8400-e29b-41d4-a716-446655440000",
          "password": "front-end-only-secret"  // 独立于 password
        }
      }
    }
  },
  "routing_policies": {
    "media": { "routes": [] }
  },
  "egresses": {}
}

frontends 是按协议命名的凭据表(frontends.<协议>)。将来新增 listener adapter 只需加一个协议条目,不动顶层 schema,且可按协议独立启停 / 轮换。新协议必须先证明自己服务的是应用入口,不是为了堆协议清单。

UDP 出口:必须落在反向 hop

TUIC 的 UDP 只经 reverse/2 UDP 出口(唯一可行的非 Direct UDP 出口)。要让某用户的 UDP 打到目标服务器,其 routing policy 必须把目标路由到一个 reverse named egress(backend.kind = "reverse"addr = hop_id):

{
  "schema_version": 1,
  "version": 43,
  "users": {
    "alice": { "password": "login-only-secret", "policy": "media" }
  },
  "routing_policies": {
    "media": {
      "routes": [],
      "default_action": { "type": "egress", "egress": "tokyo" }
    }
  },
  "egresses": {
    "tokyo": {
      "type": "upstream",
      "backend": { "kind": "reverse", "addr": "rove-hop-jp" }
    }
  }
}
  • 命中 block action 的目标:逐包丢弃,绝不出 hop。
  • 决策落到 Direct / HTTP 上游 / SOCKS5 上游的 UDP 包:fail-closed 丢弃(HTTP CONNECT 物理上载不了 UDP;Direct/SOCKS5-UDP 出口不在当前范围)。
  • 目标 hop 未声明 UDP 能力(旧版 hop):association 直接被拒(udp_unsupported)。
  • 决策落到出口链(chain)时:只在 chain 的 reverse 成员中按优先级尝试;chain 只有 HTTP/SOCKS5 成员时同样 fail-closed 丢弃。 association 建立后粘住选中的 hop,不逐包切换。UDP 主备需要 chain 里至少两个 reverse 成员。

适用场景是 client → server 的实时 UDP:WebRTC 连 SFU / 媒体服务器、OpenAI Realtime(WebRTC 变体)、游戏连专用服务器。详见 reverse/2 UDP relay

限制(当前范围)

  • 只做 native(QUIC datagram)UDP 模式,不做 quic-stream 可靠模式(会引入 HOL 阻塞,毁掉实时语义)。
  • 不做 UDP 分片重组:单个 UDP 包超过 QUIC datagram 上限(约 1200B)会被丢弃并计数。目标实时应用(WebRTC/DTLS/SRove、游戏网络码)本就 MTU 感知,不受影响。
  • 不支持 full-cone / 浏览器↔浏览器 P2P 打洞:一是需要更宽松的 NAT(攻击面大),二是出口 hop 自身往往也在 NAT 后,物理上打不通。client→server 场景不受此限。
  • UDP relay 不参与令牌桶限速;如需限额,先用连接/会话上限与目标端口约束兜底。

可观测性

  • 每条 TCP Connect 隧道结束写一条访问日志记录(protocol: "tuic",含用户、目标、决策、字节数、时长),复用现有 访问日志 管线,永不含密码。启用 sniff 后还会写 requested/sniffed/effective identity 与固定结果枚举;route 模式的策略阻断和出站失败也会落记录。
  • 连接建立/认证成功/失败通过结构化 tracing 日志输出。

客户端配置

客户端接入 · TUIC。要点:客户端的 uuid/password/alpn 必须与快照凭据和监听 alpn 对齐;自签名证书需在客户端开启「允许不安全 / skip-cert-verify」或导入 CA。