包装机器

一文理解K8s容器网络虚拟化

发布时间:2023/3/31 11:42:10   

本文需要读者熟悉Ethernet(以太网)的基本原理和Linux系统的基本网络命令,以及TCP/IP协议族并了解传统的网络模型和协议包的流转原理。文中涉及到Linux内核的具体实现时,均以内核v.9.5版本为准。

一内核网络包接收流程

从网卡到内核协议栈

如图[],网络包到达NC(NetworkComputer,本文指物理机)时,由NIC(NetworkInterfaceController,网络接口控制器,俗称网卡)设备处理,NIC以中断的方式向内核传递消息。Linux内核的中断处理分为上半部(TopHalf)和下半部(BottomHalf)。上半部需要尽快处理掉和硬件相关的工作并返回,下半部由上半部激活来处理后续比较耗时的工作。

具体到NIC的处理流程如下:当NIC收到数据时,会以DMA方式将数据拷贝到RingBuffer(接收队列)里描述符指向的映射内存区域,拷贝完成后会触发中断通知CPU进行处理。这里可以使用ethtool-g{设备名,如eth0}命令查看RX/TX(接收/发送)队列的大小。CPU识别到中断后跳转到NIC的中断处理函数开始执行。此时要区分NIC的工作模式,在早先的非NAPI(NewAPI)[]模式下,中断上半部更新相关的寄存器信息,查看接收队列并分配sk_buff结构指向接收到的数据,最后调用netif_rx()把sk_buff递交给内核处理。在netif_rx()的函数的流程中,这个分配的sk_buff结构被放入input_pkt_queue队列后,会把一个虚拟设备加入poll_list轮询队列并触发软中断NET_RX_SOFTIRQ激活中断下半部。此时中断上半部就结束了,详细的处理流程可以参见net/core/dev.c的netif_rx()-netif_rx_internal()-enqueue_to_backlog()过程。下半部NET_RX_SOFTIRQ软中断对应的处理函数是net_rx_action(),这个函数会调用设备注册的poll()函数进行处理。非NAPI的情况下这个虚拟设备的poll()函数固定指向process_backlog()函数。这个函数将sk_buff从input_pkt_queue移动到process_queue中,调用__netif_receive_skb()函数将其投递给协议栈,最后协议栈相关代码会根据协议类型调用相应的接口进行后续的处理。特别地,这里的enqueue_to_backlog()以及process_backlog()函数也用于和启用了RPS机制后的相关逻辑。

非NAPI(NewAPI)模式下每个网络包的到达都会触发一次中断处理流程,这么做降低了整体的处理能力,已经过时了。现在大多数NIC都支持NAPI模式了。NAPI模式下在首包触发NIC中断后,设备就会被加入轮询队列进行轮询操作以提升效率,轮询过程中不会产生新的中断。为了支持NAPI,每个CPU维护了一个叫softnet_data的结构,其中有一个poll_list字段放置所有的轮询设备。此时中断上半部很简单,只需要更新NIC相关的寄存器信息,以及把设备加入poll_list轮询队列并触发软中断NET_RX_SOFTIRQ就结束了。中断下半部的处理依旧是net_rx_action()来调用设备驱动提供的poll()函数。只是poll()此时指向的就是设备驱动提供的轮询处理函数了(而不是非NAPI模式下的内核函数process_backlog())。这个设备驱动提供的轮询poll()函数最后也会调用__netif_receive_skb()函数把sk_buff提交给协议栈处理。

非NAPI模式和NAPI模式下的流程对比如下(其中灰色底色是设备驱动要实现的,其他都是内核自身的实现):

关于NAPI模式网络设备驱动的实现以及详细的NAPI模式的处理流程,这里提供一篇文章和其译文作为参考[](强烈推荐)。这篇文章很详细的描述了IntelEthernetControllerI50这个NIC设备的收包和处理细节(其姊妹篇发包处理过程和译文[])。另外收包这里还涉及到多网卡的Bonding模式(可以在/proc/net/bonding/bond0里查看模式)、网络多队列(sudolspci-vvv查看Ethernetcontroller的Capabilities信息里有MSI-X:Enable+Count=0字样说明NIC支持,可以在/proc/interrupts里查看中断绑定情况)等机制。这些本文都不再赘述,有兴趣的话请参阅相关资料[5]。

内核协议栈网络包处理流程

前文说到NIC收到网络包构造出的sk_buff结构最终被__netif_receive_skb()提交给了内核协议栈解析处理。这个函数首先进行RPS[5]相关的处理,数据包会继续在队列里转一圈(一般开启了RSS的网卡不需要开启RPS)。如果需要分发包到其他CPU去处理,则会使用enqueue_to_backlog()投递给其他CPU的队列,并在process_backlog())中触发IPI(Inter-ProcessorInterrupt,处理器间中断,于APIC总线上传输,并不通过IRQ)给其他CPU发送通知(net_rps_send_ipi()函数)。

最终,数据包会由__netif_receive_skb_core()进行下一阶段的处理。这个处理函数主要的功能有:

处理ptype_all上所有的packet_type-func(),典型场景是tcpdump等工具的抓包回调(paket_type.type为ETH_P_ALL,libcap使用AF_PACKETAddressFamily)

处理VLAN(VirtualLocalAreaNetwork,虚拟局域网)报文vlan_do_receive()以及处理网桥的相关逻辑(skb-dev-rx_handler()指向了br_handle_frame())

处理ptype_base上所有的packet_type-func(),将数据包传递给上层协议层处理,例如指向IP层的回调ip_rcv()函数

截至目前,数据包仍旧在数据链路层的处理流程中。这里复习下OSI七层模型与TCP/IP五层模型:

在网络分层模型里,后一层即为前一层的数据部分,称之为载荷(Payload)。一个完整的TCP/IP应用层数据包的格式如下[6]:

__netif_receive_skb_core()的处理逻辑中需要

转载请注明:http://www.aideyishus.com/lktp/4042.html

------分隔线----------------------------