资讯动态

路由匹配:从按位与运算到最长前缀匹配原理

发布时间:2026/10/16 2:43:22 来源:尧图企业网站定制
1. 路由匹配到底在“匹配”什么先搞清楚最基础的问题网络包匹配路由这句话听着有点学院派但其实就是每个路由器、每一台Linux服务器、每一块网卡驱动都在做的事一个数据包到了查一张表决定把它从哪个口扔出去交给谁。不管你是搞运维、做开发还是刚学网络都得先把这个动作看透。我见过太多人一上来就研究OSPF、BGP结果连“匹配路由”这一步都没吃透。后面排查网络故障明明路由表里写着好几条路由数据包就是不走你想的那条最后发现是匹配规则搞错了。所以今天这篇我打算从最基础的东西讲起一个IP地址在计算机眼里就是一串32位的0和1而路由匹配的核心说白了就是拿这串0和1跟路由表里的条目做运算。具体做了什么运算就是“与”运算。路由匹配的最终原则也特别简单——最长前缀匹配。但原则背后的“为什么”以及“01与什么运算”是怎么支撑这个原则的才是真正值得展开的部分。1.1 一次数据包转发的完整逻辑先捋一遍数据包的旅程。假设你电脑上有个进程要访问某个IP数据包先从本机网卡发出去到了交换机再到路由器。路由器收到这个包不会直接凭感觉转发它一定会把自己的路由表掏出来拿数据包里的目的IP地址去跟表中的条目逐一比较。比较的方式就是“提取目的IP逐条看这个IP是否属于某条路由所描述的网络范围”。如果是就把数据包交给这条路由指定的下一跳和出接口。这个过程在教科书上叫路由查找route lookup在Linux内核里叫fib_lookup在路由器硬件里叫转发决策。我习惯打个比方你手里有一张写着地址的纸条到一个路口问路。路口站着好几个指引员每个指引员负责一片区域有的管整个城市有的只管某条街道。你要找的地址属于某个指引员负责的范围你就按他指的方向走。如果多个指引员都说自己管得着你听谁的肯定听管得最细的那个。路由匹配就是这个逻辑。所以路由匹配不是“能匹配就行”而是“匹配得最精确的才算数”。这就是最长前缀匹配后面我会详细算一遍。1.2 路由表里到底存的什么在理解匹配之前得先知道路由表里是什么。一条路由条目核心就三部分目的网络通常用网络地址加前缀长度表示比如192.168.1.0/24下一跳地址比如192.168.1.254出接口比如eth0、gigabitethernet0/0/1注意路由表里存的不是“某个具体IP”而是一个“网络范围”。192.168.1.0/24这个条目描述的是从192.168.1.0到192.168.1.255这256个地址而不是单独一个地址。设备拿目的IP跟这个范围比较判断是否落在里面。也因为这个原因路由表里天然就会存在重叠。比如我既可以写一条192.168.0.0/16的聚合路由也可以写一条192.168.1.0/24的明细路由。这两个范围是有包含关系的一个IP地址可能同时属于两者。要解决这个“同时匹配谁说了算”的问题就必须有一个统一的裁决规则。LPM就是这个规则。1.3 匹配结果决定了什么匹配结果决定了两个东西下一跳next hop和出接口outgoing interface。下一跳是“把包交给谁”出接口是“从自己哪个口送出去”。有时候下一跳为空比如目的网络就是直连网段那直接把包发给目的设备本身就行。这里有个特别容易被新手忽略的点匹配到的路由不仅影响转发方向还影响ARP行为、MTU选择、源地址选择多IP主机上尤其明显。比如一台服务器上有多个网卡分别连着不同的内网访问不同目标时走哪个源地址很大程度就是由路由查找结果决定的。我排过不少“能通但回包不对”的问题最后都是因为路由匹配到了默认路由导致出接口选错、源地址选错。所以搞清楚匹配规则是网络排查的基本功。2. 01 与什么的运算把位运算这件事讲透现在进入正题。标题里说“从01与啥的运算讲起”这里的“01”就是二进制位“与”就是按位与bitwise AND。要理解路由匹配必须先理解为什么是“与”。2.1 32位二进制视角下的IP地址一个IPv4地址比如192.168.1.132对人类来说是四个十进制数字但对计算机来说它就是一个32位的二进制数。换算一下192.168.1.132 11000000 10101000 00000001 10000100这32个bit被分成了网络部分和主机部分。网络部分标识“这是哪个网段”主机部分标识“这个网段里的哪台机器”。划分的边界由一个额外的参数决定——子网掩码。传统写法里子网掩码也是四个十进制数字比如255.255.255.0。但本质上它也是一串32位的二进制数特点是高位是连续的1低位是连续的0。1对应的位置就是网络位0对应的位置就是主机位。255.255.255.0写成二进制就是11111111 11111111 11111111 00000000前面的24个1说明这个IP地址的前24位是网络部分后8位是主机部分。所以192.168.1.0/24这个写法里的“/24”就是指掩码有24个连续的1和255.255.255.0完全等价。2.2 子网掩码就是一连串高位1从这个角度看子网掩码并不神秘。它就是一块“尺子”用来从IP地址里切出网络位。前缀越长网络位越多能容纳的主机就越少前缀越短网络位越少一个网段覆盖的地址范围越大。例如192.168.1.132/24网络位24位属于192.168.1.0/24这个网段192.168.1.132/25网络位25位属于192.168.1.128/25这个网段同一个IP地址因为掩码不同所属网段完全不同。这个例子特别重要它直接说明只看IP地址是不够的掩码才是划分网段的关键。路由匹配也一样匹配时永远是把“IP地址和掩码”放在一起看的。2.3 按位与计算网络地址判断一个IP是否属于某个网段方法非常机械把IP地址的每一位和掩码的每一位做“与”运算。规则只有四条0 AND 0 00 AND 1 01 AND 0 01 AND 1 1换句话说只有两个bit都是1结果才是1只要有一个0结果就是0。为什么用这个运算因为掩码里1的位置是网络位0的位置是主机位。IP地址跟掩码做按位与等于把主机位全部清零只保留网络位得到的就是这个网段的网络地址。我手算一遍。IP是192.168.1.132掩码是255.255.255.0IP: 11000000 10101000 00000001 10000100 掩码: 11111111 11111111 11111111 00000000 ----------------------------------------------- 与运算结果: 11000000 10101000 00000001 00000000结果是11000000 10101000 00000001 00000000换算成十进制就是192.168.1.0。所以192.168.1.132和掩码255.255.255.0的“与”结果是192.168.1.0刚好是路由条目里写的网络地址。只要这个结果等于路由条目里的网络地址就说明这个IP属于这个网段匹配成立。再把192.168.1.132放进192.168.0.0/16这个网段里算一遍。掩码变成255.255.0.0二进制是前16位为1IP: 11000000 10101000 00000001 10000100 掩码: 11111111 11111111 00000000 00000000 ----------------------------------------------- 与运算结果: 11000000 10101000 00000000 00000000结果是192.168.0.0也等于网段地址所以这条也匹配。同一个IP同时匹配两条路由这就是重叠路由的典型场景。为什么最后会选192.168.1.0/24因为它的前缀长度更长匹配更精确。这条规则稍后细说。2.4 为什么不用“或”或“异或”有人可能会问为什么匹配要用“与”不用“或”我想强调一下这个问题的价值理解了为什么用“与”才算真正理解了网络地址的语义。“或”运算的特点是只要有一个1结果就是1。拿IP和掩码做“或”会把主机位全部置1得到的是广播地址255.255.255.255方向而不是网络地址。这跟我们想要的“判断属于哪个网段”完全不同。“异或”运算的特点是相同为0、不同为1。如果拿IP和掩码做异或得到的结果既不是网段地址也不表示任何有意义的网络信息。所以“与”是唯一能实现“保留网络位、清零主机位”这个语义的运算。换句话说“与”运算在这里扮演的角色就是“筛子”——掩码是筛子1的网络位原样通过0的主机位全部清零。这台筛子筛出来的结果就是网络号。这个动作在网络领域有专门叫法提取网络地址network address calculation。2.5 一个小脚本演示与运算光看二进制不够直观我写段Python把上面的运算落成代码。这也是后面实现路由匹配的基础。def ip_to_int(ip: str) - int: parts ip.split(.) return (int(parts[0]) 24) | (int(parts[1]) 16) | (int(parts[2]) 8) | int(parts[3]) def int_to_ip(num: int) - str: return ..join([str((num 24) 0xFF), str((num 16) 0xFF), str((num 8) 0xFF), str(num 0xFF)]) def calc_network(ip: str, mask: str) - str: ip_int ip_to_int(ip) mask_int ip_to_int(mask) return int_to_ip(ip_int mask_int) print(calc_network(192.168.1.132, 255.255.255.0)) # 输出: 192.168.1.0 print(calc_network(192.168.1.132, 255.255.0.0)) # 输出: 192.168.0.0这里我用了位移和按位或来把四个字节拼成一个32位整数最后用按位与提取网络地址。这种写法在真实网卡、内核协议栈里到处都是不是什么高深技巧但干净利落。3. 匹配原则最长前缀匹配到底是怎么裁决的清楚了“与”运算之后匹配的底层机制就清楚了。但路由表里往往有几十上百条路由目的IP可能会跟好几条路由都匹配。这时必须有个裁决原则它就是最长前缀匹配Longest Prefix Match简称LPM。3.1 一个例子讲清LPM延续前面的场景。假设路由表里这样两条路由192.168.0.0/16下一跳10.0.0.1192.168.1.0/24下一跳10.0.0.2现在来了一个目的IP为192.168.1.132的数据包。按前面的运算192.168.1.132跟255.255.0.0做与结果是192.168.0.0命中第一条跟255.255.255.0做与结果是192.168.1.0命中第二条。两条都匹配但前缀长度一个是16一个是24。LPM规则说得很干脆谁的前缀长谁就赢。所以这个包会被交给10.0.0.2。道理也直白/16是个大方向/24是更精确的门牌号。既然有精确的门牌号就没理由走大方向。我举个生活化例子。你问“某大学在哪”别人先告诉你“在某个城市”又告诉你“在某路某号”。显然你会按更精确的地址走。路由匹配就是同一套逻辑只不过比较的对象是“网络位个数”不是“地址长短”。3.2 为什么默认路由总是最后兜底默认路由0.0.0.0/0前缀长度为0。跟任何目的IP做按位与因为掩码全是0结果永远是0.0.0.0所以默认路由会匹配所有目的地址。但正因为它前缀长度最短在和其他路由共存时它永远排在最后。只有所有更具体的路由都不匹配时默认路由才会生效。这个设计非常聪明。默认路由就像“找不到路时的最后退路”。如果没有这个机制任何一条默认路由都会把整个路由表的所有流量抢走那我们写明细路由就没意义了。LPM保证了默认路由“永远兜底永远不抢活”。实际网络里连接互联网的出口基本都是默认路由。内网访问外网时内网明细路由不匹配最终落到默认路由上。我遇到过某次内网访问某公有云服务不通排查发现内网有一条“黑洞路由”把该网段指到了丢弃口而且前缀长度比默认路由更长导致所有去往该网段的包被静默丢弃。这就是明细路由“抢走”默认路由流量的典型例子。3.3 从CIDR聚合理解LPM的存在价值CIDR无类域间路由出现后路由表可以不再按A/B/C类地址一刀切而是用任意长度的前缀来聚合网络。相邻的网段可以被聚合成一条路由例如192.168.0.0/24和192.168.1.0/24可以聚合成192.168.0.0/23。聚合让路由表大幅缩小也让LPM变得不可或缺。为什么因为聚合之后路由表中必然存在“大网络”和“小网络”同时指向不同下一跳的情况。如果设备只按“先匹配先得”或“随机匹配”那转发路径就不确定了。网络工程师也就无法通过写一条明细路由来把某些特定流量引到特定路径上策略路由和流量调度根本没法做。举一个实际中常见的例子。骨干网上游给你下发了两条路由一条是10.0.0.0/8另一条是10.1.0.0/16。前者走线路A后者走线路B。如果你想让10.1.0.0/16的流量走线路B其他10.x.x.x走线路A就得靠LPM保证/16比/8更优先。没有LPM这种“主备分流”策略就是空中楼阁。3.4 硬件与软件查找的差异TCAM vs 前缀树LPM规则虽然统一但软件和硬件的实现路径差别很大这里也值得展开。软件路由比如Linux服务器通常使用前缀树结构最常见的是radix trie。树上的每个节点对应一段二进制前缀查找时按目的IP的bit逐层往下走走到叶子节点就是匹配结果。因为树天然按前缀长度组织所以最长匹配很容易体现出来。Linux内核中查询路由时会先计算目的IP然后在fib_trie中查找复杂度跟前缀长度相关但整体非常快。硬件路由器则常用TCAM三态内容可寻址存储器。TCAM的特别之处在于每个存储位除了能存0和1还能存“X”就是“无所谓”。这个X恰好对应掩码里的0所以TCAM可以做到一次并行查询所有路由条目而不需要逐条计算。TCAM输出匹配结果时硬件会优先返回前缀最长的条目这个行为天然就是LPM。用TCAM做匹配的思路可以理解为“把路由表的每一个条目都画成一个模板所有模板同时去套一个IP谁套得最紧谁就赢”。这也是为什么高端路由器查表几乎不受路由表条目数量影响。预算够、性能要求高就上TCAM软路由图省事就靠CPU和前缀树。我在自己搭软路由时就喜欢在Linux上用ip route直接看LPM行为方便调试。毕竟硬件路由器通常只有命令行看不到内部数据结构不如Linux直观。4. 实操用位运算实现一个简易路由匹配理论讲再多不如自己动手写一遍。我用Python实现一个“用与运算做路由匹配”的小工具路由表用列表存匹配时逐条做按位与记录所有匹配项再按前缀长度排序选最长。这会让你对“01与”和“最长前缀匹配”的真实执行有非常直接的感受。4.1 Python实现基础工具函数先把IP和掩码转成统一的32位整数。上面已经写了ip_to_int和int_to_ip。接下来写一个“判断某IP是否匹配某条前缀”的函数def prefix_match(dst_ip: str, network: str, prefix_len: int) - bool: dst_int ip_to_int(dst_ip) net_int ip_to_int(network) mask_int (0xFFFFFFFF (32 - prefix_len)) 0xFFFFFFFF return (dst_int mask_int) net_int注意这里掩码是怎么生成的把0xFFFFFFFF左移(32 - prefix_len)位再截断到32位。比如prefix_len24就左移8位得到的前24位是1、后8位是0正好是255.255.255.0。这比手写四个十进制掩码更通用也跟真实协议栈里的做法一致。4.2 构建简易路由表并测试我构建三条路由10.0.0.0/8下一跳192.168.0.110.1.0.0/16下一跳192.168.0.20.0.0.0/0下一跳192.168.0.254用Python的namedtuple表示路由条目再写一个查找函数from collections import namedtuple Route namedtuple(Route, [network, prefix_len, nexthop]) routes [ Route(10.0.0.0, 8, 192.168.0.1), Route(10.1.0.0, 16, 192.168.0.2), Route(0.0.0.0, 0, 192.168.0.254), ] def lookup(dst_ip: str, routes): matched [] for r in routes: if prefix_match(dst_ip, r.network, r.prefix_len): matched.append(r) if not matched: return None return max(matched, keylambda r: r.prefix_len) print(lookup(10.1.2.3, routes)) # 期望匹配 Route(network10.1.0.0, prefix_len16, nexthop192.168.0.2)这里有两点值得说明。第一匹配列表里10.0.0.0/8和10.1.0.0/16都会出现但用max按prefix_len取最大自动实现了LPM。第二默认路由0.0.0.0/0因为prefix_len0永远不会被选中除非只它一条匹配。这个逻辑跟设备行为一致。4.3 观察“精确匹配 较长的前缀 默认路由”的顺序再测几个地址把结果打出来for dst in [10.1.2.3, 10.2.3.4, 10.1.0.1, 8.8.8.8]: r lookup(dst, routes) print(dst, -, r.nexthop if r else no route)输出会是这样10.1.2.3匹配/16下一跳192.168.0.210.2.3.4匹配/8下一跳192.168.0.110.1.0.1匹配/16下一跳192.168.0.2即使它在/8范围内8.8.8.8只匹配默认路由下一跳192.168.0.254这个结果跟真实路由器完全一致。10.1.0.1特别有意思它既属于10.0.0.0/8也属于10.1.0.0/16但因为/16更长所以被精确引到专用下一跳。整个路由表里“最长前缀”的决定性作用就这么体现出来了。4.4 用 ip route get 验证真实设备行为自己写的代码能跑通还要跟真实系统对照一下。Linux上查看一条目的IP实际走哪条路由用ip route getip route get 10.1.2.3输出会给出实际命中的路由表条目、源地址、出接口等信息。比如10.1.2.3 dev eth0 src 10.1.0.1如果系统里配置了默认路由和明细路由这个命令选用的也一定是最长匹配的条目。排查问题时我几乎每次都先用这个命令确认“系统视角”下到底该走哪条路再决定要不要看抓包结果。硬路由上对应命令是show ip route 具体IP也能直接显示最长匹配结果用法大同小异。5. 常见问题、踩坑与排查技巧最后聊点真正干活时用得到的经验。LPM规则本身不难难的是“你以为匹配到了实际却没匹配到”以及“明明匹配到了反而出了新问题”。我整理几个高频问题都是我摸过、踩过的。5.1 为什么看起来匹配了却不通最典型的情况路由表里有一条192.168.1.0/24目的IP也确实在这个网段里但包就是不通。这时候不要只盯匹配规则要往下看“匹配到的下一跳能不能到达”。如果下一跳ARP解析失败、出接口被关闭、下一跳自己没路由回包那包就丢在匹配之后了。我在某次模拟环境里就见过同一个目的地址ip route get明明指向了某台网关但网关侧没有配置回程路由导致包有去无回。问题不在匹配而在下一跳。排查时可以把“匹配到哪条路由”和“下一跳是否可达”分开检查这是两个完全不同的问题。5.2 掩码不连续、错误掩码的坑IPv4子网掩码合法形式必须是连续的1从最高位开始比如255.255.255.0合法255.255.0.255不合法。虽然某些硬件能接受奇怪的掩码但按标准规范来看不连续掩码属于非法配置会导致匹配结果不可预期。还有一类坑配置路由时把“前缀长度”和“十进制掩码”混用。比如有人写ip route add 192.168.1.0/255.255.255.0 via ...在部分环境下没问题但在另一些环境下语法不兼容容易引起混乱。建议统一用CIDR前缀长度写法减少跨平台坑。5.3 特殊地址段回环、组播、链路本地路由表里除了常规网段还有特殊地址段。127.0.0.0/8是回环地址只会出现在本机接口上不会真正发到网络里。224.0.0.0/4是组播地址段匹配组播路由的规则跟单播不同组播转发还涉及IGMP和组播路由协议。169.254.0.0/16是链路本地地址一般用于无DHCP环境下的零配置地址。如果某台设备的源地址或目的地址落在这些特殊段里普通路由匹配可能根本不适用。比如给一个组播目的IP做单播路由查找查不到默认路由也可能是正常的因为组播流量本来就不是按单播路由表转发的。别拿单播LPM规则往组播上套。5.4 路由优先级与AD值不是一回事路由条目还有一个属性叫AD值Administrative DistanceCisco叫管理距离华为等厂商也有类似概念。很多人把AD值和LPM搞混。其实两者管的是两件完全不同的事LPM管的是“同一种路由协议内多条路由都匹配时选谁”AD值管的是“不同路由协议之间哪条路由进路由表时优先级更高”。比如静态路由的AD值通常比OSPF直连学到的路由低所以静态路由会更优先进入路由表。但进入路由表之后条目之间的转发选择仍然由LPM决定。换句话说AD值影响“路由表里有没有这条”LPM影响“转发时用哪条”。这两个维度不能搞混。5.5 排查三板斧路由表、ip route get、抓包最后总结一下我最常用的排查套路。第一步看路由表。ip route show或show ip route确认有哪些条目、各条目前缀长度和下一跳。第二步用ip route get 目的IP确认具体某条流量匹配哪条路由。第三步抓包确认数据包实际走的路径。我曾经按这个顺序定位过一个“路由表写对了但网关策略把包丢了”的故障效率很高。如果手头有硬件设备命令可能是traceroute配合show ip route IP思路一样。先确认“匹配到了哪条”再确认“这条的下游是否正常”大部分路由类问题都能在几分钟内定位到具体环节。我个人经验是网络包匹配路由这件事越早把“与运算”和“最长前缀匹配”的关系想明白后面理解策略路由、等价负载、VPC网络都会轻松很多。它不只是一道面试题而是整个转发面的地基。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