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 等一些细节,都可以在代码中了解到,知道了整个脉络,结合上一篇的结构图,那么路由的机制也将可以顺利地理解了。

时间: 2024-12-18 14:03:42

Linux 路由 (2)的相关文章

linux路由服务

本文介绍如何使用linux创建一台简单的路由服务器.主要包括几个参数的设置:ip_forward和rp_filter. 1.开启IP forwarding # 重启后失效 $ echo "1" > /proc/sys/net/ipv4/ip_forward 或者 编辑/etc/sysctl.conf,做如下修改 net.ipv4.ip_forward = 1 # sysctl用于在内核工作时直接修改内核参数 $ sysctl -p 2. 关闭rp_filter echo &quo

linux 路由转发实验(软路由)

一.实验网络拓扑 (R1,R2由 Linux 虚拟机添加双网卡做软路由实现) 二.环境准备: (虚拟机网卡需要设置为桥接模式) 1. 关闭NetworkManager: service NetworkNanager stop 禁止开机自启动: chkconfig NetworkNanager off 清空iptables 条目: iptables -F 开启路由转发功能:(0 :禁用, 1:启用) echo 1 > /proc/sys/net/ipv4/ip_forward 检查路由转发功能开启

利用systemtap学习Linux路由代码

http://bbs.chinaunix.net/thread-4090162-1-1.html 一.为什么要这样做读kernel route子系统代码,当我弄懂了数据结构之间的关系以及控制流程后,心里还是不妥贴,总有一种“纸上得来终觉浅,绝知此事要躬行”的感觉.此时,systemtap能起大作用. 二.准备工作安装systemtap, kernel, kernel-debuginfo, kernel-debuginfo-common等.uname -r2.6.38.6-26.rc1.fc15.

Linux 路由 (1)

路由,在网络技术中扮演着核心且任务繁重的角色,由于调用频繁,所以它的性能非常重要,再加上它为每一个数据包指明了前进的道路.如同我们写信一样,只要指明了目的地址,那么根据这个信息,邮件就会被尽力送达,数据包也是如果,当我们指定了目的 IP,那么就需要有一种强有力的机制来保证它会被送达,来确保这一目的的机制就是路由. 先来解释下,一个数据包如何能一步步到达目的主机的.那么首先就要明白一点,在一个局域网内,网卡的传递数据包的时候是不需要 IP 的,只需要知道目的 MAC 地址,即如果目的主机在相同的局

网络基础~linux路由与网关、路由命令

Linux的路由与网关: 路由(Routing):路由是指从一个设备(一般指路由器)的接口上接收到数据包,依据设备所既定的某些规则,将数据包转发到其它接口的 "过程".路由工作在OSI参考模型第三层--网络层的数据包转发设备.路由器通过转发数据包来实现网络互连 路由器(Router):路由器是用于连接多个逻辑上分开的网络,所谓逻辑网络是代表一个单独的网络或者一个子网.当数据从一个子网传输到另一个子网时,可通过路由器的路由功能来完成.因此,路由器具有判断网络地址和选择IP路径的功能,它能

Linux路由实验

一    实验背景:路由器的功能是将不同网段的主机建立通信,本次试验是在使用linux主机当作路由器,完成主机间通信.如下图所示 二.实验环境:主机A和B为CentOS7.2,主机C和D为CentOS6.8,且C和D上分别都有两块网卡 三.实验步骤 注意:为避免干扰项,需将每台主机的防火墙关闭     1.主机A配置 # 配置IP地址 #配置默认网关 2.主机B配置 #配置B的IP地址(vi  /etc/sysconfig/network-scripts/ifcfg-eth#) #关闭Netwo

Linux路由转发简介

要求: 需要让Linux01通过Linux02访问Linux03,把Linux02转换成一个路由器. 实施步骤: (1)  准备环境:Linux01(ip:10.0.0.10)Linux02(双网卡eth0:10.0.0.11  eth1:10.0.1.11)Linux03(ip:10.0.1.10) (2)  配置Linux01,添加路由:route add -net 10.0.1.0/24 gw10.0.0.11 (3)  配置Linux02,开启路由转发功能:echo "1" &

linux路由

在默认路由表添加路由ip route add 网络 via 网关 dev 网卡 在指定路由表添加路由ip route add 网络 via 网关 dev 网卡 table 路由表 查看默认路由表路由ip route 查看指定路由表路由ip route show table 路由表ip route list table 路由表 策略路由ip rule add from IP table 100 查看策略路由ip rule 建立特殊路由表vi /etc/iproute2/rt_tables 例子:i

峰回路转 哪来回哪去 LINUX 路由脚本 &nbsp; ERIKXUE

[[email protected] Desktop]# ip ru sh 0: from all lookup local 32766: from all lookup main 32767: from all lookup default [[email protected] Desktop]# 实现流量从哪个网卡来,就从哪个网卡回去 [[email protected] Desktop]# [[email protected] Desktop]# for i in `ip addr  |