路由

 

数据结构

路由函数操作表

struct fib_table {
	struct hlist_node tb_hlist;     //用来将各个路由表连接成一个双向链表
	u32		tb_id;                  //路由标识,最多可以有256个路由表(静态路由、策略路由等等表项)
	unsigned	tb_stamp;
	int		tb_default;        //路由信息结构的队列序号
	int		(*tb_lookup)(struct fib_table *tb, const struct flowi *flp, struct fib_result *res);    //搜索路由表项
	int		(*tb_insert)(struct fib_table *, struct fib_config *);  //插入给定的路由表项
	int		(*tb_delete)(struct fib_table *, struct fib_config *);  //删除给定的路由表项
	int		(*tb_dump)(struct fib_table *table, struct sk_buff *skb,    //dump出路由表的内容
				     struct netlink_callback *cb);
	int		(*tb_flush)(struct fib_table *table);   //刷新路由表项,并删除带有RTNH_F_DEAD标志的fib_node
	void		(*tb_select_default)(struct fib_table *table,   //选择一条默认的路由
					     const struct flowi *flp, struct fib_result *res);

	unsigned char	tb_data[0];     //路由表项的散列表的起始地址,指向fn_hash
};

 

struct fn_hash {
	struct fn_zone	*fn_zones[33];    
	struct fn_zone	*fn_zone_list;    //fn_zone链表
};

 

路由区

struct fn_zone {
	struct fn_zone		*fz_next;	/* Next not empty zone 将不为空的路由表项fn_zone链接在一起,该链表头存储在fn_hash的fn_zone_list中	*/
	struct hlist_head	*fz_hash;	/* Hash table pointer 指向存储路由表项fib_node的散列表	*/
	int			fz_nent;	/* Number of entries  在zone的散列表中的fib_node的数目,用于判断是否需要改变散列表的容量	*/

	int			fz_divisor;	/* Hash divisor	散列表fz_hash的容量,即散列表桶的数目每次扩大2倍,最大1024	*/
	u32			fz_hashmask;	/* (fz_divisor - 1)	*/
#define FZ_HASHMASK(fz)		((fz)->fz_hashmask)

	int			fz_order;	/* Zone order	掩码fz_mask的长度	*/
	__be32			fz_mask;    //利用fz_order构造得到的网络掩码
#define FZ_MASK(fz)		((fz)->fz_mask)
};

路由节点 

struct fib_node {
	struct hlist_node	fn_hash;				//用于散列表中同一桶内的所有fib_node链接成一个双向链表
	struct list_head	fn_alias;				//指向多个fib_alias结构组成的链表
	__be32			fn_key;						//由IP和路由项的netmask与操作后得到,被用作查找路由表的搜索条件
	struct fib_alias        fn_embedded_alias;	//内嵌的fib_alias结构,一般指向最后一个fib_alias
};

路由别名

struct fib_alias {
	struct list_head	fa_list;	//将所有fib_alias组成的链表
	struct fib_info		*fa_info;	//指向fib_info,储存如何处理路由信息
	u8			fa_tos;				//路由的服务类型比特位字段
	u8			fa_type;			//路由表项的类型,间接定义了当路由查找匹配时,应采取的动作
	u8			fa_scope;			//路由表项的作用范围
	u8			fa_state;			//一些标志位,目前只有FA_S_ACCESSED。表示该表项已经被访问过。
#ifdef CONFIG_IP_FIB_TRIE
	struct rcu_head		rcu;
#endif
};

 

路由信息结构

