//创建一个新的邻居项
//参考 深入理解linux网络技术内幕
// 1.邻居子系统为具体的邻居协议,提供通用的功能接口
// 2.系统中所有的邻居协议被链接在neigh_tables链表中
// 3.neigh_table代表一个具体的邻居协议
// 4.具体邻居协议在运行时的行为,可以通过struct neigh_parms调节,
// neigh_params与设备关联,每个邻居协议neigh_table提供一个默认的neigh_params。
//注册一个邻居协议到系统中
// 1.与邻居协议neigh_table有关的定时器:垃圾回收定时器gc_timer,代理定时器proxy_timer
1.1 void neigh_table_init(struct neigh_table *tbl)
{
unsigned long now = jiffies;
unsigned long phsize;
//设置引用计数为1
atomic_set(&tbl->parms.refcnt, 1);
//tbl->parms 的类型为 struct neigh_parms,用于调整邻居协议的行为
INIT_RCU_HEAD(&tbl->parms.rcu_head);
//reachable_time表示一个时间间隔,从上一次收到可到达性认证时间t到t+reachable_time之间,都认为邻居可达
//reachable_time = base_reachable_time/2 + rand(0,base_reachable_time)
tbl->parms.reachable_time =
neigh_rand_reach_time(tbl->parms.base_reachable_time);//
//每个邻居协议私有的neighbour缓存
if (!tbl->kmem_cachep)
tbl->kmem_cachep = kmem_cache_create(tbl->id,
tbl->entry_size,
0, SLAB_HWCACHE_ALIGN,
NULL, NULL);
if (!tbl->kmem_cachep)
panic("cannot create neighbour cache");
//per-cpu统计变量
tbl->stats = alloc_percpu(struct neigh_statistics);
if (!tbl->stats)
panic("cannot create neighbour cache statistics");
//1.邻居项和代理项的hash_buckets以链表指针的形式组织
//2.每一个bucket的链表头为struct neighbour的指针
//3.neighbour通过next域链接在同一个bucket
//邻居项hash表,2个bucket
tbl->hash_mask = 1;
//hash_buckets组织成指针数组的形式,每一个struct neighbour通过next域进行链接
tbl->hash_buckets = neigh_hash_alloc(tbl->hash_mask + 1);
//代理
phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);
tbl->phash_buckets = kmalloc(phsize, GFP_KERNEL);
if (!tbl->hash_buckets || !tbl->phash_buckets)
panic("cannot allocate neighbour cache hashes");
memset(tbl->phash_buckets, 0, phsize);
//hash函数的随机性
get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));
//初始化协议垃圾回收定时器
rwlock_init(&tbl->lock);
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;
add_timer(&tbl->gc_timer);
//代理相关的定时器
init_timer(&tbl->proxy_timer);
tbl->proxy_timer.data = (unsigned long)tbl;
tbl->proxy_timer.function = neigh_proxy_process;
skb_queue_head_init(&tbl->proxy_queue);
tbl->last_flush = now;//neigh_forced_gc最近执行一次的时间
tbl->last_rand = now + tbl->parms.reachable_time * 20;
write_lock(&neigh_tbl_lock);
tbl->next = neigh_tables;//将协议挂接到neigh_tables头部,所有的邻居协议通过neigh_tables列表链接起来
neigh_tables = tbl;
write_unlock(&neigh_tbl_lock);
}
//为邻居协议分配hash表
1.2 static struct neighbour **neigh_hash_alloc(unsigned int entries)
{
unsigned long size = entries * sizeof(struct neighbour *);
struct neighbour **ret;
//大小少于一页
if (size <= PAGE_SIZE) {
ret = kmalloc(size, GFP_ATOMIC);
} else {
//多于一页,分配连续的物理页
ret = (struct neighbour **)
__get_free_pages(GFP_ATOMIC, get_order(size));
}
if (ret)
memset(ret, 0, size);
return ret;
}
//邻居项的创建是由事件驱动的,当系统需要一个邻居项,并且缓存不命中时,就创建一个邻居项
// 创建新邻居项的时机:
// 1.传输请求,当有一个向一台l2地址未知的主机传输请求时,就需要对该地址进行解析,
// 当目的主机不是与发送方直连时,解析的l2地址就是下一条网关的地址
// 2.收到solicitation请求,由于发送请求的主机在请求封包中有自己的识别信息,收到该
// 请求的主机会假定即将有两个系统之间的通信
// 3.手工添加,ip neigh add命令创建一个邻居项
// 创建新邻居项的过程:
// 1.通过slab缓存分配struct neighbour结构
// 2.通过neigh_table->constructor协议提供的初始化函数,初始化新邻居项
// 3.在neigh_table->constructor会设置neighbour->params,使用协议默认提供,或者驱动
// 提供的协议调整参数(neigh_params),通过neighbour->params->setup更新邻居项行为。
// 4.更新邻居项可到达性确认时间戳,使新邻居项比正常邻居项提前半个base_reachable_time到期
// ,从而对该邻居项进行一次主动的可到达性确认
// 5.添加到neigh_table->hash_buckets中,可能会引起hash表的扩容。
2.1 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;
//从邻居协议的neighbour缓存中分配一个邻居项
struct neighbour *n1, *rc, *n = neigh_alloc(tbl);
if (!n) {
rc = ERR_PTR(-ENOBUFS);
goto out;
}
//拷贝hash的key到邻居项中,arp为ip地址
memcpy(n->primary_key, pkey, key_len);
n->dev = dev;//到达该邻居项使用的接口
dev_hold(dev);//增加该接口的引用计数
//如果邻居协议提供了邻居项的初始化函数,则调用
if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
rc = ERR_PTR(error);
goto out_neigh_release;
}
//如果设备驱动提供了初始化邻居项的回调函数,则调用
if (n->parms->neigh_setup &&
(error = n->parms->neigh_setup(n)) < 0) {
rc = ERR_PTR(error);
goto out_neigh_release;
}
//邻居项可到达性确认的时间戳
//当前时间-base_reachable_time/2,已保证新邻居项比正常邻居项提前base_reachable_time/2时间到期,执行可到达性检测
n->confirmed = jiffies - (n->parms->base_reachable_time << 1);
write_lock_bh(&tbl->lock);
//如果协议表中的邻居数大于协议表hash表的长度
if (atomic_read(&tbl->entries) > (tbl->hash_mask + 1))
neigh_hash_grow(tbl, (tbl->hash_mask + 1) << 1);//hash表增加一半当前大小
//使用协议提供的hash函数,对key,dev做hash
hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;//保证最终范围在hash表大小内
if (n->parms->dead) {//如果此邻居项的配置信息标记为不在使用
rc = ERR_PTR(-EINVAL);
goto out_tbl_unlock;//则不添加新邻居项
}
//遍历hash表中的bucket链表
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;
}
}
//将邻居添加到bucket的list中
n->next = tbl->hash_buckets[hash_val];
tbl->hash_buckets[hash_val] = n;
n->dead = 0;//标记此neighbour为可使用
neigh_hold(n);//增加neighbour的引用计数
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;
}
//删除邻居项
// 邻居项被删除的原因:
// 1.内核企图向一个不可到达的主机发送封包,当邻居子系统察觉到传输不成功,
// 就将neighbour标记为NUD_FAILED状态,可以是异步垃圾回收机制清除该neighbour.
// 2.与该邻居结构关联的主机的l2地址改变了,但它的l3地址还是原来的,一个
// 过时的邻居项必须进入NUD_FAILED态,然后重新创建一个新邻居项.
// 邻居项被删除的过程:
// 1.停用与邻居项有关的定时器
// 2.更新与邻居项有关的l2帧头缓存,设置器hh_cache->output为blackhole函数,
// 丢弃通过此l2帧头缓存发送的数据帧,递减hh_cache的引用计数,如果为0,则释放
// 3.调用邻居项提供的删除回调函数,在neigh_table->constructor或者neigh_table->params->setup中
// 被设置。
// 4.清空该邻居项的arp_queue,此队列中缓存的skb为与此邻居相关的,l2地址待解析的skb。
// 5.更新邻居协议的邻居项个数。
3.1 void neigh_destroy(struct neighbour *neigh)
{
struct hh_cache *hh;
//增加协议统计信息,邻居项被删除的次数
NEIGH_CACHE_STAT_INC(neigh->tbl, destroys);
//邻居项没有被标记为不在使用,则说明内核有错BUG()
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");
//该邻居项缓存的l2帧头链表
//从一台主机发往另一台主机的所有封包的l2帧头都是相同的,当向一个目的地发送第一个封包后,
//驱动程序就将其l2帧头保存在hh_cache的结构中,如果下一次有相同的封包发往
//同一个邻居,则只需从缓存中拷贝一个帧头即可,每个邻居项可以缓存多个帧头,但通常只缓存一个。
while ((hh = neigh->hh) != NULL) {
neigh->hh = hh->hh_next;
//将l2地址缓存从列表上摘下来
hh->hh_next = NULL;
write_lock_bh(&hh->hh_lock);
//替换此l2地址的输出函数为neigh_blackhole,以丢弃发往此目的l2地址的所有skb
hh->hh_output = neigh_blackhole;
write_unlock_bh(&hh->hh_lock);
if (atomic_dec_and_test(&hh->hh_refcnt))
kfree(hh);
}
//邻居项提供了删除函数
if (neigh->ops && neigh->ops->destructor)
(neigh->ops->destructor)(neigh);
//清空该邻居项的arp缓存队列
skb_queue_purge(&neigh->arp_queue);
//释放对dev的引用
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);
}
// 查询l3地址对应的邻居项
// 参数:
// tbl,被查询的协议表,arp为arp_tbl
// pkey,查询的key,arp为ip
// dev,到达此邻居项使用的出口设备
4.1 struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n;
int key_len = tbl->key_len;
//hash pkey
u32 hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;
//更新统计信息,递增查询个数
NEIGH_CACHE_STAT_INC(tbl, lookups);
read_lock_bh(&tbl->lock);
//找到对应的bucket头链表
for (n = tbl->hash_buckets[hash_val]; 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;
}
//用于更新一个邻居项
/* Generic update routine.
-- lladdr is new lladdr or NULL, if it is not supplied.
-- new is new state.
-- flags
NEIGH_UPDATE_F_OVERRIDE allows to override existing lladdr,
if it is different.
NEIGH_UPDATE_F_WEAK_OVERRIDE will suspect existing "connected"
lladdr instead of overriding it
if it is different.
It also allows to retain current state
if lladdr is unchanged.
NEIGH_UPDATE_F_ADMIN means that the change is administrative.
NEIGH_UPDATE_F_OVERRIDE_ISROUTER allows to override existing
NTF_ROUTER flag.
NEIGH_UPDATE_F_ISROUTER indicates if the neighbour is known as
a router.
Caller MUST hold reference count on the entry.
*/
//可能的更新策略:
//1.新状态没有可用的l2地址,删除定时器,怀疑邻居项,退出
//2.新状态有可用的l2地址:
// 2.1旧状态有可用l2地址,
// 2.1.1新地址与旧地址不同并且没提供更新标志
// 2.1.1.1 但允许怀疑邻居项并且就状态可达,仍然使用旧地址,标示邻居项需要进行可达性确认
// 2.1.1.2 不允许怀疑邻居项并且就状态不可达,退出
// 2.2新旧地址相同,并且新状态为进行可到达性确认,允许去怀疑或者就状态为可到达,依然保持就状态
//3.新旧状态不同,删除定时器,如果新状态需要定时器,更新定时器。更新邻居状态
//4.新地址与旧地址不同,拷贝新l2地址到邻居项的ha字段,更新l2缓存
//5.如果新旧状态相同,退出
//6.更新邻居项的输出回调函数
//7.如果旧状态没有可用的l2地址
// 7.1 当前状态有可用的l2地址,将arp_queue中的所有skb发送
// 7.2 否则,清空arp_queue
5.1 int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
u32 flags)
{
u8 old;
int err;
...
struct net_device *dev;
int update_isrouter = 0;
//获取此邻居项的锁
write_lock_bh(&neigh->lock);
//此邻居项的状态
dev = neigh->dev;
old = neigh->nud_state;
err = -EPERM;
//如果没有admin,并且就状态为手动设置
if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
(old & (NUD_NOARP | NUD_PERMANENT)))
goto out;//退出
//新状态没有可用的l2地址
if (!(new & NUD_VALID)) {
neigh_del_timer(neigh);//删除邻居项的定时器
if (old & NUD_CONNECTED)//旧状态为可达的
neigh_suspect(neigh);//怀疑邻居项
neigh->nud_state = new;//更新状态为新状态
err = 0;
goto out;
}
//运行到此处,说明有新状态有可用的l2地址,开始检查此地址
//设备没有提供l2地址
if (!dev->addr_len) {
//设置提供的l2地址为邻居项的l2地址
lladdr = neigh->ha;
} else if (lladdr) {//提供了l2地址
//如果旧状态有l2地址
if ((old & NUD_VALID) &&
!memcmp(lladdr, neigh->ha, dev->addr_len))//比较新旧地址
lladdr = neigh->ha;//如果一样的话,lladdr指向邻居的l2的地址
} else {
err = -EINVAL;//没有提供地址
if (!(old & NUD_VALID))//并且旧状态没有可用的l2地址
goto out;//返回错误
lladdr = neigh->ha;
}
//新状态为可达状态
if (new & NUD_CONNECTED)
neigh->confirmed = jiffies;//更新邻居项上一次被确认的时间为当前
neigh->updated = jiffies;//上一次更新时间为当前
err = 0;
update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;//是否允许去更新NTF_ROUTER
if (old & NUD_VALID) {//旧状态有l2地址
if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {//(设备具有l2地址,或者新地址与旧地址不相同) && 没有提供更新地址的指示标志
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;//新状态改变为旧状态
}
}
if (new != old) {//如果新旧状态不同
neigh_del_timer(neigh);//删除定时器
if (new & NUD_IN_TIMER) {
neigh_hold(neigh);//添加新的定时器
neigh->timer.expires = jiffies +
((new & NUD_REACHABLE) ?
neigh->parms->reachable_time : 0);
add_timer(&neigh->timer);
}
neigh->nud_state = new;
}
if (lladdr != neigh->ha) {//新地址与旧地址不同
memcpy(&neigh->ha, lladdr, dev->addr_len);//拷贝新l2地址到邻居项的ha字段
neigh_update_hhs(neigh);//更新l2地址缓存
if (!(new & NUD_CONNECTED))
neigh->confirmed = jiffies -
(neigh->parms->base_reachable_time << 1);
}
if (new == old)//如果新旧状态相同
goto out;
if (new & NUD_CONNECTED)//新状态表示邻居可达
neigh_connect(neigh);//
else
neigh_suspect(neigh);//否则怀疑邻居项
if (!(old & NUD_VALID)) {//如果旧状态没有可用的l2地址
struct sk_buff *skb;
while (neigh->nud_state & NUD_VALID &&//并且新地址为有可用的l2地址
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {//从arp_queue中取出所有等待l2地址解析的skb
struct neighbour *n1 = neigh;
write_unlock_bh(&neigh->lock);
if (skb->dst && skb->dst->neighbour)//如果skb有路由缓存
n1 = skb->dst->neighbour;
n1->output(skb);//则通过邻居项提供的output完成传输
write_lock_bh(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);//否则清空所有待解析l2地址的skb
}
out:
if (update_isrouter) {
neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ?
(neigh->flags | NTF_ROUTER) :
(neigh->flags & ~NTF_ROUTER);
}
write_unlock_bh(&neigh->lock);
#ifdef CONFIG_ARPD
if (notify && neigh->parms->app_probes)
neigh_app_notify(neigh);
#endif
return err;
}
//邻居状态为怀疑状态,即需要可达性确认
5.2 static void neigh_suspect(struct neighbour *neigh)
{
struct hh_cache *hh;
NEIGH_PRINTK2("neigh %p is suspected.\n", neigh);
//更新邻居的输出函数为最通用的输出函数
neigh->output = neigh->ops->output;
//更新所有l2缓存的输出函数为最通用的输出函数
for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->output;
}
//邻居状态为可达状态
5.3 static void neigh_connect(struct neighbour *neigh)
{
struct hh_cache *hh;
NEIGH_PRINTK2("neigh %p is connected.\n", neigh);
//使用设备驱动程序填充l2头的输出方式
neigh->output = neigh->ops->connected_output;
//使用帧头缓存的方式,快速发送报文
for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->hh_output;
}
//当设备的硬件地址发生改变时,调用此函数
//标记使用此设备的邻居项不在使用,并且停用此邻居项的所有定时器
6.1 void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev)
{
int i;
write_lock_bh(&tbl->lock);
//邻居协议表hash表的长度
for (i=0; i <= tbl->hash_mask; i++) {
struct neighbour *n, **np;
//遍历每一个bucket
np = &tbl->hash_buckets[i];
while ((n = *np) != NULL) {
//遍历直到传入设备等于邻居协议所在的设备
if (dev && n->dev != dev) {
np = &n->next;
continue;
}
*np = n->next;
write_lock_bh(&n->lock);
//将邻居标记为dead
n->dead = 1;
//删除邻居的定时器
neigh_del_timer(n);//垃圾回收进程负责处理停用项
write_unlock_bh(&n->lock);
//递减引用计数
neigh_release(n);
}
}
write_unlock_bh(&tbl->lock);
}
//查找代理地址数据库
//pkey为目的ip
//dev为用于代理ip的设备
//creat指示在查找失败时,是否创建
7.1 struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev, int creat)
{
struct pneigh_entry *n;
int key_len = tbl->key_len;
u32 hash_val = *(u32 *)(pkey + key_len - 4);//通用性,对于arp,hash_val=ip
//hash计算
hash_val ^= (hash_val >> 16);
hash_val ^= hash_val >> 8;
hash_val ^= hash_val >> 4;
hash_val &= PNEIGH_HASHMASK;
read_lock_bh(&tbl->lock);
//代理hash表
for (n = tbl->phash_buckets[hash_val]; n; n = n->next) {
//被代理的邻居的l3地址和提供的目标l3地址相同
if (!memcmp(n->key, pkey, key_len) &&
(n->dev == dev || !n->dev)) {//当邻居项放在表示代理hash表中,neighbour->dev的语义就发生了变化
read_unlock_bh(&tbl->lock);//在一般情况下,其表示通过此设备可以访问到这个邻居,转义后,表示,由哪个设备代理此目的ip,到达此目的
goto out;//ip的设备,通过路由表查找得到
}
}
read_unlock_bh(&tbl->lock);
n = NULL;
//没有找到代理邻居项
if (!creat)
goto out;
n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL);
if (!n)
goto out;
//创建邻居项,并添加到hash表中
memcpy(n->key, pkey, key_len);
n->dev = dev;
if (dev)
dev_hold(dev);
if (tbl->pconstructor && tbl->pconstructor(n)) {
if (dev)
dev_put(dev);
kfree(n);
n = NULL;
goto out;
}
write_lock_bh(&tbl->lock);
n->next = tbl->phash_buckets[hash_val];
tbl->phash_buckets[hash_val] = n;
write_unlock_bh(&tbl->lock);
out:
return n;
}
//将代理的solicitation放入延迟队列,延迟处理
8.1 void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
struct sk_buff *skb)
{
unsigned long now = jiffies;
unsigned long sched_next = now + (net_random() % p->proxy_delay);//延时时间
if (tbl->proxy_queue.qlen > p->proxy_qlen) {//延迟队列超过限制
kfree_skb(skb);//直接丢弃封包
return;
}
skb->stamp.tv_sec = LOCALLY_ENQUEUED;//标示入队时间,在出队后,通过此字段可以判断出此skb是否曾经被入过队,防止循环入队
skb->stamp.tv_usec = sched_next;
spin_lock(&tbl->proxy_queue.lock);
if (del_timer(&tbl->proxy_timer)) {//
if (time_before(tbl->proxy_timer.expires, sched_next))
sched_next = tbl->proxy_timer.expires;//设置定时器的到期时间为需要最早被处理solicitation的时间,因此通过一个定时器就可以管理整个队列
}
dst_release(skb->dst);
skb->dst = NULL;
dev_hold(skb->dev);
__skb_queue_tail(&tbl->proxy_queue, skb);//放入tbl->proxy_queue中
mod_timer(&tbl->proxy_timer, sched_next);
spin_unlock(&tbl->proxy_queue.lock);
}
//被动学习
//接收到ARP_REQUEST封包的目的主机从请求封包中也知道了发送方的地址
//该函数会负责检查是否有一个邻居项和请求者关联,然后就会更新这个存在的邻居项,如果不存在,则创建一个新的邻居项
9.1 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);
//如果存在,则更新为NUD_STALE状态,表示有l2地址可用
if (neigh)
neigh_update(neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_OVERRIDE);
return neigh;
}
网络子系统14_邻居子系统通用接口
最新推荐文章于 2023-03-08 17:42:10 发布