Skip to content

第一百一十章:海图

问道期

涉及内核源码:

林小源走到码头的尽头,看到了一座灯塔。

灯塔不高,但位置极好——矗立在海岸线的拐角处,俯瞰着整片海域。塔顶没有灯火,而是一张巨大的桌子,桌上铺满了海图。一个头发花白的老人戴着老花镜,正用放大镜仔细地比对着什么。

"你在找路?" 林小源问。

老人头也不抬:"不是我在找路。是每一个从这里经过的数据包,都在找路。"

他从桌上拿起一张海图,指着上面密密麻麻的线条和标记:"这是路由表。每个数据包带着一个目标地址来——比如 10.0.0.20。我的工作就是告诉它:该往哪走。"

海图上画着三行记录:

目标网络          下一跳           接口
192.168.1.0/24    直接            eth0
10.0.0.0/8        192.168.1.1     eth0
0.0.0.0/0         192.168.1.1     eth0

"第一条,192.168.1.0/24——目标在同一个子网里,直接送,不需要经过网关。第二条,10.0.0.0/8——目标在 10 开头的网络里,送到 192.168.1.1 这个网关,让它转交。第三条……"

他敲了敲最后一行:"0.0.0.0/0——默认路由。如果前面的条目都匹配不上,就走这条路。通常指向网关。"

林小源看着海图,感觉像在看一张城市的公交线路图——每条线路都有起点、终点和中转站。

"前辈,一个数据包可能匹配多条路由吗?"

老人终于抬起头来,眼镜片后面的眼睛精光一闪:"好问题。会。这时候就看谁的前缀最长。"

林小源盯着海图看了一会儿——每一条线路都是一个决策,告诉数据包该往哪走。

老人从抽屉里拿出一把尺子,放在海图上。

"假设一个数据包要去 192.168.1.100。你看,它同时匹配两条路由——192.168.1.0/240.0.0.0/0。"

他用尺子量了量两条路由的前缀长度。"/24/0 长。前缀越长,匹配越精确。所以走第一条——直连路由,直接送到目标机器。"

"这就是最长前缀匹配?"

"对。" 老人放下尺子,"你想象一下,你要寄一封信到'北京市海淀区中关村大街 1 号'。'北京市'能匹配,'北京市海淀区'也能匹配,'北京市海淀区中关村大街 1 号'也能匹配。你当然选最精确的那个——前缀最长的那个。"

他在海图上画了一个圈:"路由表里的条目就是这么工作的。/8 匹配前 8 位,/24 匹配前 24 位,/32 匹配全部 32 位——那就是精确到一台主机。/0 什么都不匹配,那就是'去哪儿都行'的兜底。"

林小源拿起尺子自己量了一遍。"所以默认路由的前缀长度是 0,永远是最不精确的?"

"没错。它就是最后的保底。前面没有更具体的路由了,才轮到它。"

归根结底,最长前缀匹配就是"越具体越优先"——跟寄信一样,地址写得越详细,送得越准。

老人把海图卷起来,走到灯塔的窗边。

"你知道路由是怎么来的吗?" 他望着窗外的海面。

"直连路由是自动生成的——网卡配了 IP 地址,内核就知道这个子网直连。静态路由是管理员手动加的——ip route add。动态路由是路由协议学来的——RIP、OSPF、BGP,路由器之间互相交换信息,自动计算最优路径。"

"动态路由……路由器之间会说话?"

"当然。" 老人转过身来,"OSPF 用链路状态算法——每个路由器知道整个网络的拓扑,自己算最短路径。BGP 用路径向量算法——自治系统之间交换可达性信息。这些协议让互联网能够自动适应变化——某条链路断了,路由重新收敛,数据包走另一条路。"

他指了指灯塔下面的一台机器。那是一台路由器,面板上闪烁着无数小灯,每个灯代表一条活跃的路由。

"但这台机器的核心是 FIB——Forwarding Information Base。路由表里的信息经过查找算法处理后,存进 FIB 里。数据包来了,查 FIB,拿到下一跳,转发。整个过程在纳秒级完成。"

