当前位置: 首页 > news >正文

linux 网络子系统

__netif_receive_skb_core 是 Linux 内核网络子系统中一个非常重要的函数,它负责将网络设备驱动层接收到的数据包传递到上层协议栈进行处理。以下是对该函数的一些关键点的详细解析:

一、函数作用

__netif_receive_skb_core 函数是处理接收到的网络数据包的核心函数之一。它从网络设备驱动接收数据包(通常通过 sk_buff 结构体表示),并根据注册的协议处理函数(通过 packet_type 结构体注册)将数据包传递给相应的上层协议栈进行处理,如 IP 层、ARP 层等。

二、函数调用关系

在 Linux 内核中,数据包的接收通常涉及多个函数的调用。从网络设备驱动层开始,数据包可能会经过 netif_receive_skb -> netif_receive_skb_internal -> __netif_receive_skb(在某些内核版本中可能直接调用 __netif_receive_skb_core)等函数的传递,最终到达 __netif_receive_skb_core 函数进行处理。

三、函数实现细节

  1. 记录收包时间和设备
    • 函数首先会记录收包时间,并检查是否有包延迟。
    • 同时,记录收包设备,即数据包是从哪个网络设备接收到的。
  2. 重置各层头部
    • 为了后续协议栈的正确处理,函数会重置网络层、传输层和 MAC 层的头部指针。
  3. 处理 VLAN 报文
    • 如果数据包是 VLAN 报文(即带有 VLAN 标签),函数会去除 VLAN 头,以便后续协议栈能够正确处理。
  4. 遍历协议处理链表
    • 函数会遍历两个链表:ptype_all 和 ptype_base。这两个链表上挂载了多个 packet_type 结构体,每个结构体对应一个具体的协议处理函数。
    • 对于 ptype_all 链表上的每个 packet_type 结构体,函数会调用其对应的协议处理函数(但通常最后一个除外,以优化性能)。
    • 对于 ptype_base 链表,函数会根据数据包的协议类型选择相应的 packet_type 结构体并调用其处理函数。
  5. 减少 skb 复制
    • 为了提高性能,函数在遍历链表时采用了一种优化策略,即利用 pt_prev 变量来减少最后一次协议处理时的 skb 复制。这是通过控制 skb 的引用计数来实现的。
  6. 统计和错误处理
    • 函数会更新处理包数的统计信息。
    • 如果在处理过程中遇到错误(如无法分配内存、无法找到合适的协议处理函数等),函数会进行相应的错误处理。

四、总结

__netif_receive_skb_core 函数是 Linux 内核网络子系统中处理接收到的网络数据包的关键函数之一。它通过记录收包信息、重置头部指针、处理 VLAN 报文、遍历协议处理链表以及减少 skb 复制等步骤,将数据包高效地传递给上层协议栈进行处理。这一过程中涉及了多个内核机制和数据结构的使用,如 RCU 读锁保护、sk_buff 结构体、packet_type 结构体等。

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc) // 将skb传递到上层 
{struct packet_type *ptype, *pt_prev;rx_handler_func_t *rx_handler;struct net_device *orig_dev;struct net_device *null_or_dev;bool deliver_exact = false;//默认不精确传递int ret = NET_RX_DROP;//默认收报失败__be16 type;net_timestamp_check(!netdev_tstamp_prequeue, skb);//记录收包时间,netdev_tstamp_prequeue为0,表示可能有包延迟 trace_netif_receive_skb(skb);orig_dev = skb->dev;//记录收包设备 skb_reset_network_header(skb);//重置network header,此时skb指向IP头(没有vlan的情况下)if (!skb_transport_header_was_set(skb))skb_reset_transport_header(skb);skb_reset_mac_len(skb);// 留下一个节点,最后一次向上层传递时,不需要再inc引用,回调中会free这样相当于少调用了一次freept_prev = NULL;another_round:skb->skb_iif = skb->dev->ifindex;//设置接收设备索引号 __this_cpu_inc(softnet_data.processed);//处理包数统计 if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||skb->protocol == cpu_to_be16(ETH_P_8021AD)) {//vxlan报文处理,剥除vxlan头skb = skb_vlan_untag(skb);//剥除vxlan头if (unlikely(!skb))goto out;}#ifdef CONFIG_NET_CLS_ACTif (skb->tc_verd & TC_NCLS) {skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);goto ncls;}
#endifif (pfmemalloc)此类报文不允许ptype_all处理,即tcpdump也抓不到goto skip_taps;//先处理 ptype_all 上所有的 packet_type->func()           //所有包都会调func,对性能影响严重!所有有的钩子是随模块加载挂上的。list_for_each_entry_rcu(ptype, &ptype_all, list) {//遍历ptye_all链表if (!ptype->dev || ptype->dev == skb->dev) {//上面的paket_type.type 为 ETH_P_ALL,典型场景就是tcpdump抓包所使用的协议if (pt_prev)//pt_prev提高效率ret = deliver_skb(skb, pt_prev, orig_dev);//此函数最终调用paket_type.func()pt_prev = ptype;}	}skip_taps:
#ifdef CONFIG_NET_CLS_ACTif (static_key_false(&ingress_needed)) {skb = handle_ing(skb, &pt_prev, &ret, orig_dev);if (!skb)goto out;}skb->tc_verd = 0;
ncls:
#endifif (pfmemalloc && !skb_pfmemalloc_protocol(skb))//不支持使用pfmemalloc goto drop;if (skb_vlan_tag_present(skb)) {// 如果是vlan包 if (pt_prev) {/* 处理pt_prev */ret = deliver_skb(skb, pt_prev, orig_dev);pt_prev = NULL;}if (vlan_do_receive(&skb))/* 根据实际的vlan设备调整信息,再走一遍 */goto another_round;else if (unlikely(!skb))goto out;}
/*如果一个dev被添加到一个bridge(做为bridge的一个接口),这个接口设备的rx_handler将被设置为br_handle_frame函数,这是在br_add_if函数中设置的,而br_add_if (net/bridge/br_if.c)是在向网桥设备上添加接口时设置的。进入br_handle_frame也就进入了bridge的逻辑代码。*/rx_handler = rcu_dereference(skb->dev->rx_handler);/* 如果有注册handler,那么调用,比如网桥模块 */if (rx_handler) {if (pt_prev) {ret = deliver_skb(skb, pt_prev, orig_dev);pt_prev = NULL;}switch (rx_handler(&skb)) {case RX_HANDLER_CONSUMED:/* 已处理,无需进一步处理 */ret = NET_RX_SUCCESS;goto out;case RX_HANDLER_ANOTHER:/* 修改了skb->dev,在处理一次 */goto another_round;case RX_HANDLER_EXACT:/* 精确传递到ptype->dev == skb->dev */deliver_exact = true;case RX_HANDLER_PASS:break;default:BUG();}}if (unlikely(skb_vlan_tag_present(skb))) {/* 还有vlan标记,说明找不到vlanid对应的设备 */if (skb_vlan_tag_get_id(skb))/* 存在vlanid,则判定是到其他设备的包 */skb->pkt_type = PACKET_OTHERHOST;/* Note: we might in the future use prio bits* and set skb->priority like in vlan_do_receive()* For the time being, just ignore Priority Code Point*/skb->vlan_tci = 0;}/* deliver only exact match when indicated */null_or_dev = deliver_exact ? skb->dev : NULL;//指定精确传递的话,就精确传递,否则向未指定设备的指定协议全局发送一份type = skb->protocol;/* 设置三层协议,下面提交都是按照三层协议提交的 */list_for_each_entry_rcu(ptype,&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {if (ptype->type == type &&(ptype->dev == null_or_dev || ptype->dev == skb->dev ||ptype->dev == orig_dev)) {if (pt_prev)ret = deliver_skb(skb, pt_prev, orig_dev);//上层传递pt_prev = ptype;}}if (pt_prev) {if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))goto drop;else//使用pt_prev这里就不需要deliver_skb来inc应用数了,  func执行内部会free,减少了一次skb_freeret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);/* 传递到上层*/} else {
drop:if (!deliver_exact)atomic_long_inc(&skb->dev->rx_dropped);//网卡丢包计数elseatomic_long_inc(&skb->dev->rx_nohandler);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:return ret;
}

---------------------------------------------------------------------------------------------------------------------------------

skb->dev结构体

在 Linux 内核中,skb->dev 指向的是一个 struct net_device 类型的指针,该结构体代表了一个网络设备。struct net_device 是网络子系统中的核心数据结构之一,它包含了网络设备几乎所有的配置信息和状态信息。

