香港服务器的 UDP 全被封,VPN 就只能走 TCP 了

自建服务 · 11 分钟阅读· 59 次阅读

香港服务器的 UDP 全被封,VPN 就只能走 TCP 了

在同一台跑着博客和游戏站的香港服务器上架了个 VPN 给自己用。体检全部合格,唯独 UDP 入站被上游 NAT 封得死死的——于是 WireGuard 出局,OpenVPN 走 TCP 8443。

先说结论

  • 这台服务器做 VPN 完全可行:KVM 全虚拟化、内核自带 WireGuard、TUN 可用,但 UDP 入站被服务商 1:1 NAT 封死,原生 WireGuard 方案直接出局
  • 最终方案:OpenVPN over TCP 8443,独立端口不碰 443,实测全隧道出口正常、ping 8.8.8.8 延迟 2.4ms、Google 返回 200
  • 最大的坑不在 OpenVPN,在 firewalld:它自己的转发链末尾是一条 reject,执行优先级还比 iptables 高,iptables 里加多少条 ACCEPT 都没用

第一步:先搞清楚「能不能做」

光看配置单看不出来,直接登上去体检:

检查项 结果
虚拟化 KVM 全虚拟化(不是 OpenVZ,不会被内核锁死)
系统 CentOS Stream 9,内核 5.14,自带 wireguard 模块
权限 root 全量 capabilities
TUN/TAP /dev/net/tun 存在,ip tuntap add 实测成功
防火墙 宝塔 iptables 链 + firewalld 双层
资源 7.6G 内存(可用 2.7G)/ 50G 盘剩 22G / 下行约 110Mbps
TCP 入站 任意端口可达,实测临时开 51822 秒连上
UDP 入站 全部超时

最后一行就是整篇文章的转折点。

第二步:UDP 到底是不是真被封了

「UDP 不通」有很多种可能:我自己被封?服务器防火墙?还是服务商?三步排除:

  1. 本机发 UDP 到服务器 51820 —— 超时
  2. 服务器自己给自己公网 IP 发(hairpin)—— 超时
  3. 本机 UDP 出口正常吗?DNS(53)、NTP(123)都通 —— 排除自己这边

都排除之后,请外援:用 check-host 从全球 60 个节点同时探测 UDP 51820,服务器上同时开 tcpdump 抓 eth0。结果:60 个节点全部 timeout,服务器上一个包都没抓到。

# 服务器上只看到 silence
tcpdump -n -i eth0 udp and port 51820
listening on eth0 ...  (0 packets captured)

包根本没进机房。再看一眼网卡就明白了:eth0 是 10.0.34.2 的私有地址,机器在服务商 1:1 NAT 后面,出口 SNAT 成公网 IP,而服务商只做了 TCP 端口映射,UDP 没映射。

这一步的教训:下结论之前先让结论可被证伪。「防火墙没放行」和「上游根本不转发」是两个世界的问题。

第三步:装 OpenVPN(这步反而是最快的)

EPEL 里直接装 openvpn 和 easy-rsa,证书用 ECDSA(prime256v1),服务端配置几个关键点:

port 8443
proto tcp-server        # ← UDP 被封,必须 TCP
dev tun
topology subnet
server 10.8.0.0 255.255.255.0
push "redirect-gateway def1 bypass-dhcp"
tls-crypt ta.key        # 控制信道加密,顺便防探测
data-ciphers AES-256-GCM:AES-128-GCM
mssfix 1360             # TCP 隧道里防分片
max-clients 8

小坑一个:EPEL 包的服务单元叫 openvpn-server@server,网上教程里清一色的 openvpn@server 会直接报 Unit not found。

服务端状态与客户端实测

第四步:转发被拒(本次最大的坑)

VPN 起来了、握手成功,但隧道里的流量出不去。iptables 这边明明是:

-P FORWARD ACCEPT
-A FORWARD -i tun0 -o eth0 -j ACCEPT      ← 计数器 0 packets

规则在、策略全 ACCEPT,计数器却永远是 0——说明包压根没走到 iptables 的 filter 表。真正的凶手在这里:

# nft list chain inet firewalld filter_FORWARD
type filter hook forward priority filter + 10; policy accept;
...
reject with icmpx admin-prohibited   ← 末尾这条

firewalld 自己那张 nft 表的转发链末尾是 reject,而且它的 hook 优先级(filter+10)比 iptables filter 表先执行。也就是说:包先过 firewalld 的链,匹配不到任何放行规则就被拒了,iptables 那些规则形同虚设。

解法顺理成章:把 tun0 加进 public 区域(和 eth0 同区,同区内转发放行),NAT 用 firewalld 的 masquerade 做主、iptables 规则做备。改完重启 firewalld、重启 OpenVPN,复测全通过——这套配置现在重启机器也活着。

第五步:怎么测才算真的测过

一开始图省事,在同一台服务器上再起一个 openvpn 客户端连自己。结果 ping 10.8.0.1 不通、隧道流量也出不去,折腾半天发现是测试方法本身错了:服务端和客户端在同一个内核里,10.8.0.1 和 10.8.0.3 都是"本地地址",回程包直接被本地投递,根本不进隧道。

正确的姿势是用网络命名空间造一个真正的"外部客户端":

ip netns add vtest
ip link add veth-h type veth peer name veth-c
ip link set veth-c netns vtest
ip addr add 172.31.9.1/30 dev veth-h
ip netns exec vtest ip addr add 172.31.9.2/30 dev veth-c
ip netns exec vtest ip route add <服务器内网IP>/32 via 172.31.9.1
ip netns exec vtest openvpn --config client1.ovpn &
验证项 结果
客户端握手 通过,拿到 10.8.0.3
隧道内出口 IP 154.219.108.21(服务器公网 IP)
ping 8.8.8.8 0% 丢包,2.4ms
https://www.google.com HTTP 200
重启 firewalld + 重启 OpenVPN 后复测 全部通过

客户端分发

Mac 上用 Tunnelblick,.ovpn 一个文件打包了 CA、证书、私钥和 tls-crypt 密钥,双击导入。

两个顺带的坑:

  • tunnelblick.net 在大陆打不开。让香港服务器把最新版 dmg 下载下来再传回来,本地校验 SHA-256、codesign 开发者签名(Jonathan Bullard)和苹果公证票据,全套过了才装。
  • iOS 中国区 App Store 没有 OpenVPN Connect(2017 年整体下架过一波),得用香港区的 Apple ID 下载,付款方式选"无"就行。iOS 原生那套 IKEv2/L2TP 全是 UDP 500/4500,在这台机器上同样没戏。

运维备忘

项 位置
服务端配置 /etc/openvpn/server/server.conf
运行日志 /var/log/openvpn/openvpn.log
在线状态 /var/log/openvpn/status.log
NAT openvpn-nat.service + firewalld masquerade 双保险
吊销证书 easy-rsa revoke + gen-crl,crl-verify 已配好

最要紧的一条:别把 proto tcp-server 改成 UDP,改了立刻全断,而且这种断法连报错都不会有。

相关阅读

评论 (0)

还没有评论,来说两句吧~

发表评论

验证码

提交后需站长审核,通过后才会显示。