K8S网络(个人学习)


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

一、内核网络包接收流程

1、从网卡到内核协议栈

在这里插入图片描述
如图[1],网络包到达 NC(Network Computer,本文指物理机)时,由 NIC(Network Interface Controller,网络接口控制器,俗称网卡)设备处理,NIC 以中断的方式向内核传递消息。Linux 内核的中断处理分为上半部(Top Half)和下半部(Bottom Half)。上半部需要尽快处理掉和硬件相关的工作并返回,下半部由上半部激活来处理后续比较耗时的工作。

具体到 NIC 的处理流程如下:当 NIC 收到数据时,会以 DMA 方式将数据拷贝到 Ring Buffer (接收队列) 里描述符指向的映射内存区域,拷贝完成后会触发中断通知 CPU 进行处理。这里可以使用 ethtool -g {设备名,如eth0} 命令查看 RX/TX (接收/发送)队列的大小。CPU 识别到中断后跳转到 NIC 的中断处理函数开始执行。此时要区分 NIC 的工作模式,在早先的非 NAPI(New API)[2]模式下,中断上半部更新相关的寄存器信息,查看接收队列并分配 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(New API)模式下每个网络包的到达都会触发一次中断处理流程,这么做降低了整体的处理能力,已经过时了。现在大多数 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 模式的处理流程,这里提供一篇文章和其译文作为参考[3](强烈推荐)。这篇文章很详细的描述了 Intel Ethernet Controller I350 这个 NIC 设备的收包和处理细节(其姊妹篇发包处理过程和译文[4])。另外收包这里还涉及到多网卡的 Bonding 模式(可以在/proc/net/bonding/bond0 里查看模式)、网络多队列(sudo lspci -vvv 查看 Ethernet controller 的 Capabilities信息里有 MSI-X: Enable+ Count=10 字样说明 NIC 支持,可以在 /proc/interrupts 里查看中断绑定情况)等机制。

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

前文说到 NIC 收到网络包构造出的 sk_buff 结构最终被 __netif_receive_skb() 提交给了内核协议栈解析处理。这个函数首先进行 RPS[5] 相关的处理,数据包会继续在队列里转一圈(一般开启了 RSS 的网卡不需要开启 RPS)。如果需要分发包到其他 CPU 去处理,则会使用 enqueue_to_backlog() 投递给其他 CPU 的队列,并在 process_backlog()) 中触发 IPI(Inter-Processor Interrupt,处理器间中断,于 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_PACKET Address Family)

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

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

截至目前,数据包仍旧在数据链路层的处理流程中。这里复习下 OSI 七层模型与 TCP/IP 五层模型:
在这里插入图片描述
在网络分层模型里,后一层即为前一层的数据部分,称之为载荷(Payload)。一个完整的 TCP/IP 应用层数据包的格式如下:
在这里插入图片描述
__netif_receive_skb_core() 的处理逻辑中需要关注的是网桥和接下来 IP 层以及 TCP/UDP 层的处理。首先看 IP 层,__netif_receive_skb_core() 调用 deliver_skb(),后者调用具体协议的 .func() 接口。对于 IP 协议,这里指向的是 ip_rcv() 函数。这个函数做了一些统计和检查之后,就把包转给了 Netfilter [7]框架并指定了函数 ip_rcv_finish() 进行后续的处理(如果包没被 Netfilter 丢弃)。经过路由子系统检查处理后,如果包是属于本机的,那么会调用 ip_local_deliver() 将数据包继续往上层协议转发。这个函数类似之前的逻辑,依旧是呈递给 Netfilter 框架并指定函数 ip_local_deliver_finish() 进行后续的处理,这个函数最终会检查和选择对应的上层协议接口进行处理。

常见的上层协议比如 TCP 或者 UDP 协议的流程不在本文讨论的范围内,仅 TCP 的流程所需要的篇幅足以超过本文所有的内容。这里给出 TCP 协议(v4)的入口函数 tcp_v4_rcv() 以及 UDP 协议的入口函数 udp_rcv() 作为指引自行研究,也可以阅读其他的资料进行进一步的了解[9]。

3、Netfilter/iptables 与 NAT(网络地址转换)

关于 Netfilter 框架需要稍微着重的强调一下,因为后文要提到的网络策略和很多服务透出的实现都要使用 Netfilter 提供的机制。
Netfilter 是内核的包过滤框架(Packet Filtering Framework)的实现。简单说就是在协议栈的各个层次的包处理函数中内置了很多的 Hook 点来支持在这些点注册回调函数。
在这里插入图片描述
图片来自 Wikimedia,可以点开参考文献[8]查看大图(svg 矢量图,可以调大网页显示百分比继续放大)。

Linux 上最常用的防火墙 iptables 即是基于 Netfilter 来实现的(nftables 是新一代的防火墙)。iptables 基于表和链(Tables and Chains)的概念来组织规则。注意这里不要被“防火墙”这个词误导了,iptables 所能做的不仅仅是对包的过滤(Filter Table),还支持对包进行网络地址转换(NAT Table)以及修改包的字段(Mangle Table)。在网络虚拟化里,用的最多的便是 NAT 地址转换功能。通常此类功能一般在网关网络设备或是负载均衡设备中很常见。当 NC 需要在内部进行网络相关的虚拟化时,也是一个类似网关以及负载均衡设备了。

在设置 iptables 的 NAT 规则前,还需要打开内核的包转发功能 echo “1” > /proc/sys/net/ipv4/ip_forward 才可以。另外建议也打开 echo “1” /proc/sys/net/bridge/bridge-nf-call-iptables 开关(可能需要 modprobe br_netfilter)。bridge-nf-call-iptables 从上面的源码分析就能理解,网桥的转发处理是在 Netfilter 规则之前的。所以默认情况下二层网桥的转发是不会受到三层 iptables 的限制的,但是很多虚拟化网络的实现需要 Netfilter 规则生效,所以内核也支持了让网桥的转发逻辑也调用一下 Netfilter 的规则。这个特性默认情况不开启,所以需要检查开关。至于具体的 iptables 命令,可以参考这篇文章和其译文[10]进行了解,本文不再讨论。

这里强调下,Netfilter 的逻辑运行在内核软中断上下文里。如果 Netfilter 添加了很多规则,必然会造成一定的 CPU 开销。下文在提到虚拟化网络的性能降低时,很大一部分开销便是源自这里。

二、虚拟网络设备

在传统的网络认知里,网络就是由带有一个或多个 NIC 的一组 NC 使用硬件介质和 switch(交换机)、Router(路由器)所组成的一个通信集合(图片来自 [11],下同):

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值