找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2073

积分

0

好友

290

主题
发表于 2025-12-30 06:36:26 | 查看: 20| 回复: 0

为了清晰起见,我们以UDP数据包为例,描述一个数据包在物理网卡上的接收与发送过程,并尽量忽略一些无关的细节。

数据包接收流程

从网卡到内存

每个网络设备(网卡)都需要一个驱动程序来工作,该驱动会在内核启动时被加载。从逻辑上看,驱动程序是连接网络设备和内核网络协议栈的桥梁。当网卡接收到一个新数据包时,它会触发一个硬件中断,而处理这个中断的程序,正是加载到内核中的网卡驱动。

下图详细展示了数据包从网络设备进入系统内存,并由驱动和协议栈处理的过程。

从网卡DMA到软中断的数据包接收流程图
图1:数据包从网卡DMA到内存,再通过硬中断和软中断触发内核处理的完整流程。

  1. 数据包进入物理网卡。如果数据包的目标地址不是该网卡,且网卡未开启混杂模式,数据包会被直接丢弃。
  2. 物理网卡通过 DMA 技术,将数据包直接写入由驱动程序预先分配好的内存缓冲区。
  3. 数据写入完成后,网卡通过硬件中断(IRQ)通知CPU有新数据包需要处理。
  4. CPU根据中断向量表,调用驱动程序注册的中断处理函数。
  5. 驱动首先禁用网卡的中断。这相当于告诉网卡:“我知道数据来了,下次收到包直接放内存就好,暂时别通知我”,以此避免CPU被频繁中断,提升效率。
  6. 驱动程序启动一个软中断(soft IRQ),将实际的数据包处理工作移交出去。这是因为硬件中断处理必须快速完成,耗时操作会阻塞其他中断,所以内核引入了软中断机制来异步处理这些任务。

内核数据包处理

上一步中驱动程序触发的软中断,会由内核的网络模块接手。具体处理流程如下图所示。

内核ksoftirqd处理数据包的流程图
图2:内核软中断处理进程 ksoftirqd 调用驱动程序poll函数,并将数据包送入协议栈的路径。

  1. 内核中的 ksoftirqd 进程会调用网络模块的软中断处理函数 net_rx_action
  2. net_rx_action 接着调用网卡驱动中的 poll 函数,开始逐个处理数据包。
  3. 驱动中的 poll 函数读取网卡写入内存的原始数据,并将其转换为内核网络模块能识别的标准格式——skb(socket buffer)。
  4. 驱动程序调用 napi_gro_receive 函数。该函数会处理GRO(Generic Receive Offload),将可能的小包合并,然后判断是否启用了RPS(Receive Packet Steering)。
  5. 如果启用了RPS,会调用 enqueue_to_backlog 函数,将数据包放入 input_pkt_queue 队列。注意:如果此队列已满(大小由 net.core.netdev_max_backlog 控制),数据包将被丢弃。
  6. 随后,CPU在软中断上下文中处理自己队列里的网络数据,调用 __netif_receive_skb_core 函数。
  7. 如果未启用RPS,napi_gro_receive 会直接调用 __netif_receive_skb_core
  8. __netif_receive_skb_core 会检查是否存在 AF_PACKET 类型的原始套接字(例如 tcpdump 使用的套接字)。如果有,它会将数据包复制一份给这些套接字。
  9. 最后,数据包被传递给内核的 TCP/IP协议栈 进行处理。

当内存中所有数据包都处理完毕(poll 函数完成),驱动程序会重新启用网卡的硬件中断,等待下一次数据到达的通知。

内核网络协议栈

此时,进入协议栈的数据包是网络层(第3层)的数据包,因此会先经过IP层,再传递到传输层。

IP网络层

IP网络层数据包处理与路由决策流程图
图3:IP层对数据包进行路由决策,区分是发往本地还是需要转发的完整流程。

  • ip_rcv 是IP层的入口函数。它进行基本检查后,会调用Netfilter钩子 NF_INET_PRE_ROUTING 链上的处理函数(可通过iptables配置)。
  • 路由决策:判断目标IP是否是本机IP。如果不是,且系统未开启IP转发,则丢弃包;否则,进入 ip_forward 函数进行转发处理。
  • ip_forward 会先经过 NF_INET_FORWARD 钩子链,然后调用 dst_output_sk 函数进入发送流程。
  • 如果目标IP是本机,则调用 ip_local_deliver 函数。该函数先经过 NF_INET_LOCAL_IN 钩子链,然后将数据包传递给传输层。

传输层(UDP)

UDP层接收数据包并放入套接字接收队列的流程图
图4:UDP层查找对应套接字,并通过过滤器检查后将数据包放入接收队列的流程。

  • udp_rcv 是UDP层的入口函数。它首先调用 __udp4_lib_lookup_skb,根据目标IP和端口查找对应的套接字。若未找到,则丢弃数据包。
  • sock_queue_rcv_skb 函数检查套接字接收缓存是否已满,并调用 sk_filter 执行可能的BPF过滤器程序。如果缓存满或过滤未通过,数据包将被丢弃。
  • __skb_queue_tail 将数据包放入套接字的接收队列末尾。
  • 调用 sk_data_ready 函数通知套接字:有数据就绪,等待应用层读取。

