PVE LXC sing-box 网关 sysctl 与 UDP 性能优化

明确整理 PVE 宿主机和 LXC 容器里运行 sing-box 旁路网关时分别要添加的 sysctl、网络接口和 UDP 性能配置。

这篇只保留 PVE 下运行 LXC sing-box 旁路网关时真正需要动手添加的内容。按顺序做即可:

  1. 在 PVE 宿主机添加宿主机网络参数。
  2. 在 PVE 宿主机关闭这个 LXC 的 PVE firewall,或者确认相关接口的 rp_filter 已关闭。
  3. 在 LXC 容器里添加容器自己的转发和 sing-box 性能参数。
  4. 按本文方法从现有配置里取出 CTID、网桥、IP、网关、MAC、IPv6 等变量,替换示例命令。

下面示例使用:

  • LXC ID:101
  • PVE 网桥:vmbr0
  • 容器 IPv4:10.20.20.6/24
  • IPv4 网关:10.20.20.1
  • 容器 IPv6:fd88::6666/64
  • IPv6 网关:fd88::1111
  • 容器网卡 MAC:BC:24:11:1E:91:3C

这些值只是示例,必须按你自己的 pct config <CTID> 输出替换。

1. 先获取自己的变量

所有 pct 命令都在 PVE 宿主机 Shell 执行,不是在 LXC 容器里执行。

先列出容器,确认 sing-box 所在的 LXC ID:

pct list

假设 sing-box 容器 ID 是 101,查看它当前的网卡配置:

pct config 101 | grep '^net0'

你会看到类似输出:

net0: name=eth0,bridge=vmbr0,firewall=1,gw=10.20.20.1,gw6=fd88::1111,hwaddr=BC:24:11:1E:91:3C,ip=10.20.20.6/24,ip6=fd88::6666/64,type=veth

这里每个字段都可以直接对应到后面的 pct set 命令:

