邻居协议学习

Linux 邻居协议学习
Linux 邻居协议学习之  一  邻居协议概念及 arp 帧格式
什么是邻居协议?
如果一台主机和你的计算机连接在同一个 LAN 上(也就是说,你和这台主机通过一个共享
介质相连或点对点直接相连),那么它就是你的邻居,而且他们有相同的 L3 网络配置。定
义邻居的另一种方式是,一台主机到其邻居必须有且仅有一个 L3 跳跃点,并且它的 L3 路
由表必须提供可以直接和其邻居通信的一条路劲。不是邻居的主机间通信必须通过网关或路
由器。
neighbour 项是在什么时候创建的呢?
这需要从两个方向来分析,发送与接收:
1、对于发送方向来说,当路由器需要转发或者需要自己发送一个数据包时,会去查找路由
表当查找到的路由没有在路由缓存中时,则需要为该路由建立一个路由缓存并加入到路由缓
存链表中,同时会调用 arp_bind_neighbour 实现路由缓存与 neighbour 的绑定(如果 neighbour
项,则创建 neighbour 项)。然后再判断 neighbour 项是否可用,若不可用,则将数据包存入
队列中,并发送 arp  请求,在接收到请求后,则将 neighbour 项设置为可用,并将数据从队
列中取出并发送出去其邻居项的状态转换为 NUD_NONE  ->  NUD_INCOMPLETE  -> 
NUD_REACHABLE。
2、 对于接收方向来说,当主机接收到 arp request 报文,则认为主机与发送请求报文之间的
链路为通的,则为该发送主机创建一个邻居表项,并将其状态设置为 NUD_STATE,其邻居
项的状态转换为 NUD_NONE  ->  NUD_STALE  ->  NUD_DELAY  ->  NUD_PROBE  -> 
NUD_REACHABLE
Arp 协议帧格式
对于操作码,用于表示数据包的类型,ARP 请求为 1,ARP 响应为 2,RARP 请求为 3,
RARP 响应为 4。目前我们只需要 arp 请求与 arp 应答 
Linux 邻居协议学习之二  通用邻居处理函数对应的数据结构的分析
在 linux 代码中,对于不同的邻居项,抽象出了一个通用的模型,通用邻居层,主要是
用来进行邻居项的创建、添加、删除、查找、更新等操作。
对于通用邻居层,最主要的就是邻居项的状态机的设计,本部分先介绍相应的数据结构,
在分析通用邻居处理函数时,会仔细分析邻居状态机。本部分会简要介绍状态机。
首先是邻居状态的定义,在通用邻居项中定义了以下邻居状态:
#define NUD_INCOMPLETE  0x01
#define NUD_REACHABLE  0x02
#define NUD_STALE  0x04
#define NUD_DELAY  0x08
#define NUD_PROBE  0x10
#define NUD_FAILED  0x20
/* Dummy states */
#define NUD_NOARP  0x40
#define NUD_PERMANENT  0x80
#define NUD_NONE  0x00
其中 NUD_NOARP、 NUD_PERMANENT、 NUD_NONE 表示该邻居项不需要邻居地址的解
析。
下面我们介绍 NUD_IN_TIMER、NUD_VALID、NUD_CONNECTED
对于 NUD_IN_TIMER,通过名称我们就知道,当邻居项处于该状态时,则会启动定时器。
下面我们一一分析这几个邻居项状态, 通过分析完这几个状态,我们就基本上会理解邻居项
状态机中定时器处理函数 neigh_timer_handler 的设计逻辑了。
1、对于 NUD_INCOMPLETE,当本机发送完 arp  请求包后,还未收到应答时,即会进入该
状态。进入该状态,即会启动定时器,如果在定时器到期后,还没有收到应答时:如果没有
到达最大发包上限时,即会重新进行发送请求报文;如果超过最大发包上限还没有收到应答,
则会将状态设置为 failed
2、对于收到可到达性确认后,即会进入 NUD_REACHABLE,当进入 NUD_REACHABLE
状态。当进入 NUD_REACHABLE 后,即会启动一个定时器,当定时器到时前,该邻居协
议没有
被使用过,就会将邻居项的状态转换为 NUD_STALE
3、对于进入 NUD_STALE 状态的邻居项,即会启动一个定时器。如果在定时器到时前,有
数据需要发送,则直接将数据包发送出去,并将状态设置为 NUD_DELAY;如果在定时器到
时,没有数据需要发送,且该邻居项的引用计数为 1,则会通过垃圾回收机制,释放该邻居
项对应的缓存
4、处于 NUD_DELAY 状态的邻居项,如果在定时器到时后,没有收到可到达性确认,则会
进入 NUD_PROBE 状态;如果在定时器到达之前,收到可到达性确认,则会进入
NUD_REACHABLE (在该状态下的邻居项不会发送 solicit 请求,而只是等待可到达性应答。
主要包括对以前的 solicit 请求的应答或者收到一个对于本设备以前发送的一个数据包的应
答)
5、处于 NUD_PROBE 状态的邻居项,会发送 arp  solicit 请求,并启动一个定时器。如果在
定时器到时前,收到可到达性确认,则进入 NUD_REACHABLE;如果在定时器到时后,没
有收到可到达性确认:
a)没有超过最大发包次数时,则继续发送 solicit 请求,并启动定时器
b)如果超过最大发包次数,则将邻居项状态设置为 failed
在上面 5 个状态中,在 NUD_REACHABLE、NUD_PROBE、NUD_STALE、NUD_DELAY
状态时,数据包是可以正常发送的,只是发送的函数不同。这样就不难理解 NUD_VALID
包含 NUD_PERMANENT 、 NUD_NOARP 、 NUD_REACHABLE 、 NUD_PROBE 、
NUD_STALE、NUD_DELAY 了
对于#define NUD_CONNECTED  (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)
主要是表示邻居是可达的状态,对于 NUD_PERMANENT、NUD_NOARP 状态的邻居项,
其邻居状态是不会改变的,一直是有效的,除非删除该邻居项。对于 NUD_REACHABLE
我们在上面已经介绍过了。
下面介绍 neigh_parms、neighbour、neigh_ops、neigh_table
struct neigh_parms {
#ifdef CONFIG_NET_NS
struct net *net;
#endif
struct net_device *dev;
struct neigh_parms *next;
int  (*neigh_setup)(struct neighbour *);
void  (*neigh_cleanup)(struct neighbour *);
struct neigh_table *tbl;
void  *sysctl_table;
int dead;
atomic_t refcnt;
struct rcu_head rcu_head;
int  base_reachable_time;//基本有效时间  ,对于 arp 默认为 30s
int  retrans_time;//solicit 请求报文重发间隔时间
int  gc_staletime;//闲置时间
int  reachable_time;//确认有效时间超时长度,这个值每隔 300s 会更新一次
int  delay_probe_time;//在 nud_delay 时,为 delay 的超时时间;在 nud_reach 状态时,用
于判断是否需要进入 delay 状态的一个时间判断点
int  queue_len;//缓存数据包的队列长度
int  ucast_probes;//发送单播 solicit 请求的最大次数
int  app_probes;// ?
int  mcast_probes;//发送广播 solicit 请求的最大次数
int  anycast_delay;
int  proxy_delay;
int  proxy_qlen;
int  locktime;
};
struct neighbour {
struct neighbour  *next;
struct neigh_table  *tbl;//指向该邻居项所属的邻居表
struct neigh_parms  *parms;
struct net_device    *dev;
unsigned long    used;//邻居项使用时间
unsigned long    confirmed;//connected 状态确认时间
unsigned long    updated;//邻居项更新时间
__u8      flags;
__u8      nud_state;//邻居项状态值
__u8      type;//邻居项地址的类型
__u8      dead;
atomic_t    probes;//记录邻居项发送的 solicit 请求的次数
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;//数据包缓存队列,当有数据包要发送,但又没有目的 ip
地址对应的目的 mac 时,则会将数据包缓存在该队列中
struct timer_list  timer;
const struct neigh_ops  *ops;  //neighbour 项的函数指针表,包含发送 solicit 请求函数,
以及不同状态下对应的输出函数
u8      primary_key[0];
};
struct neigh_ops {
int      family;//所属的地址簇,对于 arp,则为 AF_INET
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*);//真正的数据传输函数,
};
struct neigh_table { 
struct neigh_table  *next; //指向下一个邻居协议对应的邻居表
int      family;//该邻居协议对应的地址簇
int      entry_size;//该邻接表所能包含的邻居项的最大值
int      key_len;//关键字的大小,对于 arp 协议,是 4 个字节大小
__u32      (*hash)(const void *pkey, const struct net_device *);//hash 函数
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;//如果邻居项的数目超过这个值,则在新建邻居项时,若超过 5
秒未进行刷新,则刷新并强制垃圾回收
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;//记录 neigh_parms 中 reach_time 成员的最近更新时间
struct kmem_cache   *kmem_cachep;//用来分配邻居项的 slab 缓存
struct neigh_statistics  __percpu *stats;
struct neighbour  **hash_buckets;//存储邻居项的 hash bucket
unsigned int    hash_mask;
__u32      hash_rnd;//hash  散列表扩容时的关键字
struct pneigh_entry **phash_buckets;
};
至此,将通用邻居项相关的主要数据结构即介绍完了。
Linux  邻居协议学习  之  三  通用邻居项的垃圾回收机制
对于通用邻居层,我认为主要可以分为三个方面:
1、邻居项处理函数,包括邻居项创建、更新、删除等
2、邻居项的状态机机制,主要是处理邻居项中状态的改变,其中包括几个邻居状态的定时
器机制,也包括发送 solicit 请求等 
3、邻居项的垃圾回收机制,主要是负责回收一个邻居表里长时间不用的邻居项,已节省邻
居缓存空间。 这三个方面需要相互协调工作,才能完成通用邻居层的功能, 这一节就分析邻
居项的垃圾回收机制。
对于垃圾回收,主要分为 2 个方面:同步垃圾回收和异步垃圾回收。
异步垃圾回收就是周期的进行垃圾回收,这个是周期性的。而同步垃圾回收机制主要是在创
建邻居项而又没有缓存空间时, 会调用同步垃圾回收,强制回收无效的邻居缓存。下面介绍
同步垃圾回收处理函数 neigh_forced_gc。
这个函数的逻辑流程还是比较简单的。
该函数的功能是遍历邻接表中 hash 散列数组中的每一个 hash 表项中的所有邻接表,
对于邻居项的引用计数为 1 且状态不是 NUD_PERMANENT 时,则设置 neigh->dead 为 1,
并调用 neigh_cleanup_and_release 释放
该邻居项占用的缓存。
static int neigh_forced_gc(struct neigh_table *tbl)
{
int shrunk = 0;
int i;
NEIGH_CACHE_STAT_INC(tbl, forced_gc_runs);
write_lock_bh(&tbl->lock);
for (i = 0; i <= tbl->hash_mask; i++) {
struct neighbour *n, **np;
np = &tbl->hash_buckets[i];
while ((n = *np) != NULL) {
/* Neighbour record may be discarded if:
* - nobody refers to it.
* - it is not permanent
*/
write_lock(&n->lock);
if (atomic_read(&n->refcnt) == 1 &&
!(n->nud_state & NUD_PERMANENT)) {
*np  = n->next;
n->dead = 1;
shrunk  = 1;
write_unlock(&n->lock);
neigh_cleanup_and_release(n);
continue;
}
write_unlock(&n->lock);
np = &n->next;
}
}
tbl->last_flush = jiffies;
write_unlock_bh(&tbl->lock);
return shrunk;
}
对于异步垃圾回收,在 2.6.34 里,对于异步垃圾回收的函数实现机制上,进行了调整,
在 2.6.21 里,是直接使用定时器来实现异步清理的,而在 2.6.34 里,则是
使用带有定时器功能的工作队列来实现邻居项的内存异步清理的。
在 2.6.1 里,对于异步清理函数,每次定时器到期后,即会扫描邻居表项
中的邻居项 hash 数组中的一个 hash 表中的所有邻居表项,对于符合删除条件
的邻居项,则会调用函数 neigh_release,释放该邻居项。
符合删除的条件为:
1、neigh->dead == 1
2、neigh->state == NUD_FAILED 或者闲置时间超过了指定上限 gc_staletime
而在 2.6.34 里,异步清理函数 neigh_periodic_work 每次清理时,会清理邻居表里的邻居
hash 数组里所有 hash 表里的所有邻居项,而不是仅搜索一个 hash 表里的所有邻居项。
下面介绍同步清理的实际处理函数 neigh_periodic_work
1、每隔 5 分钟,重置一次 reachable_time 的值,并更新邻居表的 last_rand,以便于下次执行
更新 reach_time 操作
2、遍历邻居表里的邻居 hash 数组里的每一个 hash 表里的所有邻居项,执行以下操作:
a)对于邻居项状态为 NUD_PERMANENT 或者 NUD_IN_TIMER,则遍历下一个邻居项
b)当邻居项的引用计数为 1 且状态为 NUD_FAILED 时,则调用
neigh_cleanup_and_release 释放该邻居项占用的缓存
c) 当 邻 居 项 的 引 用 计 数 为 1 且 闲 置 时 间 超 过 gc_staletime 时,调用
neigh_cleanup_and_release 释放该邻居项所占用的缓存
3、调用 schedule_delayed_work,重启定时器,待定时器超时后则调用 queue_work,调用
neigh_periodic_work 进行新一轮的异步垃圾回收。
static void neigh_periodic_work(struct work_struct *work)
{
struct neigh_table *tbl = container_of(work, struct neigh_table, gc_work.work);
struct neighbour *n, **np;
unsigned int i;
NEIGH_CACHE_STAT_INC(tbl, periodic_gc_runs);
write_lock_bh(&tbl->lock);
/*
*  periodically recompute ReachableTime from random function 
*/
if (time_after(jiffies, tbl->last_rand + 300 * HZ)) {
struct neigh_parms *p;
tbl->last_rand = jiffies;
for (p = &tbl->parms; p; p = p->next)
p->reachable_time =
neigh_rand_reach_time(p->base_reachable_time);
}
for (i = 0 ; i <= tbl->hash_mask; i++) {
np = &tbl->hash_buckets[i];
while ((n = *np) != NULL) {
unsigned int state;
write_lock(&n->lock);
state = n->nud_state;
if (state & (NUD_PERMANENT | NUD_IN_TIMER)) {
write_unlock(&n->lock);
goto next_elt;
}
if (time_before(n->used, n->confirmed))
n->used = n->confirmed;
if (atomic_read(&n->refcnt) == 1 &&
(state == NUD_FAILED ||
time_after(jiffies, n->used + n->parms->gc_staletime))) {
*np = n->next;
n->dead = 1;
write_unlock(&n->lock);
neigh_cleanup_and_release(n);
continue;
}
write_unlock(&n->lock);
next_elt:
np = &n->next;
}
/*
* It's fine to release lock here, even if hash table
* grows while we are preempted. 
*/
write_unlock_bh(&tbl->lock);
cond_resched();
write_lock_bh(&tbl->lock);
}
/* Cycle through all hash buckets every base_reachable_time/2 ticks.
*  ARP entry timeouts range from 1/2 base_reachable_time to 3/2
* base_reachable_time.
*/
schedule_delayed_work(&tbl->gc_work,
tbl->parms.base_reachable_time >> 1);
write_unlock_bh(&tbl->lock);
}
至此,完成了通用邻居层的垃圾回收机制的处理功能。
Linux 邻居协议学习之四  通用邻居项创建、查找、删除等相关的函数
上节主要是分析了通用邻居层邻居项的垃圾回收机制,这一节主要是分析邻居项的创
建、查找、删除等相关的函数,这一节只是介绍函数功能,而没有涉及状态机、通用邻居层
的架构等。比如邻居项删除函数 neigh_destroy, 而这个函数主要是通过垃圾回收机制的调用
才会执行删除操作;而对于邻居项创建函数 neigh_create,在 arp 协议下,则在路由缓存与
邻居项绑定时会触发调用 neigh_create 创建邻居项,或者接收到 arp reply 或者 arp request 数
据包时会触发调用 neigh_create 创建邻居项。本节只是分析这些函数的实现流程,而不分析
这些函数在 arp 协议中或者其他邻居协议中被其他逻辑子层调用的过。
首先介绍 neigh_create 函数,分析如下
1、调用 neigh_alloc,申请一个邻居项缓存
2、设置邻居项的 primary_key、dev 值,并增加 dev 的引用计数
3、调用邻居表的 constructor,初始化邻居表协议相关的参数
4、如果存在 n->parms->neigh_setup,则调用 neigh_setup 进行初始化(对于 arp,该函数
为 NULL)
5、设置 confirmed 时间
6、如果创建的邻居项总数超过了邻居表定义的最大值,则调用 neigh_hash_grow 扩充
邻居项的 hash 表容量
7、在将创建的邻居项插入到邻居项 hash 表之前,需要再次查找 hash 表里的邻居项:
a)如果该邻居项已经存在 hash 表里,则增加对该邻居项的引用计数,并释放已
申请的邻居项缓存
b)如果该邻居项还没有在 hash 表里,则将该邻居项插入的 hash 表里。
struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey,
struct net_device *dev) 
{
u32 hash_val;
int key_len = tbl->key_len;
int error;
struct neighbour *n1, *rc, *n = neigh_alloc(tbl);
if (!n) {
rc = ERR_PTR(-ENOBUFS);
goto out;
}
memcpy(n->primary_key, pkey, key_len);
n->dev = dev;
dev_hold(dev);
/* Protocol specific setup. */
if (tbl->constructor &&  (error = tbl->constructor(n)) < 0) {
rc = ERR_PTR(error);
goto out_neigh_release;
}
/* Device specific setup. */
if (n->parms->neigh_setup &&
(error = n->parms->neigh_setup(n)) < 0) {
rc = ERR_PTR(error);
goto out_neigh_release;
}
n->confirmed = jiffies - (n->parms->base_reachable_time << 1);
write_lock_bh(&tbl->lock);
if (atomic_read(&tbl->entries) > (tbl->hash_mask + 1))
neigh_hash_grow(tbl, (tbl->hash_mask + 1) << 1);
hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;
if (n->parms->dead) {
rc = ERR_PTR(-EINVAL);
goto out_tbl_unlock;
}
for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) {
if (dev == n1->dev && !memcmp(n1->primary_key, pkey, key_len)) { 
neigh_hold(n1);
rc = n1;
goto out_tbl_unlock;
}
}
n->next = tbl->hash_buckets[hash_val];
tbl->hash_buckets[hash_val] = n;
n->dead = 0;
neigh_hold(n);
write_unlock_bh(&tbl->lock);
NEIGH_PRINTK2("neigh %p is created.\n", n);
rc = n;
out:
return rc;
out_tbl_unlock:
write_unlock_bh(&tbl->lock);
out_neigh_release:
neigh_release(n);
goto out;
}
由于该函数调用了 neigh_alloc,下面我们分析函数 neigh_alloc
从邻居表的 slab 缓存里,申请一个邻居项,并进行通用邻居层的初始化
static struct neighbour *neigh_alloc(struct neigh_table *tbl)
{
struct neighbour *n = NULL;
unsigned long now = jiffies;
int entries;
entries = atomic_inc_return(&tbl->entries) - 1;
if (entries >= tbl->gc_thresh3 ||
(entries >= tbl->gc_thresh2 &&
time_after(now, tbl->last_flush + 5 * HZ))) {
if (!neigh_forced_gc(tbl) &&
entries >= tbl->gc_thresh3)
goto out_entries;
}
n = kmem_cache_zalloc(tbl->kmem_cachep, GFP_ATOMIC);
if (!n)
goto out_entries;
skb_queue_head_init(&n->arp_queue);
rwlock_init(&n->lock);
n->updated    = n->used = now;
n->nud_state    = NUD_NONE;
n->output  = neigh_blackhole;
n->parms    = neigh_parms_clone(&tbl->parms);//直接从邻居表中克隆
setup_timer(&n->timer, neigh_timer_handler, (unsigned long)n);//创建定时器
NEIGH_CACHE_STAT_INC(tbl, allocs);
n->tbl      = tbl;
atomic_set(&n->refcnt, 1);
n->dead      = 1;
out:
return n;
out_entries:
atomic_dec(&tbl->entries);
goto out;
}
下面分析通用邻居项的查找函数,主要是有 neigh_lookup 、neigh_lookup_nodev 、
__neigh_lookup、__neigh_lookup_errno
函数 neigh_lookup 的功能是根据关键字 net_device 与 pkey 在邻居表里查找一个邻居项
1、根据关键字 net_device 与 pkey,计算 hash 值
2、通过 hash 值,在邻居项 hash 数组中,找到指定的 hash 链表
3、遍历指定的 hash 链表,比较 net_device 与 pkey 值,查找符合条件的邻居项。
struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n;
int key_len = tbl->key_len;
u32 hash_val;
NEIGH_CACHE_STAT_INC(tbl, lookups);
read_lock_bh(&tbl->lock);
hash_val = tbl->hash(pkey, dev);
for (n = tbl->hash_buckets[hash_val & tbl->hash_mask]; n; n = n->next) {
if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) {
neigh_hold(n);
NEIGH_CACHE_STAT_INC(tbl, hits);
break; 
}
}
read_unlock_bh(&tbl->lock);
return n;
}
函数 neigh_lookup_nodev 功能是根据关键字 net 与 pkey 在邻居表里查找一个邻居项
该函数的功能与 neigh_lookup 类似
struct neighbour *neigh_lookup_nodev(struct neigh_table *tbl, struct net *net,
const void *pkey)
{
struct neighbour *n;
int key_len = tbl->key_len;
u32 hash_val;
NEIGH_CACHE_STAT_INC(tbl, lookups);
read_lock_bh(&tbl->lock);
hash_val = tbl->hash(pkey, NULL);
for (n = tbl->hash_buckets[hash_val & tbl->hash_mask]; n; n = n->next) {
if (!memcmp(n->primary_key, pkey, key_len) &&
net_eq(dev_net(n->dev), net)) {
neigh_hold(n);
NEIGH_CACHE_STAT_INC(tbl, hits);
break;
}
}
read_unlock_bh(&tbl->lock);
return n;
}
函数__neigh_lookup 的功能是同时实现查找邻居项与决定是否创建邻居项,该函数通常
被其他协议子层调用。
static inline struct neighbour *
__neigh_lookup(struct neigh_table *tbl, const void *pkey, struct net_device *dev, int creat)
{
struct neighbour *n = neigh_lookup(tbl, pkey, dev);
if (n || !creat)
return n;
n = neigh_create(tbl, pkey, dev);
return IS_ERR(n) ? NULL : n;
}
该函数与函数__neigh_lookup 相比,其在查找不到邻居项的情况下,即会默认创建一个
新的邻居项。
static inline struct neighbour *
__neigh_lookup_errno(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n = neigh_lookup(tbl, pkey, dev);
if (n)
return n;
return neigh_create(tbl, pkey, dev);
}
下面分析一下邻居项 hash 表容量的扩容函数 neigh_hash_grow
功能:邻居项散列表的扩容
1、为邻居表申请一个 hash bucket 数组指针,
2、然后将原 hash bucket 中所有的邻居项,重新计算 hash 值后,存放在新的 hash 链表

