得到所使用的路由表的索引後,就可以從 current->nsproxy->net_ns.ipv4.fib_table_hash 表中,得到路由表的指針,路由表是使用 fib_table 表示的(見上一往篇的圖)。在 fib_table 結構中,它會指定查找該表的方法,這裏是 fn_hash_lookup, 並且在該結構體的後面,會根據子網掩碼的長度,組成一個 33 個元素的數組,分別代表子網掩碼的 [0-32],一個路由表中的相同子網掩碼的路由項會被散列到對應的子網掩碼所代表的散列表中,因爲往往路由表中使用的子網掩碼長度往往較少,常用的如,8, 16, 24,32,所以以 fn_zone_list 爲頭的鏈表會把所有使用的項連接起來。當進行路由查找時,會從子網最長的開始,依次按順序查找,最後子網爲 0 的項,就是默認路由,因爲子網查找時,子網掩碼越長,表示的子網越精確。
子網掩碼長度相同的路由項,由 fn_zone 表示,並進行散列。想想現在進行到哪裏了,確定了路由表,然後開始從子網掩碼最長的子網匹配,確定目標主機與哪個子網匹配,所以根據目標主機的 IP 與此時的掩碼長度可以得到與它匹配的子網。看代碼:
static int
fn_hash_lookup(struct fib_table *tb, const struct flowi *flp, struct fib_result *res)
{
int err;
struct fn_zone *fz;
struct fn_hash *t = (struct fn_hash*)tb->tb_data;
read_lock(&fib_hash_lock);
for (fz = t->fn_zone_list; fz; fz = fz->fz_next) {
struct hlist_head *head;
struct hlist_node *node;
struct fib_node *f;
__be32 k = fz_key(flp->fl4_dst, fz);
head = &fz->fz_hash[fn_hash(k, fz)];
hlist_for_each_entry(f, node, head, fn_hash) {
if (f->fn_key != k)
continue;
err = fib_semantic_match(&f->fn_alias,
flp, res,
f->fn_key, fz->fz_mask,
fz->fz_order);
if (err <= 0)
goto out;
}
}
err = 1;
out:
read_unlock(&fib_hash_lock);
return err;
}
fib_node 結構體就代表一個子網,這裏面對相同子網掩碼的子網進行散列的算法比較簡單,這裏不講,當找到與目標主機匹配的子網以後,即 fib_node->fn_key == dst & mask ,彷彿目標就要實現了,已經找到了代表目標子網的路由項,取出下一路以及網卡出口,就可以發包了,原來是這樣的,但 linux 的路徑做的比較精細,看代碼:
int fib_semantic_match(struct list_head *head, const struct flowi *flp,
struct fib_result *res, __be32 zone, __be32 mask,
int prefixlen)
{
struct fib_alias *fa;
int nh_sel = 0;
list_for_each_entry_rcu(fa, head, fa_list) {
int err;
if (fa->fa_tos &&
fa->fa_tos != flp->fl4_tos)
continue;
if (fa->fa_scope < flp->fl4_scope)
continue;
fa->fa_state |= FA_S_ACCESSED;
err = fib_props[fa->fa_type].error;
if (err == 0) {
struct fib_info *fi = fa->fa_info;
if (fi->fib_flags & RTNH_F_DEAD)
continue;
switch (fa->fa_type) {
case RTN_UNICAST:
case RTN_LOCAL:
case RTN_BROADCAST:
case RTN_ANYCAST:
case RTN_MULTICAST:
for_nexthops(fi) {
if (nh->nh_flags&RTNH_F_DEAD)
continue;
if (!flp->oif || flp->oif == nh->nh_oif)
break;
}
#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (nhsel < fi->fib_nhs) {
nh_sel = nhsel;
goto out_fill_res;
}
#else
if (nhsel < 1) {
goto out_fill_res;
}
#endif
endfor_nexthops(fi);
continue;
default:
printk(KERN_WARNING "fib_semantic_match bad type %#x\n",
fa->fa_type);
return -EINVAL;
}
}
return err;
}
return 1;
out_fill_res:
res->prefixlen = prefixlen;
res->nh_sel = nh_sel;
res->type = fa->fa_type;
res->scope = fa->fa_scope;
res->fi = fa->fa_info;
atomic_inc(&res->fi->fib_clntref);
return 0;
}
當確定子網後,由於存在相同子網,但 tos, scope 不同的路由項,路由項是由 fib_alias 來表示的。這裏 linux 還會更加精細地判斷,tos (Type Of Service), 表示的是一種優先級,由 setsockopt 的 IP_TOS 選項可以設置一個 socket 的 TOS,而在進行路由查找時,就可以根據設置的 tos 計算出 scope, 具體代碼爲 ip_route_output_slow 中,
u32 tos = RT_FL_TOS(oldflp);
struct flowi fl = { .nl_u = { .ip4_u =
{ .daddr = oldflp->fl4_dst,
.saddr = oldflp->fl4_src,
.tos = tos & IPTOS_RT_MASK,
.scope = ((tos & RTO_ONLINK) ?
RT_SCOPE_LINK :
RT_SCOPE_UNIVERSE),
} },
.mark = oldflp->mark,
.iif = net->loopback_dev->ifindex,
.oif = oldflp->oif };
這裏的 scope 是由 tos 來的,它表示了該 socket 想在什麼範圍內路由,如果 socket 只想在 LINK 範圍內路由,但路由項的 scope 卻是 UNIVERSE, 說明子網太遠了,即 fa->fa_scope < flp->fl4_scope, 這樣的路由是不合適的,同理,如果路由項設置了 tos, 而 socket 的 tos 不匹配,則這樣的路由項也是不合適的,由此可以根據這兩項進一步確定合適的路由。
當確定了合適的路由項後,如果內核配置了 CONFIG_IP_ROUTE_MULTIPATH,什麼是路由的多路徑,這裏解釋一下,即一條路由項可能存在多個下一跳的地址,即多個下一路均可到達該路由項表示的子網,就叫做多路徑,顯然多路說明這兩條路徑都是可以使用的,這可以用來作負載均衡。確定了路由項後,路由查找就算大致結束了。
最後根據一定的算法來決定使用哪一個下一跳地址,還有出口。
這兩篇文章講述了 linux 下策略路由的框架及一些重要環節的實現,拋開了 cache 等一些細節,都可以在代碼中瞭解到,知道了整個脈絡,結合上一篇的結構圖,那麼路由的機制也將可以順利地理解了。