struct net_device 结构体的定义可能会随着内核版本的不同而有所变化,但通常包括以下几个关键部分:

  1. 设备基本信息
    • char name[IFNAMSIZ]:网络设备的名称,如 eth0lo 等。
    • unsigned long state:设备的状态标志,如是否已启动、是否正在接收数据等。
    • unsigned long flags:设备的标志位,用于控制设备的行为,如是否支持多播、是否支持巨型帧等。
  2. 硬件地址
    • unsigned char dev_addr[MAX_ADDR_LEN]:设备的硬件地址(MAC 地址)。
  3. 统计信息
    • 结构体中包含了一系列的计数器,用于统计接收和发送的数据包数量、错误数量等信息。
  4. 队列和中断处理
    • 包括接收和发送队列的指针,以及中断处理函数的指针。
  5. 协议处理
    • struct packet_type *ptype_all 和 struct list_head ptype_all:用于挂载全局的协议处理函数链表。
    • struct list_head ptype_specific:用于挂载特定于该设备的协议处理函数链表。
  6. 设备私有数据
    • void *priv:指向设备私有数据的指针,该数据对于不同类型的网络设备可能是不同的。
  7. 设备方法
    • 结构体中包含了一系列的函数指针,这些函数指针指向了处理设备特定操作(如启动、停止、发送数据包等)的函数。
  8. 其他配置和状态信息
    • 包括 MTU(最大传输单元)、设备类型、速度、双工模式等信息。

请注意,由于内核的不断发展和更新,struct net_device 结构体的具体定义可能会发生变化。因此,在查看或修改内核代码时,最好参考您正在使用的内核版本的源代码。

此外,由于 struct net_device 结构体包含了大量的信息和功能,因此在实际编程中,通常不需要直接操作整个结构体。相反,内核提供了丰富的 API 和函数来查询和修改网络设备的状态和行为。

---------------------------------------------------------------------------------------------------------------------------------

ptype_all 在 Linux 内核网络子系统中是一个非常重要的链表头,它挂载了一系列 packet_type 结构体。每个 packet_type 结构体代表了一个协议处理函数,用于接收和处理网络数据包。ptype_all 链表是全局的,意味着它包含了所有注册到内核的网络协议处理函数,这些函数可以接收来自任何网络设备的数据包。

ptype_all 的作用和特点

  1. 全局性ptype_all 是一个全局链表头,它允许内核中的任何网络协议处理函数注册自己,以便接收和处理网络数据包。

  2. 可扩展性:通过向 ptype_all 链表添加新的 packet_type 结构体,可以轻松地扩展内核的网络协议处理能力。这为新协议的开发和现有协议的修改提供了便利。

  3. 高效性:虽然 ptype_all 链表可能包含大量的协议处理函数,但内核通过优化遍历和匹配逻辑,确保数据包能够高效地传递给正确的处理函数。

packet_type 结构体

packet_type 结构体通常包含以下关键字段:

  • type:指定了数据包类型,用于匹配接收到的数据包。
  • func:指向协议处理函数的指针,当数据包与 packet_type 结构体匹配时,将调用此函数来处理数据包。
  • list:用于将 packet_type 结构体链接到 ptype_all 链表或其他链表中的双向链表节点。

使用场景

  • 网络协议开发:在开发新的网络协议时,可以通过注册一个 packet_type 结构体到 ptype_all 链表来接收和处理相应的数据包。
  • 网络监控和分析:一些网络监控和分析工具可能会注册自己的协议处理函数到 ptype_all 链表,以便捕获和分析所有经过内核的网络数据包。
  • 网络协议修改:当需要修改现有网络协议的行为时,可以通过修改或替换已注册的 packet_type 结构体来实现。

注意事项

  • 性能影响:由于 ptype_all 链表可能包含大量的协议处理函数,因此遍历链表可能会对性能产生一定影响。内核通过优化遍历逻辑来减少这种影响。
  • 同步问题:在注册或注销 packet_type 结构体时,需要确保与数据包接收和处理相关的同步问题得到妥善处理。这通常涉及到使用 RCU(Read-Copy Update)等同步机制来保护链表和相关的数据结构。

结论

ptype_all 是 Linux 内核网络子系统中用于管理和分发网络数据包的核心组件之一。通过向 ptype_all 链表注册协议处理函数,内核能够灵活地扩展和修改其网络协议处理能力。

---------------------------------------------------------------------------------------------------------------------------------

packet_type

struct packet_type {__be16          type;   /* This is really htons(ether_type). */struct net_device   *dev;   /* NULL is wildcarded here       */int         (*func) (struct sk_buff *,struct net_device *,struct packet_type *,struct net_device *);bool            (*id_match)(struct packet_type *ptype,struct sock *sk);void            *af_packet_priv;struct list_head    list;                                                                                                                              
};
static struct packet_type ip_packet_type __read_mostly = {.type = cpu_to_be16(ETH_P_IP),.func = ip_rcv,
};

packet_type结构体的链表管理

在Linux内核中,packet_type结构体用于表示网络协议处理函数的接口。这些结构体被组织在链表中,以便内核能够高效地遍历它们,并将接收到的网络数据包分发给正确的处理函数。

全局链表(ptype_all)
  • 定义ptype_all是一个全局链表头,挂载了所有注册到内核的网络协议处理函数。这些处理函数可以接收来自任何网络设备的数据包。
  • 用途:主要用于分析目的,接收所有到达网络协议栈的数据包,无论其协议类型如何。
基于协议类型的链表(ptype_base)
  • 定义:虽然直接称为“packet_base链”的概念可能不存在,但内核通常使用类似ptype_base的数组来管理基于协议类型的链表。这个数组的每个元素都是一个链表头,指向具有相同协议类型哈希值的packet_type结构体链表。
  • 生成方式
    • 当注册一个新的packet_type结构体时(通常通过调用dev_add_pack函数),内核会根据该结构体的type字段(即协议类型)计算一个哈希值。
    • 然后,内核将新的packet_type结构体添加到ptype_base数组中对应哈希值的链表上。
  • 用途:这种方式允许内核根据数据包的协议类型快速定位到相应的处理函数链表,从而提高数据包分发的效率。

示例代码(概念性)

以下是一个概念性的示例,展示了如何将packet_type结构体添加到基于协议类型的链表中(注意,这不是实际的内核代码):

 

c复制代码

// 假设ptype_base是一个包含多个链表头的数组
struct list_head ptype_base[PTYPE_HASH_SIZE]; // PTYPE_HASH_SIZE是哈希表的大小
// dev_add_pack函数的简化版本
void dev_add_pack(struct packet_type *pt) {
unsigned int hash = ntohs(pt->type) & (PTYPE_HASH_SIZE - 1); // 计算哈希值
list_add_rcu(&pt->list, &ptype_base[hash]); // 添加到对应哈希值的链表上
}

结论

虽然“packet_base链”不是Linux内核中的一个标准术语,但基于协议类型的链表管理机制(通常通过类似ptype_base的数组实现)是内核网络子系统中的一个重要组成部分。通过这种机制,内核能够高效地处理和分发接收到的网络数据包。

---------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------- 

ip_rcv

static inline int dst_input(struct sk_buff *skb)
{return skb_dst(skb)->input(skb);
}

在Linux内核网络子系统中,skb_dst(skb)->input(skb) 这一行代码执行了一个重要的操作,但它并不是直接可执行的,因为这里涉及到几个步骤和假设,我将逐一解释。

首先,skb 是指向 struct sk_buff 结构体的指针,该结构体用于表示网络数据包。struct sk_buff 包含了数据包的所有信息,包括数据本身、元数据(如源地址、目的地址、协议类型等)以及指向各种相关数据结构(如路由缓存项)的指针。

  1. skb_dst(skb)
    • 这个宏或函数用于获取与 skb 相关联的路由缓存项(routing cache entry)。在内核中,路由缓存项通常存储在 skb 的一个特定字段中,以便快速访问与数据包路由相关的信息。
    • skb_dst(skb) 返回的是一个指向 struct dst_entry 结构体的指针,该结构体包含了路由的详细信息,如下一跳地址、输出接口等。
  2. dst_entry->input
    • struct dst_entry 结构体中有一个 input 字段,它是一个函数指针,指向一个特定的函数,该函数负责处理通过该路由缓存项接收到的数据包。
    • 这个 input 函数是协议栈的一部分,它根据数据包的协议类型(如IP、IPv6等)和路由信息来决定如何进一步处理数据包。例如,对于IP数据包,input 函数可能会将数据包传递给IP层的处理函数。
  3. skb_dst(skb)->input(skb)
    • 当这行代码被执行时,它实际上是在调用与 skb 相关联的路由缓存项的 input 函数,并将 skb 作为参数传递给它。
    • 这个调用是数据包在网络协议栈中传递的关键步骤之一,它允许协议栈根据路由信息和数据包类型来正确地处理数据包。

