Linux 路由 (2)

        上篇我们讲到了,如何匹配策略,得到一个路由表,其实在 rib_rule 里,存的是该策略所使用的路由表的索引,这里顺便解释一下 FIB (Forward Infomation Base), 即转发信息数据库,即相当于路由表。
        得到所使用的路由表的索引后,就可以从 current->nsproxy->net_ns.ipv4.fib_table_hash 表中,得到路由表的指针,路由表是使用 fib_table 表示的(见上一往篇的图)。在 fib_table 结构中,它会指定查找该表的方法,这里是 fn_hash_lookup, 并且在该结构体的后面,会根据子网掩码的长度,组成一个 33 个元素的数组,分别代表子网掩码的 [0-32],一个路由表中的相同子网掩码的路由项会被散列到对应的子网掩码所代表的散列表中,因为往往路由表中使用的子网掩码长度往往较少,常用的如,8, 16, 24,32,所以以 fn_zone_list 为头的链表会把所有使用的项连接起来。当进行路由查找时,会从子网最长的开始,依次按顺序查找,最后子网为 0 的项,就是默认路由,因为子网查找时,子网掩码越长,表示的子网越精确。

        子网掩码长度相同的路由项,由 fn_zone 表示,并进行散列。想想现在进行到哪里了,确定了路由表,然后开始从子网掩码最长的子网匹配,确定目标主机与哪个子网匹配,所以根据目标主机的 IP 与此时的掩码长度可以得到与它匹配的子网。看代码:

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_node 结构体就代表一个子网,这里面对相同子网掩码的子网进行散列的算法比较简单,这里不讲,当找到与目标主机匹配的子网以后,即 fib_node->fn_key == dst & mask ,仿佛目标就要实现了,已经找到了代表目标子网的路由项,取出下一路以及网卡出口,就可以发包了,原来是这样的,但 linux 的路径做的比较精细,看代码:

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_WARNING "fib_semantic_match bad type %#x\n",
					fa->fa_type);
				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;
	atomic_inc(&res->fi->fib_clntref);
	return 0;
}

        当确定子网后,由于存在相同子网,但 tos, scope 不同的路由项,路由项是由 fib_alias 来表示的。这里 linux 还会更加精细地判断,tos (Type Of Service), 表示的是一种优先级,由 setsockopt 的 IP_TOS 选项可以设置一个 socket 的 TOS,而在进行路由查找时,就可以根据设置的 tos 计算出 scope, 具体代码为 ip_route_output_slow 中,

	u32 tos	= RT_FL_TOS(oldflp);
	struct flowi fl = { .nl_u = { .ip4_u =
				      { .daddr = oldflp->fl4_dst,
					.saddr = oldflp->fl4_src,
					.tos = tos & IPTOS_RT_MASK,
					.scope = ((tos & RTO_ONLINK) ?
						  RT_SCOPE_LINK :
						  RT_SCOPE_UNIVERSE),
				      } },
			    .mark = oldflp->mark,
			    .iif = net->loopback_dev->ifindex,
			    .oif = oldflp->oif };

       这里的 scope 是由 tos 来的,它表示了该 socket 想在什么范围内路由,如果 socket 只想在 LINK 范围内路由,但路由项的 scope 却是 UNIVERSE, 说明子网太远了,即 fa->fa_scope < flp->fl4_scope, 这样的路由是不合适的,同理,如果路由项设置了 tos, 而 socket 的 tos 不匹配,则这样的路由项也是不合适的,由此可以根据这两项进一步确定合适的路由。
        当确定了合适的路由项后,如果内核配置了 CONFIG_IP_ROUTE_MULTIPATH,什么是路由的多路径,这里解释一下,即一条路由项可能存在多个下一跳的地址,即多个下一路均可到达该路由项表示的子网,就叫做多路径,显然多路说明这两条路径都是可以使用的,这可以用来作负载均衡。确定了路由项后,路由查找就算大致结束了。
        最后根据一定的算法来决定使用哪一个下一跳地址,还有出口。
        这两篇文章讲述了 linux 下策略路由的框架及一些重要环节的实现,抛开了 cache 等一些细节,都可以在代码中了解到,知道了整个脉络,结合上一篇的结构图,那么路由的机制也将可以顺利地理解了。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。