IPv4和IPv6有什么区别?服务器部署双栈网络的注意事项

如果你在服务器上看到过 fe80:: 开头的地址,或者云厂商给你分配了 2408:/2409: 开头的地址,说明你已经碰到 IPv6 了。很多人的第一反应是把它关掉——"我又不认识这个,关了省事"。这个做法在过去问题不大,但现在会越来越麻烦:越来越多的移动端网络(尤其是 4G/5G)已经默认分配 IPv6 地址,很多 App 上架审核要求支持 IPv6 网络,部分地区的家宽也已经在纯 IPv6 或者 IPv6 优先的环境下运行。

这篇文章讲清楚 IPv4 为什么不够用、IPv6 的地址格式和几个与 IPv4 完全不同的机制变化,然后重点给出双栈部署的实操清单:DNS 的 AAAA 记录、Nginx 的监听写法、防火墙必须单独配置、安全组要单独放行、数据库和 Redis 的绑定地址、以及 Docker 的 IPv6 坑。最后讲 IPv6 带来的暴露面扩大问题怎么防。

一、IPv4 为什么不够用

IPv4 地址是 32 位,理论上限约 43 亿个。这个数字在 1980 年代看起来绰绰有余,但三件事让它在 2010 年前后就基本耗尽了:

设备数量的爆炸。 手机、平板、物联网设备、车联网,联网设备数量早已远超当初的设想。

地址分配的历史浪费。 早期分配过于粗放,大量地址块被分配但没有实际使用,且难以回收。

地址不能按"个数"精细切分。 IPv4 的子网划分受限于二进制边界,一个只需要 10 个地址的网络往往也要分到 16 或 32 个。

NAT 是权宜之计,不是解决方案

NAT(网络地址转换)让多台设备共享一个公网地址,极大地延缓了地址耗尽,但它带来了一系列副作用:

  • 端到端连接被破坏。外部无法主动发起连接,P2P、VoIP、游戏联机都需要额外的打洞机制
  • 端口成为稀缺资源。一个公网 IP 的端口只有 65535 个,运营商级 NAT(CGNAT)下大量用户共享,端口耗尽会导致新连接失败
  • 网络层变复杂。ALG(应用层网关)、NAT 穿越、会话表维护,都增加了设备的负担和故障点
  • 溯源困难。多个用户共用一个地址,日志里的 IP 不能直接定位到人,需要额外记录端口和时间

IPv6 的设计目标就是回到端到端的互联网,让每个设备都能有全球唯一的地址,从而消除 NAT 的必要性。

二、IPv6 的地址格式与前缀

格式

IPv6 地址是 128 位,写成 8 组 4 位十六进制数,用冒号分隔:

2001:0db8:85a3:0000:0000:8a2e:0370:7334

有两条缩写规则:

  1. 每组前导零可以省略:0db8 → db8,0370 → 370
  2. 连续的全零组可以用 :: 代替,但整个地址中只能用一次

上面的地址可以写成:

2001:db8:85a3::8a2e:370:7334

几个必须认识的特殊地址:

地址 含义
:: 未指定地址(等价 IPv4 的 0.0.0.0),常用于监听所有地址
::1 回环地址(等价 127.0.0.1)
fe80::/10 链路本地地址,只在同一个二层链路有效,每张网卡都会自动生成一个
fc00::/7 唯一本地地址(ULA),类似 IPv4 的私网地址,但不应做 NAT
2000::/3 全球单播地址,也就是公网 IPv6 地址
ff00::/8 组播地址
::ffff:192.0.2.1 IPv4 映射的 IPv6 地址,用于双栈套接受

注意 ULA(fc00::/7)不等于私网地址。 它在设计上允许在全球范围内唯一(随机生成的 40 位全局 ID 使得冲突概率极低),目的是用于内部通信而不做 NAT。很多人把它当成 IPv6 版本的 192.168.x.x 来用,再配合 NAT,这就走回了老路,不推荐。

