内核是如何接收⽹络包的?

应用层做的那么舒服,为什么还要去看驱动和内核?
“工作了四五年,并不是有4-5年经验,⽽是⼯作了4-5年⽽已”

引言

Linux里最重要的一个模块-网络模块,用简单的udp来举例,下图是我在大学的时候的基于linux的socket网络编程的例子:
在这里插入图片描述
上面代码是非常简单的一段 udp server 接收数据逻辑。只要客户端有对应的数据发送过来,服务器端执行 recv_from 后就能收到它,并可以把它打印出来。
那Linux 是如何接收一个网络包的呢?

正文

Linux⽹络收包总览

在 Linux 内核实现中,链路层协议靠⽹卡驱动来实现,内核协议栈来实现⽹络层和传输层。内核对更上层的应⽤层提供 socket 接⼝来供⽤户进程访问。我们⽤ Linux 的视⻆来看到的TCP/IP ⽹络分层模型应该是下⾯这个样⼦:
在这里插入图片描述
代码:

  1. 无线网络驱动通常位于 Linux 内核源代码树中的 drivers/net/wireless/ 目录下,这是 Linux
    内核中专门用于存放无线网络驱动程序的目录。在这个目录下,不同厂商的无线网卡驱动通常会有各自的子目录。
  2. 协议栈(Protocol Stack)的代码通常位于 Linux 内核源代码树中的 net/ 目录下,这是 Linux
    内核中用于存放网络协议栈相关代码的目录。协议栈负责处理网络通信中的各种协议,包括网络层、传输层和应用层协议。

在 net/ 目录下,主要的协议栈代码会分布在不同的子目录中:

  • core:包含了网络协议栈的核心代码,如协议处理、路由等。
  • ipv4:包含了 IPv4 协议栈相关的代码。
  • ipv6:包含了 IPv6 协议栈相关的代码。
  • netfilter:包含了 Netfilter 框架相关的代码,用于实现网络层的数据包过滤和修改。
  • tcp:包含了 TCP 协议栈相关的代码。
  • udp:包含了 UDP 协议栈相关的代码。
  • unix:包含了 UNIX 套接字相关的代码,用于实现本地通信。

此外,还有一些其他的子目录,用于存放特定协议或者功能的代码,如 bridge、wireless/等。

软中断、硬中断、ksoftirgd 内核线程与CPU的关系

内核和网络设备驱动是通过中断的方式来处理的。当设备上有数据到达的时候,会给CPU的相关引脚上触发一个电压变化,以通知 CPU 来处理数据。对于网络模块来说,由于处理过程比较复杂和耗时,如果在中断函数中完成所有的处理,将会导致中断处理函数(优先级过高)将过度占据 CPU ,将导致 CPU 无法响应其它设备,例如鼠标和键盘的消息。因此Linux中断处理函数是分上半部和下半部的。上半部是只进行最简单的工作,快速处理然后释放CPU ,接着 CPU 就可以允许其它中断进来。剩下将绝大部分的工作都放到下半部中,可以慢慢处理。2.4 版本以后的内核采用的下半部实现方式是软中断,由ksoftirgd 内核线程全权处理。和硬中断不同的是,硬中断是通过给 CPU 物理引脚施加电压变化,而软中断是通过给内存中的一个变量的二进制值以通知软中断处理程序。

内核收包示意图
在这里插入图片描述
流程图说明:

  1. 当⽹卡上收到数据以后,Linux 中第⼀个⼯作的模块是⽹络驱动,⽹络驱动会通过PCIE总线以 DMA的⽅式把⽹卡上收到的帧写到内存⾥。再向 CPU 发起⼀个中断,以通知 CPU 有数据到达。
  2. 当 CPU 收到中断请求后,会去调⽤⽹络驱动注册的(硬)中断处理函数。 ⽹卡的(硬)中断处理函数并不做过多⼯作,发出软中断请求,然后尽快释放 CPU。ksoftirqd 检测到有软中断请求到达,调⽤ poll 开始轮询收包,收到后交由各级协议栈处理。对于 udp 包来说,会被放到⽤户socket 的接收队列中。

Linux 启动

