邻居子系统 之 更新neigh_update
概述
neigh_update函数用来更新指定的邻居项,更新内容是硬件地址和状态,更新之后,会根据新状态设置其输出函数,CONNECTED状态则使用快速输出,否则使用慢速输出;如果是由原来的无效状态变为现在的有效状态,则需要将数据包缓存队列中的数据包发送出去;
该函数在邻居子系统中被频繁调用;arp模块再收到邻居应答,收到邻居的情况,转发单播代理请求后,会调用该函数更新地址和状态;netlink或者ioctl模块添加或者删除邻居项,也会调用该函数更新地址和状态;
源码分析
/* 更新指定的邻居项,更新内容为硬件地址和状态,新状态有效,并且有缓存包,则发送 */
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
u32 flags, u32 nlmsg_pid)
{
u8 old;
int err;
int notify = ;
struct net_device *dev;
int update_isrouter = ; write_lock_bh(&neigh->lock); dev = neigh->dev;
old = neigh->nud_state;
err = -EPERM; /* 原状态是NOARP或者PERMANENT,必须要求是用户管理员发生的更新 */
if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
(old & (NUD_NOARP | NUD_PERMANENT)))
goto out; /* 已经在销毁状态 */
if (neigh->dead)
goto out; /* 新状态不是有效状态 */
if (!(new & NUD_VALID)) { /* 删除定时器 */
neigh_del_timer(neigh); /* 原状态是已连接状态,更新输出函数 */
if (old & NUD_CONNECTED)
neigh_suspect(neigh);
/* 设置状态 */
neigh->nud_state = new;
err = ;
notify = old & NUD_VALID; /* 原状态为INCOMPLETE或者PROBE,新状态为失败状态 */
if ((old & (NUD_INCOMPLETE | NUD_PROBE)) &&
(new & NUD_FAILED)) {
/* 清空缓存包队列 */
neigh_invalidate(neigh);
notify = ;
}
goto out;
} /* 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;
} /* If entry was valid and address is not changed,
do not change entry state, if new one is STALE.
*/
err = ;
update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER; /* 原状态有效 */
if (old & NUD_VALID) {
/* 地址不同 && 无UPDATE_F_OVERRIDE标记 */
if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {
update_isrouter = ;
/* 有UPDATE_F_WEAK_OVERRIDE状态 && 原状态是连接状态 */
if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&
(old & NUD_CONNECTED)) {
/* 更新硬件地址为邻居项地址 */
lladdr = neigh->ha;
/* 更新状态为STALE */
new = NUD_STALE;
} else
goto out;
}
/* 地址相同或者有UPDATE_F_OVERRIDE标记 */
else {
/* 地址相同&&新状态为STALE&&不是管理员更新,新状态设置为原状态 */
if (lladdr == neigh->ha && new == NUD_STALE &&
!(flags & NEIGH_UPDATE_F_ADMIN))
new = old;
}
} /* Update timestamps only once we know we will make a change to the
* neighbour entry. Otherwise we risk to move the locktime window with
* noop updates and ignore relevant ARP updates.
*/
/* 新旧状态不同或新旧地址不同 */
if (new != old || lladdr != neigh->ha) {
/* 新状态是连接状态,更新确认时间 */
if (new & NUD_CONNECTED)
neigh->confirmed = jiffies;
/* 更新更新时间 */
neigh->updated = jiffies;
} /* 新旧状态不同 */
if (new != old) {
/* 删除定时器 */
neigh_del_timer(neigh); /* 新状态为PROBE,设置未接受到应答计数为0 */
if (new & NUD_PROBE)
atomic_set(&neigh->probes, ); /* 新状态需要定时器,则添加 */
if (new & NUD_IN_TIMER)
neigh_add_timer(neigh, (jiffies +
((new & NUD_REACHABLE) ?
neigh->parms->reachable_time :
)));
/* 设置新状态 */
neigh->nud_state = new;
notify = ;
} /* 新旧地址不同 */
if (lladdr != neigh->ha) {
write_seqlock(&neigh->ha_lock);
/* 拷贝新地址 */
memcpy(&neigh->ha, lladdr, dev->addr_len);
write_sequnlock(&neigh->ha_lock);
/* 更新二层头缓存 */
neigh_update_hhs(neigh); /* 新状态不是连接状态,更新确认时间 */
if (!(new & NUD_CONNECTED))
neigh->confirmed = jiffies -
(NEIGH_VAR(neigh->parms, BASE_REACHABLE_TIME) << );
notify = ;
} /* 新旧状态相同 */
if (new == old)
goto out; /* 新状态为CONNECTED,更新输出函数为connected_out */
if (new & NUD_CONNECTED)
neigh_connect(neigh);
/* 否则,输出函数为output */
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 dst_entry *dst = skb_dst(skb);
struct neighbour *n2, *n1 = neigh;
write_unlock_bh(&neigh->lock); rcu_read_lock(); /* Why not just use 'neigh' as-is? The problem is that
* things such as shaper, eql, and sch_teql can end up
* using alternative, different, neigh objects to output
* the packet in the output path. So what we need to do
* here is re-lookup the top-level neigh in the path so
* we can reinject the packet there.
*/
n2 = NULL;
/* 有路由缓存,则根据路由缓存获取邻居项,有则替换 */
if (dst) {
n2 = dst_neigh_lookup_skb(dst, skb);
if (n2)
n1 = n2;
} /* 输出数据包 */
n1->output(n1, skb); /* 是否引用的邻居项 */
if (n2)
neigh_release(n2);
rcu_read_unlock(); write_lock_bh(&neigh->lock);
} /* 清空数据包缓存队列 */
__skb_queue_purge(&neigh->arp_queue);
neigh->arp_queue_len_bytes = ;
}
out:
if (update_isrouter) {
neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ?
(neigh->flags | NTF_ROUTER) :
(neigh->flags & ~NTF_ROUTER);
}
write_unlock_bh(&neigh->lock); /* 通知其他关心的模块 */
if (notify)
neigh_update_notify(neigh, nlmsg_pid); return err;
}
邻居子系统 之 更新neigh_update的更多相关文章
- 邻居子系统 之 状态定时器回调neigh_timer_handler
概述 在分配邻居子系统之后,会设置定时器来处理那些需要定时器处理的状态,定时器回调函数为neigh_timer_handler:函数会根据状态机变换规则对状态进行切换,切换状态后,如果需要更新输出函数 ...
- 邻居子系统 之 邻居表的初始化neigh_table_init
概述 邻居子系统支持多种实现,例如ARP,ND等,这些实现需要在其初始化的时候,调用neigh_table_init将邻居表项添加到全局邻居子系统数组中,并对实例中的字段(如hash,定时器等)进行相 ...
- 邻居子系统输出 之 neigh_output、neigh_hh_output
概述 ip层在构造好ip头,检查完分片之后,会调用邻居子系统的输出函数neigh_output进行输出,输出分为有二层头缓存和没有两种情况,有缓存时调用neigh_hh_output进行快速输出,没有 ...
- 《深入理解Linux网络技术内幕》阅读笔记 --- 邻居子系统
1.封包从L3至L2的传送过程如下所示: 本地主机的路由子系统选择L3目的地址(下一个跃点). 根据路由表,如果下一个跃点在同一个网络中,邻居层就把目的L3地址解析为跃点的L2地址.这个关联会被放入缓 ...
- 邻居子系统 之 邻居项创建__neigh_create
概述 IP层输出数据包会根据路由的下一跳查询邻居项,如果不存在则会调用__neigh_create创建邻居项,然后调用邻居项的output函数进行输出: __neigh_create完成邻居项的创建, ...
- 邻居子系统 之 邻居项查找neigh_lookup、___neigh_lookup_noref
概述 邻居项查找是通过neigh_lookup相关函数来进行的: ___neigh_lookup_noref,该函数根据输出设备和主键值(IPv4为下一跳ip地址)在邻居项hash表中查找,找到则返回 ...
- 邻居子系统1.5 neigh output
1.5.1 当邻居项不处于NUD_CONNECTD状态时,不允许快速路径发送报文,函数neigh_resolve_output 用于慢而安全的输出,通常用初始化neigh_ops结构 来实例outpu ...
- 邻居子系统 arp 状态图
- 网络子系统48_ip协议数据帧的发送
//ip协议与l4协议接口,l4通过此接口向下l3传递数据帧 //函数主要任务: // 1.通过路由子系统路由封包 // 2.填充l3报头 // 3.ip分片 // 4.计算校验和 // 5.衔接邻居 ...
随机推荐
- 可能是全网最全的http面试答案
HTTP有哪些方法? HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法 HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNE ...
- vue 编译警告 Compiled with 4 warnings
问题原因: windows下盘符的大小写导致的. 我在cmd里运行的时候,是切换到小写,改成大写的E盘符就没问题了
- MySQL的基本操作一
本文主要涉及到的SQL知识点包括CREATE创建数据库和表.INSERT插入数据.SUM()求和.GROUP BY分组.DATE_FORMAT()格式化日期.ORDER BY排序.COUNT()统计行 ...
- MySQL数据库笔记三:数据查询语言(DQL)与事务控制语言(TCL)
五.数据查询语言(DQL) (重中之重) 完整语法格式: select 表达式1|字段,.... [from 表名 where 条件] [group by 列名] [having 条件] [order ...
- 小程序中使用components方法selectComponent遇到的坑 返回为null
前言:哎呦气死了,小程序等着发布审核得时候 发现了一个bug,selectComponent获取不到组件了,返回值一直为null 原因居然是因为 wx:if , 代码如下,无论if是true还是fa ...
- python自学笔记之开源小工具:SanicDB介绍
SanicDB 是为 Python的异步 Web 框架 Sanic 方便操作MySQL而开发的工具,是对 aiomysql.Pool 的轻量级封装.Sanic 是异步IO的Web框架,同时用异步IO读 ...
- 十五,K8S集群调度原理及调度策略
目录 k8s调度器Scheduler Scheduler工作原理 请求及Scheduler调度步骤: k8s的调用工作方式 常用预选策略 常用优先函数 节点亲和性调度 节点硬亲和性 节点软亲和性 Po ...
- Linux 安装 wxPython4.0.4
Ubuntu 18.04 安装 wxPython4.0.4 因为 wxPython4.x 不提供 Linux 下的 bin 文件安装,以下记录 Ubuntu 18.04 的安装过程 (Ubuntu 1 ...
- 开源框架相关面试问题-butterknife注解框架面试问题讲解
butterknife使用简介: 它的出现主要是为了解决咱们在android开发中会写大量的findViewById().setOnClickListener()这样的索然无味的代码,其实它就是一个依 ...
- Android异常与性能优化相关面试问题-内存泄漏面试问题讲解
Java内存泄漏基础知识: Java的内存的分配策略 a.静态存储区:也叫方法区,主要是存放一些静态数据及全局变量等,在程序编译时就已经分配好了,并且在静态存储区中存放的变量在整个程序运行期间都存在. ...