邻居子系统学习

所谓邻居,是指在同一个IP局域网内的主机,或者邻居之间在三层上仅相隔一跳的距离。而邻居子系统,则提供了三层协议地址与二层协议地址之间的映射关系,此外还提供二层首部缓存,以加速发送数据包。

在发送数据的时候,先进行路由查找,如果找到目的地址的路径,再查看邻居表中是否存在相应的映射关系,如果没有则新建邻居项;然后判断邻居项是否为可用状态,如不可用则把数据报存至发送缓存队列后发送请求;在接收到请求应答后,将对应邻居项置为可用,并将其缓存队列中的数据包发送出去;如果在指定时间内未收到相应包,则将对应邻居项置为无效状态。

引用LINUX邻居子系统(一)解释:

个人感觉也没那么复杂:大致意思就是L3的是逻辑地址,L2的是物理地址,需要做的就是实现这两个地址的映射。

举个简单的例子:比如我要给寄出一个包裹到国外,对方的地址就是一个逻辑地址,但是我不可能直接就交到对方手上,我需要做的第一件事情就是看以下附近有没有什么邮局,邮局就是我的一个“邻居关系”,而且它是可以帮助我把包裹送到国外的唯一渠道,那我要做的就是查到邮局的地址,然后把这个包裹送到邮局,让它帮忙。做如下等价:

收件人地址=逻辑地址

邮局地址=物理地址

两者同时具备,这个包裹就可以正确送达了。

注1:其实这就是ARP协议做的功能

注2:把它做成邻居子系统的原因就是,网络不可能只有IPV4,其他的协议也需要地址解析,如果为每个单独开发,有很多重复劳动,做一个通用结构就可以减少这些劳动了

邻居子系统的实现涉及以下文件:

include/linux/inetdevice.h   定义IPv4专用的网络设备相关的结构、宏等

include/net/neighbour.h  定义邻居项等结构、宏和函数原型

net/core/neighbour.c  邻居子系统实现

邻居子系统结构

neigh_table结构用来存储与邻居协议相关的参数、功能函数,以及邻居项散列表,一个neigh_table结构实例对应一个邻居协议,所有的实例都链接在全局链表neigh_tables中,对应ARP协议,其neigh_table结构实例是arp_tbl。

/*
 *	neighbour table manipulation
 */

struct neigh_table
{
	struct neigh_table	*next;
	int			family;
	int			entry_size;
	int			key_len;
	__u32			(*hash)(const void *pkey, const struct net_device *);
	int			(*constructor)(struct neighbour *);
	int			(*pconstructor)(struct pneigh_entry *);
	void			(*pdestructor)(struct pneigh_entry *);
	void			(*proxy_redo)(struct sk_buff *skb);
	char			*id;
	struct neigh_parms	parms;
	/* HACK. gc_* shoul follow parms without a gap! */
	int			gc_interval;
	int			gc_thresh1;
	int			gc_thresh2;
	int			gc_thresh3;
	unsigned long		last_flush;
	struct delayed_work	gc_work;
	struct timer_list 	proxy_timer;
	struct sk_buff_head	proxy_queue;
	atomic_t		entries;
	rwlock_t		lock;
	unsigned long		last_rand;
	struct kmem_cache		*kmem_cachep;
	struct neigh_statistics	*stats;
	struct neighbour	**hash_buckets;
	unsigned int		hash_mask;
	__u32			hash_rnd;
	struct pneigh_entry	**phash_buckets;
};

struct neigh_table *next

用来连接到neigh_tables中,该链表中除了ARP的arp_tbl,还有IPv6和DECnet所使用邻居协议的邻居表实例nd_tbl和dn_neigh_table等

int family

邻居协议所属的地址族,ARP为AF_INET

__u32 (*hash)(const void *pkey, const struct net_device *)

哈希函数,用来计算哈希值,ARP中为arp_hash()

int (*constructor)(struct neighbour *)

