路由

 

数据结构

路由函数操作表

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_notifier()函数注册了一个ip_netdev-notifier通知节点

static struct notifier_block ip_netdev_notifier = {
	.notifier_call =inetdev_event,
};

走的比较细节了,回到ip_rt_init函数,调用ip_fib_init()函数初始化默认路由表

void __init ip_fib_init(void)
{	/*	注册用于管理路由的 netlink	*/
	rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL);
	rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL);
	rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib);

	register_pernet_subsys(&fib_net_ops);
	register_netdevice_notifier(&fib_netdev_notifier);
	register_inetaddr_notifier(&fib_inetaddr_notifier);

	fib_hash_init();
}

代码调用了三次rtnl_register,向rtnl_msg_handlers注册了路由的三个操作,创建、删除、获取路由。

然后调用了register_pernet_subsys,其中执行了fib_net_ops的init函数

static struct pernet_operations fib_net_ops = {
	.init = fib_net_init,
	.exit = fib_net_exit,
};

static int __net_init fib_net_init(struct net *net)
{
	int error;

	error = ip_fib_net_init(net);
	if (error < 0)
		goto out;
	error = nl_fib_lookup_init(net);
	if (error < 0)
		goto out_nlfl;
	error = fib_proc_init(net);
	if (error < 0)
		goto out_proc;
out:
	return error;

out_proc:
	nl_fib_lookup_exit(net);
out_nlfl:
	ip_fib_net_exit(net);
	goto out;
}

先通过ip_fib_init 来初始化init_net 网络空间的fib_table_hash 路由表队列。ps:划重点

static int __net_init ip_fib_net_init(struct net *net)
{
	int err;
	unsigned int i;

	net->ipv4.fib_table_hash = kzalloc(
			sizeof(struct hlist_head)*FIB_TABLE_HASHSZ, GFP_KERNEL);	//为路由函数表队列分配空间
	if (net->ipv4.fib_table_hash == NULL)
		return -ENOMEM;

	for (i = 0; i < FIB_TABLE_HASHSZ; i++)
		INIT_HLIST_HEAD(&net->ipv4.fib_table_hash[i]);	//初始化每个队列头

	err = fib4_rules_init(net);	//初始化本地路由函数表和主路由函数表并链入到路由函数表队列数组中
	if (err < 0)
		goto fail;
	return 0;

fail:
	kfree(net->ipv4.fib_table_hash);	//出现错误则释放路由函数表队列空间
	return err;
}

其中FIB_TABLE_HASHZ定义为256,这里通过kzalloc()内存分配函数来完成的,这里GFP_KERNEL标志表示内核新分配的内存自动清零。然后变量每个路由函数表头,使用宏INIT_HLIST_HEAD初始化

#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)

接下来调用fib4_rules_init函数注册两个路由表并登记到队列数组中去。我们首先看下没有打开多路由规则选项的代码

static int __net_init fib4_rules_init(struct net *net)
{
	struct fib_table *local_table, *main_table;

	local_table = fib_hash_table(RT_TABLE_LOCAL);	//创建本地路由函数表
	if (local_table == NULL)
		return -ENOMEM;

	main_table  = fib_hash_table(RT_TABLE_MAIN);	//创建主路由函数表
	if (main_table == NULL)
		goto fail;

	hlist_add_head_rcu(&local_table->tb_hlist,
				&net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX]);
	hlist_add_head_rcu(&main_table->tb_hlist,
				&net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]);	//将两个路由表链入到队列数组 fib_table_hash 中
	return 0;

fail:
	kfree(local_table);
	return -ENOMEM;
}

终于到了本地路由表初始化的地方。

static inline struct fib_table *fib_get_table(struct net *net, u32 id)
{
	struct hlist_head *ptr;
 
	ptr = id == RT_TABLE_LOCAL ?
		&net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX] :    //255
		&net->ipv4.fib_table_hash[TABLE_MAIN_INDEX];       //254
	return hlist_entry(ptr->first, struct fib_table, tb_hlist);
}

首先通过fib_hash_table() 函数创建路由函数表

struct fib_table *fib_hash_table(u32 id)
{
	struct fib_table *tb;

	tb = kmalloc(sizeof(struct fib_table) + sizeof(struct fn_hash),
		     GFP_KERNEL);   //在分配fib_table时,连同fn_hash一起分配
	if (tb == NULL)
		return NULL;
    /*  初始化设置fib_table  */
	tb->tb_id = id;
	tb->tb_default = -1;
	tb->tb_lookup = fn_hash_lookup;
	tb->tb_insert = fn_hash_insert;
	tb->tb_delete = fn_hash_delete;
	tb->tb_flush = fn_hash_flush;
	tb->tb_select_default = fn_hash_select_default;
	tb->tb_dump = fn_hash_dump;
	memset(tb->tb_data, 0, sizeof(struct fn_hash));
	return tb;
}

从数据结构中可以看到,当分配fib_table空间的时候,连同fn_hash结构一起分配,tb_data数组则表示紧跟在结构后面的fn_hash。fn_hash是路由区队列结构体。

 

struct fn_hash {    //路由区队列结构
	struct fn_zone	*fn_zones[33];    
	struct fn_zone	*fn_zone_list;    //指向第一个路由区
};

路由区队列结构中包含一个路由区结构数组和一个路由结构队列,struct 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	子网掩码位数	*/
	__be32			fz_mask;    //子网掩码
#define FZ_MASK(fz)		((fz)->fz_mask)
};

fz_next指向所在队列的下一个路由区结构,由它链入到路由区结构队列fn_zone_list中。

我们在这里看到fz_mask子网掩码的概念,子网掩码是用来判断任意两台计算机的IP地址是否属于同一个子网络的,即是否属于一个路由区的。简单理解就是两台计算机各自的IP地址与子网掩码与运算,结果相同的则说明这两台计算机处于同一网段,即两台计算机就可以直接的通信。

对应IPV4协议来说,它使用的是32位的网络掩码值,所以每一个路由表就会有33套路由区来表示,其中fn_zone[0]用于默认网关。

fib_hash_table函数将fn_hash结构紧贴在路由函数表tb后面一起分配内存空间,之后对tb进行初始化设置,挂入钩子函数,最后通过memset函数将tb_data所指向的fn_hash结构空间清零初始化。

主路由函数表是内核默认使用的,如果没有指定使用的函数表则使用它。

之后调用hlist_add_head_rcu()函数将新创建的两个路由函数表插入到网络空间的ipv4的路由函数表队列中。

static inline void hlist_add_head_rcu(struct hlist_node *n,
					struct hlist_head *h)
{
	struct hlist_node *first = h->first;
	n->next = first;
	n->pprev = &h->first;
	smp_wmb();
	if (first)
		first->pprev = &n->next;
	h->first = n;
}

一直回到fib_net_init()函数中,接下来调用了nl_fib_lookup_init()函数

