Linux内核报文收发-L3 - Section 3. IP协议、邻居子系统主要是接收、转发和发送三部分
http://zhaozhanxu.com/2016/07/14/Linux/2016-07-14-Linux-Kernel-Pkts_Processing3/
版本说明
Linux版本: 3.10.103
网卡驱动: ixgbe
网络协议注册
inet_init
主要是注册各种协议- 注册TCP协议
proto_register(&tcp_prot, 1)
- 继续注册UDP、RAW、PING
arp_init, ip_init, tcp_init, udp_init, ping_init, icmp_init
dev_add_pack(&ip_packet_type)
主要是注册ip报文处理函数ip_rcv
到pttype_base。arp_init-->dev_add_pack(&arp_packet_type)
主要是注册arp报文处理函数arp_rcv
到pttype_base。
- 注册TCP协议
报文处理
- 网卡调用
__netif_receivve_skb_core
后,会调用deliver_skb(skb, pt_prev, orig_dev)
处理对应的3层协议函数。
ip协议
ip_rcv
主要获取报文头,报文健康检查,最后进入NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,...,ip_rcv_finish)
。ip_rcv_finish
进行路由查找,ip_route_input_noref-->ip_route_input_slow
进行慢速路由判定。ip_route_input_slow
判定报文是本地的话,就给input装载ip_local_deliver
函数,如果不是本地继续调用ip_mkroute_input-->__mkroute_input
查找路由,并且给input装载ip_forward
函数,output装载ip_output
。ip_local_deliver
函数直接进入NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,..., ip_local_deliver_finish)
。ip_local_deliver_finish
调用ipprot->handler
进入4层的协议处理函数。ip_forward
进入NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD,..., ip_forward_finish)
。ip_forward_finish
调用output装载的ip_output
。ip_output
进入NF_HOOK(NFPROTO_IPV4, NF_INET_POST_ROUTING,...,ip_finish_output)
ip_finish_output
进行分片的判断和操作,此处涉及到GSO的判定,最后调用ip_finish_output2
。ip_finish_output2
调用__ipv4_neigh_lookup_noref
进行邻居子系统的表查找,ipv4主要是arp表,查找到arp表,则调用dst_neigh_output-->neigh_hh_output-->dev_queue_xmit
进行报文发送。- 如果没有查找到,则调用
__neigh_create-->arp_constructor
进行发送等函数的装载,最后也调用dst_neigh_output
,然后也调用装载的发送函数,将报文修改为request,调用dev_queue_xmit
进行发送。
arp协议
arp_rcv
健康检查后,进入NF_HOOK(NFPROTO_ARP, NF_ARP_IN, ..., arp_process)
arp_process
完成了所有的处理操作,包括是reply报文则更新arp表;如果是request本地的话,则更新或者创建arp表,并且调用arp_send
回复arp报文;如果不是本地不支持arp proxy的话,丢弃报文;支持的话转发报文。arp_send
调用arp_create
创建报文,并且调用arp_xmit
发送报文。arp_xmit
进入NF_HOOK(NFPROTO_ARP, NF_ARP_OUT, ..., dev_queue_xmit_sk)
,最后调用dev_queue_xmit
发送报文。
转发流程
邻居子系统
- Generic neighbouring interface(VFT),为上层协议提供了一个统一的输出接口
neigh->output()
。 - Generic neighbouring interface,为下层提供的也是一个统一的接口
dev_queue_xmit()
。 - ARP是为IPV4设计的地址解析协议,而ND(neighbour detect)则是为IPv6设计的。这些地址解析协议可以说是“嵌入”在邻居子系统里面,但是又可以自由灵活的拆卸,非常方便。
- 为了加速数据包的发送速度,会将路由表和邻居缓存进行绑定,这个绑定其实就是把邻居缓存中的每个项的结构体嵌入路由表中的一个路由项中,这样报文在查找到路由以后,其实也相当于已经在邻居子系统中查到了缓存,减少了查找的次数。这样在进入邻居子系统后的处理流程就很短。
struct neigh_table
:邻居表,每个地址解析协议就会创建这样的一个表(比如arp)struct neighbour
:邻居项代表一个邻居。邻居项用哈希表+链表链的组织方式struct hh_cache
:这个字段存的就是封装好的二层协议头部,每个报文在进入邻居子系统前都会查找路由,路由项中就会包含这hh_cache
这个结构
状态机
邻居项存在一种状态机,邻居项都有一个对于管理和维护邻居表来说非常重要的成员,nud_state,用来表示该邻居项当前所处的状态。下面依依介绍这几个状态:
- NUD_NONE:邻居项刚建立时处于的状态,在该状态下,还没有硬件地址可以用,所以还不能发送请求报文。一旦有报文要输出到该邻居,便会出发对该邻居硬件地址的请求,进入NUD_INCOMPLETE状态,并缓存发送的报文。
- NUD_INCOMPLETE:该状态是请求报文已发送,但尚未收到应答的状态。该状态下还没解析到硬件地址,因此尚无可用硬件地址,如果有报文要输出到该邻居,会将其缓存起来。这个状态会启动一个定时器,如果在定时器到期时还没有接收到邻居的回应,则会重复发送请求报文,否则发送请求报文的次数打到上限,便会进入NUD_FAILED。
- NUD_REACHABLE:该状态以及得到并缓存了邻居的硬件地址。进入该状态首先设置邻居项相关的output函数(该状态使用neighbors_ops结构的connectd_outpt),然后查看是否存在要发送给该邻居的报文。如果在该状态下闲置时间达到上限,便会进入NUD_STATLE。
- NUD_STATLE:该状态一旦有报文要输出到该邻居,则会进入NUD_DELAY并将该报文输出。如果在该状态下闲置时间达到上限,且此时的引用计数为1,则通过垃圾回收机制将其删除,在该状态下,报文的输出不收限制,使用慢速发送过程。
- NUD_DELAY:该状态下表示NUD_STATE状态下发送的报文已经发出,需得到邻居的可达性确认的状态。在为接收到邻居的应答或确认时也会定时地重发请求,如果发送请求报文的次数到上限,如果收到邻居的应答,进入NUD_REACHABLE,否则进入NUD_FAILED,在该状态下,报文的输出不收限制,使用慢速发送过程。
操作
neigh_create:
创建一个neighbour项结构,当以下情况发生:
- 传输请求:向一台L2地址未知的主机传输请求,就需要对这个地址进行解析。
- 收到solicitation请求:收到这个请求的主机会假定两个系统有通信发生,会创建一个缓存项。
- 手工添加:手工添加一个邻居缓存项。
neigh_release:
这个函数会减少neighbour的引用计数,当引用计数为0时,才真正删除neighbour结构,这个函数真正调用neigh_destroy。以下是函数调用的原因:
- 内核企图向一个不可到达的主机发送报文。
- 与这个邻居结构相关的L2地址改变了。
- 邻居结构存在的时间太长,内核需要它占用的内存。
neigh_update:
- 更新neighbour状态和链路层地址。流程如下:
- 更新状态不是
NUD_VALID
态的邻居发生的变化,主要是状态机和neigh->output。 - 更新L2地址,给状态不是
NUD_VALID
态的邻居使用。
- 更新状态不是
- 设置一个新的链路层地址。
- 改变NUD状态。
- 处理
arp_queue
队列。