☰
IPv6路由src字段失效怎么办?四种清除方法与排查经验
2026/10/20 21:11:09 网站建设 项目流程

做网络运维这几年,IPv6的坑我踩了不少,最让我印象深刻的不是地址规划,也不是隧道配置,而是一个看起来不起眼的概念:路由条目里的src字段。简单说,IPv6 路由可以在条目里"钉死"一个优先源地址,正常情况下挺好用,但一旦这个源地址失效或被删除,路由表就成了带着脏数据的半残状态——数据包照发,对端看到的来源地址却不对,排查起来特别费劲。这篇文章我把"IPv6 路由源地址"的清除问题完整拆开讲,包括字段原理、脏数据产生场景、四种清除手法、一次完整排查复盘,以及清理之后必须注意的收尾事项,希望对搞 IPv6 网络的朋友有实际参考价值。

1. src字段的来龙去脉:为什么IPv6路由会绑定一个"源地址"

1.1 路由条目里src到底存了什么

先看一个典型的 Linux IPv6 路由表片段:

default via fe80::1 dev eth0 src 2001:db8:1::10 metric 1024 pref medium

末位的src 2001:db8:1::10就是优先源地址。它的含义很直白:凡是通过这条路由出去的流量,内核优先使用这个地址作为 IPv6 报文源地址。这个机制的好处是保证源地址可控。比如一台服务器上同时有业务地址和管理地址,你完全可以通过在路由里指定src,让某段流量固定走业务地址,日志审计也方便。

坏处同样明显:如果这个地址已经不存在了,或者已经进入 deprecated 状态,内核在选源时就可能进入异常逻辑。

需要注意的是,src并不是路由条目的必填项。不带src的条目长这样:

2001:db8:1234::/64 dev eth0 proto kernel metric 256 pref medium

这种条目没有钉死源地址,内核会按 RFC 6724 定义的流程自动选源。自动选源算法考虑了作用域、地址类型、退避状态等一堆规则,绝大多数情况下是靠谱的。问题恰恰出在"手动钉死"的部分——你越是想精确控制,越要警惕后续地址变化带来的联动效应。

1.2 和IPv4相比,为什么IPv6特别容易栽在这上面

IPv4 路由虽然也有src参数(ip route add ... src),但在日常运维中很少用到,因为一个接口上通常只有一个主 IPv4 地址,源地址没什么好纠结的。IPv6 完全不同:

  • 一个接口上可以同时存在多个全局单播地址;
  • 手动配置的地址、SLAAC 自动生成的地址、DHCPv6 获取的地址可能并存;
  • 临时隐私地址(RFC 8981)还会周期性轮换,旧的被废弃、新的自动生成。

这种多地址并存的局面,让"源地址选择"成了 IPv6 运维里绕不开的话题。很多老手为了避免源地址飘来飘去,习惯在路由里显式写src。这个习惯本身没错,但它埋下了一颗雷:地址是有生命周期的,删除一个地址时,内核并不会同步清理所有引用它的路由条目。于是路由表里就出现了引用空地址的"僵尸 src"。

换句话说,IPv6 的源地址管理是动态的,而路由条目里的 src 是静态快照。两者一旦脱节,脏数据就产生了。

2. 哪些情况下路由里的src会变成"脏数据"

2.1 地址被删了但路由没跟着清理

这是最高频的场景。举个例子,你原来给 eth0 配了2001:db8:1::10/64,并手动加了一条默认路由:

ip -6 addr add 2001:db8:1::10/64 dev eth0 ip -6 route add default via fe80::1 dev eth0 src 2001:db8:1::10

后来因为地址段重新规划,你执行了:

ip -6 addr del 2001:db8:1::10/64 dev eth0

此时接口上已经没有2001:db8:1::10了,但那条默认路由的src 2001:db8:1::10还在。内核发流量时会试图使用一个不存在的地址作为源。不同内核版本的表现并不一致:有些会直接选一个替代地址,有些则会在连接建立时报错,表现就是"能通又不完全通"。

