Linux kernel 网络数据包接收流程

图1:Network-API-and-data-flow-within-Linux-kernel

图1原图链接:https://thelinuxchannel.org/2024/10/network_data_flow_through_kernel-api-map/

注,本文基于 2.6.20 内核。

https://elixir.bootlin.com/linux/v2.6.20/source

接收流程

第 2 层:链路层(例如以太网)

网络设备会预先分配一定数量的套接字缓冲区(sk_buff)用于接收数据。具体分配的数量是针对每个设备进行配置的。通常,这些 sk_buff 中数据空间的地址会直接被配置为设备的直接内存访问(DMA)区域。设备中断处理程序会获取 sk_buff 并对其进行接收处理。在网络 API(NAPI)出现之前,这一过程是通过 netif_rx() 函数完成的。而在 NAPI 中,该过程分两个阶段进行。

  • 从中断处理程序来看,设备驱动程序只需调用 netif_rx_schedule() 函数,然后从中断中返回。netif_rx_schedule() 会将设备添加到 softnet_data 的轮询列表中,并触发 NET_RX_SOFTIRQ 软件中断。

  • 软中断守护进程 ksoftirqd 会运行 net_rx_action() 函数,该函数会调用设备的轮询虚拟方法。轮询(poll)方法会进行特定于设备的缓冲区管理,为每个 sk_buff 调用 netif_receive_skb() 函数,根据需要分配新的 sk_buff,并以 netif_rx_complete() 函数结束操作。

netif_receive_skb() 函数会确定如何将 sk_buff 传递给上层。

  1. 会调用 netpoll_rx() 函数以支持网络轮询(Netpoll)应用程序编程接口(API):

  2. 调用针对 ETH_P_ALL 协议的数据包处理程序(用于 tcpdump 工具)。

  3. 为入站(ingress)队列调用 handle_ing() 函数。

  4. 为网桥调用 handle_bridge() 函数。

  5. 为虚拟局域网调用 handle_macvlan() 函数。

  6. 调用为数据包指定的第 3 层(L3)协议所注册的数据包处理程序。

数据包处理程序是通过 deliver_skb() 函数来调用的,该函数会调用协议的 func 虚拟方法来处理数据包。

第 3 层:网络层(IPv4、ARP)

ARP(地址解析协议)

ARP 数据包由 arp_rcv() 函数处理。它会处理 ARP 信息,将其存储在邻居缓存中,并在需要时发送回复。如果需要发送回复,会为回复分配一个新的套接字缓冲区(sk_buff,带有新的数据空间)。

IPv4(互联网协议第 4 版)

IPv4 数据包由 ip_rcv() 函数处理。它会解析首部,检查其有效性,并在需要时发送 ICMP 回复或错误消息。它还会使用 ip_route_input() 函数查找目的地址。会使用 sk_buff 调用目的地址的输入虚拟方法。

  • 对于组播地址,会调用 ip_mr_input() 函数。数据包可能会使用 ip_mr_forward() 函数进行转发,也可能会使用 ip_local_delivery() 函数进行本地交付。

  • 对于有不同目的地址且本机有路由的数据包,会调用 ip_forward() 函数。它会直接调用邻居的输出虚拟方法。

  • 如果本机是数据包的目的地址,则会调用 ip_local_deliver() 函数。这里会收集数据报分片。

ip_local_deliver() 函数首先会使用 raw_local_deliver() 函数将数据交付给该连接的任何原始套接字。然后,它会调用数据报中指定协议的第 4 层(L4)协议处理程序。即使存在原始套接字,也会调用 L4 协议处理程序。

在整个过程中,会包含对 xfrm4_policy_check 的调用,以支持 IPsec(互联网协议安全)。

第 4 层:传输层(TCP)

TCP 的网络协议处理函数是 tcp_v4_rcv()。这里的大部分代码用于处理 TCP 协议相关操作,例如建立连接、执行流量控制等。

接收到的 TCP 数据包可能包含对之前发送数据包的确认信息,这可能会触发进一步的数据包发送(tcp_data_snd_check())或确认信息发送(tcp_ack_snd_check())。