邻居表项初始化函数,用于初始化一个新的neighbour结构实例中与协议相关的字段。在ARP中,该函数为arp_constructor(),由邻居表项创建函数neigh_create()中调用。

char *id

用来分配neighbour结构实例的缓存池名字字符串,arp_tlb的该字段为"arp_cache"

struct neigh_parms parms

存储一些与协议相关的可调节参数。如重传超时时间,proxy_queue队列长度等

atomic_t entries

整个表中邻居项的数目

struct kmem_cache *kmem_cachep

用来分配neighbour结构实例的slab缓存

struct neighbour **hash_buckets

用于存储邻居项的散列表,该散列表在分配邻居项时,如果邻居项数超出散列表容量,可动态扩容

struct pneigh_entry **phash_buckets

存储ARP代理三层协议地址的散列表,在neigh_table_init_no_netlink()中初始化

neighbour结构

邻居项使用neighbour结构来描述,该结构存储了邻居的相关信息,包括状态、二层和三层协议地址、提供给三层协议的函数指针,还有定时器和缓存的二层首部等。需要注意的是,一个邻居并不代表一个主机,而是一个三层协议地址,对于配置了多接口的主机,一个主机将对应多个三层协议地址

struct neighbour
{
	struct neighbour	*next;
	struct neigh_table	*tbl;
	struct neigh_parms	*parms;
	struct net_device		*dev;
	unsigned long		used;
	unsigned long		confirmed;
	unsigned long		updated;
	__u8			flags;
	__u8			nud_state;
	__u8			type;
	__u8			dead;
	atomic_t		probes;
	rwlock_t		lock;
	unsigned char		ha[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))];
	struct hh_cache		*hh;
	atomic_t		refcnt;
	int			(*output)(struct sk_buff *skb);
	struct sk_buff_head	arp_queue;
	struct timer_list	timer;
	const struct neigh_ops	*ops;
	u8			primary_key[0];
};

struct neighbour *next

通过next把邻居项插入到散列表桶链表上,总在桶的前部插入新的邻居项

struct neigh_table *tbl

指向相关协议的neigh_table结构实例,即该邻居项所在的邻居表。如果该邻居项对应的是一个IPv4地址,则该字段指向arp_tbl。

struct net_device *dev

通过此网络设备可访问到该邻居。对每个邻居来说,只能有一个可用来访问该邻居的网络设备

__u8 nud_state

标识邻居项的状态

struct hh_cache*hh

指向缓存的二层协议首部hh_cache结构实例链表

int (*output)(struct sk_buff *skb)

输出函数,用来将报文输出到该邻居。在邻居项的整个生命周期中,由于其状态是不断变化的,从而导致该函数指针会指向不同的输出函数。例如,当该邻居可达时会调用neigh_connect()将output设置为neigh_ops->connected_output,

const struct neigh_ops*ops

指向邻居项函数指针表实例。每一种邻居协议都提供3到4种不同的邻居项函数指针表

neigh_ops结构

邻居项函数指针表由在邻居的生存周期中不同时期被调用的多个函数指针组成。其中有多个函数指针式实现三层(IPv4中的IP层)与dev_queue_xmit()之间的调用桥梁,适用于不同的状态。

struct neigh_ops
{
	int			family;
	void			(*solicit)(struct neighbour *, struct sk_buff*);
	void			(*error_report)(struct neighbour *, struct sk_buff*);
	int			(*output)(struct sk_buff*);
	int			(*connected_output)(struct sk_buff*);
	int			(*hh_output)(struct sk_buff*);
	int			(*queue_xmit)(struct sk_buff*);
};

void (*solicit)(struct neighbour *, struct sk_buff*)

发送请求报文函数。发送第一个报文时,需要新的邻居项,发送报文被缓存arp_queue队列中,然后会调用solicit()发送请求报文

void (*error_report)(struct neighbour *, struct sk_buff*)

当邻居项缓存着未发送的报文,而该邻居项又不可达时,被调用来向三层报文错误的函数

int (*output)(struct sk_buff*)

