随着人工智能发展,审查手段加入机器学习,使得原来Trojan方式因为含有TLS in TLS特征而有概率被筛选出来。现如今最隐蔽的手段莫过于利用Xray工具搭建vless通信协议+reality传输层安全协议+xtls-rprx-vision流量控制的组合,如此一来审查者看到的是一次正常的 HTTPS 连接(单层 TLS),而非嵌套的 TLS in TLS

写在前面

适用情况

  • 一台外网服务器
  • 一个申请好的域名(假设为yangbo.party,自己处理好DNS解析)
    整个方案的流量链路跟传统的trojan是一样的,只是改进了传输方式。
    diagram

本篇博文主要是介绍自己在服务器有网站,并且想把Xray隐藏在自己的网站后面。对自己而言,可以用浏览器正常访问自己的网站,可以通过Xray出墙;对审查者而言,只能看到我们的网站,以为我们的流量都是访问网站的。
如果你没有自己的网站,那就只需要把自己的Xray服务器伪装成一个国外网站的CDN,你也不需要建站、申请证书了,本篇教程不适合你,请转官方配置文档VLESS-TCP-XTLS-Vision-REALITY

本方案的优势

传统的Trojan方式,用户的代理客户端与代理服务器建立一个真实的TLS连接,通过这个加密的隧道,应用程序(比如浏览器)再与目标服务器(比如Google)建立一个TLS连接。这时浏览器与Google服务器之间的端对端加密是建立在代理客户端与代理服务器的加密连接之上的。
浏览器 — 代理客户端 === 代理服务器 — Google
基于之前的技术手段,审查者只能看到我们通过443端口建立了TLS连接,看不到具体内容,以为我们在正常访问网页。但是随着机器学习的介入,这样的手段依旧有概率被识破:
代理客户端与代理服务器之间的加密套娃无可避免的一点就是在每个包都会增加一个数据包头,加密层数越多,包头就会越重。最重要的,由于每个包都增加了相同长度,它可能具有某些统计学特征。

目前新的方案中,我个人理解如下:

  • Vless通信协议相当于是一个协议容器,它本身不对流量加密,对流量的处理也基本依靠传输协议和流量控制。
  • Reality传输层安全协议,从根本上解决了TLS in TLS的现象。在握手阶段模仿一个国外网站的CDN节点(比如Google)与客户端建立TLS连接(当然这个TLS连接证书肯定是无效的,不过两边都会忽略这个问题)。握手成功后,当传输内容是TLS加密后的内容时,就不对流量进行二次加密了。
  • Vision流量控制则会填充数据包长度,来消除统计学特征。其实有了Reality就够了,不知道再使用vision流控会不会画蛇添足。vision的全称是xtls-rprx-vision流量控制,从它的名称可以看出它曾是xtls的一部分,xtls慢慢被放弃之后,vision从传输协议降级为流量控制协议,总之官方有这种配置方法,说明两个可以协同工作。
    这个方案不仅大大降低了被检测到几率,同时带来显而易见的好处:在本地路由器上部署Xray客户端时,可以减少90%的解密计算量,减少延时;如果是手机等移动客户端,同理也会优化功耗。缺点嘛。。。应该就是配置文件太长,没办法记住,要找个地方存一下。

服务器端配置

安装Nginx

如果已经安装了Nginx,请nginx -V确认编译参数中是否含有--with-http_ssl_module--with-http_v2_module--with-http_realip_moduleTLS SNI support enabled
如果以上模块不齐全,建议按照下一行重新编译安装。本教程的配置在1.29.0版本中通过测试
Nginx安装参见我之前的文章编译安装Nginx

安装Xray

如果已经安装Xray,请xray version检查版本是否>=24.9.30
如果版本过低,必须升级以支持最新的流控方式和传输模式。本教程的配置在25.8.3版本中通过测试
Xray采用脚本一键安装

1
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

申请SSL证书

一键安装acme.sh

1
sudo apt update && sudo apt install -y curl socat && curl https://get.acme.sh | sh -s email=你的邮箱地址

官方分为两大类申请证书的方式:HTTP验证DNS验证,要是细分还有很多情况,这里我选择DNS验证,支持几乎所有的域名提供商。我的域名提供商是Namesilo,只要去Namesilo网站获取API,然后写入系统变量就好了。

1
echo "export Namesilo_Key='<key>'" > ~/.bashrc && source bashrc

然后尝试获取证书,成功之后记下证书路径就好了,其他的不用管了,每个月acme.sh会自动帮我续期证书。

1
acme.sh --issue --dns dns_namesilo -d yangbo.party -d www.yangbo.party --dnssleep 900 --server letsencrypt --force --reloadcmd "chmod -R 777 /root/.acme.sh/yangbo.party_ecc/ && systemctl reload nginx"

配置Nginx和Xray

