策略路由代码分析

系统默认会有三个路由表: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_rulefib 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_rulestruct fib4_rule这两个结构中,从应用层参数到内核结构体成员的转换关系如下表所示:

ip rule 参数fib_rule_hdr 成员rtattrfib_rule 成员fib4_rule 成员
notflags |= FIB_RULE_INVERTflags |= FIB_RULE_INVERT
fromsrc_len = 前缀长度FRA_SRCsrc
src_len
srcmask
todst_len = 前缀长度FRA_DSTdst
dst_len
dstmask
tostostos
fwmarkFRA_FWMARK
FRA_FWMASK
mark
mark_mask
iifFRA_IFNAMEiifindex
iifname
oifFRA_OIFNAMEoifindex
oifname
ipprotoFRA_IP_PROTOip_proto
sportFRA_SPORT_RANGEsport_range
dportFRA_DPORT_RANGEdport_range
pref/priorityFRA_PRIORITYpref
tableaction = FR_ACT_TO_TBL
table(<256)
FRA_TABLEaction = FR_ACT_TO_TBLtable
gotoaction = FR_ACT_GOTOFRA_GOTOaction = FR_ACT_GOTO
target
nopaction = FR_ACT_NOPaction = FR_ACT_NOP
realmsFRA_FLOWtclassid

应用层使用的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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值