struct fib_info {
	struct hlist_node	fib_hash;			//所有fib_info组成的散列表,该表为全局散列表fib_info_hash
	struct hlist_node	fib_lhash;			//当存在首源地址时,才会将fib_info插入该散列表,该表为全局散列表fib_info_laddrhash
	struct net		*fib_net;
	int			fib_treeref;				//使用该fib_info结构的fib_node的数目
	atomic_t		fib_clntref;			//引用计数,路由查找成功而被持有的引用计数
	int			fib_dead;					//标记路由表项正在被删除的标志,当该标志被设置为1时,警告该数据结构将被删除而不能再使用
	unsigned		fib_flags;				//当前使用的唯一标志是RTNH_F_DEAD,表示下一跳已无效
	int			fib_protocol;				//设置路由的协议
	__be32			fib_prefsrc;			//首选源IP地址
	u32			fib_priority;				//路由优先级,默认为0,值越小优先级越高
	u32			fib_metrics[RTAX_MAX];		//与路由相关的度量值
#define fib_mtu fib_metrics[RTAX_MTU-1]
#define fib_window fib_metrics[RTAX_WINDOW-1]
#define fib_rtt fib_metrics[RTAX_RTT-1]
#define fib_advmss fib_metrics[RTAX_ADVMSS-1]
	int			fib_nhs;					//可用的下一跳数量,通常为1.只有支持多路径路由时,才大于1
#ifdef CONFIG_IP_ROUTE_MULTIPATH
	int			fib_power;
#endif
	struct fib_nh		fib_nh[0];			//表示路由的下一跳
#define fib_dev		fib_nh[0].nh_dev
};

路由跳转结构

 

struct fib_nh {
	struct net_device	*nh_dev;		//该路由表项输出网络设备
	struct hlist_node	nh_hash;		//fib_nh组成的散列表
	struct fib_info		*nh_parent;		//指向所属fib_info结构体
	unsigned		nh_flags;
	unsigned char		nh_scope;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
	int			nh_weight;
	int			nh_power;
#endif
#ifdef CONFIG_NET_CLS_ROUTE
	__u32			nh_tclassid;
#endif
	int			nh_oif;					//输出网络设备索引
	__be32			nh_gw;				//网关地址
};

路由函数表的初始化过程

介绍路由函数表初始化与从中找出路由函数表的过程,我们看inet_init()

static int __init inet_init(void)
{
	struct sk_buff *dummy_skb;
	struct inet_protosw *q;
	struct list_head *r;
	int rc = -EINVAL;
 
    ...
 
    (void)sock_register(&inet_family_ops);
    
    ...
 
    for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
		inet_register_protosw(q);

	/*
	 *	Set the ARP module up
	 */

	arp_init();

	/*
	 *	Set the IP module up
	 */

	ip_init();
 
    ...
}

我们关心其内部调用的ip_init()函数

void __init ip_init(void)
{
	ip_rt_init();
	inet_initpeers();

#if defined(CONFIG_IP_MULTICAST) && defined(CONFIG_PROC_FS)
	igmp_mc_proc_init();
#endif
}

进一步调用了ip_rt_init(),此函数实现了路由函数表初始化功能。

int __init ip_rt_init(void)
{
	int rc = 0;

	atomic_set(&rt_genid, (int) ((num_physpages ^ (num_physpages>>8)) ^
			     (jiffies ^ (jiffies >> 7))));	//设置路由随机数

#ifdef CONFIG_NET_CLS_ROUTE
	ip_rt_acct = __alloc_percpu(256 * sizeof(struct ip_rt_acct));
	if (!ip_rt_acct)
		panic("IP: failed to allocate ip_rt_acct\n");
#endif

	ipv4_dst_ops.kmem_cachep =
		kmem_cache_create("ip_dst_cache", sizeof(struct rtable), 0,
				  SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);	//创建路由项的高速缓存,对象长度为路由表的长度 rtable

	ipv4_dst_blackhole_ops.kmem_cachep = ipv4_dst_ops.kmem_cachep;

	rt_hash_table = (struct rt_hash_bucket *)
		alloc_large_system_hash("IP route cache",
					sizeof(struct rt_hash_bucket),
					rhash_entries,
					(num_physpages >= 128 * 1024) ?
					15 : 17,
					0,
					&rt_hash_log,
					&rt_hash_mask,
					0);									//创建路由哈希桶缓存
	memset(rt_hash_table, 0, (rt_hash_mask + 1) * sizeof(struct rt_hash_bucket));
	rt_hash_lock_init();	//初始化路由哈希队列

	ipv4_dst_ops.gc_thresh = (rt_hash_mask + 1);	//记录回收阈值
	ip_rt_max_size = (rt_hash_mask + 1) * 16;		//哈希桶的最大长度

	devinet_init();
	ip_fib_init();

	rt_secret_timer.function = rt_secret_rebuild;
	rt_secret_timer.data = 0;
	init_timer_deferrable(&rt_secret_timer);

	/* All the timers, started at system startup tend
	   to synchronize. Perturb it a bit.
	 */
	schedule_delayed_work(&expires_work,
		net_random() % ip_rt_gc_interval + ip_rt_gc_interval);

	rt_secret_timer.expires = jiffies + net_random() % ip_rt_secret_interval +
		ip_rt_secret_interval;
	add_timer(&rt_secret_timer);

	if (ip_rt_proc_init())
		printk(KERN_ERR "Unable to create route proc files\n");
#ifdef CONFIG_XFRM
	xfrm_init();
	xfrm4_init();
#endif
	rtnl_register(PF_INET, RTM_GETROUTE, inet_rtm_getroute, NULL);

	return rc;
}