Linux 驱动、内核协议栈等等模块在具备接收⽹卡数据包之前,要做很多的准备⼯作。比如要提前创建好ksoftirqd内核线程,要注册好各个协议对应的处理函数,⽹卡设备⼦系统要提前初始化好,⽹卡要启动好。只有这些都Ready之后,才能真正开始接收数据包。

创建ksoftirqd内核进程

  • Linux的软中断都是在专⻔的内核线程(ksoftirqd)中进⾏的,因此我们⾮常有必要看⼀下这些进程是怎么初始化的,这样我们才能在后⾯更准确地了解收包过程。该进程数量不是
    1个,⽽是 N 个,其中 N 等于你的机器的核数。

  • 系统初始化的时候在 kernel/smpboot.c中调⽤了smpboot_register_percpu_thread,
    该函数进⼀步会执⾏到 spawn_ksoftirqd(位于kernel/softirq.c)来创建出 softirqd 进程。
    在这里插入图片描述

在这里插入图片描述
当 ksoftirqd 被创建出来以后,它就会进⼊⾃⼰的线程循环函数ksoftirqd_should_run和run_ksoftirqd 了。不停地判断有没有软中断需要被处理。这⾥需要注意的⼀点是,软中断不仅仅只有⽹络软中断,还有其它类型

网络子系统初始化

linux 内核通过调⽤ subsys_initcall 来初始化各个⼦系统,在源代码⽬录⾥你可以 grep出许多对这个函数的调⽤。这⾥我们要说的是⽹络⼦系统的初始化,会执⾏到net_dev_init 函数。
在这里插入图片描述
如下是相应结构体和函数
在这里插入图片描述
在这里插入图片描述

  • 在这个函数⾥,会为每个 CPU 都申请⼀个 softnet_data 数据结构,在这个数据结构⾥的poll_list是等待驱动程序将其 poll 函数注册进来,稍后⽹卡驱动初始化的时候我们可以看到这⼀过程。
  • open_softirq 注册了每⼀种软中断都注册⼀个处理函数。 NET_TX_SOFTIRQ 的处理函数为
    net_tx_action,NET_RX_SOFTIRQ 的为 net_rx_action。继续跟踪 open_softirq后发现这个注册的⽅式是记录在 softirq_vec 变量⾥的。后⾯ ksoftirqd线程收到软中断的时候,也会使⽤这个变量来找到每⼀种软中断对应的处理函数。

在这里插入图片描述
注意!!
软硬中断的注册非常重要!面试也会问到
open_softirq() 是 Linux 内核中用于注册软中断处理函数的函数之一。

协议栈注册

  • 内核实现了⽹络层的 ip 协议,也实现了传输层的 tcp 协议和 udp 协议。 这些协议对应的实现函数分别是 ip_rcv(), tcp_v4_rcv()和 udp_rcv()。
  • 和我们平时写代码的⽅式不⼀样的是,内核是通过注册的⽅式来实现的。 Linux 内核中的 fs_initcall 和subsys_initcall 类似,也是初始化模块的⼊⼝。
  • fs_initcall 调⽤ inet_init 后开始⽹络协议栈注册。 通过 inet_init ,将这些函数注册到了 inet_protos 和ptype_base 数据结构中了。
  • 如下图:
    在这里插入图片描述
    这⾥我们需要记住 inet_protos 记录着 udp,tcp 的处理函数地址,ptype_base 存储着ip_rcv() 函数的处理地址。后⾯我们会看到软中断中会通过 ptype_base 找到 ip_rcv 函数地址,进⽽将 ip 包正确地送到 ip_rcv() 中执⾏。在 ip_rcv 中将会通过 inet_protos 找到 tcp或者 udp 的处理函数,再⽽把包转发给 udp_rcv() 或 tcp_v4_rcv() 函数。

扩展
如果看⼀下 ip_rcv 和 udp_rcv 等函数的代码能看到很多协议的处理过程。比如,ip_rcv 中会处理 netfilter 和 iptable 过滤,如果你有很多或者很复杂的 netfilter 或iptables 规则,这些都是在软中断的上下⽂中执⾏的,会加⼤⽹络延迟。再例如,udp_rcv 中会判断 socket 接收队列是否满了。对应相关内核参数是net.core.rmem_max 和net.core.rmem_default。建议⼤家好好读下inet_init 这个函数代码。

网卡驱动初始化

