FAQ
安全性
Shadowpacket 只支持四种出站协议组合:Shadowsocks + xray-plugin、VLESS + Reality + XTLS/Vision、Hysteria2、TUIC v5。这不是还没来得及加别的,是刻意只留这四种。
就当前公开可用的方案而言,这四种处在抗识别与抗主动探测最强的一档:Reality 借用真实站点的握手,连证书都不是自己的,对主动探测返回的响应和那个站点一模一样;xray-plugin 承载的 Shadowsocks 在链路上就是一条普通的 HTTPS WebSocket 会话;Hysteria2 与 TUIC v5 是标准 QUIC,与 HTTP/3 同形。这四种协议对GFW来看,都是普通的https流量,完全无法识别
为什么不支持其它协议
被明确排除在外的有这些,各有各的理由:
| 协议 | 不支持的原因 |
|---|---|
| 裸 Shadowsocks(不带 xray-plugin) | 没有外层 TLS。它自身的 AEAD 是预共享密钥,没有前向保密,口令泄露则历史流量全解;而且报文没有任何协议外壳,长度与时序特征是可统计的。 |
| Shadowsocks + xray-plugin 的 QUIC 模式 | 本项目不启用。它与 Hysteria2、TUIC 的场景重合,却没有那两者的拥塞控制与多路复用,多留一条路只是多一个要维护和要审计的形态。 |
| VMess | 依赖时间戳的握手设计,历史上已被证明可以主动探测和重放识别,上游自己也早已不推荐新部署使用。 |
| Trojan | 必须用自有证书,而证书本身就是一条可关联、可枚举的线索;对主动探测的抵抗完全依赖回落站点配得对不对,做错一步就露。Reality 从设计上消除了这个前提。 |
| plain VLESS + TLS(不带 Reality) | 同样要自有证书,SNI 明文可见可阻断,主动探测时没有真实站点替它应答。Reality 是它的严格上位替代。 |
少支持一种协议,就少一条可能被识别的路径,也少一次用户选错的机会。这四种之外的形态,配置解析时会被直接拒绝,不会被悄悄规范化成某个支持的形态跑起来。
七个安全性质
「安全」不是一个性质,是七个彼此独立的性质。四种出站协议组合各自的状态如下,无就是无——这张表刻意不回避两个「无」。
| 性质 | Shadowsocks + xray-plugin | VLESS + Reality + Vision | Hysteria2 | TUIC v5 |
|---|---|---|---|---|
| 机密性与完整性 | 有 | 有 | 有 | 有 |
| 对端认证 | 服务端证书 | Reality 公钥 | 服务端证书 | 服务端证书 |
| 前向保密 | 有 | 有 | 有 | 有 |
| 后向保密 | 无 | 无 | 无 | 无 |
| 抗重放 | 有 | 有 | 有 | 有 |
| 抗降级 | 有 | 有 | 有 | 有 |
| 抗量子 | 无 | 无 | 无 | 无 |
- 机密性与完整性:四种都是 AEAD,篡改一个比特就解不开,不存在只加密不认证的模式。Shadowsocks 这条链路是双层的——内层
aes-256-gcm,外层 xray-plugin 的 WebSocket/TLS。 - 对端认证:Shadowsocks、Hysteria2 与 TUIC v5 靠服务端证书链。Reality 不同:它没有自己的证书,认证靠客户端预置的 X25519 公钥,握手借的是真实站点的证书,所以它抗中间人的强度不依赖 CA 体系。
- 前向保密:长期密钥泄露后,此前录下的密文仍解不开。这里有个要紧的细节——Shadowsocks 的
aes-256-gcm是从固定口令派生密钥的预共享方案,本身没有前向保密,口令泄露则历史流量全解;这条链路的前向保密完全来自外层 xray-plugin 的 TLS 1.3。所以「Shadowsocks 必须始终配 xray-plugin」不只是为了抗检测,它同时是前向保密的唯一来源。VLESS 自身也不加密,前向保密来自 Reality 的真实 TLS 1.3 握手;Hysteria2 与 TUIC v5 来自 QUIC 自带的 TLS 1.3。 - 后向保密(后泄露安全):会话密钥泄露之后能否自动恢复安全。四种都没有,而这不是本项目的缺陷,是这一类协议共同的边界:真正的后向保密需要双棘轮那样的有状态密钥演进,代理协议不做。TLS 1.3 的 KeyUpdate 和 QUIC 的 key phase update 只是会话内换密钥,长期凭据一旦泄露,攻击者可以直接冒充。
- 抗重放:Reality 与两条 QUIC 路径本身就有重放保护。Shadowsocks 的经典 AEAD 恰恰是重放弱的,但在这里它跑在 TLS 里面,线路上的攻击者拿不到可重放的内层报文。
- 抗降级:不会在失败时悄悄退到更弱的模式,这条是显式设计的。没有可用的 U-DATA 后端时 UDP 直接失败关闭,绝不回落到没有外层加密的 Shadowsocks UDP;配置里也拒绝 raw Shadowsocks、VMess、Trojan 和 plain VLESS/TLS 这些形态。
- 抗量子:现在录下密文、将来用量子计算机解。四种都没有防护,没有启用任何混合密钥交换。这是已知边界,不是遗漏。
抗流量检测与抗主动探测
这是两件事,防的是两种检测手段,做法也不一样。
抗流量检测是被动的:GFW在链路上检测流量,然后识别是否是翻墙流量。
抗主动探测是主动的:GFW怀疑某个服务器IP是代理,于是自己去连一下,看它怎么回应。上一代协议基本都是死在这一关上的——裸 Shadowsocks 可以被构造的探测包区分出来,VMess 的握手可以被重放识别,Trojan 只要回落站点配错一步就露馅。
协议提供了足够的安全性,但是配置错误依然会导致流量被检测到。伪装站点必须是一个真的、有内容的网站,不能是 404、nginx 默认欢迎页或者一行「OK」;Shadowsocks 的路径必须真的只有你知道;Reality 的 dest 与 serverNames 必须指向一个真实存在、支持 TLS 1.3、且在你所在地区没有被墙的站点。这几项任何一项将就了,抗主动探测就是零。
客户端安装
请在首页按平台下载安装包,装好之后需要配置服务器节点等配置。可以通过配置文件,或者在界面里修改配置。
Windows
Windows 10 和 11 用文件名里带 windows_win10_win11 的那个,Windows 7 和 8.1 用带 windows_win7_win8 的那个。两条产品线的运行时不同,装错的包不会工作。
双击安装,需要管理员权限,默认装到 C:\Program Files\Shadowpacket。升级就是再装一次新版 MSI,原地覆盖,不会留下并存的旧副本;如果图形界面正在运行,安装程序会提示你选择关闭它、重试还是退出。
首次连接会创建一个 Shadowpacket 专属的 Wintun 虚拟网卡。之后每次断开都会把路由、DNS、防火墙规则和 DHCP/静态地址还原成连接前的样子,但保留这块网卡处于空闲状态——它不会被反复删除重建。卸载则会连网卡、驱动节点、网络配置文件、服务、路由和 NRPT 规则一起清干净,物理网卡的 IPv4/IPv6 配置原样保留。
安装不会把 Shadowpacket 注册成开机启动。这是一个需要你自己去设置里打开的选项;升级会保留你已经打开的注册。
配置文件在 %LOCALAPPDATA%\Shadowpacket\conf\shadowpacket-client.json,通常展开为 C:\Users\<用户名>\AppData\Local\Shadowpacket\conf\shadowpacket-client.json。它不在安装目录里,所以卸载重装不会把你填的节点带走。
macOS
打开 dmg,把 Shadowpacket 拖进「应用程序」,覆盖已有的 /Applications/Shadowpacket.app。Release 包已经过 Apple 公证,不需要绕过 Gatekeeper。
首次连接时系统会弹出 VPN 配置授权,必须允许,否则隧道起不来。授权后它会出现在「系统设置 → 网络」里的 VPN 列表中。
如果你在系统设置里撤销了这个授权,Shadowpacket 会把已记录的连接意图一并清掉,不会在后台反复尝试重连。
配置文件在应用组容器里:~/Library/Group Containers/3HLV443C6K.group.io.shadowpacket.app/io.shadowpacket.app/conf/shadowpacket-client.json。用应用组而不是各自的沙箱是有原因的——应用和隧道扩展是两个进程,各自解析 Application Support 会落到两棵不同的树上,扩展那边的规则列表就是空的。
iOS
当前提供的是 IPA,需要用电脑安装到自己的设备上(开发者签名)。重装同一个 bundle ID 即为原地覆盖。
首次连接时应用要写入一份 VPN 配置,系统会为此弹出确认,并要求用 Face ID、Touch ID 或锁屏密码验证。不允许就没有隧道——这一步没有任何绕过方式。配置写好后,它会出现在「设置 → 通用 → VPN 与设备管理」里;在那里删掉它就是撤销授权,下次连接会重新弹一次。
撤销授权和你自己断开、退出一样,会把已记录的连接意图一并清掉,所以之后不会有任何东西替你把隧道拉回来。
配置文件在应用组 group.io.shadowpacket.app 的容器里,路径是 deploy/conf/shadowpacket-client.json。这在 iOS 沙箱内部,设备上没有任何办法直接打开它——改配置请用应用界面,或者本机 Web Admin。
主动断开或退出之后,系统不会再重新建立 VPN 连接。反过来,隧道被系统结束掉——扩展进程被回收、崩溃,或者你重启了手机——它会自己回来,不需要你再点一次连接。
做这件事的不是应用。iOS 不允许应用从内部启动隧道进程,所以恢复靠的是连接成功那一刻装进系统的一条按需规则:应用完全没在运行也照样生效,而你主动断开时这条规则会被一起撤掉。重连发生在隧道断了之后第一次有网络活动的时候,不是在进程被回收的那一瞬间。
Android
下载 APK 后,第一次安装要先给「安装未知应用」的授权。Android 8.0 及以上依次点:
- 打开 设置
- 点 应用(部分系统叫「应用管理」或「应用和服务」)
- 点 特殊应用权限(找不到就点右上角的三点菜单再选「特殊访问权限」)
- 点 安装未知应用
- 在应用列表里,点你用来下载或打开这个 APK 的那个应用——用浏览器下载就点浏览器,用文件管理器打开就点文件管理器
- 打开 允许来自此来源
- 返回,再点一次 APK,即可安装
这个授权按来源发放,不是一个全局开关。给浏览器授了权,再改用文件管理器去打开同一个文件,系统照样拦——看起来就像刚才那次授权没生效,其实是需要再给文件管理器授一次。
直接点开下载好的 APK,系统会拦一次并提示「为了安全,你的手机目前不允许安装来自此来源的未知应用」,旁边有一个跳转按钮。点它就直接落到上面第 6 步那个开关页,来源也已经替你选好了。打开开关,返回,继续安装。
各家定制系统把这一项放在不同的地方,常见的几个位置如下(不同版本可能略有出入,翻不到就在设置的搜索框里搜「未知应用」或「未知来源」):
| 系统 | 依次点击 |
|---|---|
| 原生 Android / Pixel | 设置 → 应用 → 特殊应用权限 → 安装未知应用 |
| 小米 HyperOS / MIUI | 设置 → 应用设置 → 授权管理 → 安装未知应用 |
| 华为 EMUI | 设置 → 应用和服务 → 应用管理 → 三点菜单 → 特殊访问权限 → 安装未知应用 |
| OPPO ColorOS / 一加 | 设置 → 密码与安全 → 系统安全 → 安装外部来源应用 |
| vivo OriginOS | 设置 → 更多设置 → 权限管理 → 安装未知应用 |
| 三星 One UI | 设置 → 应用程序 → 三点菜单 → 特殊访问权限 → 安装未知应用 |
Android 7.0 与 7.1 没有按来源授权,只有一个全局开关,依次点:设置 → 安全 → 未知来源。它对整台设备生效,装完建议关掉。
两种情况下,装完之后把授权收回都不影响已经装好的应用。
首次连接会遇到两个系统弹窗,不是一个。一个是 VPN 授权,Android 强制由系统自己弹出,不允许就没有隧道;另一个是通知权限,Android 13 及以上才有。通知权限被拒绝时隧道照样能连,但下面那条常驻通知不会显示。
连接期间通知栏会常驻一条通知,这是 Android 前台服务的强制要求,关掉它等于让系统随时回收整个隧道。
配置文件在应用私有目录里:/data/data/io.shadowpacket.app/files/deploy/conf/shadowpacket-client.json。未 root 的设备上这个路径读不到,改配置请用应用界面,或者本机 Web Admin;有 adb 的话 run-as io.shadowpacket.app 可以进去。
在系统设置里关掉 VPN 授权,或者别的应用抢走了 VPN 通道,隧道都会立刻结束,并且已记录的连接意图会跟着一起清掉——所以之后不会有任何东西替你把它拉回来。开机恢复也遵守同一条线:只有在授权仍然有效、且此前记录过连接意图时,Shadowpacket 才会在物理网络就绪后自行恢复。
HarmonyOS
代码里 HarmonyOS 是完整支持的部署目标,但当前没有对外发布的 HAP。下面这几条是它与其它平台不同的地方,先记在这里。
首次连接时系统会弹出 VPN 授权,和 Android、iOS 一样,不允许就没有隧道。不同的是撤销:HarmonyOS 的「设置 → VPN」只管理系统内置的 VPN 配置,没有撤销第三方 VPN 扩展授权的入口,所以授权一旦给出,在系统设置里是收不回来的。
连接期间应用会持有一个数据传输类型的长时任务,并常驻一条通知。这条通知不是装饰:它是系统不回收应用的依据,而 VPN 扩展是应用的一个子进程,应用被回收,隧道跟着一起没。
配置文件在应用私有目录 filesDir 下的 deploy/conf/shadowpacket-client.json,bundle 名是 io.shadowpacket.app。和 Android、iOS 一样在沙箱里,改配置请用应用界面或本机 Web Admin。
HarmonyOS 是唯一没有开机恢复的部署,这是权限边界而不是遗漏:订阅开机广播需要系统签名的能力,商店分发的应用拿不到。它会在下一次启动应用时按已记录的意图恢复,不需要你再点一次连接。
Ubuntu desktop(.deb 图形客户端)
sudo apt install ./shadowpacket_ubuntu_<版本>_r.deb
装到 /opt/shadowpacket,并在 /usr/bin/shadowpacket 留一个软链接。重复安装同一个包即为原地覆盖升级,不会留下并存的旧副本。它不注册 systemd 单元:连接与断开由图形界面上的连接控件负责,也只由它负责。
配置文件在 ~/.local/share/shadowpacket/conf/shadowpacket-client.json,设置了 XDG_DATA_HOME 时则在 $XDG_DATA_HOME/shadowpacket/conf/ 下。它不在 /opt/shadowpacket 里,所以重装不会把你填的节点带走。首次运行后直接编辑它,或者在图形界面里改。
Ubuntu server 与 OpenWrt(tar.gz)
tar xzf shadowpacket_ubuntu-headless_<版本>_r.tar.gz
cd shadowpacket_ubuntu-headless_<版本>_r
sudo ./install.sh
install.sh 把整棵树放到 /usr/local/shadowpacket 并注册 systemd 单元。编辑 /usr/local/shadowpacket/conf/shadowpacket-client.json,把 servers(Shadowsocks)和 xray_outbounds(VLESS / Hysteria2 / TUIC)里的占位值换成你自己的节点,然后:
sudo systemctl enable --now shadowpacket-client
systemctl status shadowpacket-client
OpenWrt 装到同一个路径,但用 procd:把服务名换成 /etc/init.d/shadowpacket-runtime。
这两个部署没有图形界面,配置改起来还有第二条路:Web Admin 会随运行时一起起来,并且在隧道起不来的时候照样能访问——否则把配置改坏了就没有任何办法改回来了。
验证
「能上网」不等于「分流对了」。下面两组命令要分开看,因为两种路由模式该出的结果不一样。
全局代理下一切都进隧道,任何站点回来的都应该是节点的出口IP:
curl -s https://api.ipify.org # 应返回节点出口IP
配置代理下只有命中规则列表的域名才进隧道,其余照常直连。所以这时候要跑两条对照着看,一条命中、一条不命中:
curl -s https://api.ipify.org # 命中代理域名列表 → 节点出口IP
curl -s https://myip.ipip.net # 未命中代理域名列表 → 你自己的出口IP
两条返回同一个地址,就说明分流没生效——要么全都走了代理,要么全都没走。
随包的 proxy_domain.txt 里有一条 ipify.org。域名规则是后缀匹配,逐级剥掉子域再比对,所以 api.ipify.org 命中 ipify.org,子域名一并生效。如果你自己改过这份列表,这条命令就不一定还成立——先确认列表里有它,再用它当判据。
DNS 那一侧同样要对照:
dig +short @127.0.0.1 -p 1053 www.google.com # 被代理 → 198.19.x.x 合成地址
dig +short @127.0.0.1 -p 1053 www.baidu.com # 直连 → 真实地址
被代理的域名返回 198.19.x.x 这样的合成地址,查询耗时 0ms 且不出网——这是预期行为,真实地址在隧道对端解析。直连域名必须返回真实地址:如果它也变成了合成地址,说明规则判定出了问题,国内站点会连不上。
客户端使用
装好、填好节点之后,剩下的都在这一章。这些都是图形界面上的功能,Ubuntu server 与 OpenWrt 没有界面,对应的事情在 Web Admin 里做。
配置代理与全局代理
首页的代理方式就是这个开关,两个取值:
- 配置代理——只有规则列表里的域名走代理,其余一律直连。这是默认,也是日常该用的:国内站点直连,速度和平时一样,流量特征也和普通用户一样。
- 全局代理——除私有地址外全部走代理。
一个域名如果不在列表里,它就不走代理,哪怕它确实被墙。这是策略,不是缺陷:程序不会因为某个站点连不上就自动把它塞进代理。要让它走代理,把域名加进临时 Proxy Domain 列表,见下一节;或者临时切到全局代理。
私有地址永远直连
RFC1918 私网段、回环、链路本地、组播这些 bogon 地址,在两种模式下都直连,全局代理也不例外。判断排在所有名单规则之前。
这不是可有可无的细节:否则一开全局代理,路由器管理页、NAS、打印机、局域网里的一切都会被送进隧道,等于断网。随包的 temp/direct_ip.temp 预置了这些网段,就是为了第一次连接前它们就已经生效。
规则列表与临时列表
设置里有四个临时列表,改完立即生效,不用重连:
- 临时 Proxy Domain——最常用的一个。配置代理下某个站点打不开,把域名加到这里。
- 临时 Direct Domain——某个域名被规则判成了代理但你想直连,加到这里。
- 临时 Proxy IP / 临时 Direct IP——同样的意思,针对 IP 和网段。
域名规则是后缀匹配:写 example.com,a.example.com 和 b.a.example.com 一并命中,不用逐个子域添加。
持久列表 proxy_domain.txt 随包分发,有四千多条。设置里的更新持久 Proxy Domain 从配置里写的源地址重新拉一份,适合列表过时的时候用。临时列表和它是叠加关系,不会互相覆盖。
DNS Host Overrides
设置里的 DNS Host Overrides 相当于一份 hosts 表:给指定域名钉死一个或多个 IP,解析直接返回它,不再走上游也不走 fake DNS。
用得上的场景:某个域名的公开解析结果不可用而你知道正确的地址;或者要把一个域名固定到指定节点做排查。不需要的时候删掉即可,不影响其它域名。
清理缓存
运行过程中程序会把学到的东西写回几个文件——哪些 IP 属于代理侧、哪些域名解析到过什么。这些是缓存,不是你的配置。
设置里的清理缓存清空这六个:proxy_ip.txt、direct_ip.txt、direct_domain.txt、futu_ip.txt、futu_url.txt、record.txt。
它不动你的节点配置、proxy_domain.txt,也不动四个临时列表。所以线路行为变得奇怪、怀疑是旧的学习结果在作怪时,清一次是安全的第一步。
配置文件
设置 → 配置文件可以直接看到并编辑完整的 JSON,页面顶部有粘贴、复制、保存三个动作,也可以选择本地配置文件导入。改完保存才生效,中途退出不会写盘。
如果剪贴板里是一份合法的配置,应用会主动提示检测到剪贴板配置,问你要不要用——别人给你一份配置时,复制了直接打开应用即可,不用手动粘。
从局域网获取配置
设置 → 从局域网获取配置,用于把一台设备上已经配好的节点搬到另一台,不用手动抄。
- 两台设备连同一个局域网,都打开这个页面
- 提供方页面上显示本机配对码和监听地址
- 接收方在设备列表里点对方,输入那个配对码
- 接收方确认后覆盖本机配置
两点要注意:双方都要停在这个页面上,退出页面就不再提供;以及这是覆盖,接收方原有的节点会被替换掉。
Web Admin
Web Admin 是本机跑的一个网页管理端,用浏览器改配置、看状态,功能比图形界面更全。地址在设置 → 关于 → 管理端那一行,形如 127.0.0.1:9090;同一个局域网里的其它设备也能访问它的局域网地址。密码就是你在配置里设的那个。
Web Admin 跟着应用进程走。Android、iOS、HarmonyOS 上应用一旦切到后台,系统就会挂起它,管理端随之无法访问——不是崩溃,是这些平台不允许普通应用在后台一直守着一个监听端口。用手机上的 Web Admin 时,把应用放在前台别切走。
Windows、macOS、Ubuntu desktop 没有这个限制,应用在运行就一直可用。Ubuntu server 与 OpenWrt 上它由服务持有,而且隧道起不来时照样能访问——那是唯一能把配置改回去的通道。
服务端安装
服务器搭建
需要的东西:一台墙外 VPS,一个你自己的域名,以及一份该域名的证书。下面的命令按 Ubuntu 写,参考部署跑的是 Ubuntu 24.04。
四种协议可以共存在同一台机器、同一个公网 443 上,因为它们占的是不同的东西:TCP 443 由 nginx 的 stream 块按 SNI 分流,UDP 443 给 Hysteria2,TUIC 另开一个 UDP 端口。
一、域名与 Cloudflare
域名可以直接在 Cloudflare 注册,也可以在别处注册后把解析托管过来。托管到 Cloudflare 不是为了它的 CDN——恰恰相反,下面会把代理关掉——而是为了拿一个 DNS API Token,让 acme.sh 能自动签发和续期证书。
注册或转入。在 Cloudflare 控制台的 Domain Registration 里可以直接买;已经有域名的话走「Add a site」:输入域名,选 Free 计划,Cloudflare 会扫一遍现有解析记录,然后给你两个 NS 地址。去原注册商把域名的 NS 换成这两个,等生效——通常几分钟到几小时,控制台里那个域名从 Pending 变成 Active 就算好了。
加 DNS 记录。进这个域名的 DNS → Records → Add record:
A,Name 填@(或者你要用的子域名),IPv4 address 填服务器的公网 IPv4。- 服务器有公网 IPv6 的话,再加一条
AAAA,同样的 Name,填 IPv6 地址。
Cloudflare 默认给新记录开橙云(Proxied)。开着的话所有流量先进 Cloudflare 再回源,443 到不了你自己的机器:Reality 借的握手没了,Hysteria2 和 TUIC 的 UDP 根本不过 Cloudflare,四条链路里三条直接废掉。点那朵橙色的云把它切成灰色的 DNS only。
记录生效后确认一下解析回来的就是你的服务器:
dig +short example.com A
dig +short example.com AAAA
建 API Token。右上角头像 → My Profile → API Tokens → Create Token,选 Edit zone DNS 模板,然后:
- Permissions 保持模板给的 Zone / DNS / Edit,不要加别的
- Zone Resources 选 Include / Specific zone / 你的域名,不要选 All zones
- Continue to summary → Create Token
- 把 Token 复制走。它只显示这一次,关掉页面就再也看不到,只能重新生成
这个 Token 就是后面 acme.sh 要的 CF_Token。用 Token 而不是 Global API Key:Global Key 等于整个账户的全部权限,泄露了对方能改你名下每一个域名;这个 Token 只能改这一个域名的 DNS 记录。
二、装 nginx
发行版自带的 nginx 也能用,但要确认它带 stream 和 ssl_preread——整套按 SNI 分流就靠这两个。参考部署直接用 nginx 官方源:
从 nginx 官方源安装
curl -fsSL https://nginx.org/keys/nginx_signing.key \
| sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
https://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
sudo apt update && sudo apt install -y nginx
装完先确认这三项都在,缺一整套都跑不起来:
nginx -V 2>&1 | tr ' ' '\n' | grep -E 'with-stream|ssl_preread|http_ssl'
三、签证书(acme.sh)
用 DNS 验证,不用 HTTP 验证——80/443 后面马上要被 nginx 的分流占住,而且 DNS 验证才能签泛域名。上一步那个 Token 就是给它用的。
git clone https://github.com/acmesh-official/acme.sh.git
cd acme.sh
./acme.sh --install -m you@example.com
安装脚本会把自己装到 ~/.acme.sh,写好 shell 别名,并且自动装好续期用的 cron,不需要你手写。装完 source ~/.bashrc 或重开一个 shell。
签发。这里以 Cloudflare 托管 DNS 为例,先在 Cloudflare 后台建一个有该域名 DNS 编辑权限的 API Token:
export CF_Token="<你的 Cloudflare API Token>"
acme.sh --set-default-ca --server letsencrypt
acme.sh --issue --dns dns_cf --keylength ec-256 \
-d example.com -d '*.example.com'
--set-default-ca --server letsencrypt 是要紧的一步:acme.sh 3.x 默认签 ZeroSSL,那个要先注册账户,不指定就会在这里卡住。ec-256 出 ECC 证书,后面所有文件名都带 _ecc 后缀。
装到 nginx 读得到的地方,不要直接在配置里指向 ~/.acme.sh 下的文件——那里的文件在续期时会被换掉,而且 nginx 以别的用户跑:
把证书装到 nginx 读得到的位置
sudo mkdir -p /etc/nginx/ssl
acme.sh --install-cert -d example.com --ecc \
--ca-file /etc/nginx/ssl/example.com.ca.cer \
--fullchain-file /etc/nginx/ssl/example.com.fullchain.cer \
--key-file /etc/nginx/ssl/example.com.key \
--reloadcmd "nginx -t && systemctl reload nginx"
--reloadcmd 先 nginx -t 再 reload:续期后如果配置恰好是坏的,宁可不 reload 也不要把线上的 nginx 弄停。
cron 由 acme.sh 自己装,长这样,一天跑四次,到期前 60 天才真正续:
crontab -l | grep acme
# 41 5,11,17,23 * * * HOME=/home/ubuntu /home/ubuntu/.acme.sh/acme.sh --cron --home /home/ubuntu/.acme.sh > /dev/null
想确认续期链路是通的,不用等两个月:
acme.sh --cron --force --home ~/.acme.sh
四、nginx 配置
下面是参考部署里与 Shadowpacket 有关的部分,域名、公网地址和证书路径都换成了占位符,其余无关站点的 server 块已经删掉。照抄之后要改的地方:example.com 换成你的域名,<服务器公网 IPv4> 之类换成你自己的地址,/sspath 换成一个只有你知道的路径。
完整的 /etc/nginx/nginx.conf
这是一份可以直接落到 /etc/nginx/nginx.conf 的完整配置,四种协议全部覆盖。要改的地方只有三处:example.com 换成你的域名,<服务器公网 IPv4> 一类换成你自己的出口地址,/sspath 换成一个只有你知道的路径。
user www-data;
worker_processes auto;
worker_rlimit_nofile 131072;
pid /var/run/nginx.pid;
error_log /var/log/nginx/error.log;
events {
worker_connections 4096;
multi_accept on;
}
http {
include mime.types;
default_type application/octet-stream;
charset utf-8;
log_format main '$remote_addr [$time_local] "$request" $status '
'$body_bytes_sent host=$host';
access_log /var/log/nginx/access.log main;
log_format sp_ws '$remote_addr [$time_local] "$request" $status '
'bytes=$body_bytes_sent request_time=$request_time';
# 一台小内存 VPS 上,大量并发不是要去承接的流量,而是攻击或故障。
# 按 $server_port 计数,全站共用一个预算。
limit_conn_zone $server_port zone=sp_http_total:10m;
limit_conn_zone $binary_remote_addr zone=sp_http_per_ip:10m;
limit_req_zone $binary_remote_addr zone=sp_http_req:10m rate=200r/s;
limit_conn sp_http_total 1024;
limit_conn sp_http_per_ip 512;
limit_req zone=sp_http_req burst=200 nodelay;
limit_conn_status 429;
limit_req_status 429;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
keepalive_timeout 65;
client_max_body_size 16m;
proxy_connect_timeout 60s;
proxy_send_timeout 1800s;
proxy_read_timeout 1800s;
proxy_buffering on;
proxy_request_buffering off;
ssl_session_timeout 1h;
ssl_session_cache shared:SSL:16m;
ssl_prefer_server_ciphers on;
ssl_ecdh_curve X25519:P-256;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
# 公网 :443 在下面的 stream 块里,连接经回环交给这里,所以
# $remote_addr 本来永远是 127.0.0.1。stream 那边发 PROXY 头,
# 这里信它——但只信回环,放宽了任何客户端都能自己声明来源地址。
set_real_ip_from 127.0.0.1;
set_real_ip_from ::1;
real_ip_header proxy_protocol;
real_ip_recursive on;
# 80 一律跳 443。
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$host$request_uri;
}
# 承载 Shadowsocks 的站点。只监听回环,公网进不来;
# 全部流量由下面的 stream 块按 SNI 转进来。
server {
listen 127.0.0.1:8443 ssl default_server proxy_protocol;
http2 on;
server_name example.com;
ssl_trusted_certificate /etc/nginx/ssl/example.com.ca.cer;
ssl_certificate /etc/nginx/ssl/example.com.fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
# Shadowsocks 的入口。路径换成一个只有你知道的。
location /sspath {
# 第二道防回环。stream 里那道只管 Reality,这条连接
# 会一直走到 http 块,所以要在这里再拦一次。
if ($remote_addr = <服务器公网 IPv4>) { return 403; }
if ($remote_addr = <服务器内网 IPv4>) { return 403; }
if ($remote_addr = <服务器公网 IPv6>) { return 403; }
proxy_pass http://127.0.0.1:1080; # xray-plugin 服务端模式
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_buffering off;
proxy_request_buffering off;
proxy_socket_keepalive on;
tcp_nodelay on;
proxy_read_timeout 300s;
access_log /var/log/nginx/sp-ws.log sp_ws;
}
# 其余路径要像个正常网站。放点静态内容即可,
# 但不要留成 404 或者 nginx 默认欢迎页。
location / {
root /var/www/decoy;
try_files /index.html =404;
}
}
}
# http 之外,与它平级。
stream {
# 按端口限并发,不按客户端地址:一个家庭共用一个公网 IP,
# 而每条被代理的 TCP 连接都是一条独立的 stream 连接。
limit_conn_zone $server_port zone=sp_stream_total:10m;
limit_conn_zone $binary_remote_addr zone=sp_stream_per_ip:10m;
# 防回环。客户端必须直连代理端点;一旦它把本机地址也送进隧道,
# 这台机器就会不停地拨自己,直到内存耗尽。
# 本机的每一个出口地址都要列进来。
geo $sp_self_source {
default 0;
<服务器公网 IPv4>/32 1;
<服务器内网 IPv4>/32 1;
<服务器公网 IPv6>/128 1;
}
map "$sp_self_source:$ssl_preread_server_name" $sp_tcp_upstream {
default 127.0.0.1:8443; # 其余 SNI 交给 http 块
"0:www.cloudflare.com" 127.0.0.1:24443; # Reality
# 本机自己以 Reality 的 SNI 拨过来,只可能是回环。
# 127.0.0.1:9 是丢弃端口,没有任何东西监听,连接立刻被拒,
# 递归死在第一跳。
"1:www.cloudflare.com" 127.0.0.1:9;
}
# 这两个 UDP 端口只承载 Hysteria2 和 TUIC,本机没有任何理由
# 往它们发数据报,所以可以用比 TCP 那条更粗暴的判断。
map $sp_self_source $sp_hy2_upstream {
default 127.0.0.1:34444;
1 127.0.0.1:9;
}
map $sp_self_source $sp_tuic_upstream {
default 127.0.0.1:30045;
1 127.0.0.1:9;
}
log_format sp_stream '$remote_addr [$time_local] $protocol '
'$bytes_received/$bytes_sent $session_time '
'-> $upstream_addr sni=$ssl_preread_server_name';
access_log /var/log/nginx/sp-stream.log sp_stream;
error_log /var/log/nginx/sp-stream-error.log warn;
# 公网 443 TCP:读 SNI,Reality 一路,其余交给 http 块。
server {
listen 443;
listen [::]:443;
limit_conn sp_stream_total 1024;
limit_conn sp_stream_per_ip 512;
proxy_pass $sp_tcp_upstream;
ssl_preread on;
# 不发 ClientHello 的连接没有东西可路由,别让它占满 30 秒。
preread_timeout 5s;
# 后端看到的客户端地址靠它传过去,Reality 那边必须
# 相应打开 acceptProxyProtocol。
proxy_protocol on;
proxy_connect_timeout 10s;
proxy_timeout 1800s;
}
# Hysteria2:QUIC 不经过上面的 TCP server,UDP 443 直接转发。
server {
listen 443 udp reuseport;
listen [::]:443 udp reuseport;
proxy_timeout 10m;
proxy_pass $sp_hy2_upstream;
}
# TUIC v5:443 UDP 已经给了 Hysteria2,另开一个端口。
server {
listen 34445 udp reuseport;
listen [::]:34445 udp reuseport;
proxy_timeout 10m;
proxy_pass $sp_tuic_upstream;
}
}
这份配置把四条链路分到了四个地方,对照着看一遍:
| 公网入口 | 分流依据 | 后端 |
|---|---|---|
| 443 TCP | SNI 等于 Reality 的 serverNames | 127.0.0.1:24443,Xray 的 VLESS + Reality |
| 443 TCP | 其余 SNI | 127.0.0.1:8443,nginx http 块,再到 xray-plugin 与 ssserver |
| 443 UDP | 无 | 127.0.0.1:34444,sing-box 的 Hysteria2 |
| 34445 UDP | 无 | 127.0.0.1:30045,sing-box 的 TUIC v5 |
TCP 那一路用 ssl_preread 读 SNI,并且必须开 proxy_protocol on,否则所有后端看到的客户端地址都是 127.0.0.1。相应地 http 块要 set_real_ip_from 127.0.0.1 加 real_ip_header proxy_protocol,而 set_real_ip_from 必须只写回环地址——放宽了任何客户端都能自己声明来源地址。
stream 里那条只管 Reality——Reality 在第四层就被 SNI 分走了,根本到不了 http 块。而 Shadowsocks 走的是 http 块,stream 那条对它无效。两处各拦各的,缺哪一处哪条链路就是敞开的。把本机的每一个出口地址都列进去,公网 IPv4、内网 IPv4、IPv6 一个都不能漏。
改完照例先验后重载:
sudo nginx -t && sudo systemctl reload nginx
Shadowsocks + xray-plugin
服务端是上游发行的 ssserver 加 xray-plugin 的服务端模式,藏在 nginx 的某个 https 站点的一个私有路径后面。链路是 nginx 终结 TLS,转发 WebSocket 给 xray-plugin,xray-plugin 再交给 ssserver。
两个二进制都从上游的发布页取,本项目不分发它们:
ssserver—— github.com/shadowsocks/shadowsocks-rust/releases,在对应架构的压缩包里(服务器一般是x86_64-unknown-linux-gnu或aarch64-unknown-linux-gnu)。xray-plugin—— github.com/teddysun/xray-plugin/releases,同样按架构选。
目录与二进制,两个程序放在一起,配置和它们分开:
sudo mkdir -p /usr/local/shadowsocks/{bin,conf}
# 解开两个压缩包后,把 ssserver 和 xray-plugin 放进 bin/
sudo install -m 0755 ssserver xray-plugin /usr/local/shadowsocks/bin/
ssserver 配置 /usr/local/shadowsocks/conf/shadowsocks-server.json
{
"servers": [
{
"server": "127.0.0.1",
"server_port": 1080,
"password": "<与客户端相同的口令>",
"mode": "tcp_and_udp",
"timeout": 300,
"method": "aes-256-gcm",
"plugin": "/usr/local/shadowsocks/bin/xray-plugin",
"plugin_opts": "server;host=yourdomain.com;path=/sspath",
"plugin_mode": "tcp_and_udp"
}
],
"fast_open": true,
"no_delay": true
}
server_port 是 xray-plugin 监听的端口,不是 ssserver 自己的——ssserver 另外挑一个本机端口交给插件回传,不用你配。绑在 127.0.0.1 是因为对外那一层由 nginx 负责,这个端口不该出现在公网上。
服务端的 plugin_opts 不写 tls,客户端才写:TLS 由 nginx 终结,到这里已经是明文 WebSocket 了。host 与 path 两端必须一致,host 就是那个 vhost 的 server_name。
systemd 单元 /etc/systemd/system/shadowsocks-rust.service
[Unit]
Description=shadowsocks-rust server
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
DynamicUser=true
ExecStart=/usr/local/shadowsocks/bin/ssserver -c /usr/local/shadowsocks/conf/shadowsocks-server.json
Restart=on-failure
RestartSec=5
LimitNOFILE=1048576
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/usr/local/shadowsocks/conf/shadowsocks-server.json
[Install]
WantedBy=multi-user.target
DynamicUser=true 让 systemd 临时造一个专用账户,不用你建;它只绑回环端口,不需要任何特权。启动:
sudo systemctl daemon-reload
sudo systemctl enable --now shadowsocks-rust
systemctl status shadowsocks-rust
四件必须和客户端对齐的事:加密方式 aes-256-gcm;plugin_opts 里的 path 与 nginx 的 location 一致;host 等于那个 vhost 的 server_name;plugin_mode 必须是 tcp_and_udp。
plugin_mode 服务端也要写 tcp_and_udp。
两端都不行。
VLESS + Reality + XTLS/Vision
这一条由 Xray-core 提供,是四个里唯一一个不经过 nginx 的 http 块的——它在第四层就被 SNI 分走了。
二进制从 github.com/XTLS/Xray-core/releases 取,按架构选 Xray-linux-64.zip 或 Xray-linux-arm64-v8a.zip:
sudo mkdir -p /usr/local/xray/{bin,conf,data,logs}
unzip Xray-linux-64.zip xray geoip.dat geosite.dat
sudo install -m 0755 xray /usr/local/xray/bin/
sudo install -m 0644 geoip.dat geosite.dat /usr/local/xray/data/
geoip.dat 和 geosite.dat 不是可选的:下面的路由规则用了 geoip:private 和 geosite:category-ads-all,缺了它们 xray 起不来。
生成一对 Reality 密钥和一个 UUID:
/usr/local/xray/bin/xray x25519 # Private key 留服务端,Public key 填客户端
/usr/local/xray/bin/xray uuid # 客户端的 id
openssl rand -hex 4 # shortId,1 到 8 字节的十六进制
Xray 配置 /usr/local/xray/conf/config.json
{
"log": {
"loglevel": "warning",
"access": "/usr/local/xray/logs/access.log",
"error": "/usr/local/xray/logs/error.log"
},
"inbounds": [
{
"tag": "vless-reality-vision",
"listen": "127.0.0.1",
"port": 24443,
"protocol": "vless",
"settings": {
"clients": [
{ "id": "<UUID>", "flow": "xtls-rprx-vision" }
],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "www.cloudflare.com:443",
"xver": 0,
"serverNames": ["www.cloudflare.com"],
"privateKey": "<Reality 私钥>",
"shortIds": ["<shortId>"]
},
"tcpSettings": { "acceptProxyProtocol": true }
}
}
],
"outbounds": [
{ "tag": "direct", "protocol": "freedom" },
{ "tag": "blocked", "protocol": "blackhole" }
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"ip": [
"geoip:private",
"<服务器公网 IPv4>/32",
"<服务器内网 IPv4>/32",
"<服务器公网 IPv6>/128"
],
"outboundTag": "blocked"
},
{
"type": "field",
"domain": ["geosite:category-ads-all"],
"outboundTag": "blocked"
}
]
}
}
routing 里那条 blocked 是第三道防回环:把私网段和本机自己的每一个地址都拉黑,这样即使有请求要求 xray 去连本机,它也只会被丢掉。dest 和 serverNames 要选一个真实存在、支持 TLS 1.3、并且在你所在地区没有被墙的站点——Reality 借的就是它的握手。
nginx 的 stream 块开了 proxy_protocol on,而这个开关对该 server 代理到的每一个上游都生效,没有单独关掉的写法。少了它,xray 会把 PROXY 头当成 TLS ClientHello 的头几个字节,Reality 直接失败。
systemd 单元 /etc/systemd/system/xray.service
[Unit]
Description=Xray Service
Documentation=https://github.com/xtls
After=network.target nss-lookup.target
[Service]
User=root
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
Environment="XRAY_LOCATION_ASSET=/usr/local/xray/data"
ExecStart=/usr/local/xray/bin/xray run -config /usr/local/xray/conf/config.json
Restart=on-failure
RestartPreventExitStatus=23
LimitNPROC=10000
LimitNOFILE=1000000
[Install]
WantedBy=multi-user.target
XRAY_LOCATION_ASSET 必须指向放 geo 文件的那个目录,否则 xray 会去默认路径找,然后因为找不到而拒绝启动。
sudo systemctl daemon-reload
sudo systemctl enable --now xray
systemctl status xray
Hysteria2
Hysteria2 和 TUIC v5 由同一个 sing-box 提供,两个入站写在一份配置里,一个服务带起来。所以下载、目录和 systemd 只做一次,下一节的 TUIC 直接往同一份配置里加。
二进制从 github.com/SagerNet/sing-box/releases 取。官方有 .deb,装完连 systemd 单元一起带上,最省事:
curl -fsSLO https://github.com/SagerNet/sing-box/releases/latest/download/\
sing-box_<版本>_linux_amd64.deb
sudo dpkg -i sing-box_<版本>_linux_amd64.deb
用压缩包也行,那就自己放二进制、建目录、写单元:
sudo install -m 0755 sing-box /usr/bin/sing-box
sudo mkdir -p /etc/sing-box /var/lib/sing-box
sudo useradd --system --no-create-home --shell /usr/sbin/nologin sing-box
证书两个入站共用,指向 acme.sh 装好的那一份。sing-box 以非 root 跑,要能读到私钥:
sudo chgrp sing-box /etc/nginx/ssl/example.com.key
sudo chmod 0640 /etc/nginx/ssl/example.com.key
配置写到 /etc/sing-box/config.json。下面这份把 Hysteria2 和下一节的 TUIC 一起给全了:
sing-box 配置 /etc/sing-box/config.json(Hysteria2 与 TUIC 都在里面)
{
"log": { "level": "info", "timestamp": true },
"inbounds": [
{
"type": "hysteria2",
"tag": "hysteria2-in",
"listen": "127.0.0.1",
"listen_port": 34444,
"users": [
{ "name": "shadowpacket", "password": "<Hysteria2 口令>" }
],
"ignore_client_bandwidth": true,
"tls": {
"enabled": true,
"server_name": "example.com",
"certificate_path": "/etc/nginx/ssl/example.com.fullchain.cer",
"key_path": "/etc/nginx/ssl/example.com.key"
},
"masquerade": "https://example.com/"
},
{
"type": "tuic",
"tag": "tuic-in",
"listen": "127.0.0.1",
"listen_port": 30045,
"users": [
{
"name": "shadowpacket",
"uuid": "<TUIC UUID>",
"password": "<TUIC 口令>"
}
],
"congestion_control": "bbr",
"auth_timeout": "3s",
"zero_rtt_handshake": false,
"heartbeat": "10s",
"tls": {
"enabled": true,
"server_name": "example.com",
"certificate_path": "/etc/nginx/ssl/example.com.fullchain.cer",
"key_path": "/etc/nginx/ssl/example.com.key"
}
}
],
"outbounds": [ { "type": "direct", "tag": "direct" } ],
"route": { "final": "direct" }
}
口令和 UUID 这样生成:
openssl rand -hex 24 # Hysteria2 与 TUIC 各一个
sing-box generate uuid # TUIC 的 uuid
两个入站都只监听 127.0.0.1,公网那一跳由 nginx 的 stream 块转发——它们不经过 nginx 的 http 块,QUIC 也不做 SNI 分流,UDP 端口直接对应。masquerade 让用 HTTP/3 直接探它的人看到一个普通网站而不是一个代理。
systemd 单元 /etc/systemd/system/sing-box.service(用 .deb 装的话它已经在了):
systemd 单元 /etc/systemd/system/sing-box.service
[Unit]
Description=sing-box service
Documentation=https://sing-box.sagernet.org
After=network.target nss-lookup.target network-online.target
[Service]
User=sing-box
StateDirectory=sing-box
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
ExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10s
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now sing-box
systemctl status sing-box
TUIC v5
TUIC 的入站已经包含在上一节那份 /etc/sing-box/config.json 里了,不需要第二个进程,也不需要第二个单元。改完配置重载一次即可:
sudo systemctl restart sing-box
UDP 443 已经给了 Hysteria2,所以 TUIC 要另开一个公网 UDP 端口转进来,参考部署用的是 34445。zero_rtt_handshake 保持 false:0-RTT 的数据是可重放的,省下的那一个往返不值这个代价。
协议说明
| 协议 | 传输 | 适用场景 |
|---|---|---|
| Shadowsocks + xray-plugin | WebSocket / TLS over TCP | 复用 HTTPS 入口,兼容性最好 |
| VLESS + Reality + XTLS/Vision | TCP | 抗主动探测,无需自有证书 |
| Hysteria2 | QUIC / UDP | 移动网络、弱网、高丢包 |
| TUIC v5 | QUIC / UDP | 低延迟、多路复用 |
后端选择顺序
所有部署的 T-DATA 与 Proxy DNS 都按 Shadowsocks、VLESS、Hysteria2、TUIC v5 的固定顺序选择已启用节点。U-DATA 跳过 Shadowsocks,并且只有开启 UoT 的 VLESS 才参与,随后依次选择 Hysteria2、TUIC v5;一个能承载 U-DATA 的后端都没有时,UDP 安全失败而不回落。这三个结果是推导出来的,不可配置。
常见问题
为什么 ping 一个网站得到 198.19 开头的地址?
这是 fake DNS。被代理的域名一律返回保留地址池里的合成地址,不向任何上游发出查询,真实地址在隧道对端解析。好处是 DNS 既不出网也不花往返;代价是这些地址只在隧道内有意义。
为什么按主机名连 SSH / SMTP 有时会失败?
当前使用 VLESS、Hysteria2 或 TUIC 承载流量时,域名还原依赖 TLS SNI 与 HTTP Host 嗅探。先解析域名再开非 TLS/HTTP 连接的协议无法被嗅探,合成地址就无法还原。改用 Shadowsocks 后端,或直接用 IP 连接可以绕开。
DNS 完全不出网,会不会解析不了国内网站?
不会。只有被判定为需要代理的域名返回合成地址,直连域名照常经境内解析器解析,流量特征与普通用户一致。
为什么必须关掉系统的「私有 DNS」严格模式?
DNS-over-TLS 走 853 端口,会绕过 53 端口的拦截规则,fake DNS 的收益归零,因此 853 被阻断。opportunistic 模式下客户端探测失败会自动回落到明文 53,没有影响;但如果私有 DNS 被设为指定主机名的严格模式,DNS 会完全不可用。
UDP 为什么有时不通?
xray-plugin 没有 UDP 监听。选择 Shadowsocks 后端时,普通 UDP 优先走 Hysteria2,其次 TUIC;两者都没有配置时 UDP 会安全失败,而不会回落到没有外层加密的 Shadowsocks UDP。
卸载后还会留下东西吗?
退出与卸载会停止 TUN / 代理监听 / 插件进程,并撤销由本程序添加的路由、防火墙和 DNS 改动。清理是幂等的,崩溃或中途失败不会留下残余端口或规则。