static int nl_fib_lookup_init(struct net *net)
{
	struct sock *sk;
	sk = netlink_kernel_create(net, NETLINK_FIB_LOOKUP, 0,
				   nl_fib_input, NULL, THIS_MODULE);
	if (sk == NULL)
		return -EAFNOSUPPORT;
	net->ipv4.fibnl = sk;
	return 0;
}

netlink_kernel_create函数提供了非阻塞的消息传递功能[TODO]

这里将新建的sock记录到网络空间的ipv4.fibnl中,从此建立了通过网络空间找到netlink_sock 的桥梁。

回到fib_net_init()函数中,调用了fib_proc_init()在内核proc文件系统中创建的一个route节点。

int __net_init fib_proc_init(struct net *net)
{
	if (!proc_net_fops_create(net, "fib_trie", S_IRUGO, &fib_trie_fops))
		goto out1;

	if (!proc_net_fops_create(net, "fib_triestat", S_IRUGO,
				  &fib_triestat_fops))
		goto out2;

	if (!proc_net_fops_create(net, "route", S_IRUGO, &fib_route_fops))
		goto out3;

	return 0;

out3:
	proc_net_remove(net, "fib_triestat");
out2:
	proc_net_remove(net, "fib_trie");
out1:
	return -ENOMEM;
}

然后回到ip_fib_init()函数中,调用register_netdevice_notifier函数将fib_nev_notifier设备通知节点链入到内核的设备通知链netdev_chain,然后通过register_inetaddr_notifier函数将fib_inetaddr_notifier设备通知节点链入到inetaddr_chain地址通知链。

[TODO关于通知链的event]

ip_fib_init()函数的最后调用fib_hash_init来创建两个高速slab缓存

void __init fib_hash_init(void)
{
	fn_hash_kmem = kmem_cache_create("ip_fib_hash", sizeof(struct fib_node),
					 0, SLAB_PANIC, NULL);

	fn_alias_kmem = kmem_cache_create("ip_fib_alias", sizeof(struct fib_alias),
					  0, SLAB_PANIC, NULL);

}

一个用于fib_node结构使用,它是路由项结构;另一个用于fib_alias,别名结构使用。

我们继续返回到ip_rt_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;
}

初始化了rt_secret_timer定时器,然后启动了该定时器,由它执行 rt_secret_rebuild 函数定时对缓存的冲刷。rtnl_register函数在rtnl_msg_handlers数组中登记了一个获得路由的netlink,其doit函数为inet_rtm_getroute。

回到ip_init函数,调用了inet_initpeers函数,它初始化来外部IP地址(相对于服务器来说是指客户端地址)的高速缓存

/* Called from ip_output.c:ip_init  */
void __init inet_initpeers(void)
{
	struct sysinfo si;

	/* Use the straight interface to information about memory. */
	si_meminfo(&si);
	/* The values below were suggested by Alexey Kuznetsov
	 * .  I don't have any opinion about the values
	 * myself.  --SAW
	 */
	if (si.totalram <= (32768*1024)/PAGE_SIZE)
		inet_peer_threshold >>= 1; /* max pool size about 1MB on IA32 */
	if (si.totalram <= (16384*1024)/PAGE_SIZE)
		inet_peer_threshold >>= 1; /* about 512KB */
	if (si.totalram <= (8192*1024)/PAGE_SIZE)
		inet_peer_threshold >>= 2; /* about 128KB */

	peer_cachep = kmem_cache_create("inet_peer_cache",
			sizeof(struct inet_peer),
			0, SLAB_HWCACHE_ALIGN|SLAB_PANIC,
			NULL);

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

创建了inet_peer_cache高速缓存inet_peer结构,用于缓存客户端的IP路由信息,同时创建了peer_periodic_timer定时器为周期性回收这段缓存提供依据。

初始化过程完了,给我们提供了一个空的路由表结构,下面我们继续跟着流程,看如何根据路由函数表如何查找路由信息。

local_table = fib_get_table(net, RT_TABLE_LOCAL);		//查找本地路由函数表
	if (local_table) {
		ret = RTN_UNICAST;
		if (!local_table->tb_lookup(local_table, &fl, &res)) {
			if (!dev || dev == res.fi->fib_dev)
				ret = res.type;
			fib_res_put(&res);
		}
	}
	return ret;
--------------------- 
作者:泮小俊233 
来源:CSDN 
原文:https://blog.csdn.net/panxj856856/article/details/87907073 
版权声明:本文为博主原创文章,转载请附上博文链接!

fib_get_table()得到的是本地路由表,然后通过localtable->tb_lookup函数即fn_hash_lookup查找路由信息。

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;
}

看起来操作很复杂,其实逻辑很简单,可以抽象为

map<子网, map> tb;

res = tb.get(flp->nlu.ipv4.daddr & fz).get(flp->nlu.ipv4.daddr);

我们再来看下fib_semantic_match

/* Note! fib_semantic_match intentionally uses  RCU list functions. */
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;
}

这里出现了fib_alias数据结构,路由别名,为了区别不同路由至同一子网而使用的,即相同网段的每一条路由表项有各自的fib_alias结构。

多个fib_alias可以共享一个fib_info结构,即一个路由可以通往多个子网,即指向多个fib_node。

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

路由节点fib_node中有一个路由别名队列fn_alias,从fn_hash_lookup传递下来的正是路由节点的fn_alias队列指针。这里的循环是沿着队列头来的。

路由节点fib_node,代表一个路由所指向的子网,不同的路由虽然也可以指向同一个子网,但他们的路由信息不同。

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
};

fib_semantic_match一次取出挂入的fib_alias结构,再检查fa_tos,即路由别名的服务类型是否设置了,如果设置了则检查是否与键值中的服务类型相同,接着查看是否定义了路由范围,要求键值中的路由范围小于别名结构的,接着给别名结构状态标志增加FA_S_ACCESSED,表示已经访问过了。

static const struct
{
	int	error;
	u8	scope;
} fib_props[RTN_MAX + 1] = {
	{
		.error	= 0,
		.scope	= RT_SCOPE_NOWHERE,
	},	/* RTN_UNSPEC */
	{
		.error	= 0,
		.scope	= RT_SCOPE_UNIVERSE,
	},	/* RTN_UNICAST */
	{
		.error	= 0,
		.scope	= RT_SCOPE_HOST,
	},	/* RTN_LOCAL */
	{
		.error	= 0,
		.scope	= RT_SCOPE_LINK,
	},	/* RTN_BROADCAST */
	{
		.error	= 0,
		.scope	= RT_SCOPE_LINK,
	},	/* RTN_ANYCAST */
	{
		.error	= 0,
		.scope	= RT_SCOPE_UNIVERSE,
	},	/* RTN_MULTICAST */
	{
		.error	= -EINVAL,
		.scope	= RT_SCOPE_UNIVERSE,
	},	/* RTN_BLACKHOLE */
	{
		.error	= -EHOSTUNREACH,
		.scope	= RT_SCOPE_UNIVERSE,
	},	/* RTN_UNREACHABLE */
	{
		.error	= -EACCES,
		.scope	= RT_SCOPE_UNIVERSE,
	},	/* RTN_PROHIBIT */
	{
		.error	= -EAGAIN,
		.scope	= RT_SCOPE_UNIVERSE,
	},	/* RTN_THROW */
	{
		.error	= -EINVAL,
		.scope	= RT_SCOPE_NOWHERE,
	},	/* RTN_NAT */
	{
		.error	= -EINVAL,
		.scope	= RT_SCOPE_NOWHERE,
	},	/* RTN_XRESOLVE */
};

