☰
IPv6路由源地址失效排查与清理实战
2026/10/20 4:08:03 网站建设 项目流程

上个月某台测试服务器出了个特别刁钻的问题:IPv6地址都在、路由也在,从外面访问都正常,但服务器主动对外发起连接却总是超时。查了大半天,最后定位到路由表里default路由的src字段还挂着一个早已被删掉的 IPv6 地址。这种“路由源地址失效”的问题在 IPv6 网络里特别隐蔽,是很多“路由在、地址在、就是连不通”故障的常见元凶。这篇文章就用我实际排查和清理 IPv6 路由源地址的经验,从原理到命令完整梳理一遍,遇到类似问题的朋友可以直接照抄。

1. 先搞清楚:路由里为什么要存一个源地址

1.1 IPv6 天然是“多地址”的,源地址必须选

先聊点底层逻辑。IPv4 时代,一个网卡上通常只有一个 IP,发出数据包时用哪个源地址几乎没有悬念。但到了 IPv6,情况彻底变了。一台 Linux 服务器的同一块网卡上,几乎一定会同时存在多个地址:以fe80::开头的链路本地地址、全球单播地址、临时隐私地址(RFC 4941 生成的 Temporary Address),甚至还有 ULA(唯一本地地址)和各类自定义地址。

多个地址同时存在,内核发数据包时就面临一个问题:连接对端是某个具体的 IPv6 地址,那本机该用哪一个地址当作源地址?这个选择不是随便来的,内核要遵循一整套优先级规则,也就是 RFC 6724(更早是 RFC 3484)定义的源地址选择算法:比对地址作用域、匹配目标前缀长度、优先级高低、是否临时地址、标签(Label)等等,最终挑选一个“最合适”的地址出来。

但这里有个更优先级的“捷径”——路由表里的src字段。只要某条 IPv6 路由明确写了src,内核就会优先使用它指定的地址作为源地址,RFC 6724 那套复杂的算法都要往后靠。这个设计本意是好的:当你希望“访问某网段时固定用哪个地址”,直接在路由上指定即可。但它也埋下了隐患:src指向的地址一旦失效,这条路由就成了“带病上岗”。

1.2 一个典型的带 src 的 IPv6 路由长什么样

执行ip -6 route show,你大概率会看到类似这样的输出:

$ ip -6 route show 2001:db8:10::/64 dev eth0 proto kernel metric 256 pref medium default via 2001:db8:10::1 dev eth0 proto ra metric 100 src 2001:db8:10::3 pref medium

第二行就是重点关注对象。proto ra表示这条默认路由是通过路由通告(Router Advertisement,简称 RA)自动生成的;src 2001:db8:10::3是内核在生成路由时自动挑选的一个源地址。它一般会挑选与接口前缀匹配、作用域合适的全球地址,并且会优先选非临时地址、非 deprecated 状态的地址。

问题往往出在“自动挑选”这个环节之后的地址变更。比如管理员删除了这个地址、地址租约到期后状态变成 deprecated、或者运维操作把地址从 A 换成 B,但路由条目本身还留在表里,src仍然指向那个旧地址。这就形成了一个“死引用”。这时候,凡是命中这条路由的新连接,发包时都会尝试使用这个已经不可用的源地址,结果就是连接要么超时、要么无法建立。

2. 清除源地址之前,先把症状认出来

2.1 失效源地址会带来哪些实际表现

源地址失效的表现和普通网络故障不太一样,很容易让人误判。我整理了一下最常见的几种症状,如果你遇到了类似的,可以优先往路由源地址方向排查。

第一个典型症状:主动出方向的连接卡死,但被动访问完全正常。因为路由src只影响本机发出的数据包,从外面主动连进来的包不经过这段逻辑。很多服务器表现为“外面能 ping 通、SSH 能进来,但服务器自己访问外部 DNS、NTP、软件仓库全都超时”。

