ARP邻居模块

一、DARP邻居模块初始化
//IPV4模块初始化
inet_init
......

//ARP邻居模块初始化
arp_init
//邻居表初始化
neigh_table_init(&arp_tbl);
//进行arp邻居表进行初始化
neigh_table_init_no_netlink(tbl);
//将arp邻居表中参数结构对象的引用置为1
atomic_set(&tbl->parms.refcnt, 1);

//初始化参数对象的RCU锁
INIT_RCU_HEAD(&tbl->parms.rcu_head);

//初始化一个随机的reachable_time
tbl->parms.reachable_time =
neigh_rand_reach_time(tbl->parms.base_reachable_time);

//内存池资源
tbl->kmem_cachep =
kmem_cache_create(tbl->id, tbl->entry_size, 
0,SLAB_HWCACHE_ALIGN|SLAB_PANIC,NULL, NULL);

//统计对象
tbl->stats = alloc_percpu(struct neigh_statistics);

//创建Proc条目
tbl->pde = create_proc_entry(tbl->id, 0, proc_net_stat);
tbl->pde->proc_fops = &neigh_stat_seq_fops;
tbl->pde->data = tbl;
//初始时仅创建2个邻居项
tbl->hash_mask = 1;
tbl->hash_buckets = neigh_hash_alloc(tbl->hash_mask + 1);

//初始时创建16个代码邻居项
phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);
tbl->phash_buckets = kzalloc(phsize, GFP_KERNEL);

//获取随机数据?
get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));

//初始化读写锁
rwlock_init(&tbl->lock);

//初始化垃圾回收定时器,垃圾回收定时器的回调函数为
//neigh_periodic_timer
init_timer(&tbl->gc_timer);
tbl->gc_timer.data     = (unsigned long)tbl;
tbl->gc_timer.function = neigh_periodic_timer;
tbl->gc_timer.expires  = now + 1;

//初始化ARP代理定时器
init_timer(&tbl->proxy_timer);
tbl->proxy_timer.data	  = (unsigned long)tbl;
tbl->proxy_timer.function = neigh_proxy_process;

//初始化ARP代理的报文队列
skb_queue_head_init(&tbl->proxy_queue);

//初始化最后刷新时间,用于强制垃圾回收使用
tbl->last_flush = now;
tbl->last_rand	= now + tbl->parms.reachable_time * 20;

//检测当前新加入的邻居表的协议族是否已经存在,当前arp的协议族是ipv4
//,如果已存在一个ipv4协议族类型的邻居表,通常是代码中存在问题,在
//下面内核会进行告警提示。
for (tmp = neigh_tables; tmp; tmp = tmp->next)
if (tmp->family == tbl->family)
break;

//将arp_tbl加载到邻居模块的全局列表neigh_tables中。
tbl->next	= neigh_tables;
neigh_tables	= tbl;

//如果之前已经有对应协议族的邻居表,现在又添加一个,则内核进行
//告警,这种情况如果发生通常是新的邻居类型模块开发时有问题。
if (unlikely(tmp))
printk(KERN_ERR "NEIGH: Registering multiple tables for "
"family %d\n", tbl->family);
dump_stack();

//向ptype_base[]数组中注册三层类型为ARP报文的处理回调,当本地接收到
//ARP报文后由该回调进行ARP报文处理。当前回调函数为arp_rcv。
dev_add_pack(&arp_packet_type);

//创建/proc/net/arp文件,方便用户层读取邻居项信息
arp_proc_init();

//创建/proc/sys/net/ipv4/neigh/default下相关参数文件
neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4,NET_IPV4_NEIGH, "ipv4", 
NULL, NULL);

//向netdev_chain通知链中注册回调,当接口设备模块出现设备注册/解注册,
//UP/DOWN等情况时会触发当前ARP模块设置的回调。该回调目前主要处
//理设备IP地址改变的情况,如果设备IP地址改变后,则停止该邻居项的所有
//定时器使用,同时将邻居项的dead标记为1,如果该邻居项的引用计数大于1
//则需要清除所有待处理的skb报文,同时将邻居项的output回调设置为黑洞
//处理函数,保证后续通过该邻居项的发送报文都直接丢弃。邻居项的nud_state
//也一同修改,递减邻居项的引用计数,当为0时将邻居项进行销毁。
register_netdevice_notifier(&arp_netdev_notifier);

二、发送ARP请求
当本地报文发送时,会调用ip_route_output_flow进行路由模块处理,首先在路由缓存中进行查找,如果缓存中不存在,则进行路由处理,待处理完成后会插入到路由缓存中,在这里会调用arp_bind_neighbour(&rt->u.dst)进行路由缓存条目与arp邻居条目的绑定处理。同时对于发送的单播报文会设置dst.output=ip_output回调,报文发送函数最后会调用dst.output进行报文的发送。单播报文输出过程中,对路由模块处理以后进行分析,当前仅分析输出过程
中和邻居模块相关的两个关键函数arp_bind_neighbour、ip_output。

1、arp_bind_neighbour(&rt->u.dst)
dev = dst->dev;
n = dst->neighbour;		//当前第一次报文发送,路由dst对象还没有绑定邻居对象

if (n == NULL)
//路由条目的下一跳
nexthop = ((struct rtable*)dst)->rt_gateway;

//对于回环设备或者是点到点设备,没有下一跳
if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
nexthop = 0;

//进行邻居项查找,如果没有找到则新建邻居项
n = __neigh_lookup_errno(&arp_tbl, &nexthop, dev);
//搜索当前是否存在此邻居项,这里pkey就是路由条目的下一跳地址
n = neigh_lookup(tbl, pkey, dev);
//换算hash值,当前arp的hash函数为arp_hash,该函数算法传入了三
//个关键参数进行hash换算,三个关键参数分别为pkey、dev->ifindex
//和arp_tbl.hash_rnd,也就是目的IP、输出设备的索引值、和一个随机
//值。
hash_val = tbl->hash(pkey, dev);

//在ARP表中查找是否含有与当前目的地址相同、输出设备相同的邻居
//项,如果找到,则增加引用计数。
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);
break;
return n;

//如果找到则直接返回
if (n)
return n;

//进行新的邻居项创建
neigh_create(tbl, pkey, dev);
//创建新的邻居项
n = neigh_alloc(tbl);
//递增邻居项的计数
entries = atomic_inc_return(&tbl->entries) - 1;