然后取出fib_props中别名结构对应的错误码,如果为0,表示表示支持该路由类型,就从别名结构中取出struct fib_info fi,这个结构代表了一个路由,反应了一条路由信息,存储真正重要路由信息,即如何到达目的地。

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
};

从路由别名结构中取得路由信息结构指针fi后,接下来检查fi是否处于被删除状态,这要根据它的fib_flags标志中的RTNH_F_DEAD来决定。然后根据路由别名记录的路由类型来执行switch语句,我们看下宏for_nexthops

#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
	for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)

从fib_info获取可用的下一跳指针即fib_nh跳转指针,一个fib_nh结构代表一次路由可跳转的内容

struct fib_nh {
	struct net_device	*nh_dev;		//该路由表项输出网络设备
	struct hlist_node	nh_hash;		//链入到路由设备队列的哈希节点
	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;				//网关IP地址
};

每一次跳转都用这个结构表示,这段代码目的是找到未处以被移除状态且路由键值未要求指定发送设备,或者指定发送设备与跳转结构的目标设备相同,则跳出循环(说明该路由信息结构中具有可以跳转的结构)。则记录结果。否则失败。

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);

我们这一块讲了如何根据路由函数表查找相应路由结构,并未考虑路由函数表初始化之后,都为空的,却在上述代码中出现的路由节点结构、路由别名结构、路由信息结构以及路由跳转结构的初始化。

路由表项的添加

    /*  初始化设置fib_table  */
	tb->tb_id = id;
	tb->tb_default = -1;
	tb->tb_lookup = fn_hash_lookup;
	tb->tb_insert = fn_hash_insert;
	tb->tb_delete = fn_hash_delete;
	tb->tb_flush = fn_hash_flush;

添加一条新的路由表项是通过fn_hash_insert()函数实现的。函数过长,一点一点来