第二个典型症状:连接有时通有时不通,且表现不一致。比如使用临时地址做主地址的节点,在临时地址轮换后,某些路由还引用旧的临时地址,这时候新连接会偶尔失败,但重试几次或者等内核重新选择合适的地址后又可以。这种“间歇抽风”最让人头疼。

第三个典型症状:对端看到异常源地址。比如你本机明明有 A 和 B 两个地址,路由src绑定了失效的 B,结果对端看到源地址可能是一个不复存在的地址,或者根本不是你想让它看到的那个地址。这在多网卡、多出口的场景下特别容易引发诡异的安全策略问题。

2.2 我亲手踩过的一个实际案例

当时场景是这样的:某台服务器(我称它为测试机)上 eth0 有一个正式地址2001:db8:10::2,同时还有一堆用于下班后测试的临时地址。某天做安全加固,我把几个已经过期的临时地址用ip -6 addr del清理掉了,当时还特意看了眼路由,默认路由还好好的。结果第二天发现服务器对外发起 HTTPS 请求全部超时,但 SSH 登录、外部扫描都正常。

一开始怀疑是防火墙或者上游路由器的问题,排查了一轮没发现异常。后来随手敲了下ip -6 route show,才看到默认路由上赫然写着src 2001:db8:10::dead——这个地址正是我前一天清理掉的临时地址之一。内核在地址被删除后,并没有自动把路由里的src同步更新掉,旧地址被“遗弃”在路由表里,导致出站连接全部提取到一个不存在于网卡上的幽灵地址,自然发包就失败了。

这个案例让我彻底明白了:IPv6 地址清理和路由检查必须同步进行,不能只清一半。

2.3 如何快速确认“死引用”是否存在

判断起来其实不复杂,就两步对拍:

# 第一步:看路由表里有哪些 src 地址 ip -6 route show | grep src # 第二步:看网卡实际有哪些 IPv6 地址 ip -6 addr show

把第一步输出的所有src地址,和第二步输出的地址集合做比对。如果某个src地址根本不在网卡上,基本就能实锤是“失效源地址”了。更直观一点,可以直接用ip -6 route get测试:

$ ip -6 route get 2001:db8:20::1 2001:db8:20::1 via 2001:db8:10::1 dev eth0 src 2001:db8:10::dead metric 100 pref medium

看到src后面跟一个不存在的地址,那问题就再明显不过了。

3. 实操:清除 IPv6 路由源地址的几种方式

3.1 操作前的准备工作

先说一个非常非常重要的经验:在动路由表之前,一定要先备份,并保证有一条可靠的远程通道。这里的通道可以是物理带外(比如 IPMI、串口),也可以是主机商提供的 VNC 控制台。为什么强调这个?因为清错路由源地址、删错路由,极有可能导致你已经建立的 SSH 会话中断,甚至是整台服务器彻底失联——IPv6 环境里终端连接时如果靠策略路由选的链路,漏改一个字段就断网了。

备份命令很简单:

# 备份路由表 ip -6 route show > /tmp/route6_backup.txt # 备份当前地址分配 ip -6 addr show > /tmp/addr6_backup.txt

备份完,再确认一下当前正在使用的网络连接是否稳定。如果是在生产环境,强烈建议先把操作命令在测试机上跑一遍,确认没有语法问题再上。

3.2 方式一:改写路由,去掉 src 字段

这是最直接、影响最小的一种方式。用ip -6 route change修改现有路由,不带 src 参数,内核就会丢remove掉原有 src 绑定,让源地址重新回到 RFC 6724 的自动选择流程。

ip -6 route change default via 2001:db8:10::1 dev eth0

执行后再次查看:

$ ip -6 route show default via 2001:db8:10::1 dev eth0 proto ra metric 100 pref medium

src字段消失了,等于清除了路由级源地址约束。之后内核发往该默认路由的数据包,会重新按照 RFC 6724 规则选择最合适的源地址。

这里有个细节容易踩坑:如果你要改的这条路由带metric,最好把metric一并写上,否则命令可能会因为匹配问题报“RTNETLINK answers: No such file or directory”。比如原路由是metric 100,命令写成:

ip -6 route change default via 2001:db8:10::1 dev eth0 metric 100

这样更稳。

3.3 方式二:删除路由再重建

如果change没有生效,或者你想彻底重建一条干净的路由,可以采用“先删后加”的方式:

# 删除旧路由 ip -6 route del default via 2001:db8:10::1 dev eth0 # 添加新路由,不带 src ip -6 route add default via 2001:db8:10::1 dev eth0

如果你并不想完全清除源地址,而是想重新绑定到一个确实存在的地址上,那就显式带上src:

ip -6 route add default via 2001:db8:10::1 dev eth0 src 2001:db8:10::2

注意,这种“先删后加”是有极小时间窗口的,路由会短暂缺失。在高并发、对抖动敏感的场景,尽量别这么做,优先用change/replace。另外,在proto ra的默认路由上,你刚删完,路由器下一跳的 RA 通告一旦到达,内核可能立刻又自动生成一条带新 src 的默认路由。也就是“清完又自动回来”,这种情况我后面第四节会讲。

3.4 方式三:删掉多余地址,再清理引用它的路由

如果源地址失效是因为地址本身不再需要,那更彻底的办法是:先把地址清掉,再同步清掉引用它的路由。顺序很重要:

# 第一步:删除失效地址 ip -6 addr del 2001:db8:10::dead/64 dev eth0 # 第二步:检查是否存在仍引用它的路由,如果有就 rewrite ip -6 route show | grep 2001:db8:10::dead # 第三步:flush 一下路由缓存,确保旧缓存回落 ip -6 route flush cache

为什么第三步要flush cache?因为 Linux 内核里有些路由缓存(尤其是早期版本)哪怕你改了路由,旧连接或者旧的缓存条目可能还在短时间内生效。刷新一下缓存,能让变更立刻生效,避免“改了但没感觉”的尴尬。

3.5 批量自查的小脚本:扫描失效 src

真实环境不可能只有一条路由。我后来写过一个简单的 bash 脚本,扫描所有 IPv6 路由条目,找出引用了不存在地址的src,并自动重建路由。脚本思路很朴素,分享给大家:

#!/bin/bash # 对比路由表中 src 地址与网卡实际地址,找出死引用 while IFS= read -r route; do src=$(echo "$route" | grep -oP 'src \K[^ ]+' || true) [ -z "$src" ] && continue if ! ip -6 addr show | grep -q "$src"; then echo "[发现异常] 路由: $route" prefix=$(echo "$route" | awk '{print $1}') via=$(echo "$route" | grep -oP 'via \K[^ ]+' || true) dev=$(echo "$route" | grep -oP 'dev \K[^ ]+' || true) metric=$(echo "$route" | grep -oP 'metric \K[0-9]+' || true) cmd=(ip -6 route replace "$prefix") [ -n "$via" ] && cmd+=(via "$via") [ -n "$dev" ] && cmd+=(dev "$dev") [ -n "$metric" ] && cmd+=(metric "$metric") echo "修正命令: ${cmd[*]}" # 实际执行前建议先打开注释,谨慎生产环境自动执行 # "${cmd[@]}" fi done < <(ip -6 route show)

脚本输出的每一条修正命令,本质上都是“重建路由、去掉 src”。生产环境不建议直接删除注释符自动执行,先看输出结果,确认每一段都能还原路由,再手动执行就好。

3.6 修完之后的验证流程

清理完源地址,不要看一眼路由表就收工,至少要走一轮业务验证:

# 1. 确认路由已经没有 src,或者 src 指向真实存在的地址 ip -6 route show # 2. 确认网卡地址与路由 src 没有死引用 ip -6 route get 2001:db8:20::1 # 3. 实测出站连接 ping6 -c 4 2001:db8:20::1 curl -6 -I https://example.test # 替换成你自己的测试地址