注意:以上所有过程均在软中断上下文中执行。

数据包的发送流程

逻辑上,发送是接收的逆过程。我们同样以通过物理网卡发送UDP数据包为例。

应用层

应用层调用sendto发送UDP数据的流程图
图5:应用程序通过socket和sendto系统调用,将数据传递给内核传输层的初始步骤。

  • 应用调用 socket(...) 创建套接字,并初始化相关操作函数。
  • 应用调用 sendto(sock, ...) 发送数据,该调用会进入内核的 inet_sendmsg 函数。
  • inet_sendmsg 检查套接字是否绑定了源端口。如果没有,则调用 inet_autobind 自动分配一个。
  • inet_autobind 通过 get_port 函数获取一个可用端口。

传输层(UDP)

UDP层构造skb并处理路由的流程图
图6:UDP发送入口函数处理路由、构造skb并填充UDP头部的过程。

  • udp_sendmsg 是UDP发送的入口。它首先调用 ip_route_output_flow 获取路由信息(决定从哪个网卡、哪个源IP发出)。
  • ip_route_output_flow 根据路由表和目标IP确定出口设备和源IP。如果路由不可达,则返回错误。
  • 然后调用 ip_make_skb 构造 skb 结构,并检查套接字发送缓存,若满则返回 ENOBUFS 错误。
  • 最后调用 udp_send_skb 填充UDP头部和校验和,并将 skb 交给IP网络层。

IP网络层

IP层发送数据包,处理Netfilter钩子与ARP查询的流程图
图7:IP层设置包头、经过Netfilter钩子,并查询下一跳ARP地址的完整发送路径。

  • ip_send_skb 是IP层发送入口,它调用后续一系列函数。
  • __ip_local_out_sk 设置IP包头长度和校验和,然后经过 NF_INET_LOCAL_OUT Netfilter钩子链。
  • dst_output_sk 调用 ip_output 函数,将出口设备信息写入 skb,并经过 NF_INET_POST_ROUTING 钩子链(常用于SNAT)。
  • ip_finish_output 判断路由是否因NAT改变,若有变化则可能重新路由。
  • ip_finish_output2 根据目标IP在路由表中查找下一跳地址,并查询ARP表获取下一跳的MAC地址。若ARP表中无记录,则创建一个邻居项并触发ARP请求。
  • dst_neigh_output 用获取到的MAC地址填充 skb 的以太网头,然后调用 dev_queue_xmit 将数据包交给网络设备。

内核处理与设备驱动

设备层流量控制与驱动发送的流程图
图8:数据包经过流量控制(TC)队列,最终由网卡驱动程序发送出去的流程。

  • dev_queue_xmit 是内核将数据包交付给设备的入口。它首先获取设备的队列规则(qdisc)。如果是环回接口等无队列设备,则直接调用 dev_hard_start_xmit;否则,数据包会先经过流量控制(TC)模块进行排队、整形或过滤。若队列满,数据包可能被丢弃。
  • dev_hard_start_xmitskb 复制一份给“数据包探针”(供 tcpdump 等抓包工具使用),然后调用网卡驱动注册的 ndo_start_xmit 函数。如果发送失败,会触发 NET_TX_SOFTIRQ 软中断稍后重试。
  • ndo_start_xmit 是具体的网卡驱动发送函数。此后,任务完全交给网卡驱动:
    • 驱动将 skb 放入网卡的发送队列。
    • 通知网卡开始发送数据包。
    • 网卡发送完成后,向CPU发送一个中断。
    • 驱动在中断处理程序中清理已发送的 skb

总结

通过梳理Linux网络数据包的收发全路径,我们可以清晰地知道在哪个环节可以监控或修改数据包,以及在哪些情况下数据包可能被丢弃。特别是理解Netfilter各个钩子(HOOK)的位置,能帮助我们更透彻地掌握iptables的工作原理,并对Linux下的各种网络虚拟设备有更深的认识。

下图是一张更为综合的Netfilter/Iptables数据包流向全景图,涵盖了接收、转发、发送等所有路径,可以帮助我们建立全局观。

Netfilter/Iptables数据包流向全景图
图9:展示数据包经过Netfilter五个钩子点的完整路径,包括路由决策、桥接和ARP处理。

值得注意的是tcpdump 等抓包工具的抓包点,分别位于Ingress(接收路径)之前和Egress(发送路径)之后。理解整个内核网络栈的流程,是进行高性能网络监控与调试的基础。

参考

原文来源




上一篇:Trust Wallet扩展钱包2.68版本安全漏洞分析与事件复盘
下一篇:分库分表与NewSQL分布式数据库选型指南:适用场景与核心特性对比
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 11:55 , Processed in 0.241052 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表