ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

路由表与FIB表区别:转发原理与不通故障排查

路由表与FIB表区别:转发原理与不通故障排查

路由表和FIB表这两个词,很多人第一次听到是在教材的某一章,背完定义就翻过去了。真正让人卡住的,往往是动手之后冒出来的那个疑问:书上说路由器转发靠查FIB表,可我在设备上敲ip route看到的明明是路由表,这两张表到底是一张还是两张?更让人困惑的是,有时候路由表里目标网段的路由写得清清楚楚,下一跳也能通,接口也是up,可业务流量就是过不去。这类问题如果脑子里只有"路由表"一个概念,基本查不出方向;把路由表和FIB表分清楚,很多看似玄学的故障会立刻变得有迹可循。下面这些内容,我会按"这两张表各自是什么、为什么要拆开、一条路由怎么从一张表走到另一张表、查表为什么这么快、以及表对不齐时怎么一步步排"这条线来讲,适合刚接触网络设备转发原理的新手,也适合已经会用命令但没深想过内部机制的一线运维。

1. 同一台设备里的两个"分身":控制平面想清楚,转发平面跑得快

1.1 一个包从进来到出去,中间到底发生了什么

先说结论:路由表和FIB表不是同一份东西,它们服务的是两个完全不同的目标。设备上的路由协议进程、静态配置、直连网段识别,这一整套逻辑跑在控制平面里。控制平面的任务很重:它要收集邻居信息、跑最短路径算法、比较不同来源的路由、处理路由抖动、做策略过滤,最后得出一个结论——到达某个目的网段,应该走哪条路。这个结论汇总起来,就是路由表,业内也常叫RIB(Routing Information Base,路由信息库)。

转发平面做的事情则简单得多。一个数据包进来,设备只有极短的时间做决定:看目的IP,查一张已经预处理好、只包含最终结果的小表,拿到出接口和下一跳的二层信息,改包头,发出去。这张小表就是FIB表(Forwarding Information Base,转发信息库)。注意这里的关键差别:路由表是"候选集合加选优过程"的产物,FIB表是"选优结束后的最终答案"。

打个生活化的比方。路由表像物流公司调度室墙上那张巨大的线路图,上面标了所有可能的路线、每条路的时效、成本、拥堵情况、备用方案,甚至还有几条已经废弃但没擦掉的线。FIB表则像贴在每个分拣工位上的速查卡,只写一行——"发往某片区,走三号传送带"。分拣工人不需要知道三号传送带为什么最优,他只需要知道结果是三号。

提示:把路由表理解成"决策依据",把FIB表理解成"执行指令",后面所有的排错思路都是从这个划分里长出来的。

1.2 为什么非要把它们拆开:性能与确定性的双重压力

有人会问,既然最终都是要知道"往哪发",为什么不干脆让转发的时候直接查路由表?答案藏在一个词里:每秒查询次数。一台中等规模的设备,满线速转发时每秒要做几百万次甚至上亿次查表。如果每次查询都要遍历包含多种路由来源、比较优先级、比较度量值、判断路由是否有效、再递归解析下一跳的完整路由表,这个开销在硬件上根本无法承受。

拆开之后,控制平面可以慢,可以用复杂算法,可以容忍几秒钟的收敛时间;转发平面必须快,必须做成固定流水线,必须用最简单的数据结构。这就是网络设备设计里最经典的"控制与转发分离"思想。软件定义的网络架构、路由器的主控板与线卡分工,本质上都是这个思想的延伸。

还有一层原因容易被忽略:确定性。路由表在协议收敛期间是不断变化的,如果转发直接依赖它,那么收敛过程中的每一点抖动都会直接反映到数据通路上。而FIB表的下发是一个原子性的动作——要么这条路由整条装进去,要么不装,中间态不会暴露给转发引擎。这种"快照式"的下发方式,让转发行为在网络动荡时依然保持可预测。

1.3 混为一谈会踩的第一个坑

最常见的误解,是把"路由表里有"直接等同于"流量能通"。实际上从路由表到FIB表之间还隔着好几道关卡:路由优选、下一跳递归解析、邻接信息建立、表项容量检查、硬件下发确认。任何一道关卡没过,路由表里那条路由就只是一条"记录",而不是一条"通路"。我在后面第 6 节会把这几个关卡拆开讲,这里先记住一点:查不通的时候,第一步不是怀疑路由写错了,而是怀疑路由没被装进FIB。