需要注意的是,skb_dst(skb) 可能会返回 NULL,如果 skb 没有与任何路由缓存项相关联(例如,在数据包刚刚被网络设备接收但尚未进行路由查找的情况下)。因此,在实际代码中,通常会在调用 skb_dst(skb)->input(skb) 之前检查 skb_dst(skb) 是否为 NULL

   报文提交给内核协议栈处理后,最终会调用到__netif_receive_skb_core函数,如果报文没有被网桥处理函数rx_handler消费掉,最终会交给ptype_base中注册的协议处理,包括内核注册的协议,也包括raw socket等创建的协议处理。本文将分析普通ipv4    报文的处理过程,处理入口函数为ip_rcv函数。
主要调用流程:ip_rcv-->ip_rcv_finish-->ip_local_deliver-->ip_local_deliver_finish

ip_rcv:

/** 主要作用:
*(1)类型为ETH_P_IP类型的数据包,被传递到三层,调用ip_rcv函数* (2)  ip_rcv完成基本的校验( 主要检查计算的校验和与首部中存储的校验和是否一致)和处理工作后,**经过PRE_ROUTING钩子点* (3) 经过PRE_ROUTING钩子点之后,调用ip_rcv_finish完成数据包接收,包括选项处理,路由查询,并且*根据路由决定数据包是发往本机还是转发*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{const struct iphdr *iph;u32 len;/* When the interface is in promisc. mode, drop all the crap* that it receives, do not try to analyse it.*/if (skb->pkt_type == PACKET_OTHERHOST)//丢弃掉不是发往本机的报文,网卡开启混杂模式会收到此类报文goto drop;IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {//检查是否skb为share,是 则克隆报文IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);goto out;}if (!pskb_may_pull(skb, sizeof(struct iphdr)))//确保skb还可以容纳标准的报头(即20字节)goto inhdr_error;iph = ip_hdr(skb);//得到IP头/**      RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.**      Is the datagram acceptable?**      1.      Length at least the size of an ip header*      2.      Version of 4*      3.      Checksums correctly. [Speed optimisation for later, skip loopback checksums]*      4.      Doesn't have a bogus length*/if (iph->ihl < 5 || iph->version != 4)//ip头长度至少为20字节(ihl>=5,后面计算头长度会乘4),只支持v4goto inhdr_error;BUILD_BUG_ON(IPSTATS_MIB_ECT1PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_1);BUILD_BUG_ON(IPSTATS_MIB_ECT0PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_0);BUILD_BUG_ON(IPSTATS_MIB_CEPKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_CE);IP_ADD_STATS_BH(dev_net(dev),IPSTATS_MIB_NOECTPKTS + (iph->tos & INET_ECN_MASK),max_t(unsigned short, 1, skb_shinfo(skb)->gso_segs));if (!pskb_may_pull(skb, iph->ihl*4))//确保skb还可以容纳实际的报头(ihl*4)goto inhdr_error;iph = ip_hdr(skb);if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))//ip头csum校验goto csum_error;len = ntohs(iph->tot_len);//获取ip分组总长,即ip首部加数据的长度if (skb->len < len) {//skb的实际总长度小于ip分组总长,则dropIP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);goto drop;} else if (len < (iph->ihl*4))//ip头记录的分组长度就大于数据总长,则出错goto inhdr_error;/* Our transport medium may have padded the buffer out. Now we know it* is IP we can trim to the true length of the frame.* Note this now means skb->len holds ntohs(iph->tot_len).*/if (pskb_trim_rcsum(skb, len)) {//去除多余的字节IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);goto drop;} else if (len < (iph->ihl*4))goto inhdr_error;/* Our transport medium may have padded the buffer out. Now we know it* is IP we can trim to the true length of the frame.* Note this now means skb->len holds ntohs(iph->tot_len).*/if (pskb_trim_rcsum(skb, len)) {IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);goto drop;}skb->transport_header = skb->network_header + iph->ihl*4;//设置传输层header/* Remove any debris in the socket control block */memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));//清空cb,即inet_skb_parm值/* Must drop socket now because of tproxy. */skb_orphan(skb);//调用netfilter,实现iptables功能,通过后调用ip_rcv_finishreturn NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, NULL, skb,dev, NULL,ip_rcv_finish);csum_error:IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_CSUMERRORS);
inhdr_error:IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
drop:kfree_skb(skb);
out:return NET_RX_DROP;
}

ip_rcv_finish函数
作用:
1)、确定数据包是转发还是在本机协议栈上传,如果是转发要确定输出网络设备和下一个接受栈的地址。
2)、解析和处理部分IP选项

 
static int ip_rcv_finish(struct sock *sk, struct sk_buff *skb)
{const struct iphdr *iph = ip_hdr(skb);struct rtable *rt;int err;/* if ingress device is enslaved to an L3 master device pass the* skb to its handler for processing*/skb = l3mdev_ip_rcv(skb);if (!skb)return NET_RX_SUCCESS;if (sysctl_ip_early_demux && !skb_dst(skb) && skb->sk == NULL) {const struct net_protocol *ipprot;int protocol = iph->protocol;//得到传输层协议/* 找到early_demux函数,如是tcp协议就调用,tcp_v4_early_demux */ipprot = rcu_dereference(inet_protos[protocol]);if (ipprot && ipprot->early_demux) {//对于socket报文,可以通过socket快速获取路由表err = ipprot->early_demux(skb);/* 调用该函数,将路由信息缓存到_skb->refdst */if (unlikely(err))goto drop_error;/* must reload iph, skb->head might have changed */iph = ip_hdr(skb);//重新获取ip头}}/**      Initialise the virtual path cache for the packet. It describes*      how the packet travels inside Linux networking.*//* 1.  为数据包初始化虚拟路径缓存,它描述了数据包是如何在linux网络中传播的 ;2.  通常从外界接收的数据包,skb->dst不会包含路由信息,暂时还不知道在何处会设置这个字段;3.  skb->dst该数据域包含了如何到达目的地址的路由信息,如果该数据域是NULL,就通过路由子系统函数ip_route_input_noref路由,ip_route_input_noref的输入参数有源IP地址、目的IP地址、服务类型、接受数据包的网络设备,根据这5个参数决策路由。*/if (!skb_valid_dst(skb)) {// 路由查询,决定后续处理:向上传递( ip_local_deliver)、转发(ip_forward)、丢弃err = ip_route_input_noref(skb, iph->daddr, iph->saddr,iph->tos, skb->dev);if (unlikely(err))goto drop_error;}#ifdef CONFIG_IP_ROUTE_CLASSIDif (unlikely(skb_dst(skb)->tclassid)) {struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);u32 idx = skb_dst(skb)->tclassid;st[idx&0xFF].o_packets++;//更新接收数据包数量st[idx&0xFF].o_bytes += skb->len;//更新接收数据包的长度st[(idx>>16)&0xFF].i_packets++;st[(idx>>16)&0xFF].i_bytes += skb->len;}
#endifif (iph->ihl > 5 && ip_rcv_options(skb))goto drop;rt = skb_rtable(skb);//得到路由表项,统计组播和广播报文if (rt->rt_type == RTN_MULTICAST) {IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST,skb->len);} else if (rt->rt_type == RTN_BROADCAST)IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST,skb->len);return dst_input(skb);/*ip_rcv_finish的结束是调用了dst_input,实际是调用存放在skb->dst->input的数据域。该函数确定了下一步对数据包的处理,根据数据包的目的地地址,skb->dst->input字段的信息主要由路由处理流程确定,可能是往本地协议栈上传就调用 ip_local_deliver,如果是转发就调用ip_forward */drop:kfree_skb(skb);return NET_RX_DROP;drop_error:if (err == -EXDEV)NET_INC_STATS_BH(dev_net(skb->dev), LINUX_MIB_IPRPFILTER);goto drop;
}

 2.ip_rcv_finish函数
作用:
1)、确定数据包是转发还是在本机协议栈上传,如果是转发要确定输出网络设备和下一个接受栈的地址。
2)、解析和处理部分IP选项

 
static int ip_rcv_finish(struct sock *sk, struct sk_buff *skb)
{const struct iphdr *iph = ip_hdr(skb);struct rtable *rt;int err;/* if ingress device is enslaved to an L3 master device pass the* skb to its handler for processing*/skb = l3mdev_ip_rcv(skb);if (!skb)return NET_RX_SUCCESS;if (sysctl_ip_early_demux && !skb_dst(skb) && skb->sk == NULL) {const struct net_protocol *ipprot;int protocol = iph->protocol;//得到传输层协议/* 找到early_demux函数,如是tcp协议就调用,tcp_v4_early_demux */ipprot = rcu_dereference(inet_protos[protocol]);if (ipprot && ipprot->early_demux) {//对于socket报文,可以通过socket快速获取路由表err = ipprot->early_demux(skb);/* 调用该函数,将路由信息缓存到_skb->refdst */if (unlikely(err))goto drop_error;/* must reload iph, skb->head might have changed */iph = ip_hdr(skb);//重新获取ip头}}/**      Initialise the virtual path cache for the packet. It describes*      how the packet travels inside Linux networking.*//* 1.  为数据包初始化虚拟路径缓存,它描述了数据包是如何在linux网络中传播的 ;2.  通常从外界接收的数据包,skb->dst不会包含路由信息,暂时还不知道在何处会设置这个字段;3.  skb->dst该数据域包含了如何到达目的地址的路由信息,如果该数据域是NULL,就通过路由子系统函数ip_route_input_noref路由,ip_route_input_noref的输入参数有源IP地址、目的IP地址、服务类型、接受数据包的网络设备,根据这5个参数决策路由。*/if (!skb_valid_dst(skb)) {// 路由查询,决定后续处理:向上传递( ip_local_deliver)、转发(ip_forward)、丢弃err = ip_route_input_noref(skb, iph->daddr, iph->saddr,iph->tos, skb->dev);if (unlikely(err))goto drop_error;}#ifdef CONFIG_IP_ROUTE_CLASSIDif (unlikely(skb_dst(skb)->tclassid)) {struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);u32 idx = skb_dst(skb)->tclassid;st[idx&0xFF].o_packets++;//更新接收数据包数量st[idx&0xFF].o_bytes += skb->len;//更新接收数据包的长度st[(idx>>16)&0xFF].i_packets++;st[(idx>>16)&0xFF].i_bytes += skb->len;}
#endifif (iph->ihl > 5 && ip_rcv_options(skb))goto drop;rt = skb_rtable(skb);//得到路由表项,统计组播和广播报文if (rt->rt_type == RTN_MULTICAST) {IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST,skb->len);} else if (rt->rt_type == RTN_BROADCAST)IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST,skb->len);return dst_input(skb);/*ip_rcv_finish的结束是调用了dst_input,实际是调用存放在skb->dst->input的数据域。该函数确定了下一步对数据包的处理,根据数据包的目的地地址,skb->dst->input字段的信息主要由路由处理流程确定,可能是往本地协议栈上传就调用 ip_local_deliver,如果是转发就调用ip_forward */drop:kfree_skb(skb);return NET_RX_DROP;drop_error:if (err == -EXDEV)NET_INC_STATS_BH(dev_net(skb->dev), LINUX_MIB_IPRPFILTER);goto drop;
}

ip_route_input会进行路由表查询,该函数直接或间接决定了报文之后要往何处传递。是进行本地传递还是转发。

我们可以看到如果报文没有被drop掉,那么报文最终会被dst_input(skb)处理。dst_input(skb)实际上执行的是skb->dst->input(skb)。而这里的input函数其实就是由ip_route_input决定的。

对于应该本地传递的报文,input指针会指向ip_local_deliver。对于该转发的报文,input会指向ip_forward

/**  Deliver IP Packets to the higher protocol layers.*/
int ip_local_deliver(struct sk_buff *skb)
{/*  *  Reassemble IP fragments.*/if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))return 0;}   return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,ip_local_deliver_finish);
}

