所谓邻居,是指在同一个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();
}
}