上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器的 Ubuntu 环境中优化 WireGuard 内核模块,确保 CN2 专线的低延迟性能?

发布人:Minchunlin 发布时间:2025-09-02 10:25 阅读量:1558


香港葵涌机房的凌晨2点10分,北京业务端报来告警:「跨境 RTT 抖动大于 8ms,P95 延迟逼近 60ms」。我把外套搭在机柜门上,接上串口,打算在不动业务出口策略的前提下,彻底把这台承载 CN2 专线的 WireGuard 网关调到我满意的延迟曲线。下面就是那一夜我做过的每一步、踩过的坑与最后的结果。

一、目标、边界与拓扑

目标(SLO)

  • 广深链路(HK ↔ 广州/深圳)RTT:≤ 25ms,抖动(jitter P95)≤ 2.5ms
  • 沪杭链路(HK ↔ 上海/杭州)RTT:≤ 45ms,抖动(P95)≤ 3.5ms
  • 端到端TCP 吞吐(单流)≥ 1.2 Gbps(在 10GbE 接入下),小包 PPS 不丢(64B 抽检)。

边界条件

  • 不改动上游运营商(CN2 GIA)侧策略与 QoS,仅在我方 HK 出口和两端 Ubuntu 主机优化。
  • 协议固定:WireGuard 内核态(非 wireguard-go),UDP/51820。
  • 加密套件沿用 WireGuard 默认(ChaCha20-Poly1305),不自定义。

简化拓扑(ASCII)

[HK Core Switch 10G]──[HK WG-GW (Ubuntu, wg0)]──[CN2 GIA]──[CN POP]──[CN WG-GW]
                               |                                    |
                         (内网东西向)                           (内网南北向)

二、环境与硬件参数

服务器与系统

角色 机房 CPU 内核 OS NIC 驱动 固件
HK WG-GW 香港葵涌 Intel Xeon Silver 4310(12C/24T,2.1GHz,AVX2) 6.8.0(Ubuntu HWE) Ubuntu 22.04.5 LTS Intel X710 10GbE(双口,直连核心) i40e 9.20
CN WG-GW 上海外高桥 AMD EPYC 7313P(16C/32T) 6.5.0 Ubuntu 22.04.4 LTS Mellanox ConnectX-4 Lx 25GbE mlx5 16.35

选 HWE(硬件使能)内核是因为 WireGuard 已自 5.6 入主内核,6.x 在 UDP/GRO/TSO 与 fq 队列上更稳;同时 AVX2 对 ChaCha20 有明显收益。

链路与初始基线(未优化)

指标 广州(CN2) 上海(CN2) 备注
ping RTT (P50/P95) 22 / 38 ms 36 / 58 ms 抖动偏大
iperf3 TCP 单流 0.85 Gbps 0.72 Gbps fq 及拥塞控制未调
iperf3 UDP 1472B 无限速丢包率 2.3% 3.1% UDP buffer 偏小
mtr 丢包(中段) 0~1% 0~1% 运营商侧正常
内核软中断占比 ksoftirqd/0 偶发 60% - IRQ 未绑核

三、优化步骤(逐条复现)

以下操作均在 HK WG-GW 上执行;对端 CN 同步做对应设置。每一步都可独立验证,建议按顺序进行,遇错可回滚。

1)确认使用内核态 WireGuard并核对加速指令集

uname -r
modinfo wireguard | egrep 'version|srcversion'
wg --version
grep -m1 -o 'avx2' /proc/cpuinfo || echo "No AVX2"
lsmod | grep -E 'wireguard|udp_tunnel'

若系统仍安装了 wireguard-dkms 或 wireguard-go,请移除(6.x 内核不需要 DKMS):

sudo apt purge wireguard-dkms wireguard-go -y
sudo apt install wireguard-tools -y

坑 1: 曾见 DKMS 模块与内核内置并存,导致随机加载 DKMS 版本,引发性能不稳定。以 modinfo wireguard 的 filename 路径判断是否为内核内置模块(一般位于 /lib/modules/$(uname -r)/kernel/drivers/net/wireguard/wireguard.ko)。

2)NIC/队列与中断亲和(减少抖动的关键)

查看队列与 IRQ:

ethtool -l eno1
grep -H . /proc/irq/*/affinity_hint | grep i40e   # 粗定位 NIC 相关 IRQ
cat /proc/interrupts | egrep 'i40e|mlx5|eno1'

调整队列与环形缓冲(RX/TX Ring):

sudo ethtool -G eno1 rx 4096 tx 4096

绑核脚本(确保与 NUMA 拓扑一致,避免与 wg0 同核抢占):

# /usr/local/sbin/set-irq-affinity.sh
#/bin/bash
nic=eno1
cores=(2 3 4 5 6 7 8 9)  # 预留 0/1 给系统与软中断回退
i=0
for irq in $(grep $nic -l /proc/irq/*/net/*/ifindex | sed 's#/net/.##;s#/ifindex##'); do
  printf -v mask "%x" $((1<<${cores[$i]}))
  echo $mask > /proc/irq/$(basename $irq)/smp_affinity
  ((i=(i+1)%${#cores[@]}))
done

sudo bash /usr/local/sbin/set-irq-affinity.sh

禁用 irqbalance(或固定其策略):

sudo systemctl stop irqbalance
sudo systemctl disable irqbalance

坑 2: irqbalance 在低流量时帮不上忙,高峰时反而频繁迁移导致 cache miss 变多,jitter 飙升。固定后 P95 抖动显著下降。

3)网卡 Offload 与 GRO/TSO 策略

小包低时延优先:关闭 LRO、TSO,保留 GRO(配合 fq 更稳)。

sudo ethtool -K eno1 gro on gso off tso off lro off rxhash on
sudo ethtool -K eno1 tx-udp_tnl-segmentation off  # 某些驱动在隧道下会异常

坑 3: 全关 GRO/TSO 会拉低 TCP 单流吞吐并增加 CPU。这里选择 GRO on、TSO off 是延迟/吞吐的折中,在我们的 CN2 场景更合适。

4)队列规则(qdisc)与 TCP 拥塞控制(影响隧道内 TCP)

# 让默认 qdisc 使用 fq(为 UDP pacing 与 TCP 队列提供更好的公平性)
echo 'net.core.default_qdisc=fq' | sudo tee /etc/sysctl.d/10-fq.conf
# 对端业务 TCP 采用 BBR(隧道内 TCP 生效)
echo 'net.ipv4.tcp_congestion_control=bbr' | sudo tee /etc/sysctl.d/10-bbr.conf
sudo sysctl --system

WireGuard 外层是 UDP,不受 TCP 拥塞算法直接约束,但隧道内跑的 TCP 会受益于 fq + bbr 的队列与拥塞控制。

5)UDP 缓冲、后备队列与小包处理

cat <<'EOF' | sudo tee /etc/sysctl.d/60-wireguard-udp.conf
# 增大 UDP 读写缓冲与内存水线
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.udp_rmem_min = 131072
net.ipv4.udp_wmem_min = 131072
net.ipv4.udp_mem = 262144 524288 1048576

# 提升网卡入队 backlog
net.core.netdev_max_backlog = 250000

# 选路与反射
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2

# 提升 conntrack 表(如有 NAT)
net.netfilter.nf_conntrack_max = 1048576
EOF
sudo sysctl --system

坑 4: 只有调大 rmem_max/wmem_max 还不够,udp_mem 三元组不扩大时,UDP 高 PPS 仍会被系统层面限住。

6)CPU 频率与时钟

sudo apt install linux-tools-common linux-tools-generic -y
sudo cpupower frequency-set -g performance
sudo timedatectl set-ntp true   # 或安装 chrony

说明: WireGuard 对时间不敏感到「必须 PTP」,但握手超时与重传对时钟漂移比较敏感。务必保持 NTP 正常。

7)MTU/PMTU 与 TCP MSS Clamping(跨境场景的常见黑洞)

探测 PMTU:

tracepath -n <CN peer public ip>

经验值:在 CN2 + WireGuard 场景,外层 UDP 1500 的链路上,内层 MTU 1380~1420 比较稳。我最终使用 1380(保守、抖动小)。

配置 wg0 txqueuelen 与 MTU:

sudo ip link set dev wg0 txqueuelen 3000
sudo ip link set dev wg0 mtu 1380

MSS 自动钳制:

# nftables(推荐)
sudo nft add table inet mangle
sudo nft 'add chain inet mangle postrouting { type filter hook postrouting priority mangle; }'
sudo nft 'add rule inet mangle postrouting oif "wg0" tcp flags syn tcp option maxseg size set clamp-to-pmtu'

# iptables(如仍使用)
sudo iptables -t mangle -A POSTROUTING -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

坑 5: 没做 MSS 钳制时,偶发某些 CDN 目的段出现「连接建立后首包就卡死」——典型 MTU 黑洞表现。

8)WireGuard 连接配置(wg-quick 样例)

/etc/wireguard/wg0.conf(HK 侧)

[Interface]
Address = 10.66.0.1/30
ListenPort = 51820
PrivateKey = <HK_PRIVATE_KEY>
# 建议显式指定 MTU,亦可在 PostUp 里设
MTU = 1380

# 仅将需要的网段/流量走 CN2(策略路由见下)
Table = off

# 观测/调试友好
PostUp = ip link set dev wg0 txqueuelen 3000
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = nft add rule inet filter forward iifname "wg0" accept
PostDown = nft delete rule inet filter forward handle <fill-once>

[Peer]
PublicKey = <CN_PUBLIC_KEY>
Endpoint = <CN_PUBLIC_IP>:51820
AllowedIPs = 10.66.0.2/32, 10.100.0.0/16, 10.200.0.0/16
PersistentKeepalive = 15

PersistentKeepalive=15 在跨境场景能更快恢复 NAT 映射(即使公网是固定,也有上游中间盒),避免闲时首包慢。

9)策略路由(只把「要低延迟」的流量压到 CN2)

步骤: 使用 fwmark + ip rule + 自定义路由表。

# 新表
echo '100 cn2' | sudo tee -a /etc/iproute2/rt_tables

# 标记「去中国目的 AS/前缀」的流量(示例:业务出口网段 172.16.0.0/12)
sudo nft add table inet policy
sudo nft 'add chain inet policy prerouting { type filter hook prerouting priority mangle; }'
sudo nft 'add rule inet policy prerouting ip saddr 172.16.0.0/12 ip daddr { 101.226.0.0/15, 36.96.0.0/12 } meta mark set 0x6c2'

# 基于 mark 的路由
sudo ip rule add fwmark 0x6c2 table cn2
sudo ip route add default dev wg0 table cn2

生产中我会把中国大陆常见目的前缀表(可来自业务画像/NetFlow 分析)动态同步到 nft set 里,避免全量 0.0.0.0/0 都走 CN2。

10)外层 UDP 优先级(可选,是否生效取决于运营商)

# 给 WireGuard 外层 UDP 打 EF(46) DSCP 标记
sudo nft add table inet qos
sudo nft 'add chain inet qos output { type filter hook output priority mangle; }'
sudo nft 'add rule inet qos output udp sport 51820 ip dscp set cs5'  # 或 46(EF)

说明: 实测部分 CN2 段会保留 DSCP,高峰期 jitter 更平滑;但不保证运营商全网尊重此标记,不作为硬性指标。

11)可观测性与压测方法

实时观测:

  • wg show 监控 transfer, latest handshake 与 persistent keepalive
  • ss -u -a | grep 51820 观测 UDP 队列
  • Prometheus:node_exporter(软中断、CPU)、blackbox_exporter(ICMP/TCP 端口探测隧道内目标)
  • mtr -u -c 200 <CN peer> 与 mtr -c 200(ICMP)对比

压测:

# 隧道内 TCP 单流/多流
iperf3 -c 10.66.0.2 -P 1 -t 60
iperf3 -c 10.66.0.2 -P 4 -t 60

# 隧道内 UDP:接收端
iperf3 -s -p 5201
# 发送端(HK→CN,注意 -b 设为 0 表示尽力而为,实际会受 qdisc pacing)
iperf3 -c 10.66.0.2 -u -b 0 -l 1400 -t 60

四、完整 sysctl(便于一次性落地)

/etc/sysctl.d/99-wg-cn2.conf

net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.udp_rmem_min = 131072
net.ipv4.udp_wmem_min = 131072
net.ipv4.udp_mem = 262144 524288 1048576

net.core.netdev_max_backlog = 250000
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2

应用:

sudo sysctl --system

五、我踩过且你大概率也会踩的坑

  • DKMS 与内核模块并存:随机加载旧版,性能波动。→ 清理 DKMS,保留内核内置。
  • irqbalance 抢核:流量上来后 jitter 上升。→ 固定 IRQ 亲和,保留少数核给系统。
  • TSO/GSO 误配置:全关导致吞吐雪崩;全开导致隧道内小包延迟抖动。→ GRO on、TSO off。
  • MTU 黑洞:不做 MSS 钳制,个别段落网跳丢第一个大包。→ clamp-to-pmtu。
  • Docker/ufw 篡改规则:容器与 UFW 叠加导致策略路由失效。→ nftables 接管,审计规则顺序。
  • 时钟漂移:握手超时重试增加尾延迟。→ 开启 chrony/ntp,注意宿主机与容器一致。
  • TX Ring 太小:高 PPS 下 egress 丢包。→ ethtool -G 合理放大,配合 netdev_max_backlog。
  • 忘了 txqueuelen:wg0 默认队列太短,高峰抖动明显。→ ip link set wg0 txqueuelen 3000。

六、优化后的结果(与基线对比)

指标 基线(HK↔GZ) 优化后(HK↔GZ) 基线(HK↔SH) 优化后(HK↔SH)
ping RTT P50/P95 22 / 38 ms 19 / 24 ms 36 / 58 ms 34 / 42 ms
jitter(P95) 6.8ms 2.1ms 8.5ms 3.2ms
TCP 单流吞吐 0.85 Gbps 1.35 Gbps 0.72 Gbps 1.22 Gbps
UDP 1472B 丢包 2.3% 0.3% 3.1% 0.6%
ksoftirqd 峰值 60% <25% 54% <28%

效果的 80% 来自 IRQ 亲和 + qdisc/fq + MTU/MSS 三板斧;其余来自 UDP 缓冲与电源管理的细抠。

七、回滚与验证清单(变更手册化)

所有调整均通过独立配置文件落地(/etc/sysctl.d/*.conf、nftables 脚本、wg0.conf),随时可回滚:

  • sysctl --system 恢复前先备份;
  • nft 支持 list ruleset 导出与 flush ruleset 回退;
  • wg-quick down wg0 && up wg0 可原地重启隧道。
  • 验证顺序:链路连通 → MTU/PMTU → jitter → 吞吐 → PPS。
  • 观测:Prometheus 看 软中断、CPU、UDP 接收丢包 三条曲线是否同步改善。

八、FAQ(我当晚确实被问过)

能不能把 MTU 设到 1420?
可以,但在我们的路由段里偶发路径会引起重片或黑洞。我更倾向 1380 保守值,抖动曲线更平。

BBR 对 WireGuard 有用吗?
对隧道内的 TCP 有用;外层 UDP 不受影响,但配合 fq,内层 TCP 的发送更平滑。

DSCP 在 CN2 会被尊重吗?
部分段会,不能依赖。作为锦上添花,不作为 SLO 保障项。

九、结尾:灯灭之前

03:47,我把最后一条 nft list ruleset 保存到变更记录里,黑盒上的绿色 Link 灯仍然稳稳闪烁。Grafana 的延迟面板从锯齿变成了顺滑的波浪线,报警频道安静下来。
我把外套穿上、关掉 KVM,往电梯口走——手机里还开着 wg show 的状态页。对我来说,低延迟不是一句口号,它是 内核、网卡、队列、MTU、策略路由 共同协作的结果。每一台香港的出口网关,只要肯在这些细节上较真,就能把 CN2 这条路走得又稳又快。

附:一次性自动化脚本(精简版,生产请按需拆分)

#!/usr/bin/env bash
set -euo pipefail

# 1. 基础包
apt update && apt install -y wireguard-tools nftables iperf3 linux-tools-common linux-tools-generic

# 2. 关闭 dkms/go 版 WG
apt purge -y wireguard-dkms wireguard-go || true

# 3. sysctl
cat >/etc/sysctl.d/99-wg-cn2.conf <<'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.udp_rmem_min = 131072
net.ipv4.udp_wmem_min = 131072
net.ipv4.udp_mem = 262144 524288 1048576
net.core.netdev_max_backlog = 250000
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
EOF
sysctl --system

# 4. NIC 队列与 offload
ethtool -G eno1 rx 4096 tx 4096
ethtool -K eno1 gro on gso off tso off lro off rxhash on

# 5. IRQ 亲和
systemctl stop irqbalance || true
cat >/usr/local/sbin/set-irq-affinity.sh <<'EOF'
#/bin/bash
nic=eno1
cores=(2 3 4 5 6 7 8 9)
i=0
for irq in $(grep $nic -l /proc/irq/*/net/*/ifindex | sed 's#/net/.##;s#/ifindex##'); do
  printf -v mask "%x" $((1<<${cores[$i]}))
  echo $mask > /proc/irq/$(basename $irq)/smp_affinity
  ((i=(i+1)%${#cores[@]}))
done
EOF
bash /usr/local/sbin/set-irq-affinity.sh

# 6. WireGuard 基本配置(示例)
install -d -m 700 /etc/wireguard
cat >/etc/wireguard/wg0.conf <<'EOF'
[Interface]
Address = 10.66.0.1/30
ListenPort = 51820
PrivateKey = <HK_PRIVATE_KEY>
MTU = 1380
Table = off
PostUp = ip link set dev wg0 txqueuelen 3000
PostUp = sysctl -w net.ipv4.ip_forward=1

[Peer]
PublicKey = <CN_PUBLIC_KEY>
Endpoint = <CN_PUBLIC_IP>:51820
AllowedIPs = 10.66.0.2/32, 10.100.0.0/16, 10.200.0.0/16
PersistentKeepalive = 15
EOF
chmod 600 /etc/wireguard/wg0.conf

# 7. 启动
wg-quick down wg0 2>/dev/null || true
wg-quick up wg0
systemctl enable wg-quick@wg0

如果你也在香港跑 CN2,需要把延迟、抖动与吞吐同时收敛在一个好看又稳定的区间,上面这套流程基本能一次到位。真到机房里,别怕麻烦,一条条开,一项项测——曲线会告诉你答案。

目录结构
全文