我们知道,IPv4要将报文传送给上层协议(本地传递),那它需要对分段的报文进行重组,ip_defrag即完成报文重组。
然后由调用Netfilter决定是否调用ip_local_deliver_finish。

ip_local_deliver_finish

ret = ipprot->handler(skb);

static int ip_local_deliver_finish(struct sk_buff *skb)
{struct net *net = dev_net(skb->dev);__skb_pull(skb, ip_hdrlen(skb));  /* 跳过IP头部 *//* Point into the IP datagram, just past the header. *//* 设置传输层头部位置 */skb_reset_transport_header(skb);rcu_read_lock();{int protocol = ip_hdr(skb)->protocol; //取出ip头中的协议.int hash, raw;const struct net_protocol *ipprot;resubmit:// 若是raw socket发送的,需要做相应的处理,clone数据包raw = raw_local_deliver(skb, protocol); //得到raw socket, 如果不是raw socket,则返回0hash = protocol & (MAX_INET_PROTOS - 1);  // 计算传输层协议处理结构在inet_protos数组hash表中的位置ipprot = rcu_dereference(inet_protos[hash]); // 获取传输层协议处理指针if (ipprot != NULL) {int ret;//主要是ipprot是否有被当前主机注册if (!net_eq(net, &init_net) && !ipprot->netns_ok) { // 若获取到了对应传输层的处理结构if (net_ratelimit())printk("%s: proto %d isn't netns-ready\n",__func__, protocol);kfree_skb(skb);goto out;}//判断ipsec,并进行相关处理. if (!ipprot->no_policy) {if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {kfree_skb(skb);goto out;}nf_reset(skb);}//调用handler,进入相应的4层协议的处理.ret = ipprot->handler(skb);if (ret < 0) {  // 处理数据包失败,再次尝试protocol = -ret;goto resubmit;}IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);// 添加数据包处理统计信息} else {// 若没有找到相应传输层的处理函数if (!raw) {if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS);icmp_send(skb, ICMP_DEST_UNREACH,ICMP_PROT_UNREACH, 0);}} elseIP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);kfree_skb(skb);}}out:rcu_read_unlock();return 0;
}

UDP报文接收:ret = ipprot->handler(skb);

udp_rev

int udp_rcv(struct sk_buff *skb)
{struct net_data_s net_data;net_data.pskb = &skb;/* ecnt_dp_hook */ECNT_UDP_RCV_HOOK(ECNT_NET_UDP_RCV,&net_data);return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP);
}
EXPORT_SYMBOL(udp_rcv);

__udp4_lib_rcv函数

__udp4_lib_lookup_skb 是根据 skb 来寻找对应的socket,当找到以后将数据包放到
socket 的缓存队列⾥。如果没有找到,则发送⼀个⽬标不可达的 icmp 包。

ret = udp_queue_rcv_skb(sk, skb);

int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,int proto)
{struct sock *sk;struct udphdr *uh;unsigned short ulen;struct rtable *rt = skb_rtable(skb);__be32 saddr, daddr;struct net *net = dev_net(skb->dev);/**  Validate the packet.*/if (!pskb_may_pull(skb, sizeof(struct udphdr)))goto drop;		/* No space for header. */uh   = udp_hdr(skb);ulen = ntohs(uh->len);saddr = ip_hdr(skb)->saddr;daddr = ip_hdr(skb)->daddr;if (ulen > skb->len)goto short_packet;if (proto == IPPROTO_UDP) {/* UDP validates ulen. */if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))goto short_packet;uh = udp_hdr(skb);}if (udp4_csum_init(skb, uh, proto))goto csum_error;sk = skb_steal_sock(skb);if (sk) {struct dst_entry *dst = skb_dst(skb);int ret;if (unlikely(sk->sk_rx_dst != dst))udp_sk_rx_dst_set(sk, dst);ret = udp_queue_rcv_skb(sk, skb);sock_put(sk);/* a return value > 0 means to resubmit the input, but* it wants the return to be -protocol, or 0*/if (ret > 0)return -ret;return 0;}if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))return __udp4_lib_mcast_deliver(net, skb, uh,saddr, daddr, udptable, proto);sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);if (sk) {int ret;if (inet_get_convert_csum(sk) && uh->check && !IS_UDPLITE(sk))skb_checksum_try_convert(skb, IPPROTO_UDP, uh->check,inet_compute_pseudo);ret = udp_queue_rcv_skb(sk, skb);sock_put(sk);/* a return value > 0 means to resubmit the input, but* it wants the return to be -protocol, or 0*/if (ret > 0)return -ret;return 0;}if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))goto drop;nf_reset(skb);/* No socket. Drop packet silently, if checksum is wrong */if (udp_lib_checksum_complete(skb))goto csum_error;UDP_INC_STATS_BH(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);/** Hmm.  We got an UDP packet to a port to which we* don't wanna listen.  Ignore it.*/kfree_skb(skb);return 0;short_packet:net_dbg_ratelimited("UDP%s: short packet: From %pI4:%u %d/%d to %pI4:%u\n",proto == IPPROTO_UDPLITE ? "Lite" : "",&saddr, ntohs(uh->source),ulen, skb->len,&daddr, ntohs(uh->dest));goto drop;csum_error:/** RFC1122: OK.  Discards the bad packet silently (as far as* the network is concerned, anyway) as per 4.1.3.4 (MUST).*/net_dbg_ratelimited("UDP%s: bad checksum. From %pI4:%u to %pI4:%u ulen %d\n",proto == IPPROTO_UDPLITE ? "Lite" : "",&saddr, ntohs(uh->source), &daddr, ntohs(uh->dest),ulen);UDP_INC_STATS_BH(net, UDP_MIB_CSUMERRORS, proto == IPPROTO_UDPLITE);
drop:UDP_INC_STATS_BH(net, UDP_MIB_INERRORS, proto == IPPROTO_UDPLITE);kfree_skb(skb);return 0;
}/*__udp4_lib_rcv 函数是 Linux 内核中处理 IPv4 UDP 数据包接收的核心函数之一。它负责验证数据包的有效性、查找相关的套接字(socket)、并将数据包传递给正确的套接字进行处理。以下是对该函数主要逻辑的详细解释:验证数据包:
首先,函数检查是否有足够的空间来拉取 UDP 头部。如果没有,则跳转到 drop 标签,释放数据包。
提取 UDP 头部和长度信息,并检查 UDP 数据包的总长度是否超过了实际接收到的数据包长度。如果是,则跳转到 short_packet 标签,记录错误并释放数据包。
对于标准的 UDP 协议(proto == IPPROTO_UDP),进一步检查 UDP 长度是否合法,并调整数据包长度以匹配 UDP 头部中指定的长度。
校验和检查:
调用 udp4_csum_init 函数初始化校验和计算(如果需要的话)。如果校验和初始化失败,则跳转到 csum_error 标签。
快速路径处理(已连接的套接字):
检查数据包是否已经被绑定到一个套接字(通过 skb_steal_sock)。如果是,则直接将该数据包传递给该套接字处理,并返回结果。
多播处理:
如果数据包是广播或多播的,则调用 __udp4_lib_mcast_deliver 函数进行处理。
查找套接字:
使用 __udp4_lib_lookup_skb 函数根据源端口、目的端口和 UDP 表来查找对应的套接字。如果找到了套接字,则进行必要的校验和转换(如果需要的话),并将数据包传递给该套接字处理。
策略检查和防火墙处理:
调用 xfrm4_policy_check 函数检查数据包是否符合安全策略。如果不符合,则跳转到 drop 标签。
重置网络过滤(netfilter)标记。
无套接字情况处理:
如果没有找到对应的套接字,且校验和正确,则记录统计信息(无端口错误),发送 ICMP 端口不可达消息,并释放数据包。
错误处理:
如果遇到短数据包或校验和错误,则记录相应的调试信息和统计信息,并释放数据包。
函数通过返回 0 来表示成功处理数据包(无论是传递给套接字还是直接丢弃)。在快速路径处理中,如果 udp_queue_rcv_skb 函数返回一个大于 0 的值,表示需要重新提交输入处理(但通常是通过返回负数来表示协议错误),这里通过返回 -ret 来处理这种情况。然而,需要注意的是,在标准的 Linux 内核实现中,udp_queue_rcv_skb 函数通常不会返回大于 0 的值,因此这部分代码可能是一个防御性编程实践或特定于某些定制内核的实现*/

