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 的详细对比:
Phantun | udp2raw | |
---|---|---|
UDP over FakeTCP 混淆 | ✅ | ✅ |
UDP over ICMP 混淆 | ❌ | ✅ |
UDP over UDP 混淆 | ❌ | ✅ |
多线程 | ✅ | ❌ |
吞吐量 | Better | Good |
三层转发模式 | TUN interface | Raw sockets + BPF |
隧道 MTU 开销 | 12 bytes | 44 bytes |
每个 UDP 流都有自己独立的 TCP 连接 | Client/Server | Server 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.2
和 fcc8::2
,服务端 TUN 网卡默认分配的 IPv4/IPv6 地址分别是 192.168.201.2
和 fcc9::2
。
客户端与服务端都需要开启 IP forwarding,并且需要创建相应的 NAT 规则。客户端在流量离开物理网卡之前,需要对 IP 192.168.200.2
进行 SNAT;服务端在流量进入网卡之前,需要将 IP DNAT 为 192.168.201.2
。
Phantun 配置步骤
接下来会根据官方文档指示,通过一个示例来演示如何使用 Phantun 将 WireGuard 的 UDP 流量伪装成 TCP。我们需要在服务端和客户端分别安装 phantun,可以到 release 页面下载,推荐下载静态编译版本 phantun_x86_64-unknown-linux-musl.zip
。
服务端
假设服务端的公网 IP 地址是 1.2.3.4
,WireGuard 监听端口是 1234
。首先修改配置文件/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:1234 &> /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 = 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 = gEmV3JiRXb/z8O+oGTxp5J0tk59wAiTp9Af3RH+y8E0=
Address = 10.0.0.2/32
ListenPort = 1234
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:1234 &> /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 = 24E/CM0IpjZ2AeqXTEWPLZQEz4q19Cjkxp2/DSLnlEU=
AllowedIPs = 10.0.0.1/32
最后重启 WireGuard 即可:
systemctl restart wg-quick@wg0
客户端
首先修改配置文件 /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:1234 --remote 1.2.3.4: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 = RUST_LOG=info phantun_client --local 127.0.0.1:1234 --remote 1.2.3.4:4567 &> /var/log/phantun_client.log &
这里会启动 phantun_client,监听在 1234
端口,并与服务端建立连接,将伪装的 TCP 数据包传送给服务端。
除此之外还需要修改 WireGuard peer 的 Endpoint,将其修改为 127.0.0.1:1234。
Endpoint = 127.0.0.1:1234
客户端完整的 WireGuard 配置:
# local settings for Endpoint A
[Interface]
PrivateKey = OOMquWJPgHfczh1kmJ6pHQa6VWQCAL1GG7jTcunT7mo=
Address = 10.0.0.1/32
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:1234 --remote 1.2.3.4: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 = NjeeE6QM7+yFJzOA4a1E6t8KWIcI4BrUYjzEr1MUUxY=
Endpoint = 127.0.0.1:1234
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25
最后重启 WireGuard 即可:
systemctl restart wg-quick@wg0
查看 wg0 接口:
wg show wg0
interface: wg0
public key: 24E/CM0IpjZ2AeqXTEWPLZQEz4q19Cjkxp2/DSLnlEU=
private key: (hidden)
listening port: 50090
peer: NjeeE6QM7+yFJzOA4a1E6t8KWIcI4BrUYjzEr1MUUxY=
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
MTU 调优
如果你使用 ping 或者 dig 等工具(小数据包)测试 WireGuard 隧道能够正常工作,但浏览器或者远程桌面(大数据包)却无法正常访问,很有可能是 MTU 的问题,你需要将 MTU 的值调小一点。
Phantun 官方建议将 MTU 的值设为 1428
(假设物理网卡的 MTU 是 1500),但经我测试是有问题的。建议直接将 MTU 设置为最低值 1280
,然后渐渐增加,直到无法正常工作为止,此时你的 MTU 就是最佳值。
后记
在使用phantun的时候,我试过自定义地址,分别使用参数--tun-local
与--tun-peer
来指定TUN 网卡的地址,指定客户端地址如下
RUST_LOG=info phantun_server --local 4567 --remote 127.0.0.1:1234 --tun-local 192.168.254.1 --tun-peer 192.168.254.2
此时查看客户端TUN 网卡的地址为
# ip addr show tun0
81: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 192.168.254.1 peer 192.168.254.2/32 scope global tun0
valid_lft forever preferred_lft forever
此时本地端地址为192.168.254.1
,但发包出去的源地址是192.168.254.2
,用tcpdump
对tun0
端口抓包可看见如下
# tcpdump -nn -i tun0
01:51:27.120882 IP 1.2.3.4.4567 > 192.168.254.2.33134: Flags [.], seq 150977:151073, ack 1447504, win 65535, length 96
01:51:27.120889 IP 1.2.3.4.4567 > 192.168.254.2.33134: Flags [.], seq 151073:151169, ack 1447504, win 65535, length 96
01:51:27.120893 IP 1.2.3.4.4567 > 192.168.254.2.33134: Flags [.], seq 151265:151361, ack 1447504, win 65535, length 96
01:51:27.120893 IP 1.2.3.4.4567 > 192.168.254.2.33134: Flags [.], seq 151361:151457, ack 1447504, win 65535, length 96
01:51:27.120895 IP 1.2.3.4.4567 > 192.168.254.2.33134: Flags [.], seq 151169:151265, ack 1447504, win 65535, length 96
01:51:27.120896 IP 1.2.3.4.4567 > 192.168.254.2.33134: Flags [.], seq 151457:151553, ack 1447504, win 65535, length 96
01:51:27.121049 IP 192.168.254.2.33134 > 1.2.3.4.4567: Flags [.], seq 1456320:1457344, ack 151553, win 65535, length 1024
01:51:27.121149 IP 192.168.254.2.33134 > 1.2.3.4.4567: Flags [.], seq 1457344:1457584, ack 151553, win 65535, length 240
01:51:27.121227 IP 192.168.254.2.33134 > 1.2.3.4.4567: Flags [.], seq 1457584:1457824, ack 151553, win 65535, length 240
01:51:27.121321 IP 192.168.254.2.33134 > 1.2.3.4.4567: Flags [.], seq 1457824:1458064, ack 151553, win 65535, length 240
01:51:27.121398 IP 192.168.254.2.33134 > 1.2.3.4.4567: Flags [.], seq 1458064:1458304, ack 151553, win 65535, length 240
01:51:27.121493 IP 192.168.254.2.33134 > 1.2.3.4.4567: Flags [.], seq 1458304:1458544, ack 151553, win 65535, length 240
01:51:27.121570 IP 192.168.254.2.33134 > 1.2.3.4.4567: Flags [.], seq 1458544:1458784, ack 151553, win 65535, length 240
01:51:27.121662 IP 192.168.254.2.33134 > 1.2.3.4.4567: Flags [.], seq 1458784:1459024, ack 151553, win 65535, length 240
可以确切看见我们并非以端口的192.168.254.1
去发包的,而是以192.168.254.2
发包给服务端,但为什么不以tun0
的ip地址为源发包呢?
再看回上面的抓包,发现服务端回来的包,是指向192.168.254.2
的,由此可以猜测,此tun0
端口的目的是为了让linux插入一条路由表如下
ip route show 192.168.254.2
192.168.254.2 dev tun0 proto kernel scope link src 192.168.254.1
当有上面的这条路由的时候,服务端发回来的包,到达客户端的时候,经系统查看目的是192.168.254.2
,所以此时系统发现目的并非自己(192.168.254.1
),是192.168.254.2
,系统此时会查路由表然后将包转发出去,查路由表可见有一条指向192.168.254.2
的路由,经tun0
发送出去,所以流量会经过tun0
接口,此时phantun
程序会接收到这个包并进行处理。
由此可见两边端口配置任何ip地址都是没有问题的,主要要搞清楚自己在配置了自定义端口地址的时候,iptables需要怎么作出调整。
转载请注明来源