static int fn_hash_insert(struct fib_table *tb, struct fib_config *cfg)
{
	struct fn_hash *table = (struct fn_hash *) tb->tb_data;
	struct fib_node *new_f = NULL;
	struct fib_node *f;
	struct fib_alias *fa, *new_fa;
	struct fn_zone *fz;
	struct fib_info *fi;
	u8 tos = cfg->fc_tos;
	__be32 key;
	int err;

	if (cfg->fc_dst_len > 32)   //检查设置的ip地址长度
		return -EINVAL;

	fz = table->fn_zones[cfg->fc_dst_len];  //取得对应路由区结构
	if (!fz && !(fz = fn_new_zone(table, cfg->fc_dst_len))) //不存在则创建
		return -ENOBUFS;

	key = 0;
	if (cfg->fc_dst) {
		if (cfg->fc_dst & ~FZ_MASK(fz))
			return -EINVAL;
		key = fz_key(cfg->fc_dst, fz);  //确定子网键值
	}

	fi = fib_create_info(cfg);  //查找或创建路由信息结构

之前我们介绍了没有支持多路由选项,内核会建立两个路由函数表,一个是本地路由函数表,一个是主路由函数表。按照之前的流程这里传递进来的第一个参数tb是本地函数路由表,第二个参数是路由配置结构fib_config cfg。

struct fib_config {
	u8			fc_dst_len;		//子网掩码位数
	u8			fc_tos;			//服务类型 TOS
	u8			fc_protocol;	//路由协议
	u8			fc_scope;		//路由范围
	u8			fc_type;		//路由类型
	/* 3 bytes unused */
	u32			fc_table;		//路由函数表
	__be32			fc_dst;		//路由目标地址
	__be32			fc_gw;		//网关
	int			fc_oif;			//网络设备id
	u32			fc_flags;		//路由标志位
	u32			fc_priority;	//路由优先级
	__be32			fc_prefsrc;	//指定的IP地址
	struct nlattr		*fc_mx;	//指向netlink属性队列
	struct rtnexthop	*fc_mp;	//配置的跳转结构队列
	int			fc_mx_len;		//全部netlink属性队列的总长度
	int			fc_mp_len;		//全部配置跳转结构的总长度
	u32			fc_flow;
	u32			fc_nlflags;		//netlink 标志位
	struct nl_info		fc_nlinfo;	//netlink 信息结构
 };

这个数据结构可以理解成是用户配置上去的,即通过net-tools、IPROUTE2设置、通知链等方式传递进来。

函数首先通过tb->data指针得到了代表路由区队列的fn_hash指针,然后通过路由配置结构得到配置的路由服务类型,fc_dst_len记录的是子网掩码的位数,首先chck是否不超过32位,然后以它为下标从fn_hash中取出对应的fn_zone,即对应的路由区结构指针,如果该结构(所在的子网的路由区结构)为空,那么通过fn_new_zone()函数创建新的路由区结构。

static struct fn_zone *
fn_new_zone(struct fn_hash *table, int z)
{
	int i;
	struct fn_zone *fz = kzalloc(sizeof(struct fn_zone), GFP_KERNEL);
	if (!fz)
		return NULL;

	if (z) {	//检查子网掩码位数
		fz->fz_divisor = 16;	//哈希头数量默认16
	} else {
		fz->fz_divisor = 1;
	}
	fz->fz_hashmask = (fz->fz_divisor - 1);	//确定哈希头队列的掩码
	fz->fz_hash = fz_hash_alloc(fz->fz_divisor);	//分配哈希头队列结构
	if (!fz->fz_hash) {
		kfree(fz);
		return NULL;
	}
	fz->fz_order = z;	//确定子网掩码位数
	fz->fz_mask = inet_make_mask(z);	//转化成子网掩码值

	/* Find the first not empty zone with more specific mask */
	for (i=z+1; i<=32; i++)	//找到新建的路由区,在链表fn_zone_list中的位置
		if (table->fn_zones[i])
			break;
	write_lock_bh(&fib_hash_lock);
	if (i>32) {
		/* No more specific masks, we are the first. 没找到则挂到fn_zone_list首节点 */
		fz->fz_next = table->fn_zone_list;
		table->fn_zone_list = fz;
	} else {	//找到则插入到fn_zones[i]后面
		fz->fz_next = table->fn_zones[i]->fz_next;
		table->fn_zones[i]->fz_next = fz;
	}
	table->fn_zones[z] = fz;
	fib_hash_genid++;
	write_unlock_bh(&fib_hash_lock);
	return fz;
}

逻辑比较简单,在插入队列的时候加了写屏障。回到 fn_hash_insert 函数中,检查 cfg->fc_dst 是否指定了目的IP地址,如果指定了则通过fz_key函数计算网段地址,子网键值,即 IP地址&子网掩码 

然后调用 fib_create_info 根据配置信息创建路由信息结构,代码很长分段分析

struct fib_info *fib_create_info(struct fib_config *cfg)
{
	int err;
	struct fib_info *fi = NULL;
	struct fib_info *ofi;
	int nhs = 1;
	struct net *net = cfg->fc_nlinfo.nl_net;

	/* Fast check to catch the most weird cases	检查指定的范围 */
	if (fib_props[cfg->fc_type].scope > cfg->fc_scope)	//配置的cfg的使用范围应大于对应类型的范围
		goto err_inval;

#ifdef CONFIG_IP_ROUTE_MULTIPATH
	if (cfg->fc_mp) {
		nhs = fib_count_nexthops(cfg->fc_mp, cfg->fc_mp_len);
		if (nhs == 0)
			goto err_inval;
	}
#endif

	err = -ENOBUFS;
	if (fib_info_cnt >= fib_hash_size) {
		unsigned int new_size = fib_hash_size << 1;
		struct hlist_head *new_info_hash;
		struct hlist_head *new_laddrhash;
		unsigned int bytes;

		if (!new_size)
			new_size = 1;
		bytes = new_size * sizeof(struct hlist_head *);
		new_info_hash = fib_hash_alloc(bytes);
		new_laddrhash = fib_hash_alloc(bytes);
		if (!new_info_hash || !new_laddrhash) {
			fib_hash_free(new_info_hash, bytes);
			fib_hash_free(new_laddrhash, bytes);
		} else
			fib_hash_move(new_info_hash, new_laddrhash, new_size);

		if (!fib_hash_size)
			goto failure;
	}

注意到,如果开启了多通道路由选项,那么需要检查cfg->fc_map是否存在,它是rtnexthop结构指针。

struct rtnexthop	//配置的跳转结构
{
	unsigned short		rtnh_len;	//指定的跳转结构长度
	unsigned char		rtnh_flags;	//指定的标志位
	unsigned char		rtnh_hops;	//指定的跳转次数
	int			rtnh_ifindex;	//指定的跳转设备
};

可以看到这个结构指定了路由过程中跳转结构的信息,这些信息将设置到内核的跳转结构fib_nh中,如果配置了,则通 fib_count_nexthops 统计指定的跳转次数。

static int fib_count_nexthops(struct rtnexthop *rtnh, int remaining)
{
	int nhs = 0;

	while (rtnh_ok(rtnh, remaining)) {
		nhs++;
		rtnh = rtnh_next(rtnh, &remaining);
	}

	/* leftover implies invalid nexthop configuration, discard it */
	return remaining > 0 ? 0 : nhs;
}

static inline int rtnh_ok(const struct rtnexthop *rtnh, int remaining)
{
	return remaining >= sizeof(*rtnh) &&
	       rtnh->rtnh_len >= sizeof(*rtnh) &&
	       rtnh->rtnh_len <= remaining;
}

static inline struct rtnexthop *rtnh_next(const struct rtnexthop *rtnh,
                                         int *remaining)
{
	int totlen = NLA_ALIGN(rtnh->rtnh_len);	//取得rtnexthop结构的长度

	*remaining -= totlen;	//减少长度范围
	return (struct rtnexthop *) ((char *) rtnh + totlen);	//指向下一个rtnexthop结构
}

我们看到remaining为cfg的fc_mp_len,它记录着配置的跳转结构的总长度,我们看到rtnexthop在内存中以数组的形式存放,这里只需“配置的跳转结构”在指定范围内,不断叠加nhs,统计下一跳可以跳转的次数。[TODO 关系图]

继续回到fib_create_info函数,根据我们hashmap,我们知道扩容机制,这里则使用fib_info_cnt记录路由信息结构的数量,fib_hash_size则表示路由信息结构队列fib_info_hash的长度。

static unsigned int fib_hash_size;
static unsigned int fib_info_cnt;

这里的数据结构很有意思,我的理解就是二维hashmap,并不是简单的map套map,而是每个fib_info有两个指针,分别用于两种map。

fib_hash用于fib_info_hash、fib_lhash用于fib_info_laddrhash

	if (fib_info_cnt >= fib_hash_size) {
		unsigned int new_size = fib_hash_size << 1;	//新长度
		struct hlist_head *new_info_hash;
		struct hlist_head *new_laddrhash;
		unsigned int bytes;

		if (!new_size)
			new_size = 1;
		bytes = new_size * sizeof(struct hlist_head *);
		new_info_hash = fib_hash_alloc(bytes);
		new_laddrhash = fib_hash_alloc(bytes);
		if (!new_info_hash || !new_laddrhash) {	//有一个内存申请失败则都释放
			fib_hash_free(new_info_hash, bytes);
			fib_hash_free(new_laddrhash, bytes);
		} else
			fib_hash_move(new_info_hash, new_laddrhash, new_size);//均申请成功

		if (!fib_hash_size)
			goto failure;
	}

static void fib_hash_move(struct hlist_head *new_info_hash,
			  struct hlist_head *new_laddrhash,
			  unsigned int new_size)
{
	struct hlist_head *old_info_hash, *old_laddrhash;
	unsigned int old_size = fib_hash_size;
	unsigned int i, bytes;

	spin_lock_bh(&fib_info_lock);
	old_info_hash = fib_info_hash;
	old_laddrhash = fib_info_laddrhash;
	fib_hash_size = new_size;

	for (i = 0; i < old_size; i++) {
		struct hlist_head *head = &fib_info_hash[i];
		struct hlist_node *node, *n;
		struct fib_info *fi;

		hlist_for_each_entry_safe(fi, node, n, head, fib_hash) {
			struct hlist_head *dest;
			unsigned int new_hash;

			hlist_del(&fi->fib_hash);	//从旧链摘除

			new_hash = fib_info_hashfn(fi);	//在新链中的位置
			dest = &new_info_hash[new_hash];	//新链的队列头
			hlist_add_head(&fi->fib_hash, dest);	//链入
		}
	}
	fib_info_hash = new_info_hash;

	for (i = 0; i < old_size; i++) {
		struct hlist_head *lhead = &fib_info_laddrhash[i];
		struct hlist_node *node, *n;
		struct fib_info *fi;

		hlist_for_each_entry_safe(fi, node, n, lhead, fib_lhash) {
			struct hlist_head *ldest;
			unsigned int new_hash;

			hlist_del(&fi->fib_lhash);

			new_hash = fib_laddr_hashfn(fi->fib_prefsrc);
			ldest = &new_laddrhash[new_hash];
			hlist_add_head(&fi->fib_lhash, ldest);
		}
	}
	fib_info_laddrhash = new_laddrhash;

	spin_unlock_bh(&fib_info_lock);

	bytes = old_size * sizeof(struct hlist_head *);
	fib_hash_free(old_info_hash, bytes);
	fib_hash_free(old_laddrhash, bytes);
}

感觉好像扩容了2*2,后来想想,还是仅仅扩容了两倍,只不过两个map描述的是同一个结构的不同信息罢了。

[TODO这里还有很多内核操作的细节,回来分析] 读到这里,对内存中fib_info的数据结构多熟悉了些。

回到fib_create_info函数,调整完两个队列数组容量后,开始对fib_info的创建与初始化

	fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);	//为新路由结构申请空间
	if (fi == NULL)
		goto failure;
	fib_info_cnt++;	//递增路由信息结构的计数器

	fi->fib_net = hold_net(net);
	fi->fib_protocol = cfg->fc_protocol;
	fi->fib_flags = cfg->fc_flags;
	fi->fib_priority = cfg->fc_priority;
	fi->fib_prefsrc = cfg->fc_prefsrc;

	fi->fib_nhs = nhs;
	change_nexthops(fi) {	//让所有允许的跳转结构与该路由信息结构挂钩
		nh->nh_parent = fi;
	} endfor_nexthops(fi)

	if (cfg->fc_mx) {	//如果指定了netlink的属性队列
		struct nlattr *nla;
		int remaining;

		nla_for_each_attr(nla, cfg->fc_mx, cfg->fc_mx_len, remaining) {	//循环 取得的每个属性结构
			int type = nla_type(nla);

			if (type) {
				if (type > RTAX_MAX)
					goto err_inval;
				fi->fib_metrics[type - 1] = nla_get_u32(nla);	//记录属性结构装载的数据地址
			}
		}
	}

首先通过kzalloc函数在内存中为路由信息结构分配空间,在这里可以看到分配的空间大小包含了之前统计的下一跳转的结构空间。之前有对nhs进行统计。分配成功后递增路由信息结构计数器。然后是对fib_info这个结构的初始化设置、网络空间fib_net指针、指定IP地址、优先级、协议等内容,然后把下一跳转队列的数量nhs也记录其中。

#define change_nexthops(fi) { int nhsel; struct dn_fib_nh *nh;\
	for(nhsel = 0, nh = (struct dn_fib_nh *)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++)

#define endfor_nexthops(fi) }

	change_nexthops(fi) {	//让所有允许的跳转结构与该路由信息结构挂钩
		nh->nh_parent = fi;
	} endfor_nexthops(fi)

逻辑很简单,遍历所以相关nh,使其nh_parent指向fi。

其中fc_mx指向struct nlattr 结构队列,即netlink的属性集合。

/*
 *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 * |        Header       | Pad |     Payload       | Pad |
 * |   (struct nlattr)   | ing |                   | ing |
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 *  <-------------- nlattr->nla_len -------------->
 */

struct nlattr
{
	__u16           nla_len;
	__u16           nla_type;
};

如果fc_mx存在的话,则通过宏 nla_for_each_attr 遍历所有属性结构

#define nla_for_each_attr(pos, head, len, rem) \
	for (pos = head, rem = len; \
	     nla_ok(pos, rem); \
	     pos = nla_next(pos, &(rem)))

static inline int nla_ok(const struct nlattr *nla, int remaining)
{
	return remaining >= sizeof(*nla) &&
	       nla->nla_len >= sizeof(*nla) &&
	       nla->nla_len <= remaining;
}

static inline struct nlattr *nla_next(const struct nlattr *nla, int *remaining)
{
	int totlen = NLA_ALIGN(nla->nla_len);

	*remaining -= totlen;
	return (struct nlattr *) ((char *) nla + totlen);
}

这儿能看出这是个nlattr数组,遍历方式跟之前的nlh一样。依次取出nlattr结构后调用nla_type()函数取得属性类型,其中fc_max_len记录着整个队列的长度。

static inline int nla_type(const struct nlattr *nla)
{
	return nla->nla_type & NLA_TYPE_MASK;
}

以nla_type为下标初始化路由信息中的fib_metrics数组,在数组的相应位置记录下属性结构装载的数据地址。

static inline u32 nla_get_u32(struct nlattr *nla)
{
	return *(u32 *) nla_data(nla);
}


static inline void *nla_data(const struct nlattr *nla)
{
	return (char *) nla + NLA_HDRLEN;
}

可以看到其指针指向nla结构跨过NLA_HDRLEN头结构后的地址。

其中fc_mp指向的struct rtnexthop “配置的跳转结构”队列,调用fib_get_nhs函数修改路由信息中的跳转结构

static int fib_get_nhs(struct fib_info *fi, struct rtnexthop *rtnh,
		       int remaining, struct fib_config *cfg)
{
	change_nexthops(fi) {	//取出每个跳转结构
		int attrlen;

		if (!rtnh_ok(rtnh, remaining))	//控制范围
			return -EINVAL;

		nh->nh_flags = (cfg->fc_flags & ~0xFF) | rtnh->rtnh_flags;
		nh->nh_oif = rtnh->rtnh_ifindex;
		nh->nh_weight = rtnh->rtnh_hops + 1;

		attrlen = rtnh_attrlen(rtnh);	//检查属性结构后面是否还有内容
		if (attrlen > 0) {
			struct nlattr *nla, *attrs = rtnh_attrs(rtnh);

			nla = nla_find(attrs, attrlen, RTA_GATEWAY);	//取得属性数据中的网关地址
			nh->nh_gw = nla ? nla_get_be32(nla) : 0;
#ifdef CONFIG_NET_CLS_ROUTE
			nla = nla_find(attrs, attrlen, RTA_FLOW);
			nh->nh_tclassid = nla ? nla_get_u32(nla) : 0;
#endif
		}

		rtnh = rtnh_next(rtnh, &remaining);	//指向下一个
	} endfor_nexthops(fi);

	return 0;
}

这里的逻辑是取出“配置的跳转结构”rtnexthop对路由信息结构中的fib_nh跳转结构进行设置,包括标志位、指定的设备、跳转次数等。

其中rtnh_attrlen函数计算rtnexthop结构后面的附加内容长度attrlen,就是netlink的属性结构struct nlattr。即nlattr属性结构队列紧跟在所属的配置的跳转结构后面。

然后通过nla_find函数循环对应的属性结构队列,得到指定类型的属性结构,这里得到的是网关类型的属性结构。当然其承载着即为网关地址,于是将其结果的data指针赋值给了跳转结构的nh_gw。

回到fib_create_info函数中,我们看余下的代码。

	if (fib_props[cfg->fc_type].error) {
		if (cfg->fc_gw || cfg->fc_oif || cfg->fc_mp)
			goto err_inval;
		goto link_it;
	}

	if (cfg->fc_scope > RT_SCOPE_HOST)	//配置的范围比本机范围还小,出错
		goto err_inval;

	if (cfg->fc_scope == RT_SCOPE_HOST) {	//配置的范围等于本机范围
		struct fib_nh *nh = fi->fib_nh;

		/* Local address is added. */
		if (nhs != 1 || nh->nh_gw)	//检查跳转次数和网关地址
			goto err_inval;
		nh->nh_scope = RT_SCOPE_NOWHERE;	//修改跳转范围
		nh->nh_dev = dev_get_by_index(net, fi->fib_nh->nh_oif);
		err = -ENODEV;
		if (nh->nh_dev == NULL)
			goto failure;
	} else {	//路由范围不是本机
		change_nexthops(fi) {
			if ((err = fib_check_nh(cfg, fi, nh)) != 0)	//检查每一个跳转地址的合法性
				goto failure;
		} endfor_nexthops(fi)
	}

	if (fi->fib_prefsrc) {	//如果指定了IP地址,则检查其地址类型
		if (cfg->fc_type != RTN_LOCAL || !cfg->fc_dst ||
		    fi->fib_prefsrc != cfg->fc_dst)
			if (inet_addr_type(net, fi->fib_prefsrc) != RTN_LOCAL)
				goto err_inval;
	}

link_it:
	if ((ofi = fib_find_info(fi)) != NULL) {	//检查是否存在相同的路由信息结构
		fi->fib_dead = 1;
		free_fib_info(fi);
		ofi->fib_treeref++;
		return ofi;	//如果存在则使用旧的路由信息结构
	}

	fi->fib_treeref++;
	atomic_inc(&fi->fib_clntref);
	spin_lock_bh(&fib_info_lock);
	hlist_add_head(&fi->fib_hash,
		       &fib_info_hash[fib_info_hashfn(fi)]);	//链入路由信息结构队列
	if (fi->fib_prefsrc) {	//如果指定了IP地址,则同时链入路由地址队列
		struct hlist_head *head;

		head = &fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)];
		hlist_add_head(&fi->fib_lhash, head);
	}
	change_nexthops(fi) {	//循环取得路由信息中的每一个跳转结构
		struct hlist_head *head;
		unsigned int hash;

		if (!nh->nh_dev)	//检查跳转结构是否指定了跳转设备
			continue;
		hash = fib_devindex_hashfn(nh->nh_dev->ifindex);	//确定设备的哈希值
		head = &fib_info_devhash[hash];	//路由设备的哈希数组中找到头
		hlist_add_head(&nh->nh_hash, head);	//将跳转结构通过设备id链入到路由设备队列中
	} endfor_nexthops(fi)
	spin_unlock_bh(&fib_info_lock);
	return fi;

