先来解释下,一个数据包如何能一步步到达目的主机的。那么首先就要明白一点,在一个局域网内,网卡的传递数据包的时候是不需要 IP 的,只需要知道目的 MAC 地址,即如果目的主机在相同的局域网中,只需要查到它的 IP 对应的 MAC,就可以把数据包发给它。即要想发送一个数据包给一个目的主机,只需要把包发到它的局域网就行了,而局域网与局域网之间的连接,往往需要有一个接口,我们就叫它网关。当要发送一个数据包时,这里往往就是一个 IP 包,会给它指定一个目的 IP,网络协议栈会根据自己的路由,判断目的主机是不是在自己的局域网中,即一个子网中,如果在,那么很简单,直接按前面提到的方法传送,如果不在,那么本地路由表应该指出,该数据包应该由哪个网卡发送,应该发给的下一台主机是谁,由下一台主机继续决定如何处理这个数据包。这样,数据包就可以进入下一个子网,如果目的主机不在下一个子网,那么继续路由,直到到达目的地
,或者不能够决策而丢弃。
其实上面就提到了路由的几个重要的概念,子网,下一跳的地址,从自己的哪个网卡发包。
而 Linux 在此基础上又进行了进一步的扩展,产生了一种叫做策略路由的机制,即存在多张路由表,具体如何选择这些路由表,是由一些策略决定的。也就是当发一个数据包时,根据该数据包的一些特性,如源地址,目的地址,tos 等信息,去检查是否和制定的一些规则匹配,如果匹配成功,则根据该规则指定的路由表将被使用。下图就是策略路由的框架:
从图中我们可以看出,当进行路由查找时,先去按顺序搜索规则链表,去进行匹配。我们以代码来理解 linux 下的策略路由的实现,笔者的 linux 版本号为 2.6.27.62。
上图是以 IP v4 为例,具体的数据结构图,路由策略都是被以连接到 fib4_rules_ops_template->rules_list 的单链表中。ip v4 的策略是由 fib4_rules_ops_template 来表示的,它指定了策略链表的位置,以及策略的匹配规则,以及,匹配成功后的操作。我们以 udp 的发包为例讲解,上述的结构如何应用到策略路由中。
udp 的发包是在 udp_sendmsg 中完成的,当需要路由时,它会传入 源地址,源端口,目的地址,目的端口,tos 等信息,然后调用,ip_route_output_flow 来查找路由,查找路由过程中为了加快速度,会先查找 cache 中的内容,这里我们略过,直接看查找一个不在 cache 中的路由的过程,这更具有意义一些,它会依次调用 __ip_route_output_key, ip_route_output_slow,从而通过 fib_lookup 来查找路由表,这里是调用 net/ipv4/fib_fules.c
里的 fib_lookup。它会继续调用 fib_rules_lookup 来遍历策略表,即 fib4_rules_ops_template->rules_list。代码如下:
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) {
jumped:
if (!fib_rule_match(rule, ops, fl, flags))
continue;
if (rule->action == FR_ACT_GOTO) {
struct fib_rule *target;
target = rcu_dereference(rule->ctarget);
if (target == NULL) {
continue;
} else {
rule = target;
goto jumped;
}
} else if (rule->action == FR_ACT_NOP)
continue;
else
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;
}
这就是策略路由中策略的核心所在,由于策略添加时是有顺序的,所以在匹配策略时是按照,本地策略,自定义策略,主策略,默认策略来搜索。匹配方法是由 fib4_rule_match 来实现,根据策略定义的 action 来决定下一步动作,而每个策略都会指定一个路由表,即是我们将使用的路由表。
(未完待续)