2. 拆开字段看差别:路由表臃肿是因为它要"讲道理",FIB精简是因为它只"干活"

2.1 路由表项里那些只影响"选谁"、不影响"怎么转"的字段

一条典型的路由表项远不止"目的网段"和"下一跳"两个信息,它通常还带着这些内容:路由来源(是直连、静态配置,还是某个动态协议学来的)、优先级或管理距离(不同来源之间比大小用的权重)、度量值(同一来源内部比优劣的数值)、路由年龄和时间戳、路由标记、在某些协议里还有AS路径和下一跳的原始属性。

这些东西的共同特点是:它们只参与"选优",选完之后对转发毫无用处。举个例子,一条静态路由优先级是60,一条动态学来的路由优先级是110,静态胜出。可一旦静态胜出并被装进FIB,转发引擎根本不需要知道"这条路是静态配的"或者"它是60不是110",它只需要知道出哪个口、下一跳MAC是多少。FIB表把这些决策痕迹全部剥掉了,留下的正是纯粹的执行信息。

2.2 FIB表项的三要素:前缀、出接口、邻接

一条FIB表项,在大多数实现里核心就是三样东西:目的前缀加掩码长度、出接口索引、邻接索引。邻接索引是个很关键的间接层,它指向一张邻接表,邻接表里存的是二层重写需要的信息——下一跳的MAC地址、本接口的MAC地址、VLAN标签等。为什么要多这一层?因为多个不同的目的前缀可能指向同一个下一跳,共享一份邻接信息,既省空间,也方便下一跳MAC变化时统一更新。

除了这三样,FIB表项往往还挂着一些附加信息:转发统计计数器、QoS队列映射、ECMP组标识、是否允许分片的标志等。这些属于"转发时需要顺手用一下"的信息,和路由表里那些"决策时才用"的信息完全是两回事。

对比维度路由表(RIB)FIB表
维护者控制平面(协议进程、配置模块)转发平面(内核转发路径或芯片)
内容范围所有来源、所有候选路由仅优选后生效的路由
典型字段前缀、下一跳、出接口、优先级、度量、来源、标记、年龄前缀、出接口、邻接索引、ECMP组、统计计数
数据规模大,含冗余和失效项小,只留有效项
更新节奏协议收敛时批量变化随优选结果逐条增量下发
查询方式软件遍历、有序结构、带条件判断最长前缀匹配,硬件并行或软件专用树
存放位置主控内存芯片表项或内核转发数据结构

这张表建议反复看几遍。后面所有的排错动作,其实都是在判断"问题出在左列还是右列"。

2.3 一个容易忽略的细节:FIB里可能出现路由表里"看不见"的项

反过来也有意思的情况:FIB表里有时候会有一些路由表里翻不到对应记录的项。比如某些实现会为每个接口自动生成一条直连的主机路由,或者为广播地址、组播地址生成特殊表项,这些在常规的路由表展示里可能被折叠或省略。所以排错时如果发现FIB里的条目数比路由表多,不必惊讶,先确认那些多出来的项是不是系统自动生成的辅助表项。

3. 一条路由从路由表走进FIB表的完整旅程

3.1 第一关:路由优选,同前缀多来源打架时谁说了算

同一个目的前缀,完全可能同时被多种方式覆盖。比如一个网段既配了静态路由,又被动态协议学到了一模一样的路由,还可能有条默认路由也能覆盖它。这时候设备必须做选择,选择规则通常是分层的:

第一步比优先级(有的实现叫管理距离或preference)。直连路由优先级最高,静态路由次之,各种动态协议按约定排开,外部引入的路由通常优先级最低。这一步是跨来源的比较,目的是让"人工干预"压过"自动学习"。

第二步,如果来源相同或优先级相同,比度量值。度量值是同一种协议内部算出来的代价,比如跳数、带宽开销、延迟累加值。

第三步,如果度量和优先级都相同,就进入等价多路径处理,也就是常说的ECMP——几条路一起用,靠哈希分流。