//检测当前邻居项的计数,如果超出了阀值3,则立即进行垃圾回收
//,否则如果超出了阀值2,并且上次强制回收时间已经超过5秒钟
//,则也立即进行垃圾回收。neigh_forced_gc强行回收函数仅将
//引用计数为1,非静态创建的邻居项进行释放。如果强制回收处理
//没有去除任何一个邻居项,同时总条目数已经超出阀值3,则去
//之前递增的总条目数,返回NULL的邻居项。
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_alloc(tbl->kmem_cachep, GFP_ATOMIC);
memset(n, 0, tbl->entry_size);

skb_queue_head_init(&n->arp_queue);

rwlock_init(&n->lock);

//updated表示邻居项最近状态改变时间,used表示邻居项最近
//使用的时间。
n->updated	  = n->used = now;

//默认的邻居状态
n->nud_state	  = NUD_NONE;

n->output	  = neigh_blackhole;	//默认输出回调为将任何包丢弃
//邻居项的参数对象引用tbl表中总的parms,同时将tbl表中总的
	//parms的引用计数递增。
n->parms	  = neigh_parms_clone(&tbl->parms);
atomic_inc(&parms->refcnt);

//每个邻居项都有一个定时器处理回调,根据当前邻居项的状态
//进行对应处理。
init_timer(&n->timer);
n->timer.function = neigh_timer_handler;
n->timer.data	  = (unsigned long)n;

//将邻居项与arp的表进行关联,同时设置引用计数为1,注意
//由于邻居项自身创建时设置引用计数为1,所以将删除时,只要
//判断引用计数为1,就可以进行删除。同时初始邻居项的dead为
//真。
n->tbl		  = tbl;
atomic_set(&n->refcnt, 1);
n->dead		  = 1;

//将arp查询的三层协议地址存储到邻居项中
memcpy(n->primary_key, pkey, key_len);

//将邻居项关联到设备对象
n->dev = dev;
dev_hold(dev);

//调用tbl对象的构建回调,当前arp模块的回调为arp_constructor
tbl->constructor(n)
arp_constructor
addr = *(__be32*)neigh->primary_key;
dev = neigh->dev;

//获取当前三层地址的类型
neigh->type = inet_addr_type(addr);
//这两种情况的特殊地址都归为广播地址
//a、0.x.x.x的地址
//b、最高4位为全1的地址
if (ZERONET(addr) || BADCLASS(addr))
return RTN_BROADCAST;

//多播地址
if (MULTICAST(addr))
return RTN_MULTICAST;

//利用本地路由表进行查找,当前local类型的路由表中
//存储了所有设备接口相关的本地地址及广播地址。
//如果本地路由表中查找失败,则默认为UNICAST地
//地址,即又不是多播地址、又不是本地主机地址或广播
//地址,则为单播地址。
if (ip_fib_local_table)
ret = RTN_UNICAST;

if (!ip_fib_local_table->tb_lookup(ip_fib_local_table,
&fl, &res))
ret = res.type;
fib_res_put(&res);

ret;

//最初在neigh_alloc时,邻居项的参数对象是对arp表对象
//全局参数对象的引用,这里释放对arp表对象中全局参数对象
//的引用,而改为对ipv4设备对象中参数对象的引用。其中ipv4
//设备对象中参数对象的创建是在inetdev_init中,设备对象中
//参数首先从arp表对象参数中进行复制,之后判断如果设备对
//象支持neigh_setup回调,则使用该回调重新设置参数值来替
//换从arp表对象中复制的参数。
in_dev = __in_dev_get_rcu(dev);
parms = in_dev->arp_parms;
__neigh_parms_put(neigh->parms);
neigh->parms = neigh_parms_clone(parms);

//如果设置对象中没有构建二层报文头的回调函数,则表明此设
//备是不需要邻居处理的,所以邻居对象的nud_state状态直接为
//NO_ARP,同时设置该邻居项的操作集为arp_direct_ops,该操
//作集中所有输出相关回调都设置为dev_queue_xmit,即从三层
//输出的数据跳过邻居处理,直接进行QOS队列输出阶段。
if (dev->hard_header == NULL)
neigh->nud_state = NUD_NOARP;
neigh->ops = &arp_direct_ops;
neigh->output = neigh->ops->queue_xmit;
else
//真对特殊的设备类型设置操作集,假设当前为以太网设
//备,则当前设置类型为ARPHRD_ETHER,是在
//ether_setup中设置的。
switch (dev->type)
default:
break;
case ARPHRD_ROSE:
case ARPHRD_AX25:
case ARPHRD_NETROM:
neigh->ops = &arp_broken_ops;
neigh->output = neigh->ops->output;
return 0;

//对于多播类型的报文,不需要ARP,可以根据三层地址
//直接转换为二层地址。
if (neigh->type == RTN_MULTICAST)
neigh->nud_state = NUD_NOARP;

//根据不同设备类型,对多播地址进行二层转换,当前
//仅关注以太网类型设备。
arp_mc_map(addr, neigh->ha, dev, 1);
switch (dev->type)
case ARPHRD_ETHER:
//多播6个字节MAC地址,前3个字节固定
//为0x01005e,后3个字节取三层地址的后23
//位。
ip_eth_mc_map(addr, haddr);
addr=ntohl(naddr);
buf[0]=0x01;
buf[1]=0x00;
buf[2]=0x5e;
buf[5]=addr&0xFF;
addr>>=8;
buf[4]=addr&0xFF;
addr>>=8;
buf[3]=addr&0x7F;

//如果设备为不需要ARP类型,或者为回环设备
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);

//根据设备是否支持硬件头缓存设置不同的处理集,以太
//网类型设备都支持,在ether_setup中会设置
//hard_header_cache回调。但分析当前arp_hh_ops与
//arp_generic_ops处理集的回调,仅connected_output回调
//不同,分别为neigh_connected_output和
//neigh_resolve_output,但仔细对比在
//dev->hard_header_cache条件已知的情况下,好像这两个
//函数的代码处理的流程是一样的。这个点是if分支有什么
//好处呢???
if (dev->hard_header_cache)
neigh->ops = &arp_hh_ops;
else
neigh->ops = &arp_generic_ops;

