188 lines
4.9 KiB
Markdown
188 lines
4.9 KiB
Markdown
# Xray 透明代理与 DHT 直连处理
|
|
|
|
本文只适用于宿主机启用了 Xray TProxy 或其他全局透明代理的特殊部署环境
|
|
|
|
普通 Docker 主机直接使用根目录 `compose.yaml` 不需要这里的专用网络或 nftables 规则
|
|
|
|
## 为什么会发生
|
|
|
|
DHT 会向大量公网节点发送 UDP 查询 并尝试连接大量 Peer 的随机 TCP 端口
|
|
|
|
全局 TProxy 如果在 `prerouting` 中接管所有公网 TCP 和 UDP Docker 容器流量也会进入 Xray
|
|
|
|
Xray 即使最终选择直连出站仍然需要维护这些 UDP 会话 因此可能积累大量 socket 并让 Metadata TCP 连接失败
|
|
|
|
## 典型现象
|
|
|
|
- DHT 节点 Peer 和采样 infohash 持续增长
|
|
- Metadata 成功长期为零或远低于正常水平
|
|
- Metadata 失败几乎全部是连接失败
|
|
- Xray 文件描述符和宿主机 UDP socket 持续增长
|
|
- SSH 或其他新连接开始偶发断开
|
|
- `dht-search` 容器自身文件描述符并不高
|
|
|
|
仅看到 Metadata 超时不能直接判断为 Xray 问题 公网 DHT 本身就包含大量不可达 Peer
|
|
|
|
## 如何确认
|
|
|
|
查看服务统计
|
|
|
|
```shell
|
|
docker run --rm --network web-network curlimages/curl:8.16.0 -fsS http://dht-search:8080/stats
|
|
```
|
|
|
|
重点比较以下字段
|
|
|
|
```text
|
|
metadata_ok
|
|
metadata_failed
|
|
metadata_failure_connect
|
|
metadata_failure_timeout
|
|
metadata_in_flight
|
|
```
|
|
|
|
查看宿主机和 Xray socket
|
|
|
|
```shell
|
|
cat /proc/net/sockstat
|
|
|
|
xray_pid="$(pidof xray)"
|
|
find "/proc/${xray_pid}/fd" -maxdepth 1 -type l | wc -l
|
|
grep "Max open files" "/proc/${xray_pid}/limits"
|
|
```
|
|
|
|
查看 DHT 容器自身 socket
|
|
|
|
```shell
|
|
docker exec dht-search cat /proc/net/sockstat
|
|
docker exec dht-search sh -c 'ls /proc/1/fd | wc -l'
|
|
```
|
|
|
|
查看透明代理规则
|
|
|
|
```shell
|
|
nft list ruleset
|
|
ip rule show
|
|
```
|
|
|
|
如果存在类似规则 Docker 公网流量会进入 Xray
|
|
|
|
```nft
|
|
meta l4proto { tcp, udp } tproxy to :12345 meta mark set 1 accept
|
|
```
|
|
|
|
## 推荐解决方案
|
|
|
|
让 DHT 同时连接反向代理网络和独立直连网络
|
|
|
|
```yaml
|
|
networks:
|
|
web-network:
|
|
external: true
|
|
name: web-network
|
|
dht-direct:
|
|
name: dht-direct
|
|
driver: bridge
|
|
ipam:
|
|
config:
|
|
- subnet: 172.30.0.0/24
|
|
|
|
services:
|
|
dht-search:
|
|
networks:
|
|
web-network:
|
|
dht-direct:
|
|
gw_priority: 1
|
|
```
|
|
|
|
`web-network` 只负责 Caddy 到 `dht-search:8080`
|
|
|
|
`dht-direct` 具有更高网关优先级并负责 DHT 和 Peer 公网流量
|
|
|
|
部署前必须确认 `172.30.0.0/24` 没有与宿主机路由或其他 Docker 网络冲突
|
|
|
|
```shell
|
|
ip route show 172.30.0.0/24
|
|
docker network inspect dht-direct
|
|
```
|
|
|
|
## 配置 Xray 来源旁路
|
|
|
|
在 Xray 持久化 nftables 文件中增加来源集合
|
|
|
|
```nft
|
|
set bypass_source_ipv4 {
|
|
type ipv4_addr
|
|
flags interval
|
|
elements = {
|
|
172.30.0.0/24
|
|
}
|
|
}
|
|
```
|
|
|
|
在 `prerouting` 链进入 TProxy 之前增加
|
|
|
|
```nft
|
|
ip saddr @bypass_source_ipv4 return
|
|
```
|
|
|
|
必须修改 Xray 服务启动时实际加载的持久化文件 只向当前运行规则临时执行 `nft add rule` 会在重启后失效
|
|
|
|
## 安全实施顺序
|
|
|
|
1. 使用 `docker compose stop` 优雅停止 DHT
|
|
2. 备份 Compose 和 Xray nftables 文件
|
|
3. 增加 `dht-direct` 网络和来源旁路
|
|
4. 使用 `docker compose config --quiet` 校验 Compose
|
|
5. 使用 `nft -c -f <规则文件>` 校验 nftables
|
|
6. 重启 Xray 释放已经积累的旧会话
|
|
7. 使用 `docker compose up -d` 重新创建 DHT 容器
|
|
8. 验证默认网关 Metadata 成功率和 socket 数量
|
|
|
|
重启 Xray 会短暂中断现有代理连接 应选择允许短暂中断的时间执行
|
|
|
|
## 验收
|
|
|
|
确认容器同时加入两个网络且没有宿主机端口绑定
|
|
|
|
```shell
|
|
docker compose ps
|
|
docker inspect dht-search --format '{{json .HostConfig.PortBindings}}'
|
|
docker inspect dht-search --format '{{json .NetworkSettings.Networks}}'
|
|
docker exec dht-search cat /proc/net/route
|
|
```
|
|
|
|
默认网关应该属于 `dht-direct` 然后持续观察
|
|
|
|
- `metadata_ok` 和 `persistence_inserted` 开始增长
|
|
- Metadata 失败不再全部是连接失败
|
|
- Xray 文件描述符不再随 DHT 流量快速增长
|
|
- 宿主机 UDP socket 恢复到正常范围
|
|
- Caddy 仍能通过 `web-network` 访问服务
|
|
|
|
## 实际案例
|
|
|
|
一次远端 Debian 部署中修复前 Metadata 尝试超过一百万次但成功为零 Xray 文件描述符增长到约三十九万 主机 UDP socket 超过三十三万
|
|
|
|
增加独立直连网络并重启 Xray 后两分钟内 Metadata 成功 1222 条并入库 1218 条 Xray 文件描述符稳定在 12 主机 UDP socket 稳定在 6
|
|
|
|
## 不建议的处理
|
|
|
|
- 只提高 Xray `LimitNOFILE`
|
|
- 只增加机器内存
|
|
- 只降低 Metadata 并发
|
|
- 让 DHT 流量进入 Xray 后再选择 direct outbound
|
|
|
|
这些方法只能延迟资源耗尽或仍然让 Xray 维护海量 UDP 会话 不能消除错误流量边界
|
|
|
|
## 回滚
|
|
|
|
如果专用网络启动失败
|
|
|
|
1. 停止 DHT 容器
|
|
2. 恢复原 Compose 和 nftables 备份
|
|
3. 校验并重启 Xray
|
|
4. 保持 DHT 停止直到重新确认路由方案
|
|
|
|
不要在恢复了全局 TProxy 且没有来源旁路的情况下重新启动高并发 DHT
|