上一节分析了路由的添加,本节接着分析路由的查找流程,路由查找流程也是被最多使用的接口。当设备三层协议栈接收到数据包、发送数据包等操作时,都要进行路由查找操作。
对于路由的查找,又分为两个查找过程,即不支持策略路由时的路由查找函数,以及支持策略路由时的路由查找流程,显然支持策略路由时的查找流程又相对复杂一些,所以本节主要分析支持策略路由时的路由查找流程。
注:对于路由查找,首先是查找路由缓存,只有在缓存不命中时才会查找路由,关于路由缓存的查找,此处不分析,后续会专门分析路由缓存
在分析函数之前,先熟悉一下几个数据结构:
/*
路由查找结果相关的结构体
*/
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;
};
策略路由相关的路由查找流程大概可以分为如下几个方面:
a)策略规则的匹配
b)路由表中规则的匹配
对于路由查找,不管是策略路由,还是非策略路由的查找,都是通过调用函数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.遍历传入的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;
}
接着就按上面说的策略规则的三个方面开始说起,以使文章能够结构化
对于这个功能,是通过函数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;
}
在函数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函数进行路由项的匹配操作。这才是本小节的重点。
功能:路由表结构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;
}
至此就将策略路由相关的路由查找函数分析完了,下面分析非策略路由的查找流程
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,
},
};
至此,完成了路由查找流程的分析,下一节分析路由的删除以及路由缓存相关的流程和策略规则功能的分析。