类似的场景还有 Docker 网桥、Kubernetes 节点、虚拟机热迁移之后,网卡被重建,地址被替换,路由表里的老src却没跟着刷新。迁移后第一反应通常是通不通,很少有人先去看路由表里是不是已经有脏字段。

2.2 RA自动学习出的路由带着过时源地址

第二个高频场景来自 SLAAC。现在的发行版默认在物理网卡上开启了接收路由器通告(accept_ra)。路由器发 RA 时,内核会动态学习路由。这个过程中,内核给新路由填写的src是从接口当前已有地址里选出来的。

问题在于:IPv6 临时地址是变化的。假设某台机器开机的瞬间,接口上第一个全局地址是临时隐私地址2001:db8:1::abcd:1234,内核从 RA 里学到了默认路由,并把src钉成这个临时地址。过了一段时间,临时地址过期被自动废弃,新临时地址2001:db8:1::abcd:9999生成,但 RA 学到的路由条目并不会立刻更新src。

于是你看到的现象就是:机器能拿到新地址,也能 ping 通外网网关,但出去的流量源地址还是那个已经废弃的老地址。在老地址进入 deprecated 状态后,内核尝试以它为源建立新连接时会被拒绝,表现非常诡异。

2.3 多地址和临时地址导致的"源打架"

还有一种情况不在路由表本身,而在地址生命周期。假设接口上有两个全局地址:

地址状态用途
2001:db8:1::10preferred固定业务地址
2001:db8:1::abcd:1234deprecated隐私地址,已轮换

如果某条路由的src指向的是 deprecated 的地址,内核源地址选择时会有意避开它,但你路由里显式钉了它,规则冲突就会导致连接不稳定。这时候"清除路由源地址"的实际操作,要么是把路由里的src改到 preferred 地址,要么干脆删掉src让内核重新按 RFC 6724 做选择。

这种"源打架"在纯 IPv4 环境里几乎不存在,但在 IPv6 环境里每周都可能遇上一次。排查这类问题,核心思路就一条:先看路由表里的 src,再看地址状态,两者对不上就是根源。

3. 清除路由源地址的实操方法:四条路子按需选

3.1 方法一:删了重建,最直接

这是最彻底、也最不容易出意外的做法。先删掉带脏src的路由,再不带src重新添加:

# 删除指定路由 ip -6 route del default via fe80::1 dev eth0 # 重新添加,不指定 src ip -6 route add default via fe80::1 dev eth0 # 如果是前缀路由,同理 ip -6 route del 2001:db8:1234::/64 dev eth0 ip -6 route add 2001:db8:1234::/64 dev eth0

这里有个细节值得说明:ip -6 route change在修改已有路由时,未指定的属性默认保留原值。也就是说你执行ip -6 route change default via fe80::1 dev eth0,很可能发现src还在。这不是 bug,是 change 的语义就是"改我指定的、留我没提的"。所以想彻底去掉src,删了重建是最可靠的,永远不要指望 change 帮你抹掉字段。

3.2 方法二:replace原地替换,适合做脚本

如果你不想让流量有哪怕一瞬的中断,可以用replace:

ip -6 route replace default via fe80::1 dev eth0

replace的语义和change不同:它会用你给定的参数整体替换原条目,未指定的属性恢复默认值。所以上面的命令执行完,这条默认路由的src字段会被清掉,而且路由表里始终存在对应的条目,不会出现删除和添加之间的空窗期。

需要提醒的是,replace在路由不存在时会直接新建一条,这通常没问题,但如果你本意是"必须改的是已有条目",建议先ip -6 route show确认一下。脚本化场景下,先 replace 后验证是标准操作。

3.3 方法三:针对RA动态路由的处理

如果脏src来自 RA 自动学习,光删不解决问题,因为下一个 RA 周期又会给你装回来。这时候要从源头处理:

# 先关闭接口的 RA 接收 sysctl -w net.ipv6.conf.eth0.accept_ra=0 # 删除动态学来的路由 ip -6 route del default via fe80::1 dev eth0 # 或者更暴力一点,清空默认路由表(谨慎使用,会断开所有 IPv6 连接) ip -6 route flush table main