3、释放原 hash bucket 数组所占用的缓存。
static void neigh_hash_grow(struct neigh_table *tbl, unsigned long new_entries)
{
struct neighbour **new_hash, **old_hash;
unsigned int i, new_hash_mask, old_entries;
NEIGH_CACHE_STAT_INC(tbl, hash_grows);
BUG_ON(!is_power_of_2(new_entries));
new_hash = neigh_hash_alloc(new_entries);
if (!new_hash)
return;
old_entries = tbl->hash_mask + 1;
new_hash_mask = new_entries - 1;
old_hash = tbl->hash_buckets;
get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));
for (i = 0; i < old_entries; i++) {
struct neighbour *n, *next; 
for (n = old_hash[i]; n; n = next) {
unsigned int hash_val = tbl->hash(n->primary_key, n->dev);
hash_val &= new_hash_mask;
next = n->next;
n->next = new_hash[hash_val];
new_hash[hash_val] = n;
}
}
tbl->hash_buckets = new_hash;
tbl->hash_mask = new_hash_mask;
neigh_hash_free(old_hash, old_entries);
}
下面分析邻居项的删除函数 neigh_destroy
1、判断该邻居项是否可以删除  dead==1,则可以删除
2、删除该邻居项的定时器
3、对于可以删除的邻居项,  将邻居项所关联的所有二层缓存头的 hh_output 设置
为 neigh_blackhole,即直接丢弃数据包
4、调用 skb_queue_purge,丢弃队列中所有待发送的数据包
5、取消对 net_dev、neigh_parms 的引用
6、最后调用 kmem_cache_free,将邻居项的缓存释放给邻居表的 slab 缓存中。
void neigh_destroy(struct neighbour *neigh)
{
struct hh_cache *hh;
NEIGH_CACHE_STAT_INC(neigh->tbl, destroys);
if (!neigh->dead) {
printk(KERN_WARNING
"Destroying alive neighbour %p\n", neigh);
dump_stack();
return;
}
if (neigh_del_timer(neigh))
printk(KERN_WARNING "Impossible event.\n");
while ((hh = neigh->hh) != NULL) { 
neigh->hh = hh->hh_next;
hh->hh_next = NULL;
write_seqlock_bh(&hh->hh_lock);
hh->hh_output = neigh_blackhole;
write_sequnlock_bh(&hh->hh_lock);
if (atomic_dec_and_test(&hh->hh_refcnt))
kfree(hh);
}
skb_queue_purge(&neigh->arp_queue);
dev_put(neigh->dev);
neigh_parms_put(neigh->parms);
NEIGH_PRINTK2("neigh %p is destroyed.\n", neigh);
atomic_dec(&neigh->tbl->entries);
kmem_cache_free(neigh->tbl->kmem_cachep, neigh);
}
函数 neigh_release 是 neigh_destroy 的包裹函数,增加了对 neigh->refcnt 的判断。
如果邻居项的引用计数为 1 时,则调用 neigh_destroy 释放该邻居项所对应的缓存
static inline void neigh_release(struct neighbour *neigh)
{
if (atomic_dec_and_test(&neigh->refcnt))
neigh_destroy(neigh);
}
下面分析一下一些小函数
neigh_clone,该函数只是增加对邻居项的引用,而并不 clone 一个邻居项
static inline struct neighbour * neigh_clone(struct neighbour *neigh)
{
if (neigh)
atomic_inc(&neigh->refcnt);
return neigh;
}
设置邻居项的 confirmed 值为当前时间
static inline void neigh_confirm(struct neighbour *neigh)