字段 示例值 怎么获取
CTID 101 pct list 第一列
name eth0 `pct config 101
bridge vmbr0 同一行里的 bridge=
gw 10.20.20.1 同一行里的 gw=
gw6 fd88::1111 同一行里的 gw6=,没有 IPv6 就不要写 gw6
hwaddr BC:24:11:1E:91:3C 同一行里的 hwaddr=,这就是原来的 MAC 地址
ip 10.20.20.6/24 同一行里的 ip=
ip6 fd88::6666/64 同一行里的 ip6=,没有 IPv6 就不要写 ip6
type veth 同一行里的 type=

hwaddr 后面的参数不需要重新生成,直接从当前 net0hwaddr= 抄出来即可。这样关闭 PVE firewall 后,容器 MAC 不会变化,路由器 DHCP 静态绑定、邻居表、ACL 也不会被打乱。

再确认这个 LXC 在 PVE 宿主机侧对应的接口名:

ip -br link | grep -E 'veth101|fwbr101|fwpr101|fwln101|vmbr0'

常见情况:

vmbr0             UP ...
veth101i0@if2    UP ... master vmbr0 ...

含义是:

  • vmbr0 是容器挂载的 PVE 网桥。
  • veth101i0 是 LXC 101 在 PVE 宿主机侧的虚拟网卡。
  • 如果还能看到 fwbr101i0fwpr101p0fwln101i0,说明这个 LXC 还在 PVE firewall 路径里。

2. PVE 宿主机要添加什么

这一部分只在 PVE 宿主机 做。

2.1 添加全局网关性能参数

编辑 PVE 宿主机文件:

nvim /etc/sysctl.d/99-sing-box-gateway.conf

写入:

# PVE 宿主机承载 sing-box LXC 网关时使用,负责宿主机桥接、物理网卡队列和全局转发。
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1

# TCP 出站使用 fq + BBR;Hysteria2/TUIC/QUIC 主链路是 UDP,BBR 不会直接优化 UDP。
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

# 提高宿主机收包积压队列和 TCP 监听队列,适合旁路网关突发流量。
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192

# 提高 TCP/UDP socket 缓冲上限,避免高速 UDP 先撞到系统缓冲限制。
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.ipv4.udp_rmem_min = 16384

# 保留基础连接健壮性,不使用过激 TIME_WAIT 回收参数。
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 10000 65000
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200

应用:

sysctl -p /etc/sysctl.d/99-sing-box-gateway.conf

2.2 添加 PVE 旁路网关 rp_filter 配置

编辑 PVE 宿主机文件:

nvim /etc/sysctl.d/98-sing-box-pve-bypass.conf

如果你的 CTID 是 101,网桥是 vmbr0,宿主机侧接口是 veth101i0,写入:

# PVE 宿主机侧关闭反向路径过滤,避免 TProxy、FakeIP、策略路由的非对称路径被桥接层误丢。
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.vmbr0.rp_filter = 0
net.ipv4.conf.veth101i0.rp_filter = 0

如果 ip -br link 里实际存在 PVE firewall 接口,再额外追加:

# 只有这些接口真实存在时才添加;不存在就不要写,避免 sysctl 加载时报错。
net.ipv4.conf.fwbr101i0.rp_filter = 0
net.ipv4.conf.fwpr101p0.rp_filter = 0
net.ipv4.conf.fwln101i0.rp_filter = 0

应用:

sysctl -p /etc/sysctl.d/98-sing-box-pve-bypass.conf

2.3 关闭这个 LXC 的 PVE firewall

如果这个 LXC 专门做 sing-box 网关,且不依赖 PVE firewall 过滤入站,建议关闭该 LXC 网卡的 PVE firewall,减少 fwbr/fwpr/fwln 这一层。

先用当前配置取值:

pct config 101 | grep '^net0'

再按原值改写 pct set。示例:

pct set 101 -net0 name=eth0,bridge=vmbr0,firewall=0,gw=10.20.20.1,gw6=fd88::1111,hwaddr=BC:24:11:1E:91:3C,ip=10.20.20.6/24,ip6=fd88::6666/64,type=veth

替换规则:

  • 101:换成你的 CTID。
  • bridge=vmbr0:换成 pct config 里原来的 bridge=
  • gw=gw6=ip=ip6=:照抄原来的值;没有 IPv6 就删除 gw6=...ip6=...
  • hwaddr=...:照抄原来的 hwaddr=,不要随便换 MAC。
  • firewall=0:这是这条命令唯一主动修改的目标。

改完确认:

pct config 101 | grep '^net0'
ip -br link | grep -E 'veth101|fwbr101|fwpr101|fwln101'

理想结果是 net0 里显示 firewall=0,运行态只剩 veth101i0,不再出现 fwbr101i0fwpr101p0fwln101i0

3. LXC 容器里要添加什么

这一部分只在 sing-box 所在的 LXC 容器内 做。

3.1 添加容器转发参数

编辑容器内文件:

vi /etc/sysctl.d/local.conf

写入:

# LXC 容器作为旁路网关时必须开启转发,并关闭容器内 eth0 的反向路径过滤。
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.eth0.rp_filter = 0
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1
net.ipv6.conf.eth0.forwarding = 1
net.ipv6.conf.eth0.accept_ra = 2
net.ipv6.conf.eth0.accept_ra_defrtr = 1

Alpine / BusyBox 的 sysctl 通常不支持 --system,直接指定文件加载:

sysctl -p /etc/sysctl.d/local.conf

如果容器不用 IPv6,可以删除所有 net.ipv6.* 行。

3.2 添加 sing-box 性能参数

编辑容器内文件:

vi /etc/sysctl.d/98-sing-box-performance.conf

写入:

# sing-box 在容器内发起 TCP/UDP 出站连接,这些参数要在容器命名空间内单独设置。
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_rmem = 4096 131072 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.ipv4.udp_rmem_min = 16384
net.ipv4.tcp_slow_start_after_idle = 0

应用:

sysctl -p /etc/sysctl.d/98-sing-box-performance.conf

如果提示 bbr 不可用,先看可用算法:

sysctl net.ipv4.tcp_available_congestion_control

如果输出里没有 bbr,就把这一行注释掉:

# net.ipv4.tcp_congestion_control = bbr

3.3 设置开机自动加载

Alpine LXC 使用 OpenRC 时,在容器内执行:

rc-update add sysctl boot

如果手动验证:

rc-service sysctl start

非特权 LXC 里可能看到 kernel.*fs.*Read-only file system 提示,这是系统默认参数碰到容器不可写项。只要下面的检查值正确,就说明本文添加的网关参数已经生效。

4. 最后验证

4.1 PVE 宿主机验证

在 PVE 宿主机执行:

sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding
sysctl net.ipv4.tcp_congestion_control
sysctl net.core.default_qdisc
sysctl net.core.rmem_max net.core.wmem_max
sysctl net.ipv4.udp_rmem_min
sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.conf.vmbr0.rp_filter
sysctl net.ipv4.conf.veth101i0.rp_filter

预期关键值:

net.ipv4.ip_forward = 1
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.vmbr0.rp_filter = 0
net.ipv4.conf.veth101i0.rp_filter = 0

4.2 LXC 容器验证

在 LXC 容器内执行:

sysctl net.ipv4.ip_forward
sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.conf.eth0.rp_filter
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.ipv4.tcp_congestion_control
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmem
sysctl net.ipv4.udp_rmem_min

预期关键值:

net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.eth0.rp_filter = 0
net.ipv4.tcp_congestion_control = bbr
net.ipv4.udp_rmem_min = 16384

4.3 UDP 丢包检查

在 LXC 容器内观察 sing-box UDP socket:

ss -unmp

连续观察:

watch -n 1 'ss -unmp | grep -E "sing-box|drops|udp"'

如果测速或高峰下载时 drops 持续增长,优先检查:

  • 容器内 net.ipv4.udp_rmem_min 是否生效。
  • PVE 宿主机 net.core.rmem_maxnet.core.wmem_max 是否生效。
  • sing-box / Hysteria2 / TUIC 自身是否还有 socket buffer 或带宽参数需要调整。
  • PVE 宿主机 CPU、物理网卡和 veth offload 能力。

4.4 offload 检查

offload 在 PVE 宿主机检查,不是在 LXC 容器里检查。

先找物理网卡和容器 veth:

ip -br link

master vmbr0 的接口。常见关系如下:

enp2s0       UP ... master vmbr0 ...
vmbr0        UP ...
veth101i0    UP ... master vmbr0 ...

检查:

ethtool -k enp2s0
ethtool -k veth101i0

重点看:

generic-segmentation-offload
generic-receive-offload
tx-udp-segmentation

如果物理网卡或 veth 的关键项是 off,并且驱动允许修改,再尝试:

ethtool -K enp2s0 gso on gro on
ethtool -K enp2s0 tx-udp-segmentation on
ethtool -K veth101i0 gso on gro on
ethtool -K veth101i0 tx-udp-segmentation on

有些 veth 不允许打开 GRO,有些网卡不支持 tx-udp-segmentation。这不是 sysctl 能解决的问题,按实际输出判断即可。

5. 不要添加这些旧参数

下面这些参数不建议为了“优化”照抄:

net.ipv4.tcp_congestion_control = hybla
fs.file-max = 100000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 5000

原因很简单:

  • hybla 不适合作为现代代理网关默认拥塞控制,优先用 bbr
  • fs.file-max = 100000 可能反而低于现代 Linux 默认值。
  • tcp_tw_reuse 不是透明代理和 UDP 性能问题的通用解法。
  • tcp_max_tw_buckets = 5000 太小,可能导致内核过早回收连接状态。

最终只记住两层边界:PVE 宿主机负责 vmbr0veth101i0、PVE firewall 路径、物理网卡和全局缓冲;LXC 容器负责自己的 eth0、转发、容器命名空间 sysctl,以及 sing-box 进程实际使用的 TCP/UDP 参数。