__udp4_lib_rcv 函数是 Linux 内核中处理 IPv4 UDP 数据包接收的核心函数之一。它负责验证数据包的有效性、查找相关的套接字(socket)、并将数据包传递给正确的套接字进行处理。以下是对该函数主要逻辑的详细解释:

  1. 验证数据包
    • 首先,函数检查是否有足够的空间来拉取 UDP 头部。如果没有,则跳转到 drop 标签,释放数据包。
    • 提取 UDP 头部和长度信息,并检查 UDP 数据包的总长度是否超过了实际接收到的数据包长度。如果是,则跳转到 short_packet 标签,记录错误并释放数据包。
    • 对于标准的 UDP 协议(proto == IPPROTO_UDP),进一步检查 UDP 长度是否合法,并调整数据包长度以匹配 UDP 头部中指定的长度。
  2. 校验和检查
    • 调用 udp4_csum_init 函数初始化校验和计算(如果需要的话)。如果校验和初始化失败,则跳转到 csum_error 标签。
  3. 快速路径处理(已连接的套接字)
    • 检查数据包是否已经被绑定到一个套接字(通过 skb_steal_sock)。如果是,则直接将该数据包传递给该套接字处理,并返回结果。
  4. 多播处理
    • 如果数据包是广播或多播的,则调用 __udp4_lib_mcast_deliver 函数进行处理。
  5. 查找套接字
    • 使用 __udp4_lib_lookup_skb 函数根据源端口、目的端口和 UDP 表来查找对应的套接字。如果找到了套接字,则进行必要的校验和转换(如果需要的话),并将数据包传递给该套接字处理。
  6. 策略检查和防火墙处理
    • 调用 xfrm4_policy_check 函数检查数据包是否符合安全策略。如果不符合,则跳转到 drop 标签。
    • 重置网络过滤(netfilter)标记。
  7. 无套接字情况处理
    • 如果没有找到对应的套接字,且校验和正确,则记录统计信息(无端口错误),发送 ICMP 端口不可达消息,并释放数据包。
  8. 错误处理
    • 如果遇到短数据包或校验和错误,则记录相应的调试信息和统计信息,并释放数据包。

函数通过返回 0 来表示成功处理数据包(无论是传递给套接字还是直接丢弃)。在快速路径处理中,如果 udp_queue_rcv_skb 函数返回一个大于 0 的值,表示需要重新提交输入处理(但通常是通过返回负数来表示协议错误),这里通过返回 -ret 来处理这种情况。然而,需要注意的是,在标准的 Linux 内核实现中,udp_queue_rcv_skb 函数通常不会返回大于 0 的值,因此这部分代码可能是一个防御性编程实践或特定于某些定制内核的实现

udp_queue_rcv_skb

int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{struct udp_sock *up = udp_sk(sk);int rc;int is_udplite = IS_UDPLITE(sk);/**	Charge it to the socket, dropping if the queue is full.*/if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))goto drop;nf_reset(skb);if (static_key_false(&udp_encap_needed) && up->encap_type) {int (*encap_rcv)(struct sock *sk, struct sk_buff *skb);/** This is an encapsulation socket so pass the skb to* the socket's udp_encap_rcv() hook. Otherwise, just* fall through and pass this up the UDP socket.* up->encap_rcv() returns the following value:* =0 if skb was successfully passed to the encap*    handler or was discarded by it.* >0 if skb should be passed on to UDP.* <0 if skb should be resubmitted as proto -N*//* if we're overly short, let UDP handle it */encap_rcv = ACCESS_ONCE(up->encap_rcv);if (encap_rcv) {int ret;/* Verify checksum before giving to encap */if (udp_lib_checksum_complete(skb))goto csum_error;ret = encap_rcv(sk, skb);if (ret <= 0) {UDP_INC_STATS_BH(sock_net(sk),UDP_MIB_INDATAGRAMS,is_udplite);return -ret;}}/* FALLTHROUGH -- it's a UDP Packet */}/** 	UDP-Lite specific tests, ignored on UDP sockets*/if ((is_udplite & UDPLITE_RECV_CC)  &&  UDP_SKB_CB(skb)->partial_cov) {/** MIB statistics other than incrementing the error count are* disabled for the following two types of errors: these depend* on the application settings, not on the functioning of the* protocol stack as such.** RFC 3828 here recommends (sec 3.3): "There should also be a* way ... to ... at least let the receiving application block* delivery of packets with coverage values less than a value* provided by the application."*/if (up->pcrlen == 0) {          /* full coverage was set  */net_dbg_ratelimited("UDPLite: partial coverage %d while full coverage %d requested\n",UDP_SKB_CB(skb)->cscov, skb->len);goto drop;}/* The next case involves violating the min. coverage requested* by the receiver. This is subtle: if receiver wants x and x is* greater than the buffersize/MTU then receiver will complain* that it wants x while sender emits packets of smaller size y.* Therefore the above ...()->partial_cov statement is essential.*/if (UDP_SKB_CB(skb)->cscov  <  up->pcrlen) {net_dbg_ratelimited("UDPLite: coverage %d too small, need min %d\n",UDP_SKB_CB(skb)->cscov, up->pcrlen);goto drop;}}if (rcu_access_pointer(sk->sk_filter) &&udp_lib_checksum_complete(skb))goto csum_error;if (sk_rcvqueues_full(sk, sk->sk_rcvbuf)) {UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_RCVBUFERRORS,is_udplite);goto drop;}rc = 0;ipv4_pktinfo_prepare(sk, skb);bh_lock_sock(sk);if (!sock_owned_by_user(sk))rc = __udp_queue_rcv_skb(sk, skb);else if (sk_add_backlog(sk, skb, sk->sk_rcvbuf)) {bh_unlock_sock(sk);goto drop;}bh_unlock_sock(sk);return rc;csum_error:UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_CSUMERRORS, is_udplite);
drop:UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS, is_udplite);atomic_inc(&sk->sk_drops);kfree_skb(skb);return -1;
}

sock_owned_by_user 判断的是⽤户是不是正在这个 socket 上进⾏系统调⽤( socket 被占⽤)。
如果没有,那就可以直接放到 socket 的接收队列中。
如果有,那就通过 sk_add_backlog 把数据包添加到 backlog 队列。 当⽤户释放的 socket 的时候,内核会检查 backlog 队列,如果有数据再移动到接收队列中。
sk_rcvqueues_full 接收队列如果满了的话,将直接把包丢弃。接收队列⼤⼩受内核参数
net.core.rmem_max 和 net.core.rmem_default 影响