if (neigh)
neigh->confirmed = jiffies;
}
/*
从 hh_cache 中拷贝二层头部,并调用 hh_cache->output 输出 skb
*/
static inline int neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb)
{
unsigned seq;
int hh_len;
do {
int hh_alen;
seq = read_seqbegin(&hh->hh_lock);
hh_len = hh->hh_len;
hh_alen = HH_DATA_ALIGN(hh_len);
memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
} while (read_seqretry(&hh->hh_lock, seq));
skb_push(skb, hh_len);
return hh->hh_output(skb);
}
Linux 邻居协议学习之五通用邻居项的状态机机制
邻居项的状态机机制是通用邻居层最重要的内容,主要是处理邻居项中状态的改
变,其中包括几个邻居状态的定时器机制,以及邻居项的更新,solicit 请求的发送等
对于通用邻居项的状态机,主要有如下几个状态:
NUD_INCOMPLETE 、 NUD_REACHABLE 、 NUD_DELAY 、 NUD_PROBE 、
NUD_STALE、NUD_NOARP、NUD_PERMANENT、NUD_PROBE、NUD_FAILED
其中,处于如下状态的邻居项,都会启动一个定时器:
#define NUD_IN_TIMER
(NUD_INCOMPLETE|NUD_REACHABLE|NUD_DELAY|NUD_PROBE)
而处于如下状态的邻居项,我们认为邻居项是可达的: 
#define NUD_CONNECTED  (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)
对于处于 NUD_PERMANENT、NUD_NOARP 状态的邻居项,是不会进行邻居项的状
态的转换的,其中 NUD_PERMANENT 说明邻居项是通过 netlink 机制添加的邻居项,不会
改变;而处于 NUD_NOARP 状态的邻居项,一般是地址为组播的邻居项,其二层地址是可
以根据三层地址计算出来的,不需要进行邻居项的学习,也不会进行状态的改变。
从上面 2 个宏定义我们知道,处于 NUD_REACHABLE 状态的邻居项,  肯定会进行状
态的状态的转变。因为处于该状态的邻居项,要启动一个定时器,那如果定时器超时后,邻
居项一直没有被使用,则邻居项的状态就会转变。
我们知道,邻居项的创建原因有两个:
1、  有数据要发送出去,我们还不知道目的三层地址对应的二层地址或者下一跳网关对
应的二层地址。
(在有数据发送时,会先去查找路由表,若查找到路由,且该路由没有在路由缓存
中,则会创建路由缓存,并在创建路由缓存时,会创建一个邻居项与该路由缓存绑定)
2、  接口收到一个 solicit 的请求报文,且没有在邻居表的邻居项 hash  数组中查找到
符合条件的邻居项,则创建一个邻居项。
3、应用层通过 netlink 消息创建一个三层地址对应的二层地址的邻居项
对于上面 3 种创建邻居项,其初始状态以及状态的变化会有所不同。
a)  对于因为有数据要发送而创建的邻居项,会将其邻居项状态设置为 NUD_NONE。
然后邻居项的状态会设置为 NUD_INCOMPLETE 状态,处于该状态的邻居项会主动发
送 solicti 请求, 如 果 再 定 时 器 到 期 前 收 到 应 答 , 则 会 将 邻 居 项 的 状 态 设 置
NUD_REACHABLE,否则,在定时器到期且超过最大发包次数的请求下,则会将邻居
项的状态设置为 NUD_FAILED,处于该状态的邻居项,其占用的缓存将会被释放掉。
b)  对于接收到一个 solicit 的请求报文而创建的邻居项,因为既然有远方的 solicit 请求,则
会 有 数 据 发 送 过 来 , 此 时 创 建 的 邻 居 项 , 就 没 有 将 邻 居 项 的 状 态 设 置 为
NUD_INCOMPLETE 而发送 solicit 请求,但也不能直接就将状态设置为 NUD_CONNECT,
此时是将邻居项的状态设置为 NUD_STALE,这样如果远方有数据发送过来,而且需要
通过该邻居项发送数据到远方,就会将状态设置为 NUD_DELAY, 如果再收到该远方发
来的四层数据的确认等,就间接实现了邻居项的确认,从而将状态设置为
NUD_CONNET
c)  对 于 通 过 netlink 消 息 创 建 的 静 态 邻 居 项 , 我 们 会 将 邻 居 项 的 状 态 设 置 为
NUD_PERMANENT,且不会再改变该邻居项的状态。
疑问:因为创建的邻居项的状态为 NUD_NONE,而 NUD_NONE 也不处于定时器状态,那
么处于 NUD_NONE 状态的邻居项,是如何将邻居项的状态转变为 NUD_INCOMPLETE 的
呢?对于处于 NUD_STALE 状态的邻居项,又是如何实现邻居项状态的转变的呢?
在上面的 a)中,我只是说邻居项从 NUD_NONE 转变为 NUD_INCOMPLETE,却没有
说明这个转换是如何进行的。 其转变过程大致如下(此处以 ipv4 为例): 当有数据包要发送时,
首先是查找路由表,确定目的地址可达。 在这个查找的过程中,若还没有与该目的地址对应
的邻居项,则会创建一个邻居项,并与查找到的路由缓存相关联,此时邻居项的状态还是
NUD_NONE。对于 ipv4 来说,接着就会执行 ip_output,然后就会调用到 ip_finish_output2,
接着就会调用到 neighbour->output,而在 neighbour->output 里就会调用到__neigh_event_send
判断数据包是否可以直接发送出去,如果此时邻居项的状态为 NUD_NONE,则会将邻居项
的状态设置为 NUD_INCOMPLETE,并将要发送的数据包缓存到邻居项的队列中。而处于
NUD_INCOMPLETE 状态的邻居项的状态转变会有定时器处理函数来实现。
对于处于 NUD_ST ALE 状态的邻居项,有两个条件实现状态的转变:
1)  在闲置时间没有超过最大值之前,有数据要通过该邻居项进行发送,则会将邻居项的状
态设置为 NUD_DELAY,接着状态的转变就有定时器超时函数来接管了。
2)  在超过最大闲置时间后,没有数据通过该邻居项进行发送,则会将邻居项的状态设置为
NUD_FAILED,并会被垃圾回收机制进行缓存回收。
1、对于 NUD_INCOMPLETE,当本机发送完 arp  请求包后,还未收到应答时,即会进入该
状态。  进入该状态,即会启动定时器,如果在定时器到期后,还没有收到应答时:如果没
有到达最大发包上限时,即会重新进行发送请求报文;如果超过最大发包上限还没有收到应
答,则会将状态设置为 failed
2、对于收到可到达性确认后,即会进入 NUD_REACHABLE,当进入 NUD_REACHABLE
状态。当进入 NUD_REACHABLE 后,即会启动一个定时器,当定时器到时前,该邻居协
议没有被使用过,就会将邻居项的状态转换为 NUD_STALE
3、对于进入 NUD_STALE 状态的邻居项,即会启动一个定时器。如果在定时器到时前,有
数据需要发送,则直接将数据包发送出去,并将状态设置为 NUD_DELAY;如果在定时器
到时,没有数据需要发送,且该邻居项的引用计数为 1,则会通过垃圾回收机制,释放该邻
居项对应的缓存
4、处于 NUD_DELAY 状态的邻居项,如果在定时器到时后,没有收到可到达性确认,则会
进入 NUD_PROBE 状态;如果在定时器到达之前,收到可到达性确认,则会进入
NUD_REACHABLE (在该状态下的邻居项不会发送 solicit 请求,而只是等待可到达性应答。
主要包括对以前的 solicit 请求的应答或者收到一个对于本设备以前发送的一个数据包的应
答)
5、处于 NUD_PROBE 状态的邻居项,会发送 arp  solicit 请求,并启动一个定时器。如果在
定时器到时前,收到可到达性确认,则进入 NUD_REACHABLE;如果在定时器到时后,没
有收到可到达性确认:
a)没有超过最大发包次数时,则继续发送 solicit 请求,并启动定时器
b)如果超过最大发包次数,则将邻居项状态设置为 failed
下图是邻居项的状态转换逻辑图,通过上面的描述和下面的逻辑图,能够很好的理解邻居项
的状态机机制。
邻居项的创建:
我们知道邻居项的创建有三种方式,下面分析第一种方式的处理流程,即有数据要发送
出去,我们还不知道目的三层地址对应的二层地址或者下一跳网关对应的二层地址,对邻居
项的创建(我们以 ipv4 为例)。通过上面的分析,我们知道,当查找到路由并创建路由缓存
时,则会调用 arp_bind_neighbour 进行路由缓存与邻居项的绑定,该函数主要实现了邻居项
的查找与创建,下面是该函数的分析:
该函数实现 arp 协议中 neighbour 项与路由缓存中的 dst_entry 表项的绑定
通过下一跳网关地址和 net_dev 为关键字查找一个 neighbour 项
1、若查找到,则将 dst->neighbour 指向该 neighbour 项
2、若没有查找到,则调用 neigh_create 创建一个邻居表项并加入到 arp_table 的邻居表
项链表中,并将 dst->neighbour 指向该 neighbour 项
int arp_bind_neighbour(struct dst_entry *dst)
{
struct net_device *dev = dst->dev;
struct neighbour *n = dst->neighbour;
if (dev == NULL)
return -EINVAL;
if (n == NULL) {
__be32 nexthop = ((struct rtable *)dst)->rt_gateway;
if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
nexthop = 0;
n = __neigh_lookup_errno(
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
dev->type == ARPHRD_ATM ? clip_tbl_hook :
#endif
&arp_tbl, &nexthop, dev);
if (IS_ERR(n))
return PTR_ERR(n);
dst->neighbour = n;
}
return 0;
}
执行完上面邻居项的创建以后,后面会间接调用函数 neigh_event_send,实现邻居项状
态从 NUD_NONE 到 NUD_INCOMPLETTE 状态的改变。
我们接着分析第二种创建邻居项的执行流程,仍以 ipv4 为例:
在 arp 的接收处理函数 arp_rcv 里,在对 arp 包的头部信息进行检查以及防火墙规则检
查以后,对于允许接收的 arp 包,则会调用 arp_process 进行后续的处理,而在 arp_process
中,对于接收到的 arp 请求报文后, 则会调用 neigh_event_ns 进行邻居项的查找与创建功能,
对于新创建的邻居项,则会调用函数 neigh_update 更新邻居项的状态。
函数 neigh_event_ns 的逻辑流程还是比较简单的,主要是将邻居项的状态设置为
NUD_STALE
struct neighbour *neigh_event_ns(struct neigh_table *tbl,
u8 *lladdr, void *saddr,
struct net_device *dev)
{
struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev,
lladdr || !dev->addr_len);
if (neigh)
neigh_update(neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_OVERRIDE);
return neigh;
}
对于第三种创建邻居项的执行流程,是通过 netlink 消息机制实现的,最终会调用函数
neigh_add 实现邻居项的创建。对于新创建的邻居项,则会调用函数 neigh_update 或者
neigh_event_send 实现邻居项状态的更新。
通过对于以上三种情况下邻居项的创建流程,我们发现会调用函数 neigh_update 、
neigh_event_send 进行邻居项状态的更新,其实对于处于定时器状态的邻居项,会通过定时
器超时处理函数实现邻居项状态的转变,下面我们分析一下这 3 个函数的处理流程。
邻居项状态的更新函数 1:
下面我们分析一下函数 neigh_update:
该函数的功能:邻居项的更新,主要是更新二层地址与邻居项的状态,并会
根据邻居项的状态,选择相对应的输出函数
1、判断输入二层地址,判断是否需要覆盖邻居项的二层地址
2、判断邻居项状态的改变是否合法
3、根据不同的邻居项状态设置不同的邻居项输出函数,并设置与该
邻居项关联的所有二层缓存头部
该函数被调用的情形有:
1、当接收到邻居项的应答报文后,则会调用该函数更新二层地址和状态为 CONNECT
2、当接收到邻居项的请求报文后,则会调用该函数将邻居项的状态设置为 STALE
3、处理通过 ioctl 或者 netlink 执行的邻居项的添加、删除邻居项时,也会调用该函数
更新邻居项的状态与二层地址
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
u32 flags)
{
u8 old;
int err;
int notify = 0;
struct net_device *dev;
int update_isrouter = 0;
write_lock_bh(&neigh->lock);
dev     = neigh->dev;
old     = neigh->nud_state;
err     = -EPERM;
/*
对于邻居项的原状态为 NOARP 或者 PERMANENT,且不是 admin 发送的请求,则直
接返回
*/
if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
(old & (NUD_NOARP | NUD_PERMANENT)))
goto out;
/*
当邻居项状态不是有效状态时(即是 NUD_INCOMPLET、 NUD_NONE、 NUD_FAILED):
1、删除该邻居项的定时器
2、对于原状态是 CONNECT 而新状态不是有效态时,则调用 neigh_suspect 将邻居项的 
输出函数设置为通用输出函数
3、如果原状态是 NUD_INCOMPLETE 或者 NUD_PROBE,且新状态为 NUD_FAILED
时,则调用
neigh_invalidate 发送错误报告,并发送通知信息,函数返回
*/
if (!(new & NUD_VALID)) {
neigh_del_timer(neigh);
if (old & NUD_CONNECTED)
neigh_suspect(neigh);
neigh->nud_state = new;
err = 0;
notify = old & NUD_VALID;
if ((old & (NUD_INCOMPLETE | NUD_PROBE)) &&
(new & NUD_FAILED)) {
neigh_invalidate(neigh);
notify = 1;
}
goto out;
}
/*
1、对于设备二层地址长度为 0 的情形,则不需要更新二层地址,直接
使用 neigh->ha
2、原状态为有效的,且要更改的地址与邻居项存储的地址相同,则
无需更改
3、原状态为无效,且要更改的地址也是无效,则是逻辑错误,函数直接
返回
4、原状态有效,且要更改的地址无效时,则先将地址设置为邻居项的地址. ???
5、其他情况下不更改传进来的二层地址。
即:
原状态有效,且修改的地址与原邻居项地址不同
原状态无效,且修改的地址有效时
*/
/* Compare new lladdr with cached one */
if (!dev->addr_len) {
/* First case: device needs no address. */
lladdr = neigh->ha;
} else if (lladdr) {
/* The second case: if something is already cached
and a new address is proposed:
- compare new & old
- if they are different, check override flag
*/ 
if ((old & NUD_VALID) &&
!memcmp(lladdr, neigh->ha, dev->addr_len))
lladdr = neigh->ha;
} else {
/* No address is supplied; if we know something,
use it, otherwise discard the request.
*/
err = -EINVAL;
if (!(old & NUD_VALID))
goto out;
lladdr = neigh->ha;
}
/*
1、邻居项的新状态是 CONNECT 时,更新 connect 时间
2、更新 update 时间
*/
if (new & NUD_CONNECTED)
neigh->confirmed = jiffies;
neigh->updated = jiffies;
/* If entry was valid and address is not changed,
do not change entry state, if new one is STALE.
*/
err = 0;
/*
1、原状态有效,且不允许覆盖原来值时,且二层地址不同,且原状态为
CONNECT 时,则不更新邻居项的二层地址,而只是将状态设置为 STALE
(这是在二层地址不同时,不修改二层地址的最后一个条件)
2、原状态有效,且二层地址不变,且新状态为 STALE 时,则不改邻居项的
状态
*/
update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;
if (old & NUD_VALID) {
if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {
update_isrouter = 0;
if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&
(old & NUD_CONNECTED)) {
lladdr = neigh->ha;
new = NUD_STALE;
} else
goto out; 
} else {
if (lladdr == neigh->ha && new == NUD_STALE &&
((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) ||
(old & NUD_CONNECTED))
)
new = old;
}
}
/*
当邻居项的新旧状态不同时,则会删除定时器。若新状态是
NUD_TIMER,则重新添加定时器。
(其中对于定时器的超时时间,如果新状态为 NUD_REACHABLE,则将
超时时间设置为 reachable_time,否则将定时器的超时时间设置为当前时间)
设置邻居项的状态为新状态
*/
if (new != old) {
neigh_del_timer(neigh);
if (new & NUD_IN_TIMER)
neigh_add_timer(neigh, (jiffies +
((new & NUD_REACHABLE) ?
neigh->parms->reachable_time :
0)));
neigh->nud_state = new;
}
/*
如果邻居项的二层地址不同,则更新邻居项里的二层地址,并
调用 neigh_update_hhs,更新与该邻居项相关联的所有二层头部缓存。
如果新状态不是 CONNECT 状态,则将 confirm 时间设置为比当前时间早
2*base_reachable_time.
根据邻居项的不同更新邻居项的输出函数:
当为 NUD_CONNECTED,则调用 neigh_connect 将邻居项的输出函数设置为快速输出
函数
当为非 NUD_CONNECTED,则调用 neigh_suspect 将邻居项的输出函数设置为通用输
出函数
*/
if (lladdr != neigh->ha) {
memcpy(&neigh->ha, lladdr, dev->addr_len);
neigh_update_hhs(neigh);
if (!(new & NUD_CONNECTED))
neigh->confirmed = jiffies -(neigh->parms->base_reachable_time << 1); 
notify = 1;
}
if (new == old)
goto out;
if (new & NUD_CONNECTED)
neigh_connect(neigh);
else
neigh_suspect(neigh);
if (!(old & NUD_VALID)) {
struct sk_buff *skb;
/* Again: avoid dead loop if something went wrong */
while (neigh->nud_state & NUD_VALID &&
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
struct neighbour *n1 = neigh;
write_unlock_bh(&neigh->lock);
/* On shaper/eql skb->dst->neighbour != neigh :( */
if (skb_dst(skb) && skb_dst(skb)->neighbour)
n1 = skb_dst(skb)->neighbour;
n1->output(skb);
write_lock_bh(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
}
out:
if (update_isrouter) {
neigh->flags = (flags & NEIGH_UPDA TE_F_ISROUTER) ?
(neigh->flags | NTF_ROUTER) :
(neigh->flags & ~NTF_ROUTER);
}
write_unlock_bh(&neigh->lock);
if (notify)
neigh_update_notify(neigh);
return err;
}
邻居项状态的更新函数 2:
使用定时器实现邻居项状态转变的处理:
通过上面的分析,我们知道对于这几个邻居项状态的转变,最主要的就是需要定时器的
那几个邻居项的状态,所以我们下面分析邻居项的定时器的处理函数,对于这几个邻居项的
状态,内核使用的是同一个定时器,只不过设置的超时时间不同罢了。 
该函数的功能:邻居项最主要的定时器超时处理函数,
实现了诸多邻居项状态的转换以及邻居项 solicit 请求相关的函数
1、如果邻居项的当前状态不属于 NUD_IN_TIMER,则函数返回。
*/
static void neigh_timer_handler(unsigned long arg)
{
unsigned long now, next;
struct neighbour *neigh = (struct neighbour *)arg;
unsigned state;
int notify = 0;
write_lock(&neigh->lock);
state = neigh->nud_state;
now = jiffies;
next = now + HZ;
if (!(state & NUD_IN_TIMER)) {
#ifndef CONFIG_SMP
printk(KERN_WARNING "neigh: timer & !nud_in_timer\n");
#endif
goto out;
}
/*
对于处于 reach 状态的邻居项:
1、如果当前时间距确认时间 confirmed,还未到超时时限 reachable_time,则将定时器
时间设置为邻居项的超时
时限 reachable_time
2、当前时间已晚于确认时间加上超时时限,当未超过邻居项使用时间
加上 delay_probe_time,则将状态设置为 DELAY。
这个状态的改变条件,我感觉设置的很巧妙。
一般是进入 stale 状态的邻居项,在超时前有数据时,则进入 Delay 状态。
为什么可以直接从 REACH 状态进入 Delay 状态呢?
3、当前时间晚于 used+delay_probe_time,说明在 confirmed+reachable_time 超时前的短
暂时间点
内没有数据发送,此时即将状态设置为 STALE,
对于 Delay 状态的邻居项:
1、当前时间小于 connect_time+delay_time 时,说明邻居项可能在定时器超时函数刚执
行时
即已经更新了 connect_time 时间,此时即可以在邻居项的状态设置为 reach
(connect_time 会在 neigh_update 里被更新) 
2、说明该邻居项在 delay_time 超时后,还没有被外部确认,此时就需要将邻居项
的状态设置为 probe,准备发送 solict 请求
对于 probe 与 incomplete 状态的邻居项,此时需要将定时器的下一次超时时间设置为
retrain,如果在下一次超时前,还没有得到确认,则还会执行该定时器处理函数
对于 probe 与 incomplete 状态的邻居项:
1、如果已经超过了最大发包次数,则将邻居项的状态设置 FAILED,并调用
neigh_invalidate,发送错误报告,并释放缓存的数据包
2、如果还没有超过最大发包次数,则调用 solicit,发送邻居项 solicit 请求。
*/
if (state & NUD_REACHABLE) {
if (time_before_eq(now,
neigh->confirmed + neigh->parms->reachable_time)) {
NEIGH_PRINTK2("neigh %p is still alive.\n", neigh);
next = neigh->confirmed + neigh->parms->reachable_time;
} else if (time_before_eq(now,
neigh->used + neigh->parms->delay_probe_time)) {
NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;
neigh_suspect(neigh);
next = now + neigh->parms->delay_probe_time;
} else {
NEIGH_PRINTK2("neigh %p is suspected.\n", neigh);
neigh->nud_state = NUD_STALE;
neigh->updated = jiffies;
neigh_suspect(neigh);
notify = 1;
}
} else if (state & NUD_DELAY) {
if (time_before_eq(now,
neigh->confirmed + neigh->parms->delay_probe_time)) {
NEIGH_PRINTK2("neigh %p is now reachable.\n", neigh);
neigh->nud_state = NUD_REACHABLE;
neigh->updated = jiffies;
neigh_connect(neigh);
notify = 1;
next = neigh->confirmed + neigh->parms->reachable_time;
} else {
NEIGH_PRINTK2("neigh %p is probed.\n", neigh);
neigh->nud_state = NUD_PROBE;
neigh->updated = jiffies;
atomic_set(&neigh->probes, 0); 
next = now + neigh->parms->retrans_time;
}
} else {
/* NUD_PROBE|NUD_INCOMPLETE */
next = now + neigh->parms->retrans_time;
}
if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) &&
atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) {
neigh->nud_state = NUD_FAILED;
notify = 1;
neigh_invalidate(neigh);
}
if (neigh->nud_state & NUD_IN_TIMER) {
if (time_before(next, jiffies + HZ/2))
next = jiffies + HZ/2;
if (!mod_timer(&neigh->timer, next))
neigh_hold(neigh);
}
if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {
struct sk_buff *skb = skb_peek(&neigh->arp_queue);
/* keep skb alive even if arp_queue overflows */
if (skb)
skb = skb_copy(skb, GFP_ATOMIC);
write_unlock(&neigh->lock);
neigh->ops->solicit(neigh, skb);
atomic_inc(&neigh->probes);
kfree_skb(skb);
} else {
out:
write_unlock(&neigh->lock);
}
if (notify)
neigh_update_notify(neigh);
neigh_release(neigh);
}
在申请邻居项的内存函数 neigh_alloc 里,会创建该定时器,并会将定时器的超时处理
函数设置为 neigh_timer_handler。
邻居项状态的更新函数 3
第三个邻居项状态的更新函数,通过
/*
1、对于 connect、delay、probe 状态的邻居项,返回 0
2、
*/
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
int rc;
unsigned long now;
write_lock_bh(&neigh->lock);
rc = 0;
if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
goto out_unlock_bh;
now = jiffies;
/*
当状态为 NUD_NONE 时,
1、如果组播探测最大次数加上应用探测的最大次数
不为 0,则将状态设置为 INCOMPLETE,更新 update 时间,并修改定时器的超时
时间
2、否则将邻居项的状态设置为 failed,更新 update 时间,直接释放数据包缓存
当状态为 STALE 时,当有数据包要发送时,则将状态设置为 DELAY,更新 update 时

并修改邻居项定时器的超时时间
*/
if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
atomic_set(&neigh->probes, neigh->parms->ucast_probes);
neigh->nud_state      = NUD_INCOMPLETE;
neigh->updated = jiffies;
neigh_add_timer(neigh, now + 1);
} else {
neigh->nud_state = NUD_FAILED;
neigh->updated = jiffies;
write_unlock_bh(&neigh->lock);
kfree_skb(skb);
return 1;

} else if (neigh->nud_state & NUD_STALE) {
NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;
neigh_add_timer(neigh,
jiffies + neigh->parms->delay_probe_time);
}
/*
对于处于 INCOMPLETE 状态的邻居项,已经启动了邻居项定时器,此时只需要
将要发送的数据包存入邻居项的数据包缓存队列里即可。后续如果
邻居项可达时则会有相应的函数发送出去
*/
if (neigh->nud_state == NUD_INCOMPLETE) {
if (skb) {
if (skb_queue_len(&neigh->arp_queue) >=
neigh->parms->queue_len) {
struct sk_buff *buff;
buff = __skb_dequeue(&neigh->arp_queue);
kfree_skb(buff);
NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);
}
__skb_queue_tail(&neigh->arp_queue, skb);
}
rc = 1;
}
out_unlock_bh:
write_unlock_bh(&neigh->lock);
return rc;
}
总结:通过上面的分析,用于 邻 居 项 状 态 改 变 的 函 数 有 neigh_update 、
neigh_timer_handler、__neigh_event_send。而对于每个函数被使用的情景,我们已经在上面
分析完了。
下面分析一下几个通用邻居项常用的函数,它们或者被上面的函数间接调用或者被直接
调用,它们的逻辑结构也比较简单:
/*
遍历该邻居项所关联的所有二层缓存结构,将其函数指针 hh_output 指向
该邻居项的通用输出函数
*/
static void neigh_suspect(struct neighbour *neigh)
{
struct hh_cache *hh; 
NEIGH_PRINTK2("neigh %p is suspected.\n", neigh);
neigh->output = neigh->ops->output;
for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->output;
}
/* Neighbour state is OK;
enable fast path.
Called with write_locked neigh.
*/
/*
遍历该邻居项所关联的所有二层缓存结构,将其函数指针 hh_output 指向
该邻居项的快速输出函数 hh_output
*/
static void neigh_connect(struct neighbour *neigh)
{
struct hh_cache *hh;
NEIGH_PRINTK2("neigh %p is connected.\n", neigh);
neigh->output = neigh->ops->connected_output;
for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->hh_output;
}
/*
返回邻居项能发送 solicit 的最大次数
*/
static __inline__ int neigh_max_probes(struct neighbour *n)
{
struct neigh_parms *p = n->parms;
return (n->nud_state & NUD_PROBE ?
p->ucast_probes :
p->ucast_probes + p->app_probes + p->mcast_probes);
}
/*
功能:发送错误报告
1、更新邻居项的 update 时间
2、对于邻居状态为 failed,且邻居项的队列里有数据包等待发送时,则调用
error_report 函数,发送错误报告
*/
static void neigh_invalidate(struct neighbour *neigh)
__releases(neigh->lock)
__acquires(neigh->lock)
{
struct sk_buff *skb;
NEIGH_CACHE_STAT_INC(neigh->tbl, res_failed);
NEIGH_PRINTK2("neigh %p is failed.\n", neigh);
neigh->updated = jiffies;
/* It is very thin place. report_unreachable is very complicated
routine. Particularly, it can hit the same neighbour entry!
So that, we try to be accurate and avoid dead loop. --ANK
*/
while (neigh->nud_state == NUD_FAILED &&
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
write_unlock(&neigh->lock);
neigh->ops->error_report(neigh, skb);
write_lock(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
}
/*
功能:对于一个给定的邻居项,更新与该邻居项有关联的所有二层缓存头部结构成员
*/
static void neigh_update_hhs(struct neighbour *neigh)
{
struct hh_cache *hh;
void (*update)(struct hh_cache*, const struct net_device*, const unsigned char *)
= neigh->dev->header_ops->cache_update;
if (update) {
for (hh = neigh->hh; hh; hh = hh->hh_next) {
write_seqlock_bh(&hh->hh_lock); 
update(hh, neigh->dev, neigh->ha);
write_sequnlock_bh(&hh->hh_lock);
}
}
}
/*
功能:邻居项关联的二层缓存头部的初始化
*/
static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst,
__be16 protocol)
{
struct hh_cache  *hh;
struct net_device *dev = dst->dev;
for (hh = n->hh; hh; hh = hh->hh_next)
if (hh->hh_type == protocol)
break;
if (!hh && (hh = kzalloc(sizeof(*hh), GFP_ATOMIC)) != NULL) {
seqlock_init(&hh->hh_lock);
hh->hh_type = protocol;
atomic_set(&hh->hh_refcnt, 0);
hh->hh_next = NULL;
if (dev->header_ops->cache(n, hh)) {
kfree(hh);
hh = NULL;
} else {
atomic_inc(&hh->hh_refcnt);
hh->hh_next = n->hh;
n->hh    = hh;
if (n->nud_state & NUD_CONNECTED)
hh->hh_output = n->ops->hh_output;
else
hh->hh_output = n->ops->output;
}
}
if (hh)  {
atomic_inc(&hh->hh_refcnt);
dst->hh = hh;
}

至此,大致分析完了通用邻居项的工作流程,通过这次认真的阅读通用邻居层的代码机
制,对于以后编程应该会有影响。通过分析该子层,对于一个比较好的子层代码:
1、  当实现一个功能时,尽量抽象一个通用层,实现通用层功能的处理,也有利于后续
增加新的具体的子层功能
2、  要具有垃圾回收机制,因为内存是有限的,所以就需要有一个机制实现周期性的内
存回收,同时最好需要一个同步的回收机制,以便没有内存用于创建新的项时,能
够及时的删除很久没被使用的项以创建新的项
3、  对于网络层相关的子层,一般会伴随状态的变化,需要考虑状态机的构建以及完整
性分析
目前也就总结出这 3 项,通过这次总结,后续分析路由子层的代码时,应该会比较容易。
Linux 邻居协议学习之六  arp 协议处理
上面分析完了通用邻居层的架构以及代码处理,下面分析 ipv4 的邻居协议 arp。
对于 linux 邻居协议层,我认为通用邻居层是最重要的实现, arp 协议层的处理,
主要是涉及三个方面:1、处理 arp 请求、应答,并创建相应的邻居项 2、发送
arp 请求,并创建相应的邻居项 3、处理应用层通过 ioctl 创建或者删除邻居项
的请求。它们最终都好调用通用邻居层的函数。
1、arp 协议的格式 
Linux 内核中 arp 相关的数据结构:
struct arphdr {
__be16    ar_hrd;    /* format of hardware address*/
__be16    ar_pro;    /* format of protocol address  */
unsigned char  ar_hln;    /* length of hardware address  */
unsigned char  ar_pln;    /* length of protocol address  */
__be16    ar_op;    /* ARP opcode (command)    */
#if 0
/*
*    Ethernet looks like this : This bit is variable sized however...
*/
unsigned char    ar_sha[ETH_ALEN];  /* sender hardware address  */
unsigned char    ar_sip[4];    /* sender IP address  */
unsigned char    ar_tha[ETH_ALEN];  /* target hardware address  */
unsigned char    ar_tip[4];   /* target IP address    */
#endif
};
其中 ar_op 对应的操作码有:
/* ARP protocol opcodes. */
#define  ARPOP_REQUEST  1    /* ARP request      */ 
#define  ARPOP_REPLY  2    /* ARP reply      */
#define  ARPOP_RREQUEST  3    /* RARP request      */
#define  ARPOP_RREPLY  4    /* RARP reply      */
#define  ARPOP_InREQUEST  8    /* InARP request    */
#define  ARPOP_InREPLY  9    /* InARP reply      */
#define  ARPOP_NAK  10    /* (ATM)ARP NAK      */
因为我们目前只使用 arp 进行地址解析,所以在 arp_rcv 中只处理 arp_request 与 arp_reply 这
两种操作码。
2、arp 协议的初始化
a)  使用 neigh_table_init 初始化 arp 协议对应的邻居表 arp_tbl
b)  为 arp 协议注册协议处理函数 arp_rcv 对于网络设备驱动处理完的数据,会由函数
netif_receive_skb 继续二、三层协议的处理。对于进入 netif_receive_skb 的数据包,如果桥接
数据没有进行处理,则会遍历 ptype_base  hash 数组中的每一个 hash 表中的所有已注册的协
议处理函数,查找与 skb 数据包相同的协议处理函数,对于 arp 数据包来说,就会通过
deliver_skb,调用到函数 arp_rcv 进行 arp 数据包的处理
c)  向 proc 文件系统中注册 arp 相关的 proc 文件
d)  向 netdev_chain 通知链中注册 arp 的事件通知函数,主要是处理二层地址改变的事件
void __init arp_init(void)
{
neigh_table_init(&arp_tbl);
dev_add_pack(&arp_packet_type);
/* */
arp_proc_init();
#ifdef CONFIG_SYSCTL
neigh_sysctl_register(NULL, &arp_tbl.parms, "ipv4", NULL);
#endif
register_netdevice_notifier(&arp_netdev_notifier);
}
由于上次没有分析邻居项初始化的函数,这次继续分析下函数 neigh_table_init
/*
功能:初始化邻居表
1、调用 neigh_table_init_no_netlink 初始化邻居表项的成员值
2、将该邻居表添加到邻居表链表 neigh_tables 中
*/
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();
}
}
我们接着分析 neigh_table_init_no_netlink
/*
1、设置邻居表的 reachable_time
2、为该邻居表申请 slab 缓存,用于创建邻居项
3、为邻居表的邻居项 hash 数组申请缓存
4、创建一个带有延迟功能的工作队列,用于进行邻居项的垃圾回收
*/
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);
/*创建带有延迟功能的工作队列,并将创建的工作添加
到工作队列 keventd_wq 中去,并开启一个定时器延迟 tbl->parms.reachable_time
后,调用 queue_work 执行函数 neigh_periodic_work
*/
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;
}
3、arp_table 表项的初始化
struct neigh_table arp_tbl = {
.family =  AF_INET,
.entry_size =  sizeof(struct  neighbour)  +  4,//这里的大小为什么要加 4 呢?因为 neighbour
中的最后一个零数组成员是指向 ipv4 地址的
.key_len =  4,//ipv4  地址长度
.hash =    arp_hash,//arp  协议的 hash 函数
.constructor =  arp_constructor,//邻居表现初始化函数,初始化 neighbour 项中与协议相关
的成员
.proxy_redo =  parp_redo,
.id =    "arp_cache",
.parms = {
.tbl =      &arp_tbl,
.base_reachable_time =  30 * HZ,
.retrans_time =  1 * HZ,
.gc_staletime =  60 * HZ,
.reachable_time =    30 * HZ,
.delay_probe_time =  5 * HZ,
.queue_len =    3,
.ucast_probes =  3,
.mcast_probes =  3,
.anycast_delay =  1 * HZ,
.proxy_delay =    (8 * HZ) / 10,
.proxy_qlen =    64,
.locktime =    1 * HZ,
},
.gc_interval =  30 * HZ,
.gc_thresh1 =  128,
.gc_thresh2 =  512,
.gc_thresh3 =  1024,
};
这个邻居表项设置了 arp 邻居表项的初始化处理函数的设置函数 arp_constructor;邻居项异
步垃圾回收启动相关的阀值设置 gc_thresh1、gc_thresh2、gc_thresh3;一个邻居项发送 arp 
request 数据包的最大值;重传时间; 邻居项状态转换相关的时间间隔值 base_reachable_time、
reachable_time、delay_probe_time;arp 协议相关的 hash 函数。
4、arp 协议相关的邻居项的初始化函数 arp_constructor
/*
该函数用于 arp 协议中,初始化 neighbour 项中与 arp 协议相关的项
1、设置邻居项的状态
2、设置邻居项的 ops 指针
3、设置邻居项的 output 函数指针
*/
static int arp_constructor(struct neighbour *neigh)