每⼀个驱动程序(不仅仅只是⽹卡驱动)会使⽤ module_init 向内核注册⼀个初始化函数,当驱动被加载时,内核会调⽤这个函数。⽐如igb⽹卡驱动的代码位于drivers/net/ethernet/intel/igb/igb_main.c

在这里插入图片描述
在这里插入图片描述
驱动的 pci_register_driver 调⽤完成后,Linux 内核就知道该驱动的相关信息,例如igb ⽹卡驱动的 igb_driver_name 和 igb_probe 函数地址等等。当⽹卡设备被识别以后,内核会调⽤其驱动的 probe ⽅法(igb_driver 的 probe ⽅法是 igb_probe)。驱动 probe⽅法执⾏的⽬的就是让设备 ready ,对于igb ⽹卡,其 igb_probe 位于drivers/net/ethernet/intel/igb/igb_main.c 下。

主要执⾏的操作如下:
在这里插入图片描述

  • 第5步中我们看到,⽹卡驱动实现了 ethtool 所需要的接⼝,也在这⾥注册完成函数地址的注册。当 ethtool发起⼀个系统调⽤之后,内核会找到对应操作的回调函数。对于 igb⽹卡来说,其实现函数都在drivers/net/ethernet/intel/igb/igb_ethtool.c 下。相信你这次能彻底理解 ethtool 的⼯作原理了吧? 这个命令之所以能查看⽹卡收发包统计、能修改⽹卡⾃适应模式、能调整 RX队列的数量和⼤⼩,是因为 ethtool 命令最终调⽤到⽹卡驱动相应⽅法,⽽不是 ethtool 本身有这个能⼒。
  • 第6步注册的 igb_netdev_ops 中包含的是 igb_open 等函数,该函数在⽹卡被启动的时候会被调⽤。
static const struct net_device_ops igb_netdev_ops = {.ndo_open		= igb_open,.ndo_stop		= igb_close,.ndo_start_xmit		= igb_xmit_frame_adv,.ndo_get_stats64	= igb_get_stats64,.ndo_set_rx_mode	= igb_set_rx_mode,.ndo_set_multicast_list	= igb_set_rx_mode,.ndo_set_mac_address	= igb_set_mac,.ndo_change_mtu		= igb_change_mtu,.ndo_do_ioctl		= igb_ioctl,.ndo_tx_timeout		= igb_tx_timeout,.ndo_validate_addr	= eth_validate_addr,.ndo_vlan_rx_register	= igb_vlan_rx_register,.ndo_vlan_rx_add_vid	= igb_vlan_rx_add_vid,.ndo_vlan_rx_kill_vid	= igb_vlan_rx_kill_vid,.ndo_set_vf_mac		= igb_ndo_set_vf_mac,.ndo_set_vf_vlan	= igb_ndo_set_vf_vlan,.ndo_set_vf_tx_rate	= igb_ndo_set_vf_bw,.ndo_get_vf_config	= igb_ndo_get_vf_config,
#ifdef CONFIG_NET_POLL_CONTROLLER.ndo_poll_controller	= igb_netpoll,
#endif
};

在这里插入图片描述

  • 第7步中,在 igb_probe 初始化过程中,还调⽤到了 igb_alloc_q_vector 。他注册了⼀个 NAPI 机制所必须的poll 函数,对于 igb ⽹卡驱动来说,这个函数就是 igb_poll ,如下代码所示在这里插入图片描述
 static int igb_alloc_q_vectors(struct igb_adapter *adapter)
{struct igb_q_vector *q_vector;struct e1000_hw *hw = &adapter->hw;int v_idx;for (v_idx = 0; v_idx < adapter->num_q_vectors; v_idx++) {q_vector = kzalloc(sizeof(struct igb_q_vector), GFP_KERNEL);if (!q_vector)goto err_out;q_vector->adapter = adapter;q_vector->itr_register = hw->hw_addr + E1000_EITR(0);q_vector->itr_val = IGB_START_ITR;netif_napi_add(adapter->netdev, &q_vector->napi, igb_poll, 64);adapter->q_vector[v_idx] = q_vector;}return 0;err_out:igb_free_q_vectors(adapter);return -ENOMEM;
}

