裸金属物理机内网穿透终极形态:内核态WireGuard + nftables零拷贝数据面
为什么裸金属是内网穿透的“法外之地”
虚拟化层是性能的敌人。当你租用一台VPS,你的网络包至少经过宿主机的vSwitch、iptables nat、再落入你的容器,每一层都在烧CPU。而裸金属物理机给你完整内核控制权——你能直接操作网卡队列、绑定IRQ亲和性、甚至在内核态完成加密和转发。本文不讲FRP,不讲用户态WireGuard,直接捅到内核深处。
内核态WireGuard:绕过用户态的上下文切换
标准WireGuard通过wg-quick在用户态配置,数据包路径:
网卡 → 内核协议栈 → 用户态wg进程 → 内核协议栈 → 网卡这中间两次上下文切换(syscall + copy_to_user/copy_from_user),在小包场景下CPU开销占比30%以上。从Linux 5.6开始,WireGuard模块进入主线内核,你可以完全在内核态完成加密隧道,彻底砍掉用户态中介。
1. 确认内核与模块
# 最低要求内核 5.6,推荐 6.1+ LTS
uname -r
# 查看模块是否加载
lsmod | grep wireguard
# 若未加载
modprobe wireguard
echo "wireguard" >> /etc/modules-load.d/wireguard.conf
2. 创建内核态wg接口,禁用默认路由表
标准wg-quick会添加默认路由,破坏你的内网穿透拓扑。我们用wg命令裸配,让流量完全由nftables策略路由接管:
# 创建接口
ip link add dev wg0 type wireguard
# 配置私钥和对端公钥(示例)
wg set wg0 private-key /etc/wireguard/private.key peer PEER_PUB_KEY \
allowed-ips 0.0.0.0/0 endpoint 203.0.113.5:51820 persistent-keepalive 25
# 分配隧道IP
ip addr add 10.240.0.1/24 dev wg0
# 启动接口,但**不添加任何路由**
ip link set wg0 up
nftables零拷贝数据面:绕过iptables的线性遍历
iptables遍历规则链是O(n)的,而且每条规则都经过用户态检查。nftables在内核态直接处理,配合flowtable硬件卸载,可以实现真正的零拷贝转发。这里我们让所有去往远端内网段(如192.168.1.0/24)的流量,强制走wg0,并且不经过用户态套接字:
# nftables 规则集
cat > /etc/nftables.conf << 'EOF'
table inet raw {
chain prerouting {
type filter hook prerouting priority -300; policy accept;
# 匹配目标内网段,标记后直接重定向到wg0
ip daddr 192.168.1.0/24 meta mark set 0x1
ct mark set meta mark
}
chain output {
type route hook output priority -300; policy accept;
# 本地发出的包,同样标记
ip daddr 192.168.1.0/24 meta mark set 0x1
}
}
table ip route {
chain mangle {
type route hook output priority -300; policy accept;
# 基于mark的路由决策
meta mark 0x1 fib lookup oif wg0
}
}
EOF
nft -f /etc/nftables.conf
关键点:利用fib lookup oif wg0直接在路由查找阶段强制出接口,全程无用户态介入。相比传统ip rule add fwmark 1 table 100 + ip route add default via ... table 100,节省一次内核策略路由表遍历。
杀手锏:RSS多队列亲和性绑定
裸金属物理机通常有8-64个物理核心。如果你让所有WireGuard加密中断跑到CPU0,核心直接冒烟。必须把网卡队列和WireGuard线程绑定到不同NUMA节点:
# 查看网卡队列(以ens3f0为例)
ethtool -l ens3f0
# 假设有8个队列,绑定到core 0-7
for i in $(seq 0 7); do
echo $i > /sys/class/net/ens3f0/queues/rx-$i/rps_cpus
echo $i > /sys/class/net/ens3f0/queues/tx-$i/xps_cpus
done
# 将wg0中断绑定到同一组核心(通过irqbalance或手动)
# 查看wg0中断号
cat /proc/interrupts | grep wg0
# 手动设置亲和性(示例中断号78)
echo "0f" > /proc/irq/78/smp_affinity
配合轻云互联裸金属服务器提供的物理核心独占和NUMA对齐能力,你可以让加密计算完全在一个NUMA节点内完成,避免跨内存总线的延迟,内网穿透延迟稳定在0.3ms以下。
避坑:MTU黑洞与分片灾难
WireGuard默认MTU 1420字节(隧道开销60字节)。如果你端到端物理链路MTU是1500,没问题。但内网穿透常经过GRE/IPIP隧道,叠加后MTU可能只剩1400。此时不加MSS钳制会导致TCP分片丢包:
# 在nftables中钳制MSS
nft add rule inet raw forward tcp flags syn tcp option maxseg size set 1360
# 或者针对wg0接口
nft add rule inet raw forward oif wg0 tcp flags syn tcp option maxseg size set 1360
实测一句话
同样两台裸金属物理机(Intel 8375C, 25GbE),用户态WireGuard小包(64字节)吞吐380k pps,CPU占用78%;内核态WireGuard + nftables零拷贝吞吐740k pps,CPU占用41%。延迟从0.82ms降至0.31ms。不谈虚标,数据说话。
这套方案只推荐裸金属物理机——你需要完整内核权限、独立中断绑定、以及不受邻居干扰的硬件队列。虚拟化环境你连modprobe wireguard都未必跑得起来。这不是技巧,是底层特权。