这里有个实操中很常见的误区:有人以为"路由表里显示的那条就是唯一的路",其实展示出来的往往只是优选结果,被淘汰的候选路由在详细视图里还能看到,标记为无效或被抑制。排查路由选错方向的问题时,一定要看详细视图而不是简略视图。

3.2 第二关:下一跳递归解析,BGP场景下的高频翻车点

动态协议里,尤其是边界网关协议,学到的路由给出的下一跳经常不是直连的地址,而是一个"再走几跳才能到"的地址。设备必须做一次递归查找:拿这个下一跳地址再去路由表里查一遍,直到找到一个直连的下一跳为止,然后把这条链路的最终出接口和邻接信息写进FIB。

这一步是故障高发区。如果递归查不到结果,这条路由就会被判定为不可达,不会进FIB。表现出来就是:路由表里明明有这条路由,状态显示有效,但流量就是不通。更隐蔽的情况是递归链在中途断了——比如中间某一段的下一跳接口down了,整条链跟着失效,但上层路由的状态更新有延迟。

我的经验是,遇到"路由在但不通"且涉及动态协议的场景,先确认下一跳能不能递归到底。可以通过带递归解析的查表命令验证实际选中的出接口和下一跳,而不是只看路由表里写的那个原始下一跳。

3.3 第三关:邻接建立与硬件下发,看不见的那一半

路由被优选、下一跳解析成功,还不算完。转发平面要真正把包发出去,必须知道下一跳的二层地址。这个信息存在邻居表里,通过地址解析协议或者邻居发现协议动态获取。如果邻居表里没有对应条目,或者条目处于不完整状态,包会被暂存或者直接丢弃,同时触发解析请求。这时候抓包能看到请求发出去了但没回应,流量表现为"发不出去"。

再往后是硬件下发。在带专用转发芯片的设备上,控制平面算好的FIB还要通过驱动接口写进芯片的表项里。这一步可能因为芯片表项满、下发表项冲突、驱动队列堵塞而失败。失败之后的表现非常典型:软件侧查询一切正常,硬件侧实际转发却是黑洞。这类问题在路由规模突然增长、或者配置了大量细粒度前缀之后特别容易出现。

注意:如果你的设备同时有软件转发和硬件转发两条路径,排错时一定要确认流量实际走的是哪一条。测试用的ping包可能走软件路径(正常),而业务大流量走硬件路径(异常),两者结论完全相反。

4. 转发查表为什么能这么快:最长前缀匹配与它的两种实现

4.1 哈希表在这里为什么不好使

很多人第一反应是:查表嘛,用哈希不就行了,O(1)。这个思路在FIB上直接失效,原因是转发需要的不是"精确匹配",而是最长前缀匹配(Longest Prefix Match,LPM)。

设想目的地址是 192.168.1.5,设备上同时存在 192.168.1.0/24、192.168.0.0/16、0.0.0.0/0 三条路由。这三条都能"覆盖"这个地址,但必须选掩码最长的那条,也就是 /24。哈希只能回答"这个键存不存在",回答不了"在所有能覆盖它的前缀里,哪个最长"。所以FIB的查找结构必须是专门为前缀匹配设计的。

4.2 软件实现:压缩前缀树,一层层往下走

纯软件转发的主机或者软件路由器,通常用各种变体的前缀树来做LPM。经典做法是把地址按比特展开成一棵树,从根往下走,沿途记录最后一个命中的有效前缀,走到走不动为止。为了减少层数、提高缓存命中率,实际实现会做路径压缩和多比特合并——把只有单个子节点的链压缩成一条边,把连续几层合并成一次比较多个比特。这样树的高度大幅降低,查询只需要几次内存访问。

这类结构的特点是内存占用可控、支持大规模路由、更新相对灵活,代价是查询要走多次内存访问,靠缓存来弥补。在通用处理器上,配合预取和缓存优化,做到千万级每秒的查询是可行的,但和专用芯片还是有数量级差距。

4.3 硬件实现:三态内容寻址存储器,一次并行比较出结果