//NUD_VALID是一个状态集,表示当前状态不需要ARP
//或者已经有了二层地址的情况。对于目前新创建的这个
//点来说,通常就是指当前设备不需要ARP,或者是手工
//创建的静态ARP条目,则设置output回调为
//connected_output,下一步的输出流程会调用邻居项的
//output回调,这里即为直接走到QOS队列输出流程。
if (neigh->nud_state&NUD_VALID)
neigh->output = neigh->ops->connected_output;
//这里需要进行ARP地址解析,当前output的回调函
//数为neigh_resolve_output,下一步的输出流程会调用
//邻居项的output回调,即调用neigh_resolve_output
//进行地址解析。
else
neigh->output = neigh->ops->output;

//当前邻居的参数对象已经为ipv4设备对象中的参数对象,通常ipv4
//设备对象没有设置neigh_setup回调时,是从arp表对象的参数复制
//的,当前是没有neigh_setup回调,所以这里不处理。
if (n->parms->neigh_setup &&(error = n->parms->neigh_setup(n)) < 0)
rc = ERR_PTR(error);
goto out_neigh_release;

//base_reachable_time默认为30秒。
//对于新创建的邻居项,确认时间直接提前流逝60秒,这里的作用主要
//用于被动邻居学习收到ARP请求后(nud_state 从NUD_NOARP迁为
//NUD_STALE),给对端回应ARP应答(nud_state从NUD_STALE迁为
//NUD_DELAY态),之后周期定时器在NUD_DELAY态处理邻居项
//时会根据confirmed最后确认时间很快超时,而立即从NUD_DELAY
//态迁为NUD_PROBE态,触发向对端发出ARP请求而进行被动学习。
n->confirmed = jiffies - (n->parms->base_reachable_time << 1);

//如果当前arp表条目已经超出hash表最大值,则进行扩容,扩大到
//原来的2倍。
if (atomic_read(&tbl->entries) > (tbl->hash_mask + 1))
neigh_hash_grow(tbl, (tbl->hash_mask + 1) << 1);
new_hash = neigh_hash_alloc(new_entries);

old_entries = tbl->hash_mask + 1;
new_hash_mask = new_entries - 1;
old_hash = tbl->hash_buckets;

//每次扩容重新计算hash随机值,邻居项的hash算法使用3个
//关键参数,一个待解析的三层地址、一个是输出设备的ID、另
//一个就是这个随机值,这里变换用于防止恶意的攻击。
get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));

//将老的hash表中所有邻居项都加入到新的hash表中。
for (i = 0; i < old_entries; i++)
for (n = old_hash[i]; n; n = next)
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;

//将老的hash表删除
neigh_hash_free(old_hash, old_entries);
//对新建的邻居项进行hash key计算
hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;

//如果邻居项的参数对象已经标记为dead,则删除当前新建的邻居项,
//已经不再需要了。这里通常是由inetdev_destroy函数将parms->dead
//设置为true,表示当前输出接口设备已经Down。
if (n->parms->dead)
rc = ERR_PTR(-EINVAL);
goto out_tbl_unlock;

//如果当前arp表中已经存在该邻居项,则将已经存在的邻居项引用值
//递增,同时删除当前新建的邻居项。
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;

//将新建的邻居项添加到当前hash桶列表的最前端。
n->next = tbl->hash_buckets[hash_val];
tbl->hash_buckets[hash_val] = n;

//标记当前邻居项已经可用,同时增加引用计数。
n->dead = 0;
neigh_hold(n);

rc = n;
out:
return rc;
out_tbl_unlock:
write_unlock_bh(&tbl->lock);
out_neigh_release:
neigh_release(n);
goto out;

//将新建的邻居项绑定到路由dst对象中。
dst->neighbour = n;

2、ip_output
ip_output
dev = skb->dst->dev;
skb->dev = dev;
skb->protocol = htons(ETH_P_IP);

//触发netfilter模块的POST_ROUTING钩子回调,当前假设规则允许报文通过,
//则调用回调参数ip_finish_output
NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev, 
ip_finish_output, !(IPCB(skb)->flags & IPSKB_REROUTED));
--------------------------------------------------------------------------------------------------------------------
ip_finish_output
//如果当前报文长度大于MTU,同时当前设备不支持GSO特性,则需要进行分片
//处理。
if (skb->len > dst_mtu(skb->dst) && !skb_is_gso(skb))
ip_fragment(skb, ip_finish_output2);
//否则直接调用ip_finish_output2进行输出。
else
ip_finish_output2(skb);
//如果当前skb的head指针与data指针之间的空间无法容纳二层报头,
//同时当前设备支持硬件头的处理,则对分配一个新的skb。
if (unlikely(skb_headroom(skb) < hh_len && dev->hard_header))
skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
if (skb->sk)
skb_set_owner_w(skb2, skb->sk);
kfree_skb(skb);
skb = skb2;

//当一个ARP请求收到应答后,会创建一个硬件缓存对象,同时将该对象
//关联到路由dst对象上,后续从这个路由条目缓存发送的报文都会走此
//流程。
if (dst->hh)
return neigh_hh_output(dst->hh, skb);
//当路由dst对象还没有关联硬件缓存对象中,执行邻居对象的output回调,
//当前邻居对象新创建,假设当前设备为普通以太网设备,需要进行Arp地
//址解析,则在上面调用arp_constructor时,设置的回调函数
//为neigh_resolve_output。
else if (dst->neighbour)
return dst->neighbour->output(skb);
//当前路由条目对象又没有绑定硬件头缓冲、又没有绑定邻居对象,则没
//有办法进行报文发送,将报文丢弃。
kfree_skb(skb);
return -EINVAL;
---------------------------------------------------------------------------------------------------------------------
neigh_resolve_output
//给二层报文留下空间。
__skb_pull(skb, skb->nh.raw - skb->data);

rc = neigh_event_send(neigh, skb)
//当前邻居对象已经使用,则更新最近使用时间点
neigh->used = jiffies;

//CONNECTED、DELAY、PROBE三种状态都表示已经存在可用的二层地址,无
//须进行地址解析。否则才需要走此解析流程。
if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
return __neigh_event_send(neigh, skb);
//这三种状态表明有可用的二层地址,直接返回0,则调用该函数
//的点直接进行数据发送处理。
if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | 
NUD_PROBE))
goto out_unlock_bh;
now = jiffies;