udp_queue_rcv_skb 函数是 Linux 内核中用于处理接收到的 UDP 数据包并将其排队到相应套接字接收队列的函数。以下是该函数主要逻辑的详细解释:

  1. 安全策略检查
    • 使用 xfrm4_policy_check 函数检查数据包是否符合安全策略。如果不符合,则跳转到 drop 标签释放数据包。
  2. 重置网络过滤标记
    • 调用 nf_reset 函数重置数据包的网络过滤(netfilter)标记。
  3. 封装处理
    • 如果套接字被配置为需要封装(通过 udp_encap_needed 静态键和 up->encap_type 字段检查),则调用套接字的 encap_rcv 钩子函数处理数据包。
    • 如果 encap_rcv 钩子函数存在且返回非正值,表示数据包应该继续传递给 UDP 层处理。如果返回 0 或负值,则根据返回值进行相应的处理(记录统计信息或重新提交数据包)。
  4. UDP-Lite 特定处理
    • 如果套接字是 UDP-Lite 类型的,并且启用了部分校验和覆盖(UDPLITE_RECV_CC)且数据包的实际校验和覆盖长度小于套接字请求的校验和覆盖长度,则记录错误并跳转到 drop 标签。
  5. 校验和检查
    • 如果套接字上安装了过滤器(sk_filter)并且数据包的校验和检查失败,则跳转到 csum_error 标签。
  6. 接收队列检查
    • 检查套接字的接收队列是否已满。如果已满,则记录错误并跳转到 drop 标签。
  7. 数据包排队
    • 如果套接字当前没有被用户进程锁定(即不在阻塞接收操作中),则调用 __udp_queue_rcv_skb 函数将数据包添加到套接字的接收队列中。
    • 如果套接字被用户进程锁定,则尝试将数据包添加到套接字的backlog队列中。如果backlog队列也满了,则跳转到 drop 标签。
  8. 错误处理和统计
    • 如果在处理过程中遇到校验和错误或需要丢弃数据包,则跳转到 csum_error 或 drop 标签。在这些标签中,会记录相应的统计信息(如校验和错误、接收错误等),并释放数据包。
  9. 返回值
    • 如果数据包成功排队到套接字接收队列中,则返回 0。
    • 如果需要重新提交数据包(这在实际的内核实现中不常见,因为 __udp_queue_rcv_skb 通常不会返回这样的值),则通过返回 -ret(其中 ret 是 __udp_queue_rcv_skb 的返回值,但这里有一个逻辑上的不匹配,因为标准的 __udp_queue_rcv_skb 实现不会返回大于 0 的值)来处理。然而,请注意,这里的注释和代码实现之间可能存在不一致,实际的内核行为可能与此描述略有不同。

总的来说,udp_queue_rcv_skb 函数负责接收到的 UDP 数据包的校验、封装处理(如果需要的话)、队列检查以及最终的排队操作。如果处理过程中出现任何错误,数据包将被丢弃并记录相应的统计信息。

__udp_queue_rcv_skb

__udp_queue_rcv_skb()将skb添加到sk->sk_receive_queue队列上

static int __udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{int rc;if (inet_sk(sk)->inet_daddr) {sock_rps_save_rxhash(sk, skb);sk_mark_napi_id(sk, skb);sk_incoming_cpu_update(sk);}rc = sock_queue_rcv_skb(sk, skb);if (rc < 0) {int is_udplite = IS_UDPLITE(sk);/* Note that an ENOMEM error is charged twice */if (rc == -ENOMEM)UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_RCVBUFERRORS,is_udplite);UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS, is_udplite);kfree_skb(skb);trace_udp_fail_queue_rcv_skb(rc, sk);return -1;}return 0;}

__udp_queue_rcv_skb 函数是 Linux 内核中用于将接收到的 UDP 数据包排队到套接字接收队列的辅助函数。这个函数是 UDP 数据包接收路径中的一部分,负责处理数据包的一些前期准备工作,并将其传递给 sock_queue_rcv_skb 函数进行实际的排队操作。

以下是该函数的主要逻辑解释:

  1. 目的地址检查
    如果套接字的目的地址(inet_sk(sk)->inet_daddr)非零,表示这是一个已连接的 UDP 套接字(即已经通过 connect 系统调用绑定了远程地址的套接字)。对于这种情况,函数会执行一些与接收处理相关的额外步骤,包括:

    • 使用 sock_rps_save_rxhash 函数保存数据包的接收哈希值,这有助于后续的数据包在接收处理时的负载均衡。
    • 调用 sk_mark_napi_id 函数将套接字与数据包的 NAPI ID 关联起来,这同样有助于接收处理时的性能优化。
    • 调用 sk_incoming_cpu_update 函数更新套接字的入站 CPU 信息,这有助于在多核处理器上优化数据包的处理。
  2. 排队操作
    调用 sock_queue_rcv_skb 函数尝试将数据包添加到套接字的接收队列中。sock_queue_rcv_skb 函数会检查接收队列是否有足够的空间,如果有,则将数据包添加到队列中并返回 0;否则,返回错误码(通常是 -ENOMEM,表示内存不足)。

  3. 错误处理
    如果 sock_queue_rcv_skb 函数返回错误码,__udp_queue_rcv_skb 函数会执行相应的错误处理:

    • 对于 -ENOMEM 错误,会特别记录一个额外的统计信息(UDP_MIB_RCVBUFERRORS),以指示接收缓冲区错误。这是因为内存不足错误可能会被多次记录(例如,在尝试重新分配缓冲区时)。
    • 无论错误码是什么,都会记录一个接收错误统计信息(UDP_MIB_INERRORS),并释放数据包(通过调用 kfree_skb)。
    • 使用 trace_udp_fail_queue_rcv_skb 函数(如果启用了跟踪)记录错误跟踪信息。
    • 最后,函数返回 -1 表示数据包无法被排队。

需要注意的是,尽管函数名以双下划线开头(__),这通常表示该函数是内部的或私有的,不应在模块外部直接调用,但在 UDP 数据包的接收路径中,这个函数是被 udp_queue_rcv_skb 或其他类似函数调用的。

此外,随着 Linux 内核的发展,网络子系统的实现细节可能会发生变化,因此建议参考您正在使用的内核版本的源代码和文档

sock_queue_rcv_skb

--------------------------------------------------------------------------------------------------------------------------------

recvfrom 系统调⽤实现

用户如何收取报文?(Linux内核分析 - 网络[十二]:UDP模块 - 收发_udp recvfrom 提取报文-CSDN博客)

用户可以调用sys_recvfrom()或sys_recv()来接收报文,所不同的是,sys_recvfrom()可能通过参数获得报文的来源地址,而sys_recv()则不可以,但对接收报文并没有影响。在用户调用recvfrom()或recv()接收报文前,发给该socket的报文都会被添加到sk->sk_receive_queue上,recvfrom()和recv()要做的就是从sk_receive_queue上取出报文,拷贝到用户空间,供用户使用

代码⾥调⽤的 recvfrom 是⼀个 glibc 的库函数,该函数在执⾏后会将⽤户进⾏陷⼊到内核态,进⼊到 Linux 实现的系统调⽤ sys_recvfrom 

socket 数据结构中的 const struct proto_ops 对应的是协议的⽅法集合。每个协议都会实现不同的⽅法集,对于IPv4 Internet 协议族来说,每种协议都有对应的处理⽅法,如下:
对于 udp 来说,是通过 inet_dgram_ops 来定义的,其中注册了 inet_recvmsg ⽅法。

---------------------------------------------------------------------------------------------------------------------------------

​​​​​​​

​​​​​​​

相关的文章:

https://blog.csdn.net/qy532846454/article/details/6744252

https://blog.csdn.net/qy532846454/category_1385933.html

深入理解Linux网络技术内幕——IPv4 报文的接收(转发与本地传递)_ipv4协议报文交互-CSDN博客

linux协议栈: 

 https://blog.csdn.net/qy532846454/category_1385933.html

相关文章:

linux 网络子系统

__netif_receive_skb_core 是 Linux 内核网络子系统中一个非常重要的函数&#xff0c;它负责将网络设备驱动层接收到的数据包传递到上层协议栈进行处理。以下是对该函数的一些关键点的详细解析&#xff1a; 一、函数作用 __netif_receive_skb_core 函数是处理接收到的网络数据…...

JVM:垃圾回收器演进

文章目录 一、演进二、Shenandoah三、ZGC 一、演进 二、Shenandoah Shenandoah是由Red Hat开发的一款低延迟的垃圾收集器&#xff0c;Shenandoah并发执行大部分GC工作&#xff0c;包括并发的整理&#xff0c;堆大小对STW的时间基本没有影响。 三、ZGC ZGC是一种可扩展的低延…...

全新微软语音合成网页版源码,短视频影视解说配音网页版系统-仿真人语音