首先通过宏atomic_set以原子操作方式设置原子变量rt_genid为随机数,该随机数在路由缓存生成hash关键字时做为一个参数使用,目的是为了防止DDOS攻击,该随机值后期在每次缓存刷新时也会重新生成。__alloc_percpu函数也是同步操作,这里申请了连续的256个ip_rt_acct空间

struct ip_rt_acct		//用于路由参数统计
{
	__u32 	o_bytes;	//发出的字节
	__u32 	o_packets;	//发出的包数
	__u32 	i_bytes;	//收到的字节
	__u32 	i_packets;	//收到的包数
};

然后创建 ipv4_dst_ops 的slab高速缓存,ipv4_dst_ops是一个全局dst_opt结构变量,创建的高速缓存赋值到kmem_cache成员上,名为ip_dst_cache(用于路由表rtable结构)。

static struct dst_ops ipv4_dst_ops = {
	.family =		AF_INET,
	.protocol =		__constant_htons(ETH_P_IP),
	.gc =			rt_garbage_collect,
	.check =		ipv4_dst_check,
	.destroy =		ipv4_dst_destroy,
	.ifdown =		ipv4_dst_ifdown,
	.negative_advice =	ipv4_negative_advice,
	.link_failure =		ipv4_link_failure,
	.update_pmtu =		ip_rt_update_pmtu,
	.local_out =		__ip_local_out,
	.entry_size =		sizeof(struct rtable),
	.entries =		ATOMIC_INIT(0),
};

关于struct dst_ops结构其实属于协议无关的缓存间的接口,也指定了一些事件的协议通知(如链接出错情况等),这里IPV4的dst_ops实现为ipv4_dst_ops。

然后将ipv4_dst_blackhole_ops(路由项“黑洞”的处理函数表)的kmem_cachep也指向ipv4_dst_ops 的路由表slab高速缓存[TODO 什么作用?]。

然后对rt_hash_table进行初始化

static struct rt_hash_bucket *rt_hash_table __read_mostly;

struct rt_hash_bucket {    //路由哈希队列
	struct rtable	*chain;//路由表结构队列
};

struct rtable
{
	union
	{
		struct dst_entry	dst;
	} u;

	/* Cache lookup keys */
	struct flowi		fl;

	struct in_device	*idev;
	
	int			rt_genid;
	unsigned		rt_flags;
	__u16			rt_type;

	__be32			rt_dst;	/* Path destination	*/
	__be32			rt_src;	/* Path source		*/
	int			rt_iif;

	/* Info on neighbour */
	__be32			rt_gateway;

	/* Miscellaneous cached information */
	__be32			rt_spec_dst; /* RFC1122 specific destination */
	struct inet_peer	*peer; /* long-living peer info */
};

struct dst_entry        //路由项的定义
{
	......
	union {
		struct dst_entry *next;
		struct rtable    *rt_next;
		struct rt6_info   *rt6_next;
		struct dn_route  *dn_next;
	};
};