//过滤到上面三种状态,这里再排除这两种状态,那么满足的条件的只
//有NUD_NONE、NUD_FAILED,当前是新建的邻居项,所以当前状态
//为NUD_NONE。
if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE)))
//检测邻居项参数是否允许广播发送或者应用程序arpd发送
if (neigh->parms->mcast_probes + neigh->parms->app_probes)
//初始化邻居项ARP请求次数,默认值为3
atomic_set(&neigh->probes, neigh->parms->ucast_probes);

//邻居项状态迁为INCOMPLETE,详细下面
//对neigh_timer_handler定时器的分析,该定时器处理函数
//如果检测到当前邻居项为INCOMPLETE状态,则会发起
//ARP请求。
neigh->nud_state     = NUD_INCOMPLETE;

//当前状态改变,更新时间戳
neigh->updated = jiffies;

//递增当前邻居对象的引用,当前邻居对象正被定时器使用。
neigh_hold(neigh);	

//触发定时器1秒会运行,该定时器回调函数见下面
//neigh_timer_handler中的分析。
neigh_add_timer(neigh, now + 1);
mod_timer(&n->timer, when)
//如果当前设置参数不允许广播发送邻居请求,或者不允许应用程序
//arpd发送,则状态迁为FAILED,后面由周期性的垃圾回收进行处
//理,当前报文无法发送,直接丢弃。
else
neigh->nud_state = NUD_FAILED;
neigh->updated = jiffies;
if (skb)
kfree_skb(skb);
return 1;
//当前状态为老化态的情况进行处理
else if (neigh->nud_state & NUD_STALE)
//增加邻居对象引用,当前定时器在使用
neigh_hold(neigh);
//当前邻居项状态迁为DELAY
neigh->nud_state = NUD_DELAY;
//记载当状态变迁时间点
neigh->updated = jiffies;
//启用定时器,超时时间默认为5秒,该定时器的超时处理函数详见
//下面neigh_timer_handler定时器的分析
neigh_add_timer(neigh,jiffies + neigh->parms->delay_probe_time);

//待准备进行ARP请求或者已经进行ARP请求,但还未收到回应的这
//个时间段内,需要将待发送到当前三层地址的报文都缓存到当前邻居
//对象的arp_queue列表中,如果ARP解析成功,会检测这个列表是否
//有待发送的数据。
if (neigh->nud_state == NUD_INCOMPLETE)
if (skb)
//默认一个邻居项仅能缓存3个待发送的报文,如果溢出则
//将最早的报文进行丢弃。
if (skb_queue_len(&neigh->arp_queue) >=
neigh->parms->queue_len)
buff = neigh->arp_queue.next;
__skb_unlink(buff, &neigh->arp_queue);
kfree_skb(buff);

//将待发送的报文加入到邻居项arp_queue的尾部。
__skb_queue_tail(&neigh->arp_queue, skb);

//返回1,告诉调用它的函数,不需要继续处理。
rc = 1;

out_unlock_bh:
return rc;

//返回0,表示有二层地址可用,无需解析。
return 0;

//当前已经含有可用的二层地址,可以直接进行报文发送。
if ( rc == 0 )
//如果当前设备支持硬件缓冲,同时当前路由dst对象还没有关联硬件缓冲,则
//走此流程。
if (dev->hard_header_cache && !dst->hh)
//进行硬件缓冲对象构建,同时关联到邻居对象,以及关联到路由dst对象
neigh_hh_init(neigh, dst, dst->ops->protocol);
//在当前邻居项中查找是否已经存在此硬件缓冲对象。
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);
//当前为ETH_P_IP,即ipv4
hh->hh_type = protocol;
atomic_set(&hh->hh_refcnt, 0);
hh->hh_next = NULL;

//假设当前为以太网设备,则在ether_setup中进行设置,当前回调
//函数为eth_header_cache
dev->hard_header_cache(n, hh)
//构建以太网类型的硬件缓冲对象
eth_header_cache
eth = (struct ethhdr *)(((u8 *) hh->hh_data) + 
(HH_DATA_OFF(sizeof(*eth))));
eth->h_proto = type;
memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
memcpy(eth->h_dest, neigh->ha, dev->addr_len);
hh->hh_len = ETH_HLEN;

//当前邻居对象引用该硬件缓冲对象,将硬件缓冲对象的引用计数递
//增,同时将硬件缓冲对象加入到邻居对象的hh列表中。
atomic_inc(&hh->hh_refcnt);
hh->hh_next = n->hh;
n->hh	    = hh;
//根据邻居对象的状态,设置硬件缓冲对象的hh_output回调。
//这里CONNECTED是一个复合状态,如果状态为
//PERMANENT或NOARP或REACHABLE,则都归为CONNECTED
//状态类型,这里会设置回调为dev_queue_xmit。
if (n->nud_state & NUD_CONNECTED)
hh->hh_output = n->ops->hh_output;
//不是上述状态,则回调为neigh_resolve_output,进行慢速处理。
else
hh->hh_output = n->ops->output;

if (hh)
//增加硬件缓冲对象的引用,此时该对象被邻居条目及路由dst对象
//都进行了引用。
atomic_inc(&hh->hh_refcnt);
//将硬件缓冲对象关联到路由dst对象中。
dst->hh = hh;

//使用设备对象的hard_header进行二层报文填充。
dev->hard_header(skb, dev, ntohs(skb->protocol),neigh->ha, NULL, skb->len);
else
//使用设备对象的hard_header进行二层报文填充。
dev->hard_header(skb, dev, ntohs(skb->protocol),neigh->ha, NULL, skb->len);

//当前假设设备对象为普通以太网类型,需要进行arp解析,同时设备支持硬件
//缓冲,则当前回调为dev_queue_xmit
neigh->ops->queue_xmit(skb);
//进行数据包发送,此时发送流程经过邻居模块,走到QOS队列流程。此
//处分析可以参见《接口设备发包》
dev_queue_xmit

三、接收ARP请求
Arp模块在arp_init初始化时向内核ptype_base[]数组中注册三层类型为ARP报文的处理回调,当本地接收到ARP报文后由该回调进行ARP报文处理,当前回调函数为arp_rcv。

arp_rcv
//校验接收的ARP报文的长度是否合法
if (!pskb_may_pull(skb, (sizeof(struct arphdr) +(2 * dev->addr_len) +(2 * sizeof(u32)))))
goto freeskb;

arp = skb->nh.arph;