err_inval:
	err = -EINVAL;

failure:
	if (fi) {
		fi->fib_dead = 1;	//如果路由信息结构处于删除状态则释放
		free_fib_info(fi);
	}

	return ERR_PTR(err);
}

整体大致逻辑很清晰,我们看一下一些细节。跳转结构在本机范围内,dev_get_by_index可以看到是通过跳转结构的输出设备ID找到对应的网络设备的结构。

struct net_device *dev_get_by_index(struct net *net, int ifindex)
{
	struct net_device *dev;

	read_lock(&dev_base_lock);
	dev = __dev_get_by_index(net, ifindex);
	if (dev)
		dev_hold(dev);
	read_unlock(&dev_base_lock);
	return dev;
}

struct net_device *__dev_get_by_index(struct net *net, int ifindex)
{
	struct hlist_node *p;

	hlist_for_each(p, dev_index_hash(net, ifindex)) {
		struct net_device *dev
			= hlist_entry(p, struct net_device, index_hlist);
		if (dev->ifindex == ifindex)
			return dev;
	}
	return NULL;
}

先套了一层读锁,然后通过设备id在net结构中查找网络设备结构(相当于hashmap.get(id))

static inline struct hlist_head *dev_index_hash(struct net *net, int ifindex)
{
	return &net->dev_index_head[ifindex & ((1 << NETDEV_HASHBITS) - 1)];
}