rt_hash_table专门用于路由表队列,内部包含结构队列指针 chain。rtable是路由表结构体,其开始处有个联合体,内部声明了一个路由项结构变量dst,dst_entry结构的最后也有一个联合体,其内部通过next指针将路由项结构链成了队列,rtable则通过rt_next链成路由表队列。

[TODO rtable结构图]

这里解释一下 fib_table 与rtable 区别,rtable是数据包使用到的结构,fib_table及相关的结构都是搭建路由表rtable的基础,他们为路由表提供具体的路由信息。

继续ip_rt_init函数,通过alloc_large_system_hash函数分配一个rt_hash_bucket的路由表队列空间,rt_hash_table指向其。

alloc_large_system_hash函数从 bootmem 空间分配大量的路由表空间,bootmem空间是内核启动时使用的内存管理策略,主要指内核使用的空间,其意图是从这部分内存中按页进行分配,这部分的页面是不会被linux交换或者回收的,一旦分配就不再改变了。然后通过memset(rt_hash_table, 0, (rt_hash_mask + 1) * sizeof(struct rt_hash_bucket))初始化。其中rt_hash_mask代表分配的路由哈希桶的数量减1。

然后hash锁初始化,再使ipv4_dst_ops记录下可以回收的碎片数gc_thresh(进行强制路由缓存垃圾回收的阀值,为路由缓存hash桶的个数),ip_rt_max_size记录下允许最大的路由缓存条目数,为路由缓存hash桶的16倍。

然后通过devinet_init()函数完成一些注册工作

void __init devinet_init(void)
{
	register_pernet_subsys(&devinet_ops);

	register_gifconf(PF_INET, inet_gifconf);	//注册IO配置程序
	register_netdevice_notifier(&ip_netdev_notifier);	//注册inet_dev_even通知节点
	/*	注册处理路由地址的 netlink	*/
	rtnl_register(PF_INET, RTM_NEWADDR, inet_rtm_newaddr, NULL);
	rtnl_register(PF_INET, RTM_DELADDR, inet_rtm_deladdr, NULL);
	rtnl_register(PF_INET, RTM_GETADDR, NULL, inet_dump_ifaddr);
}

可以看到通过register_pernet_subsys函数向内核注册了一个pernet_operations结构,即网络空间操作表,这里登记的是devinet_ops

static __net_initdata struct pernet_operations devinet_ops = {
	.init = devinet_init_net,
	.exit = devinet_exit_net,
};

提供了初始化网络空间、释放网络空间的钩子函数。

int register_pernet_subsys(struct pernet_operations *ops)
{
	int error;
	mutex_lock(&net_mutex);
	error =  register_pernet_operations(first_device, ops);
	mutex_unlock(&net_mutex);
	return error;
}

#ifdef CONFIG_NET_NS    //在内核中CONFIG_NET_NS配置选项是为了让用户自定义自己的网络空间结构
static int register_pernet_operations(struct list_head *list,
				      struct pernet_operations *ops)
{
	struct net *net, *undo_net;
	int error;

	list_add_tail(&ops->list, list);
	if (ops->init) {
		for_each_net(net) {
			error = ops->init(net);
			if (error)
				goto out_undo;
		}
	}
	return 0;

out_undo:
	/* If I have an error cleanup all namespaces I initialized */
	list_del(&ops->list);
	if (ops->exit) {
		for_each_net(undo_net) {
			if (undo_net == net)
				goto undone;
			ops->exit(undo_net);
		}
	}
undone:
	return error;
}

#else

static int register_pernet_operations(struct list_head *list,
				      struct pernet_operations *ops)
{
	if (ops->init == NULL)
		return 0;
	return ops->init(&init_net);
}

#endif

CONFIG_NET_NS 

register_gifconf()函数向内核注册了一个SIOCGIF处理程序(socket IO Config Interface),gif指generous interface configure(通用接口配置),这里注册了inet_gifconf

int register_gifconf(unsigned int family, gifconf_func_t * gifconf)
{
	if (family >= NPROTO)
		return -EINVAL;
	gifconf_list[family] = gifconf;
	return 0;
}

register_netdevice_

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值