启动网卡

  • 当上⾯的初始化都完成以后,就可以启动⽹卡了。回忆前⾯⽹卡驱动初始化时,我们提到了驱动向内核注册了 structure net_device_ops 变量,它包含着⽹卡启⽤、发包、设置mac地址等回调函数(函数指针)。当启⽤⼀个⽹卡时(例如,通过 ifconfig eth0 up),net_device_ops 中的 igb_open ⽅法会被调⽤。

它通常会做以下事情
在这里插入图片描述

static int igb_open(struct net_device *netdev)
{
............err = igb_request_irq(adapter);if (err)goto err_req_irq;for (i = 0; i < adapter->num_q_vectors; i++) {struct igb_q_vector *q_vector = adapter->q_vector[i];napi_enable(&q_vector->napi);
..............}
}

在这里插入图片描述

  • 在上⾯ __igb_open 函数调⽤了 igb_setup_all_tx_resources,和igb_setup_all_rx_resources。在 igb_setup_all_rx_resources这⼀步操作中,分配了RingBuffer,并建⽴内存和Rx队列的映射关系。(Rx Tx 队列的数量和⼤⼩可以通过ethtool进⾏配置)。我们再接着看中断函数注册 igb_request_irq:
    在这里插入图片描述
    在这里插入图片描述
  • 在上⾯的代码中跟踪函数调⽤, __igb_open => igb_request_irq =>igb_request_msix , 在
    igb_request_msix 中我们看到了,对于多队列的⽹卡,为每⼀个队列都注册了中断,其对应的中断处理函数是igb_msix_ring(该函数也在drivers/net/ethernet/intel/igb/igb_main.c 下)。我们也可以看到,msix ⽅式下,每个RX 队列有独⽴的 MSI-X 中断,从⽹卡硬件中断的层⾯就可以设置让收到的包被不同的 CPU处理。

当做好以上准备⼯作以后,就可以接收数据包了!

接收数据包

硬中断处理

  • ⾸先当数据帧从⽹线到达⽹卡上的时候,第⼀站是⽹卡的接收队列。⽹卡在分配给⾃⼰的RingBuffer 中寻找可⽤的内存位置,找到后 DMA引擎会把数据 DMA 到⽹卡之前关联的内存⾥,这个时候 CPU 都是⽆感的。当 DMA 操作完成以后,⽹卡会向 CPU发起⼀个硬中断,通知 CPU 有数据到达。
    在这里插入图片描述

注意:当RingBuffer满的时候,新来的数据包将给丢弃。ifconfig查看⽹卡的时候,可以⾥⾯有个overruns,表示因为环形队列满被丢弃的包。如果发现有丢包,可能需要通过ethtool命令来加⼤环形队列的⻓度。

在启动⽹卡⼀节,我们说到了⽹卡的硬中断注册的处理函数是igb_msix_ring。

static irqreturn_t igb_msix_ring(int irq, void *data)
{struct igb_q_vector *q_vector = data;/* Write the ITR value calculated from the previous interrupt. */igb_write_itr(q_vector);napi_schedule(&q_vector->napi);return IRQ_HANDLED;
}
  • igb_write_itr 只是记录⼀下硬件中断频率(据说⽬的是在减少对 CPU 的中断频率时⽤到)。顺着 napi_schedule
    调⽤⼀路跟踪下去, __napi_schedule => ____napi_schedule
