這篇筆記以哈希方式的路由項組織方式,介紹了路由數據庫的查詢過程,相關代碼文件如下:
源代碼路徑 | 說明 |
---|---|
net/ipv4/fib_hash.c | 哈希方式的路由數據庫的實現文件 |
include/net/ip_fib.h | 路由數據庫頭文件 |
1. 查詢條件: struct flowi
struct flowi {
//輸入輸出網絡設備接口索引
int oif;
int iif;
__u32 mark;
//網絡層地址信息,這裏關注IPv4
union {
struct {
__be32 daddr;
__be32 saddr;
__u8 tos;
__u8 scope;
} ip4_u;
struct {
struct in6_addr daddr;
struct in6_addr saddr;
__be32 flowlabel;
} ip6_u;
struct {
__le16 daddr;
__le16 saddr;
__u8 scope;
} dn_u;
} nl_u;
#define fld_dst nl_u.dn_u.daddr
#define fld_src nl_u.dn_u.saddr
#define fld_scope nl_u.dn_u.scope
#define fl6_dst nl_u.ip6_u.daddr
#define fl6_src nl_u.ip6_u.saddr
#define fl6_flowlabel nl_u.ip6_u.flowlabel
#define fl4_dst nl_u.ip4_u.daddr
#define fl4_src nl_u.ip4_u.saddr
#define fl4_tos nl_u.ip4_u.tos
#define fl4_scope nl_u.ip4_u.scope
//L4協議類型
__u8 proto;
__u8 flags;
//L4地址
union {
struct {
__be16 sport;
__be16 dport;
} ports;
struct {
__u8 type;
__u8 code;
} icmpt;
struct {
__le16 sport;
__le16 dport;
} dnports;
__be32 spi;
struct {
__u8 type;
} mht;
} uli_u;
#define fl_ip_sport uli_u.ports.sport
#define fl_ip_dport uli_u.ports.dport
#define fl_icmp_type uli_u.icmpt.type
#define fl_icmp_code uli_u.icmpt.code
#define fl_ipsec_spi uli_u.spi
#define fl_mh_type uli_u.mht.type
__u32 secid; /* used by xfrm; see secid.txt */
} __attribute__((__aligned__(BITS_PER_LONG/8)));
2. 查詢結果: struct fib_result
這些字段的值直接來自struct fib_info和struct fib_alias以及struct fib_node。
struct fib_result {
unsigned char prefixlen;
unsigned char nh_sel;
unsigned char type;
unsigned char scope;
struct fib_info *fi;
#ifdef CONFIG_IP_MULTIPLE_TABLES
struct fib_rule *r;
#endif
};
3. 路由查詢: fn_hash_lookup()
真正的路由查詢過程涉及路由緩存、策略路由查詢和路由數據庫查詢三部分,對於路由數據庫查詢,又按照路由項的組織方式不同,可以有hash和tire兩種方式,這裏僅僅看hash方式的路由數據庫查詢過程。
如果最終需要查詢路由數據庫,那麼是通過fn_hash_lookup()完成的,該函數原型如下:
int fn_hash_lookup(struct fib_table *tb, const struct flowi *flp, struct fib_result *res);
對於輸入還是輸出都是該函數,所以首先需要了解下對於輸入和輸出路由查詢時,查詢條件各自都是如何構造的。
3.1 輸入路由查詢條件
輸入路由是由ip_rcv_finish()觸發,但是查詢條件ip_route_input_slow()中構造的,相關代碼如下:
static int ip_rcv_finish(struct sk_buff *skb)
{
...
if (skb->dst == NULL) {
int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
}
...
}
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
u8 tos, struct net_device *dev)
{
struct flowi fl = { .nl_u = { .ip4_u =
{ .daddr = daddr,
.saddr = saddr,
.tos = tos, //tos字段就是來自IP首部的TOS
.scope = RT_SCOPE_UNIVERSE, //作用域爲UNIVERSE,最大
} },
.mark = skb->mark, //mark來自數據包中的mark標記,一般該標記在過magle表時設置
.iif = dev->ifindex }; //輸入設備索引
...
}
3.2 輸出路由查詢條件
這裏以ip_queue_xmit()中的條件爲例看輸出路由查詢條件是如何指定的,實際使用中,可能更高層的協議(比如TCP)在調用IP層接口發送數據前就已經進行了路由查詢,不過查詢條件的指定方式是一致的。
#define RT_CONN_FLAGS(sk) (RT_TOS(inet_sk(sk)->tos) | sock_flag(sk, SOCK_LOCALROUTE))
int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
{
...
struct flowi fl = { .oif = sk->sk_bound_dev_if, //可以通過SO_BINDTODEVICE選項將某個socket綁定到某個網卡
.nl_u = { .ip4_u =
{ .daddr = daddr,
.saddr = inet->saddr,
.tos = RT_CONN_FLAGS(sk) } }, //TCB中的tos字段來源需要進一步確定
.proto = sk->sk_protocol,
.uli_u = { .ports =
{ .sport = inet->sport,
.dport = inet->dport } } };
//scope未指定,那麼默認就是0,即UNIVERSE
...
}
3.3 fn_hash_lookup()
注意:調用該函數時,已經明確了當前要查找的路由表,這是由策略路由決定的。
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);
//之前有分析過,fn_zone_list是按照子網掩碼由大到小的順序排列的,即這裏從具體到一般的順序進行匹配
for (fz = t->fn_zone_list; fz; fz = fz->fz_next) {
struct hlist_head *head;
struct hlist_node *node;
struct fib_node *f;
//每個路由區代表一個子網掩碼,這裏結合查詢條件中的目的地址,相與後得到key,即網絡地址,進而查詢路由結點
__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) {
//尋找key值相等的路由結點
if (f->fn_key != k)
continue;
//找到了路由結點,說明目的地址匹配了,進一步檢查其它條件是否匹配
err = fib_semantic_match(&f->fn_alias, flp, res, f->fn_key, fz->fz_mask, fz->fz_order);
//1. 大於0表示繼續查詢後面的路由;
//2. 等於0表示路由查找成功,結束查找過程;
//3. 小於0表示沒有路由,路由查詢失敗,結束查詢過程。對於支持策略路由的情況,
//都不會這麼配置,而是將策略路由規則表的最後一條規則配置爲不可達,用來表示無路由
if (err <= 0)
goto out;
}
}
//如果當前路由表沒有找到合適的路由,那麼返回1,對於策略路由,這會繼續下一條策略規則的匹配
err = 1;
out:
read_unlock(&fib_hash_lock);
return err;
}
3.3.1 路由項匹配: fib_semantic_match()
調用該函數的前提是路由項的目的地址和要查詢的目的地址的網絡地址是匹配的,該函數用於檢查其它參數是否匹配。
@head:共享同一個路由結點的fa_alias列表
@zone: 網絡地址的大端表示;
@mask:子網掩碼的大端表示;
@prefixlen:子網掩碼長度
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;
//如果路由項中配置了tos,才檢查tos是否相等,如果沒有配置,那麼該字段就不作爲匹配條件
if (fa->fa_tos && fa->fa_tos != flp->fl4_tos)
continue;
//路由項的作用域值越大,表示本機距離路由項表示的目的地址越近。這裏要求路由項的作用域要比
//查詢條件的作用域大,符合設計思想,即通過該路由項路由後,數據包要離目的地址更近
if (fa->fa_scope < flp->fl4_scope)
continue;
//標記該fa_alias被訪問過了
fa->fa_state |= FA_S_ACCESSED;
//fib_props這個全局變量的設計沒有理解
err = fib_props[fa->fa_type].error;
if (err == 0) {
struct fib_info *fi = fa->fa_info;
//如果該路由項設置了DEAD標記,那麼表示該路由項即將被刪除,這種路由不能正常使用了,繼續查詢下一條
if (fi->fib_flags & RTNH_F_DEAD)
continue;
//尋找下一跳地址fib_nh
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;
}
4. 小結
從路由查詢過程可以看出,真正用於路由查詢的條件並不多:
- 目的地址,這是最關鍵的一個條件;
- TOS,基本上就是之IP首部的tos字段對應;而且如果路由項中如果不指定tos,那麼該條件忽略;
- scope:如果指定,那麼路由項的scope必須更加大,即離目的地址更近;
- 輸出接口:如果指定,那麼需要匹配;如果不指定,那麼該條件忽略;