看到了,net结构通过dev_index_head保存所有网络设备结构。

如果不是本机范围内,则遍历路由信息中的所有跳转结构,依次调用fib_check_nh函数来检查他们的合法性。

static int fib_check_nh(struct fib_config *cfg, struct fib_info *fi,
			struct fib_nh *nh)
{
	int err;
	struct net *net;

	net = cfg->fc_nlinfo.nl_net;
	if (nh->nh_gw) {	//如果跳转结构指定了网关
		struct fib_result res;

#ifdef CONFIG_IP_ROUTE_PERVASIVE
		if (nh->nh_flags&RTNH_F_PERVASIVE)	//不支持跳转、递归则返回
			return 0;
#endif
		if (nh->nh_flags&RTNH_F_ONLINK) {//不需要对跳转地址进行检测,一般用于路由通往虚拟设备
			struct net_device *dev;
			//其实从这段代码中我们也看到,通过快速返回来处理多if条件的良好习惯
			if (cfg->fc_scope >= RT_SCOPE_LINK)	//不在本地子网范围内
				return -EINVAL;
			if (inet_addr_type(net, nh->nh_gw) != RTN_UNICAST)	//属于单播
				return -EINVAL;
			if ((dev = __dev_get_by_index(net, nh->nh_oif)) == NULL)	//找到设备结构
				return -ENODEV;
			if (!(dev->flags&IFF_UP))	//设备开启
				return -ENETDOWN;
			nh->nh_dev = dev;	//设备结构复制给跳转结构
			dev_hold(dev);	//原子方式增加计数
			nh->nh_scope = RT_SCOPE_LINK;	//局域网内
			return 0;
		}    //else
		{	//需要进行网关地址检测
			struct flowi fl = {	//准备路由键值来查找网关的地址类型
				.nl_u = {
					.ip4_u = {
						.daddr = nh->nh_gw,
						.scope = cfg->fc_scope + 1,
					},
				},
				.oif = nh->nh_oif,
			};

			/* It is not necessary, but requires a bit of thinking */
			if (fl.fl4_scope < RT_SCOPE_LINK)
				fl.fl4_scope = RT_SCOPE_LINK; //局域网内
			if ((err = fib_lookup(net, &fl, &res)) != 0)
				return err;
		}
		err = -EINVAL;
		if (res.type != RTN_UNICAST && res.type != RTN_LOCAL) //即不是单播路由类型,而且不是本地转发的路由类型
			goto out;
		nh->nh_scope = res.scope;
		nh->nh_oif = FIB_RES_OIF(res);
		if ((nh->nh_dev = FIB_RES_DEV(res)) == NULL)
			goto out;
		dev_hold(nh->nh_dev);
		err = -ENETDOWN;
		if (!(nh->nh_dev->flags & IFF_UP))
			goto out;
		err = 0;
out:
		fib_res_put(&res);
		return err;
	} else {	//相反的,跳转结构中没有指定网关
		struct in_device *in_dev;

		if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
			return -EINVAL;

		in_dev = inetdev_by_index(net, nh->nh_oif);	//取得输入设备结构
		if (in_dev == NULL)
			return -ENODEV;
		if (!(in_dev->dev->flags&IFF_UP)) {	//设别是否工作状态
			in_dev_put(in_dev);
			return -ENETDOWN;
		}
		nh->nh_dev = in_dev->dev;	//记录到跳转结构中
		dev_hold(nh->nh_dev);	//增加网络设备的计数器
		nh->nh_scope = RT_SCOPE_HOST;	//本机范围
		in_dev_put(in_dev);
	}
	return 0;
}

