一次IPv6接入事故:地址有了,路由不通
公司给线上服务器开IPv6,云控制台上显示IPv6地址已分配。我SSH登录,ip -6 addr一看,地址确实在网卡上。但ping6 2400:3200::1(阿里DNS)直接超时,curl -6 https://www.taobao.com卡到报错。
排查了半小时,最后发现两个问题:
- Cloud-init生成的网卡配置文件里,
IPV6INIT=no,系统广播了RA(Router Advertisement)但没让IPv6生效 - 云安全组只放行了ICMPv4,没放行ICMPv6,导致
ping6不通
IPv6不是“地址配上就完事”。它和IPv4有本质区别:无状态地址自动配置(SLAAC)、重复地址检测(DAD)、ICMPv6的NDP协议,任何一个环节出问题,地址就是“死”的。
这篇文章记录这次完整过程:问题定位、SLAAC和DHCPv6两种方案的选择、Nginx双栈监听配置、以及最后的压测数据。
问题分析:IPv6双栈为什么连不通
服务器环境:
| 项目 | 版本/型号 |
|---|---|
| 操作系统 | CentOS Stream 9 |
| 内核 | 5.14.0-427.el9.x86_64 |
| Nginx | 1.24.0 |
| 网卡 | ens3(virtio) |
| IPv4地址 | 1.2.3.4(示例) |
| IPv6地址 | 2409:8a55:1a2b:3c00::5(示例) |
IPv6双栈服务器要正常工作,需要四个链路全部通:
- 网卡有IPv6地址(链路本地+全局)
- 有默认IPv6路由(通过RA或DHCPv6获取)
- DNS能解析AAAA记录(或直连IP测试)
- 防火墙/安全组放行IPv6协议(ICMPv6、TCP、UDP)
我的案例里,第1条和第2条都“看起来正常”:地址有,路由表也有default via fe80::1。但实际网络层不通——因为安全组把ICMPv6全拦了,NDP协议(邻居发现协议)无法完成邻居解析,数据包发不出去。
这个坑后面细讲。下面先看两种IPv6地址分配方案。
方案对比:SLAAC vs DHCPv6
服务器接入IPv6双栈,核心是“怎么拿地址”。两种常见方式:
| 维度 | SLAAC(无状态自动配置) | DHCPv6(有状态配置) |
|---|---|---|
| 地址分配方式 | 网卡根据RA报文自行生成 | DHCP服务器分配 |
| 可控性 | 低,无法记录谁拿了哪个地址 | 高,可以按MAC绑定固定IP |
| 配置复杂度 | 低(路由器/网关只需开RA) | 中(需要部署DHCPv6服务) |
| 运营商支持 | 国内部分云厂商/运营商支持 | 支持较广泛 |
| 排障难度 | 中,需要抓包看RA | 低,有明确租约记录 |
| 适用场景 | 家庭网关、路由器、临时测试 | 企业服务器、数据中心 |
我这次用的是SLAAC,因为云厂商已经在链路里广播RA,网卡配置好就能自动拿地址。如果你的网络环境支持DHCPv6,或者需要固定地址便于审计,建议用DHCPv6。下面两种方案都给出完整配置。
方案一:SLAAC无状态配置(CentOS Stream 9)
1. 检查内核IPv6是否启用
# 检查内核模块
cat /proc/net/if_inet6
# 输出包含以下行说明IPv6已启用
# 00000000000000000000000000000001 01 80 10 80 lo
# fe800000000000000000000000000000 03 40 20 80 ens3
# 检查sysctl配置,确认没有禁用
sysctl net.ipv6.conf.all.disable_ipv6
# 必须输出 0,如果是1,执行:
# echo "net.ipv6.conf.all.disable_ipv6=0" >> /etc/sysctl.conf
# sysctl -p
2. 配置网卡启用IPv6自动获取
CentOS Stream 9使用NetworkManager管理网络。用nmcli配置双栈:
# 查看当前连接名称(一般是ens3或Wired connection 1)
nmcli connection show
# 修改连接配置,启用IPv6并设为自动获取(SLAAC+DHCPv6)
nmcli connection modify "ens3" \
ipv6.method auto \
ipv6.addr-gen-mode stable-privacy \
ipv6.dhcp-duid ll \
ipv6.dhcp-iaid mac
# 重新激活连接
nmcli connection down "ens3" && nmcli connection up "ens3"
# 验证IPv6地址和路由
ip -6 addr show ens3
ip -6 route show
关键参数:
ipv6.method auto:启用SLAAC同时尝试DHCPv6ipv6.addr-gen-mode stable-privacy:生成稳定且隐私保护的地址(RFC 7217),避免每次重启地址变化ipv6.dhcp-duid ll:使用链路层地址作为DUID
3. 验证SLAAC是否生效
# 查看全局IPv6地址(ens3上应该有2409开头的地址)
ip -6 addr show ens3
# 输出示例:
# inet6 2409:8a55:1a2b:3c00::5/128 scope global dynamic
# inet6 fe80::5054:ff:fe12:3456/64 scope link
# 查看默认路由
ip -6 route show default
# 输出示例:
# default via fe80::1 dev ens3 proto ra metric 100
# 测试IPv6连通性(阿里DNS)
ping6 -c 4 2400:3200::1
方案二:DHCPv6有状态分配(Kea DHCPv6)
如果你没有RA广播,或者需要固定IPv6地址,用DHCPv6。这里用Kea DHCPv6 2.4.0作为服务端,它比系统自带的dhcpd更易配置。
1. 安装Kea DHCPv6
# CentOS Stream 9
dnf install -y epel-release
dnf install -y kea-dhcp6-server
# 验证版本
kea-dhcp6 --version
# Kea DHCPv6 2.4.0
2. 配置Kea(JSON格式)
{
"Dhcp6": {
"interfaces-config": {
"interfaces": ["ens3"]
},
"lease-database": {
"type": "memfile",
"name": "/var/lib/kea/kea-dhcp6.leases"
},
"preferred-lifetime": 3600,
"valid-lifetime": 7200,
"subnet6": [
{
"subnet": "2409:8a55:1a2b:3c00::/64",
"pd-pools": [],
"pools": [
{ "pool": "2409:8a55:1a2b:3c00::100 - 2409:8a55:1a2b:3c00::1ff" }
],
"reservations": [
{
"hw-address": "52:54:00:12:34:56",
"ip-addresses": ["2409:8a55:1a2b:3c00::5"]
}
]
}
]
}
}
3. 启动Kea并设置开机自启
systemctl enable --now kea-dhcp6-server
journalctl -u kea-dhcp6-server -f
# 看到 "DHCPv6 server start" 说明启动成功
4. 客户端配置(Ubuntu Netplan)
如果你的服务器是Ubuntu(我测试用的备用机),Netplan配置如下:
network:
version: 2
ethernets:
ens3:
dhcp4: yes
dhcp6: yes
accept-ra: yes
# 使用DHCPv6获取地址,同时接受RA中的路由信息
ipv6-privacy: true
Nginx双栈监听配置
地址通了,Web服务也要同时监听IPv4和IPv6。Nginx默认只监听IPv4,必须显式配置IPv6监听。
我的Nginx 1.24.0配置:
server {
# IPv4监听
listen 80;
listen 443 ssl;
# IPv6监听
listen [::]:80;
listen [::]:443 ssl;
server_name api.example.com;
# 开启IPv6解析和缓存
resolver 2400:3200::1 8.8.8.8 valid=30s;
resolver_timeout 5s;
ssl_certificate /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
注意:listen [::]:80在Linux上默认同时监听IPv4和IPv6(dual-stack socket)。如果只想监听IPv6,加ipv6only=on:
listen [::]:80 ipv6only=on;
改完配置重载:
nginx -t && systemctl reload nginx
验证与压测数据
1. 连通性验证
# IPv4测试
curl -4 -o /dev/null -s -w "IPv4 HTTP状态码: %{http_code}, TTFB: %{time_starttransfer}s\n" https://api.example.com
# IPv6测试(指定-6强制走IPv6)
curl -6 -o /dev/null -s -w "IPv6 HTTP状态码: %{http_code}, TTFB: %{time_starttransfer}s\n" https://api.example.com
2. 压测数据对比
用ab(ApacheBench 2.3)对双栈接口压测,Nginx工作进程4个,KeepAlive开启,压测30秒:
| 压测场景 | QPS(请求/秒) | 平均响应时间(ms) | 失败率 |
|---|---|---|---|
| 纯IPv4访问 | 14162 | 2.12 | 0% |
| 纯IPv6访问 | 13985 | 2.15 | 0% |
| IPv4+IPv6混合 | 13840 | 2.19 | 0% |
IPv6比IPv4性能低1.2%~2.2%。这个差异来自双栈socket的额外开销(IPv4-mapped地址处理),对业务无感知。双栈同时监听没有出现资源争用。
如果Nginx关闭ipv6only=on,只监听IPv6,QPS会降到约13200,因为IPv4请求需要经过mapped地址转换。所以生产环境保持默认的dual-stack模式。
3. 真实环境的IPv6延迟
# 电信网络,服务器在华东
ping -c 10 1.2.3.4 | tail -1
# rtt min/avg/max/mdev = 4.213/4.568/5.102/0.312 ms
ping6 -c 10 2409:8a55:1a2b:3c00::5 | tail -1
# rtt min/avg/max/mdev = 3.887/4.201/4.776/0.245 ms
IPv6平均RTT比IPv4低0.3ms。原因是运营商IPv6网络链路更直,少了一层NAT转换。
避坑指南
以下四个坑,都是我在这次配置中实际遇到并花时间排查的:
坑1:Cloud-init默认关闭IPv6
云服务器首次启动时,Cloud-init会根据云平台metadata生成网卡配置。我检查/etc/sysconfig/network-scripts/ifcfg-ens3发现:
IPV6INIT=no
IPV6_AUTOCONF=no
即使系统内核支持IPv6,网卡也不会接收RA报文。改法有两种:
# 方法1:nmcli(推荐)
nmcli connection modify "ens3" ipv6.method auto ipv6.addr-gen-mode stable-privacy
# 方法2:直接编辑ifcfg文件
sed -i 's/IPV6INIT=no/IPV6INIT=yes/' /etc/sysconfig/network-scripts/ifcfg-ens3
echo 'IPV6_AUTOCONF=yes' >> /etc/sysconfig/network-scripts/ifcfg-ens3
systemctl restart NetworkManager
注意:编辑ifcfg文件后不要用systemctl restart network,CentOS Stream 9已经没有network服务,要用NetworkManager。
坑2:安全组拦ICMPv6导致NDP失败
这是最隐蔽的坑。Linux的IPv6邻居发现协议(NDP)完全依赖ICMPv6。
如果你的云安全组/防火墙只放行TCP和UDP,没有放行ICMPv6,那么服务器无法回复邻居请求(Neighbor Solicitation),数据包发送不出去,地址“看起来”在但实际不可达。
云安全组需要放行以下ICMPv6类型:
| ICMPv6类型 | 用途 | 必开 |
|---|---|---|
| 128 | Echo Request(ping请求) | 是 |
| 129 | Echo Reply(ping回复) | 是 |
| 133 | Router Solicitation | 是 |
| 134 | Router Advertisement | 是 |
| 135 | Neighbor Solicitation | 必须 |
| 136 | Neighbor Advertisement | 必须 |
本地防火墙(firewalld)也同理:
# 放行ICMPv6(所有类型)
firewall-cmd --permanent --add-rich-rule='rule protocol value="ipv6-icmp" accept'
firewall-cmd --reload
坑3:Nginx的listen [::]:443不会自动覆盖IPv4
很多教程写listen [::]:443 ssl;就完事了。实测Nginx 1.24在Linux上,这个配置确实同时监听IPv4和IPv6(dual-stack)。但在FreeBSD上或者如果编译时加了--without-ipv6,就不是这样。
最稳妥的写法是两行都写:
listen 443 ssl;
listen [::]:443 ssl;
这样在Nginx可以同时监听两个协议栈。检查是否生效:
ss -tlnp | grep nginx
# 输出:
# LISTEN 0 511 *:80 *:* users:(("nginx",pid=1234,fd=6))
# LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=1234,fd=7))
IPv6的socket([::])同时在跑,这就对了。
坑4:curl走代理导致IPv6测试失败
测试IPv6时,如果服务器或本地环境配了HTTP代理(比如http_proxy环境变量),curl会走代理解析DNS,代理没有IPv6出口,就会报Connection timed out。
# 强制绕过代理,直连IPv6地址
curl --noproxy '*' -6 -I http://[2409:8a55:1a2b:3c00::5]/
排查时先用ip -6 route确认路由,再用ping6确认ICMP,最后才用curl测试HTTP。顺序反了会浪费大量时间。
写给后来者的配置清单
整理一份最小化的IPv6双栈服务器checklist:
- 确认内核支持:
cat /proc/net/if_inet6有内容 - 网卡启用IPv6:
nmcli connection modify "ens3" ipv6.method auto - 安全组放行ICMPv6全部类型
ip -6 addr show确认拿到2409开头的全局地址ip -6 route show default确认有默认路由ping6 -c 4 2400:3200::1验证外网连通- Nginx配置双栈监听,
nginx -t检查 ss -tlnp | grep nginx确认两个socket都在监听
这套流程走完,你的服务器就是真正的IPv6双栈了。