如果 ping6 能通、curl 能返回,基本就 OK 了。如果 ping 通但业务还不行,那就要看是不是防火墙、SELinux 或者应用配置的问题了,可以顺着业务路径继续排查。

4. 刚清理完又复发?根源和持久化对策

4.1 为什么 src 会被反复自动绑定

不少朋友会遇到这种情况:手动清了src,没过一会儿再看,路由上又出现了一个新地址。这通常不是你清得不够彻底,而是有“自动机制”在帮忙还原。

最大的“元凶”就是 RA(Router Advertisement)。路由器会周期性发送 RA 通告,比如默认每隔几十秒到几分钟一次。内核接收 RA 后,如果某个接口具备自动配置能力(autoconf),就会重新学习默认路由,并自动挑选一个当前接口地址作为src写进去。于是你手动清掉一次,路由器下一条 RA 一到达,内核又给配回来了。

还有一类情况是临时地址轮换。启用了use_tempaddr=2时,接口会周期性生成新临时地址,并淘汰旧的临时地址。在轮换瞬间,内核可能给某条路由绑定了当前最新的临时地址,过段时间又变了,导致新连接源地址不断变化。如果你的业务对源地址有强依赖,这会引发另一类问题。

4.2 持久化配置层面的规避办法

如果你希望彻底摆脱“来源不明的 src”,直接的做法是从配置层面让路由固定化,而不是每次启动后靠 ip 命令手工调。

在使用 NetworkManager 的发行版上,可以给连接配置设置静态 IPv6 地址和默认路由,并尽量避免纯 SLAAC 自动配置。比如在/etc/sysconfig/network-scripts/ifcfg-eth0(RHEL/CentOS 系)里:

IPV6INIT=yes IPV6ADDR=2001:db8:10::2/64 IPV6_DEFAULTGW=2001:db8:10::1 IPV6_DEFAULTGW_SRC=2001:db8:10::2

这里IPV6_DEFAULTGW_SRC会强制默认路由绑定到2001:db8:10::2,只要这个地址不过期、不被删,路由就不会乱变。

如果是 Debian/Ubuntu 的/etc/network/interfaces,可以这样写:

iface eth0 inet6 static address 2001:db8:10::2/64 gateway 2001:db8:10::1 # 增加一行钩子:网卡起来后强制替换默认路由,去掉/修正 src up ip -6 route replace default via 2001:db8:10::1 dev eth0 src 2001:db8:10::2 down ip -6 route flush cache

用钩子脚本的好处是,无论系统怎么重启、网络怎么重启,只要 eth0 起来,路由都会按照脚本里写好的src重新绑一遍,不会被 RA 或者系统默认逻辑覆盖掉。

4.3 内核 sysctl 参数的配合调整

如果确实不需要某个接口自动接收 RA、自动生成默认路由,可以关掉对应接口的 RA 接收和自动配置。这里给一个常用的 sysctl 配置示例:

net.ipv6.conf.all.accept_ra = 0 net.ipv6.conf.eth0.accept_ra = 0 net.ipv6.conf.eth0.autoconf = 0 net.ipv6.conf.eth0.use_tempaddr = 0

解释一下三个参数的作用:

  • accept_ra = 0:该接口不再接收 RA 通告,内核也就不会因为 RA 而自动添加默认路由,自然不会再自动往路由里写src。
  • autoconf = 0:关闭无状态地址自动配置,防止从 RA 前缀自动生成新的 IPv6 地址。
  • use_tempaddr = 0:禁用临时隐私地址,避免地址频繁轮换导致路由绑定的src一直变化。

改完记得持久化到/etc/sysctl.conf或/etc/sysctl.d/目录下,用sysctl -p或者重启验证。但要提醒一句:关掉自动配置后,该接口就无法通过 SLAAC 自动获取地址了,纯 IPv6 的裸机环境改用静态或 DHCPv6 地址更合适。

4.4 顺带聊聊“源地址选择策略表”的差别

