一:邻居子系统概述
邻居子系统是从物理来说是指在同一个局域网内的终端。从网络拓扑的结构来说,是指他们之间相隔的距离仅为一跳,他们属于同一个突冲域 邻居子系统的作用:它为第三层协议与第二层协议提供地址映射关系。提供邻居头部缓存,加速发包的速度
二:邻居子系统在整个协议栈的地位
发送数据的时候,要在本机进行路由查找,如果有到目的地地址的路径,查看arp缓存中是否存在相应的映射关系,如果没有,则新建邻居项。判断邻居项是否为可用状态。如果不可用。把skb 存至邻居发送对列中,然后将发送arp请求。如果接收到arp应答。则将对应邻居项置为可用。如果在指定时间内末收到响应包,则将对应邻居项置为无效状态。如果邻居更改为可用状态,则把邻居项对应的skb对列中的数据包发送出去
三:流程概述;
发包流程。下面以包送udp数据为例,看看是怎么与邻居子系统相关联的Sendmsg() à ip_route_output()(到路由缓存中查找目的出口)à ip_route_output_slow( 如果缓存中不存在目的项,则到路由表中查找) à ip_build_xmit() à output_maybe_reroute à skb->dst->output()如果至时找到了路由,则根据路由信息分配个dst_entry,并调用arp_bind_neighbour为之绑定邻居 output指针赋值为ip_output 转到执行ip_output ip_output à __ip_finish_output() -à ip_finish_output2() à dst->neighbour->output()现在就转至邻居项的出口函数了。
所谓邻居就是二层直连的两个主机,如A与B直连或者A与B通过二层交换机连接,都是邻居。邻居子系统的作用是就是实现L3地址和L2地址的映射关系。
邻居子系统本身只实现一个通用架构,具体实现按照具体的L3协议和L2协议确定,如对于IPV4/ethernet,ARP协议就是邻居子系统的实现内容,对于IPV6/ethernet则是ND协议,对于其他的L3协议和L2协议还会有其他的协议。事实上理解了邻居子系统,也就理解了ARP或其他L2.5协议。
那么ARP和邻居是什么关系?不同的链路层协议使用不同的L2.5层协议,而邻居子系统是一个通用的子系统,为所有类似ARP的L2.5层协议封装了一个通用架构。邻居子系统不仅仅提供最基本的二三层地址绑定关系,还实现通用的处理机制,如邻居条目状态机、用户接口、超时机制等,其他L2.5层协议如ARP都是在邻居子系统架构下实现。
邻居子系统最核心的内容是邻居状态机,每个L2.5层协议都会调用函数neigh_table_init在邻居子系统中注册自己的邻居表,以管理自己的邻居条目,对于ARP协议是全局变量arp_tbl;每个路由缓存条目都会绑定的邻居条目就是这样的一个数据结构,其中的邻居的目的MAC地址是struct hh_cache结构体成员hh,nbm对应的接口是成员dev,发送报文的方法还是就是其中的output成员,每个存在着的邻居条目都会处在邻居子系统状态机的某一个状态,由nud_state指示,如果发送了请求后,无法正常收到邻居的应答,会触发邻居子系统的重传机制,成员probes记录已重传次数,若当前无法发送报文则会把报文先缓存在队列arp_queue中,primary_key存储的是路由缓存条目的IP地址。
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状态下,这里的邻居条目都将被删除;
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实际发送报文。