裸金属物理机内网穿透终极形态:内核态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都未必跑得起来。这不是技巧,是底层特权。