//如果出现以下情况之一,都为非法报文。
//1、如果arp报文中硬件地址长度字段与接收设备的地址长度不匹配
//2、如果当前设备已经标记为不需要进行ARP处理
//3、如果当前报文非本地报文,即目的MAC地址不是当前设备的。
//4、如果当前报文是发送环回设备,环回设备是不需要进行ARP处理的。
//5、如果arp报文中协议地址长度不是ipv4类型地址长度。
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;

//如果当前skb是共享的,则需要复制
skb = skb_share_check(skb, GFP_ATOMIC))

//清除skb.cb控制块,该控制块可以在不同层次处理时,临时保留自己需要的信息。
memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));

//经过ARP netfilter模块的ARP_IN钩子链,假设当前规则都通过,则触发回调函数
//arp_process
NF_HOOK(NF_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);
---------------------------------------------------------------------------------------------------------------------
arp_process
arp = skb->nh.arph;

//根据不同设备类型进行校验处理,当前仅关注以太网类型。
switch (dev_type)
case ARPHRD_ETHER:
//如果ARP报文中硬件类型、协议类型字段不匹配以太网、IPv4,则为非法。
if ((arp->ar_hrd != htons(ARPHRD_ETHER) &&arp->ar_hrd != 
htons(ARPHRD_IEEE802)) ||arp->ar_pro != htons(ETH_P_IP))
goto out;

//当前只支持请求和应答操作码,其它非法。
if (arp->ar_op != htons(ARPOP_REPLY) &&arp->ar_op != htons(ARPOP_REQUEST))
goto out;

//从报文中提取源二三层地址、目的二三层地址。
arp_ptr= (unsigned char *)(arp+1);
sha	= arp_ptr;
arp_ptr += dev->addr_len;
memcpy(&sip, arp_ptr, 4);
arp_ptr += 4;
tha	= arp_ptr;
arp_ptr += dev->addr_len;
memcpy(&tip, arp_ptr, 4);

//目的地址为环回地址或多播地址都为非法。
if (LOOPBACK(tip) || MULTICAST(tip))
goto out;

//这种设备类型的特殊处理,不清楚。
if (dev_type == ARPHRD_DLCI)
sha = dev->broadcast;

//如果源IP为全0,这种ARP请求是特殊情况,作用是为了检测重复IP地址探测,
//DHCP服务端程序在进行IP地址分配时,都会先进行重复IP地址探测,没有冲突
//地址后才进行分配。
if (sip == 0)
//如果收到的ARP请求的目的地址就是本机使用的地址,则给远端发送单播的
//ARP响应报文,通知对端这个IP地址冲突,本机已经在使用。
if (arp->ar_op == htons(ARPOP_REQUEST) &&inet_addr_type(tip) == 
RTN_LOCAL &&!arp_ignore(in_dev,dev,sip,tip))
arp_send(ARPOP_REPLY,ETH_P_ARP,tip,dev,tip,sha,dev->dev_addr,
dev->dev_addr);

goto out;

//对接收到的arp请求进行处理,进行输入路由处理,主要进行一些反向路径检测,以
//及判断出当前报文是三层是否是本地报文。
if (arp->ar_op == htons(ARPOP_REQUEST) &&ip_route_input(skb, tip, sip, 0, dev) 
== 0)
rt = (struct rtable*)skb->dst;
addr_type = rt->rt_type;

//如果当前报文是发给本地的
if (addr_type == RTN_LOCAL)
//当收到ARP请求后,除了上面分析的源IP地址是全0地址情况外,通常
//除了给对端回应请求,帮助对端完成ARP学习之外,本端也会进行被动
//ARP学习。
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
//这里开始进行被动ARP学习的初步处理,首先使用从ARP请求报文
//中源地址信息(即对端的信息)进行邻居条目查找,如果不存在,则
//需要进行创建。被动ARP学习的状态变迁为NUD_NONE-->
//NUD_STALE-->NUD_DELAY-->NUD_PROBE(发起ARP请求)
//-->NUD_REACHABLE(收到响应),即并不是直接就认为对端是可用
//的。
neigh = __neigh_lookup(tbl, saddr, dev,lladdr || !dev->addr_len);
//根据两个关键信息,当前接收设备及远端地址进行ARP的邻居查
//找。
n = neigh_lookup(tbl, pkey, dev);

//如果查找成功,或者没有查找失败但指示不需要创建新项则返回
if (n || !creat)
return n;

//查找失败,但要求创建新项,则进行创建。在函数在上面已经分析
//过,可以参考上面的分析。可以留意一下这里新创建的邻居项的
//confirmed最后确认时间会提前进行流逝设置,这里主要用于被
//动学习后,状态迁为NUD_STALE,刚本地给对端发送报文使用该
//邻居项后迁为NUD_DELAY,此时邻居周期定时器在处理DELAY
//状态时会根据confirmed的流逝时间来觉定是否切到PROBE态来
//立即向远端发送ARP请求。
neigh_create(tbl, pkey, dev);

if (neigh)
//将新建的邻居项状态迁为NUD_STALE
neigh_update(neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_OVERRIDE);
//如果老的状态为NUD_NOARP或NUD_PERMANENT,则
//必须当前是通过命令行触发的操作来允许更新。
if (!(flags & NEIGH_UPDATE_F_ADMIN) && 
(old & (NUD_NOARP | NUD_PERMANENT)))
goto out;

//如果新的状态为不含有效二层地址的状态,则停止当前邻居项
//的定时器,同时如果之前老的状态确认含有二层地址的状态,
//则需要将邻居项及硬件缓冲对象的output回调设置为慢速发
//送回调。
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;
goto out;

//如果当前设备不需要二层地址,则这里lladdr取值从邻居项取。
if (!dev->addr_len)
lladdr = neigh->ha;
//如果当前设备需要使用二层地址、同时传入参数也给了更新
//地址,则检测之前老的状态是否是含有可用的二层地址的状
//态,如果是则对比传入的地址是否和已经使用的地址相同,
//如果相同则直接使用邻居项的地址。
else if (lladdr)
if ((old & NUD_VALID) && !memcmp(lladdr, neigh->ha, 
dev->addr_len))
lladdr = neigh->ha;
//如果当前设备需要使用二层地址,但传入参数没有给出更新地
//址,当老的状态为没有可用二层地址的状态时,返回错误。
else 
err = -EINVAL;
if (!(old & NUD_VALID))
goto out;
lladdr = neigh->ha;

//当新的状态为NUD_PERMANENT或NUD_NOARP或
//NUD_REACHABLE时,更新最后确认时间。
if (new & NUD_CONNECTED)
neigh->confirmed = jiffies;

