赵占旭的博客

Linux内核报文收发-L3

版本说明


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。

avatar

报文处理


  • 网卡调用__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发送报文。

转发流程

avatar

邻居子系统


avatar

  • Generic neighbouring interface(VFT),为上层协议提供了一个统一的输出接口neigh->output()
  • Generic neighbouring interface,为下层提供的也是一个统一的接口dev_queue_xmit()
  • ARP是为IPV4设计的地址解析协议,而ND(neighbour detect)则是为IPv6设计的。这些地址解析协议可以说是“嵌入”在邻居子系统里面,但是又可以自由灵活的拆卸,非常方便。
  • 为了加速数据包的发送速度,会将路由表和邻居缓存进行绑定,这个绑定其实就是把邻居缓存中的每个项的结构体嵌入路由表中的一个路由项中,这样报文在查找到路由以后,其实也相当于已经在邻居子系统中查到了缓存,减少了查找的次数。这样在进入邻居子系统后的处理流程就很短。

avatar

struct neigh_table :邻居表,每个地址解析协议就会创建这样的一个表(比如arp)
struct neighbour :邻居项代表一个邻居。邻居项用哈希表+链表链的组织方式
struct hh_cache :这个字段存的就是封装好的二层协议头部,每个报文在进入邻居子系统前都会查找路由,路由项中就会包含这hh_cache这个结构

状态机

avatar

邻居项存在一种状态机,邻居项都有一个对于管理和维护邻居表来说非常重要的成员,nud_state,用来表示该邻居项当前所处的状态。下面依依介绍这几个状态:

  1. NUD_NONE:邻居项刚建立时处于的状态,在该状态下,还没有硬件地址可以用,所以还不能发送请求报文。一旦有报文要输出到该邻居,便会出发对该邻居硬件地址的请求,进入NUD_INCOMPLETE状态,并缓存发送的报文。
  2. NUD_INCOMPLETE:该状态是请求报文已发送,但尚未收到应答的状态。该状态下还没解析到硬件地址,因此尚无可用硬件地址,如果有报文要输出到该邻居,会将其缓存起来。这个状态会启动一个定时器,如果在定时器到期时还没有接收到邻居的回应,则会重复发送请求报文,否则发送请求报文的次数打到上限,便会进入NUD_FAILED。
  3. NUD_REACHABLE:该状态以及得到并缓存了邻居的硬件地址。进入该状态首先设置邻居项相关的output函数(该状态使用neighbors_ops结构的connectd_outpt),然后查看是否存在要发送给该邻居的报文。如果在该状态下闲置时间达到上限,便会进入NUD_STATLE。
  4. NUD_STATLE:该状态一旦有报文要输出到该邻居,则会进入NUD_DELAY并将该报文输出。如果在该状态下闲置时间达到上限,且此时的引用计数为1,则通过垃圾回收机制将其删除,在该状态下,报文的输出不收限制,使用慢速发送过程。
  5. 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队列。

arp例子

avatar

注意:所有文章非特别说明皆为原创。为保证信息与源同步,转载时请务必注明文章出处!谢谢合作 :-)

原始链接:http://zhaozhanxu.com/2016/07/14/Linux/2016-07-14-Linux-Kernel-Pkts_Processing3/

许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。