- 五节点撤除 Hysteria2/TUIC 入站,sing-box 恢复 vless+ss 原状(UDP 入站不再监听) - §2.6 UDP 策略定稿:不部署 UDP 传输服务,UDP 需求走 UoT;保留 UDP 系统调优项 - 记录撤除理由(非标 UDP 端口封禁代价近零、CN 侧 QoS、IP 被烧会连带打死 TCP 服务);明确加密性无问题、风险在可封禁面 - §6.4 标注「已回滚,本节为留档」,保留配置片段/各节点 NAT·NSG 差异/端到端验证方法 - §10 验证清单中的 UDP 项标注仅适用于重新启用场景
850 lines
42 KiB
Markdown
850 lines
42 KiB
Markdown
# RRPP 服务端部署规范
|
||
|
||
> 一套统一的跨境代理节点部署流程,适用于 Debian/Ubuntu 系 Linux。
|
||
> 每台节点运行相同的服务栈:**BBR 拥塞控制 + nginx CONNECT 隧道 + sing-box(VLESS+REALITY / Shadowsocks) + mtg(MTProto)**。
|
||
>
|
||
> 本文档为**通用规范**,所有 IP / 域名 / 端口 / 密钥均以**占位符**表示,部署时替换为实际值。
|
||
> 凡标注 `<...>` 的均为占位符,不要原样照抄。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [0. 架构总览](#0-架构总览)
|
||
- [1. 端口与服务规划](#1-端口与服务规划)
|
||
- [2. 系统基础优化(BBR 跨境丢包优化)](#2-系统基础优化bbr-跨境丢包优化)
|
||
- [3. 伪装站点页面](#3-伪装站点页面)
|
||
- [4. HTTPS 证书(acme.sh DNS-01)](#4-https-证书acmesh-dns-01)
|
||
- [5. nginx 源码编译 + CONNECT 隧道](#5-nginx-源码编译--connect-隧道)
|
||
- [6. sing-box(VLESS+REALITY / Shadowsocks)](#6-sing-boxvlessreality--shadowsocks)
|
||
- [7. mtg(MTProto 代理)](#7-mtgmtproto-代理)
|
||
- [8. 本机公网 IP 自动识别(防自代理回环)](#8-本机公网-ip-自动识别防自代理回环)
|
||
- [9. systemd 服务文件](#9-systemd-服务文件)
|
||
- [10. 启动顺序与验证清单](#10-启动顺序与验证清单)
|
||
- [11. 目录结构与文件索引](#11-目录结构与文件索引)
|
||
- [12. RRPP 订阅后端(PAC → 规则集下发)](#12-rrpp-订阅后端pac--规则集下发)
|
||
|
||
---
|
||
|
||
## 0. 架构总览
|
||
|
||
```
|
||
客户端 ──► nginx(443/<nginx端口>) ──┬─► CONNECT 隧道(任意端口, 全端口放行)
|
||
│ └─► 伪装页面(直接访问/IP访问)
|
||
│
|
||
├─► sing-box(<vless端口>) ──► VLESS+REALITY(vision)
|
||
├─► sing-box(<ss端口>) ──► Shadowsocks(2022-blake3-aes-256-gcm)
|
||
└─► mtg(<mtproto端口>) ──► MTProto(Fake TLS, Telegram 内置代理)
|
||
```
|
||
|
||
**四层代理各司其职:**
|
||
|
||
| 层 | 组件 | 协议 | 适用场景 |
|
||
|---|---|---|---|
|
||
| HTTP 隧道 | nginx | CONNECT + Basic Auth | 浏览器/curl/系统代理,全端口 WebSocket 透传 |
|
||
| 通用代理 | sing-box | VLESS+REALITY | Shadowrocket/Clash/v2rayN 客户端 |
|
||
| 兼容旧客户端 | sing-box | Shadowsocks | Shadowrocket 2.x 等不支持 REALITY 的客户端 || Telegram 专用 | mtg | MTProto Fake TLS | Telegram 内置代理,抗封锁 |
|
||
|
||
**核心密钥体系(2026-09 起全部每节点独立):**
|
||
- VLESS+REALITY:每节点**独立** UUID、REALITY 密钥对、short_id;SNI 仍全节点统一(用 `<伪装SNI域名>`,选一个高流量、TLS 指纹稳定的知名站点域名)
|
||
- Shadowsocks:每节点**独立** PSK,方法 `2022-blake3-aes-256-gcm`(SIP022,抗重放探测)
|
||
- nginx CONNECT:每节点**独立** Basic Auth 密码(客户端按节点携带凭据,无兼容性问题)
|
||
- MTProto:每台**独立 secret**(Fake TLS 类型,fronting 到 `<伪装SNI域名>`)
|
||
|
||
> 独立化的目的:单节点密钥泄漏/被污染时不波及其余节点,且换钥粒度更小。
|
||
> 代价是订阅/nodes.json 必须逐节点维护,改任一项后必须做端到端验证(见 6.3、第 10 节)。
|
||
|
||
---
|
||
|
||
## 1. 端口与服务规划
|
||
|
||
端口全部落在**同一区段**便于防火墙管理(示例用 1200–1220,可按需调整):
|
||
|
||
| 端口 | 服务 | 说明 |
|
||
|---|---|---|
|
||
| `<nginx端口>` | nginx | HTTPS 站点 + CONNECT 隧道(示例 `1205`) |
|
||
| `<vless端口>` | sing-box | VLESS+REALITY(示例 `1206`) |
|
||
| `<ss端口>` | sing-box | Shadowsocks(示例 `1207`) |
|
||
| `<mtproto端口>` | mtg | MTProto(示例 `1210`) |
|
||
|
||
> 节点 A 用 `80xx` 区段、节点 B 用 `120xx` 区段皆可,**只要同节点内不冲突、且运营商/防火墙放行**。
|
||
> 防火墙放行:`iptables -A INPUT -p tcp -m multiport --dports <nginx端口>,<vless端口>,<ss端口>,<mtproto端口> -j ACCEPT`
|
||
|
||
**域名规划:** 每个节点一个独立域名 `<节点域名>`(如 `node1.example.com`),A 记录指向节点公网 IP。acme.sh 用 DNS-01 签证书,无需 80 端口可达。
|
||
|
||
---
|
||
|
||
## 2. 系统基础优化(BBR 跨境丢包优化)
|
||
|
||
BBR(Bottleneck Bandwidth and RTT)+ fq 队列,显著改善跨境 TCP 丢包/高延迟下的吞吐。
|
||
|
||
### 2.1 启用 BBR
|
||
|
||
写入 `/etc/sysctl.d/99-bbr.conf`:
|
||
|
||
```conf
|
||
net.core.default_qdisc=fq
|
||
net.ipv4.tcp_congestion_control=bbr
|
||
```
|
||
|
||
应用并验证:
|
||
|
||
```bash
|
||
modprobe tcp_bbr 2>/dev/null || true # 部分内核需手动加载模块
|
||
sysctl --system
|
||
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
|
||
# 期望输出: bbr / fq
|
||
```
|
||
|
||
> **内核要求**:BBR 需 Linux 4.9+。`default_qdisc=fq` 在部分新内核(6.12+ cloud 内核)可能无此参数,仅保留 `tcp_congestion_control=bbr` 即可,不影响 BBR 生效。
|
||
|
||
### 2.2 文件描述符
|
||
|
||
nginx worker 高并发需要提高 limit,在 `/etc/security/limits.conf` 追加:
|
||
|
||
```
|
||
* soft nofile 65535
|
||
* hard nofile 65535
|
||
```
|
||
|
||
### 2.3 时区与 NTP
|
||
|
||
代理证书校验、日志排障都依赖准确时间:
|
||
|
||
```bash
|
||
timedatectl set-timezone Asia/Shanghai
|
||
apt install -y chrony && systemctl enable --now chrony
|
||
```
|
||
|
||
### 2.4 PMTUD 黑洞缓解(tcp_mtu_probing)
|
||
|
||
**症状**:部分客户端网络上出现"TCP 能连上、但 TLS 握手稳定超时"
|
||
(如 `_ssl.c:1011 handshake operation timed out`),而服务器从其他网络探测一切正常。
|
||
原因是客户端↔服务器路径上某跳 MTU 变小且 ICMP Fragmentation Needed 被过滤
|
||
(PMTUD 黑洞),服务器发出的满尺寸大包永远到不了客户端。
|
||
|
||
开启 RFC 4821 PLPMTUD,让服务器在探测失败后自动降低 MSS 恢复通信。
|
||
写入 `/etc/sysctl.d/99-mtu-probing.conf`:
|
||
|
||
```conf
|
||
net.ipv4.tcp_mtu_probing = 1
|
||
```
|
||
|
||
```bash
|
||
sysctl --system
|
||
sysctl net.ipv4.tcp_mtu_probing # 期望输出: 1
|
||
```
|
||
|
||
> 只影响命中黑洞的连接,正常连接无感知,可安全全节点开启。
|
||
|
||
### 2.5 fail2ban(sshd 爆破防护)
|
||
|
||
节点 SSH 暴露在公网,爆破尝试是常态(实测单节点累计 3 万+ 次)。装 fail2ban 护 sshd:
|
||
|
||
```bash
|
||
apt install -y fail2ban
|
||
cat > /etc/fail2ban/jail.d/sshd.local << 'EOF'
|
||
[sshd]
|
||
enabled = true
|
||
backend = systemd
|
||
EOF
|
||
systemctl enable --now fail2ban
|
||
fail2ban-client status sshd # 确认 jail 在跑
|
||
```
|
||
|
||
> - `backend = systemd`:无 `/var/log/auth.log` 的最小化系统(无 rsyslog)必须加这行,
|
||
> 否则 fail2ban 启动报 "Have not found any log file for sshd jail"。
|
||
> - LXC 容器也能正常封禁(实测 ban/unban 链路可用)。
|
||
> - 只加 fail2ban、**不动 sshd 认证方式**(root 登录等现状保留)。
|
||
> - 如需同时护 nginx CONNECT 的 401 爆破,可再加 nginx jail(凭据够强时可不做)。
|
||
|
||
### 2.6 系统级网络优化(KVM / LXC 差异)与 qdisc 持久化
|
||
|
||
> 模板:[sysctl/99-zz-rrpp-network.kvm.conf](sysctl/99-zz-rrpp-network.kvm.conf)(完整 VM,含 Azure 等)、
|
||
> [sysctl/99-zz-rrpp-network.lxc.conf](sysctl/99-zz-rrpp-network.lxc.conf)(容器)
|
||
> 落地位置统一为 `/etc/sysctl.d/99-zz-rrpp-network.conf`;
|
||
> qdisc 伴侣:[systemd/rrpp-qdisc.service](systemd/rrpp-qdisc.service) + [scripts/rrpp-qdisc.sh](scripts/rrpp-qdisc.sh)。
|
||
|
||
```bash
|
||
# KVM / 完整 VM
|
||
install -m 644 sysctl/99-zz-rrpp-network.kvm.conf /etc/sysctl.d/99-zz-rrpp-network.conf
|
||
# LXC / OpenVZ 容器
|
||
install -m 644 sysctl/99-zz-rrpp-network.lxc.conf /etc/sysctl.d/99-zz-rrpp-network.conf
|
||
# qdisc 持久化(仅完整 VM 有意义;容器内网卡是 noqueue,脚本会自动跳过)
|
||
install -m 755 scripts/rrpp-qdisc.sh /usr/local/sbin/rrpp-qdisc.sh
|
||
install -m 644 systemd/rrpp-qdisc.service /etc/systemd/system/
|
||
systemctl daemon-reload && systemctl enable --now rrpp-qdisc
|
||
sysctl --system
|
||
```
|
||
|
||
**为什么文件名是 `99-zz-`**:sysctl 按文件名字典序加载、**后者覆盖前者**。`99-zz-` 保证本文件排在最后,
|
||
避免被其它 `99-*.conf` 覆盖。
|
||
|
||
**⚠ 最容易踩的坑:`/etc/sysctl.conf`**。`sysctl --system` 会在**所有 sysctl.d 之后**再加载 `/etc/sysctl.conf`。
|
||
2026-09 实测:某节点(aq)存在 2022 年的一键优化版 `/etc/sysctl.conf`,把 `tcp_mtu_probing` 覆盖回 `0`,
|
||
导致 sysctl.d 里的设置完全不生效(现象:`sysctl -n` 与文件内容不一致)。
|
||
**处理**:该文件里凡是与本规范重叠的键,逐行注释掉(保留其非冲突的调优,如 conntrack、rps、swappiness),
|
||
或直接把本规范内容追加到该文件末尾。排查方法:
|
||
|
||
```bash
|
||
sysctl -p /etc/sysctl.d/99-zz-rrpp-network.conf # 显式加载后应看到期望值
|
||
sysctl --system 2>/dev/null | grep -n tcp_mtu_probing | tail -5 # 看谁在最后覆盖
|
||
```
|
||
|
||
**KVM / 完整 VM 可写(本规范设置项)**
|
||
|
||
| 项 | 值 | 作用 |
|
||
|---|---|---|
|
||
| `tcp_congestion_control` / `core.default_qdisc` | `bbr` / `fq` | BBR 需 fq 搭配,否则 bufferbloat |
|
||
| `tcp_mtu_probing` | 1 | PMTUD 黑洞缓解(见 2.4) |
|
||
| `tcp_fastopen` | 3 | 客户端+服务端 TFO,省一次 RTT |
|
||
| `tcp_slow_start_after_idle` | 0 | 空闲后不回退慢启动(长连接代理有益) |
|
||
| `tcp_tw_reuse` / `tcp_fin_timeout` | 2 / 15 | TIME-WAIT 复用与回收 |
|
||
| `tcp_keepalive_time/intvl/probes` | 300 / 30 / 5 | 及时清理死连接(默认 7200 过长) |
|
||
| `ip_local_port_range` | 10240 65000 | 出站端口范围,防高并发端口耗尽 |
|
||
| `core.somaxconn` / `tcp_max_syn_backlog` | 32768 | accept / 半连接队列 |
|
||
| `core.netdev_max_backlog` | 32768 | 网卡收包队列(默认 1000 易突发丢包) |
|
||
| `core.rmem_max` / `wmem_max` | 33554432 | socket 缓冲上限(默认 208KB 偏小) |
|
||
| `tcp_rmem` / `tcp_wmem` | 4K/128K/32M、4K/16K/32M | 自动调优范围 |
|
||
|
||
**LXC / OpenVZ 容器不可用(由宿主机控制,实测 2026-09)**
|
||
|
||
| 项 | 现象 |
|
||
|---|---|
|
||
| `core.rmem_max` / `core.wmem_max` | **不存在**或 `-r--r--r--` 只读 |
|
||
| `core.default_qdisc` | **不存在**(`sysctl` 报 cannot stat) |
|
||
| `core.netdev_max_backlog` | 缺失 |
|
||
| 默认路由网卡根 qdisc | `noqueue`,**无法设置 fq** |
|
||
| `/usr/lib/sysctl.d` 的 `kernel.pid_max`、`fs.protected_*` | `permission denied`——容器内属**正常现象**,与本文档无关 |
|
||
|
||
> 结论:容器节点只能调 **netns 级**项(拥塞算法、MTU 探测、TFO、TW 复用、keepalive、
|
||
> somaxconn、syn backlog、本地端口范围);socket 缓冲上限与 qdisc 需服务商在**宿主机**设置。
|
||
> 因此容器节点想进一步提升,应优先在**应用层/传输层**做(例如走 UoT 而不是直连 UDP)。
|
||
|
||
**qdisc 为什么必须单独用 systemd 单元**:`net.core.default_qdisc=fq` **只对新建网卡生效**;
|
||
已存在的网卡即使重启也可能仍是 `pfifo_fast` / `fq_codel`(实测:aq 为 `pfifo_fast`、us 为 `fq_codel`)。
|
||
`rrpp-qdisc.service` 在开机后显式执行 `tc qdisc replace dev <默认路由网卡> root fq` 补齐;
|
||
脚本对容器(`noqueue`)会自动跳过。
|
||
|
||
**UDP 策略(2026-09-20 定稿)**:UDP 传输(Hysteria2 / TUIC)曾于当日全节点部署并端到端验证,
|
||
但基于「**抗封优先**」的评估,**当晚已全部撤除**(详情与留档见 §6.4)。现行口径:
|
||
|
||
- **节点不部署 UDP 传输服务**;UDP 需求统一走 **UoT v2**(§5.5)——数据报封装进 TCP CONNECT 隧道,
|
||
经 nginx magic 地址转本机 `127.0.0.1:15201` relay 解封装;
|
||
- 抗封主路径 = **REALITY + Vision(TCP)**,不做 fallback 组(订阅仍为 `select`);
|
||
- 节点上的 **UDP 系统调优项保留**(无害,且对 UoT / SS-UDP 有轻微收益)。
|
||
|
||
撤除理由(实测 + 推演):非标准 UDP 端口被封对 GFW **几乎零代价**(不像封 TCP 443 会误伤大量正常 HTTPS);
|
||
UDP 在 CN 长期被 QoS/限速;且一旦 IP 因 UDP 特征被判为代理,会**连带打死同 IP 上的 TCP 服务**(REALITY/SS/nginx)。
|
||
加密性本身没有问题(QUIC=TLS1.3,实测证书校验强制、错凭据被拒)——风险在**可封禁面**,不在密码学。
|
||
|
||
节点侧 UDP 相关系统调优(保留):
|
||
|
||
| 项 | 值 / 说明 |
|
||
|---|---|
|
||
| `net.ipv4.udp_rmem_min` / `udp_wmem_min` | `8192`(容器内可写,实测 LXC 同样生效) |
|
||
| `net.core.rmem_max` / `wmem_max` | `33554432`(KVM 可写;**LXC 缺失/只读**) |
|
||
| `rx-udp-gro-forwarding` / `rx-gro-list`(ethtool) | 由 `rrpp-qdisc.sh` 开机开启;**实测支持情况**:us/jp ✅ 两项;aq(内核 5.10)仅 `rx-gro-list`;**LXC 容器无 netdev 权限,无法设置** |
|
||
| QUIC/UDP 吞吐 | 受宿主机 socket 缓冲上限约束——**LXC 节点上这是硬约束**,需服务商在宿主调 `rmem_max` |
|
||
|
||
**明确不采用的手段**:锐速/LotServer(闭源、绑定旧内核)、net-speeder(重复包、已废弃)、
|
||
`tcp_tw_recycle`(4.12 起内核已移除,开启会破坏 NAT 后客户端)、`tcp_notsent_lowat` 激进值
|
||
(可能压制高 BDP 吞吐)、`busy_poll`/`busy_read`(1 vCPU 机器上代价大于收益)、各类"一键优化脚本"
|
||
(多为本文档子集 + 心理安慰,且常与 sysctl.d 冲突)。
|
||
|
||
**落地状态(2026-09-20)**
|
||
|
||
| 节点 | 虚拟化 | 落地情况 |
|
||
|---|---|---|
|
||
| hk2 | LXC(Debian 12) | LXC 模板;`bbr` + `mtu_probing=1` + `fastopen=3` + 队列/端口项;缓冲与 qdisc 受宿主限制 |
|
||
| ru | LXC(Debian 13 cloud 内核) | 同上(`rmem_max`/`wmem_max` 只读) |
|
||
| us | KVM(Debian 12) | KVM 模板 + fq(原 `fq_codel`) |
|
||
| aq | KVM(Debian 11 cloud) | KVM 模板 + fq(原 `pfifo_fast`);并注释其 `/etc/sysctl.conf` 中 17 行冲突键(原 `tcp_mtu_probing=0`) |
|
||
| jp | Azure VM(Ubuntu 6.17) | KVM 模板(经 `sudo` 落地;原有 `99-tune.conf` 保留) |
|
||
|
||
---
|
||
|
||
## 3. 伪装站点页面
|
||
|
||
直接访问节点/IP 时返回一个普通网页(而非代理错误),降低被探测的特征。
|
||
|
||
放置一个静态 `index.html` 到 `/var/www/html/index.html`:
|
||
- 任意抓取的合法 HTML 页面(博客/门户首页快照均可)
|
||
- **所有节点用同一份**(md5 一致),避免逐节点特征差异
|
||
|
||
```bash
|
||
mkdir -p /var/www/html
|
||
# index.html 为本仓库 fake-site/index.html 的拷贝
|
||
cp fake-site/index.html /var/www/html/index.html
|
||
```
|
||
|
||
---
|
||
|
||
## 4. HTTPS 证书(acme.sh DNS-01)
|
||
|
||
nginx 站点与 CONNECT 隧道共用一张证书。用 [acme.sh](https://github.com/acmesh-official/acme.sh) + DNS-01(以阿里云 DNS 为例,其他 DNS 商同理):
|
||
|
||
```bash
|
||
curl https://get.acme.sh | sh -s email=<你的邮箱>
|
||
export Ali_Key="<阿里云AccessKey>"
|
||
export Ali_Secret="<阿里云Secret>"
|
||
|
||
~/.acme.sh/acme.sh --issue --dns dns_ali -d <节点域名> --keylength ec-256
|
||
|
||
# 安装到 nginx 读取路径
|
||
mkdir -p /etc/nginx/ssl/<节点域名>
|
||
~/.acme.sh/acme.sh --install-cert -d <节点域名> --ecc \
|
||
--fullchain-file /etc/nginx/ssl/<节点域名>/fullchain.pem \
|
||
--key-file /etc/nginx/ssl/<节点域名>/privkey.pem
|
||
```
|
||
|
||
acme.sh 自带 cron 自动续期。证书路径固定为 `/etc/nginx/ssl/<节点域名>/{fullchain,privkey}.pem`。
|
||
|
||
---
|
||
|
||
## 5. nginx 源码编译 + CONNECT 隧道
|
||
|
||
标准 nginx 不自带 CONNECT 方法支持,需打 [ngx_http_proxy_connect_module](https://github.com/chobits/ngx_http_proxy_connect_module) 补丁后**源码编译**。
|
||
|
||
### 5.1 编译
|
||
|
||
```bash
|
||
apt install -y build-essential libssl-dev libpcre3-dev zlib1g-dev curl wget
|
||
|
||
NGINX_VER=1.26.3
|
||
cd /usr/local/src
|
||
curl -LO http://nginx.org/download/nginx-$NGINX_VER.tar.gz
|
||
tar xzf nginx-$NGINX_VER.tar.gz
|
||
|
||
git clone https://github.com/chobits/ngx_http_proxy_connect_module.git
|
||
cd nginx-$NGINX_VER
|
||
patch -p1 < ../ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_102101.patch
|
||
|
||
./configure \
|
||
--prefix=/usr/local/nginx \
|
||
--conf-path=/etc/nginx/nginx.conf \
|
||
--pid-path=/var/run/nginx.pid \
|
||
--error-log-path=/var/log/nginx/error.log \
|
||
--http-log-path=/var/log/nginx/access.log \
|
||
--with-http_ssl_module \
|
||
--with-http_v2_module \
|
||
--with-http_realip_module \
|
||
--with-http_stub_status_module \
|
||
--with-http_gzip_static_module \
|
||
--add-module=../ngx_http_proxy_connect_module
|
||
make -j$(nproc) && make install
|
||
```
|
||
|
||
> 若发行版 nginx 已自带 proxy_connect 补丁(极少见),可用 `apt install nginx` 跳过编译,但源码编译是通用做法。
|
||
|
||
### 5.2 Basic Auth 凭据
|
||
|
||
CONNECT 代理需要 Basic Auth(用户名/密码),**每节点独立密码**(客户端配置按节点携带凭据,无兼容性问题)。生成 htpasswd:
|
||
|
||
```bash
|
||
apt install -y apache2-utils
|
||
htpasswd -bc /etc/nginx/.htpasswd <代理用户名> '<本节点独立密码>'
|
||
chmod 644 /etc/nginx/.htpasswd # worker 以 nobody 运行,必须可读
|
||
```
|
||
|
||
> 无 apache2-utils 的机器可用 openssl 生成(sha512-crypt,nginx 同样支持):
|
||
> `echo "<用户名>:$(openssl passwd -6 '<密码>')" > /etc/nginx/.htpasswd`
|
||
> htpasswd 是**每次请求实时读取**的,改密码无需 reload nginx。
|
||
|
||
### 5.3 nginx.conf
|
||
|
||
完整配置见 [nginx/nginx.conf](nginx/nginx.conf)。**关键设计**:
|
||
|
||
- **两个 server 块共用 `<nginx端口>`**:
|
||
- 主块 `server_name <节点域名>`:证书站点 + `location /` 返回伪装页;非本域名一律 rewrite 到 `/__proxy` 转发
|
||
- fallback 块 `default_server`:其他 SNI 也接受 CONNECT 代理;本机/私网/公网 IP 直连返回伪装页,其余走 `/__proxy`
|
||
- `proxy_connect_allow all;`:**全端口** CONNECT 放行(codex/WebSocket 走任意端口均通)。若需限制,改回 `proxy_connect_allow 443 563;`
|
||
- `auth_basic`:仅 CONNECT/正向代理需要认证,`location /` 伪装页 `auth_basic off`
|
||
- **自代理回环保护**:`/__proxy` 转发时带 `X-Proxy-Loop-Guard` 头,二次进入返回 508 防死循环
|
||
- `/__local`:本机 IP 直连时剥前缀返回伪装页
|
||
- `map $host $is_local`:私网/回环段判定;公网 IP 由 cron 脚本动态写入 `local_ip_rules.conf`
|
||
|
||
### 5.4 自代理回环保护说明
|
||
|
||
正向代理转发目标若指向**本机自己**(如域名公网 IP 变更、或代理目标就是本节点),会无限循环。两层防护:
|
||
1. `map` 判定私网/回环段 + cron 维护的本机公网 IP 规则 → 直连伪装页
|
||
2. `/__proxy` 的 Loop-Guard 头兜底 → 二次进入返回 508
|
||
|
||
### 5.5 路线 B 裸 UoT relay
|
||
|
||
TUN 客户端的 UDP 会先通过认证 CONNECT 请求
|
||
`sp.v2.udp-over-tcp.arpa:443`。当前 hk2 使用的
|
||
`ngx_http_proxy_connect_module` 没有注册 `$connect_host`,因此在 fallback
|
||
server 实际接收的 CONNECT 请求上用 `$request` 正则匹配,再把连接转到
|
||
`127.0.0.1:15201`;nginx 只负责 TLS、Basic Auth 和字节流转发,不解析 UoT。relay 使用仓库中的
|
||
`[uot_relay.py](uot_relay.py)`,只监听回环地址,不需要 sing-box:
|
||
|
||
```bash
|
||
install -m 755 uot_relay.py /opt/rrpp-uot-relay/uot_relay.py
|
||
install -m 644 systemd/uot-relay.service /etc/systemd/system/
|
||
systemctl daemon-reload
|
||
systemctl enable --now uot-relay
|
||
ss -lntp | grep 127.0.0.1:15201
|
||
nginx -t && systemctl reload nginx
|
||
```
|
||
|
||
客户端的 CONNECT 端口必须大于 0,当前统一使用 `443`;`15201` 是服务器回环
|
||
relay 的内部端口,不写入客户端 CONNECT 目标。nginx fallback 块使用
|
||
`proxy_connect_read_timeout 10m` 和 `proxy_connect_send_timeout 10m`,不要再同时
|
||
配置会产生 duplicate 的 `proxy_connect_data_timeout`。
|
||
|
||
---
|
||
|
||
### 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 帧头)。
|
||
|
||
**健壮性要求(2026-09 实测修复,别踩)**:
|
||
- relay 必须用**未连接**的 UDP socket(`sendto`/`recvfrom`)。用连接式 socket 时,
|
||
目标侧 ICMP(port unreachable 等)会变成 `ConnectionRefusedError` 在 send/recv 上抛出,
|
||
若被兜底 `except` 接住并 `stop.set()`,**一条数据报出错就杀掉整条 UoT 隧道**
|
||
(客户端表现为 "UoT TCP 流已关闭"、多条隧道同秒集体死亡)。
|
||
- 数据报级错误(`BlockingIOError`/EAGAIN、`ENETUNREACH`、`EHOSTUNREACH`、`EMSGSIZE`)
|
||
**只丢当前包**,EAGAIN 先等可写再重试;只有 TCP EOF、协议错误、空闲超时才关隧道。
|
||
- 按地址族惰性建 socket,non-connect 模式可混用 IPv4/IPv6 目标;IPv6 `recvfrom`
|
||
返回 4 元组需归一化。
|
||
- connect 模式要**预建** socket(否则主循环首轮无可 select 对象,会立刻关连接)。
|
||
|
||
**节点出口差异(实测)**:RU 节点到境外 DNS(8.8.8.8:53)UDP 成功率仅约 4/10
|
||
(俄 ISP 限制),表现为 UoT DNS 偶发超时——属该节点出口特性,非 relay 故障。
|
||
此类节点的 UDP 业务建议换用当地可达目标,或走其它节点。
|
||
|
||
**客户端协议**:认证与 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 帧每帧自带目标地址。
|
||
|
||
### 5.6 CONNECT 超时口径(避免客户端"卡死")
|
||
|
||
三个超时要按"客户端最没耐心"来配:
|
||
|
||
| 项 | 值 | 说明 |
|
||
|---|---|---|
|
||
| `proxy_connect_connect_timeout` | **10s** | 出站连接目标;须 < 客户端超时(lite 客户端 15s) |
|
||
| `proxy_connect_read_timeout` / `send_timeout` | 10m | 隧道空闲超时(UoT 长连接需要;注意与 data_timeout 同槽位) |
|
||
|
||
**典型故障**:目标 IP 是黑洞(不发 RST)时,若 nginx 出站超时设 60s 而客户端 15s 超时,
|
||
客户端会表现为 `The read operation timed out` 而不是收到 502 —— 看起来像"代理卡死",
|
||
实际是超时错配。2026-09 实测:改为 10s 后,黑洞目标从 >25s 降至 10.2s 返回,
|
||
正常目标(0.4~0.7s)不受影响。
|
||
|
||
**排查"CONNECT 502"的正确顺序**(重要):
|
||
1. 先看目标是否**本身就不可达**——从多个网络(国内/日本/美国/香港)实测该 IP:端口,
|
||
若都失败则是目标问题(服务下线/过期 IP/黑洞),不是代理问题;
|
||
2. 再看 nginx error.log:`Connection refused` / `timed out` / `Network is unreachable`
|
||
分别对应目标拒绝、目标黑洞、路由不可达;
|
||
3. 最后才怀疑 CONNECT 模块本身。
|
||
|
||
> 客户端日志只记 IP 不记域名时,无法判断"是不是 DNS 过期解析"。
|
||
> 建议客户端日志同时记录原始域名(实测案例:某 Dropbox 旧 IP 已失效,
|
||
> 域名现已解析到其它地址,客户端仍在拨旧 IP)。
|
||
|
||
## 6. sing-box(VLESS+REALITY / Shadowsocks)
|
||
|
||
### 6.1 安装
|
||
|
||
```bash
|
||
SINGBOX_VER=1.13.19
|
||
curl -LO https://github.com/SagerNet/sing-box/releases/download/v$SINGBOX_VER/sing-box-$SINGBOX_VER-linux-amd64.tar.gz
|
||
tar xzf sing-box-*.tar.gz
|
||
install -m 755 sing-box-$SINGBOX_VER-linux-amd64/sing-box /usr/local/bin/sing-box
|
||
```
|
||
|
||
### 6.2 配置
|
||
|
||
完整配置见 [sing-box/config.json](sing-box/config.json)。要点:
|
||
- **VLESS+REALITY**:每节点独立 UUID/密钥对/short_id,SNI 全节点统一,flow `xtls-rprx-vision`;REALITY handshake server 用 `<伪装SNI域名>:443`
|
||
- **Shadowsocks**:`2022-blake3-aes-256-gcm`(SIP022:会话子密钥 + 抗重放探测,显著优于老 AEAD `aes-256-gcm`)+ 每节点独立 PSK,独立端口(兼容不支持 REALITY 的老客户端)
|
||
- **PSK 格式要求**:密码必须是 base64 编码的 32 字节密钥(`openssl rand -base64 32`),不是任意字符串;改错格式 sing-box 启动即报错
|
||
- sing-box / Shadowrocket / mihomo 均支持 2022-blake3 系列
|
||
- 无 SOCKS5 inbound(已按需移除;如需可在 inbounds 追加 `type: socks`)
|
||
|
||
### 6.3 REALITY 密钥生成
|
||
|
||
**每台节点独立生成一套**(互不共用):
|
||
|
||
```bash
|
||
# 生成私钥/公钥(sing-box 1.13+ 内置 reality 密钥生成)
|
||
sing-box generate reality-keypair
|
||
# 输出 private_key / public_key,记录备用
|
||
# short_id 用 8 字节随机 hex:
|
||
openssl rand -hex 8
|
||
# UUID:
|
||
sing-box generate uuid
|
||
```
|
||
|
||
> 私钥只放服务端配置;公钥给客户端订阅。UUID/密钥对/short_id 每节点独立。
|
||
|
||
**公钥必须推导验证,禁止手抄。** 订阅/节点配置里的 `public_key` 与服务端
|
||
`private_key` 必须严格配对(X25519 推导),曾发生手抄错一个字符导致全节点
|
||
REALITY 握手失败(客户端报 `reality verification failed`)的事故。改配置后验证:
|
||
|
||
```bash
|
||
python3 - << 'EOF'
|
||
import base64
|
||
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
|
||
from cryptography.hazmat.primitives import serialization
|
||
priv = base64.urlsafe_b64decode('<REALITY_PRIVATE_KEY>' + '==')
|
||
pub = X25519PrivateKey.from_private_bytes(priv).public_key().public_bytes(
|
||
serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||
print(base64.urlsafe_b64encode(pub).decode().rstrip('=')) # 应与下发的 public_key 逐字符一致
|
||
EOF
|
||
```
|
||
|
||
---
|
||
|
||
### 6.4 Hysteria2 / TUIC(UDP 传输)— ⚠️ 已回滚,本节为留档
|
||
|
||
> **状态:已回滚(2026-09-20 当晚)。** 当日曾在五节点部署并逐组合端到端验证通过(见下表与「验证方法」),
|
||
> 随后因「**绝对不可以被 GFW 封禁**」这一优先级,**全部撤除**——五节点 sing-box 已恢复为
|
||
> `vless + shadowsocks` 原状,新增的 UDP 入站不再监听。
|
||
>
|
||
> 本节保留作**留档**:配置片段、各节点 NAT/NSG 差异、端到端验证方法(尤其"只看监听不足以证明外部可达"
|
||
> 这个坑)在日后重新启用或换机时仍然有效。若要重新启用,照本节执行即可,**但请先读 §2.6 的撤除理由**。
|
||
|
||
TCP 系协议在跨境丢包链路上受拥塞控制限制(BBR 已缓解但仍有天花板);QUIC 系
|
||
(**Hysteria2** 的 Brutal CC、**TUIC v5**)能显著改善高丢包下的吞吐与时延。
|
||
两者都是 **sing-box 内建**,无需额外二进制;UDP 需求也可走 UoT(§5.5)。
|
||
|
||
```json
|
||
// Hysteria2(sing-box inbound)
|
||
{ "type": "hysteria2", "tag": "hy2-in-<端口>", "listen": "0.0.0.0", "listen_port": "<端口>",
|
||
"up_mbps": 100, "down_mbps": 100,
|
||
"obfs": { "type": "salamander", "password": "<obfs密码>" },
|
||
"users": [ { "name": "<节点>-hy2", "password": "<hy2密码>" } ],
|
||
"tls": { "enabled": true, "server_name": "<节点域名>",
|
||
"certificate_path": "/etc/nginx/ssl/<节点域名>/fullchain.pem",
|
||
"key_path": "/etc/nginx/ssl/<节点域名>/privkey.pem", "alpn": ["h3"] },
|
||
"masquerade": "file:///var/www/html" }
|
||
|
||
// TUIC v5
|
||
{ "type": "tuic", "tag": "tuic-in-<端口>", "listen": "0.0.0.0", "listen_port": "<端口>",
|
||
"users": [ { "name": "<节点>-tuic", "uuid": "<UUID>", "password": "<密码>" } ],
|
||
"congestion_control": "bbr",
|
||
"tls": { /* 同上 */ } }
|
||
```
|
||
|
||
**要点**
|
||
|
||
- **带宽声明决定 Brutal**:`up_mbps`/`down_mbps` 不写时,服务端会命令客户端改用 BBR;
|
||
写了才允许 Hysteria CC(Brutal)。默认 100 Mbps,计量流量的节点给 50。
|
||
Brutal **不遵守拥塞控制**,设错会占满链路甚至被服务商判定异常流量——务必按实际带宽设。
|
||
- `obfs: salamander` 给 QUIC 加一层混淆(对抗 QUIC 特征识别);`masquerade` 指向伪装页,
|
||
未认证访问返回与 nginx 一致的页面。
|
||
- TUIC 保持 `zero_rtt_handshake` 关闭(默认,0-RTT 可被重放)。
|
||
- 凭据**每节点独立**,与 VLESS/SS/CONNECT 同策略。
|
||
|
||
**各节点端口与部署差异(2026-09-20 实测,务必照抄结论)**
|
||
|
||
| 节点 | 虚拟化 | Hysteria2 (UDP) | TUIC (UDP) | 差异 / 坑 |
|
||
|---|---|---|---|---|
|
||
| hk2 | LXC(NAT) | 4305 | 4306 | NAT 段 4301-4320 UDP 1:1(探针 + 外部回扫实测) |
|
||
| us | KVM | 1208 | 1209 | 公网直连,UDP 任意端口可用 |
|
||
| ru | LXC(NAT) | 23333 | 23334 | **外部只有宿主已映射的 UDP 端口可达**:容器内 bind 任意端口都"正常",但 23336+ 实测直接 `connection refused`(ICMP port unreachable);最终复用 **23333/23334**——与 nginx/vless 占用的 **TCP 同号**,UDP 与 TCP 互不冲突 |
|
||
| aq | KVM(IPv6-only) | 8444 | 8445 | 节点为 IPv6 出口,`listen` 用 `"::"`;客户端须走 IPv6 |
|
||
| jp | Azure VM | 未部署 | 8445 | **Azure NSG 只放行 UDP 8445**(443/80/8080/8088/18080/8443/9444 的 UDP 面实测均不可达)→ 单节点只能承载一个 UDP 协议;要加 Hysteria2 须先在 NSG 放行第二个 UDP 端口 |
|
||
|
||
**客户端链接格式(手工导入用)**
|
||
|
||
```
|
||
hysteria2://<password>@<域名>:<端口>/?sni=<域名>&alpn=h3&obfs=salamander&obfs-password=<obfs密码>#备注
|
||
tuic://<uuid>:<password>@<域名>:<端口>/?congestion_control=bbr&alpn=h3&sni=<域名>#备注
|
||
```
|
||
|
||
**接入现状(2026-09-20,务必先读)**
|
||
|
||
| 层 | 状态 |
|
||
|---|---|
|
||
| 服务端 | ⛔ **已撤除**(原:五节点部署并端到端验证)——2026-09-20 当晚按抗封优先回滚 |
|
||
| **订阅下发** | ❌ 从未下发(`app.py` 无 hysteria2/tuic 生成逻辑,`nodes.json` 无对应字段;订阅里只有 http / vless / ss),且已决定不做 |
|
||
| **代理组** | ❌ 订阅只生成一个 `RRPP`(`type: select`,手动选节点),**没有 fallback / url-test**,因此**不存在"UDP 挂了自动回落 TCP"** |
|
||
| lite 客户端(Windows) | 仅 HTTP CONNECT + UoT v2(UDP 走 magic 地址),**不使用 UDP 入站**,且节点为手动切换、无自动故障转移 |
|
||
| 结论 | UDP 通道目前**只能手工导入**上面的链接使用;抗封主路径仍是 **REALITY+Vision(TCP)**,UDP 属可选加速,未接入客户端链路 |
|
||
|
||
> 若日后要接入订阅,需要三步:① `nodes.json` 每节点补 `methods.hysteria2` / `methods.tuic`;
|
||
> ② `app.py` 增加 mihomo `type: hysteria2` / `type: tuic` 生成与分享链接;
|
||
> ③ 代理组改为**每节点一个 `fallback` 组、REALITY 排第一**(否则"UDP 挂了回落 TCP"不成立)。
|
||
> 注意 mihomo 的 fallback 依赖健康检查周期(默认 300s),切换不是瞬时的。
|
||
|
||
**验证方法(端到端,不能只看监听)**
|
||
|
||
```bash
|
||
# 在任意机器用 sing-box 作客户端:socks 入站 + 上述 outbound,然后
|
||
curl -s -m 20 -x socks5h://127.0.0.1:11080 -o /dev/null -w '%{http_code}\n' http://example.com/ # 期望 200
|
||
```
|
||
|
||
`ss -lunp` 看到端口在听**不足以**证明外部可达:ru(NAT 未映射)与 jp(NSG 未放行)
|
||
都是"监听正常但外部不可达"的真实案例。
|
||
|
||
⚠️ 代价与限制:UDP 在部分运营商会被 QoS/封禁;Brutal 对同链路其他流量不友好;
|
||
计量流量的节点(如 jp,15GB/月)注意消耗;LXC 容器内 socket 缓冲上限受宿主机约束。
|
||
|
||
---
|
||
|
||
## 7. mtg(MTProto 代理)
|
||
|
||
Telegram 内置代理,Fake TLS 模式抗封锁。用 [mtg](https://github.com/9seconds/mtg)(Go 单二进制):
|
||
|
||
### 7.1 安装
|
||
|
||
```bash
|
||
MTG_VER=2.2.8
|
||
curl -LO https://github.com/9seconds/mtg/releases/download/v$MTG_VER/mtg-$MTG_VER-linux-amd64.tar.gz
|
||
tar xzf mtg-*.tar.gz
|
||
install -m 755 mtg-$MTG_VER-linux-amd64/mtg /usr/local/bin/mtg
|
||
```
|
||
|
||
### 7.2 生成 secret(每台独立)
|
||
|
||
```bash
|
||
mtg generate-secret --hex <伪装SNI域名>
|
||
# 输出 ee<32hex><域名hex>,以 ee 开头 = Fake TLS 类型
|
||
```
|
||
|
||
### 7.3 运行
|
||
|
||
```bash
|
||
mtg simple-run --prefer-ip=prefer-ipv4 0.0.0.0:<mtproto端口> <上面生成的secret>
|
||
```
|
||
|
||
> `--prefer-ip=prefer-ipv4`:强制 IPv4 出站,避免容器/双栈环境下 IPv6 不可达导致回退失败。
|
||
|
||
### 7.4 Telegram 客户端参数
|
||
|
||
- 服务器:`<节点域名>`
|
||
- 端口:`<mtproto端口>`
|
||
- Secret:上面生成的 hex(Telegram 自动识别 Fake TLS)
|
||
|
||
---
|
||
|
||
## 8. 本机公网 IP 自动识别(防自代理回环)
|
||
|
||
正向代理转发时,若目标 Host 是本机公网 IP,会被识别为"本机直连"返回伪装页(而非经代理转发本机,造成回环)。公网 IP 可能变动,用 cron 脚本每 5 分钟自动维护。
|
||
|
||
脚本见 [scripts/sync-nginx-local-ips.sh](scripts/sync-nginx-local-ips.sh),cron 见 [cron/sync-nginx-local-ips](cron/sync-nginx-local-ips):
|
||
|
||
```cron
|
||
*/5 * * * * root /usr/local/sbin/sync-nginx-local-ips.sh >/dev/null 2>&1
|
||
```
|
||
|
||
脚本逻辑:取 `api.ipify.org` 出站 IP + 节点域名 A 记录,比对重写 `/etc/nginx/local_ip_rules.conf`,**仅变更时** `nginx -t && systemctl reload nginx`。
|
||
|
||
---
|
||
|
||
## 9. systemd 服务文件
|
||
|
||
| 服务 | 文件 |
|
||
|---|---|
|
||
| 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) |
|
||
| rrpp-qdisc(fq 队列,见 2.6) | [systemd/rrpp-qdisc.service](systemd/rrpp-qdisc.service) |
|
||
|
||
```bash
|
||
cp systemd/*.service /etc/systemd/system/
|
||
systemctl daemon-reload
|
||
systemctl enable --now nginx sing-box mtg
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 启动顺序与验证清单
|
||
|
||
### 启动顺序
|
||
|
||
```bash
|
||
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 前可用)
|
||
```
|
||
|
||
### 验证清单
|
||
|
||
```bash
|
||
# BBR
|
||
sysctl net.ipv4.tcp_congestion_control # => bbr
|
||
sysctl net.ipv4.tcp_mtu_probing # => 1(PMTUD 黑洞缓解,见 2.4)
|
||
|
||
# 系统级网络优化(见 2.6):确认 sysctl.d 的值真的生效(注意 /etc/sysctl.conf 覆盖坑)
|
||
sysctl -n net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_fastopen \
|
||
net.ipv4.tcp_slow_start_after_idle net.ipv4.ip_local_port_range
|
||
# KVM/完整 VM 追加:net.core.netdev_max_backlog / rmem_max / wmem_max
|
||
tc qdisc show | grep -v noqueue # 期望默认路由网卡为 fq(容器内为 noqueue,属正常)
|
||
systemctl is-active rrpp-qdisc # 期望 active(容器内该单元会自行跳过)
|
||
|
||
# fail2ban
|
||
fail2ban-client status sshd # => jail 存在(见 2.5)
|
||
|
||
# sing-box
|
||
sing-box check -c /etc/sing-box/config.json
|
||
systemctl is-active sing-box
|
||
|
||
# mtg(Fake TLS:非法 ClientHello 应返回 TLS alert 0x15)
|
||
timeout 8 python3 -c "
|
||
import socket,os,struct
|
||
s=socket.connect(('<节点域名>',<mtproto端口>)) if False else socket.create_connection(('<节点域名>',<mtproto端口>),timeout=6)
|
||
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
|
||
|
||
# UDP 传输(Hysteria2/TUIC)——已于 2026-09-20 撤除;以下仅当重新启用时才适用
|
||
# 必须端到端验证,不能只看监听
|
||
ss -lunp | grep -E ':<hy2端口>|:<tuic端口>' # 仅确认服务在听
|
||
# 再用 sing-box 客户端 + curl 经由 socks 实测(见 6.4「验证方法」)
|
||
ethtool -k <网卡> | grep -E 'rx-udp-gro-forwarding|rx-gro-list' # KVM 期望 on;LXC 容器不可设
|
||
|
||
# nginx CONNECT(带 Basic Auth,B64 = base64('<代理用户名>:<代理密码>'))
|
||
B64=$(printf '<代理用户名>:<代理密码>' | base64 -w0)
|
||
curl -sk --proxy-insecure -x https://<节点域名>:<nginx端口> \
|
||
--proxy-header "Authorization: Basic $B64" \
|
||
-o /dev/null -w '%{http_code}\n' http://example.com/ # => 200
|
||
|
||
# 伪装页(直连 IP/域名,无认证)
|
||
curl -sk https://<节点域名>:<nginx端口>/ -o /dev/null -w '%{http_code}\n' # => 200
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 目录结构与文件索引
|
||
|
||
```
|
||
server_config/
|
||
├── README.md ← 本文档
|
||
├── sysctl/
|
||
│ ├── 99-zz-rrpp-network.kvm.conf ← 系统级网络优化(KVM/完整 VM,见 §2.6)
|
||
│ └── 99-zz-rrpp-network.lxc.conf ← 系统级网络优化(LXC/OpenVZ 容器,见 §2.6)
|
||
├── nginx/
|
||
│ ├── 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/
|
||
│ ├── nginx.service
|
||
│ ├── sing-box.service
|
||
│ ├── mtg.service
|
||
│ └── rrpp-qdisc.service ← 开机把默认网卡 qdisc 设为 fq(见 §2.6)
|
||
├── scripts/
|
||
│ ├── sync-nginx-local-ips.sh ← 本机公网 IP 规则自动维护
|
||
│ └── rrpp-qdisc.sh ← fq 队列设置脚本(rrpp-qdisc.service 调用)
|
||
├── cron/
|
||
│ └── sync-nginx-local-ips ← cron 定义(每 5 分钟)
|
||
├── rrpp-subscription/ ← RRPP 订阅后端(bj,见 §12)
|
||
│ ├── API.md ← 订阅端点 / method / 分层 / 规则集文档
|
||
│ └── gen_rulesets.py ← PAC → 客户端规则集转换脚本
|
||
└── fake-site/
|
||
└── index.html ← 伪装站点页面(所有节点共用同一份)
|
||
```
|
||
|
||
> 旧的 `traffic-monitor/`、`pac/`、`conf.d/*.conf` 已下线或迁移,不再维护,已从本目录移除。
|
||
|
||
---
|
||
|
||
## 12. RRPP 订阅后端(PAC → 规则集下发)
|
||
|
||
订阅服务运行在 **bj**(`C:/BtSoft/nginx/others/backEnd/api-rrrosmontis-icu/RRPP/`,nssm 服务 `RRPPSubBackend`,监听 `127.0.0.1:5010`,由 nginx `api.rrrosmontis.icu` 反代)。
|
||
完整端点说明见 `rrpp-subscription/API.md`(bj 上同名文件为主副本,两处内容保持一致)。
|
||
|
||
### 12.1 PAC 为什么不能直接下发
|
||
|
||
PAC 是浏览器/系统用的 JS 自动配置,**mihomo/Clash 与 Shadowrocket 都读不了**。两者唯一共同能吃的格式是
|
||
「每行自带规则类型的规则行文本」(Clash 的 `behavior: classical`):
|
||
|
||
- mihomo → `rule-providers`(`behavior: classical` + `format: text`)
|
||
- Shadowrocket → `RULE-SET,<url>,<策略>`(官方手册:规则集内容需包含规则类型)
|
||
|
||
因此由后端 `gen_rulesets.py` 把 PAC 数据转成三个 `.list`,两种客户端共用同一份文件。
|
||
|
||
### 12.2 规则集产物
|
||
|
||
| 规则集 | 文件(`https://cdn.rrrosmontis.icu/RRPP/PAC/`) | 规则数 | 策略 |
|
||
|---|---|---|---|
|
||
| `rrpp-proxy` | `rrpp-proxy.list` | 3943(`DOMAIN` 539 + `DOMAIN-SUFFIX` 3404) | `RRPP` / `PROXY` |
|
||
| `rrpp-direct` | `rrpp-direct.list` | 16(`DOMAIN-SUFFIX`) | `DIRECT` |
|
||
| `rrpp-cn-ip` | `rrpp-cn-ip.list` | 7456(`IP-CIDR`) | `DIRECT` |
|
||
|
||
顺序(与 PAC 语义一致):强制代理 → 直连种子 → 中国 IP 直连 → `MATCH` 全代理。
|
||
|
||
### 12.3 维护:PAC 更新后重新生成
|
||
|
||
```bash
|
||
# bj 上执行(Windows:C:/BtSoft/nginx/others/htmlResources/cdn/RRPP/PAC/ 为生成目录,同时即 CDN 根)
|
||
python gen_rulesets.py # 幂等:内容无变化则不写盘;原子替换
|
||
python gen_rulesets.py --check # 只预览差异,不写盘
|
||
```
|
||
|
||
产物文件名与 URL 固定,**订阅端与客户端均无需改动**:mihomo 按 `interval: 86400` 自动更新,
|
||
Shadowrocket 在「配置 → 规则集URL」中自动更新。
|
||
|
||
### 12.4 客户端接入
|
||
|
||
```bash
|
||
# mihomo / Clash Verge:规则走 rule-providers(YAML 约 2.4KB)
|
||
...&method=clash
|
||
# 不认 rule-providers,或要「导入一次到位」:规则内联(约 440KB)
|
||
...&method=clash&rules=inline
|
||
# Shadowrocket:只下发规则([General] + 远程 RULE-SET + FINAL,不含节点)
|
||
...&method=shadowrocket
|
||
# 兜底:全流量走代理(旧行为)
|
||
...&method=clash&rules=global
|
||
```
|
||
|
||
> `method=shadowrocket` 的 `.conf` 不写 `[Proxy]` 段——Shadowrocket 的 REALITY 参数写法官方未文档化,不猜测;
|
||
> 策略 `PROXY` 指向首页当前选中的节点,节点用 `clash`/`v2ray` 订阅导入。
|
||
> 想在小火箭里「一次导入节点+规则」,用 `method=clash&rules=inline`。
|
||
|
||
### 12.5 验证口径
|
||
|
||
- 生成:11415 条规则(`regexp:` 跳过 7、非法 1)
|
||
- CDN:三个 URL HTTP 200,md5 与本地一致(`806808b8` / `8f89397e` / `3e75093d`)
|
||
- mihomo v1.19.31 真实内核:`-t` 通过;运行时 `GET /providers/rules` 报 `3943 / 16 / 7456`(`behavior=Classical`),
|
||
落地文件字节数与生成一致;`rules=inline` 亦通过 `-t`
|
||
- Shadowrocket 为 iOS 应用,未做实机验证(依官方手册 `RULE-SET` 与 Clash YAML 兼容说明实现)
|