源码介绍 最新微软语音合成网页版源码&#xff0c;可以用来给影视解说和短视频配音。它是TTS文本转语言&#xff0c;API接口和PHP源码。 这个微软语音合成接口的源码&#xff0c;超级简单&#xff0c;就几个文件搞定。用的是官方的API&#xff0c;试过了&#xff0c;合成速度…...

大语言模型-对比学习-Contrastive Learning

一、对比学习概念 对比学习是一种特殊的无监督学习方法。 旨在通过拉近相关样本的距离并且推远不相关样本的距离&#xff0c;来学习数据表示。 通常使用一种高自由度、自定义的规则来生成正负样本。在模型预训练中有着广泛的应用。 二、对比学习小案例 对比学习主要分为三个…...

C++ 封装的用法

C(七)封装 封装&#xff0c;可以达到&#xff0c;对外提供接口&#xff0c;屏蔽数据&#xff0c;对内开放数据。 权限控制 struct 中所有行为和属性都是 public 的(默认)&#xff0c;此举也是为了 C兼容 C 语言&#xff0c; 因为 C 语言中没有权限的概念。 C中的 class 可以…...

【C++11:异常】

目录 抛异常标准书写格式 抛异常如何执行&#xff1f; 指定抛出异常类型&#xff1a; noexcept 关键字&#xff1a;throw 抛异常标准书写格式 抛异常如何执行&#xff1f; 当212行的异常被抛出&#xff0c;程序会重新返回函数func中&#xff0c;在函数中去寻找catch 语句的…...

Dify中HTTP请求节点的常见操作

HTTP节点包括API请求类型&#xff08;GET、POST、HEAD、PATCH、PUT、DELETE&#xff09;&#xff0c;鉴权类型&#xff08;无、API-Key基础、API-Key Bearer、API-Key自定义&#xff09;&#xff0c;HEADERS键值设置&#xff0c;PARAMS键值设置&#xff0c;BODY&#xff08;non…...

《大语言模型(赵鑫)》知识框图

...

【Android】性能实践—编码优化与布局优化学习笔记

编码优化 使用场景 如果需要拼接字符串&#xff0c;优先使用StringBuffer和StringBuilder进行凭借&#xff0c;他们的性能优于直接用加号进行拼接&#xff0c;因为使用加号连接符会创建多余的对象一般情况下使用基本数据类来代替封装数据类型&#xff08;比如int优于Integer&…...

如何合规与安全地利用专业爬虫工具,构建企业数据竞争优势

摘要&#xff1a; 本文深入探讨了在当今大数据时代&#xff0c;企业如何通过合规且安全的方式运用专业爬虫工具&#xff0c;有效收集并分析海量信息&#xff0c;进而转化为企业独有的数据优势。我们不仅会介绍最佳实践&#xff0c;还会讨论关键技术和策略&#xff0c;帮助企业…...

自动驾驶三维车道线检测系列—OpenLane数据集介绍

文章目录 1. 背景介绍2. OpenLane数据集详细描述2.1 数据集特点2.2 坐标系定义 3. 使用方法4. 结论 1. 背景介绍 自动驾驶技术的发展日新月异&#xff0c;而3D车道感知是其核心之一。本文将深入介绍OpenLane数据集——迄今为止规模最大、最接近真实世界的3D车道数据集。我们将…...

CMakeList学习笔记

设置项目&#xff1a;project project(planning VERSION 1.0.0 LANGUAGES CXX) # 项目的名字 版本 1.1.0 编程语言 CXX 设置包含目录&#xff1a;include_directories、targer_include_directories 设置编译类型&#xff1a;add_executable、add_library add_executable(demo d…...

将git默认的编辑器设置为vin

git默认编辑器现状 如下&#xff0c;很多linux发行版&#xff0c;未加修改的情况下&#xff0c;git的默认编辑器使用起来不太方便 Signed-off-by: root <rootxxx.COM># Please enter the commit message for your changes. Lines starting # with # will be ignored, a…...

ros2_control 6 自由度机械臂

系列文章目录 前言 ros2_control 是一个实时控制框架&#xff0c;专为普通机器人应用而设计。标准的 c 接口用于与硬件交互和查询用户定义的控制器命令。这些接口增强了代码的模块化和与机器人无关的设计。具体的应用细节&#xff0c;例如使用什么控制器、机器人有多少个关节以…...

Python 在自动化中的实际应用:用 Python 简化繁琐任务

文章目录 1、概述2、自动化文件和目录管理3.数据处理与分析4.网页爬虫5. 系统管理6。定时任务7.结语 1、概述 这篇文章将深入探讨Python在自动化中的实际应用&#xff0c;帮助您用Python简化繁琐任务。 我们将从多个方面入手&#xff0c;展示如何利用Python进行文件管理、数据…...

解释 Spring 框架的核心模块(如 IoC 容器、AOP )及其工作原理。描述如何使用 Spring Boot 快速搭建一个 RESTful Web服务?

Spring框架是一个广泛使用的Java企业级应用程序开发框架&#xff0c;它提供了一系列的模块来帮助开发者构建健壮、可测试、可维护的应用程序。 其中&#xff0c;最核心的模块包括IoC容器和AOP&#xff08;Aspect Oriented Programming&#xff0c;面向切面编程&#xff09;。 …...

数据分析详解

一、数据分析教程 1. 入门教程 在线课程&#xff1a;如Coursera、Udemy、网易云课堂等平台提供了大量数据分析的入门课程&#xff0c;涵盖统计学基础、Python/R语言编程、数据可视化等内容。书籍推荐&#xff1a;《Python数据分析实战》、《R语言实战》等书籍是数据分析入门的…...

SpringCloud之@FeignClient()注解的使用方式

FeignClient介绍 FeignClient 是 Spring Cloud 中用于声明一个 Feign 客户端的注解。由于SpringCloud采用分布式微服务架构&#xff0c;难免在各个子模块下存在模块方法互相调用的情况。比如订单服务要调用库存服务的方法&#xff0c;FeignClient()注解就是为了解决这个问题的…...

20.rabbitmq插件实现延迟队列

问题 前面谈到基于死信的延迟队列&#xff0c;存在的问题&#xff1a;如果第一个消息延时时间很长&#xff0c;而第二个消息延时时间很短&#xff0c;第二个消息并不会优先得到执行。 下载插件 地址&#xff1a;https://github.com/rabbitmq/rabbitmq-delayed-message-excha…...

TS如何处理js模块的类型?

现在很多插件都直接用ts开发了&#xff0c;本身包含了类型定义常见的第三方插件&#xff0c;都有’types/xxx’包&#xff0c;安装即可使用其他的&#xff0c;可通过declare module定义类型 比如&#xff1a; // someModule.js export function greet(name) {return Hello, $…...

GPS定位系统(VUE框架)

源码下载&#xff1a;小宅博客网 博主之前写的《GPS定位系统&#xff08;MVC框架&#xff09;》版本&#xff0c;并没有做到前后端分离&#xff0c;不太适合多人协作开发&#xff0c;这边博主分享一个基于asp.net web api vue3的GPS定位系统框架&#xff0c;本框架继承了MVC框…...

分布式光伏并网AM5SE-IS防孤岛保护装置介绍——安科瑞 叶西平

产品简介 功能&#xff1a; AM5SE-IS防孤岛保护装置主要适用于35kV、10kV及低压380V光伏发电、燃气发电等新能源并网供电系统。当发生孤岛现象时&#xff0c;可以快速切除并网点&#xff0c;使本站与电网侧快速脱离&#xff0c;保证整个电站和相关维护人员的生命安全。 应用…...

神奇的方法解决Navicat闪退

原因 打开Navicat操作上面的工具等就会闪退&#xff0c;原因竟然是屏幕划词&#xff01;&#xff01;&#xff01; 解决方法 看别人提到有道词典的划词功能的原因 我没有安装有道词典&#xff0c;但我安装豆包&#xff0c;它也有划词翻译的功能&#xff0c;关闭即可...

openmv学习笔记(24电赛笔记)

感光元件 openmv采用小孔摄像模式&#xff0c;将图像映射到感光原件上面&#xff0c;来传递图片&#xff0c;通过图片快速的刷新行成视频&#xff0c;在IDE中通过对感光原件的编辑可以控制视频的效果。 重置感光元件到默认状态 import sensor #导入感光元件这个库sensor.res…...

Linux shell编程学习笔记67: tracepath命令 追踪数据包的路由信息

0 前言 网络信息是电脑网络信息安全检查中的一块重要内容&#xff0c;Linux和基于Linux的操作系统&#xff0c;提供了很多的网络命令&#xff0c;今天我们研究tracepath命令。 Tracepath 在大多数 Linux 发行版中都是可用的。如果在你的系统中没有预装&#xff0c;请根据你的…...

生鲜云订单零售系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商品分类管理&#xff0c;商品信息管理&#xff0c;订单评价管理&#xff0c;订单管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;商品信息&#…...

BLE自适应跳频算法详解

