草庐IT

突破运营商 QoS 封锁,WireGuard 真有“一套”!

米开朗基杨 2023-11-01 原文

原文链接🔗:https://icloudnative.io/posts/wireguard-over-tcp-using-phantun/
或者点击左下角的 阅读原文 直接查看原文👇

👉WireGuard 作为一个更先进、更现代的 VPN 协议,比起传统的 IPSec、OpenVPN 等实现,效率更高,配置更简单,并且已经合并入 Linux 内核,使用起来更加方便,简直就是 VPN 中的战斗机。越来越多的高人利用 WireGuard 实现很多奇奇怪怪的需求。例如国内与国外机器通过 WireGuard 打通隧道,变成伪 IPLC 专线;或者打通本地与 Kubernetes 集群的网络。

但是 WireGuard 在国内网络环境下会遇到一个致命的问题:UDP 封锁/限速。虽然通过 WireGuard 可以在隧道内传输任何基于 IP 的协议(TCP、UDP、ICMP、SCTP、IPIP、GRE 等),但 WireGuard 隧道本身是通过 UDP 协议进行通信的,而国内运营商根本没有能力和精力根据 TCP 和 UDP 的不同去深度定制不同的 QoS 策略,几乎全部采取一刀切的手段:对 UDP 进行限速甚至封锁

鲁迅先生说过:羊毛出在羊身上!突破口还是在运营商身上:虽然对 UDP 不友好,但却无力深度检测 TCP 连接的真实性

这就好办了,既然你对 TCP 连接睁一只眼闭一只眼,那我将 UDP 连接伪装成 TCP 连接不就蒙混过关了。目前支持将 UDP 流量伪装成 TCP 流量的主流工具是 udp2raw[1],相信很多小伙伴对这个工具都轻车熟路了,但是很遗憾,今天的主角不是它,而是另一款比它更强大的新工具:Phantun[2]

Phantun 介绍

Phantun 整个项目完全使用 Rust 实现,性能吊打 udp2raw。它的初衷和 udp2raw 类似,都是为了实现一种简单的用户态 TCP 状态机来对 UDP 流量做伪装。主要的目的是希望能让 UDP 流量看起来像是 TCP,又不希望受到 TCP retransmission 或者 congestion control 的影响。

需要申明的是,Phantun 的目标不是为了替代 udp2raw,从一开始 Phantun 就希望设计足够的简单高效,所以 udp2raw 支持的 ICMP 隧道,加密,防止重放等等功能 Phantun 都选择不实现。

Phantun 假设 UDP 协议本身已经解决了这些问题,所以整个转发过程就是简单的明文换头加上一些必要的 TCP  状态控制信息。对于我日常使用的 WireGuard 来说,Phantun 这种设计是足够安全的,因为 WireGuard  的协议已经更好的实现了这些安全功能。

Phantun 使用 TUN 接口来收发 3 层数据包,udp2raw 使用 Raw Socket + BFP 过滤器。个人感觉基于 TUN 的实现要稍微的优雅一点,而且跨平台移植也要更容易。

Phantun 的 TCP 连接是按需创建的,只启动 Client 不会主动去连接服务器,需要第一个数据包到达了后才会按需创建。每个 UDP  流都有自己独立的 TCP 连接。这一点跟 udp2raw 很不一样,udp2raw 所有的 UDP 连接共用一个 TCP 连接。这样做的坏处就是 udp2raw 需要额外的头部信息来区分连接,更加增加了头部的开销。跟纯 UDP 比较,Phantun 每个数据包的额外头部开销是 12  byte,udp2raw 根据我的测试达到了 44 bytes 。

下面是 Phantun 与 udp2raw 的详细对比:


Phantunudp2raw
UDP over FakeTCP 混淆
UDP over ICMP 混淆
UDP over UDP 混淆
多线程
吞吐量BetterGood
三层转发模式TUN interfaceRaw sockets + BPF
隧道 MTU 开销12 bytes44 bytes
每个 UDP  流都有自己独立的 TCP 连接Client/ServerServer only
防止重放,加密
IPv6

Phantun 工作原理

