数据包要往哪发,是路由表说了算。netstat -rn 里那一串 flags 和 link#14 是什么意思?为什么发往公网的包走默认路由、发往邻居的包不经网关?“最长前缀匹配”到底怎么匹配?路由表很大的时候怎么查才快?这篇用本机的真实路由表和 route get 一项项验证。
Destination Gateway Flags Netif Expire default <本机局域网IP> UGScg en0 127 127.0.0.1 UCS lo0 127.0.0.1 127.0.0.1 UH lo0 169.254 link#14 UCS en0 ! <本机局域网IP>/22 link#14 UCS en0 ! 224.0.0/4 link#14 UmCS en0 ! 255.255.255.255/32 link#14 UCS en0 ! 出现过的 flags 字母含义: U=路由可用;G=下一跳是网关;H=主机路由(/32);S=静态(手动 / 系统添加);C=可克隆:用到时为每个目的地克隆一条主机路由;c=该路由是克隆出来的;g=默认路由标志(网关路由);L=带链路层地址(ARP 邻居);W=由克隆产生;I=绑定到特定接口(ifscope);i=地址已解析完成;m=组播;b=广播
目的地、网关、标志、出口接口、寿命。网关栏写 link#N 表示直连,写地址表示经这个网关转发;U / G / H / S / C / L 等标志说明路由的性质。
回环 目的地 127.0.0.1 → 匹配的路由 127.0.0.1/? 网关 (直连) 接口 lo0 标志 <UP,HOST,DONE,LOCAL> 本机自己的 en0 地址目的地 <本机局域网IP> → 匹配的路由 <本机局域网IP>/? 网关 (直连) 接口 lo0 标志 <UP,HOST,DONE,LLINFO,WASCLONED,LOCAL,IFSCOPE,IFREF> 同网段的邻居 目的地 <本机局域网IP> → 匹配的路由 <本机局域网IP>/255.255.252.0 网关 (直连) 接口 en0 标志 <UP,DONE,CLONING,STATIC> 公网地址 目的地 8.8.8.8 → 匹配的路由 default/default 网关 <本机局域网IP> 接口 en0 标志 <UP,GATEWAY,DONE,STATIC,PRCLONING,GLOBAL> 组播 目的地 224.0.0.1 → 匹配的路由 224.0.0.0/240.0.0.0 网关 (直连) 接口 en0 标志 <UP,DONE,CLONING,STATIC,MULTICAST> 链路本地 目的地 169.254.1.1 → 匹配的路由 169.254.0.0/255.255.0.0 网关 (直连) 接口 en0 标志 <UP,DONE,CLONING,STATIC>
route get 给出内核对某个目的地选中的那条路由:本机地址走 lo0,同网段直连,公网走 default 经网关,组播和链路本地走各自的网段路由。
127.0.0.1 我的 LPM: /32 接口 lo0 内核: 接口 lo0 一致 <本机局域网IP> 我的 LPM: /32 接口 en0 内核: 接口 lo0 不一致 <本机局域网IP> 我的 LPM: /32 接口 en0 内核: 接口 en0 一致 <本机局域网IP> 我的 LPM: /32 接口 en0 内核: 接口 en0 一致 8.8.8.8 我的 LPM: /0 接口 en0 内核: 接口 en0 一致 1.1.1.1 我的 LPM: /0 接口 en0 内核: 接口 en0 一致 224.0.0.251 我的 LPM: /32 接口 en0 内核: 接口 en0 一致 255.255.255.255 我的 LPM: /32 接口 en0 内核: 接口 en0 一致 169.254.100.1 我的 LPM: /16 接口 en0 内核: 接口 en0 一致 10.0.0.1 我的 LPM: /0 接口 en0 内核: 接口 en0 一致 172.16.0.1 我的 LPM: /0 接口 en0 内核: 接口 en0 一致 203.0.113.9 我的 LPM: /0 接口 en0 内核: 接口 en0 一致 netstat -rn 里有没有本机自己 en0 地址的那条路由: 有(route get 却能查到一条标志含 LOCAL、出口 lo0 的主机路由) 12 个目的地里 11 个一致。多条路由同时匹配时,总是前缀最长的赢——例如本机自己 en0 的地址同时匹配 default(/0)、局域网(/22)、主机路由 /32,选的是 /32 路由表里一共有 206 条路由,前缀分布 [0, 4, 8, 16, 22, 32]
所有匹配的路由里取前缀最长的;default 是 /0,是没有更具体路由时的兜底。实测我的实现与内核 11/12 一致,例外的是本机地址。
逐条扫描:5000 次查询 6406 ms(每次 1281 µs);按前缀长度分桶 + 哈希:同样 5000 次 4 ms(每次 0.9 µs),两种方法结果一致 分桶哈希对 5 万次查询 43 ms;最多只要比较 8 个前缀长度,与路由条数无关——真实路由器用 trie 或硬件 TCAM,思路是一样的:不要逐条比
逐条扫描随表增大线性变慢;按前缀长度分桶加哈希,比较次数只取决于不同前缀长度的个数。