清完之后,你可以选择手动配静态路由,也可以重新打开 accept_ra:

sysctl -w net.ipv6.conf.eth0.accept_ra=1

重新打开后,内核会重新学习 RA,并用当前有效的接口地址填充src。实测下来,这个办法在路由器刚改完 RA 参数、或者接口地址整体换代时特别管用。注意 flush 操作在远程机器上要格外小心,先确认你有别的通道能连上去,不然清完默认路由,SSH 会话可能直接断。

3.4 方法四:调整地址状态让src自动失效

还有一种思路是反其道而行之:不直接动路由,而是让地址状态恢复。比如由于隐私地址轮换导致src指向了 deprecated 地址,你可以重新生成一个 preferred 地址,再更新路由里的src:

# 查看当前地址状态 ip -6 addr show eth0 # 删除废弃地址 ip -6 addr del 2001:db8:1::abcd:1234/64 dev eth0 # 想让内核自动刷新,可临时关闭再开启临时地址机制 sysctl -w net.ipv6.conf.eth0.use_tempaddr=0 sysctl -w net.ipv6.conf.eth0.use_tempaddr=2

这个方法的优势是不用打断路由表,缺点是不可控:你没法保证内核刷新路由的时机。所以我的建议是,方法四只作为辅助手段,真正要清除 src,还是回到方法一或方法二。

四种方法选哪个,取决于你对"中断"的容忍度和src的来源:

方法适用场景优点风险
删了重建静态路由带脏 src最彻底,可控有瞬间空窗期
replace 替换脚本化、在线调整无空窗期条目不存在时会新建
处理 RA 路由RA 动态学习导致的脏 src从源头解决需要动 sysctl,影响范围大
调整地址状态地址 deprecated 导致选源异常不打断路由时机不可控,不彻底

4. 一次完整排查复盘:从"能ping通但源地址不对"开始

4.1 问题现象与初始判断

场景还原一下:一套多节点服务集群,某节点 A 需要访问另一个网段的节点 B。现象是 A 能 ping 通 B 的 IPv6 地址,但 B 上收到的 ICMPv6 ping 报文源地址不是 A 当前配的2001:db8:1::20,而是一个已经不存在的老地址2001:db8:1::10。B 上的防火墙策略是针对新地址段放的,老地址段早已停用,于是回包被拦,表现为"能 ping 通但连接建立不起来"。

这种"通一半"的问题最容易被误判为防火墙、路由环路、MTU 问题。我当时的判断顺序是:先确认物理连通性,再检查防火墙,最后才去看路由表。后来发现,前两步都是白费功夫,真正的根因就藏在路由表里。

4.2 逐步定位到src的完整链路

第一步,看接口地址:

$ ip -6 addr show eth0 inet6 2001:db8:1::20/64 scope global dynamic

接口上只有2001:db8:1::20,老的2001:db8:1::10已经不在了。

第二步,看路由表:

$ ip -6 route show default via fe80::1 dev eth0 src 2001:db8:1::10 metric 1024 pref medium

能明显看到默认路由的src指向老地址,问题基本锁定了。这里有个小技巧:不要只看ip -6 route show,一定要配合ip -6 route get看内核实际选源结果——

$ ip -6 route get 2001:db8:2::50 2001:db8:2::50 from :: via fe80::1 dev eth0 src 2001:db8:1::10 metric 1024 pref medium

route get是排查源地址问题的神器。它直接告诉你:如果现在要发一个到2001:db8:2::50的包,内核会用什么源地址。看到src 2001:db8:1::10的那一刻,问题就不再是猜谜,而是变成了"怎么改掉它"。

第三步,为了确认影响范围,再用抓包验证一下实际发出的报文:

tcpdump -i eth0 -nn icmp6

抓包结果里,出去的 ping 请求源地址确实是2001:db8:1::10,与分析一致。

4.3 清除后的验证与抓包确认