Phantun 分为服务端和客户端,服务端会监听一个端口,比如 4567(通过 --local 参数指定),并将 UDP 数据包转发到 UDP 服务(这里指的就是服务端 WireGuard 的监听端口和地址,通过 --remote 参数指定)。

客户端也会监听一个端口,比如 127.0.0.1:4567(通过 --local 参数指定),并且通过 --remote 参数与服务端(比如 10.0.0.1:4567)建立连接。

客户端与服务端都会创建一个 TUN 网卡,客户端 TUN 网卡默认分配的 IPv4/IPv6 地址分别是 192.168.200.2fcc8::2,服务端 TUN 网卡默认分配的 IPv4/IPv6 地址分别是 192.168.201.2fcc9::2

客户端与服务端都需要开启 IP forwarding,并且需要创建相应的 NAT 规则。客户端在流量离开物理网卡之前,需要对 IP 192.168.200.2 进行 SNAT;服务端在流量进入网卡之前,需要将 IP DNAT 为 192.168.201.2

Phantun 配置步骤

接下来我会通过一个示例来演示如何使用 Phantun 将 WireGuard 的 UDP 流量伪装成 TCP。我们需要在服务端和客户端分别安装 phantun,可以到 release 页面[3]下载,推荐下载静态编译版本 phantun_x86_64-unknown-linux-musl.zip

服务端

假设服务端的公网 IP 地址是 121.36.134.95,WireGuard 监听端口是 51822。首先修改配置文件 /etc/wireguard/wg0.conf,在 [Interface] 中添加以下配置:

MTU = 1300
PreUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination 192.168.201.2
PreUp = RUST_LOG=info phantun_server --local 4567 --remote 127.0.0.1:51822 &> /var/log/phantun_server.log &
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination 192.168.201.2
PostDown = killall phantun_server || true

你需要将 eth0 替换为你服务端的物理网卡名。MTU 值先不管,后面再告诉大家调试方法。

PreUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination 192.168.201.2

这条 iptables 规则表示将 4567 端口的入站流量 DNAT 为 TUN 网卡的 IP 地址。

PreUp = RUST_LOG=info phantun_server --local 4567 --remote 127.0.0.1:51822 &> /var/log/phantun_server.log &

这里会启动 phantun_server,监听在 4567 端口,并将 UDP 数据包转发到 WireGuard。

服务端完整的 WireGuard 配置:

# local settings for Endpoint B
[Interface]
PrivateKey = QH1BJzIZcGo89ZTykxls4i2DKgvByUkHIBy3BES2gX8= 
Address = 10.0.0.2/32
ListenPort = 51822
MTU = 1300
PreUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination 192.168.201.2
PreUp = RUST_LOG=info phantun_server --local 4567 --remote 127.0.0.1:51822 &> /var/log/phantun_server.log &
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination 192.168.201.2
PostDown = killall phantun_server || true

# remote settings for Endpoint A
[Peer]
PublicKey = wXtD/VrRo92JHc66q4Ypmnd4JpMk7b1Sb0AcT+pJfwY= 
AllowedIPs = 10.0.0.1/32

最后重启 WireGuard 即可:

$ systemctl restart wg-quick@wg0

客户端

假设客户端的 WireGuard 监听端口是 51821。首先修改配置文件 /etc/wireguard/wg0.conf,在 [Interface] 中添加以下配置:

MTU = 1300
PreUp = iptables -t nat -A POSTROUTING -o eth0 -s 192.168.200.2 -j MASQUERADE
PreUp = RUST_LOG=info phantun_client --local 127.0.0.1:4567 --remote 121.36.134.95:4567 &> /var/log/phantun_client.log &
PostDown = iptables -t nat -D POSTROUTING -o eth0 -s 192.168.200.2 -j MASQUERADE
PostDown = killall phantun_client || true

你需要将 eth0 替换为你服务端的物理网卡名。

PreUp = iptables -t nat -A POSTROUTING -o eth0 -s 192.168.200.2 -j MASQUERADE

这条 iptables 规则表示对来自 192.168.200.2(TUN 网卡) 的出站流量进行 MASQUERADE。

PreUp = RUST_LOG=info phantun_client --local 127.0.0.1:4567 --remote 121.36.134.95:4567 &> /var/log/phantun_client.log &

