本文主要是介绍linux网络协议栈(四)链路层 (3)邻居子系统ARP,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
4.3、邻居子系统+ARP:
4.3.1、什么是邻居:
所谓邻居就是二层直连的两个主机,如A与B直连或者A与B通过二层交换机连接,都是邻居。邻居子系统的作用是就是实现L3地址和L2地址的映射关系。
邻居子系统本身只实现一个通用架构,具体实现按照具体的L3协议和L2协议确定,如对于IPV4/ethernet,ARP协议就是邻居子系统的实现内容,对于IPV6/ethernet则是ND协议,对于其他的L3协议和L2协议还会有其他的协议。事实上理解了邻居子系统,也就理解了ARP或其他L2.5协议。
在详细描述邻居子系统之前,首先仔细感受下它到底的用途是什么,在输入报文需要转发,以及输出报文的处理,在其路由缓存条目的路由结果dst_entry中的output方法都会设置为ip_output函数,output方法是离开L3层的处理方法,也就是说无论输入报文需要转发还是输出报文,都要调用ip_output函数离开L3层进入L2层,该函数逻辑如下图:
注意记录出接口的操作,这是非常重要的一个容易被忽略的地方,接下来是函数ip_finish_output,它的主要任务是处理分帧,关于分帧详见第五章,接下来它会调用函数ip_finish_output2,在这里将根据路由缓存条目绑定的邻居(未绑定邻居无法发送)的情况,选择发送方式,首次发送将选择慢速方式发送,之后则按快速方式发送,如下图:
路由结果dst_entry的hh,回看5.3.2.2节中,在实际把路由缓存条目加入路由缓存表时对调用函数arp_bind_neighbour创建邻居条目,即dst->neighbour,所以第一次发送时调用dst->neighbour->output方法发送,这是慢速发送。
上面的内容重点不在细节,而是感受到邻居系统的作用:由路由缓存条目绑定的邻居条目,确定发送报文的目的MAC,并调用相应的发送方法发送报文。事实上理解了这一点已经差不多够了,邻居子系统的细节是为了更好的理解链路层处理。
由前面已经知道,邻居子系统的作用是绑定L3地址和L2地址的对应关系,最终发送报文必须知道给哪个主机的链路层发送,典型如以太网,在二层是按MAC地址转发,所以必须确定目的地的MAC地址,否则报文无法发到目的地,而所谓邻居,就是那么目的地,作为终端的主机可能没有多少邻居,但可以看下作为路由器网关的设备,如下图是192.168.99.1的一个路由器网关设备的arp缓存情况:
实际条目是上图的10倍以上,可以感受到邻居数目的众多。
那么ARP和邻居是什么关系?不同的链路层协议使用不同的L2.5层协议,而邻居子系统是一个通用的子系统,为所有类似ARP的L2.5层协议封装了一个通用架构。邻居子系统不仅仅提供最基本的二三层地址绑定关系,还实现通用的处理机制,如邻居条目状态机、用户接口、超时机制等,其他L2.5层协议如ARP都是在邻居子系统架构下实现。
4.3.2、邻居子系统详述:
邻居子系统最核心的内容是邻居状态机,在讨论邻居状态机之前首先描述一些必要的内容:
1、邻居表
每个L2.5层协议都会调用函数neigh_table_init在邻居子系统中注册自己的邻居表,以管理自己的邻居条目,对于ARP协议是全局变量arp_tbl;
2、邻居条目:
目前无需理解全部字段的含义,只需知道如下信息:
每个路由缓存条目都会绑定的邻居条目就是这样的一个数据结构,其中的邻居的目的MAC地址是struct hh_cache结构体成员hh,
对应的接口是成员dev,
发送报文的方法还是就是其中的output成员,
每个存在着的邻居条目都会处在邻居子系统状态机的某一个状态,由nud_state指示,
如果发送了请求后,无法正常收到邻居的应答,会触发邻居子系统的重传机制,成员probes记录已重传次数,
若当前无法发送报文则会把报文先缓存在队列arp_queue中,
primary_key存储的是路由缓存条目的IP地址。4.3.2.1、邻居子系统状态机:
了解上述内容后,下图是邻居子系统状态机:
1、在创建新的邻居条目,这是在创建新的路由缓存条目时调用arp_bind_neighbour函数创建的邻居条目,创建邻居条目最终会调用函数neigh_create,无需过于纠缠其实现细节,重点要知道在此处把路由缓存条目中的下一跳网关成员rt_gateway作为IP地址赋给邻居条目的primary_key成员,把输出接口成员dev赋给邻居条目的dev成员,初始状态为NUD_NONE;
2、收到了ARP请求报文,并且是发给自己的(通过路由信息的路由类型字段),那么状态变为NUD_STALE,解析出对方的IP地址和MAC地址,创建这个发送者的邻居条目;
3、收到了ARP请求报文,但不是发给自己的(通过路由信息的路由类型字段),这就是ARP代理(事实上这是邻居子系统通用功能,所有L2.5层协议都支持L2.5层代理),路由器网关必须行使的功能,状态变为NUD_STALE,它将把报文放入邻居代理队列(proxy_queue),启动代理队列定时器,该定时器处理将给发送者发送回应,即行使ARP代理功能;
4、收到了ARP回复报文,并且是发给自己的(通过报文的类型字段skb->pkt_type),那么状态升为NUD_REACHABLE;
5、在NUD_NONE情况下,发送报文,由于路由结果dst_entry中还没有邻居(ARP)缓存,即hh_cache成员还未赋值,属于第一次发送,由函数neigh_resolve_output处理,将按慢速发送处理,所谓慢速发送就是要首先确保所要发送的邻居条目有效(由函数neigh_event_send确定是否有效)然后才发送,如果有效,那么将在路由缓存条目中缓存二层包头并填充skb的二层包头并发送,从此路由缓存结果dst_entry中就记录了邻居缓存即目的MAC地址,从此以后就可以走快速发送方式,即直接由邻居缓存中记录的目的MAC地址填充报文并立即发送,注意发送函数是dev_queue_xmit,它将走到相应的网卡驱动;状态升为NUD_INCOMPLETE,与此同时,将启动定时器,检查是否收到了回复;
6、在NUD_INCOMPLETE情况下,只有在NUD_NONE状态下发送报文才会进入此状态,并且同时启动了定时器,邻居定时器处理(neigh_timer_handler)将会检查状态依然是NUD_INCOMPLETE (因为收到回复是异步的,比如arp_process收到给本机的回复则走第4步,状态就会升为NUD_REACHABLE)的邻居条目,检查其重传次数是否到限,限制值是由邻居条目创建时的邻居参数决定的(不是重点),若到限则状态降为NUD_FAILED,否则继续重传;
7、在NUD_INCOMPLETE情况下,如果在重传次数到限之前收到了邻居的回复,则状态升为NUD_REACHABLE,并且把MAC地址记录在路由缓存条目的hh_cache中,标记本邻居条目今后可走快速发送路径,则报文发送时直接由路由缓存条目的hh_cache填充包头发送;
8、在第2、3步中由于接受到ARP请求所以创建邻居条目并且状态为NUD_STALE,在定时器处理中,检查其是否超时(闲置时间过长),若超时则状态降为NUD_FAILED;
9、在NUD_STALE下,如果要发送ARP请求,则状态报文NUD_DELAY,如果超时时间内未收到回复则状态报文NUD_PROBE即重传状态,否则状态升为NUD_REACHABLE;
10、在NUD_REACHABLE状态下,如果闲置时间过长(老化),该邻居条目状态降为NUD_STALE;
11、在NUD_REACHABLE状态下,发送ARP请求,状态变为NUD_DELAY,如果超时时间内未收到回复则状态报文NUD_PROBE即重传状态,否则状态依然为NUD_REACHABLE;
12、在重传状态NUD_PROBE下,如果超时并且重传次数到限,则状态降为NUD_FAILED,如果收到了邻居的回复,则状态升为NUD_REACHABLE,如果超时但重传次数还未到限,并且也未收到邻居的回复,则继续重传,状态不变;
13、在NUD_FAILED状态下,这里的邻居条目都将被删除;
4.3.3、ARP:
ARP是用于L3为IPV4和L2为以太网的L2.5层协议,它绑定的是IPV4地址和MAC地址,注意ARP条目不是永远存在,超时会老化掉,否则需要保存的ARP缓存条目太多了。
所有ARP报文按照以太网类型0x0806注册在内核的链路层处理中(proc/net/ptype),其hook处理函数为arp_rcv,该函数在进行ARP处理前netfilter之后调用arp_process,这是ARP报文的实际处理的地方,事实上该函数就是在根据收到的ARP报文的类型调用相关的邻居子系统方法,创建/删除/更新邻居条目及其状态,行使包括ARP代理在内的ARP协议功能;ARP报文发送接口函数为arp_send,在构造arp报文之后调用arp_xmit,它将进行ARP处理后netfilter之后调用dev_queue_xmit实际发送报文。
在proc文件系统中可通过查看proc/net/arp查看本机当前的arp缓存条目,对于IPV4和以太网的设备来说事实上就是邻居条目。
下图是结合ARP的邻居子系统的流程图:
关于ARP子系统的一些细节,后续再补充,个人认为其细节不是重点,对于ARP或者说对于邻居子系统重在理解原理和解决一些可能出现的问题,后续对ARP防控多做总结
这篇关于linux网络协议栈(四)链路层 (3)邻居子系统ARP的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!