定位完成后,我用 replace 一步处理:

ip -6 route replace default via fe80::1 dev eth0

再确认:

$ ip -6 route show default via fe80::1 dev eth0 metric 1024 pref medium

src消失了。继续用route get验证选源结果:

$ ip -6 route get 2001:db8:2::50 2001:db8:2::50 from :: via fe80::1 dev eth0 src 2001:db8:1::20 metric 1024 pref medium

源地址已经变成了当前有效的2001:db8:1::20。再执行 ping 和业务连通性测试,一切正常。整个过程从最开始看到现象到定位到src,大概花了四十分钟,其中大部分时间浪费在怀疑防火墙上。如果一开始就查路由表的src,十分钟内就能解决。

5. 清理之后的收尾经验:持久化、自动化与避坑提醒

5.1 路由配置的持久化方案

手动执行ip -6 route命令只对当前运行态生效,重启后一切回到默认。要持久化,必须把路由配置写进系统网络管理配置里。

用 systemd-networkd 的话,在网卡配置里写:

[Route] Destination=2001:db8:1234::/64 # 不要写 PreferredSource=,或者写当前有效的那个地址

用 NetworkManager 的话,通过 nmcli 修改路由属性,同样要注意ipv6.route列表里不要携带已经失效的src。

很多人在迁移地址段时,只改了地址不改路由配置,网络一重启,路由表里又重新冒出老的src,就是因为这些配置还躺在网卡配置文件里。改地址的时候,一定要顺手把静态路由里的 src 一起改了,这句不是套话,是我踩了不止一次后的真实教训。

5.2 自动化检查:让"脏src"无处遁形

源地址这类问题平时不冒头,一冒头就是生产事故。与其被动救火,不如写个脚本定期检查。思路非常简单:用ip -6 route get探测一个固定目标的选源结果,判断src是否落在当前接口地址范围内。

#!/bin/bash # 检测默认路由的 src 是否仍然有效 GW="2001:db8:2::1" dev="eth0" expected=$(ip -6 addr show dev "$dev" | awk '/inet6/ && /global/ {print $2}' | cut -d/ -f1) got=$(ip -6 route get "$GW" | sed -n 's/.*src \([0-9a-fA-F:]*\).*/\1/p') if ! echo "$expected" | grep -q "$got"; then echo "[WARN] route src $got is stale, fixing..." ip -6 route replace default via fe80::1 dev "$dev" fi

把这个脚本丢进 cron 里每五分钟跑一次,源地址漂移问题基本能在几分钟内被自动纠正。我在一个长期运营的节点上挂了类似脚本,半年内自动修复了三次,每次都是临时地址轮换导致的脏src。

5.3 三个容易忽略的坑

第一,清理 src 不等于清理整个路由表。远程操作时千万不要手滑执行ip -6 route flush table main,那会把所有 IPv6 默认路由和业务路由全部清掉,SSH 会话可能瞬间断开。真要 flush,也要精确指定路由特征,或者先加一条兜底路由再 flush。

第二,RA 自动路由会"复活"。如果你只是删掉了 RA 学来的那条路由,而没有动 accept_ra 参数,下一次 RA 通告到达时,路由会被重新创建,src又变回老样子。所以对 RA 场景,一定要在 sysctl 层面处理,而不是只删路由。

第三,对端设备可能有基于源地址的 ACL。你清理了src之后,内核自动选出的新源地址,未必是对端白名单里的地址。如果清理完发现连接还是不通,先检查对端是否只允许特定前缀的源地址,必要时要把正确的src显式写回路由里——注意,是写当前有效的地址,而不是直接清掉。

最后再分享一个小技巧:处理这类问题,养成"先ip -6 route get再ip -6 route show"的习惯。前者告诉你内核当前会怎么选,后者告诉你路由表里写了什么,两者一对照,脏src立刻现形。IPv6 网络本来就比 IPv4 多一层源地址选择的复杂度,把这条链路彻底理解透,很多看似诡异的问题都会变得有迹可循。

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

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

立即咨询