几条线,1、指定网关且网关地址无需检测,则是不在本地子网范围内且属于单播,于是通过id在net结构中找到对应的网络设备结构,然后把范围调整为局域网内。2、指定网关但网关地址需要检测,然后构建路由查找键值(daddr = nh->nh_gw;scope = cfg->fc_scope + 1),查找路由表,并通过res配置查找结果。3、跳转结构中没有指定网关,则直接通过id查找in_device设备结构,然后把范围调整为本机。

第三条线中出现了in_device结构,net_device结构是Linux内核全部网络协议的公用结构体,其内部ip_ptr指针用于ipv4协议的专用设备结构in_device。

static inline struct in_device *__in_dev_get_rcu(const struct net_device *dev)
{
	struct in_device *in_dev = dev->ip_ptr;
	if (in_dev)
		in_dev = rcu_dereference(in_dev);
	return in_dev;
}

回到fib_create_info,如果指定了IP地址fib_prefsrc,则检查其类型不是本地转发类型,或者目标地址为空,或者设置的路由地址与目标地址不同,则调用inet_addr_type查找路由表重新取得地址类型,判断其是否是本地转发类型。

接下来调用fib_find_info检查是否存在相同的路由信息结构。

static struct fib_info *fib_find_info(const struct fib_info *nfi)
{
	struct hlist_head *head;
	struct hlist_node *node;
	struct fib_info *fi;
	unsigned int hash;

	hash = fib_info_hashfn(nfi);
	head = &fib_info_hash[hash];

	hlist_for_each_entry(fi, node, head, fib_hash) {
		if (fi->fib_net != nfi->fib_net)
			continue;
		if (fi->fib_nhs != nfi->fib_nhs)
			continue;
		if (nfi->fib_protocol == fi->fib_protocol &&
		    nfi->fib_prefsrc == fi->fib_prefsrc &&
		    nfi->fib_priority == fi->fib_priority &&
		    memcmp(nfi->fib_metrics, fi->fib_metrics,
			   sizeof(fi->fib_metrics)) == 0 &&
		    ((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 &&
		    (nfi->fib_nhs == 0 || nh_comp(fi, nfi) == 0))
			return fi;
	}

	return NULL;
}

逻辑很简单,我们重点看下两个路由信息结构相同的判断依据。同一个网络空间、跳转次数相同、相同的协议、相同的地址、优先级、fib_metrics负载值相同、相同的路由标志、跳转次数。如果有相同的则用老的路由信息结构。

void free_fib_info(struct fib_info *fi)
{
	if (fi->fib_dead == 0) {
		printk(KERN_WARNING "Freeing alive fib_info %p\n", fi);
		return;
	}
	change_nexthops(fi) {
		if (nh->nh_dev)
			dev_put(nh->nh_dev);
		nh->nh_dev = NULL;
	} endfor_nexthops(fi);
	fib_info_cnt--;
	release_net(fi->fib_net);
	kfree(fi);
}

释放我们可以看到,先是分跳转结构,然后释放路由信息结构。计数--。

回到fn_hash_insert中

	if (IS_ERR(fi))	//指针错误,指向内存最后一页
		return PTR_ERR(fi);

	if (fz->fz_nent > (fz->fz_divisor<<1) &&
	    fz->fz_divisor < FZ_MAX_DIVISOR &&
	    (cfg->fc_dst_len == 32 ||
	     (1 << cfg->fc_dst_len) > fz->fz_divisor))
		fn_rehash_zone(fz);	//检查路由数量和队列头数量的比例,是否需要调整路由区的队列头数量

	f = fib_find_node(fz, key);

	if (!f)
		fa = NULL;
	else
		fa = fib_find_alias(&f->fn_alias, tos, fi->fib_priority);

路由区中的fib_node节点数大于哈希散列头数量fz_divisor的两倍且,fz_divisor小于配置的最大值,则有必要进行路由区的队列头数量扩容。

static void fn_rehash_zone(struct fn_zone *fz)
{
	struct hlist_head *ht, *old_ht;
	int old_divisor, new_divisor;
	u32 new_hashmask;

	old_divisor = fz->fz_divisor;

	switch (old_divisor) {	//根据已有的队列头数量确定一个新值
	case 16:
		new_divisor = 256;
		break;
	case 256:
		new_divisor = 1024;
		break;
	default:
		if ((old_divisor << 1) > FZ_MAX_DIVISOR) {
			printk(KERN_CRIT "route.c: bad divisor %d!\n", old_divisor);
			return;
		}
		new_divisor = (old_divisor << 1);	//默认情况下扩大一倍
		break;
	}

	new_hashmask = (new_divisor - 1);	//新的队列哈希掩码

#if RT_CACHE_DEBUG >= 2
	printk(KERN_DEBUG "fn_rehash_zone: hash for zone %d grows from %d\n",
	       fz->fz_order, old_divisor);
#endif

	ht = fz_hash_alloc(new_divisor);	//申请新的空间

	if (ht)	{
		write_lock_bh(&fib_hash_lock);	//写锁
		old_ht = fz->fz_hash;
		fz->fz_hash = ht;
		fz->fz_hashmask = new_hashmask;
		fz->fz_divisor = new_divisor;
		fn_rebuild_zone(fz, old_ht, old_divisor);
		fib_hash_genid++;
		write_unlock_bh(&fib_hash_lock);

		fz_hash_free(old_ht, old_divisor);	//释放原来的队列头空间
	}
}

还是类似hashmap。。。为什么内核不搞个hashmap结构呢??我们看其节点转移流程,fn_rebuild_zone

/* The fib hash lock must be held when this is called. */
static inline void fn_rebuild_zone(struct fn_zone *fz,
				   struct hlist_head *old_ht,
				   int old_divisor)
{
	int i;

	for (i = 0; i < old_divisor; i++) {
		struct hlist_node *node, *n;
		struct fib_node *f;

		hlist_for_each_entry_safe(f, node, n, &old_ht[i], fn_hash) {
			struct hlist_head *new_head;

			hlist_del(&f->fn_hash);

			new_head = &fz->fz_hash[fn_hash(f->fn_key, fz)];
			hlist_add_head(&f->fn_hash, new_head);
		}
	}
}

转移这一块整个是加锁的,但其实思考concurrenthashmap,采取分段锁亦或者分段+cas,可能会优化不少。

再回到fn_hash_insert中,之后调用了fib_find_node函数,在路由区结构中根据子网key值找到对应的路由节点结构。

/* Return the node in FZ matching KEY. */
static struct fib_node *fib_find_node(struct fn_zone *fz, __be32 key)
{
	struct hlist_head *head = &fz->fz_hash[fn_hash(key, fz)];
	struct hlist_node *node;
	struct fib_node *f;

	hlist_for_each_entry(f, node, head, fn_hash) {
		if (f->fn_key == key)
			return f;
	}

	return NULL;
}

找到路由节点后,通过fib_find_alias函数在它的路由别名队列f->fn_alias队列中查找符合服务类型TOS跟优先级的路由别名结构。

/* Return the first fib alias matching TOS with
 * priority less than or equal to PRIO.
 */
struct fib_alias *fib_find_alias(struct list_head *fah, u8 tos, u32 prio)
{
	if (fah) {
		struct fib_alias *fa;
		list_for_each_entry(fa, fah, fa_list) {
			if (fa->fa_tos > tos)
				continue;
			if (fa->fa_info->fib_priority >= prio ||
			    fa->fa_tos < tos)
				return fa;
		}
	}
	return NULL;
}

此时f指向对应的路由节点结构。fa指向路由别名队列中的第一个符合条件(fa_tos <= tos &&(fa_tos < tos || priority >= prio))的结构。

	if (fa && fa->fa_tos == tos &&
	    fa->fa_info->fib_priority == fi->fib_priority) {	//检查路由别名中第一个结构
		struct fib_alias *fa_first, *fa_match;

		err = -EEXIST;
		if (cfg->fc_nlflags & NLM_F_EXCL) //如果条目已经存在,将失败
			goto out;

		/* We have 2 goals:
		 * 1. Find exact match for type, scope, fib_info to avoid
		 * duplicate routes
		 * 2. Find next 'fa' (or head), NLM_F_APPEND inserts before it
		 */
		fa_match = NULL;
		fa_first = fa;	//记录"模糊查找"的第一个fa结构
		fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list);
		list_for_each_entry_continue(fa, &f->fn_alias, fa_list) {//沿着队列仔细查找最符合的路由别名结构
			if (fa->fa_tos != tos)
				break;
			if (fa->fa_info->fib_priority != fi->fib_priority)
				break;
			if (fa->fa_type == cfg->fc_type &&
			    fa->fa_scope == cfg->fc_scope &&
			    fa->fa_info == fi) {
				fa_match = fa;	//找到后记录它
				break;
			}
		}

		if (cfg->fc_nlflags & NLM_F_REPLACE) {	//是否允许被修改替换
			struct fib_info *fi_drop;
			u8 state;

			fa = fa_first;
			if (fa_match) {	//如果前面找到了匹配的则不用修改
				if (fa == fa_match)
					err = 0;
				goto out;
			}
			write_lock_bh(&fib_hash_lock);
			fi_drop = fa->fa_info;
			fa->fa_info = fi;
			fa->fa_type = cfg->fc_type;
			fa->fa_scope = cfg->fc_scope;
			state = fa->fa_state;
			fa->fa_state &= ~FA_S_ACCESSED;
			fib_hash_genid++;
			write_unlock_bh(&fib_hash_lock);

			fib_release_info(fi_drop);	//递减旧的路由信息结构的使用计数
			if (state & FA_S_ACCESSED)	//是否已经使用过
				rt_cache_flush(-1);	//冲刷缓存中的路由表
			rtmsg_fib(RTM_NEWROUTE, key, fa, cfg->fc_dst_len, tb->tb_id, //通过netlink向IPROUT2回复
				  &cfg->fc_nlinfo, NLM_F_REPLACE);
			return 0;
		}

		/* Error if we find a perfect match which
		 * uses the same scope, type, and nexthop
		 * information.
		 */
		if (fa_match)	//直接返回匹配的路由别名结构
			goto out;

		if (!(cfg->fc_nlflags & NLM_F_APPEND))	//不允许追加到队列就使用"模糊"的结构
			fa = fa_first;
	}