//更新最后更新时间。
neigh->updated = jiffies;

err = 0;

//提取当前是否允许进行isrouter标记覆盖的指示
update_isrouter = flags & 
NEIGH_UPDATE_F_OVERRIDE_ISROUTER;

//之前老的状态为有可用二层地址的状态
if (old & NUD_VALID)
//如果当前二层地址有变更,同时传入参数又不允许进行覆
//盖。
//1、检测传入参数允许轻度覆盖,同时老的状态之前是
//触发已确认状态,则lladdr还是指向老的二层地址,仅
//将新的状态变迁为老化态NUD_STALE。
//2、否则直接退出。
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
//如果二层地址没有变更,同时传入的新的状态为老化
//态时,如果满足如下两个条件任意一个,则状态维持
//原来的状态。
//1、传入标记指示轻度覆盖。
//2、老的状态是确认态。
if (lladdr == neigh->ha && new == NUD_STALE &&
((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) 	||(old & NUD_CONNECTED))
new = old;

//状态有变迁
if (new != old)
//停止当前邻居项的定时器
neigh_del_timer(neigh);

//如果新的状态含有定时器处理,则激活邻居定时器,
//定时器的超时时间取决于当前新的状态是否是
//REACHABLE,如果是则延时reachable_time时间,
//否则立即触发。
if (new & NUD_IN_TIMER)
neigh_hold(neigh);
neigh_add_timer(neigh, 
(jiffies + ((new & NUD_REACHABLE) ? 
neigh->parms->reachable_time :0)));

neigh->nud_state = new;	//状态变迁

//如果二层地址有变更,则将新的二层地址覆盖掉邻居项中
//老的二层地址,同时将邻居项及路由dst条目引用的硬件
//缓冲对象的二层地址也同步更新。如果当前新的状态并不是
//确认态,则将confirmed最后确认时间提前流失两倍的
//base_reachable_time时间,这里作用如下,比如一个ARP
//请求,通常状态会变迁为老化态,如果此时二层地址有变
//更,则提前流失确认时间可以加速老化,使得异步垃圾回
//收机制可以快速将处于老化态并且没有被使用的邻居项收回。
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;

//根据新状态是否是确认态,变更邻居项及关联的硬件缓存对
//象的output函数。
if (new & NUD_CONNECTED)
neigh_connect(neigh);
else
neigh_suspect(neigh);

//当前的状态如果没有可用的二层地址,则在上面已经退出了,
//走到这里表明新的状态有可用的二层地址,这里判断当老的
//状态如果是没有可用二层地址态时,则检测之前邻居项是否
//含有待发送的报文,有则将报文进行发送,同时删除邻居项
//存储在队列中的报文。
if (!(old & NUD_VALID))
while (neigh->nud_state & NUD_VALID &&
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL)
n1 = neigh;
if (skb->dst && skb->dst->neighbour)
n1 = skb->dst->neighbour;
n1->output(skb);
skb_queue_purge(&neigh->arp_queue);

out:

//传入参数允许更新isrouter标记,则进行标记更新。
if (update_isrouter)
neigh->flags = 
(flags & NEIGH_UPDATE_F_ISROUTER) ?
(neigh->flags | NTF_ROUTER) :
(neigh->flags & ~NTF_ROUTER);

//如果上面处理项需要通知,则给注册当前通知链的模块发送
//邻居项更新通知。
if (notify)
call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, 
neigh);

//有应用层ARPD处理程序,则给该程序发送通知,未分析。
if (notify && neigh->parms->app_probes)
neigh_app_notify(neigh);
int dont_send = 0;

if (!dont_send)
dont_send |= arp_ignore(in_dev,dev,sip,tip);
//根据配置参数arp_ignore来确认是否忽略对此ARP请求的应答
switch (IN_DEV_ARP_IGNORE(in_dev))
//默认为0,不忽略
case 0:
return 0;
//要求ARP请求的地址必须为之前接收接口的地址。
case 1:
sip = 0;
scope = RT_SCOPE_HOST;

//除了要求ARP请求地址必须为之前接收接口的地址,还要求ARP
//请求中的源地址必须和本地接口为同一网段。
case 2:
scope = RT_SCOPE_HOST;

//要求只要ARP请求的地址不是主机范围类型(如127.0.0.1)的都
//允许。
case 3:
sip = 0;
scope = RT_SCOPE_LINK;
dev = NULL;

//这些值预留,如果配置为这些值,则认为允许。
case 4:
case 5:
case 6:
case 7:
return 0;

//总是不允许
case 8:
return 1;

//根据1、2、3配置类型进行有效性确认。
return !inet_confirm_addr(dev, sip, tip, scope);

//根据配置参数arp_filter来确认是否进行过滤处理。
if (!dont_send && IN_DEV_ARPFILTER(in_dev))
//将源地址与目的地址对调,进行反方向输出路由查找,检测接收的设备
//是否与发送的设备相同,如果不同则禁止回应ARP响应。
dont_send |= arp_filter(sip,tip,dev);
fl = { .nl_u = { .ip4_u = { .daddr = sip,.saddr = tip } } };

if (ip_route_output_key(&rt, &fl) < 0)
return 1;

if (rt->u.dst.dev != dev)
flag = 1;

ip_rt_put(rt);
return flag;

if (!dont_send)
//如果上述限制条件都通过,则给对端回应ARP请求,发送ARP响应。
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,
sha);

neigh_release(n);

//当前收到的ARP请求的三层地址并非本地地址,则进行ARP代理处理。首先
//判断配置项是否设置了允许转发。
else if (IN_DEV_FORWARD(in_dev))
//如果当前路由条目为DNAT标记,则允许进行ARP代理处理,当前代码
//在decnet网络源码中有涉及,没有去分析。
//或者如果报文是单播类型、输出设备与输入设备不同,并且当前开启
//了ARP代理配置,当前输入设备与输出设备的媒介ID不同,则允许
//进行ARP代理处理。
//或者如果报文是单播类型、输出设备与输入设备不同,ARP代理地址
//表中有需要进行处理的地址,则允许进行ARP代理处理。
if ((rt->rt_flags&RTCF_DNAT) ||
(addr_type == RTN_UNICAST  && rt->u.dst.dev != dev &&
(arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, &tip, dev, 0))
//反向ARP学习,该函数在上面已经分析过了。
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);