最通用的输出函数,可用于所有情况。此输出函数实现了完整的输出过程,因此存在较多的校验与操作,以确保报文输出,因此该函数相对较消耗资源

int (*queue_xmit)(struct sk_buff*)

实际上,以上几个输出接口,除了hh_output外,并不真正传输数据包,只是在准备好二层首部之后,调用queue_xmit接口

邻居表的初始化

邻居表由neigh_table_init()初始化。对arp_tbl的初始化,在ARP模块初始化由arp_init()调用。实际上,邻居表的初始化工作大部分是由neigh_table_init_no_netlink()完成的

void neigh_table_init_no_netlink(struct neigh_table *tbl)
{
	unsigned long now = jiffies;
	unsigned long phsize;

	write_pnet(&tbl->parms.net, &init_net);
	atomic_set(&tbl->parms.refcnt, 1);
	tbl->parms.reachable_time =
			  neigh_rand_reach_time(tbl->parms.base_reachable_time);

	if (!tbl->kmem_cachep)
		tbl->kmem_cachep =
			kmem_cache_create(tbl->id, tbl->entry_size, 0,
					  SLAB_HWCACHE_ALIGN|SLAB_PANIC,
					  NULL);
	tbl->stats = alloc_percpu(struct neigh_statistics);
	if (!tbl->stats)
		panic("cannot create neighbour cache statistics");

#ifdef CONFIG_PROC_FS
	if (!proc_create_data(tbl->id, 0, init_net.proc_net_stat,
			      &neigh_stat_seq_fops, tbl))
		panic("cannot create neighbour proc dir entry");
#endif

	tbl->hash_mask = 1;
	tbl->hash_buckets = neigh_hash_alloc(tbl->hash_mask + 1);

	phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);
	tbl->phash_buckets = kzalloc(phsize, GFP_KERNEL);

	if (!tbl->hash_buckets || !tbl->phash_buckets)
		panic("cannot allocate neighbour cache hashes");

	get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));

	rwlock_init(&tbl->lock);
	INIT_DELAYED_WORK_DEFERRABLE(&tbl->gc_work, neigh_periodic_work);
	schedule_delayed_work(&tbl->gc_work, tbl->parms.reachable_time);
	setup_timer(&tbl->proxy_timer, neigh_proxy_process, (unsigned long)tbl);
	skb_queue_head_init_class(&tbl->proxy_queue,
			&neigh_table_proxy_queue_class);

	tbl->last_flush = now;
	tbl->last_rand	= now + tbl->parms.reachable_time * 20;
}

void neigh_table_init(struct neigh_table *tbl)
{
	struct neigh_table *tmp;

	neigh_table_init_no_netlink(tbl);
	write_lock(&neigh_tbl_lock);
	for (tmp = neigh_tables; tmp; tmp = tmp->next) {
		if (tmp->family == tbl->family)
			break;
	}
	tbl->next	= neigh_tables;
	neigh_tables	= tbl;
	write_unlock(&neigh_tbl_lock);

	if (unlikely(tmp)) {
		printk(KERN_ERR "NEIGH: Registering multiple tables for "
		       "family %d\n", tbl->family);
		dump_stack();
	}
}
时间: 2024-08-08 13:52:54

邻居子系统学习的相关文章

Linux邻居子系统的细节之confirm-OpenVPN server模式的MAC地址学习

在<Linux实现的ARP缓存老化时间原理解析>一文中,我剖析了Linux协议栈IPv4的邻居子系统的转化,再次贴出那个状态机转化图,可是这个图更详细了些,因为它有一个外部输入,那就是confirm: 请注意,如果socket或者路由子系统在上层confirm了一个neighbour,那么该arp将持续留在reachable状态而可以不用转换到stale状态.这个特性是有意义的.请观察一个现象:1.本机IP地址为192.168.1.10/24,直连的机器IP地址为192.168.1.20/24

基于tiny4412的Linux内核移植 -- PWM子系统学习(七)