将接收到的数据包传递给上层的操作在 tcp_rcv_established()tcp_data_queue() 函数中完成。这些函数会维护 TCP 连接的乱序队列,以及套接字的接收队列(sk_receive_queue)和异步等待队列(sk_async_wait_queue)。如果有用户进程已经在等待数据到达,会使用 skb_copy_datagram_iovec() 函数立即将数据复制到用户空间。否则,sk_buff 会被添加到套接字的某个队列中,稍后再进行复制。

最后,接收函数会调用套接字的 sk_data_ready 虚拟方法,以表明有数据可用。这会唤醒正在等待的进程。

第 5 层:会话层(套接字和文件)

有三种系统调用可用于从网络接收数据:

  • read(从文件描述符读取内存数据)

  • recvfrom(从套接字读取内存数据)

  • recvmsg(从套接字读取复合消息)

所有这些系统调用最终都会调用 __sock_recvmsg() 函数。该函数会先调用 security_sock_recvmsg() 进行权限检查,然后使用套接字的 recvmsg 虚拟方法向更下一层请求消息。通常调用的是 sock_common_recvmsg() 函数,它会调用套接字所使用协议的 recvmsg 虚拟方法。

tcp_recvmsg() 函数要么使用 skb_copy_datagram_iovec() 从套接字队列复制数据,要么使用 sk_wait_data() 等待数据到达。后一种方式会使进程阻塞,并且会在第 4 层的处理流程中被唤醒。

附录部分函数源码

int netif_receive_skb(struct sk_buff *skb)
{
 struct packet_type *ptype, *pt_prev;
 struct net_device *orig_dev;
 int ret = NET_RX_DROP;
 __be16 type;

 /* if we've gotten here through NAPI, check netpoll */
 if (skb->dev->poll && netpoll_rx(skb))
  return NET_RX_DROP;

 if (!skb->tstamp.off_sec)
  net_timestamp(skb);

 if (!skb->input_dev)
  skb->input_dev = skb->dev;

 orig_dev = skb_bond(skb);

 if (!orig_dev)
  return NET_RX_DROP;

 __get_cpu_var(netdev_rx_stat).total++;

 skb->h.raw = skb->nh.raw = skb->data;
 skb->mac_len = skb->nh.raw - skb->mac.raw;

 pt_prev = NULL;

 rcu_read_lock();

#ifdef CONFIG_NET_CLS_ACT
 if (skb->tc_verd & TC_NCLS) {
  skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
  goto ncls;
 }
#endif

 list_for_each_entry_rcu(ptype, &ptype_all, list) {
  if (!ptype->dev || ptype->dev == skb->dev) {
   if (pt_prev) 
    ret = deliver_skb(skb, pt_prev, orig_dev);
   pt_prev = ptype;
  }
 }

#ifdef CONFIG_NET_CLS_ACT
 if (pt_prev) {
  ret = deliver_skb(skb, pt_prev, orig_dev);
  pt_prev = NULL; /* noone else should process this after*/
 } else {
  skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
 }

 ret = ing_filter(skb);

 if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {
  kfree_skb(skb);
  goto out;
 }

 skb->tc_verd = 0;
ncls:
#endif

 if (handle_bridge(&skb, &pt_prev, &ret, orig_dev))
  goto out;

 type = skb->protocol;
 list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
  if (ptype->type == type &&
      (!ptype->dev || ptype->dev == skb->dev)) {
   if (pt_prev) 
    ret = deliver_skb(skb, pt_prev, orig_dev);
   pt_prev = ptype;
  }
 }

 if (pt_prev) {
  ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
 } else {
  kfree_skb(skb);
  /* Jamal, now you will not able to escape explaining
   * me how you were going to use this. :-)
   */
  ret = NET_RX_DROP;
 }

out:
 rcu_read_unlock();
 return ret;
}
/* Try to reschedule poll. Called by irq handler. */

static inline void netif_rx_schedule(struct net_device *dev)
{
 if (netif_rx_schedule_prep(dev))
  __netif_rx_schedule(dev);
}
static __inline__ int deliver_skb(struct sk_buff *skb,
      struct packet_type *pt_prev,
      struct net_device *orig_dev)
{
 atomic_inc(&skb->users);
 return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

Source

https://wiki.linuxfoundation.org/networking/kernel_flow

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值