Linux2.4-net源码学习笔记 IP层协议栈的实现(II IP数据报路由)

1写在前面

路由器是从一个物理网向另一个物理网发送数据包的装置,路由器通常被称为网关,它承但着分发数据包的任务。

查看主机当前的路由表。

$ netstat -rn 

$ route

查看主机的网络信息

$ netstat

路由表

2.1 路由表的构成

一个路由表的每一项至少包含两个信息:

路由表项:|目的主机IP地址 下一个路由的IP地址 | ....|

IP数据报文头中的“目的IP地址”和路由表中的每一项中的“目的主机IP地址”进行匹配,查找最匹配的表项,此表项中的“下一个路由的IP地址”指定了IP数据报的路由出口。

2.2 路由表查表原则

路由查表原则为:
● 首先,路由表会去搜索一个“目的IP地址”字段与数据报文中目的IP地址完全相同的条目。这就意味着IP地址的主机ID与网络ID完全的匹配。如果找到,则数据包被发送到相应接口或中间路由器。
● 如果没有找到一个完全的匹配IP,那么就接着搜索相匹配的网络ID。如果找到,那么该数据报文会被转发到指定的路由器。所以我们看到,这个网络上的所有主机都通过这个路由表中的单个(这个)条目来管理。
● 如果上述两个条件都不匹配,那么该数据报文将转发到一个“默认路由器”。
● 如果上述步骤失败,即没有默认路由器,那么该数据报文最终无法被转发。任何无法投递的数据报文都将产生一个ICMP主机不可达或ICMP网络不可达的错误,并将此错误返回给生成此数据报文的应用程序。

有了更精确的主机条目为什么还需要网络相关的条目?在路由表中包含与网络相关的路由条目是一个很大的优点。其优点在于,拥有一个与完整网络相关的条目,能够避免包含此网络中所有单独的主机条目(这个数据量非常巨大)。这使得路由表的大小降到一个可收受的数量级,这样就非常好。

(来源网络)

3 Linux 路由检

3.1过程概述

1)查路由缓存hash表 rt_hash_table(最近使用的路由表项)

它记录着最近使用过的路由转发表项,每个表项是根据saddr,daddr,tos计算而得出hash值进行hash链的归类存放,方便对缓存中rt_hash_table的快速检索。

2)查路由表FIB

在路由缓存查找失败后,到路由表中查找路由项。

这个过程和上述路由查表原则类似,具体实现过程为:Linux的路由表的数据结构是struct fib_table{},记录IP转发信息的索引表转发表的每一记录(节点)描述了具有某一类目的地址的IP包应该使用哪一输出设备发给哪一目的主机转发表记录按网络区进行分类每一网络区描述了在特定网络地址位长下具有不同网络号的目的地址的转发信息0区的网络地址位长为0, 与所有的IP地址都匹配用来描述缺省网关32区的网络地址位长为32, 用来与完整的IP地址匹配在建立网络区时它们按网络地址位长从大到小的顺序排列在搜索IP地址时先从全主机地址的第32区开始匹配最后第0区与所有的地址都匹配产生缺省网关.

