前阵子排一个IPv6跨站点不通的问题,接口地址配得好好的,默认路由也在,curl就是出不去。抓包才发现,出去的包居然带着一个已经不存在的源地址,而这个源地址的来路,竟然是路由表项里残留的 src 属性。从那以后,我对“IPv6清除路由源地址”这类操作格外上心,把Linux下IPv6源地址选择、路由表修改、缓存刷新以及自动化脚本的事一并整理了出来,这一篇就当是给同行留的一份排查笔记。网上讨论IPv6的文章不少,但大多停在“怎么配地址、怎么加默认路由”这一步,很少有人讲路由表里那个 src 字段出了问题该怎么收拾。真实环境里,这个不起眼的字段恰恰容易埋雷:它会默默覆盖内核的源地址选择逻辑,一旦指向一个失效的地址,整个出站方向可能全废。这篇内容适合正在做IPv6网络改造、服务器双栈上线的同学,也适合那些被“地址选错”问题折磨过的运维和网络工程师。1. 为什么非要清除“路由源地址”:一个普通属性带来的连锁故障1.1 路由表里的 src 到底是什么IPv6路由表里,每条路由除了目标网段、网关、出接口之外,还有一个不常被注意的属性叫 src,全称是 preferred source。它的含义很直接:当发往这个目标网段的数据包需要选择源地址时,内核会优先使用这个指定的IPv6地址,而不是自行计算。举个例子。服务器上有两个IPv6地址,分别是2001:db8:100::2/64和2001:db8:200::2/64,属于同一个物理接口。默认情况下,发往2001:db8:200::5的数据包,内核通过路由查表找到对应的路由后,会自己选一个合适的源地址。但如果这条路由上写了 src 2001:db8:100::2,那无论数据包从哪个方向出去,源地址都会被锁死成2001:db8:100::2。这个机制在正经场景里是好事。比如多网卡服务器,回程流量必须从某个固定地址出去,以便对端防火墙只放行这一个地址。但问题也恰恰出在这里——当这个 src 指向的地址已经不存在、被删掉、或者因为SLAAC变更换了新地址时,路由表里那个值就成了一段“僵尸配置”。1.2 源地址选错引发的典型问题我在实际环境里遇到的情况大致分三类。第一类是出站连接直接失败。发往目标的报文,源地址写了一个当前接口不存在的地址。对端收到后,回程报文无法正确路由回发送方,连接自然建立不起来。表现就是ping通邻居、ping不通远端,路由表看起来又毫无问题。第二类是连接能建立,但行为诡异。比如某些服务会基于源地址做访问控制,源地址和预期不符,请求被后端拒绝,但重试几次偶尔又能通过——因为如果系统开了隐私扩展,临时地址会变化,运气好时碰上一个“合法”地址就通了。这种“时好时坏”的问题最耗时间,因为它不具备可复现性。第三类是路由策略被src干扰。配置了策略路由的环境里,路由表项之间的优先级、匹配顺序都比较敏感。某个路由表项里带了一个错误的src,可能让原本走特定出口的流量被“拽”到另一个地址上,整条链路的路由意图全部跑偏。1.3 什么时候才需要主动清除搞清楚“为什么清”之前,得先知道“什么情况算该清”。根据我的经验,下面这些场景基本跑不掉:某张网卡的物理地址或虚拟地址被移除,但路由表里还残留着指向该地址的 src。使用SLAAC自动配置地址时,系统自动生成的路由带了 src,但后来临时地址过期或更换了,旧值还留在表里。手动在路由上配置了错误的,后面要回退到内核自动选择源地址。做了一堆实验后,路由表里堆积了各种带src的临时路由,想要恢复一个干净的状态。这类问题有个共同点:你无法通过单纯的“重启网卡”解决。重启网卡只会重新生成地址,路由表里明确指定的 src 属性不会跟着消失,重启网络服务也未必能覆盖掉所有手动配置的路由。2. 动手前先摸清机制:IPv6源地址选择到底听谁的2.1 从 RFC 3484 到策略表IPv6源地址选择有一套标准算法,定义在RFC 3484里。Linux在实现时把它演化成了一套规则,大致顺序是:优先匹配出接口上同一网段的地址,再看路由表项中的 src 属性,然后比较地址的作用域、生命周期是否有效等等。关键点在于,路由表项中显式写的 src,优先级非常高。它相当于直接给这条路由“钦定”了一个源地址,绕过了其他大部分选择逻辑。所以只要路由表里存在这个属性,无论接口上有多少看起来更合适的地址,内核都会照着它走。Linux的源地址选择还会参考一个叫“地址标签策略表”的东西,用ip addrlabel show可以查看。它给不同的IPv6前缀分配合适的标签优先级,比如本机地址、链路本地地址、全局单播地址,各有各的标签。这个表平时不起眼,但在多前缀、多接入的环境里,它会参与“公平比较”。当路由表项里没有 src 时,源地址选择的结果很大程度上由地址标签策略表和地址scope共同决定。2.2 src 与 addrlabel 的优先级关系这里有一个容易混淆的点:既然有addrlabel,为什么路由里的 src 还这么容易引起故障?因为两者的作用阶段不同。addrlabel主要影响源地址的初步筛选和排序,而路由表里的 src 是“一票制”,只要它还在,基本不需要走到后续比较环节。也就是说,addrlabel解决的是“候选地址中选哪个”的问题,src解决的是“直接用这个地址”的问题。前者是推荐制,后者是任命制。实际排查中,如果你发现无论怎么调addrlabel,源地址都选不对,那很有可能是某条路由的 src 在作梗。这时候应该先去看路由表,而不是继续调标签策略。2.3 临时地址、隐私扩展带来的隐藏干扰IPv6的隐私扩展(privacy extension)会让系统为接口周期性地生成临时地址,用于出站连接。这是好事,能防止基于地址的习惯追踪。但在“路由 src 已固定”的场景下,临时地址反而会让问题更难发现。因为临时地址经常变化,接口上的地址列表每隔一段时间就换一批。如果路由表里的 src 写的还是一个已经过期、被系统主动废弃的临时地址,连接表现就是“时断时续”:临时地址未过期时,一切正常;过期后,出站依然带着旧地址,直接失败。很多同行排查到这一步就卡住了,因为他们只看了当前接口有什么地址,没有去对路由表里那个“旧相好”。如果你希望某个业务地址长期稳定,不想被临时地址干扰,可以在接口配置里把隐私扩展关掉,或者把use_tempaddr设成0。但要注意,这个改动只影响邻居发现和源地址生成的策略,不等同于清除路由里的 src。两者要分开处理。3. 查看、删除、修改与持久化:清除路由源地址的完整实操3.1 第一步永远是“看清楚再动刀”在Linux下,查看IPv6路由最常用的命令是:ip -6 route show如果默认路由表看不出全部信息,可以指定表名:ip -6 route show table main ip -6 route show table all输出里每一行代表一条路由,格式大致如下:2001:db8:100::/64 dev eth0 proto kernel metric 256 pref medium default via 2001:db8::1 dev eth0 src 2001:db8:100::2 metric 1024 pref medium注意看第二行里的src 2001:db8:100::2,这就是我们要找的字段。但“看清路由表”不等于“看清实际问题”。更关键的一个命令是ip -6 route get,它能直接告诉你,发往某个目的地址时,内核会选用哪个源地址:ip -6 route get 2001:db8:200::5输出类似:2001:db8:200::5 via 2001:db8::1 dev eth0 src 2001:db8:100::2 metric 1024 pref medium这里的 src 就是实际生效的源地址。如果你手头遇到“出站流量源地址不对”的问题,先跑这命令,比对着接口地址列表猜要快得多。3.2 删除和修改:三个层次的清理办法第一种办法,最直接:删除整条路由,再重新添加一条不带 src 的路由。ip -6 route del default via 2001:db8::1 dev eth0 ip -6 route add default via 2001:db8::1 dev eth0重新添加时不写 src,路由表里自然就没有这个属性了。这种方法简单粗暴,但风险是:在删除和添加之间,有一瞬间没有默认路由,可能导致报文被丢弃。生产环境操作时要小心。第二种办法,更稳妥:用 replace 做原子替换。ip -6 route replace default via 2001:db8::1 dev eth0replace 会根据“目标网段+网关+出接口”找到对应的旧路由,并用新属性覆盖。实测中,不带 src 的新路由,一般可以顶掉旧路由里残留的 src。但如果碰上某些老版本内核或者特殊路由协议注入的情况,replace 不生效,那就老老实实走第一方案:del 再 add。第三种办法,针对需要修改成另一个源地址的场景:ip -6 route replace default via 2001:db8::1 dev eth0 src 2001:db8:200::2这样就能把 src 从一个地址改成另一个地址,保留路由本身和其他属性。这是最常用的“修正”操作,适合那种确定要固定源地址、但之前写错了的情况。3.3 刷新缓存与验证:改完不验证等于白改Linux的IPv6路由缓存机制在不同内核版本里差异很大。早期的内核会维护一个路由缓存表,查过一次路由后,结果会缓存一段时间。这就导致了一个很坑的现象:你明明改了路由表,但已建立的连接还是用旧源地址,新连接也可能短暂命中旧缓存。虽然较新内核已经用按需查找替代了大块缓存,但为了保险,改完还是建议刷新一下:ip -6 route flush cache如果问题还涉及邻居关系,比如源地址变了、邻居缓存中还存着旧的映射关系,可以顺手清一下邻居表:ip -6 neigh flush all然后再用ip -6 route get确认效果:ip -6 route get 2001:db8:200::5看到输出里的 src 已经从旧地址变成新地址,或者完全消失(由内核自动选择),才算真正搞定。3.4 持久化配置:别让临时命令只活在当下命令行改完,重启网络服务或者重启机器,配置很可能被旧的配置重新覆盖。想要让“清除路由源地址”的结果稳定落地,必须在系统配置层面处理。使用systemd-networkd管理网络的环境,可以在网络配置文件中修改路由部分。比如10-eth0.network:[Route] Gateway=2001:db8::1 Destination=2001:db8:200::/64 Source=2001:db8:100::2把Source=这一行去掉,或者改成正确的地址,再执行networkctl reload重启网络配置。使用netplan的环境,配置文件类似:routes: - to: default via: 2001:db8::1 source: 2001:db8:100::2同样,把source:字段去掉或者改掉。netplan环境记得netplan apply生效。使用ifupdown的老式环境,则需要在/etc/network/interfaces里检查是否有额外的手动路由命令。很多系统里会在post-up里写ip -6 route add ... src ...,这种写法最隐蔽,因为表面看配置文件很简单,实际生效的行为完全要靠那行post-up命令撑起来。排查时要格外留意这类“藏起来”的配置。3.5 别混淆:路由里的 src 和策略路由的 fromIPv6环境下,还有一类常见操作叫“基于源地址的策略路由”,也就是规则里写from <源地址>。比如:ip -6 rule add from 2001:db8:100::2 lookup 100这种规则会让指定源地址的流量进入另一个路由表。如果某天你想“清除路由源地址”,很可能是指要删掉这一类基于源地址的规则。它们的操作方式完全不同于路由项里的 src:ip -6 rule show ip -6 rule del from 2001:db8:100::2 lookup 100我见过不少同事把这两个概念搞混:明明想清的是策略路由规则,却一直在路由表里折腾 src,结果怎么都无效。先确认自己遇到的问题到底属于哪一种,再选择对应的操作方式,能省下大把时间。4. 验证效果与批量处理:别让配置只在当下生效4.1 从数据包层面确认源地址命令显示的结果正确,不代表实际流量就正常。更可靠的验证方式是抓包。tcpdump -i eth0 -n 'ip6 and tcp port 443'然后从该服务器发起一次出站连接,观察抓包结果里的源IPv6地址。如果和预期一致,说明路由 src 的清除/修改已经生效。如果你想验证的是已建立连接的行为,可以用ss命令看当前会话的本地地址:ss -6 -tnp输出里的 Local Address 列,就是该连接正在使用的源地址。如果这里显示的还是一个旧地址,那很可能是连接建立时缓存了旧路由,需要重建连接再测试。4.2 批量清理:脚本化处理多接口环境当服务器上接口多、路由条目多时,一条一条手工检查不现实。我通常会用一个小脚本,把路由表里所有 src 值和接口当前实际存在的地址做对比,找出那些“引用了不存在地址”的路由。# 提取当前接口上的全局IPv6地址 ip -6 addr show scope global | awk '/inet6/{print $2}' | awk -F/ '{print $1}' | sort -u > /tmp/current_addrs # 提取路由表中所有 src 字段 ip -6 route show table main | awk '/src/{for(i=1;i<=NF;i++)if($i=="src")print $(i+1)}' | sort -u > /tmp/route_srcs # 找出路由表里引用了但接口上不存在的地址 comm -23 /tmp/route_srcs /tmp/current_addrs如果上面最后一步有输出,就说明路由表里存在“僵尸 src”。再针对这些地址找对应的路由,执行 replace 或 del 操作时,就能很精确地定位到问题条目,不会误伤其他正常配置。另一个思路是直接写一个修复脚本,对所有路由逐条检查并重建:ip -6 route show table main | while read route; do # 解析出 prefix、via、dev prefix=$(echo "$route" | awk '{print $1}') via=$(echo "$route" | awk '/via/{for(i=1;i<=NF;i++)if($i=="via")print $(i+1)}') dev=$(echo "$route" | awk '/dev/{for(i=1;i<=NF;i++)if($i=="dev")print $(i+1)}') # 如果src存在且不在当前地址列表,就用replace清掉 src=$(echo "$route" | awk '/src/{for(i=1;i<=NF;i++)if($i=="src")print $(i+1)}') if [ -n "$src" ] && ! grep -qx "$src" /tmp/current_addrs; then ip -6 route replace "$prefix" via "$via" dev "$dev" echo "fixed: $prefix (removed stale src $src)" fi done这个脚本的思路并不复杂,核心就是“对比再修复”。实际生产使用时,建议先在测试环境跑一遍,确认 replace 的匹配条件不会误改其他字段,再拿到线上批量执行。4.3 几个真实踩过的坑第一,replace 不是万能的。有些路由是内核通过RA(路由通告)自动生成的,replace 后可能又被RA重新改回来。遇到这种情况,单纯改路由表没用,要从源头调整接口的accept_ra设置,或者禁用接口的自动配置。第二,多路由表环境容易漏。很多人只盯着 main 表,忽略了其他自定义路由表。如果问题流量走了策略规则指定的特殊表,你清main表清得再干净也没用。排查时一定要ip -6 route show table all看一眼全貌。第三,动态环境里 src 会“自动复活”。如果系统里有网络管理工具、DHCPv6客户端或者云平台初始化脚本在运行,它们可能会在每次网络事件后重新注入带 src 的路由。清理完一次,过几分钟又出现了。这时候要追查是谁写进去的,比如查一下服务状态、打开日志,看看有没有脚本在按指定源地址重建路由。第四,临时地址过期比想象中来得快。默认配置下,Linux的临时IPv6地址有效生命周期通常是几天到一周。如果你在路由里固定了一个临时地址,它必然会在未来某个时间点失效。除非有明确理由,否则别把临时地址写进路由的 src。5. 常见问题速查表:线上处理怎么少走弯路下面这个表是我把多年IPv6排障中的典型场景浓缩出来的,直接照着对应关系处理,能省不少事。场景典型现象推荐处理路由表 src 指向已删除地址出站连接失败,route get 显示旧地址ip -6 route replace不带 src,或 del 再 add多条路由都带错误 src多个目的网段都有问题脚本对比接口地址批量 replaceRA 自动生成的路由带 src刚清完又自动恢复调整接口accept_ra或关闭自动配置策略路由规则 from 干扰特定源地址流量绕路ip -6 rule del from <地址>隐私扩展临时地址过期连接时好时坏检查地址生命周期,避免把临时地址写入路由改了路由但连接仍用旧地址已建立连接不受影响,新连接正常刷新路由缓存,重建连接测试配置文件里藏了 post-up 路由命令重启网络后问题复发检查配置文件中的手动路由行并移除多路由表问题main 表正常但流量仍然异常ip -6 route show table all全面排查表格里最后一条要特别提醒:IPv6排障最忌讳只看局部。一个流量路径涉及接口地址、邻居表、路由表、策略规则、防火墙规则好几层。遇到疑难问题时,从ip -6 addr、ip -6 neigh、ip -6 route、ip -6 rule四个命令逐一排查,基本不会漏掉关键环节。我个人在实际操作中还有一个习惯:任何一次修改都先备份当前的路由表,比如ip -6 route show table all > /tmp/route_backup_$(date +%F)。这样即使改坏了,也能快速恢复原状,不至于手忙脚乱。IPv6操作相对IPv4而言恢复成本更高,因为地址数量多、前缀长度灵活、缓存机制复杂,一旦改错,排查起来更费力。先备份,永远不亏。
前阵子排一个IPv6跨站点不通的问题,接口地址配得好好的,默认路由也在,curl就是出不去。抓包才发现,出去的包居然带着一个已经不存在的源地址,而这个源地址的来路,竟然是路由表项里残留的 src 属性。从那以后,我对“IPv6清除路由源地址”这类操作格外上心,把Linux下IPv6源地址选择、路由表修改、缓存刷新以及自动化脚本的事一并整理了出来,这一篇就当是给同行留的一份排查笔记。网上讨论IPv6的文章不少,但大多停在“怎么配地址、怎么加默认路由”这一步,很少有人讲路由表里那个 src 字段出了问题该怎么收拾。真实环境里,这个不起眼的字段恰恰容易埋雷:它会默默覆盖内核的源地址选择逻辑,一旦指向一个失效的地址,整个出站方向可能全废。这篇内容适合正在做IPv6网络改造、服务器双栈上线的同学,也适合那些被“地址选错”问题折磨过的运维和网络工程师。1. 为什么非要清除“路由源地址”:一个普通属性带来的连锁故障1.1 路由表里的 src 到底是什么IPv6路由表里,每条路由除了目标网段、网关、出接口之外,还有一个不常被注意的属性叫 src,全称是 preferred source。它的含义很直接:当发往这个目标网段的数据包需要选择源地址时,内核会优先使用这个指定的IPv6地址,而不是自行计算。举个例子。服务器上有两个IPv6地址,分别是2001:db8:100::2/64和2001:db8:200::2/64,属于同一个物理接口。默认情况下,发往2001:db8:200::5的数据包,内核通过路由查表找到对应的路由后,会自己选一个合适的源地址。但如果这条路由上写了 src 2001:db8:100::2,那无论数据包从哪个方向出去,源地址都会被锁死成2001:db8:100::2。这个机制在正经场景里是好事。比如多网卡服务器,回程流量必须从某个固定地址出去,以便对端防火墙只放行这一个地址。但问题也恰恰出在这里——当这个 src 指向的地址已经不存在、被删掉、或者因为SLAAC变更换了新地址时,路由表里那个值就成了一段“僵尸配置”。1.2 源地址选错引发的典型问题我在实际环境里遇到的情况大致分三类。第一类是出站连接直接失败。发往目标的报文,源地址写了一个当前接口不存在的地址。对端收到后,回程报文无法正确路由回发送方,连接自然建立不起来。表现就是ping通邻居、ping不通远端,路由表看起来又毫无问题。第二类是连接能建立,但行为诡异。比如某些服务会基于源地址做访问控制,源地址和预期不符,请求被后端拒绝,但重试几次偶尔又能通过——因为如果系统开了隐私扩展,临时地址会变化,运气好时碰上一个“合法”地址就通了。这种“时好时坏”的问题最耗时间,因为它不具备可复现性。第三类是路由策略被src干扰。配置了策略路由的环境里,路由表项之间的优先级、匹配顺序都比较敏感。某个路由表项里带了一个错误的src,可能让原本走特定出口的流量被“拽”到另一个地址上,整条链路的路由意图全部跑偏。1.3 什么时候才需要主动清除搞清楚“为什么清”之前,得先知道“什么情况算该清”。根据我的经验,下面这些场景基本跑不掉:某张网卡的物理地址或虚拟地址被移除,但路由表里还残留着指向该地址的 src。使用SLAAC自动配置地址时,系统自动生成的路由带了 src,但后来临时地址过期或更换了,旧值还留在表里。手动在路由上配置了错误的,后面要回退到内核自动选择源地址。做了一堆实验后,路由表里堆积了各种带src的临时路由,想要恢复一个干净的状态。这类问题有个共同点:你无法通过单纯的“重启网卡”解决。重启网卡只会重新生成地址,路由表里明确指定的 src 属性不会跟着消失,重启网络服务也未必能覆盖掉所有手动配置的路由。2. 动手前先摸清机制:IPv6源地址选择到底听谁的2.1 从 RFC 3484 到策略表IPv6源地址选择有一套标准算法,定义在RFC 3484里。Linux在实现时把它演化成了一套规则,大致顺序是:优先匹配出接口上同一网段的地址,再看路由表项中的 src 属性,然后比较地址的作用域、生命周期是否有效等等。关键点在于,路由表项中显式写的 src,优先级非常高。它相当于直接给这条路由“钦定”了一个源地址,绕过了其他大部分选择逻辑。所以只要路由表里存在这个属性,无论接口上有多少看起来更合适的地址,内核都会照着它走。Linux的源地址选择还会参考一个叫“地址标签策略表”的东西,用ip addrlabel show可以查看。它给不同的IPv6前缀分配合适的标签优先级,比如本机地址、链路本地地址、全局单播地址,各有各的标签。这个表平时不起眼,但在多前缀、多接入的环境里,它会参与“公平比较”。当路由表项里没有 src 时,源地址选择的结果很大程度上由地址标签策略表和地址scope共同决定。2.2 src 与 addrlabel 的优先级关系这里有一个容易混淆的点:既然有addrlabel,为什么路由里的 src 还这么容易引起故障?因为两者的作用阶段不同。addrlabel主要影响源地址的初步筛选和排序,而路由表里的 src 是“一票制”,只要它还在,基本不需要走到后续比较环节。也就是说,addrlabel解决的是“候选地址中选哪个”的问题,src解决的是“直接用这个地址”的问题。前者是推荐制,后者是任命制。实际排查中,如果你发现无论怎么调addrlabel,源地址都选不对,那很有可能是某条路由的 src 在作梗。这时候应该先去看路由表,而不是继续调标签策略。2.3 临时地址、隐私扩展带来的隐藏干扰IPv6的隐私扩展(privacy extension)会让系统为接口周期性地生成临时地址,用于出站连接。这是好事,能防止基于地址的习惯追踪。但在“路由 src 已固定”的场景下,临时地址反而会让问题更难发现。因为临时地址经常变化,接口上的地址列表每隔一段时间就换一批。如果路由表里的 src 写的还是一个已经过期、被系统主动废弃的临时地址,连接表现就是“时断时续”:临时地址未过期时,一切正常;过期后,出站依然带着旧地址,直接失败。很多同行排查到这一步就卡住了,因为他们只看了当前接口有什么地址,没有去对路由表里那个“旧相好”。如果你希望某个业务地址长期稳定,不想被临时地址干扰,可以在接口配置里把隐私扩展关掉,或者把use_tempaddr设成0。但要注意,这个改动只影响邻居发现和源地址生成的策略,不等同于清除路由里的 src。两者要分开处理。3. 查看、删除、修改与持久化:清除路由源地址的完整实操3.1 第一步永远是“看清楚再动刀”在Linux下,查看IPv6路由最常用的命令是:ip -6 route show如果默认路由表看不出全部信息,可以指定表名:ip -6 route show table main ip -6 route show table all输出里每一行代表一条路由,格式大致如下:2001:db8:100::/64 dev eth0 proto kernel metric 256 pref medium default via 2001:db8::1 dev eth0 src 2001:db8:100::2 metric 1024 pref medium注意看第二行里的src 2001:db8:100::2,这就是我们要找的字段。但“看清路由表”不等于“看清实际问题”。更关键的一个命令是ip -6 route get,它能直接告诉你,发往某个目的地址时,内核会选用哪个源地址:ip -6 route get 2001:db8:200::5输出类似:2001:db8:200::5 via 2001:db8::1 dev eth0 src 2001:db8:100::2 metric 1024 pref medium这里的 src 就是实际生效的源地址。如果你手头遇到“出站流量源地址不对”的问题,先跑这命令,比对着接口地址列表猜要快得多。3.2 删除和修改:三个层次的清理办法第一种办法,最直接:删除整条路由,再重新添加一条不带 src 的路由。ip -6 route del default via 2001:db8::1 dev eth0 ip -6 route add default via 2001:db8::1 dev eth0重新添加时不写 src,路由表里自然就没有这个属性了。这种方法简单粗暴,但风险是:在删除和添加之间,有一瞬间没有默认路由,可能导致报文被丢弃。生产环境操作时要小心。第二种办法,更稳妥:用 replace 做原子替换。ip -6 route replace default via 2001:db8::1 dev eth0replace 会根据“目标网段+网关+出接口”找到对应的旧路由,并用新属性覆盖。实测中,不带 src 的新路由,一般可以顶掉旧路由里残留的 src。但如果碰上某些老版本内核或者特殊路由协议注入的情况,replace 不生效,那就老老实实走第一方案:del 再 add。第三种办法,针对需要修改成另一个源地址的场景:ip -6 route replace default via 2001:db8::1 dev eth0 src 2001:db8:200::2这样就能把 src 从一个地址改成另一个地址,保留路由本身和其他属性。这是最常用的“修正”操作,适合那种确定要固定源地址、但之前写错了的情况。3.3 刷新缓存与验证:改完不验证等于白改Linux的IPv6路由缓存机制在不同内核版本里差异很大。早期的内核会维护一个路由缓存表,查过一次路由后,结果会缓存一段时间。这就导致了一个很坑的现象:你明明改了路由表,但已建立的连接还是用旧源地址,新连接也可能短暂命中旧缓存。虽然较新内核已经用按需查找替代了大块缓存,但为了保险,改完还是建议刷新一下:ip -6 route flush cache如果问题还涉及邻居关系,比如源地址变了、邻居缓存中还存着旧的映射关系,可以顺手清一下邻居表:ip -6 neigh flush all然后再用ip -6 route get确认效果:ip -6 route get 2001:db8:200::5看到输出里的 src 已经从旧地址变成新地址,或者完全消失(由内核自动选择),才算真正搞定。3.4 持久化配置:别让临时命令只活在当下命令行改完,重启网络服务或者重启机器,配置很可能被旧的配置重新覆盖。想要让“清除路由源地址”的结果稳定落地,必须在系统配置层面处理。使用systemd-networkd管理网络的环境,可以在网络配置文件中修改路由部分。比如10-eth0.network:[Route] Gateway=2001:db8::1 Destination=2001:db8:200::/64 Source=2001:db8:100::2把Source=这一行去掉,或者改成正确的地址,再执行networkctl reload重启网络配置。使用netplan的环境,配置文件类似:routes: - to: default via: 2001:db8::1 source: 2001:db8:100::2同样,把source:字段去掉或者改掉。netplan环境记得netplan apply生效。使用ifupdown的老式环境,则需要在/etc/network/interfaces里检查是否有额外的手动路由命令。很多系统里会在post-up里写ip -6 route add ... src ...,这种写法最隐蔽,因为表面看配置文件很简单,实际生效的行为完全要靠那行post-up命令撑起来。排查时要格外留意这类“藏起来”的配置。3.5 别混淆:路由里的 src 和策略路由的 fromIPv6环境下,还有一类常见操作叫“基于源地址的策略路由”,也就是规则里写from <源地址>。比如:ip -6 rule add from 2001:db8:100::2 lookup 100这种规则会让指定源地址的流量进入另一个路由表。如果某天你想“清除路由源地址”,很可能是指要删掉这一类基于源地址的规则。它们的操作方式完全不同于路由项里的 src:ip -6 rule show ip -6 rule del from 2001:db8:100::2 lookup 100我见过不少同事把这两个概念搞混:明明想清的是策略路由规则,却一直在路由表里折腾 src,结果怎么都无效。先确认自己遇到的问题到底属于哪一种,再选择对应的操作方式,能省下大把时间。4. 验证效果与批量处理:别让配置只在当下生效4.1 从数据包层面确认源地址命令显示的结果正确,不代表实际流量就正常。更可靠的验证方式是抓包。tcpdump -i eth0 -n 'ip6 and tcp port 443'然后从该服务器发起一次出站连接,观察抓包结果里的源IPv6地址。如果和预期一致,说明路由 src 的清除/修改已经生效。如果你想验证的是已建立连接的行为,可以用ss命令看当前会话的本地地址:ss -6 -tnp输出里的 Local Address 列,就是该连接正在使用的源地址。如果这里显示的还是一个旧地址,那很可能是连接建立时缓存了旧路由,需要重建连接再测试。4.2 批量清理:脚本化处理多接口环境当服务器上接口多、路由条目多时,一条一条手工检查不现实。我通常会用一个小脚本,把路由表里所有 src 值和接口当前实际存在的地址做对比,找出那些“引用了不存在地址”的路由。# 提取当前接口上的全局IPv6地址 ip -6 addr show scope global | awk '/inet6/{print $2}' | awk -F/ '{print $1}' | sort -u > /tmp/current_addrs # 提取路由表中所有 src 字段 ip -6 route show table main | awk '/src/{for(i=1;i<=NF;i++)if($i=="src")print $(i+1)}' | sort -u > /tmp/route_srcs # 找出路由表里引用了但接口上不存在的地址 comm -23 /tmp/route_srcs /tmp/current_addrs如果上面最后一步有输出,就说明路由表里存在“僵尸 src”。再针对这些地址找对应的路由,执行 replace 或 del 操作时,就能很精确地定位到问题条目,不会误伤其他正常配置。另一个思路是直接写一个修复脚本,对所有路由逐条检查并重建:ip -6 route show table main | while read route; do # 解析出 prefix、via、dev prefix=$(echo "$route" | awk '{print $1}') via=$(echo "$route" | awk '/via/{for(i=1;i<=NF;i++)if($i=="via")print $(i+1)}') dev=$(echo "$route" | awk '/dev/{for(i=1;i<=NF;i++)if($i=="dev")print $(i+1)}') # 如果src存在且不在当前地址列表,就用replace清掉 src=$(echo "$route" | awk '/src/{for(i=1;i<=NF;i++)if($i=="src")print $(i+1)}') if [ -n "$src" ] && ! grep -qx "$src" /tmp/current_addrs; then ip -6 route replace "$prefix" via "$via" dev "$dev" echo "fixed: $prefix (removed stale src $src)" fi done这个脚本的思路并不复杂,核心就是“对比再修复”。实际生产使用时,建议先在测试环境跑一遍,确认 replace 的匹配条件不会误改其他字段,再拿到线上批量执行。4.3 几个真实踩过的坑第一,replace 不是万能的。有些路由是内核通过RA(路由通告)自动生成的,replace 后可能又被RA重新改回来。遇到这种情况,单纯改路由表没用,要从源头调整接口的accept_ra设置,或者禁用接口的自动配置。第二,多路由表环境容易漏。很多人只盯着 main 表,忽略了其他自定义路由表。如果问题流量走了策略规则指定的特殊表,你清main表清得再干净也没用。排查时一定要ip -6 route show table all看一眼全貌。第三,动态环境里 src 会“自动复活”。如果系统里有网络管理工具、DHCPv6客户端或者云平台初始化脚本在运行,它们可能会在每次网络事件后重新注入带 src 的路由。清理完一次,过几分钟又出现了。这时候要追查是谁写进去的,比如查一下服务状态、打开日志,看看有没有脚本在按指定源地址重建路由。第四,临时地址过期比想象中来得快。默认配置下,Linux的临时IPv6地址有效生命周期通常是几天到一周。如果你在路由里固定了一个临时地址,它必然会在未来某个时间点失效。除非有明确理由,否则别把临时地址写进路由的 src。5. 常见问题速查表:线上处理怎么少走弯路下面这个表是我把多年IPv6排障中的典型场景浓缩出来的,直接照着对应关系处理,能省不少事。场景典型现象推荐处理路由表 src 指向已删除地址出站连接失败,route get 显示旧地址ip -6 route replace不带 src,或 del 再 add多条路由都带错误 src多个目的网段都有问题脚本对比接口地址批量 replaceRA 自动生成的路由带 src刚清完又自动恢复调整接口accept_ra或关闭自动配置策略路由规则 from 干扰特定源地址流量绕路ip -6 rule del from <地址>隐私扩展临时地址过期连接时好时坏检查地址生命周期,避免把临时地址写入路由改了路由但连接仍用旧地址已建立连接不受影响,新连接正常刷新路由缓存,重建连接测试配置文件里藏了 post-up 路由命令重启网络后问题复发检查配置文件中的手动路由行并移除多路由表问题main 表正常但流量仍然异常ip -6 route show table all全面排查表格里最后一条要特别提醒:IPv6排障最忌讳只看局部。一个流量路径涉及接口地址、邻居表、路由表、策略规则、防火墙规则好几层。遇到疑难问题时,从ip -6 addr、ip -6 neigh、ip -6 route、ip -6 rule四个命令逐一排查,基本不会漏掉关键环节。我个人在实际操作中还有一个习惯:任何一次修改都先备份当前的路由表,比如ip -6 route show table all > /tmp/route_backup_$(date +%F)。这样即使改坏了,也能快速恢复原状,不至于手忙脚乱。IPv6操作相对IPv4而言恢复成本更高,因为地址数量多、前缀长度灵活、缓存机制复杂,一旦改错,排查起来更费力。先备份,永远不亏。