__be32 addr = *(__be32*)neigh->primary_key;
struct net_device *dev = neigh->dev;
struct in_device *in_dev;
struct neigh_parms *parms;
rcu_read_lock();
in_dev = __in_dev_get_rcu(dev);
if (in_dev == NULL) {
rcu_read_unlock();
return -EINVAL;
}
neigh->type = inet_addr_type(dev_net(dev), addr);
parms = in_dev->arp_parms;
__neigh_parms_put(neigh->parms);
neigh->parms = neigh_parms_clone(parms);
rcu_read_unlock();
/*对于以太网设备,其 dev->header_ops 为 eth_header_ops*/
if (!dev->header_ops) {
neigh->nud_state = NUD_NOARP;
neigh->ops = &arp_direct_ops;
neigh->output = neigh->ops->queue_xmit;
} else {
/* Good devices (checked by reading texts, but only Ethernet is
tested)
ARPHRD_ETHER: (ethernet, apfddi)
ARPHRD_FDDI: (fddi)
ARPHRD_IEEE802: (tr)
ARPHRD_METRICOM: (strip)
ARPHRD_ARCNET:
etc. etc. etc.
ARPHRD_IPDDP will also work, if author repairs it.
I did not it, because this driver does not work even
in old paradigm.
*/
#if 1
/* So... these "amateur" devices are hopeless.
The only thing, that I can say now: 
It is very sad that we need to keep ugly obsolete
code to make them happy.
They should be moved to more reasonable state, now
they use rebuild_header INSTEAD OF hard_start_xmit!!!
Besides that, they are sort of out of date
(a lot of redundant clones/copies, useless in 2.1),
I wonder why people believe that they work.
*/
switch (dev->type) {
default:
break;
case ARPHRD_ROSE:
#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
case ARPHRD_AX25:
#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
case ARPHRD_NETROM:
#endif
neigh->ops = &arp_broken_ops;
neigh->output = neigh->ops->output;
return 0;
#endif
;}
#endif
/*
1、对于组播类型的 neighbour 项,则将该邻居项的状态设置为 NUD_NOARP
2、对于不需要 arp 的设备或者回环设备,将 nud_state  设置为 NUD_NOARP
3、对于广播类型或者点对点设备的邻居项,不需要 arp
*/
if (neigh->type == RTN_MULTICAST) {
neigh->nud_state = NUD_NOARP;
arp_mc_map(addr, neigh->ha, dev, 1);
} else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {
neigh->nud_state = NUD_NOARP;
memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
} else if (neigh->type == RTN_BROADCAST || dev->flags&IFF_POINTOPOINT) {
neigh->nud_state = NUD_NOARP;
memcpy(neigh->ha, dev->broadcast, dev->addr_len);
}
/*
如果设备的 header_ops->cache 存在,则将邻居项的 ops 设置为 arp_hh_ops,
对于以太网设备,其 header_ops->cache 为 eth_header_cache,所以对于以太网设备
其 neighbour->ops 为 arp_hh_ops 
*/
if (dev->header_ops->cache)
neigh->ops = &arp_hh_ops;
else
neigh->ops = &arp_generic_ops;
/* 对 于 邻 居 项 状 态 为 有 效 状 态 时 , 则 将 neigh->output 设置为
neigh->ops->connected_output*/
if (neigh->nud_state&NUD_VALID)
neigh->output = neigh->ops->connected_output;
else
neigh->output = neigh->ops->output;
}
return 0;
}
至此分析完了 arp 协议的初始化相关的处理流程,分析了 arp 协议函数与通用处理函数之间
的调用关系
Linux 邻居协议学习之七  arp 数据包处理流程
上面一节分析了 arp 协议的初始化过程。本节主要是 arp 数据包的处理流程,在
arp 初始化时,通过调用 dev_add_pack 将 arp 协议的接收处理函数添加到了三层
协议数据包处理函数相关的 hash 链表 ptype_base 中(关于三层协议数据包处理
函数相关的 hash 链表,请参考文档)。当底层接收到属于本机的 arp 数据包时,
就会调用 arp_rcv 进行后续处理。
下面我们就分析 arp_rcv 以及与其相关的函数.
arp_rcv 的定义如下, 该函数主要就是 arp_process 的封装,相比 arp_process
主要是增加了 arp 数据包的合理性检查,以及增加防火墙的 hook 函数。对于满
足要求的数据包才会调用 arp_process。
功能:对接收到的 arp 数据包的处理函数
1、首先对 arp 数据包进行合理性检查
2、调用 NF_HOOK,判断是否需要对 arp 进行进一步的处理,对于需要
进一步处理的数据包,则调用 arp_process 进行后续处理。
static int arp_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
struct arphdr *arp;
/* ARP header, plus 2 device addresses, plus 2 IP addresses. */
if (!pskb_may_pull(skb, arp_hdr_len(dev))) 
goto freeskb;
arp = arp_hdr(skb);
if (arp->ar_hln != dev->addr_len ||
dev->flags & IFF_NOARP ||
skb->pkt_type == PACKET_OTHERHOST ||
skb->pkt_type == PACKET_LOOPBACK ||
arp->ar_pln != 4)
goto freeskb;
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
goto out_of_mem;
memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));
return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);
freeskb:
kfree_skb(skb);
out_of_mem:
return 0;
}
下面我们分析 arp_process
功能:处理一个 arp 请求
对于 arp_process,主要是考虑如下几个方面:
1、 arp 数据包的格式是否正确,是否是属于系统支持的邻居项协议
2、 是否需要丢弃接收到的 arp 数据包
3、 处理符合条件的 arp 数据包。
下面是处理 arp 包的几个条件:
丢弃数据包的标准:
1、arp_process 只处理 request、reply 的 arp 数据包,丢弃其他类型的数据包
a)对于类型为 request 的数据包,丢弃目的地址是组播或者 loopback 的 arp
数据。
对于需要处理的数据包,大致可以分为几个方面:
1、对本机发送的 arp 请求的应答数据包的处理
2、arp 请求数据包
a)目的地址是本地地址,且源地址不为 0 的 arp 请求数据包
b)目的地址是本地地址,且源地址为 0 的重复地址检测的 arp 请求数据

