Linux 路由 學習筆記 之三 路由查詢流程分析
上一節分析了路由的新增,本節接著分析路由的查詢流程,路由查詢流程也是被最多使用的介面。當裝置三層協議棧接收到資料包、傳送資料包等操作時,都要進行路由查詢操作。
對於路由的查詢,又分為兩個查詢過程,即不支援策略路由時的路由查詢函式,以及支援策略路由時的路由查詢流程,顯然支援策略路由時的查詢流程又相對複雜一些,所以本節主要分析支援策略路由時的路由查詢流程。
注:對於路由查詢,首先是查詢路由快取,只有在快取不命中時才會查詢路由,關於路由快取的查詢,此處不分析,後續會專門分析路由快取
在分析函式之前,先熟悉一下幾個資料結構:
/*
路由查詢結果相關的結構體
*/
struct fib_result {
/*掩碼長度*/
unsigned char prefixlen;
/*fib_info變數中的下一跳閘道器變數的index,根據該index值與struct fib_info結構
型別的變數,就能夠找到struct fib_nh結構的變數,從而就能夠獲取下一跳
閘道器相關的屬性*/
unsigned char nh_sel;
/*路由項的型別:為RTN_MULTICAST、RTN_UNICAST、RTN_BROADCAST等*/
unsigned char type;
/*路由項的scope:取值為RT_SCOPE_UNIVERSE、RT_SCOPE_LINK等*/
unsigned char scope;
#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
__be32 network;
__be32 netmask;
#endif
/*指向關聯的struct fib_info結構型別的變數*/
struct fib_info *fi;
#ifdef CONFIG_IP_MULTIPLE_TABLES
/*指向關聯的fib_rule結構的變數,用於策略路由*/
struct fib_rule *r;
#endif
};
/*
策略規則對應的資料結構
*/
struct fib_rule
{
/*將策略規則連結在一起*/
struct list_head list;
/*引用計數*/
atomic_t refcnt;
/*介面的index*/
int ifindex;
/*介面的名稱*/
char ifname[IFNAMSIZ];
/*mark值以及mark的掩碼值*/
u32 mark;
u32 mark_mask;
/*優先順序,值越小優先順序越大*/
u32 pref;
u32 flags;
/*路由表的id*/
u32 table;
/*fib rule的action規則,包括FR_ACT_TO_TBL等*/
u8 action;
struct rcu_head rcu;
};
/*
策略規則的操作相關的資料結構
*/
struct fib_rules_ops
{
/*對應的協議簇,對於ipv4為AF_INET*/
int family;
/*連結串列用於將該協議簇已新增的所有fib_rule規則連結在一起*/
struct list_head list;
int rule_size;
int addr_size;
/*協議相關的action函式*/
int (*action)(struct fib_rule *,
struct flowi *, int,
struct fib_lookup_arg *);
/*協議相關的規則匹配函式*/
int (*match)(struct fib_rule *,
struct flowi *, int);
/*協議相關的配置函式*/
int (*configure)(struct fib_rule *,
struct sk_buff *,
struct nlmsghdr *,
struct fib_rule_hdr *,
struct nlattr **);
int (*compare)(struct fib_rule *,
struct fib_rule_hdr *,
struct nlattr **);
int (*fill)(struct fib_rule *, struct sk_buff *,
struct nlmsghdr *,
struct fib_rule_hdr *);
u32 (*default_pref)(void);
size_t (*nlmsg_payload)(struct fib_rule *);
int nlgroup;
struct nla_policy *policy;
struct list_head *rules_list;
struct module *owner;
};
1.策略路由相關的路由查詢流程。
策略路由相關的路由查詢流程大概可以分為如下幾個方面:
a)策略規則的匹配
b)路由表中規則的匹配
1.1 策略規則的匹配
對於路由查詢,不管是策略路由,還是非策略路由的查詢,都是通過呼叫函式fib_lookup來實現的,只不過非策略路由的查詢,就直接在該函式裡對local表與main表裡的路由項進行匹配罷了。
這又涉及了幾個方面:
策略規則的通用引數的匹配
策略規則的協議相關的引數的匹配
策略規則的action操作。
下面先分析下這個函式
這個函式會被ip_route_output_slow、ip_route_input_slow呼叫,而ip_route_output_slow、ip_route_input_slow。
這兩個函式主要是為入口資料包和出口資料包查詢路由的函式,而呼叫這兩個函式
的函式只有在查詢路由快取不命中時,才會呼叫這兩個函式,在路由表中查詢路由。
功能:路由查詢
對於支援策略路由時的函式定義如下:
其中struct flowi*flp為傳入的路由查詢條件,而struct fib_result為
路由查詢結果
該函式也就是對fib_rules_lookup的封裝,主要的查詢由fib_rules_lookup實現
對於策略路由的查詢,主要應該包括兩個方面
1.策略匹配
2.根據策略匹配的結果,在相應的路由表繼續進行路由的查詢。
int fib_lookup(struct flowi *flp, struct fib_result *res)
{
struct fib_lookup_arg arg = {
.result = res,
};
int err;
err = fib_rules_lookup(&fib4_rules_ops, flp, 0, &arg);
res->r = arg.rule;
return err;
}
這個函式主要是呼叫函式 fib_rules_lookup完成策略的匹配與路由的查詢,接下來分析這個函式。
1.1.1 fib_rules_lookup
這個函式的功能是策略路由對應的路由查詢函式
1.遍歷傳入的ops變數的rules_list連結串列,對於每一個fib_rule
a)呼叫fib_rule_match進行fib_rule規則匹配
i)當規則匹配後,則呼叫傳入的ops變數的函式指標action進行
路由項的查詢(對於ipv4,fib4_rules_ops->action即為fib4_rule_action),當路由
查詢到後,則會呼叫fib_rule_get增加對匹配規則的引用計數,並
將arg->rule指向該規則的首地址
在fib_rule_match中,在完成了通用引數的match後,會呼叫協議相關的match函式,
對協議相關的引數進行match。而對於ipv4的match函式即為fib4_rule_match。
在match到相應的fib rule後,即會呼叫協議相關的action函式,進行action操作,對於
ipv4而言,即是fib4_rule_action。
fib_rule的action有FR_ACT_TO_TBL、FR_ACT_BLACKHOLE等,而我們使用fib rule主要是進行策略路由的,
因此,其action一般都是 FR_ACT_TO_TBL。基於策略路由的功能,我們 可以猜測fib4_rule_action函式
主要是根據table id找到相應的路由表,然後再呼叫路由表的查詢函式,根據傳入的條件,
查詢符合要求的路由,我們接著來分析一下fib4_rule_action是否與我們猜測的一樣。
int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,
int flags, struct fib_lookup_arg *arg)
{
struct fib_rule *rule;
int err;
rcu_read_lock();
list_for_each_entry_rcu(rule, ops->rules_list, list) {
if (!fib_rule_match(rule, ops, fl, flags))
continue;
err = ops->action(rule, fl, flags, arg);
if (err != -EAGAIN) {
fib_rule_get(rule);
arg->rule = rule;
goto out;
}
}
err = -ESRCH;
out:
rcu_read_unlock();
return err;
}
接著就按上面說的策略規則的三個方面開始說起,以使文章能夠結構化
1.1.2 策略規則的通用引數的匹配
對於這個功能,是通過函式fib_rule_match實現的(其實該函式通過呼叫協議相關的match函式也完成了協議相關的引數的匹配,此處這樣說有些不妥),下面分析這個函式
fib_rule_match函式首先會進行通用引數的match,主要是包括介面index、fwmark等match。接著才會呼叫協議相關的match函式,對協議相關的引數進行match操作。
這個函式完成的功能如下:
對於傳入的struct fib_rule *結構的指標rule,與傳入的路由查詢相關的struct flowi *
結構的指標fl
1.判斷輸入介面的index是否相等
2.判斷mark值是否相等
3.呼叫函式ops->match繼續進行fib_rule規則的匹配,對於ipv4,即為函式fib4_rule_match
4.如果fib_rule的規則是取反時,則返回的結果也需要進行取反操作
(不過目前通過ip rule新增的規則是不允許使用取反的,所以第四個判斷操作目前
來說均是返回ret)
*/
static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,
struct flowi *fl, int flags)
{
int ret = 0;
if (rule->ifindex && (rule->ifindex != fl->iif))
goto out;
if ((rule->mark ^ fl->mark) & rule->mark_mask)
goto out;
/*對於ipv4協議,其match函式為fib4_rule_match*/
ret = ops->match(rule, fl, flags);
out:
return (rule->flags & FIB_RULE_INVERT) ? !ret : ret;
}
1.1.3 策略規則的協議相關的引數的匹配
接著上面的函式,對於ipv4,其協議相關的match函式為fib4_rule_match函式,我們分析一下這個函式。
這個函式主要是對源ip地址、目的ip地址以及tos的匹配操作。
(fib rule的新增類似於如下命令:
# ip rule add fwmark 0x4/0x40004 from 192.168.1.1/32 to 192.168.33.9/24 tos 10
iif br0 table 231)
static int fib4_rule_match(struct fib_rule *rule, struct flowi *fl, int flags)
{
struct fib4_rule *r = (struct fib4_rule *) rule;
__be32 daddr = fl->fl4_dst;
__be32 saddr = fl->fl4_src;
if (((saddr ^ r->src) & r->srcmask) ||
((daddr ^ r->dst) & r->dstmask))
return 0;
if (r->tos && (r->tos != fl->fl4_tos))
return 0;
return 1;
}
1.1.4 策略規則的協議相關的action操作
在函式fib_rules_lookup裡,我們知道,當完成上述1.1.2、1.1.3的match操作,找到符合條件的路由表後,即會呼叫協議相關的action操作。在分析v4協議相關的策略規則ops的action操作之前,我們先看下的策略規則的action型別有哪些
/*fib rule的action型別,FR_ACT_TO_TBL即該fib rule與路由表關聯*/
enum
{
FR_ACT_UNSPEC,
FR_ACT_TO_TBL, /* Pass to fixed table */
FR_ACT_RES1,
FR_ACT_RES2,
FR_ACT_RES3,
FR_ACT_RES4,
FR_ACT_BLACKHOLE, /* Drop without notification */
FR_ACT_UNREACHABLE, /* Drop with ENETUNREACH */
FR_ACT_PROHIBIT, /* Drop with EACCES */
__FR_ACT_MAX,
};
當我們建立一個策略規則,且與路由表的id關聯時,即是選擇了action FR_ACT_TO_TBL,對於策略路由來說,肯定是這個action的。
下面分析下v4的action函式fib4_rule_action
/*
功能:ipv4的fib rule 的操作函式action
1.根據rule的action規則決定後續操作,對於支援策略路由而言,我們建立fib rule的
action均是FR_ACT_TO_TBL,而對於FR_ACT_UNREACHABLE、FR_ACT_PROHIBIT、FR_ACT_BLACKHOLE等均是
返回相應的失敗碼。而對於FR_ACT_TO_TBL,則需要進一步進行路由項的查詢
2.對於FR_ACT_TO_TBL,則根據fib_rule->table的table id值,呼叫函式fib_get_table獲取相應的路由
表,接著就是呼叫路由表的查詢路由函式tb_lookup進行路由項的查詢,對於ipv4
其路由表的tb_lookup即為函式fn_hash_lookup
*/
static int fib4_rule_action(struct fib_rule *rule, struct flowi *flp,
int flags, struct fib_lookup_arg *arg)
{
int err = -EAGAIN;
struct fib_table *tbl;
switch (rule->action) {
case FR_ACT_TO_TBL:
break;
case FR_ACT_UNREACHABLE:
err = -ENETUNREACH;
goto errout;
case FR_ACT_PROHIBIT:
err = -EACCES;
goto errout;
case FR_ACT_BLACKHOLE:
default:
err = -EINVAL;
goto errout;
}
if ((tbl = fib_get_table(rule->table)) == NULL)
goto errout;
err = tbl->tb_lookup(tbl, flp, (struct fib_result *) arg->result);
if (err > 0)
err = -EAGAIN;
errout:
return err;
}
這個函式會根據策略規則繫結的路由表id,通過函式fib_get_table找到相應的路由表,然後再根據路由表的tb_lookup函式進行路由項的匹配操作。這才是本小節的重點。
1.1.5 路由表項的查詢匹配函式fn_hash_lookup
功能:路由表結構fib_table->tb_lookup對應的處理函式
1.遍歷路由表結構fib_table變數指向的fn_hash變數的fn_zone_list連結串列
a)根據目的ip地址與fn_zone變數,構建搜尋關鍵字k
b)遍歷該fn_zone變數的hash連結串列指標fz_hash的每一個hash連結串列
i)對於每一個fib_node變數,若其fn_key與剛才構造的搜尋關鍵字k相等
呼叫fib_semantic_match進行fib_alias、fib_nh變數的匹配,若匹配則fib_semantic_match返回0;
若不匹配則fib_semantic_match返回非0值。
若fib_semantic_match返回大於0值,則繼續遍歷
若fib_semantic_match返回小於或等於0值,則說明出現錯誤或者路由查詢成功,
此時則程式返回。
ii)若不相等,則繼續執行b)
(即進行最長掩碼匹配查詢,先從掩碼最大的fn_zone變數開始進行路由項匹配,只有在不匹配時才會繼續遍歷掩碼長度次之的fn_zone變數)
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_semantic_match
這個函式就是根據傳入的條件,判斷已存在的路由項是否match
該函式會遍歷傳入的連結串列head,搜尋符合條件的fib_alias變數與fib_info變數
1.遍歷連結串列head,查詢符合條件的fib_alias變數
a)當fa_tos不等於傳入的fowi結構變數對應的fl4_tos成員值;或者fa_tos
與fl4_tos的值相等,但是fa_scope值小於傳入的fowi結構變數對應的
fl4_scope成員值時,則繼續進行遍歷操作
b)若fa_tos與fl4_tos的值相等,且fa_scope值大於或者等於傳入的fowi結構
變數對應的fl4_scope成員值時,則進一步進行fib_info中的fib_nh型別的
變數的匹配操作
i)遍歷上述b中查詢的fib_info結構變數的fib_nh[]陣列查詢是否有滿足要求
的fib_nh變數
若傳入的oif的值為0或者傳入的oif的值與fib_nh->oif相等,且該fib_nh結 構變數的nh_flags值的RTNH_F_DEAD位不為1,則認為查詢到符合條件的 路由項。並對返回值res(fib_result結構)的prefixlen、nh_sel、type、scope、fi 等成員值進行賦值,並增加對fib_clntref的引用計數
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_DEBUG "impossible 102\n");
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;
#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
res->netmask = mask;
res->network = zone & inet_make_mask(prefixlen);
#endif
atomic_inc(&res->fi->fib_clntref);
return 0;
}
至此就將策略路由相關的路由查詢函式分析完了,下面分析非策略路由的查詢流程
2.非策略路由的查詢流程
static inline int fib_lookup(const struct flowi *flp, struct fib_result *res)
{
if (ip_fib_local_table->tb_lookup(ip_fib_local_table, flp, res) &&
ip_fib_main_table->tb_lookup(ip_fib_main_table, flp, res))
return -ENETUNREACH;
return 0;
}
非策略路由的查詢,就是先在local表中查詢,當沒有查詢到後,則會再main表中繼續查詢。與策略路由查詢相比,沒有了策略規則的匹配過程,也沒有了路由表的選擇過程。
看到這,相信有人要問了,如果我編譯核心時支援了策略路由選項,但是實際建立路由時,又沒有建立策略規則,那路由的查詢是怎樣的呢?是不是也是先查詢local表,不匹配時才會查詢main表呢?
我們先看下ipv4的策略規則的初始化函式
void __init fib4_rules_init(void)
{
list_add_tail(&local_rule.common.list, &fib4_rules);
list_add_tail(&main_rule.common.list, &fib4_rules);
list_add_tail(&default_rule.common.list, &fib4_rules);
fib_rules_register(&fib4_rules_ops);
}
在初始化函式裡,首先在策略規則裡添加了local、main、default3個策略規則,我們再看下這三個規則的定義:
預設建立3個fib_rule規則,而fib rule規則新增到相應協議簇
的fib_rules_ops的list連結串列中時,是根據pref的優先順序來進行新增
到,pref的值越小,而優先順序越大。
而在ipv4的fib rule的初始化中,首先建立了default_rule、main_rule、local_rule
並分別新增到fib4_rules_ops.list中,因此以後新增的ipv4 fib rule規則,即使
優先順序最大,也是在local_rule規則之後。
因此,在使用策略路由查詢時,首先就會匹配local_rule規則,即進入
local路由表中進行路由匹配,其次才會匹配其他的fib rule規則
以下3個預設ipv4 fib rule是沒有設定匹配條件的,即只要遍歷到
下面3個ipv4 fib rule規則,即會匹配。
這樣的話,當建立的路由項沒有與策略規則關聯,且系統中沒有建立新的路由表(除了local、main、default),則在策略規則的匹配時,首先就會匹配local規則,接著就進入local表中進行路由項的查詢與匹配。在不匹配時則會進入main規則,即會進行main表進行路由項的查詢與匹配。而當系統建立了新的路由表,且有策略規則與該路由表繫結後,則查詢路由時,照樣會先匹配local表的路由,只有在local表不命中時,才會繼續進行後面優先順序的策略規則的匹配。
以上所有就解釋了上面提出的疑問,關於策略規則的初始化、規則的新增、規則的刪除等功能,後續會專門用一小節進行分析。
static struct fib4_rule default_rule = {
.common = {
.refcnt = ATOMIC_INIT(2),
.pref = 0x7FFF,
.table = RT_TABLE_DEFAULT,
.action = FR_ACT_TO_TBL,
},
};
static struct fib4_rule main_rule = {
.common = {
.refcnt = ATOMIC_INIT(2),
.pref = 0x7FFE,
.table = RT_TABLE_MAIN,
.action = FR_ACT_TO_TBL,
},
};
static struct fib4_rule local_rule = {
.common = {
.refcnt = ATOMIC_INIT(2),
.table = RT_TABLE_LOCAL,
.action = FR_ACT_TO_TBL,
.flags = FIB_RULE_PERMANENT,
},
};
至此,完成了路由查詢流程的分析,下一節分析路由的刪除以及路由快取相關的流程和策略規則功能的分析。