专用转发芯片走的是另一条路。它用的存储结构,每个比特可以存三种状态:0、1、以及"不关心"。这个"不关心"就是关键——一条 /24 的路由,前24位有效,后8位全部标记为"不关心",那么任何前24位匹配的地址,一次比较就能命中。所有表项并行比较,一个时钟周期内出结果,天然满足最长前缀匹配的需求(把前缀长度也纳入比较维度,长的优先)。

代价也很直接:这种存储器单元面积大、功耗高、容量有限。所以硬件FIB的规模是硬约束,IPv4通常能做到几万到几十万条量级,IPv6因为前缀更长、表项更宽,能装的条数还要再打折。这就是为什么核心设备上要精细规划路由规模,也是为什么有些设备会提供"把部分路由放到软件慢路径"的降级方案。

4.4 等价多路径:查完之后还要再选一次

FIB里一个前缀可能对应一组出接口和下一跳,这就是等价多路径。查表命中之后,还要在这组里挑一个,通常用哈希,哈希因子取五元组(源目的地址、源目的端口、协议号)。这里有个实测中很值得注意的现象:哈希函数的分布不均会导致某条链路被打爆而其他链路空闲,尤其是流量种类单一、源目的地址组合很少的场景。遇到链路利用率严重不均,先别怀疑路由配错,八成是哈希极化。

5. 主机上的多张表与多条规则:比"一张路由表"复杂得多的现实

5.1 默认看到的那张表,只是其中一张

在通用操作系统上敲查看路由的命令,默认展示的往往只是主表。而系统内部其实维护着多张表,每张表有各自的编号和用途,还有一套规则决定"什么样的包查哪张表"。这套机制叫策略路由。

默认规则通常是这样排的:优先级最高的规则把本地地址相关的流量导向本地表,接着才是主表,最后是默认表兜底。这意味着你配在主表里的路由,不一定对某个包生效——如果前面有更高优先级的规则把它导向了别的表,主表里那条路由就是摆设。

这个机制解释了一个非常经典的困惑:"我明明在主表里加了路由,为什么流量还是走老路?"答案往往是别的表里有条更具体的路由,或者某条规则把流量提前截走了。

5.2 本地表和那些"看不见"的主机路由

本地表里存的是本机所有接口地址、广播地址、回环地址对应的表项,作用是让发往本机地址的包走本地投递而不是往外转发。这些表项数量不少,而且会随接口地址变化自动增减。排错时如果发现某个地址的流量被本地吞掉了,多半是本地表里有对应条目——比如你给接口配了个地址,恰好和你要访问的目的地址冲突。

广播地址的处理也容易出问题。发往子网广播地址的包应该从对应接口发出去,如果本地表或者主表里缺了相应条目,包可能被直接丢弃。这类问题在多网卡、多子网的机器上尤其常见。

5.3 策略路由实战:什么时候真的需要多张表

策略路由不是炫技,它解决的是"按源地址、按入接口、按标记分流"这类需求,普通的目的地址路由做不到。典型场景包括:多线接入时让不同来源的流量走不同出口、把特定业务的流量强制导向某条专线、以及做流量牵引和黑洞。

配置的时候有个顺序问题必须注意:规则和表是两个独立的东西,先有表里有路由,再加规则指向这张表,否则规则生效的瞬间流量会因为查不到路由而被丢弃。反过来删的时候要先删规则再清表,避免中间出现一段指向空表的窗口期。这个顺序踩过一次坑就记住了。

6. 表里有路由但流量不通:一条可以照着走的排查链路

6.1 第一步:确认包实际查的是哪张表、命中了哪条

不要一上来就翻路由表找那条路由,先让系统告诉你"如果现在发一个去某地址的包,它会怎么走"。带查表模拟功能的命令能直接给出选中的源地址、出接口、下一跳,这个结果才是转发平面真正会用的答案,比你自己在路由表里找要可靠得多。

如果模拟结果和预期不符,就顺着规则链往上找:是哪条规则把查询导向了别的表,那张表里又有什么更具体的条目。这一步能把"配了没生效"和"配错了地方"两类问题区分开。

6.2 第二步:下一跳能不能到,邻居表里有没有对应条目