3、非本地发送的 arp 请求的应答数据包
static int arp_process(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct in_device *in_dev = in_dev_get(dev);
struct arphdr *arp;
unsigned char *arp_ptr;
struct rtable *rt;
unsigned char *sha;
__be32 sip, tip;
u16 dev_type = dev->type;
int addr_type;
struct neighbour *n;
struct net *net = dev_net(dev);
/* arp_rcv below verifies the ARP header and verifies the device
* is ARP'able.
*/
if (in_dev == NULL)
goto out;
/*调用 arp_hdr 获取 skb 数据中 arp 头的开始指针*/
arp = arp_hdr(skb);
/*判断设备的类型与数据包中的硬件类型是否相符*/
switch (dev_type) {
default:
if (arp->ar_pro != htons(ETH_P_IP) ||
htons(dev_type) != arp->ar_hrd)
goto out;
break;
case ARPHRD_ETHER:
case ARPHRD_IEEE802_TR:
case ARPHRD_FDDI:
case ARPHRD_IEEE802:
/*
* ETHERNET, Token Ring and Fibre Channel (which are IEEE 802
* devices, according to RFC 2625) devices will accept ARP
* hardware types of either 1 (Ethernet) or 6 (IEEE 802.2).
* This is the case also of FDDI, where the RFC 1390 says that
* FDDI devices should accept ARP hardware of (1) Ethernet,
* however, to be more robust, we'll accept both 1 (Ethernet)
* or 6 (IEEE 802.2)
*/ 
if ((arp->ar_hrd != htons(ARPHRD_ETHER) &&
arp->ar_hrd != htons(ARPHRD_IEEE802)) ||
arp->ar_pro != htons(ETH_P_IP))
goto out;
break;
case ARPHRD_AX25:
if (arp->ar_pro != htons(AX25_P_IP) ||
arp->ar_hrd != htons(ARPHRD_AX25))
goto out;
break;
case ARPHRD_NETROM:
if (arp->ar_pro != htons(AX25_P_IP) ||
arp->ar_hrd != htons(ARPHRD_NETROM))
goto out;
break;
}
/* Understand only these message types */
if (arp->ar_op != htons(ARPOP_REPLY) &&
arp->ar_op != htons(ARPOP_REQUEST))
goto out;
/*
*  Extract fields
*/
/*
获取 arp 数据包中源 mac 地址、源 ip 地址、目的 mac 地址、目的 ip 地址
*/
arp_ptr= (unsigned char *)(arp+1);
sha  = arp_ptr;
arp_ptr += dev->addr_len;
memcpy(&sip, arp_ptr, 4);
arp_ptr += 4;
arp_ptr += dev->addr_len;
memcpy(&tip, arp_ptr, 4);
/*
*  Check for bad requests for 127.x.x.x and requests for multicast
*  addresses. If this is one such, delete it.
*/
/*丢弃目的地址是组播或者 loopback 的 arp 数据(对于组播地址和 loopback
地址是不需要 arp)*/
if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
goto out; 
/*
* Special case: We must set Frame Relay source Q.922 address
*/
if (dev_type == ARPHRD_DLCI)
sha = dev->broadcast;
/*
* Process entry. The idea here is we want to  send a reply if it is a
* request for us or if it is a request for someone else that we hold
* a proxy for. We want to add an entry to our cache if it is a reply
* to us or if it is a request for our address.
* (The assumption for this last is that if someone is requesting our
* address, they are probably intending to talk to us, so it saves time
* if we cache their address. Their address is also probably not in
* our cache, since ours is not in their cache.)
*
* Putting this another way, we only care about replies if they are to
* us, in which case we add them to the cache. For requests, we care
* about those for us and those for our proxies. We reply to both,
* and in the case of requests for us we add the requester to the arp
* cache.
*/
/* Special case: IPv4 duplicate address detection packet (RFC2131) 
*/
/*对于源 ip 地址是 0 的 arp 请求,一般用于重复地址检测,
此时如果 arp 类型为 request,且目的 ip 地址是本地地址,且可以进
行 arp 应答时,
则调用 arp_send 发送 arp reply 数据包。*/
/*
对于源地址为 0 的数据包,在发送 arp 应答报文时,为什么没有先查
找路由表呢?
在我们建立路由表时,都会建立一个全零的默认路由,所以对于目的 ip
为 0 的
数据包,其路由是一直存在的。所以在处理时不用查找路由表,直接生成
arp reply 数据包,并发送出去。
*/
if (sip == 0) {
if (arp->ar_op == htons(ARPOP_REQUEST) &&
inet_addr_type(net, tip) == RTN_LOCAL &&
!arp_ignore(in_dev, sip, tip)) 
arp_send(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha,
dev->dev_addr, sha);
goto out;
}
/*
对于 arp 类型为 request 的数据包,且能找到到目的地址 tip 的路由,则执
行下面的代码
*/
if (arp->ar_op == htons(ARPOP_REQUEST) &&
ip_route_input(skb, tip, sip, 0, dev) == 0) {
/*获取 tip 对应的路由缓存*/
rt = skb_rtable(skb);
addr_type = rt->rt_type;
/*1、如果路由缓存对应的 ip 地址类型为 local,则调用 neigh_event_ns,
查找符合条件的邻居项
a)如果找到符合条件的邻居项,则调用 arp_send 发送对该 arp 
request 包的 reply 包,并返回
b)直接返回。
2、如果路由缓存对应的 ip 地址类型不是 local,则进行 arp proxy
的处理,完成后
直接返回
*/
if (addr_type == RTN_LOCAL) {
int dont_send = 0;
if (!dont_send)
dont_send |= arp_ignore(in_dev,sip,tip);
if (!dont_send && IN_DEV_ARPFILTER(in_dev))
dont_send |= arp_filter(sip,tip,dev);
if (!dont_send) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n) {
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha)
;
neigh_release(n);
}
}
goto out;
} else if (IN_DEV_FORWARD(in_dev)) {
if (addr_type == RTN_UNICAST &&
(arp_fwd_proxy(in_dev, dev, rt) ||
arp_fwd_pvlan(in_dev, dev, rt, sip, tip) || 
pneigh_lookup(&arp_tbl, net, &tip, dev, 0)))
{
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n)
neigh_release(n);
if (NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED ||
skb->pkt_type == PACKET_HOST ||
in_dev->arp_parms->proxy_delay == 0) {
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha)
;
} else {
pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
in_dev_put(in_dev);
return 0;
}
goto out;
}
}
}
/* Update our ARP tables */
/*
1、对于 arp reply 数据包,进入下面的处理流程
2、对于 arp request 数据包,且没有找到 tip ip 对应的路由缓存
*/
/*调用__neigh_lookup,查找 arp_tbl 的 neighbour hash bucket,查找 sip
对应的邻居项*/
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
/*对于系统允许非 arp 请求的 arp reply,则进行如下处理*/
if (IPV4_DEVCONF_ALL(dev_net(dev), ARP_ACCEPT)) {
/* Unsolicited ARP is not accepted by default.
It is possible, that this option should be enabled for some
devices (strip is candidate)
*/
/*1、对于非由 arp 请求的 arp reply,且没有相应的 neighbour,则强
制创建新的 neighbour 
2 、对于 sip 与 tip 相等的 arp  request ,也强 制创 建 新的
neighbour ??
*/
if (n == NULL &&
(arp->ar_op == htons(ARPOP_REPLY) ||
(arp->ar_op == htons(ARPOP_REQUEST) && tip == sip)) &&
inet_addr_type(net, sip) == RTN_UNICAST)
n = __neigh_lookup(&arp_tbl, &sip, dev, 1);
}
/*如果查找到符合条件的 neighbour,则执行如下代码
1、对于发给给本机的 arp reply 报文,则将邻居项设置为 reach 状态
2、对于发给给本机的 arp request 报文,则将邻居项状态设置为 stale 状

最后调用 neigh_update,更新 neighbour 的状态
*/
if (n) {
int state = NUD_REACHABLE;
int override;
/* If several different ARP replies follows back-to-back,
use the FIRST one. It is possible, if several proxy
agents are active. Taking the first reply prevents
arp trashing and chooses the fastest router.
*/
override = time_after(jiffies, n->updated + n->parms->locktime);
/* Broadcast replies and request packets
do not assert neighbour reachability.
*/
if (arp->ar_op != htons(ARPOP_REPLY) ||
skb->pkt_type != PACKET_HOST)
state = NUD_STALE;
neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 
0);
neigh_release(n);
}
out:
if (in_dev)
in_dev_put(in_dev);
consume_skb(skb); 
return 0;
}
在上面的函数里,出现了发送 arp 数据包的函数 arp_send, 下面我们分析
arp_send 与 arp_create
arp_send 就是 arp_create 的封装函数,相比 arp_creare, 增加了判断设备是否
为 NOARP 的设备。
void arp_send(int type, int ptype, __be32 dest_ip,
struct net_device *dev, __be32 src_ip,
const unsigned char *dest_hw, const unsigned char *src_hw,
const unsigned char *target_hw)
{
struct sk_buff *skb;
/*
*  No arp on this interface.
*/
/*
如果该设备不需要 arp,则直接返回
*/
if (dev->flags&IFF_NOARP)
return;
skb = arp_create(type, ptype, dest_ip, dev, src_ip,
dest_hw, src_hw, target_hw);
if (skb == NULL) {
return;
}
arp_xmit(skb);
}
下面分析 arp_create
该函数主要是申请一个缓存,并根据 arp 协议的格式, 创建一个 arp 数据包。该
函数还是比较简单的。
struct sk_buff *arp_create(int type, int ptype, __be32 dest_ip,
struct net_device *dev, __be32 src_ip,
const unsigned char *dest_hw, 
const unsigned char *src_hw,
const unsigned char *target_hw)
{
struct sk_buff *skb;
struct arphdr *arp;
unsigned char *arp_ptr;
/*
*  Allocate a buffer
*/
/*首先调用 alloc_skb,申请缓存空间*/
skb  =  alloc_skb(arp_hdr_len(dev)  +  LL_ALLOCATED_SPACE(dev), 
GFP_ATOMIC);
if (skb == NULL)
return NULL;
/*留出源、目的 mac 地址的空间*/
skb_reserve(skb, LL_RESERVED_SPACE(dev));
/*设置三层头部指针*/
skb_reset_network_header(skb);
/*设置 arp 头指针*/
arp = (struct arphdr *) skb_put(skb, arp_hdr_len(dev));
skb->dev = dev;
skb->protocol = htons(ETH_P_ARP);
/*设置源、目的 mac 地址*/
if (src_hw == NULL)
src_hw = dev->dev_addr;
if (dest_hw == NULL)
dest_hw = dev->broadcast;
/*
*  Fill the device header for the ARP frame
*/
/*通过调用 eth_header,填充二层头部*/
if (dev_hard_header(skb, dev, ptype, dest_hw, src_hw,  skb->len) < 0)
goto out;
/*
* Fill out the arp protocol part.
*
* The arp hardware type should match the device type, except for FDDI,
* which (according to RFC 1390) should always equal 1 (Ethernet).
*/
/*
*  Exceptions everywhere. AX.25 uses the AX.25 PID value not the 
*  DIX code for the protocol. Make these device structure fields.
*/
switch (dev->type) {
default:
/*设置硬件协议类型与软件协议类型,对于 Ethernet 硬件类型为 1 软件
类型为 0x0800*/
arp->ar_hrd = htons(dev->type);
arp->ar_pro = htons(ETH_P_IP);
break;
#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
case ARPHRD_AX25:
arp->ar_hrd = htons(ARPHRD_AX25);
arp->ar_pro = htons(AX25_P_IP);
break;
#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
case ARPHRD_NETROM:
arp->ar_hrd = htons(ARPHRD_NETROM);
arp->ar_pro = htons(AX25_P_IP);
break;
#endif
#endif
#if defined(CONFIG_FDDI) || defined(CONFIG_FDDI_MODULE)
case ARPHRD_FDDI:
arp->ar_hrd = htons(ARPHRD_ETHER);
arp->ar_pro = htons(ETH_P_IP);
break;
#endif
#if defined(CONFIG_TR) || defined(CONFIG_TR_MODULE)
case ARPHRD_IEEE802_TR:
arp->ar_hrd = htons(ARPHRD_IEEE802);
arp->ar_pro = htons(ETH_P_IP);
break;
#endif
}
/*设置硬件协议长度、软件协议长度、arp 包类型*/
arp->ar_hln = dev->addr_len;
arp->ar_pln = 4;
arp->ar_op = htons(type);
/*设置 arp 的源 mac、ip 与目的 mac、ip 地址*/
arp_ptr=(unsigned char *)(arp+1); 
memcpy(arp_ptr, src_hw, dev->addr_len);
arp_ptr += dev->addr_len;
memcpy(arp_ptr, &src_ip, 4);
arp_ptr += 4;
if (target_hw != NULL)
memcpy(arp_ptr, target_hw, dev->addr_len);
else
memset(arp_ptr, 0, dev->addr_len);
arp_ptr += dev->addr_len;
memcpy(arp_ptr, &dest_ip, 4);
return skb;
out:
kfree_skb(skb);
return NULL;
}
对于 arp_send,既可以发送 arp 请求数据包,也可以发送 arp 应答报文,主要是
在 arp_process 中调用。对于应答报文,回复的依据为:
1) 对于重复地址检测请求,则发送一个 arp reply 消息。
2) 对于发往本地的 arp request,则发送一个 arp reply 消息,并将邻居项的
状态设置为 NUD_STALE。
Arp 邻居项的创建以及 arp solicit 请求发送的流程
那对于 arp request 消息是如何发送的呢?
当本地有数据需要发送时, 则会查找路由, 在查找到路由且没有路由缓存时,
则会创建路由缓存,而在创建路由缓存的过程中,就会调用 arp_bind_neighbour,
实现路由缓存与 arp 邻居项的绑定,对于不存在的邻居项,则创建该邻居项,并
将邻居项的状态设置为 NUD_NONE。
此时邻居项的状态还是 NUD_NONE。接着就会执行 ip_output,然后就会调用
到 ip_finish_output2,接着就会调用到 neighbour->output,对于刚创建的邻
居项,其 output 为 neigh_resolve_output。在 neigh_resolve_output 里就会
调用到__neigh_event_send 判断数据包是否可以直接发送出去,如果此时邻居
项的状态为 NUD_NONE,则会将邻居项的状态设置为 NUD_INCOMPLETE,并将要发
送的数据包缓存到邻居项的队列中。而处于 NUD_INCOMPLETE 状态的邻居项的状
态转变会有定时器处理函数来实现。
由以前的分析,我们知道处于 NUD_INCOMPLETE 状态的邻居项,就会调用
neigh->solicit,发送邻居项请求的数据包,对于 arp 来说, 其 neigh->solicit
即为 arp_solicit,在分析之前,首先需要理解 arp announce 的级别
当发送 arp 请求的主机对应的 ip 地址不止一个时,arp announce 级别
决定如何选择 ip 地址
0:任何 ip 地址都可以
1:尽可能选择和目的 ip 处于同一个子网的 ip 地址,否则使用级别 2 的选择
2:优先使用主地址
static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb)
{
__be32 saddr = 0;
u8 *dst_ha = NULL;
struct net_device *dev = neigh->dev; 
__be32 target = *(__be32*)neigh->primary_key;
int probes = atomic_read(&neigh->probes);
/* 获取源设备 ip 层的相关信息*/
struct in_device *in_dev = in_dev_get(dev);
if (!in_dev)
return;
switch (IN_DEV_ARP_ANNOUNCE(in_dev)) {
default:
case 0:    /* By default announce any local IP */
/*判断数据包的源地址是否为本地地址。
若是,则将源地址设置为数据包的源地址;
若不是,则调用 inet_select_addr 选择一个源地址*/
if (skb && inet_addr_type(dev_net(dev), ip_hdr(skb)->saddr) == 
RTN_LOCAL)
saddr = ip_hdr(skb)->saddr;
break;
case 1:    /* Restrict announcements of saddr in same subnet */
/*判断数据包的源地址是否为本地地址。
若是,则优先选择与目的 ip 地址在相同子网上的 ip 地址,否则则调用
inet_select_addr 优先使用主地址*/
if (!skb)
break;
saddr = ip_hdr(skb)->saddr;
if (inet_addr_type(dev_net(dev), saddr) == RTN_LOCAL) {
/* saddr should be known to target */
if (inet_addr_onlink(in_dev, target, saddr))
break; 
}
saddr = 0;
break;
case 2:    /* Avoid secondary IPs, get a primary/preferred one */
/*调用 inet_select_addr 优先获取一个符合条件的主地址*/
break;
}
if (in_dev)
in_dev_put(in_dev);
/*若此时还没有设置源 ip 地址,则调用 inet_select_addr 获取 ip 地址
该函数主要实现的功能
1 、对于指定设备 dev 所关联的 ip 配置块,查找 scope 小于等于
RT_SCOPE_LINK 且与目的地址
属于同一子网的地址作为源 ip 地址
2、对于符合 scope 条件的 ip 地址,若没有子网相同的地址,则选择主地址
作为源
ip 地址
3、对于在指定设备 dev 上找不到满足 scope 条件的 ifaddr 结构,则遍历
所有 dev 设备,找到
符合条件的 ifaddr 结构,并将其主地址作为源 ip 地址。
*/
if (!saddr)
saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);
/*判断 arp 请求报文是否到达上限,若到达上限则不发送*/
if ((probes -= neigh->parms->ucast_probes) < 0) {
if (!(neigh->nud_state&NUD_VALID))
printk(KERN_DEBUG "trying to ucast probe in NUD_INVALID\n");
dst_ha = neigh->ha;
read_lock_bh(&neigh->lock);
} else if ((probes -= neigh->parms->app_probes) < 0) {
#ifdef CONFIG_ARPD
neigh_app_ns(neigh);
#endif
return;
}
/*调用 arp_send 发送 arp 请求包*/
arp_send(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,
dst_ha, dev->dev_addr, NULL);
if (dst_ha)
read_unlock_bh(&neigh->lock);
}。 
刚才我们有提到 arp_bind_neighbour,下面分析一下这个函数:
实现 arp 协议中,neighbour 项与路由缓存中的 dst_entry 表项的绑定
通过下一跳网关地址和 net_dev 为关键字查找一个 neighbour 项
1、若查找到,则将 dst->neighbour 指向该 neighbour 项
2、若没有查找到,则调用 neigh_create 创建一个邻居表项并加入到 arp_table
的邻居表项链表中,并将 dst->neighbour 指向该 neighbour 项
int arp_bind_neighbour(struct dst_entry *dst)
{
struct net_device *dev = dst->dev;
struct neighbour *n = dst->neighbour;
if (dev == NULL)
return -EINVAL;
if (n == NULL) {
__be32 nexthop = ((struct rtable *)dst)->rt_gateway;
if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
nexthop = 0;
n = __neigh_lookup_errno(
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
dev->type == ARPHRD_ATM ? clip_tbl_hook :
#endif
&arp_tbl, &nexthop, dev);
if (IS_ERR(n))
return PTR_ERR(n);
dst->neighbour = n;
}
return 0;
}
至此完成 arp  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值