/* Called with irq disabled */
static inline void ____napi_schedule(struct softnet_data *sd,struct napi_struct *napi)
{list_add_tail(&napi->poll_list, &sd->poll_list);__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
  • 这⾥我们看到, list_add_tail 修改了 CPU 变量 softnet_data ⾥的 poll_list ,将驱动napi_struct 传过来的 poll_list 添加了进来。
  • 其中 softnet_data 中的 poll_list 是⼀个双向列表,其中的设备都带有输⼊帧等着被处理。紧接着 __raise_softirq_irqoff 触发了⼀个软中断 NET_RX_SOFTIRQ, 这个所谓的触发过程只是对⼀个变量进⾏了⼀次或运算⽽已。
static inline void __raise_softirq_irqoff(unsigned int nr)
{trace_softirq_raise(nr);or_softirq_pending(1UL << nr);
}
  • Linux在硬中断⾥只完成简单必要的⼯作,剩下的⼤部分的处理都是转交给软中断的。通过上⾯代码可以看到,硬中断处理过程真的是⾮常短。只是记录了⼀个寄存器,修改了⼀下下CPU 的 poll_list,然后发出个软中断。就这么简单,硬中断⼯作就算是完成了

ksoftirqd 内核线程处理软中断

在这里插入图片描述

内核线程初始化的时候,我们介绍了 ksoftirqd 中两个线程函数 ksoftirqd_should_run 和run_ksoftirqd 。

这⾥看到和硬中断中调⽤了同⼀个函数 local_softirq_pending 。使⽤⽅式不同的是硬中断位置是为了写⼊标记,这⾥仅仅只是读取。如果硬中断中设置了NET_RX_SOFTIRQ ,这⾥⾃然能读取的到。接下来会真正进⼊线程函数中run_ksoftirqd 处理:
在这里插入图片描述
在 __do_softirq 中,判断根据当前 CPU 的软中断类型,调⽤其注册的 action ⽅法。
在这里插入图片描述

  • 这⾥需要注意⼀个细节,硬中断中设置软中断标记,和 ksoftirq 的判断是否有软中断到达,都是基于 smp_processor_id()
    的。这意味着只要硬中断在哪个 CPU 上被响应,那么软中断也是在这个 CPU 上处理的。所以说,如果你发现你的 Linux 软中断 CPU消耗都集中在⼀个核上的话,做法是要把调整硬中断的 CPU 亲和性,来将硬中断打散到不同的 CPU 核上去。

核⼼函数 net_rx_action :
在这里插入图片描述
在这里插入图片描述

函数开头的 time_limit 和 budget 是⽤来控制 net_rx_action 函数主动退出的,⽬的是保证⽹络包的接收不霸占 CPU 不放。 等下次⽹卡再有硬中断过来的时候再处理剩下的接收数据包。其中 budget 可以通过内核参数调整。 这个函数中剩下的核⼼逻辑是获取到当前 CPU变量 softnet_data,对其 poll_list 进⾏遍历, 然后执⾏到⽹卡驱动注册到的 poll 函数。对于igb ⽹卡来说,就是 igb 驱动⾥的 igb_poll 函数了。

疑问:

在硬中断中添加设备到 poll_list,会不会重复添加呢?

  • 不会的,在软中断处理函数 net_rx_action 这⾥⼀进来就调⽤ local_irq_disable把所有的硬中断都给关了,不会让硬中断重复添加 poll_list 的机会。在硬中断的处理函数中本身也有类似的判断机制。
    在这里插入图片描述
  • 在读取操作中, igb_poll 的重点⼯作是对 igb_clean_rx_irq 的调⽤。

在这里插入图片描述
igb_fetch_rx_buffer 和 igb_is_non_eop 的作⽤就是把数据帧从 RingBuffer 上取下来。为什么需要两个函数呢?因为有可能帧要占多个 RingBuffer,所以是在⼀个循环中获取的,直到帧尾部。获取下来的⼀个数据帧⽤⼀个 sk_buff 来表示。收取完数据以后,对其进⾏⼀些校验,然后开始设置 sbk 变量的 timestamp, VLAN id, protocol 等字段。接下来进⼊到napi_gro_receive 中:
在这里插入图片描述
dev_gro_receive 这个函数代表的是⽹卡 GRO 特性,可以简单理解成把相关的⼩包合并成⼀个⼤包就⾏,⽬的是减少传送给⽹络栈的包数,这有助于减少 CPU 的使⽤量。我们暂且忽略,直接看 napi_skb_finish , 这个函数主要就是调⽤了 netif_receive_skb :
在这里插入图片描述
在 netif_receive_skb 中,数据包将被送到协议栈中。

⽹络协议栈处理

netif_receive_skb 函数会根据包的协议,假如是 udp 包,会将包依次送到 ip_rcv(),udp_rcv() 协议处理函数中进⾏处理。

在这里插入图片描述
在这里插入图片描述

  • 在 __netif_receive_skb_core 中,我看着了原来经常使⽤的 tcpdump的抓包点,很是激动,看来读⼀遍源代码时间真的没⽩浪费。接着 __netif_receive_skb_core取出protocol,它会从数据包中取出协议信息,然后遍历注册在这个协议上的回调函数列表。 ptype_base 是⼀个 hash table,在协议注册⼩节我们提到过。ip_rcv 函数地址就是存在这个hash table中的。

在这里插入图片描述

  • pt_prev->func 这⼀⾏就调⽤到了协议层注册的处理函数了。对于 ip 包来讲,就会进⼊到ip_rcv(如果是arp包的话,会进⼊到arp_rcv)。

IP 协议层处理

我们再来⼤致看⼀下 linux 在 ip 协议层都做了什么,包⼜是怎么样进⼀步被送到 udp 或 tcp协议处理函数中的。
在这里插入图片描述
在这里插入图片描述

  • 这⾥ NF_HOOK 是⼀个钩⼦函数,当执⾏完注册的钩⼦后就会执⾏到最后⼀个参数指向的函数ip_rcv_finish 。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 跟踪 ip_route_input_noref 后看到它⼜调⽤了 ip_route_input_mc 。在ip_route_input_mc 中,函数 ip_local_deliver 被赋值给了 dst.input.
  • 所以回到 ip_rcv_finish 中的 return dst_input(skb) 。
    在这里插入图片描述
  • skb_dst(skb)->input 调⽤的 input ⽅法就是路由⼦系统赋的 ip_local_deliver。
    在这里插入图片描述
    在这里插入图片描述
  • 如协议注册⼩节看到 inet_protos 中保存着 tcp_v4_rcv() 和 udp_rcv()的函数地址。这⾥将会根据包中的协议类型选择进⾏分发,在这⾥ skb 包将会进⼀步被派送到更上层的协议中,udp 和 tcp。

总结

  • ⽹络模块是 Linux 内核中最复杂的模块了,看起来⼀个简简单单的收包过程就涉及到许多内核组件之间的交互,如⽹卡驱动、协议栈,内核ksoftirqd 线程等。

看起来很复杂,本⽂想通过源码 + 图示的⽅式,尽量以容易理解的⽅式来将内核收包过程讲清楚。现在让我们再串⼀串整个收包过程:
当⽤户执⾏完 recvfrom 调⽤后,⽤户进程就通过系统调⽤进⾏到内核态⼯作了。如果接收队列没有数据,进程就进⼊睡眠状态被操作系统挂起。这块相对⽐较简单,剩下⼤部分的戏份都是由 Linux 内核其它模块来表演了。

⾸先在开始收包之前,Linux 要做许多的准备⼯作:

  1. 创建ksoftirqd线程,为它设置好它⾃⼰的线程函数,后⾯就指望着它来处理软中断呢。
  2. 协议栈注册,linux要实现许多协议,⽐如arp,icmp,ip,udp,tcp,每⼀个协议都会将⾃⼰的处理函数注册⼀下,⽅便包来了迅速找到对应的处理函数
  3. ⽹卡驱动初始化,每个驱动都有⼀个初始化函数,内核会让驱动也初始化⼀下。在这个初始化过程中,把⾃⼰的DMA准备好,把NAPI的poll函数地址告诉内核
  4. 启动⽹卡,分配RX,TX队列,注册中断对应的处理函数

以上是内核准备收包之前的重要⼯作,当上⾯都 ready 之后,就可以打开硬中断,等待数据
包的到来了。

当数据到到来了以后,第⼀个迎接它的是⽹卡:

  1. ⽹卡将数据帧 DMA 到内存的 RingBuffer 中,然后向 CPU 发起中断通知
  2. CPU 响应中断请求,调⽤⽹卡启动时注册的硬中断处理函数
  3. 中断处理函数⼏乎没⼲啥,就发起了软中断请求
  4. 内核线程 ksoftirqd 线程发现有软中断请求到来,先关闭硬中断
  5. ksoftirqd 线程开始调⽤驱动的 poll 函数收包
  6. poll 函数将收到的包送到协议栈注册的 ip_rcv 函数中
  7. ip_rcv 函数再将包送到 udp_rcv 函数中(对于 tcp 包就送到 tcp_rcv )

疑问

网络驱动注册的中断是硬中断还是软中断?

  • 网络驱动通常注册的是硬中断。当网络设备接收到数据包时,硬件会向 CPU发送中断信号,通知系统有数据到达。网络驱动会注册一个硬中断处理程序,用于处理这些网络设备发出的硬中断。一旦硬中断被触发,CPU会立即跳转到注册的中断处理程序中执行相应的处理逻辑,从而及时地处理接收到的数据包。

Tcpdump到底是从哪一层抓取的包?

  • 一般来说,tcpdump 默认情况下是在链路层(数据链路层)抓取数据包的,即它能够捕获到链路层的数据帧,包括以太网帧、Wi-Fi帧等。然而,tcpdump 也支持在更高层次抓取数据包,例如 IP 层、TCP/UDP 层等。
  • 通过PF_PACKET这个特殊的套接字协议,直接接收来自链路层的帧。数据包并非没有进入内核,而是在进入内核后直接跳过了内核中三层/四层的协议栈,直达套接字接口,被应用层的tcpdump所使用。实际上,在网卡驱动程序通知内核接受到数据帧的时候,数据包就已经进入了内核处理流程。在网上找到了可以用于理解的框架图:在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/309874.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

顶顶通呼叫中心中间件(mod_cti基于FreeSWITCH)-回铃音补偿

文章目录 前言联系我们解决问题操作步骤 前言 回铃音&#xff1a; 当别人打电话给你时&#xff0c;你的电话响铃了&#xff0c;而他听到的声音叫做回铃音。回铃音是被叫方向主叫方传送&#xff0c;也是彩铃功能的基础。我们平时打电话听到的“嘟 嘟 嘟 嘟”的声音&#xff0c;就…

mysql 查询实战-变量方式-解答

对mysql 查询实战-变量方式-题目&#xff0c;进行一个解答。&#xff08;先看题&#xff0c;先做&#xff0c;再看解答&#xff09; 1、查询表中⾄少连续三次的数字 1&#xff0c;处理思路 要计算连续出现的数字&#xff0c;加个前置变量&#xff0c;记录上一个的值&#xff0c…

postgresql uuid

示例数据库版本PG16&#xff0c;对于参照官方文档截图&#xff0c;可以在最上方切换到对应版本查看&#xff0c;相差不大。 方法一&#xff1a;自带函数 select gen_random_uuid(); 去掉四个斜杠&#xff0c;简化成32位 select replace(gen_random_uuid()::text, -, ); 官网介绍…

LoRa无线电机温振传感器,FlexLua低代码技术助力快速实现。

在物联网时代&#xff0c;无线传感技术的应用愈发广泛。其中&#xff0c;LoRa&#xff08;长距离低功耗无线技术&#xff09;作为一种适用于远距离、低功耗的通信技术&#xff0c;被广泛应用于各种物联网场景。而结合温度和振动传感技术&#xff0c;能够构建出用于监测机器状态…

pytest-yaml-sanmu(二):使用hook自定义yaml用例的执行方式

前言 本文抛砖引玉&#xff0c;通过以下几个测试框架的封装示例&#xff0c;一步步引导你实现属于自己的 yaml 测试框架&#xff1a; 加法测试 计算测试 接口测试 Web 测试 使用本插件需要对 Python 和 Pytest 较为熟练的应用经验&#xff0c;本文认为你已经具备这些条件。…

HTTP协议名词解释

一、HTTP协议通讯名词解释-URL URL(Uniform Resource Locator&#xff0c;统一资源定位符)是标识Web资源的唯一标识符。通过它即可获取其标识的资源。 最常用的URL格式如下: protocol://hostname[:port]/[path/Ifile[?paramvaluel 这个结构中有几个部分是可选的。如果端口…

Python数学建模学习-PageRank算法

1-基本概念 PageRank算法是由Google创始人Larry Page在斯坦福大学时提出&#xff0c;又称PR&#xff0c;佩奇排名。主要针对网页进行排名&#xff0c;计算网站的重要性&#xff0c;优化搜索引擎的搜索结果。PR值是表示其重要性的因子。 中心思想&#xff1a; 数量假设&#…

10kV配电室在线监控改造技术方案

安科瑞薛瑶瑶18701709087 摘要&#xff1a;目前&#xff0c;我国经济高速发展&#xff0c;社会在不断进步&#xff0c;国家加大了农村低压配电网络改造升级投入&#xff0c;低压配电网供电可靠性及供电质量得到明显提升&#xff0c;但低压配电网络自动化运维水平及农村电网用电…

如何使用Docker部署WPS Office服务并实现无公网IP远程处理文档表格

文章目录 1. 拉取WPS Office镜像2. 运行WPS Office镜像容器3. 本地访问WPS Office4. 群晖安装Cpolar5. 配置WPS Office远程地址6. 远程访问WPS Office小结 7. 固定公网地址 wps-office是一个在Linux服务器上部署WPS Office的镜像。它基于WPS Office的Linux版本&#xff0c;通过…

移动开发避坑指南——内存泄漏

在日常编写代码时难免会遇到各种各样的问题和坑&#xff0c;这些问题可能会影响我们的开发效率和代码质量&#xff0c;因此我们需要不断总结和学习&#xff0c;以避免这些问题的出现。接下来我们将围绕移动开发中常见问题做出总结&#xff0c;以提高大家的开发质量。本系列文章…

【Qt编译】ARM环境 Qt5.14.2-QtWebEngine库编译 (完整版)

ARM 编译Qt5.14.2源码 1.下载源码 下载Qt5.14.2源代码&#xff08;可根据自己的需求下载不同版本&#xff09; 下载网站&#xff1a;https://download.qt.io/new_archive/qt/5.14/5.14.2/single/ 2.相关依赖(如果需要的话) 先参考官方文档的需求进行安装&#xff1a; 官方…

第十五届蓝桥杯省赛C/C++大学B组真题及赛后总结

目录 个人总结 C/C 组真题 握手问题 小球反弹 好数 R 格式 宝石组合 数字接龙 爬山 拔河 ​编辑 再总结及后续规划 个人总结 第一次参加蓝桥杯&#xff0c;大二&#xff0c;以前都在在学技术&#xff0c;没有系统的学过算法。所以&#xff0c;还是花了挺多时间去备…

unity按路径移动

using System; using System.Collections; using System.Collections.Generic; using UnityEngine;public class FollowPathMove : MonoBehaviour {public Transform[] wayPointArray;[SerializeField] private Transform PathA;//路径点的父物体[SerializeField]private Trans…

用html写一个有趣的鬼魂动画

<!DOCTYPE html> <html lang"en" > <head><meta charset"UTF-8"><title>一个有趣的鬼魂动画</title><link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.m…

[leetcode] minimum-falling-path-sum

. - 力扣&#xff08;LeetCode&#xff09; 给你一个 n x n 的 方形 整数数组 matrix &#xff0c;请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开始&#xff0c;并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多…

a == 1 a== 2 a== 3 返回 true ?

1. 前言 下面这道题是 阿里、百度、腾讯 三个大厂都出过的面试题&#xff0c;一个前端同事跳槽面试也被问了这道题 // &#xff1f; 位置应该怎么写&#xff0c;才能输出 trueconst a ?console.log(a 1 && a 2 && a 3) 看了大厂的面试题会对面试官的精神…

applicaitonListener配合ApplicationEvent原理

今天突然想看看applicationListener和applicationEvent是怎么实现的观察者模式所以看了下源码 先定义两个观察者 Component public class ListenerOne implements ApplicationListener<MyEvent> {Overridepublic void onApplicationEvent(MyEvent event) {System.out.pr…

三次握手与四次挥手到底是怎么回事?

三次握手和四次挥手是TCP/IP协议中建立和断开连接的关键步骤&#xff0c;它们是保证可靠通信的重要机制。这里将探讨这两个概念&#xff0c;并解释它们背后的原理。 三次握手 三次握手用于建立TCP连接&#xff0c;它由客户端和服务器之间发送的三个报文组成&#xff1a; 第一次…

(三)C++自制植物大战僵尸游戏项目结构说明

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/ErelL 一、项目结构 打开项目后&#xff0c;在解决方案管理器中有五个项目&#xff0c;分别是libbox2d、libcocos2d、librecast、libSpine、PlantsVsZombies五个项目&#xff0c;除PlantsVsZombies外&#xff0c;其他四个…

python爬取京东商品信息与可视化

项目介绍&#xff1a;使用python爬取京东电商拿到价格、店铺、链接、销量并做可视化 ........................................................................................................................................................... 项目介绍效果展示全部…