模拟查表给出了下一跳地址之后,紧接着要确认这个下一跳是不是活的。方法有两个方向:一是看邻居表里有没有对应的二层地址映射,状态是否完整;二是直接对下一跳做连通性测试,但要注意测试包可能走的是和业务不同的路径,结论要谨慎采信。

邻居表里的条目状态很能说明问题。如果条目一直处于待解析状态,说明请求发出去了没回应,可能是对端没开、中间有隔离、或者地址配错;如果条目根本不存在,说明从来没有触发过解析,那就要回头看看是不是流量压根没走到这一步。

6.3 第三步:软件侧和硬件侧对齐了吗

这一步针对带专用转发芯片的设备。判断方法通常是看接口的转发统计:软件侧能查到包被正确转发,但硬件侧接口计数器不动,或者只有少量包动,基本可以确定是硬件表项没下发成功。

触发硬件下发失败的常见原因有三个:表项容量满、下发表项时出现冲突、以及驱动或固件层面的异常。第一种最普遍,尤其是在路由条目数量突然增长的场景下。验证方法是看当前已用表项数和上限的比值,以及有没有相关的溢出计数或告警。有些设备在表项满时不会报错,而是静默地把新路由丢在软件侧,这种"无声降级"最难查。

6.4 第四步:反向路径检查和各类过滤是不是在偷偷丢包

前面三步都正常,流量还是不通,就要考虑过滤机制了。反向路径检查的逻辑是:收到一个包,先看它的源地址从"来的这个接口"回不回得去,回不去就丢。这个机制在多出口、非对称路由的环境里非常容易误伤,表现就是单向通、或者特定来源的流量死活进不来。

类似的还有各类访问控制列表、防火墙规则、以及接口上的方向性过滤。排查这类问题的效率取决于你有没有在关键节点上抓包——抓包能立刻告诉你包是"没进来"还是"进来了被丢了",这一个信息就能砍掉一半的排查范围。

7. 容量、老化与规模:那些文档里不会写、但天天要面对的事

7.1 硬件表项的容量规划,比想象中更紧张

带专用芯片的设备,FIB容量是硬指标,而且这个指标通常是"共享池"的形式——IPv4路由、IPv6路由、访问控制列表、MAC表可能共用同一块存储。也就是说,你多配几千条访问控制条目,可能就把路由的可用空间挤掉了一部分。这个联动关系在很多产品文档里不会明说,但它真实存在。

规划的时候要留余量。我的习惯是实际路由规模不超过标称容量的六七成,剩下的留给突发增长、协议收敛期间的临时表项、以及后续业务扩展。如果业务会引入大量细粒度前缀(比如按目的地址做精细化调度),更要提前算清楚,别等到上线之后才发现装不下。

7.2 邻居表老化带来的间歇性不通

邻居表条目是有生命周期的,长时间没有流量就会老化删除,下次有流量时重新解析。正常情况下这个过程无感,但在高负载或者对端响应慢的场景下,会出现"解析请求发出去了,回包还没到,包已经被丢了"的短暂窗口。表现出来就是偶发的首包丢失或者间歇性不通,用持续ping很难复现,用高频短连接的业务最容易撞上。

缓解办法一般有两个方向:一是调整老化时间,让条目活得久一点;二是保证链路上有持续的低速保活流量,让条目不被老化。前者要注意别设太长导致地址变更后残留错误映射,后者要注意保活流量本身的开销。两边都要权衡。

7.3 收敛期间的表项抖动,以及一个实用的小习惯

路由协议收敛的时候,FIB表会经历一轮批量删除和批量下发。这个过程中如果表项数量接近容量上限,可能出现"删了旧的还没装新的"的短暂空洞,流量在这几百毫秒里会被丢弃。做变更窗口的时候,如果业务对丢包敏感,最好避开路由规模接近上限的时段,或者提前清理掉不再使用的路由条目腾出空间。

最后分享一个我自己养成的小习惯:每次排查转发类问题,先把"路由表视图"和"实际查表结果"两个东西并排打出来对比,再看一眼邻居表状态。这三样东西放在一起,八成的问题当场就能定位到是哪一层出了岔子。这个动作花不了两分钟,但比我早期那种"到处翻配置、凭感觉改参数"的做法效率高太多了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进