为 ocserv 配置 IPv6:一次由 MTU 1200 引发的隐蔽故障排查
给一台已经正常运行的 ocserv 服务器补上 IPv6,看起来只需要打开地址池、转发和防火墙。实际配置完成后,我遇到了一个很绕的问题:服务器有公网 IPv6,IPv6 转发和 NAT66 也都正常,客户端却始终只有 IPv4。
最后发现,问题不在 IPv6 地址池,也不在 OpenConnect 客户端,而是原配置中的 mtu = 1200。扣除隧道和加密开销后,有效 MTU 只剩 1114,低于 IPv6 要求的 1280。ocserv 因此主动停止向客户端下发 IPv6。
这篇文章记录完整配置和排查过程。
环境与目标
本次环境:
- Ubuntu 20.04
- ocserv 1.1.6
- ocserv 监听 TCP/UDP 666
- 服务器公网 IPv6:
2406:da18:d4c:c000:1173:148b:7e09:a4fa - VPN IPv4 地址池:
10.10.10.0/24 - VPN IPv6 地址池:
fda9:4efe:7e3b:03ea::/48 - 出口网卡:
eth0 - UFW 管理防火墙
目标是让 VPN 客户端同时获得 IPv4 和 IPv6,并让公网 IPv6 流量从 ocserv 服务器出口访问互联网。
这里给客户端分配的是 ULA 地址 fda9:...,再通过 NAT66 转换成服务器的公网 IPv6。LinuxBabe 的 ocserv IPv6 配置示例采用了同样的结构。[1]
如果云服务商或机房向服务器路由了可供客户端使用的公网 IPv6 前缀,也可以直接路由该前缀,不必使用 NAT66。本文讨论的是服务器只有单个公网 IPv6、没有可委派前缀时的实用方案。
确认服务器的 IPv6 出口
先确认服务器自己能通过 IPv6 访问互联网:
1 | ip -6 addr show scope global |
正常情况下应看到公网 IPv6、IPv6 默认路由,以及 curl -6 返回的服务器出口地址。
如果服务器自己都不能访问 IPv6,先处理云平台 IPv6 分配、路由表、安全组和网络 ACL。ocserv 无法替代底层网络配置。
配置 ocserv IPv6 地址池
编辑:
1 | sudo nano /etc/ocserv/ocserv.conf |
加入或取消注释:
1 | ipv6-network = fda9:4efe:7e3b:03ea::/48 |
可以同时下发 IPv6 DNS:
1 | dns = 2001:4860:4860::8888 |
本次完整的地址池相关配置如下:
1 | device = vpns |
ipv6-network 使用 /48,ocserv 再按 ipv6-subnet-prefix = 64 为连接分配子网。修改后需要重启 ocserv,新建的 VPN 会话才会使用新参数。
开启 IPv6 转发
检查当前值:
1 | sysctl net.ipv6.conf.all.forwarding |
如果结果不是 1,在 /etc/sysctl.conf 或独立的 sysctl 配置文件中加入:
1 | net.ipv6.conf.all.forwarding = 1 |
应用配置:
1 | sudo sysctl -p |
再次确认:
1 | sysctl net.ipv6.conf.all.forwarding |
配置 UFW 转发和 NAT66
客户端使用的是 ULA 地址,访问公网 IPv6 时需要在出口网卡做 MASQUERADE。
在 /etc/ufw/before6.rules 的 NAT 表中加入:
1 | *nat |
把 eth0 换成服务器的实际出口网卡。建议限制源地址为 VPN IPv6 网段,不要对所有 IPv6 流量无条件做 MASQUERADE。
在 ufw6-before-forward 链允许 VPN IPv6 网段转发:
1 | -A ufw6-before-forward -s fda9:4efe:7e3b::/48 -j ACCEPT |
确认 /etc/default/ufw 中启用了 IPv6:
1 | IPV6=yes |
应用规则后检查:
1 | sudo ip6tables -t nat -vnL POSTROUTING |
LinuxBabe 的教程也要求 IPv6 forwarding、UFW IPv6 转发规则和出口 MASQUERADE。[1]
第一次失败:所有客户端都只有 IPv4
最初配置完成后,服务端看起来没有问题:
- 服务器有公网 IPv6;
net.ipv6.conf.all.forwarding = 1;- UFW 已允许
fda9:4efe:7e3b::/48; ip6tables已存在 MASQUERADE;- ocserv 配置中已经启用 IPv6 地址池。
但 Windows OpenConnect GUI 的 CONNECT 响应只有:
1 | X-CSTP-Address: 10.10.10.108 |
没有:
1 | X-CSTP-Address-IP6: fda9:.../64 |
macOS Cisco Secure Client 提示本次 VPN 不支持 IPv6,并阻断 IPv6 流量。macOS OpenConnect 9.21 也只显示:
1 | Configured as 10.10.10.71 |
换了三个客户端,结果完全一样,问题显然不能再简单归因于某个 GUI 或网卡驱动。
容易误导的 occtl 输出
当时执行:
1 | sudo occtl show user <用户名> |
服务端却能看到:
1 | IPv4: 10.10.10.71 |
这很容易让人误以为 IPv6 已经下发成功。实际上,ocserv 可以先为会话生成或预留 IPv6 地址,之后再根据客户端能力和隧道条件决定是否把该地址写入 CSTP 响应。
判断客户端是否真正收到 IPv6,应该同时检查:
- 客户端 CONNECT 响应是否包含
X-CSTP-Address-IP6; - 客户端 VPN 网卡是否有
fda9:地址; - 客户端是否安装了通过 VPN 的 IPv6 路由。
只看 occtl 不够。
真正根因:有效 MTU 低于 IPv6 最低要求
原 ocserv 配置中有:
1 | mtu = 1200 |
客户端实际收到:
1 | X-CSTP-Base-MTU: 1200 |
1200 还要扣除 CSTP、DTLS、IP、UDP/TCP 和加密相关开销,最终可用于隧道数据的 MTU 只有 1114。
IPv6 规定每条链路必须支持至少 1280 字节的 MTU;如果链路无法直接承载,链路层需要提供分片与重组能力。[2]
ocserv 1.1.6 在发送地址前有一段明确检查:当计算出的数据 MTU 小于 1280,并且会话原本允许 IPv6 时,ocserv 会把该会话标记为不使用 IPv6。[3]
对应逻辑可以概括为:
1 | if (DATA_MTU(ws, ws->link_mtu) < 1280 && |
这解释了之前所有看似矛盾的现象:
1 | ocserv 已生成 fda9 地址 |
Windows 日志中反复出现的报文丢弃也指向同一问题:
1 | Drop oversized packet retrieved from Wintun adapter (1141 > 1114) |
它不是单独的 Wintun 故障,而是低 MTU 已经开始影响正常数据传输。
修复 MTU
不能简单把 mtu 从 1200 改成 1280,因为扣除协议开销后,有效 MTU 仍然会低于 1280。
本次环境中:
1 | 基础 MTU:1200 |
要让有效 MTU 达到 1280,基础值理论上至少需要约 1366。最终采用:
1 | mtu = 1400 |
重启:
1 | sudo systemctl restart ocserv |
重连后,服务端 VPN 接口 MTU 变成:
1 | MTU: 1334 |
有效值已经高于 1280。客户端随即收到:
1 | Configured as 10.10.10.70 + fda9:4efe:7e3b:.../64 |
Windows OpenConnect GUI 和 macOS Cisco Secure Client 也都能正常显示服务器的 IPv6 出口地址。
1400 不是适用于所有网络的固定答案。正确原则是:
- 扣除隧道开销后的有效 MTU 必须不低于 1280;
- 还要考虑客户端到服务器之间的真实路径 MTU;
- 调整后观察 DTLS/CSTP 日志中是否还有 oversized packet、分片或重传问题。
验证清单
服务端
1 | ip -6 addr show scope global |
VPN 接口应出现:
1 | inet6 fda9:4efe:7e3b::1/128 |
并有客户端子网路由:
1 | fda9:4efe:7e3b:<子网>::/64 dev vpnsX |
客户端
OpenConnect 日志应包含:
1 | X-CSTP-Address-IP6: fda9:.../64 |
或者连接摘要:
1 | Configured as 10.10.10.x + fda9:.../64 |
检查地址和出口:
1 | ifconfig |
IP 检测网站显示的 IPv6 应变成 ocserv 服务器的公网 IPv6,而不是客户端本地运营商分配的地址。
观察计数器
连接客户端并执行一次 curl -6 后,在服务器观察:
1 | sudo ip6tables -vnL ufw6-before-forward |
来自 fda9:4efe:7e3b::/48 的转发和 NAT 计数应增加。如果客户端已经有 fda9: 地址,但计数始终为零,通常是客户端没有安装 IPv6 VPN 路由。
小结
一套可用的 ocserv IPv6 配置至少包括:
ipv6-network和ipv6-subnet-prefix;- Linux IPv6 forwarding;
- UFW/iptables 转发规则;
- 使用 ULA 时的 NAT66;
- 不低于 IPv6 要求的有效隧道 MTU;
- 客户端 IPv6 地址和路由验证。
这次最耗时间的地方,是服务器内部已经能看到 fda9: 地址,让排查方向一度转向客户端。真正有用的线索是 X-CSTP-MTU: 1114,以及 ocserv 源码中小于 1280 就禁用 IPv6 的判断。
以后再遇到“ocserv 配了 IPv6,但客户端始终只有 IPv4”,除了地址池、防火墙和 NAT,也应该第一时间看有效 MTU。
Sources
[1] https://www.linuxbabe.com/linux-server/ocserv-openconnect-vpn-advanced — Ocserv Advanced: Enable IPv6
[2] https://www.rfc-editor.org/rfc/rfc8200 — RFC 8200: Internet Protocol, Version 6
[3] https://gitlab.com/openconnect/ocserv/-/blob/1.1.6/src/worker-vpn.c — ocserv 1.1.6 worker-vpn.c MTU check