作者信息 作者: 彭东林 邮箱:[email protected] QQ:405728433 平台简介 开发板:tiny4412ADK + S700 + 4GB Flash 要移植的内核版本:Linux-4.4.0 (支持device tree) u-boot版本:友善之臂自带的 U-Boot 2010.12 (为支持uImage启动,做了少许改动) busybox版本:busybox 1.25 交叉编译工具链: arm-none-linux-gnueabi-gcc (gcc version 4

S3C2440 输入子系统学习笔记 第一节

接触S3C2440已经有一段时间了,可是总是没有去坚持学习,刚毕业的我深受到自身技能的缺乏和工作中的压力,决定痛改前非,坚持每天下班都去学习,在这里我不敢说自己能把2440学完,因为技术永无止境.但是我相信我能一直坚持下去.我是一个热爱思考,并且将思考的东西通过各种方法实现,我要做我思想的造物主.博客从今天开始每隔一天将会有新的博客,前期会根据韦东山老师的视频教程目录来更新,如果有写得不好的地方请大家指点指点. 好了废话不多说,进入正题. 本博客的起点是韦东山老师的第2期的学习视频. 在第一期视

input子系统学习之四:核心层

核心层:给事件层和设备层提供接口,规范编程接口. 一.  输入子系统核心分析. 1.输入子系统核心对应与/drivers/input/input.c文件,这个也是作为一个模块注册到内核的.所以首先分析模块初始化函数. 1 .cnblogs 2. 输入子系统的核心其他部分都是提供的接口,向上连接事件处理层,向下连接驱动层.    向下对驱动层的接口主要有:    input_allocate_device    这个函数主要是分配一个input_dev接口,并初始化一些基本的成员,这就是我们不能简

SElinux安全子系统---学习

SElinux是一个强制访问控制的安全子系统,是为了让各个服务进程都受到约束,只能获取到属于自己的资源 SElinux有三种配置模式: 1:enforcing--强制启动安全配置策略,拦截不合法的请求. 2:permissive--遇到服务越权访问是=时,只发出报警而不去拦截. 3:disabled--对于越权的行为不报警也不拦截. SElinux主配置文件--- /etc/selinux/config  其中定义的是selinux的默认运行状态,也就是说他是系统重启后的运行状态,不行修改后就立

基于tiny4412的Linux内核移植 -- PWM子系统学习(八)

作者信息 作者: 彭东林 邮箱:[email protected] QQ:405728433 平台简介 开发板:tiny4412ADK + S700 + 4GB Flash 要移植的内核版本:Linux-4.4.0 (支持device tree) u-boot版本:友善之臂自带的 U-Boot 2010.12 (为支持uImage启动,做了少许改动) busybox版本:busybox 1.25 交叉编译工具链: arm-none-linux-gnueabi-gcc (gcc version 4

linux内核I2C子系统学习(三)

写设备驱动: 四部曲: 构建i2c_driver 注册i2c_driver 构建i2c_client ( 第一种方法:注册字符设备驱动.第二种方法:通过板文件的i2c_board_info填充,然后注册) 注销i2c_driver 具体如下: ●    构建i2c_driver static struct i2c_driver pca953x_driver = {                 .driver = {                                     .n

Linux input子系统学习总结(二)----Input事件驱动

Input 事件驱动:  (主要文件 :drivers/input/evdev.c  .  drivers/input/input.h)基于kernel 4.0  一. 关键函数调用顺序: 1.input_register_handler(&evdev_handler); ///注册 evdev_handler 这个input事件驱evdev.c      2.input_attach_handler(dev, handler);////input 设备和 input 事件进行匹配   inpu

输入子系统学习笔记

1.声明input_dev结构体 static struct input_dev *buttons_dev; 2.init函数中分配input_dev结构体并对其设置.注册 /* 1. 分配一个input_dev结构体 */ buttons_dev = input_allocate_device();; /* 2. 设置 */ /* 2.1 能产生哪类事件 */ set_bit(EV_KEY, buttons_dev->evbit); set_bit(EV_REP, buttons_dev->