前言 &#xff08;1&#xff09;自适应跳频算法是相当的简单&#xff0c;小学生都能够看懂&#xff0c;而且网上已经有相当多的关于自适应跳频算法的介绍。既然如此&#xff0c;为什么我还要写这样一篇博客呢&#xff1f; &#xff08;2&#xff09;原因很简单&#xff0c;我发…...

[Meachines] [Easy] Beep Elastix-CMS-LFI

信息收集 IP AddressOpening Ports10.10.10.7TCP:22, 25, 80, 110, 111, 143, 443, 993, 995, 3306 $ nmap -p- 10.10.10.7 --min-rate 1000 -sC -sV Nmap scan report for 10.10.10.7 (10.10.10.7) Host is up (0.53s latency). Not shown: 65486 filtered tcp ports (no-…...

甘肃麻花:酥脆香甜的陇原美味

在甘肃的美食画卷中&#xff0c;甘肃麻花以其独特的魅力占据着重要的一席之地。甘肃食家巷麻花&#xff0c;那金黄酥脆的外形&#xff0c;宛如一件件精美的艺术品。每一根麻花的纹理都清晰可见&#xff0c;缠绕交织&#xff0c;散发着诱人的光泽。 制作甘肃麻花是一门传统的手艺…...

C语言刷题小记2

前言 本篇博客还是为大家分享一些C语言的OJ题目&#xff0c;如果你感兴趣&#xff0c;希望大佬一键三连。多多支持。下面进入正文部分。 题目1竞选社长 分析&#xff1a;本题要求我们输入一串字符&#xff0c;并且统计个数的多少&#xff0c;那么我们可以通过getchar函数来获…...

JavaScript图片轮播

代码在文章最后面&#xff08;含图片URL&#xff09; 实现功能 按向左按钮图片显示上一张按向右按钮图片显示下一张每隔2000毫秒显示下一张图底部三个圆点显示当前的图片的编号 实现流程 初始化图片数组 创建一个包含图片URL的数组&#xff0c;轮播时会通过这个数组来切换图…...

MSSQL注入前置知识

简述 Microsoft SQL server也叫SQL server / MSSQL&#xff0c;由微软推出的关系型数据库&#xff0c;默认端口1433 常见搭配C# / .net IISmssql mssql的数据库文件 数据文件&#xff08;.mdf&#xff09;&#xff1a;主要的数据文件&#xff0c;包含数据表中的数据和对象信息…...

idea一键为实体类赋值

file -> settings -> plugins -> marketplace 把这个插件装上 找个实体&#xff0c;选中&#xff0c;altenter进入edit界面 我是选择只保留右边这种生成方法&#xff0c;然后选择ok 返回到那个实体&#xff0c;选择&#xff0c;altenter generate生成...

秋招突击——7/24——知识补充——JVM类加载机制

文章目录 引言类加载机制知识点复习类的生命周期1、加载2、连接——验证3、连接——准备4、连接——解析5、初始化 类加载器和类加载机制类加载器类加载机制——双亲委派模型 面试题整理1、类加载是什么2、类加载的过程是什么3、有哪些类加载器&#xff1f;4、双亲委派模型是什…...

如何在 Microsoft SQL Server 中增加字段-完整指南

在使用 Microsoft SQL Server (MSSQL) 进行数据库管理时,添加新字段(列)是一项常见的任务。无论你是需要存储额外的信息,还是调整数据模型以适应新的业务需求,本指南都将帮助你轻松完成这项操作。 目录 1. 使用 T-SQL 添加字段2. 使用 SQL Server Management Studio (SSMS) 添加…...

快手电商Android一面凉经(2024)

快手电商Android一面凉经(2024) 笔者作为一名双非二本毕业7年老Android, 最近面试了不少公司, 目前已告一段落, 整理一下各家的面试问题, 打算陆续发布出来, 供有缘人参考。今天给大家带来的是《快手电商Android一面凉经(2024)》。 面试职位: Android工程师 技术一面 面试形式…...

随机点名器

练习1 package lx;import java.io.*; import java.util.ArrayList; import java.util.Collections; import java.util.Random;/*需求&#xff1a;需求&#xff1a;有一个文件里面存储了班级同学的信息&#xff0c;每一个信息占一行。格式为&#xff1a;张三-男-23要求通过程序…...

添加动态云层

<template> <div class"topbox"> xx卫星管理 </div> <div class"selectbox"> <div class"title"> 卫星列表 </div> <el-table :data"tableData" style"width: 100%;height:230px;" …...

Spring Boot组成的分布式系统中实现日志跟踪

Spring Boot组成的分布式系统中实现日志跟踪 首发2024-07-25 08:54潘多编程 在分布式系统中&#xff0c;日志跟踪是一项非常重要的功能&#xff0c;它帮助开发者了解请求在整个系统中的流转过程&#xff0c;这对于调试、监控和故障排查至关重要。Spring Boot应用通常作为微服…...

GPT-4o Mini 模型的性能与成本优势全解析

GPT-4o Mini 模型的性能与成本优势全解析 &#x1f4c8; &#x1f31f; GPT-4o Mini 模型的性能与成本优势全解析 &#x1f4c8;摘要引言正文内容GPT-4o Mini 模型简介 &#x1f680;性能测试与对比 &#x1f4ca;应用场景 &#x1f310;自然语言处理对话系统内容生成 ✍️ &am…...

web前端 - HTML 基础知识大揭秘

HTML 大揭秘 什么是 HTML HTML&#xff08;Hyper Text Markup Language&#xff09;&#xff0c;中文译为超文本标记语言。其中&#xff0c;我们需要注意两个关键词。一个是 超文本&#xff0c;一个是 标记。所谓超文本&#xff0c;就是将不同空间的文字信息通过超链接的方式…...

HTML meta

<meta>标签用于提供html文档的元信息&#xff08;metadata&#xff09;。这些信息不会显示在页面上&#xff0c;但会被浏览器或搜索引擎用来识别页面的编码方式、关键字、描述、作者信息、刷新时间等。 基本语法 <meta name"属性名" content"属性值&q…...

【学习笔记】子集DP

背景 有一类问题和子集有关。 给你一个集合 S S S&#xff0c;令 T T T 为 S S S 的超集&#xff0c;也就是 S S S 所有子集的集合&#xff0c;求 T T T 中所有元素的和。 暴力1 先预处理子集的元素和 A i A_i Ai​&#xff0c;再枚举子集。 for(int s0; s<(1<…...

苦学Opencv的第十四天:人脸检测和人脸识别

Python OpenCV入门到精通学习日记&#xff1a;人脸检测和人脸识别 前言 经过了十三天的不懈努力&#xff0c;我们终于也是来到了人脸检测和人脸识别啦&#xff01;相信大家也很激动吧。接下来我们开始吧&#xff01; 人脸识别是基于人的脸部特征信息进行身份识别的一种生物识…...

PyTorch学习(1)

PyTorch学习&#xff08;1&#xff09; CIFAR-10数据集-图像分类 数据集来源是官方提供的&#xff1a; torchvision.datasets.CIFAR10()共有十类物品&#xff0c;需要用CNN实现图像分类问题。 代码如下&#xff1a;(CIFAR_10_Classifier_Self_1.py) import torch import t…...

三思而后行:计算机行业的决策智慧

在计算机行业&#xff0c;"三思而后行"这一原则显得尤为重要。在这个快速发展、技术不断更新换代的领域&#xff0c;每一个决策都可能对项目的成功与否产生深远的影响。以下是一篇关于在计算机行业中三思重要性的文章。 三思而后行&#xff1a;计算机行业的决策智慧 …...

Linux--Socket编程UDP

前文&#xff1a;Socket套接字编程 UDP协议特点 无连接&#xff1a;UDP在发送数据之前不需要建立连接&#xff0c;减少了开销和发送数据之前的时延。尽最大努力交付&#xff1a;UDP不保证可靠交付&#xff0c;主机不需要维持复杂的连接状态表。面向报文&#xff1a;UDP对应用层…...

《javaEE篇》--单例模式详解

目录 单例模式 饿汉模式 懒汉模式 懒汉模式(优化) 指令重排序 总结 单例模式 单例模式属于一种设计模式&#xff0c;设计模式就好比是一种固定代码套路类似于棋谱&#xff0c;是由前人总结并且记录下来我们可以直接使用的代码设计思路。 单例模式就是&#xff0c;在有…...

Java核心 - Lambda表达式详解与应用示例

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有疑问和建议&#xff0c;请私信或评论留言&#xff01; 前言 Lambda表达式是…...

算法通关:006_1二分查找

二分查找 查找一个数组里面是否存在num主要代码运行结果 详细写法自动生成数组和num&#xff0c;利用对数器查看二分代码是否正确 查找一个数组里面是否存在num 主要代码 /*** Author: ggdpzhk* CreateTime: 2024-07-27*/ public class cg {//二分查找public static boolean …...