(reference http://blog.chinaunix.net/uid-488742-id-2113729.html)

3.2源码分析

3.2.1 路由入口数据结构

描述IP数据报在路由过程中的下一个入口:(下一跳要往路由的哪个口出去)

struct dst_entry
{
	struct dst_entry        *next;
	atomic_t		__refcnt;		//"目的入口"的引用计数,创建成功后即设为1/* client references	*/
	int			__use;				//一个统计数值,该"目的入口"被使用一次(发送一个IP数据报),__use就加1
	struct net_device       *dev;	//该路由的输出网络设备接口
	int			obsolete;
	int			flags;				//标志位,其取值可以是DST_HOST, DST_NOXFRM, DST_NOPOLICY, DST_NOHASH, DST_BALANCED(用在路由有多路径的情况下)
#define DST_HOST		1
	unsigned long		lastuse;	//最近发数据报的时间,用于维护路由缓存表:删除最长时间未是使用的项目
	unsigned long		expires;	
	unsigned		mxlock;
	unsigned		pmtu;
	unsigned		window;
	unsigned		rtt;
	unsigned		rttvar;
	unsigned		ssthresh;
	unsigned		cwnd;
	unsigned		advmss;
	unsigned		reordering;
	unsigned long		rate_last;	/* rate limiting for ICMP */
	unsigned long		rate_tokens;
	int			error;
	struct neighbour	*neighbour;	//为该路由绑定的邻居节点(与ARP相关)
	struct hh_cache		*hh;		//ARP:mac-IP地址缓存|硬件头缓存,ARP解析得到的邻居的mac地址缓存在这里,再次发送IP数据报的时候,就不需要再到ARP缓存中去取硬件头。
	//input和output分别是该目的入口的输入和输出函数。
	int			(*input)(struct sk_buff*);
	int			(*output)(struct sk_buff*);
#ifdef CONFIG_NET_CLS_ROUTE
	__u32			tclassid;
#endif
	struct  dst_ops	        *ops;
	char			info[0];
};

3.2.2路由hash缓存表项

struct rtable
{
union{
struct dst_entry    dst;
struct rtable       *rt_next;
}u;
struct in_device    *idev;
unsigned    rt_flags;
__u16       rt_type;
__u16       rt_multipath_alg;
__u32       rt_dst;	 //路由的目的地址
__u32       rt_src;	 //是路由的源地址
int         rt_iif;	 //路由的输入设备接口的索引号
__u32           rt_gateway;	//路由网关的IP地址
struct flowi    fl;
__u32           rt_spec_dst;
struct inet_peer    *peer;
};

Rtable描述了路由hash缓存中的每个路由表项,总体可分为三部分

1)除Union外的其他成员

路由表项主要路由查找信息,记录着前一次该表项路由了IP包的源地址,目的地址,转发标记等信息。如果IP数据报中的信息和这些信息匹配,那么说明在路由缓冲找找到了路由表项了。

2)联合体中的u.dst

指明了该路由表项的出口 entry。如果确定该路由表项是IP包的路由出口节点,那么就可以将u.dst地址赋值给skb->dst,这样就确定了IP数据包的下一个路由入口。

3)联合体中的u.rt_next

用于路由表链:它占用了entry中的第一个成员struct dst_entry  *next; 实现了内存的union共享

(详见:http://blog.csdn.net/ordeder/article/details/10054151

3.2.3路由hash缓冲表

struct rt_hash_bucket {
	struct rtable	*chain;
	rwlock_t	lock;
} __attribute__((__aligned__(8)));
static struct rt_hash_bucket 	*rt_hash_table;	//路由hash缓存表
static unsigned			rt_hash_mask;			//有这么条hash链

3.2.4 路由缓冲表初始化

1)计算申请连续的内存页数

2)调整rt_hash_mask到达2^n的上限

3)申请内存页,rt_hash_table指向它

4)初始化rt_hash_table[]

void __init ip_rt_init(void)
{
	int i, order, goal;
#ifdef CONFIG_NET_CLS_ROUTE
	for (order=0;
	     (PAGE_SIZE<<order) < 256*sizeof(ip_rt_acct)*NR_CPUS; order++)
		/* NOTHING */;
	ip_rt_acct = (struct ip_rt_acct *)__get_free_pages(GFP_KERNEL, order);
	if (!ip_rt_acct)
		panic("IP: failed to allocate ip_rt_acct\n");
	memset(ip_rt_acct, 0, PAGE_SIZE<<order);
#endif

	ipv4_dst_ops.kmem_cachep = kmem_cache_create("ip_dst_cache",
						     sizeof(struct rtable),
						     0, SLAB_HWCACHE_ALIGN,
						     NULL, NULL);

	if (!ipv4_dst_ops.kmem_cachep)
		panic("IP: failed to allocate ip_dst_cache\n");

	goal = num_physpages >> (26 - PAGE_SHIFT);	//计算要申请连续的页框:goal

	for (order = 0; (1UL << order) < goal; order++)
		/* NOTHING */;

	do {
		rt_hash_mask = (1UL << order) * PAGE_SIZE /
			sizeof(struct rt_hash_bucket);			//上限2^order 连续的页框能装下几个rt_hash_bucket 
		while (rt_hash_mask & (rt_hash_mask-1))		//调整 到2的n次方
			rt_hash_mask--;
		rt_hash_table = (struct rt_hash_bucket *)
			__get_free_pages(GFP_ATOMIC, order);	//请求2^order个连续的页框,每个rt_hash_table 占用一个页框
	} while (rt_hash_table == NULL && --order > 0);
	...
	rt_hash_mask--;
	for (i = 0; i <= rt_hash_mask; i++) {			//初始化rt_hash_table[]数组
		rt_hash_table[i].lock = RW_LOCK_UNLOCKED;
		rt_hash_table[i].chain = NULL;
	}

	ipv4_dst_ops.gc_thresh = (rt_hash_mask+1);
	ip_rt_max_size = (rt_hash_mask+1)*16;

	devinet_init();
	ip_fib_init();

	rt_flush_timer.function = rt_run_flush;
	rt_periodic_timer.function = rt_check_expire;

	/* All the timers, started at system startup tend
	   to synchronize. Perturb it a bit.
	 */
	rt_periodic_timer.expires = jiffies + net_random()%ip_rt_gc_interval
		+ ip_rt_gc_interval;
	add_timer(&rt_periodic_timer);

	proc_net_create ("rt_cache", 0, rt_cache_get_info);
#ifdef CONFIG_NET_CLS_ROUTE
	create_proc_read_entry("net/rt_acct", 0, 0, ip_rt_acct_read, NULL);
#endif
}