前缀长度

IPv6 的子网前缀用 CIDR 表示,常见的分配惯例:

前缀 通常分配给 可用子网数
/32 大型 LIR、运营商 极多
/48 企业、机房、大客户 65536 个 /64
/56 中小企业、部分家宽 256 个 /64
/64 单个链路/子网的标准大小 一个子网

/64 是一个硬性约定,不要随手切分成 /96 或 /112。 因为 SLAAC(无状态地址自动配置)依赖 64 位的接口标识,前缀不是 /64 会导致 SLAAC 无法工作,某些操作系统也会拒绝配置。

三、与 IPv4 不同的几个机制

这部分是 IPv6 最容易让人困惑的地方,也是排查问题时最需要知道的差异。

机制 IPv4 IPv6
地址解析 ARP(广播) NDP 邻居发现(组播,ICMPv6)
广播 有 没有广播,只有组播和任播
地址配置 DHCP(主要)、手动 SLAAC、DHCPv6、手动
分片 中间路由器可分片 只有端系统分片,依赖 PMTUD
头部校验和 有 无(交给上层协议)
包头 变长,带选项 固定 40 字节,扩展头链式
每接口地址数 通常一个 多个是常态(链路本地 + 全球 + 临时地址)
IPSec 可选 协议规范要求支持(实际部署仍可选)

SLAAC:不用 DHCP 也能拿到地址

SLAAC(Stateless Address Autoconfiguration,RFC 4862)的工作方式是:路由器周期性发送 RA(路由器通告)报文,报文中包含网络前缀;主机收到后用前缀加上自己的接口标识生成完整地址。

接口标识的生成有两种方式:

  • EUI-64:由 MAC 地址转换而来。优点是可预测、便于管理;缺点是地址里暴露了 MAC,且换了网络也能被追踪,隐私性差
  • 随机/稳定隐私地址:现代操作系统默认使用(RFC 4941 临时地址、RFC 7217 稳定隐私地址)

RA 报文里还有几个关键标志位:

标志 含义
A(Autonomous) 是否允许主机用 SLAAC 生成地址
M(Managed) 是否需要用 DHCPv6 获取地址
O(Other) 是否需要用 DHCPv6 获取其他配置(如 DNS)

一个极其常见的坑:SLAAC 本身不告诉主机 DNS 服务器地址。 主机能拿到 IPv6 地址和默认网关,但没有 DNS,导致"有 IPv6 但解析不了域名"。解决方式有两种:路由器在 RA 里带上 RDNSS 选项(RFC 6106),或者用 DHCPv6 提供(M/O 标志置位)。

在 Linux 上查看是否收到 RA 及其内容:

ip -6 addr show
ip -6 route show
rdisc6 eth0              # 主动发送 RS 并查看回应(需安装 ndisc6 包)

DHCPv6

DHCPv6 分两种模式:

  • 有状态(Stateful):像 IPv4 的 DHCP 一样分配地址,M 标志置位
  • 无状态(Stateless):只分配 DNS 等配置信息,地址仍由 SLAAC 生成,O 标志置位

服务器场景通常不需要 DHCPv6,用静态配置或云平台的自动分配即可。

PMTUD 与必须放行 ICMPv6

IPv6 不允许中间路由器分片,超大包会被丢弃并返回 ICMPv6 "Packet Too Big" 消息,由发送端自行降低包大小。这个机制叫 PMTUD(路径 MTU 发现)。

直接后果:如果防火墙把 ICMPv6 全拦了,PMTUD 就会失败,表现为小包正常、大包卡住——能 ping 通、能建立连接,但打开网页或者传输文件时卡死。这是 IPv6 环境下最经典的故障。

所以配置 IPv6 防火墙时,必须放行 ICMPv6 的关键类型(至少包括目的不可达、包过大、超时、以及邻居发现的请求和通告)。不要照搬 IPv4 时代"禁掉 ICMP"的习惯。