这里会启动 phantun_client,监听在 4567 端口,并与服务端建立连接,将伪装的 TCP 数据包传送给服务端。

除此之外还需要修改 WireGuard peer 的 Endpoint,将其修改为 127.0.0.1:4567。

Endpoint = 127.0.0.1:4567

客户端完整的 WireGuard 配置:

# local settings for Endpoint A
[Interface]
PrivateKey = 0Pyz3cIg2gRt+KxZ0Vm1PvSIU+0FGufPIzv92jTyGWk=
Address = 10.0.0.1/32
ListenPort = 51821
MTU = 1300
PreUp = iptables -t nat -A POSTROUTING -o eth0 -s 192.168.200.2 -j MASQUERADE
PreUp = RUST_LOG=info phantun_client --local 127.0.0.1:4567 --remote 121.36.134.95:4567 &> /var/log/phantun_client.log &
PostDown = iptables -t nat -D POSTROUTING -o eth0 -s 192.168.200.2 -j MASQUERADE
PostDown = killall phantun_client || true

# remote settings for Endpoint B
[Peer]
PublicKey = m40NDb5Cqtb78b1DVwY1+kxbG2yEcRhxlrLm/DlPpz8=
Endpoint = 127.0.0.1:4567
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25

最后重启 WireGuard 即可:

$ systemctl restart wg-quick@wg0

查看 phantun_client 的日志:

$ tail -f /var/log/phantun_client.log
 INFO  client > Remote address is: 121.36.134.95:4567
 INFO  client > 1 cores available
 INFO  client > Created TUN device tun0
 INFO  client > New UDP client from 127.0.0.1:51821
 INFO  fake_tcp > Sent SYN to server
 INFO  fake_tcp > Connection to 121.36.134.95:4567 established

查看 wg0 接口:

$ wg show wg0
interface: wg0
  public key: wXtD/VrRo92JHc66q4Ypmnd4JpMk7b1Sb0AcT+pJfwY=
  private key: (hidden)
  listening port: 51821

peer: m40NDb5Cqtb78b1DVwY1+kxbG2yEcRhxlrLm/DlPpz8=
  endpoint: 127.0.0.1:4567
  allowed ips: 10.0.0.2/32
  latest handshake: 1 minute, 57 seconds ago
  transfer: 184 B received, 648 B sent
  persistent keepalive: every 25 seconds

测试连通性:

$ ping 10.0.0.2 -c 3
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=13.7 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=14.4 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=15.0 ms

--- 10.0.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2005ms
rtt min/avg/max/mdev = 13.718/14.373/15.047/0.542 ms

客户端(多服务端)

如果客户端想和多个服务端建立连接,则新增的服务端配置如下:

PreUp = RUST_LOG=info phantun_client --local 127.0.0.1:4568 --remote xxxx:4567 --tun-local=192.168.202.1 --tun-peer=192.168.202.2 &> /var/log/phantun_client.log &
PostDown = iptables -t nat -D POSTROUTING -o eth0 -s 192.168.202.2 -j MASQUERADE

本地监听端口需要选择一个与之前不同的端口,同理,TUN 网卡的地址也需要修改。最终的配置如下:

# local settings for Endpoint A
[Interface]
PrivateKey = 0Pyz3cIg2gRt+KxZ0Vm1PvSIU+0FGufPIzv92jTyGWk=
Address = 10.0.0.1/32
ListenPort = 51821
MTU = 1300
PreUp = iptables -t nat -A POSTROUTING -o eth0 -s 192.168.200.2 -j MASQUERADE
PreUp = RUST_LOG=info phantun_client --local 127.0.0.1:4567 --remote 121.36.134.95:4567 &> /var/log/phantun_client.log &
PreUp = RUST_LOG=info phantun_client --local 127.0.0.1:4568 --remote xxxx:4567 --tun-local=192.168.202.1 --tun-peer=192.168.202.2 &> /var/log/phantun_client.log &
PostDown = iptables -t nat -D POSTROUTING -o eth0 -s 192.168.200.2 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -s 192.168.202.2 -j MASQUERADE
PostDown = killall phantun_client || true

# remote settings for Endpoint B
[Peer]
PublicKey = m40NDb5Cqtb78b1DVwY1+kxbG2yEcRhxlrLm/DlPpz8=
Endpoint = 127.0.0.1:4567
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25