3.2.5路由查找

1)查缓冲路由rt_hash_table

2)判断是否为多播,多播处理: ip_route_input_mc()

3)进入FIB路由表中查路由:ip_route_input_slow

int ip_route_input(struct sk_buff *skb, u32 daddr, u32 saddr,
		   u8 tos, struct net_device *dev)
{
	struct rtable * rth;
	unsigned	hash;
	int iif = dev->ifindex;

	tos &= IPTOS_RT_MASK;
	hash = rt_hash_code(daddr, saddr^(iif<<5), tos);
	
	read_lock(&rt_hash_table[hash].lock);
	for (rth=rt_hash_table[hash].chain; rth; rth=rth->u.rt_next) {	//在路由缓存hash表中查找
		if (rth->key.dst == daddr &&	//源地址
		    rth->key.src == saddr &&	//目标地址
		    rth->key.iif == iif &&		//输入设备接口
		    rth->key.oif == 0 &&
#ifdef CONFIG_IP_ROUTE_FWMARK
		    rth->key.fwmark == skb->nfmark &&	//转发标记
#endif
		    rth->key.tos == tos) {				//服务类型
			rth->u.dst.lastuse = jiffies;		//bingo! 更新表项最近使用的时间戳
			dst_hold(&rth->u.dst);				//该dst_entry->__refcnt++ 出口引用次数
			rth->u.dst.__use++;					//缓存表项使用次数++
			read_unlock(&rt_hash_table[hash].lock);
			skb->dst = (struct dst_entry*)rth;	//在缓冲表中找到了对应路由表项,将其作为路由出口
			return 0;
		}
	}
	read_unlock(&rt_hash_table[hash].lock);

	/* Multicast recognition logic is moved from route cache to here.
		多播的处理从路由缓存中移到这儿,
	   The problem was that too many Ethernet cards have broken/missing
	   hardware multicast filters :-( As result the host on multicasting
	   network acquires a lot of useless route cache entries, sort of
	   以太网有太多的多播数据报,导致路由
	   SDR messages from all the world. Now we try to get rid of them.
	   缓存的表项被大量占用
	   Really, provided software IP multicast filter is organized
	   reasonably (at least, hashed), it does not result in a slowdown
	   comparing with route cache reject entries.
	   Note, that multicast routers are not affected, because
	   route cache entry is created eventually.
	 */
	if (MULTICAST(daddr)) {		//多播数据报的路由处理
		struct in_device *in_dev;

		read_lock(&inetdev_lock);
		if ((in_dev = __in_dev_get(dev)) != NULL) {
			int our = ip_check_mc(in_dev, daddr);
			if (our
#ifdef CONFIG_IP_MROUTE
			    || (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev))
#endif
			    ) {
				read_unlock(&inetdev_lock);
				return ip_route_input_mc(skb, daddr, saddr, tos, dev, our);
			}
		}
		read_unlock(&inetdev_lock);
		return -EINVAL;
	}
	//到FIB(路由表) 中查找路由,前面路由缓存没找到:-( 
	return ip_route_input_slow(skb, daddr, saddr, tos, dev);
}

4写在后面

本文描述了路由表及其查表过程,并以Linux内核为例进行分析。主要分析的是路由入口、路由缓冲表。以及分析了路由缓冲表的初始化和查表过程ip_route_input()

FIB查表过程 ip_route_input_slow()是路由的核心部分,由于篇幅原因,以后再做分析。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值