IPv6路由src字段失效怎么办?四种清除方法与排查经验

发布时间:2026/10/20 21:11:42
IPv6路由src字段失效怎么办?四种清除方法与排查经验
做网络运维这几年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原地替换适合做脚本如果你不想让流量有哪怕一瞬的中断可以用replaceip -6 route replace default via fe80::1 dev eth0replace的语义和change不同它会用你给定的参数整体替换原条目未指定的属性恢复默认值。所以上面的命令执行完这条默认路由的src字段会被清掉而且路由表里始终存在对应的条目不会出现删除和添加之间的空窗期。需要提醒的是replace在路由不存在时会直接新建一条这通常没问题但如果你本意是必须改的是已有条目建议先ip -6 route show确认一下。脚本化场景下先 replace 后验证是标准操作。3.3 方法三针对RA动态路由的处理如果脏src来自 RA 自动学习光删不解决问题因为下一个 RA 周期又会给你装回来。这时候要从源头处理# 先关闭接口的 RA 接收 sysctl -w net.ipv6.conf.eth0.accept_ra0 # 删除动态学来的路由 ip -6 route del default via fe80::1 dev eth0 # 或者更暴力一点清空默认路由表谨慎使用会断开所有 IPv6 连接 ip -6 route flush table main清完之后你可以选择手动配静态路由也可以重新打开 accept_rasysctl -w net.ipv6.conf.eth0.accept_ra1重新打开后内核会重新学习 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_tempaddr0 sysctl -w net.ipv6.conf.eth0.use_tempaddr2这个方法的优势是不用打断路由表缺点是不可控你没法保证内核刷新路由的时机。所以我的建议是方法四只作为辅助手段真正要清除 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 mediumroute 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 mediumsrc消失了。继续用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] Destination2001:db8:1234::/64 # 不要写 PreferredSource或者写当前有效的那个地址用 NetworkManager 的话通过 nmcli 修改路由属性同样要注意ipv6.route列表里不要携带已经失效的src。很多人在迁移地址段时只改了地址不改路由配置网络一重启路由表里又重新冒出老的src就是因为这些配置还躺在网卡配置文件里。改地址的时候一定要顺手把静态路由里的 src 一起改了这句不是套话是我踩了不止一次后的真实教训。5.2 自动化检查让脏src无处遁形源地址这类问题平时不冒头一冒头就是生产事故。与其被动救火不如写个脚本定期检查。思路非常简单用ip -6 route get探测一个固定目标的选源结果判断src是否落在当前接口地址范围内。#!/bin/bash # 检测默认路由的 src 是否仍然有效 GW2001:db8:2::1 deveth0 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 多一层源地址选择的复杂度把这条链路彻底理解透很多看似诡异的问题都会变得有迹可循。