MTU 调优

如果你使用 ping 或者 dig 等工具(小数据包)测试 WireGuard 隧道能够正常工作,但浏览器或者远程桌面(大数据包)却无法正常访问,很有可能是 MTU 的问题,你需要将 MTU 的值调小一点。

Phantun 官方建议将 MTU 的值设为 1428(假设物理网卡的 MTU 是 1500),但经我测试是有问题的。建议直接将 MTU 设置为最低值 1280,然后渐渐增加,直到无法正常工作为止,此时你的 MTU 就是最佳值。

你学废了吗?

参考资料

  • Phantun - Rust 写的轻量级 UDP -> TCP 混淆器[4]

  • Phantun GitHub Repo[5]

引用链接

[1]

udp2raw: https://github.com/wangyu-/udp2raw-tunnel

[2]

Phantun: https://github.com/dndx/phantun

[3]

release 页面: https://github.com/dndx/phantun/releases

[4]

Phantun - Rust 写的轻量级 UDP -> TCP 混淆器: https://www.v2ex.com/t/802949

[5]

Phantun GitHub Repo: https://github.com/dndx/phantun


你可能还喜欢

点击下方图片即可阅读

使用 WireGuard 组建非对称路由以降低延迟

2022-09-04

彻底理解 WireGuard 的路由策略

2022-08-31

芜湖,Tailscale 开源版本让你的 WireGuard 直接起飞~

2022-03-22

自建 DERP 中继服务器,从此 Tailscale 畅通无阻

2022-03-27

云原生是一种信仰 🤘

关注公众号

后台回复◉k8s◉获取史上最方便快捷的 Kubernetes 高可用部署工具,只需一条命令,连 ssh 都不需要!

点击 "阅读原文" 获取更好的阅读体验!

发现朋友圈变“安静”了吗?