"这么快?"

"Linux 用的是 LC-trie 查找——一种压缩的前缀树。最长前缀匹配在 trie 上走一遍就能完成,时间复杂度跟地址长度有关,跟路由表大小关系不大。" 老人拍了拍那台机器,"所以路由表再大,查找也不会慢到哪里去。"

默认路由就是兜底——前面没有更具体的路了,才轮到它上场。

c
/*
 * 路由的概念:
 *
 *   主机 A (192.168.1.10)
 *     ↓
 *   默认网关 (192.168.1.1)
 *     ↓
 *   路由器 (10.0.0.1)
 *     ↓
 *   主机 B (10.0.0.20)
 *
 * 路由表:
 *   目标网络        下一跳         接口
 *   192.168.1.0/24  直接          eth0
 *   10.0.0.0/8      192.168.1.1   eth0
 *   0.0.0.0/0       192.168.1.1   eth0
 *
 * IP 地址:
 *   网络部分 + 主机部分
 *   192.168.1.10/24
 *   网络: 192.168.1.0
 *   主机: 10
 *
 * 子网掩码:
 *   255.255.255.0 = /24
 *   区分网络和主机部分
 */

printf("=== 路由 — 数据包的海图 ===\n\n");

printf("路由的概念:\n\n");
printf("  主机 A (192.168.1.10)\n");
printf("    ↓\n");
printf("  默认网关 (192.168.1.1)\n");
printf("    ↓\n");
printf("  路由器 (10.0.0.1)\n");
printf("    ↓\n");
printf("  主机 B (10.0.0.20)\n\n");

printf("--- 路由表 ---\n");
printf("%-20s %-15s %-8s\n",
       "目标网络", "下一跳", "接口");
printf("%-20s %-15s %-8s\n",
       "---", "---", "---");
printf("%-20s %-15s %-8s\n",
       "192.168.1.0/24", "直接", "eth0");
printf("%-20s %-15s %-8s\n",
       "10.0.0.0/8", "192.168.1.1", "eth0");
printf("%-20s %-15s %-8s\n",
       "0.0.0.0/0", "192.168.1.1", "eth0\n");

printf("--- IP 地址结构 ---\n");
printf("192.168.1.10/24:\n");
printf("  网络部分: 192.168.1.0\n");
printf("  主机部分: 10\n");
printf("  子网掩码: 255.255.255.0\n\n");

printf("--- 路由查找 ---\n");
printf("1. 查找目标 IP\n");
printf("2. 匹配最长前缀\n");
printf("3. 获取下一跳\n");
printf("4. 转发数据包\n\n");

printf("--- 查看路由 ---\n");
printf("ip route show\n");
printf("  default via 192.168.1.1 dev eth0\n");
printf("  192.168.1.0/24 dev eth0\n");
printf("  10.0.0.0/8 via 192.168.1.1\n\n");

printf("--- 路由类型 ---\n");
printf("直连路由: 同一子网\n");
printf("静态路由: 手动配置\n");
printf("动态路由: 路由协议(RIP, OSPF)\n");

道藏笔记

内核启示

灯塔老人的海图说白了就是路由表——每条记录告诉数据包"目标网络在哪、下一跳是谁、从哪个接口出去"。

路由查找的核心是最长前缀匹配:同时匹配多条路由时,选前缀最长的那个,因为越长越具体。就像寄信到"北京市海淀区中关村大街 1 号",你当然选最精确的地址。默认路由 0.0.0.0/0 前缀长度是 0,永远是最不精确的,只有前面没有更具体的路由时才轮到它。

路由有三种来源:直连路由是网卡配了 IP 自动生成的,静态路由是管理员用 ip route add 手动加的,动态路由是 RIP、OSPF、BGP 这些路由协议在路由器之间交换信息学来的。Linux 用 LC-trie 做最长前缀匹配,时间复杂度跟地址长度有关,跟路由表大小关系不大——路由表再大,查找也不会慢。


破关试炼

路由之试

海图一章中,手动添加路由时正文给出的命令前缀是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道