系统默认会有三个路由表:local、main、default,local表由内核更新,那些通往本机的路由会保存到local表,而应用层通过route
指令或者ip route
指令(不指定路由表id的情况)添加的路由会默认保存到main表,default表本来是用于保存默认路由,但写者工作中常见到的是默认路由也被保存在main表中,而default表是空的。其实不止local\main\default表,linux可以支持多达0xffffffff张路由表,在使用ip route
指令添加路由时可以指定路由表的id,路由表会随着路由的添加而被创建。然而在不使能策略路由的情况下,路由查找时会直接查看local、main、default的三个表中的路由(按照local-mian-default的顺序),不会查看其它的表。在使能策略路由后,路由查找时具体会查哪张表,按照怎样的顺序来查表都全由路由策略决定。也就是说使能策略路由后,路由查找过程将变成两段,一段是通过路由策略确定路由表,另一段是在路由表中查找路由。接下来的篇幅中将分析策略路由的代码,简单梳理策略路由工作流程。
路由策略的组成
在应用层可以通过ip rule
指令来向系统添加路由策略,只要添加任意一条路由策略后,策略路由就会使能,路由查找的流程就会变成先查策略后查路由表。一条路由策略大致由两部分组成,一部分是匹配条件,一部分是动作,当匹配条件满足时就会执行相应的动作。
通过ip rule
指令添加路由策略时,可以指定匹配条件大致有下面这些:
not: 该参数表示匹配条件吻合时不执行动作
from: 该参数指定的前缀将用于与报文的源ip进行匹配
to: 该参数指定的前缀将用于与报文的目的ip进行匹配
tos: 该参数与ip报头携带的tos字段或者IP_TOS(socket ip option)进行匹配
fwmark: 该参数与sk_buff携带的mark(多来自于iptables PREROUTING mark)或者SO_MARK (socket option)进行匹配
iif/oif: 用以匹配报文的输入输出设备
ipproto: 该参数指定ip协议号,用于匹配IP报文协议字段,比如TCP,UDP
sport/dport: 该参数指定源/目的端口范围,将与报文的源/目的端口进行匹配
可以指定的动作大致如下:
table: 查找某路由表
goto: 跳转到另一条路由策略(比如goto 2,会跳转到优先级为2的策略)
nop: 不进行任何操作,执行下一条路由策略
realms: 这个action会给那些匹配上该路由策略且成功从路由表查找到了合适路由的skb记录上这个realm,经过tc时有的类型的filter会根据realm来选择class
blackhole/unreachable/prohibit/multicast: 从代码来看这些action会中断路由策略的执行流程,返回错误码给上层调用者
假设有一条路由策略如下,它就会匹配源ip属于192.168.1.0/24网段,目的ip属于192.168.2.0/24网段,携带的mark为1,报文输入接口为br-lan,传输层协议为UDP,源端口为6666,目的端口为9999的的报文,让那些报文去查找id为1234的路由表。
ip rule add from 192.168.1.0/24 to 192.168.2.0/24 fwmark 0x01/0xff iif br-lan ipproto UDP sport 6666 dport 9999 table 1234
路由策略的优先级
在使用ip rule
指令添加路由策略时,可以通过pref/priority
关键字来指定路由策略的优先级,优先级的范围是0~0xFFFFFFFF。
内核默认情况下会创建三条路由策略,这三条策略能够匹配所有的数据包,action分别是local/mian/default表,三条策略如下:
# ip rule
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
虽然上面三条策略已经使用了优先级0、32766、32767,但我们仍然是可以向那三个优先级上添加策略的。内核使用链表来组织路由策略,链表上路由策略是按照优先级从高到低排列的(值越小优先级越高),优先级相同的策略则会按照添加的先后顺序进行排列。
路由策略的执行过程
在执行路由策略时,执行过程大致分为如下几步:
- 从链表上首个路由策略开始执行,若满足路由策略的匹配条件,就执行路由策略的动作,若动作不满足就执行链表上下一条路由策略
- 若动作是nop,就直接执行下一条路由策略
- 若动作是goto,就跳转到特定策略去执行
- 若动作是blackhole/unreachable/prohibit之类的,就中断执行过程直接返回错误码给上层调用者
- 若动作是table就去查找指定路由表,根据路由表的查找结果执行下一步动作,如果结果是EAGAIN(路由表为空或者没有匹配的路由时会返回EAGAIN)就执行下一条策略,如果结果是其它错误码(比如命中的路由的type是prohibit/blackhole之类的)就直接返回给上层调用者,如果找到了匹配的路由也会直接返回给上层调用者
- 到达了链表尾部还没有找到匹配的路由,就返回错误码给上层调用者
代码分析
因为Ipv4/IPv6都会用到策略路由,所以内核中策略路由的代码分为了两部分,一部分是核心代码,它实现了一些公共的逻辑,另一部分是协议私有代码,IPv4和IPv6的实现会有不同。为了方便描述,稍后使用fib rule core
来指代策略路由的核心逻辑,使用fib rule
来指代路由策略。
内核将各协议不同的行为抽象了出来,使用struct fib_rules_ops
结构体来描述,IPv4和IPv6都会构建一个struct fib_rules_ops
类型的结构体变量,并向fib rule core
注册。fib_rules_ops
主要包含了一个链接路由策略的链表头和一系列回调,在创建、删除、查找路由策略时,fib rule core
会先执行一些公共的逻辑,然后调用fib_rules_ops
中的回调来实现功能。
fib rule core
的代码放置在 linux/net/core/fib_rules.c
文件,而协议私有代码则放置在协议独有的目录下,比如 linux/net/ipv4/fib_rules.c
,linux/net/ipv6/fib6_rules.c
。
关键的数据结构
struct fib_rules_ops
的结构体成员大致如下:
// inclue/net/fib_rules.h
struct fib_rules_ops {
int family; // 协议家族,AF_INET/AF_INET6/AF_DECnet
struct list_head list; // 所有的fib_rules_ops是以链表的形式组织在一起的,这个list就是链接到全局链表的链表节点
int rule_size; // fib rule的大小,各协议描述fib rule的结构不同,而分配fib rule的动作是由fib rule core执行的,故注册给fib rule core的fib_rules_ops需要指明fib rule的大小
int addr_size; // 地址大小,比如IPv4就是4字节,IPv6就是16字节
int unresolved_rules; // 统计 unresolved goto的数目,unresolved goto是指action为goto但目标fib rule还不存在
int nr_goto_rules; // action 为 goto 的fib rule数目
unsigned int fib_rules_seq;
int (*action)(struct fib_rule *,
struct flowi *, int,
struct fib_lookup_arg *); // 当fib rule的匹配条件满足时,fib rule core会调用action指向的函数来执行动作
bool (*suppress)(struct fib_rule *, int,
struct fib_lookup_arg *);
int (*match)(struct fib_rule *,
struct flowi *, int); // fib rule core会调用match指向的函数来确定数据包是否满足fib rule的匹配条件
int (*configure)(struct fib_rule *,
struct sk_buff *,
struct fib_rule_hdr *,
struct nlattr **,
struct netlink_ext_ack *); // 创建fib rule的过程中, fib rule core会调用configure指向的函数来完成一些协议特有的初始化动作
int (*delete)(struct fib_rule *); // 删除fib rule的过程中, fib rule core会调用delete指向的函数来完成一些协议特有的删除动作
int (*compare)(struct fib_rule *,
struct fib_rule_hdr *,
struct nlattr **); // 创建fib rule的过程中, fib rule core会调用compare指向的函数来比较两条fib rule是否相同,就是用来查重的
int (*fill)(struct fib_rule *, struct sk_buff *,
struct fib_rule_hdr *);
size_t (*nlmsg_payload)(struct fib_rule *);
void (*flush_cache)(struct fib_rules_ops *ops);
int nlgroup;
const struct nla_policy *policy;
struct list_head rules_list; // 链表头,用于链接当前网络命名空间中所有fib rule
struct module *owner;
struct net *fro_net; // 指向网络命名空间的指针
struct rcu_head rcu;
};
接下来将以IPv4为例来分析策略路由的代码,IPv4向fib rule core
注册的fib_rules_ops
如下所示:
// linux/net/ipv4/fib_rules.c
static const struct fib_rules_ops __net_initconst fib4_rules_ops_template = {
.family = AF_INET,
.rule_size = sizeof(struct fib4_rule), // IPv4 使用 struct fib4_rule 来描述路由策略
.addr_size = sizeof(u32),
.action = fib4_rule_action, // fib rule与数据包匹配时调用的函数
.suppress = fib4_rule_suppress,
.match = fib4_rule_match, // 用以确定fib rule与数据包是否匹配
.configure = fib4_rule_configure, // 添加fib rule时调用
.delete = fib4_rule_delete,
.compare = fib4_rule_compare, // 用于fib rule的查重
.fill = fib4_rule_fill,
.nlmsg_payload = fib4_rule_nlmsg_payload,
.flush_cache = fib4_rule_flush_cache,
.nlgroup = RTNLGRP_IPV4_RULE,
.policy = fib4_rule_policy,
.owner = THIS_MODULE,
};
IPv4使用struct fib4_rule
来描述fib rule, struct fib4_rule
是对struct fib_rule
的扩展,struct fib_rule
是fib rule core
对路由策略的抽象,其它协议描述路由策略的数据结构也是对struct fib_rule
的扩展。
struct fib4_rule
结构体成员如下:
// linux/net/ipv4/fib_rules.c
struct fib4_rule {
struct fib_rule common;
u8 dst_len; // 使用'to'关键字传递的前缀长度, 比如 to 192.168.0.0/24 ---> dst_len = 24
u8 src_len; // 使用'from'关键字传递的前缀长度
u8 tos; // tos
__be32 src; // 使用'from'关键字传递的前缀值, 比如 from 192.168.0.0/24 ---> src = 192.168.0.0
__be32 srcmask; // 源地址掩码,比如 from 192.168.0.0/24 ---> srcmask = 255.255.255.0
__be32 dst; // 使用'to'关键字传递的前缀值
__be32 dstmask; // 目的地址掩码
#ifdef CONFIG_IP_ROUTE_CLASSID
u32 tclassid; // realm
#endif
};
struct fib_rule
结构体成员如下:
// linux/include/net/fib_rules.h
struct fib_rule {
struct list_head list; // 链表节点,链接到fib_rules_ops的rules_list链表上
int iifindex; // 输入设备index
int oifindex; // 输出设备index
u32 mark; // mark
u32 mark_mask; // mark的掩码
u32 flags; // 用于存储一些标志位, 比如FIB_RULE_INVERT
u32 table; // 如果action是 table, 则table成员用于存储table id
u8 action; // fib rule的动作,内核有一个枚举来表示那些动作, table、goto、nop分别对应 FR_ACT_TO_TBL、FR_ACT_TO_TBL、FR_ACT_NOP
u8 l3mdev;
u8 proto;
u8 ip_proto; // ip协议号
u32 target; // 如果action是 goto ,则 target 成员用于存储目标fib rule的pref(目标fib rule的优先级)
__be64 tun_id;
struct fib_rule __rcu *ctarget; // 如果action是 goto ,则 ctarget 指针会指向目标fib rule
struct net *fr_net;
refcount_t refcnt; // fib rule引用计数
u32 pref; // 本fib rule的优先级
int suppress_ifgroup;
int suppress_prefixlen;
char iifname[IFNAMSIZ]; // 输入设备名
char oifname[IFNAMSIZ]; // 输出设备名
struct fib_kuid_range uid_range;
struct fib_rule_port_range sport_range; // 源端口范围
struct fib_rule_port_range dport_range; // 目的端口范围
struct rcu_head rcu;
};
新建路由策略
新建fib rule时,应用层的ip rule
会将fib rule的参数封装到一个struct fib_rule_hdr
类型的结构体变量和若干struct rtattr
类型(netlink attr)的变量中,然后通过netlink消息将传递给内核。内核会将netlink消息中的参数转存到struct fib_rule
和struct fib4_rule
这两个结构中,从应用层参数到内核结构体成员的转换关系如下表所示:
ip rule 参数 | fib_rule_hdr 成员 | rtattr | fib_rule 成员 | fib4_rule 成员 |
---|---|---|---|---|
not | flags |= FIB_RULE_INVERT | flags |= FIB_RULE_INVERT | ||
from | src_len = 前缀长度 | FRA_SRC | src src_len srcmask | |
to | dst_len = 前缀长度 | FRA_DST | dst dst_len dstmask | |
tos | tos | tos | ||
fwmark | FRA_FWMARK FRA_FWMASK | mark mark_mask | ||
iif | FRA_IFNAME | iifindex iifname | ||
oif | FRA_OIFNAME | oifindex oifname | ||
ipproto | FRA_IP_PROTO | ip_proto | ||
sport | FRA_SPORT_RANGE | sport_range | ||
dport | FRA_DPORT_RANGE | dport_range | ||
pref/priority | FRA_PRIORITY | pref | ||
table | action = FR_ACT_TO_TBL table(<256) | FRA_TABLE | action = FR_ACT_TO_TBL | table |
goto | action = FR_ACT_GOTO | FRA_GOTO | action = FR_ACT_GOTO target | |
nop | action = FR_ACT_NOP | action = FR_ACT_NOP | ||
realms | FRA_FLOW | tclassid |
应用层使用的netlink命令是RTM_NEWRULE
,内核收到该指令时会调用fib_nl_newrule
函数,fib_nl_newrule
的代码大致如下:
// linux/net/core/fib_rules.c
// nlh 是指向netlink消息头部的指针
int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack)
{
struct net *net = sock_net(skb->sk);
struct fib_rule_hdr *frh = nlmsg_data(nlh); // fib_rule_hdr中包含了一部分参数
struct fib_rules_ops *ops = NULL;
struct fib_rule *rule = NULL, *r, *last = NULL;
struct nlattr *tb[FRA_MAX + 1];
int err = -EINVAL, unresolved = 0;
bool user_priority = false;
......
// 1 根据frh->family 查找 fib_rules_ops,使用'ip rule'指令时,默认传递的family为'AF_INET'(IPv4)
// 对于IPv4而言查找结果为 fib4_rules_ops_template
ops = lookup_rules_ops(net, frh->family);
......
// 2 分配struct fib_rule结构体变量, 将应用层传递的参数转存到fib_rule(包括not/fwmark/iif/oif/ipproto/sport/dport/pref/table/goto/nop等参数),对于IPv4而言实际分配的是stuct fib4_rule类型的结构体变量
err = fib_nl2rule(skb, nlh, extack, ops, tb, &rule, &user_priority);
if (err)
goto errout;
// 3 添加新的fib rule时会比对是否已经存在完全相同的fib rule, 若有报错返回
if ((nlh->nlmsg_flags & NLM_F_EXCL) && rule_exists(ops, frh, tb, rule)) {
err = -EEXIST;
goto errout_free;
}
// 4 调用fib_rules_ops的configure回调函数,对于IPv4而言 fib4_rule_configure 函数被调用, fib4_rule_configure函数将应用层传递的IPv4相关的参数转存到 fib4_rule(处理to/from/realms/tos这些参数)
err = ops->configure(rule, skb, frh, tb, extack);
......
// 5.1 如果fib rule的action为goto, 则这里会遍历当前已有的fib rule, 将目标fib rule地址赋予rule->ctarget
list_for_each_entry(r, &ops->rules_list, list) {
if (r->pref == rule->target) {
RCU_INIT_POINTER(rule->ctarget, r);
break;
}
}
//5.2 如果ctarget为NULL, 则表示目标fib rule暂时还不存在,给unresolved赋值为1,内核会统计fib_rules_ops下unresolved goto的数目
if (rcu_dereference_protected(rule->ctarget, 1) == NULL)
unresolved = 1;
//6 接下来的几行将新的fib rule插入链表(ops->rules_list),插入顺序有fib rule的pref值(优先级)来决定, pref值越小越靠前,pref值相同的fib rule,按照创建的先后顺序排列,先创建的fib fule越靠前,查找fib rule的时候越靠前的fib rule优先级也越高
list_for_each_entry(r, &ops->rules_list, list) {
if (r->pref > rule->pref)
break;
last = r;
}
if (last)
list_add_rcu(&rule->list, &last->list);
else
list_add_rcu(&rule->list, &ops->rules_list);
//7 检查当前ops下是否有unresolved goto的fib rule,若有则看看那些unresolved goto的target是不是当前新增的这条fib rule, 若是则给它们的ctarget赋值
if (ops->unresolved_rules) {
list_for_each_entry(r, &ops->rules_list, list) {
if (r->action == FR_ACT_GOTO &&
r->target == rule->pref &&
rtnl_dereference(r->ctarget) == NULL) {
rcu_assign_pointer(r->ctarget, rule);
if (--ops->unresolved_rules == 0)
break;
}
}
}
// 8.1 更新当前ops下action为 goto 的fib rule数目
if (rule->action == FR_ACT_GOTO)
ops->nr_goto_rules++;
// 8.2 更新当前ops下unresolved goto的数目
if (unresolved)
ops->unresolved_rules++;
......
return 0;
errout_free:
kfree(rule);
errout:
rules_ops_put(ops);
return err;
}
fib4_rule_configure
函数如下:
// linux/net/ipv4/fib_rules.c
static int fib4_rule_configure(struct fib_rule *rule, struct sk_buff *skb,
struct fib_rule_hdr *frh,
struct nlattr **tb,
struct netlink_ext_ack *extack)
{
struct net *net = sock_net(skb->sk);
int err = -EINVAL;
struct fib4_rule *rule4 = (struct fib4_rule *) rule;
if (frh->tos & ~IPTOS_TOS_MASK) {
NL_SET_ERR_MSG(extack, "Invalid tos");
goto errout;
}
// 1 在使能策略路由前,main表和local表用的是同一棵前缀树,这里的fib_unmerge会把那棵树分割成两棵独立的树
err = fib_unmerge(net);
if (err)
goto errout;
if (rule->table == RT_TABLE_UNSPEC && !rule->l3mdev) {
if (rule->action == FR_ACT_TO_TBL) {
struct fib_table *table;
table = fib_empty_table(net);
if (!table) {
err = -ENOBUFS;
goto errout;
}
rule->table = table->tb_id;
}
}
// 2 记录应用层传递的from和to参数
if (frh->src_len)
rule4->src = nla_get_in_addr(tb[FRA_SRC]);
if (frh->dst_len)
rule4->dst = nla_get_in_addr(tb[FRA_DST]);
// 3 记录应用层传递的realms参数
#ifdef CONFIG_IP_ROUTE_CLASSID
if (tb[FRA_FLOW]) {
rule4->tclassid = nla_get_u32(tb[FRA_FLOW]);
if (rule4->tclassid)
atomic_inc(&net->ipv4.fib_num_tclassid_users);
}
#endif
if (fib_rule_requires_fldissect(rule))
net->ipv4.fib_rules_require_fldissect++;
// 2 记录应用层传递的from和to参数
rule4->src_len = frh->src_len;
rule4->srcmask = inet_make_mask(rule4->src_len);
rule4->dst_len = frh->dst_len;
rule4->dstmask = inet_make_mask(rule4->dst_len);
// 4 记录应用层传递的tos参数
rule4->tos = frh->tos;
// 5 将fib_has_custom_rules设置为true,它标志着策略路由被使能了
net->ipv4.fib_has_custom_rules = true;
err = 0;
errout:
return err;
}
执行路由策略
查找IPv4路由时,fib_lookup
函数会被调用,fib_lookup
大致如下:
// linux/include/net/ip_fib.h
// flp是调用方传递的参数指针,struct flowi4中包含了许多用于匹配fib rule和匹配路由的参数, 比如 tos, mark, 源/目的端口号等
inline int fib_lookup(struct net *net, struct flowi4 *flp,
struct fib_result *res, unsigned int flags)
{
......
// 1 当使能了策略路由时,调用__fib_lookup查找fib rule
if (net->ipv4.fib_has_custom_rules)
return __fib_lookup(net, flp, res, flags);
......
// 2.1 当没有使能策略路由时,先查找main表,在查找main表的过程中其实local表也一并被查找了,因为此时的main表和local表使用的是相同的前缀树
tb = rcu_dereference_rtnl(net->ipv4.fib_main);
if (tb)
err = fib_table_lookup(tb, flp, res, flags);
......
// 2.2 查完main表再查找default表
tb = rcu_dereference_rtnl(net->ipv4.fib_default);
if (tb)
err = fib_table_lookup(tb, flp, res, flags);
......
}
__fib_lookup
函数如下:
// linux/net/ipv4/fib_rules.c
int __fib_lookup(struct net *net, struct flowi4 *flp,
struct fib_result *res, unsigned int flags)
{
struct fib_lookup_arg arg = {
.result = res,
.flags = flags,
};
int err;
/* update flow if oif or iif point to device enslaved to l3mdev */
l3mdev_update_flow(net, flowi4_to_flowi(flp));
// 1 调用fib_rules_lookup函数,fib_rules_lookup会查看fib_rules_ops::rules_list链表上的fib rule, 比对fib rule和flowi的各项参数,若flowi的各项参数都符合fib rule的要求,则执行fib rule的action
err = fib_rules_lookup(net->ipv4.rules_ops, flowi4_to_flowi(flp), 0, &arg);
#ifdef CONFIG_IP_ROUTE_CLASSID
if (arg.rule)
res->tclassid = ((struct fib4_rule *)arg.rule)->tclassid; // 2 将fib rule的realm保存下来,在tc处理数据包的时候,有的filter会根据tclassid来匹配数据包
else
res->tclassid = 0;
#endif
if (err == -ESRCH)
err = -ENETUNREACH;
return err;
}
fib_rules_lookup
函数如下:
// linux/net/ipv4/fib_rules.c
int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,
int flags, struct fib_lookup_arg *arg)
{
struct fib_rule *rule;
......
list_for_each_entry_rcu(rule, &ops->rules_list, list) {
jumped:
// 1 fib_rule_match 先比对一些公有的参数,如输入输出接口、mark等,然后调用ops->match比对协议特有的参数, 对于IPv4而言, fib4_rule_match函数会被调用,fib4_rule_match会判断源/目的ip、源/目的端口、tos、ip协议号等参数是否匹配
// 若fib rule没有配置not参数, 则flowi各项参数满足fib rule的条件时fib_rule_match返回1, 不满足则返回0
// 若fib rule配置了not参数, 则flowi各项参数满足fib rule的条件时fib_rule_match返回0, 不满足则返回1
if (!fib_rule_match(rule, ops, fl, flags, arg))
continue;
// 2 若action为goto, 则直接跳转到target rule, 被跳过的那些fib rule就不再管它们了
if (rule->action == FR_ACT_GOTO) {
struct fib_rule *target;
target = rcu_dereference(rule->ctarget);
if (target == NULL) {
continue;
} else {
rule = target;
goto jumped;
}
// 3 若action为nop, 则什么操作都不做,跳转到下一条fib rule去执行
} else if (rule->action == FR_ACT_NOP)
continue;
else
err = ops->action(rule, fl, flags, arg); // 4 调用执行ops->action, 对于IPv4而言fib4_rule_action函数会被调用
if (!err && ops->suppress && ops->suppress(rule, flags, arg))
continue;
// 5 错误码为EAGAIN时,会去匹配下一条fib rule, 通常在路由表中没有找到匹配的路由时会返回EAGAIN。
// 如果被命中的路由配置了prohibit/blackhole之类的type,查找路由表的函数也会返回错误码,这种情况下就不会再去匹配下一条fib rule了,直接把错误码返回给上层调用函数
// 如果fib rule的配置了prohibit、reject、unreachable之类的action,ops->action也会返回对应的错误码,这种情况下也不会再去匹配下一条fib rule.
if (err != -EAGAIN) {
if ((arg->flags & FIB_LOOKUP_NOREF) ||
likely(refcount_inc_not_zero(&rule->refcnt))) {
arg->rule = rule;
goto out;
}
break;
}
}
// 6 所有fib rule都匹配了一遍,没有任何路由被命中,返回错误码给上层调用者
err = -ESRCH;
out:
rcu_read_unlock();
return err;
}
fib4_rule_action
函数如下:
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;
u32 tb_id;
// 1 action 为table时会去查找路由表, 其它action(prohibit\reject\unreachable之类的action)直接返回错误码
switch (rule->action) {
case FR_ACT_TO_TBL:
break;
case FR_ACT_UNREACHABLE:
return -ENETUNREACH;
case FR_ACT_PROHIBIT:
return -EACCES;
case FR_ACT_BLACKHOLE:
default:
return -EINVAL;
}
rcu_read_lock();
// 2 获取fib rule记录的table id
tb_id = fib_rule_get_table(rule, arg);
// 2.1 获取路由表
tbl = fib_get_table(rule->fr_net, tb_id);
// 2.2 调用fib_table_lookup来查找路由
if (tbl)
err = fib_table_lookup(tbl, &flp->u.ip4,
(struct fib_result *)arg->result,
arg->flags);
rcu_read_unlock();
return err;
}