先说原理:Xray监听443端口,通过层层验证判断是否为代理请求,如果不是就回落到Nginx监听的8080,如果是就建立通道。(其实整体跟Trojan一样,只不过验证阶段使用TLS,建立完请求后,不再对正常访问https网站产生的TLS流量加密)。
Nginx默认配置文件路径:/etc/nginx/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
user  root;
worker_processes 1;
pid /var/run/nginx.pid;
error_log /var/log/nginx_error.log;
events {
use epoll;
worker_connections 1024;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
charset utf-8,gbk;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 60;
client_header_buffer_size 4k;
open_file_cache max=102400 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 1;
client_header_timeout 15;
client_body_timeout 15;
reset_timedout_connection on;
send_timeout 15;
http2_recv_buffer_size 32k;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 3;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
server_tokens off;
access_log /var/log/nginx_access.log;
server { #如果有人访问http端口,就把它重定向到443端口,这就跟其他网站行为一致
listen 80;
server_name www.yangbo.party yangbo.party;
return 301 https://$server_name$request_uri;
}

server { #8080端口当作正常https网页去设置,注意替换证书路径
listen 127.0.0.1:8080 ssl fastopen=3;
http2 on;
server_name www.yangbo.party yangbo.party;
ssl_early_data on;
ssl_buffer_size 4k;
keepalive_timeout 75s;
ssl_certificate /root/.acme.sh/yangbo.party_ecc/yangbo.party.cer;
ssl_certificate_key /root/.acme.sh/yangbo.party_ecc/yangbo.party.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
client_max_body_size 20M;
client_body_timeout 12;
underscores_in_headers on;
location / {
root /www/;
expires 10h;
fancyindex on;
fancyindex_exact_size off;
fancyindex_localtime on;
fancyindex_header "/fancyindex/header.html";
fancyindex_footer "/fancyindex/footer.html";
fancyindex_ignore "fancyindex";
}
location ~* ^.+\.(jpg|gif|png|img|apk|tar.gz|wmv|jpeg|mp3|mp4|zip|rar)$ {
valid_referers none blocked www.yangbo.party yangbo.party;
if ($invalid_referer){
return 403;
break;
}
}
}
}

Xray默认配置文件路径:/usr/local/etc/xray/config.json,Xray配置较为复杂,可以参考官方wiki

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
{
"log": {
"loglevel": "debug",
"access": "/var/log/xray/access.log",
"error": "/var/log/xray/error.log"
},
"inbounds": [
{
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "使用xray uuid命令随机生成",
"flow": "xtls-rprx-vision"
}
],
"decryption": "none",
"fallbacks": [
{
"dest": "127.0.0.1:8080",
"xver": 0
}
]
},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"show": false,
"target": "127.0.0.1:8080",
"xver": 0,
"serverNames": ["yangbo.party","www.yangbo.party"],
"privateKey": "运行命令xray x25519生成,其中的privateKey",
"password": "上一条命令xray x25519生成,其中的publicKey",
"fingerprint": "chrome",
"shortIds": ["运行 openssl rand -hex 4 命令生成"]
},
"sockopt": {
"tcpNoDelay": true,
"tcpFastOpen": true,
"tcpKeepAliveInterval": 30,
"tcpcongestion": "bbr"
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {
"sockopt": {
"tcpNoDelay": true,
"tcpFastOpen": true,
"tcpKeepAliveInterval": 30,
"tcpcongestion": "bbr"
}
}
},
{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}
],
"routing": {
"rules": [
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "blocked"
}
]
}
}

systemctl restart nginx && systemctl restart xray

排错指南

理论上这一步结束,就应该完成了。试试看直接浏览器访问网页能不能正常回落。若不能,可以直接测试nginx监听的8080端口是否可以访问:curl -vk https://127.0.0.1:8080或检查端口是否被监听:ss -tulnp或查看xray和nginx的日志(日志目录见配置文件)

一些网络优化

1
2
3
4
echo "net.ipv4.tcp_fastopen=3" >> /etc/sysctl.conf
echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
sysctl -p

客户端推荐

  • 电脑端推荐v2rayN
  • Android推荐v2rayNG
  • OpenWrt推荐homeproxy。是sing-box内核,但是兼容xray的vless+reality这种组合。

后记

懂行的朋友可以看出我这种配置的弊端或者说可以改进的地方:

  • xray配置中fallback到nginx的流量并不是xray官方建议的proxy protocol v1流量,而是原始流量。这样在nginx看来所有请求都是来自127.0.0.1这个IP,并不知道真实的访问者IP。如果nginx中想做一些基于ip地址的防护、日志排查就做不到了。我不启用proxy protocol的原因是reality协议中需要一个对外完全正常的https网站,这与处理proxy protocol类型的fallback流量冲突。如果想要完美解决这个问题,需要专门再开一个端口,nginx再专门监听fallback流量,想想就算了,反正就自己的静态网站,这么做何必呢。
  • 本文用的传输方式(transport)是raw(旧称tcp),是一个保守但稳妥的协议。明显xray目前的发展方向是xhttp,功能更强大,速度更快。但是一来这个特性目前只有xray支持,OpenWrt上没有好用的xray客户端。二来新功能的抗审查能力还有待观察。