server_config: 新增 UoT v2(UDP over TCP)支持

全节点(jp/us/ru/aq/hk2)已部署并实测通过(UoT DNS 端到端 ✅ + TCP 回归 ✅):

- nginx 模板:map $request 识别 sing-box UoT v2 magic 地址
  (sp.v2.udp-over-tcp.arpa)→ 127.0.0.1:15201;default_server 块加
  proxy_connect_address;read/send timeout 10m
- 新增 uot-relay/:Python 标准库 UoT v2 relay(connect/non-connect 双模式,
  仅监听 127.0.0.1,空闲 9 分钟断开)
- README 5.5 节:部署方法 + 客户端协议 + 四个实测坑
  ($connect_host 未注册变量 / data_timeout 与 read_timeout 同槽 /
  CONNECT 恒落 default_server / 拒绝端口 0)
This commit is contained in:
RRRosmontis
2026-09-19 00:31:41 +08:00
parent e16ef58182
commit 2e56f82e7b
4 changed files with 259 additions and 4 deletions
+51
View File
@@ -267,6 +267,49 @@ chmod 644 /etc/nginx/.htpasswd # worker 以 nobody 运行,必须可读
---
### 5.5 UoT(UDP over TCP)支持
HTTP CONNECT 只隧道 TCP。要让 UDP 应用(DNS/游戏/QUIC 等)走代理,需 **UoT v2**
(SagerNet 专有协议,见 sing-box 文档 `configuration/shared/udp-over-tcp`):
客户端把 UDP 数据报封装进 TCP 流,经 CONNECT 隧道发到 magic 地址
`sp.v2.udp-over-tcp.arpa`,由本机 **UoT relay** 解封装后转发真实 UDP 目标。
```
客户端 TUN/UDP ──UoT v2──▶ TLS HTTP CONNECT ──▶ nginx proxy_connect
──▶ 127.0.0.1:15201(uot-relay)──▶ 真实 UDP 目标
```
**relay 部署**(Python 标准库实现,见 [uot-relay/uot_relay.py](uot-relay/uot_relay.py)):
```bash
install -d /opt/rrpp-uot-relay
install -m 755 uot-relay/uot_relay.py /opt/rrpp-uot-relay/uot_relay.py
install -m 644 uot-relay/uot-relay.service /etc/systemd/system/uot-relay.service
systemctl daemon-reload && systemctl enable --now uot-relay
```
relay 只监听 `127.0.0.1:15201`,空闲 9 分钟断开(须小于 nginx 隧道超时 10m)。
支持 UoT v2 两种模式:`isConnect=1` 固定目标、`isConnect=0` 每帧自带目标。
**nginx 侧**(模板已含,[nginx/nginx.conf](nginx/nginx.conf)):`map $request` 识别
magic 地址 → `127.0.0.1:15201`,其余 CONNECT 走 `$connect_addr` 默认解析;
`proxy_connect_address $rrpp_uot_connect_address;` 放在 **default_server 块**。
**四个实测坑(2026-09 全节点部署验证)**:
1. 该模块版本**未注册 `$connect_host` 变量**(README 有、源码无)→ 必须用
`$request` 正则匹配(任何阶段可用);
2. `proxy_connect_data_timeout` 与 `proxy_connect_read_timeout` **写同一配置槽**,
同时写报 duplicate → 长连接超时用 read/send timeout 设 10m;
3. **所有 CONNECT 请求都落在 `default_server`(fallback)块**——nginx 按请求
Host 头选虚拟主机,CONNECT 的 Host 是目标域名,永远匹配不到 `server_name`;
4. 模块**拒绝 CONNECT 端口 0**(`port < 1` 返回 400)→ 客户端连 magic 地址时
端口必须非 0(用 443 即可,relay 不读 CONNECT 端口,目标取自 UoT 帧头)。
**客户端协议**:认证与 TCP CONNECT 相同(TLS + Basic Auth),然后
`CONNECT sp.v2.udp-over-tcp.arpa:443 HTTP/1.1`,200 之后收发 UoT v2 帧:
请求头 `| isConnect u8 | ATYP u8 | address | port u16be |`,
connect 模式帧 `| length u16be | data |`,non-connect 帧每帧自带目标地址。
## 6. sing-box(VLESS+REALITY / Shadowsocks)
### 6.1 安装
@@ -378,6 +421,7 @@ mtg simple-run --prefer-ip=prefer-ipv4 0.0.0.0:<mtproto端口> <上面生成的s
| nginx | [systemd/nginx.service](systemd/nginx.service) |
| sing-box | [systemd/sing-box.service](systemd/sing-box.service) |
| mtg | [systemd/mtg.service](systemd/mtg.service) |
| uot-relay | [uot-relay/uot-relay.service](uot-relay/uot-relay.service) |
```bash
cp systemd/*.service /etc/systemd/system/
@@ -396,6 +440,7 @@ sysctl --system # 1. BBR
systemctl start sing-box # 2. VLESS/SS
systemctl start mtg # 3. MTProto
systemctl start nginx # 4. nginx(最后,依赖证书/伪装页/规则文件)
systemctl start uot-relay # 5. UoT relay(须在 nginx 前可用)
```
### 验证清单
@@ -419,6 +464,9 @@ s=socket.connect(('<节点域名>',<mtproto端口>)) if False else socket.create
s.sendall(b'\x16\x03\x01'+struct.pack('>H',100)+os.urandom(100))
print('resp:',s.recv(8).hex())" # 期望首字节 15 (TLS alert)
# UoT relay(127.0.0.1:15201 监听 + CONNECT magic 地址)
ss -tlnp | grep 15201 # => uot_relay.py 监听 127.0.0.1:15201
# nginx CONNECT(带 Basic Auth,B64 = base64('<代理用户名>:<代理密码>'))
B64=$(printf '<代理用户名>:<代理密码>' | base64 -w0)
curl -sk --proxy-insecure -x https://<节点域名>:<nginx端口> \
@@ -440,6 +488,9 @@ server_config/
│ ├── nginx.conf ← 完整 nginx 配置(占位符版)
│ ├── local_ip_rules.conf.example ← 本机 IP 规则文件示例(cron 自动生成,勿手改)
│ └── conf.d/ ← 预留:额外站点配置
├── uot-relay/
│ ├── uot_relay.py ← UoT v2 relay(UDP over TCP 解封装)
│ └── uot-relay.service ← systemd unit
├── sing-box/
│ └── config.json ← sing-box 配置(VLESS+REALITY + SS,占位符版)
├── systemd/