四、双栈部署实操

双栈(Dual Stack)指同时运行 IPv4 和 IPv6,是当前最现实的过渡方案。

确认与配置

ip -6 addr show                      # 看 IPv6 地址
ip -6 route show                     # 看 IPv6 路由
cat /proc/net/if_inet6               # 另一种查看方式
ping -6 -c 4 2400:3200::1            # 测 IPv6 连通(或用 ping6)
curl -6 -sS https://ifconfig.co      # 看自己的 IPv6 出网地址

Ubuntu 的 netplan 配置(/etc/netplan/*.yaml):

network:
  version: 2
  ethernets:
    eth0:
      addresses:
        - 10.0.0.10/24
        - "2001:db8:1::10/64"
      routes:
        - to: default
          via: 10.0.0.1
        - to: "::/0"
          via: "2001:db8:1::1"
      nameservers:
        addresses:
          - 223.5.5.5
          - "2400:3200::1"
sudo netplan try
sudo netplan apply

风险提示:远程改网络配置有断连风险,务必用 netplan try(有自动回滚窗口),不要用 apply 直接生效。

RHEL 系用 nmcli:

sudo nmcli con mod eth0 ipv6.addresses 2001:db8:1::10/64
sudo nmcli con mod eth0 ipv6.gateway 2001:db8:1::1
sudo nmcli con mod eth0 ipv6.dns "2400:3200::1"
sudo nmcli con mod eth0 ipv6.method manual
sudo nmcli con up eth0

云平台一般会自动下发 IPv6 地址(DHCPv6 或 SLAAC),手动配置前先确认控制台是否已分配。

如何禁用 IPv6

有时确实需要禁用(比如环境没有 IPv6 连通性但应用会优先尝试 v6 导致延迟)。三种方式,影响范围不同:

# 方式一:sysctl,临时生效 + 可持久化(推荐,最容易回滚)
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
echo 'net.ipv6.conf.all.disable_ipv6 = 1' | sudo tee /etc/sysctl.d/99-disable-ipv6.conf
sudo sysctl -p /etc/sysctl.d/99-disable-ipv6.conf
# 方式二:grub 内核参数,彻底不加载 IPv6 模块(需要重启)
# 在 /etc/default/grub 的 GRUB_CMDLINE_LINUX 里加 ipv6.disable=1
sudo grub2-mkconfig -o /boot/grub2/grub.cfg     # RHEL 系 BIOS 启动
sudo update-grub                                 # Debian、Ubuntu

方式三:只禁用单张网卡,把 all 换成网卡名:

sudo sysctl -w net.ipv6.conf.eth0.disable_ipv6=1

回滚方式:把对应参数改回 0 并 sysctl -p;grub 方式需要改回配置并重新生成 grub 配置后重启。

建议:优先让 IPv6 正常工作,而不是禁用它。 禁用的合理场景只有"确实没有 IPv6 连通性且排查暂缓"。

DNS:添加 AAAA 记录

在域名解析里添加 AAAA 记录指向服务器的 IPv6 地址:

dig AAAA www.example.com +short
dig AAAA www.example.com @223.5.5.5 +short

客户端行为要理解:现代系统使用 Happy Eyeballs(RFC 8305)算法,会同时请求 A 和 AAAA,优先尝试 IPv6,如果在很短时间内(通常 250 毫秒左右)没建立连接,就并行尝试 IPv4。

这带来一个重要结论:如果 AAAA 记录存在但 IPv6 实际不通,用户不会完全打不开,而是每次访问多出几百毫秒的延迟。 这种"慢一点但不报错"的问题最难被发现。所以要么把 IPv6 配通,要么干脆不要加 AAAA 记录,不要留一个半通不通的状态。

Nginx 监听

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;

    server_name www.example.com;
    # ...
}

要点:

  • listen [::]:443 监听所有 IPv6 地址,与 listen 443 配合实现双栈
  • 老版本 Nginx 上可能需要显式加 ipv6only=on(默认已是 on,加了也无妨)
  • 不要只写 listen [::]:443 就以为同时覆盖了 IPv4。在 ipv6only=on 时它只处理 IPv6;Linux 上绑定 :: 是否同时接受 IPv4 取决于 net.ipv6.bindv6only 的值,依赖这个行为是不稳妥的。显式写两条 listen 最清晰。

验证:

sudo ss -lntp | grep -E ':443|:80'
# 应该看到 tcp 和 tcp6 两行,或者一行显示 :::443
curl -6 -sS -o /dev/null -w '%{http_code}\n' https://www.example.com/

防火墙必须单独配置

这是 IPv6 部署最大的坑:IPv4 和 IPv6 的防火墙是两套独立的规则。

sudo iptables -L -n -v      # 只看 IPv4
sudo ip6tables -L -n -v     # 看 IPv6,很可能是一条规则都没有!

如果只配了 iptables 而没配 ip6tables,你的 IPv6 服务就是完全裸奔状态——所有端口对所有 IPv6 用户开放。

ip6tables 放行:

sudo ip6tables -I INPUT -p tcp --dport 443 -j ACCEPT
sudo ip6tables -I INPUT -p tcp --dport 80 -j ACCEPT
# 必须放行 ICMPv6,否则 PMTUD 失败
sudo ip6tables -I INPUT -p ipv6-icmp -j ACCEPT

nftables 推荐用 inet 地址族,一份规则同时管 v4 和 v6:

sudo nft add table inet filter
sudo nft 'add chain inet filter input { type filter hook input priority 0 ; policy accept ; }'
sudo nft add rule inet filter input tcp dport { 80, 443 } accept
sudo nft add rule inet filter input meta l4proto ipv6-icmp accept
sudo nft list ruleset

firewalld 默认同时管理 IPv4 和 IPv6,用 firewall-cmd --add-port 加的端口对两者都生效,这是它比裸 iptables 方便的地方。

云安全组也要单独放行

云安全组的 IPv4 规则和 IPv6 规则通常是分开配置的。 在控制台添加规则时要注意选择协议版本或者地址段填 ::/0。很多人在安全组里只加了 0.0.0.0/0 的规则,结果 IPv6 完全不通,或者相反——某些平台默认对 IPv6 全放行,导致暴露面意外扩大。

配置完必须在外部用 IPv6 网络验证一次。

数据库与缓存的绑定地址

MySQL:

[mysqld]
bind-address = *          # MySQL 8.0 支持 *,表示所有地址(含 IPv6)
# 老版本用 0.0.0.0;需要监听 IPv6 时可试 ::

Redis(6.0+):

bind * -::*               # 同时监听所有 IPv4 和 IPv6
sudo ss -lntp | grep -E '3306|6379'

关键认知:在 Linux 上,绑定 :: 时是否同时接受 IPv4 连接,取决于内核参数:

sysctl net.ipv6.bindv6only    # 0 = 监听 :: 时也接受 IPv4(IPv4 映射地址)

默认一般是 0,所以绑定 :: 通常能同时服务 v4 和 v6。但依赖这个默认值不安全(不同发行版和容器环境可能不同),最稳妥的还是显式确认实际监听情况。

安全提醒:数据库和 Redis 一旦监听 IPv6 公网地址,等于对全球开放。务必配合安全组限制来源、设置强密码、禁用默认账号。

Docker 的 IPv6 坑

Docker 默认不启用 IPv6,容器只有 IPv4。要启用需要改 /etc/docker/daemon.json:

{
  "ipv6": true,
  "fixed-cidr-v6": "2001:db8:1::/64",
  "ip6tables": true,
  "experimental": true
}
sudo systemctl restart docker

几个需要注意的坑:

坑一:默认桥接网络(docker0)不支持 IPv6。 即使用 daemon.json 开了 IPv6,也必须使用自定义 bridge 网络才能给容器分配 IPv6 地址:

docker network create --ipv6 --subnet 2001:db8:2::/64 mynet
docker run --network mynet -d nginx

坑二:前缀长度不够。 如果宿主机只拿到一个 /64,再切成更小的子网给 Docker 会导致 SLAAC 和某些实现出问题。理想情况是宿主机拿到 /56 或 /48,能给 Docker 分配独立的 /64。

坑三:转发要开。 容器访问外部 IPv6 需要开启转发:

sudo sysctl -w net.ipv6.conf.all.forwarding=1

坑四:ip6tables 规则会被 Docker 管理。 开了 ip6tables: true 后 Docker 会自己往 ip6tables 里加规则,手动加的规则可能被覆盖或顺序错乱。

风险提示:重启 Docker 会中断所有容器,务必在业务低峰操作。修改 daemon.json 前先备份原文件,配置错误导致 Docker 起不来时可以直接回滚。

五、暴露面扩大与防护

IPv6 最大的安全影响是:NAT 这道"天然防火墙"消失了。 IPv4 时代内网机器靠 NAT 隐藏,外部无法主动连接;IPv6 时代每台设备都有全球唯一地址,理论上端到端可达。

必须做的几件事:

第一,防火墙和安全组必须按 IPv6 完整配置一遍,不能只配 IPv4。 这是最重要的一条,前面已经强调过。

第二,不要把"地址空间大所以扫不到"当成安全。 确实,暴力扫描一个 /64 子网(2 的 64 次方个地址)不可行,但攻击者有其他手段:通过 DNS 记录、证书透明日志、邮件头、应用层日志拿到真实地址;在链路上通过组播和 NDP 枚举邻居;或者利用 EUI-64 地址里包含的 MAC 信息反推。

第三,服务器上建议关闭隐私扩展相关的临时地址(服务器需要稳定可访问的地址),同时避免对外服务使用 EUI-64 派生地址,减少信息泄露:

sudo sysctl -w net.ipv6.conf.eth0.use_tempaddr=0

第四,服务监听要精确。 数据库、Redis、管理后台只监听内网地址或 ::1,需要对外时才监听 :: 或 0.0.0.0,并配合来源限制。

第五,日志和监控系统要能处理 IPv6 地址。 检查你的 WAF、日志分析、封禁脚本是否支持解析和匹配 IPv6 格式(包括各种缩写形式),否则会出现"封禁规则对 IPv6 无效"的情况。

六、验证与排错

完整验证清单

# 1. 本机是否有 IPv6 地址
ip -6 addr show

# 2. 是否能 ping 通外部 IPv6
ping -6 -c 4 2400:3200::1

# 3. DNS 是否返回 AAAA
dig AAAA www.example.com +short

# 4. 服务是否监听 IPv6
sudo ss -lntp | grep 443

# 5. 防火墙是否放行
sudo ip6tables -L INPUT -n -v

# 6. 本机自测
curl -6 -sS -o /dev/null -w '%{http_code}\n' https://www.example.com/

# 7. 外部测试(用手机流量或另一台有 IPv6 的机器)
curl -6 -sS -o /dev/null -w '%{http_code}\n' https://www.example.com/

常见故障与原因

现象 原因
有 IPv6 地址但 ping 不通外部 安全组没放行 IPv6、路由缺失、或云平台未开启 IPv6
能 ping 通但网页打不开 防火墙拦了 ICMPv6 导致 PMTUD 失败,或 AAAA 记录指向错误地址
部分网站能开、部分卡住 典型的 PMTUD 问题,检查 ICMPv6 是否放行
网站变慢但能打开 AAAA 记录存在但 IPv6 不通,Happy Eyeballs 退回到 IPv4 产生额外延迟
服务监听了但外部连不上 安全组或 ip6tables 只配了 IPv4 那一套
容器拿不到 IPv6 Docker 未启用 IPv6,或使用了默认的 docker0 桥接

常见问题

我应该现在就启用 IPv6 吗?

如果是面向公众的网站或 App 后端,建议启用。移动端网络 IPv6 普及率已经很高,且部分审核要求支持 IPv6。如果是纯内部系统,可以暂缓,但要记着这件事。启用前确保能完整配置防火墙和安全组,否则不如不启用。

关掉 IPv6 有什么坏处?

短期内可能没感觉,但会造成:IPv6 用户访问时依赖 Happy Eyeballs 退回(多出延迟);错过 IPv6 用户;未来迁移成本更高。另外如果你的域名有 AAAA 记录而服务器禁用了 IPv6,会产生"能打开但慢"的隐性故障。

为什么有了 IPv6 还要保留 IPv4?

因为互联网上仍有大量纯 IPv4 的服务和用户。双栈是当前唯一现实的方案——同时支持两者,由客户端自己选择。纯 IPv6 环境需要 NAT64/DNS64 这类转换机制才能访问 IPv4 资源。

IPv6 还需要 NAT 吗?

设计上不需要,也不推荐。NAT66 存在但会重蹈 IPv4 的覆辙。正确的做法是用全球单播地址配合防火墙做访问控制——防火墙负责安全,地址转换不该承担安全职责。

为什么我配置了 IPv6 但访问变慢了?

大概率是 AAAA 记录指向的地址不通,或者防火墙拦了 ICMPv6 导致 PMTUD 失败。先用 curl -6 测试,再用 ping -6 -s 1400 测大包。如果短时间内无法修好,先把 AAAA 记录删掉,不要留半通不通的状态。

一个 /64 够用吗?

对单个子网来说,/64 是标准配置且绰绰有余(能容纳的地址数量是个天文数字)。要小心的是不要把它再切分成更小的前缀给 Docker 或其他用途,那样会破坏 SLAAC。需要多个子网时,向服务商申请 /56 或 /48。

总结

  • IPv4 的 32 位地址早已耗尽,NAT 只是延缓手段且破坏了端到端连接、引入端口耗尽和溯源困难;IPv6 的 128 位地址目标是让每个设备拥有全球唯一地址,回归端到端。
  • 记住几个关键地址:::1 回环、fe80::/10 链路本地、fc00::/7 唯一本地(不是私网,不该做 NAT)、2000::/3 全球单播;子网前缀标准是 /64,不要切分成更小的前缀,否则 SLAAC 会失效。
  • IPv6 的机制差异里最容易踩坑的三点:没有广播(用组播和 NDP 代替 ARP)、SLAAC 不下发 DNS(需要 RDNSS 或 DHCPv6 配合)、中间设备不分片(依赖 PMTUD,防火墙必须放行 ICMPv6,否则会出现"能 ping 通但网页打不开")。
  • 双栈配置要成配对做:DNS 加 AAAA 记录、Nginx 显式写 listen 443 和 listen [::]:443 两条、ip6tables 和云安全组都要单独配置。只配 IPv4 那一套等于 IPv6 端口裸奔。
  • 服务绑定地址要注意 net.ipv6.bindv6only 这个内核参数(默认为 0 时绑定 :: 会同时接受 IPv4),不要依赖默认值,用 ss -lntp 确认实际监听;数据库和 Redis 一旦监听 IPv6 公网地址就是全球可达,必须限制来源并设强密码。
  • Docker 的 IPv6 需要改 daemon.json 启用、必须使用自定义 bridge 网络(默认 docker0 不支持)、宿主机最好有 /56 或 /48 前缀、并开启 IPv6 转发;重启 Docker 会中断容器,操作前备份配置。
  • IPv6 让 NAT 这道天然屏障消失,暴露面实质扩大:"地址空间大扫不到"不是安全保障,要完整配置防火墙、避免使用 EUI-64 派生地址、并确认日志和封禁系统能处理 IPv6 格式。