	err = -ENOENT;
	if (!(cfg->fc_nlflags & NLM_F_CREATE))	//不允许创建则退出
		goto out;

检查其服务类型值tos是否与配置的相同,检查优先级是否相同。这时候fa指向路由别名队列的第一个结构,沿着fa_list队列循环查找第一个符合所有条件的路由别名结构(tos、优先级、范围、类型、fa_info是否指向当前路由信息结构)。找到则记录在fa_match上,匹配的路由别名结构。

如果配置结构fc_nlflags标志中设置了替换标志NLM_F_REPLACE,则重新设置找到的路由别名结构。设置之前使用fib_drop记录下之前的路由信息结构,然后把路由别名的路由信息结构指针指向当前的路由信息结构,并且修改条件。当然如果前面找到了匹配的则不用修改,直接使用。

如果配置的结构fc_nlflags标志中未设置NLM_F_APPEND,则不允许追加到队列就使用"模糊"的结构。

	err = -ENOBUFS;

	if (!f) {	//如果没有已经找到的子网的路由节点结构则创建新的路由节点
		new_f = kmem_cache_zalloc(fn_hash_kmem, GFP_KERNEL);
		if (new_f == NULL)
			goto out;

		INIT_HLIST_NODE(&new_f->fn_hash);	//初始化哈希队列头
		INIT_LIST_HEAD(&new_f->fn_alias);
		new_f->fn_key = key;	//记录子网键值
		f = new_f;
	}

	new_fa = &f->fn_embedded_alias;	//指向嵌入在节点内部的别名结构
	if (new_fa->fa_info != NULL) {	//如果这个结构已经使用了,就创建一个新的
		new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
		if (new_fa == NULL)
			goto out;
	}
	new_fa->fa_info = fi;	//路由信息
	new_fa->fa_tos = tos;	//TOS
	new_fa->fa_type = cfg->fc_type;	//路由类型
	new_fa->fa_scope = cfg->fc_scope;	//路由范围
	new_fa->fa_state = 0;	//未被访问

	/*
	 * Insert new entry to the list.
	 */

	write_lock_bh(&fib_hash_lock);
	if (new_f)
		fib_insert_node(fz, new_f);	//将新建的节点链入路由区的队列中
	list_add_tail(&new_fa->fa_list,
		 (fa ? &fa->fa_list : &f->fn_alias));	//将新建的别名结构链入节点的队列中
	fib_hash_genid++;
	write_unlock_bh(&fib_hash_lock);

	if (new_f)
		fz->fz_nent++;	//增加路由区包含的节点数
	rt_cache_flush(-1);	//冲刷路由表缓存

	rtmsg_fib(RTM_NEWROUTE, key, new_fa, cfg->fc_dst_len, tb->tb_id,
		  &cfg->fc_nlinfo, 0);
	return 0;

out:
	if (new_f)
		kmem_cache_free(fn_hash_kmem, new_f);
	fib_release_info(fi);
	return err;
}

至此,fn_hash_insert函数,按照cfg在内存中建立了路由需要的环境。

你可能感兴趣的:(源码学习,Linux,内核,tcpip)