//如果存储ARP请求方的邻居项,则进行释放。
if (n)
neigh_release(n);

//如下3种情况,则直接进行ARP代理请求回应。
//1、之前该ARP请求项已经放入ARP代理延迟队列,定时器已到,可
//以进行回应。
//2、这个ARP请求报文就是发向本端的。
//3、当前配置ARP代理延时参数为0。
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);
//其它情况下,需要将当前ARP请求延时一段时间再进行回应
else
pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
now = jiffies;
sched_next = now + (net_random() % p->proxy_delay);

//队列已满,当前ARP请求丢弃。默认值为64
if (tbl->proxy_queue.qlen > p->proxy_qlen)
kfree_skb(skb);
return;

//记载下一次调度时间,并设置LOCALLY_ENQUEUED标
//记。
NEIGH_CB(skb)->sched_next = sched_next;
NEIGH_CB(skb)->flags |= LOCALLY_ENQUEUED;

//如果当前ARP代理定时器已经运行,则先停止。对比已经
//运行的定时器超时时间是否比较近,如果是,则更新下次
//调度时间仍为原来的值。
if (del_timer(&tbl->proxy_timer))
if (time_before(tbl->proxy_timer.expires, sched_next))
sched_next = tbl->proxy_timer.expires;

//删除路由DST对象引用。
dst_release(skb->dst);
skb->dst = NULL;
//增加对设备对象的引用。
dev_hold(skb->dev);

//将当前ARP请求报文添加到ARP代理队列中。
__skb_queue_tail(&tbl->proxy_queue, skb);

//启动ARP代理定时器,该定时器的回调函数为
//neigh_proxy_process,超时后会调用arp_process
//将队列中的ARP报文给予回应。
mod_timer(&tbl->proxy_timer, sched_next);

in_dev_put(in_dev);

	return 0;
goto out;

四、接收ARP响应
前半部分同接收ARP请求的处理流程相同,这里仅分析后部分和ARP响应相关的处理。

arp_rcv
arp_process
......

//查询之前创建的邻居项
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);

//如果当前收到了ARP响应,但之前没有对该地址发出ARP请求,则检测
//当前配置是否允许,如果允许,则创建新的邻居项。
if (ipv4_devconf.arp_accept)
if (n == NULL &&arp->ar_op == htons(ARPOP_REPLY) &&
inet_addr_type(sip) == RTN_UNICAST)
n = __neigh_lookup(&arp_tbl, &sip, dev, -1);

//当前已经含有该邻居项
if (n)
int state = NUD_REACHABLE;

//有可能发出去一个ARP请求后,收到多个响应,这里有一个锁定时
//间限制,收到第1个响应开始,这段时间内如果又收到其它的响应
//则在进行邻居项更新时不能覆盖。默认时间为1秒。
override = time_after(jiffies, n->updated + n->parms->locktime);

//如果当前接收的不是ARP响应,或者当前收到的报文不是发给本地的,
//,则状态需要切换到STALE,否则切换到REACHABLE。
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);

五、单个邻居项周期性定时器处理函数
neigh_timer_handler
state = neigh->nud_state;
now = jiffies;
next = now + HZ;

//当前状态为已经可达
if (state & NUD_REACHABLE)
//如果在最近确认时间点之后还没有超过reachable_time时间,则将定时器下一周
//期的时间设置为最近确认时间点加上reachable_time时间,reachable_time时间
//默认为15秒~45秒之间。
If (time_before_eq(now, neigh->confirmed + neigh->parms->reachable_time))
next = neigh->confirmed + neigh->parms->reachable_time;
//如果最近确认时间已经超过reachable_time,但使用时间没有超过
//delay_probe_time,当前默认为5秒,则迁为DELAY态。
else if (time_before_eq(now, neigh->used + neigh->parms->delay_probe_time))
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;

neigh_suspect(neigh);
//将邻居项的output设置为output,启动慢速发送,当前回调函数为
//neigh_resolve_output
neigh->output = neigh->ops->output;

//将邻居项的所有关联硬件缓冲对象的hh_output设置为output,启动
//慢送发送。
for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->output;

//定时器下一周时间修改为5秒。
next = now + neigh->parms->delay_probe_time;
//如果最近确认时间已经超过reachable_time,同时最近使用时间也超过
//delay_probe_time,则迁为STALE态。
else
neigh->nud_state = NUD_STALE;
neigh->updated = jiffies;
neigh_suspect(neigh);
notify = 1;
//当前态为延时态,等待收到使用该邻居项
else if (state & NUD_DELAY)
//如果最近确认时间未超过delay_probe_time,则表明收到可达性的确认,迁
//为REACHABLE态。当前默认时间为5秒。
if (time_before_eq(now, neigh->confirmed + neigh->parms->delay_probe_time))
neigh->nud_state = NUD_REACHABLE;
neigh->updated = jiffies;

//到达REACHABLE,邻居对象的输出回调设置为neigh_resolve_output
neigh_connect(neigh);
neigh->output = neigh->ops->connected_output;

//硬件缓冲对象的输出回调设置为dev_queue_xmit可以加快输出。
for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->hh_output;

notify = 1;

//定时器更新为15秒~45秒。
next = neigh->confirmed + neigh->parms->reachable_time;
//最近确认时间也超过delay_probe_time,迁为PROBE态,触发ARP请求。
else
neigh->nud_state = NUD_PROBE;
neigh->updated = jiffies;
atomic_set(&neigh->probes, 0);
//定时器更新为重传时间,默认1秒。
next = now + neigh->parms->retrans_time;
//其它状态情况下,当前为PROBE或INCOMPLETE,仅更新定时器为重传时间。
else
next = now + neigh->parms->retrans_time;

//如果在INCOMPLETE状态或者PROBE都需要发送arp请求,如果此时发送次
//数已经超出设置的最大的值,是状态迁为FAILED
if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) &&
atomic_read(&neigh->probes) >= neigh_max_probes(neigh))
neigh->nud_state = NUD_FAILED;
neigh->updated = jiffies;
notify = 1;

//向当前邻居项中arp_queue所有存储的待发送的数据的四层协议触发错误通知。
while (neigh->nud_state == NUD_FAILED &&(skb = 
__skb_dequeue(&neigh->arp_queue)) != NULL)
neigh->ops->error_report(neigh, skb);