有时候我们会把“路由 src”和“源地址选择策略规则”混为一谈。其实它们是两个东西:

  • 路由表里的src:只对命中这条路由的数据包生效,是最直接的源地址约束。
  • 策略路由(ip -6 rule):通过 rule 里的from、to、fwmark等条件,让特定数据包走特定路由表,从而间接影响源地址。比如ip -6 rule add from 2001:db8:20::2 table 100,就是把来自某地址的流量强制甩到另一张路由表。

如果你的需求是“不同来源的流量用不同出口、不同源地址”,那就得用策略路由而不是单纯改src。但如果只是解决“路由 src 指向失效地址”的问题,用本文第三节的方法就够了,别把两套机制混在一起改,容易乱。

5. 排查清单和实操避坑记录

5.1 高频问题速查表

症状可能原因排查命令解决办法
出站连接全部超时,入站正常默认路由 src 指向失效地址ip -6 route show、ip -6 route getip -6 route change/replace去掉 src,或绑定有效地址
连接间歇性失败临时地址轮换,路由 src 还引用旧临时地址ip -6 addr show看 deprecated关掉use_tempaddr,或把 src 固定为正式地址
清理地址后路由仍带旧 src内核未自动同步路由的 srcip -6 route show | grep src手动 change/rewrite 路由,并ip -6 route flush cache
改完路由,几秒后 src 又出现RA 自动重建默认路由ip -6 route show、tcpdump -i eth0 icmp6关闭accept_ra、autoconf,或写网络钩子固定路由
多网卡环境源地址不对路由绑定了其他接口的地址ip -6 rule show、ip -6 route show table all指定正确 dev/接口,或使用策略路由分离流量

5.2 我在实操中踩过的几个坑

第一个坑是change命令报错。用ip -6 route change default via ... dev eth0去改一条带 metric 的默认路由时,如果命令里没带 metric,有时候内核匹配不到那条路由,直接报 “RTNETLINK answers: No such file or directory”。原因在于不少发行版生成的默认路由自带metric,而change匹配时比较严格。解决办法很简单:改的时候把 metric 也带上去,或者看一眼原路由的属性原样填写。

第二个坑是删地址时把正在使用的源地址也删了。有次清理临时地址,我以为某地址是多余的,执行ip -6 addr del后才发现 SSH 会话就是靠这个地址维持的,结果断线了。从那以后我养成了习惯:在动 IPv6 地址之前,先确认当前会话的源地址是哪一个,用ip route get看一眼,避免误删。

第三个坑是重复执行“先删后加”导致路由抖动。在高并发环境里,del和add之间的空档虽然只有几百毫秒,但已经有业务方报短暂超时了。从此能改就直接改,不能改才用“先删后加”,并且尽量选在业务低峰期做。

第四个坑是改完路由后没flush cache。我有一次改完src,用ping6测一直还是走旧源地址,一度以为命令没生效。后来一查才知道是路由缓存里还留着旧条目。执行ip -6 route flush cache后立刻就好了。这可能和具体内核版本有关,但多一句命令没坏处。

5.3 避免再次踩坑的几个预防措施

现在我的所有变更操作都会强制叠加这四条规则:

  1. 改 IPv6 地址前必看ip -6 route get <对端地址>,确认当前会话来源。
  2. 删地址后必查ip -6 route show | grep src,确认没有死引用。
  3. 日常巡检脚本定期对比ip -6 addr show与ip -6 route show中的 src 集合,发现异常直接告警。
  4. 生产环境的默认路由尽量在配置文件中显式固定src,不给 RA 自动绑定的机会。

坚持这几个习惯之后,IPv6 路由源地址引起的“幽灵连接”问题基本绝迹了。这个领域里踩过的坑不少,但这套处理思路足够我搞定 90% 以上的场景。如果你在清理时遇到什么奇怪的现象,比如改完还无法连接、src 地址显示::之类,可以顺着路由表、地址状态、RA 通告和 sysctl 四个方向交叉排查,大概率能找到答案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询