有关突破运营商 QoS 封锁,WireGuard 真有“一套”!的更多相关文章

  1. ruby - 如果一个空字符串为真有什么好处? - 2

    很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visitthehelpcenter.关闭9年前。在Ruby中,空字符串""对于条件判断是真值。我看到很多人都在为这个事实而苦苦挣扎。RubyonRails的blank?是克服这个问题的一种尝试。另一方面,当这种规范变得方便时,并不是立即显而易见的。是否有任何用例使这一事实变得方便,或者如果不是这样,是否会存在理论上的问题?

  2. 如何给《羊了个羊》配置一套智能客服系统? - 2

    几乎是一夜之间,微信小游戏《羊了个羊》火了。​这个依靠寻找相同元素消除方块的小游戏,凭借其“变态级别”的游戏难度成功破圈,闯入了无数人的休闲时间,并数次冲上热搜。当然,很多人在微博、朋友圈对它的评价主要是:连第一关都过不了!▲ 《羊了个羊》游戏界面对于这样一个规模不大的小游戏开发团队来说,收获超高人气的同时,头疼的事情发生了:▲《羊了个羊》官方微博通告是的,突然涌入的大量玩家致使游戏服务器异常,而且问题出现了不止一次。这也导致不少玩家在微博上疯狂吐槽:好不容易被人安利了这款游戏,结果发现根本进不去!也有一些人在微博等渠道向开发团队提出改进建议,但又不确定能否被官方看到。其实,不仅是《羊了个羊》

  3. NFT的突破性及其实质 - 2

    人们之所以热衷买一个NFT,因为第一次在互联网上,人们踏实地感觉到自己拥有了一个数字作品的拥有权(所有权,ownership)。而且这个权利,是全网公开透明的,是主人亲自掌握的,是谁也剥夺不了的。这个能力是区块链技术带来的。背后是区块链中无处不在的密码学技术。只有掌握了私钥的人,才能转移、转让他的NFT,其他任何人都无法做到这点。为了明白这点,我们先仔细看看,对于一个NFT,区块链上到底存放了什么。从目前绝大多数NFT的实现上看,区块链上只是记录了一个作品拥有者的地址、作品的编号、以及这个作品的链接。作品本身并不在区块链上,除非是那种很简单的作品(因为作品比较大,存在区块链上很贵的)。简单地说

  4. ruby - 如何从 ruby block 中突破? - 2

    这是Bar#do_things:classBardefdo_thingsFoo.some_method(x)do|x|y=x.do_somethingreturny_is_badify.bad?#howdoitellittostopandreturndo_things?y.do_something_elseendkeep_doing_more_thingsendend这里是Foo#some_method:classFoodefself.some_method(targets,&block)targets.eachdo|target|beginr=yield(target)rescuef

  5. javascript - 如何通过索引号突破 .each() - 2

    我想将列表中的每三个项目分开并为那个child添加一个类(class);1234567有什么想法吗? 最佳答案 你应该使用第nth-child伪选择器$("ulli:nth-child(3n)").addClass("break-here"); 关于javascript-如何通过索引号突破.each(),我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/10154647/

  6. 低成本、大容量、高交互…Polkadot 引领 GameFi 实现新突破 - 2

    有一天,你可以边打游戏边赚钱,实现Playtoearn的梦想。这话或许听来有些白日梦,但随着区块链行业的高速发展,DeFi、NFT的兴起,传统游戏也在区块链行业的大背景下实现新一步的更迭。PlayToearn的理想正在加速落地,GameFi赛道的火爆让越来越多的技术开发人员投身于此,也让越来越多的用户通过游戏进一步盘活资产。然而目前,GameFi并不拥有相对完备的成熟运作体系,面临诸多亟待解决的难题,那么Polkadot是否有着天然的解困优势,为Web3时代的GameFi注入活力呢?今天就让我们一起来探讨这个话题。何谓GameFi?GameFi,即Game+DeFi,通过在游戏中注入DeFi的

  7. javascript - 使用 ||运营商通知 - 2

    Javascript代码:vara=(b)?b:40;它正在运行,只是NetBeans说:“使用||运算符(列[?在哪里])”。我没有找到任何解释。这是什么?谢谢! 最佳答案 如果您只是测试b的真实性,那么您可以这样做:vara=b||40;…更短而且(可以说)更明显。在JavaScript中,||是一个短路运算符。如果为真,则返回左侧,否则返回右侧。(即,除非输入是bool值,否则它不会返回bool值)。如果您想查看b是否实际定义,那么您最好使用:vara=(typeofb!=="undefined")?b:40;

  8. 基于区块链技术的“三体五信”算网运营体系研究 - 2

    摘要【目的】本文针对东数西算背景下的算网运营体系建设方案进行探索,介绍了算网运营体系包含的“算力一体供给、算力一站式服务、算力可信交易”三个核心体系的能力建设,提出了基于区块链的“身份、协议、订单、账单、佣金”五信交易模型。 【方法】本文从算力提供、算力交易、算力使用、交易安全等方面出发,阐述了多方协同供给算力机制、算网产品体系和售卖模式、算网业务使用方式和算网可信交易支撑方式。 【结果】基于区块链技术的“三体五信”算网运营体系,具备多方算力汇聚、算网融合、共享交易的能力,具备交易主体身份可信、交易主体协议可信、交易过程订单可信、交易分成佣金可信、交易结果账单可信的可靠保障。 【局限】算力交易

  9. javascript - || 是什么意思运营商呢? - 2

    Attacklab.wmd_env.buttons=Attacklab.wmd_env.buttons||_4;||是什么意思在这种情况下怎么办?将_4添加到Attacklab.wmd_env.buttons数组? 最佳答案 JavaScript中的||运算符如果该值的计算结果不为false,则返回左侧的值,否则返回右侧的值。来自Mozilla'sCoreJavaScript1.5Reference:expr1||expr2Returnsexpr1ifitcanbeconvertedtotrue;otherwise,returnse

  10. javascript - 使用 errorCallback 突破 Promise "then"链 - 2

    --编辑--我最近遇到了一件关于promises的奇怪事情,但我想这可能是因为它违反了promises的哲学。考虑以下代码://AssumingAuthisjustasimplelibdoinghttprequestswithpromisesAuth.signup().then(succCall,errCall).then(loginSucc,loginErr)//MycallbacksherefunctionsuccCall(){//OK,sendsecondpromiseconsole.log('succCall');returnAuth.login();}functionerrC

随机推荐