//释放到当前邻居项中所有待发送的报文
skb_queue_purge(&neigh->arp_queue);

//当前NUD_IN_TIMER为复合状态,包含INCOMPLETE、REACHABLE、
//DELAY、PROBE4个中任意一个,表示有定时器相关的状态。修改定时器的时间,
//同时对小于500ms的值修正为500ms。
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);

//这两种状态则需要发送Arp请求。
if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE))
//从当前邻居项的队列中引用第一个报文,这里并不从队列中删除。
skb = skb_peek(&neigh->arp_queue);

//对报文对象的引用递增
if (skb)
skb_get(skb);

//发送Arp请求,这里回调函数为arp_solicit
neigh->ops->solicit(neigh, skb);
arp_solicit
probes = atomic_read(&neigh->probes);
in_dev = in_dev_get(dev);

//根据系统配置的arp_announce值进行源地址选择,默认值为0
switch (IN_DEV_ARP_ANNOUNCE(in_dev))
default:
case 0:
//当前报文的源地址为本地任何接口地址,则选择它是源地址
if (skb && inet_addr_type(skb->nh.iph->saddr) == RTN_LOCAL)
saddr = skb->nh.iph->saddr;
break;

case 1:
//当前报文的源地址与待解析的目的地址为同一网段才进行选择。
saddr = skb->nh.iph->saddr;
if (inet_addr_type(saddr) == RTN_LOCAL)
if (inet_addr_onlink(in_dev, target, saddr))
break;
saddr = 0;
break;
case 2:
//直接进行默认选择
break;

if (in_dev)
in_dev_put(in_dev);

//在上面arp_announce为1、2种类型没有匹配成功,或者选择了第3种
//类型,则进行默认选择。
if (!saddr)
//这里要注意地址范围这个概念,常见的地址范围值包括
//RT_SCOPE_UNIVERSE=0、RT_SCOPE_LINK=253、
//RT_SCOPE_HOST=254,分别指全球任意点、链路网络、本地,
//值越大表示范围越近,通常在对接口进行IP地址配置时,如果没
//有刻意指定范围,则为UNIVERSE,像127.0.0.1环回接口地址则
//为HOST。
//这里第3个参数为LINK,表示在进行地址选择时,地址范围不能
//大于LINK,即忽略127.0.0.1这类的地址。该函数算法思路如下:
//1、优先根据当前给定的dev,选择当前dev地址与target为同一
//网段的。
//2、如果没有同一网段的,则选择给定dev最后一个范围小于LINK
//的地址。
//3、如果又没有同一网段的,给定设备又不含范围小于LINK的地址。
//则遍历当前系统所有设备列表,选择第一个设备地址可以满足范围
//小于LINK的地址。
saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);

//如果当前ARP请求次数还未超过ucast_probes(默认为3),则选择
//二层报文的目的地址为邻居项的地址,当前邻居项的地址还未解析,所
//以即使从邻居项中取,dst_ha还是0。
if ((probes -= neigh->parms->ucast_probes) < 0)
dst_ha = neigh->ha;
//否则如果ARP请求次数已经超过ucast_probes,但还可以使用应用层
//ARPD进行请求尝试,则使用ARPD进行处理。
else if ((probes -= neigh->parms->app_probes) < 0)
neigh_app_ns(neigh);
return;

//进行ARP请求发送。
arp_send(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,dst_ha, 
dev->dev_addr, NULL);
//进行ARP请求报文构造,这里由于dst_ha的址址当前为0,
//所以构造的ARP请求报文的二层链路目的地址为广播地址。
skb = arp_create(type, ptype, dest_ip, dev, src_ip,dest_hw, src_hw,
 target_hw);

//进行arp请求报文发送
arp_xmit(skb);
//这里可以看到针对ARP也有单独的一套netfilter钩子机制
//,这里假设当前ARP_OUT链的规则项都通过,则触发回
//调函数dev_queue_xmit。dev_queue_xmit的分析详见
//《接口设备发包》
NF_HOOK(NF_ARP, NF_ARP_OUT, skb, NULL, skb->dev, 
dev_queue_xmit);

//如果无需再使用,则删除
if (skb)
kfree_skb(skb);

//上面处理过程中,如果标记需要进行通知,则往通知链发送邻居更新消息。
if (notify)
call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, neigh);

//如果当前有应用层ARPD进程在处理,则给该进程发送消息,这里没有分析。
if (notify && neigh->parms->app_probes)
neigh_app_notify(neigh);

//每当邻居项进行处理时,外部函数都会在启动定时器的同时,增加一个邻居项的引
//用计数,这里定时器处理完后需要将引用递减。
neigh_release(neigh);

六、周期性的垃圾回收
neigh_periodic_timer
//5分钟更新一次所有邻居项参数对象的到达时间
if (time_after(now, tbl->last_rand + 300 * HZ))
tbl->last_rand = now;
for (p = &tbl->parms; p; p = p->next)
p->reachable_time =neigh_rand_reach_time(p->base_reachable_time);

//hash_chain_gc中记载了上一次进行垃圾回收的hash桶,一次仅处理一个hahs桶
np = &tbl->hash_buckets[tbl->hash_chain_gc];
tbl->hash_chain_gc = ((tbl->hash_chain_gc + 1) & tbl->hash_mask);

while ((n = *np) != NULL)
state = n->nud_state;

//如果邻居项是命令行创建的静态类型,或者当前邻居项触于含定时器的状态,
//则忽略。
if (state & (NUD_PERMANENT | NUD_IN_TIMER))
goto next_elt;

//更新使用时间为最后确认时间
if (time_before(n->used, n->confirmed))
n->used = n->confirmed;

//当前邻居项没有被引用时,以下2种条件任意1种满足都需要被回收
//1、当前邻居项状态为FAILED
//2、当前邻居项的最后使用时间已经超出老化时间,默认是1分钟
if (atomic_read(&n->refcnt) == 1 &&(state == NUD_FAILED ||
time_after(now, n->used + n->parms->gc_staletime)))
*np = n->next;
n->dead = 1;
neigh_release(n);
continue;

next_elt:
np = &n->next;

//下个周期时间为 (base_reachable_time / 2) / (hash_mask + 1)
expire = tbl->parms.base_reachable_time >> 1;
expire /= (tbl->hash_mask + 1);
if (!expire)
expire = 1;

//重置垃圾回收定时器
mod_timer(&tbl->gc_timer, now + expire);

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值