#include <linux/ip.h>
#include <linux/kernel.h>
void print_ip_addresses(struct sk_buff *skb) {
struct iphdr *iph;
// 检查skb是否为NULL,并确认其协议类型为IP
if (!skb || skb->protocol != htons(ETH_P_IP)) {
printk(KERN_WARNING "Invalid skb or not an IP packet\n");
return;
}
// 获取IP头部指针
iph = ip_hdr(skb);
// 打印源IP和目的IP地址
printk(KERN_INFO "Source IP: %pI4\n", &iph->saddr);
printk(KERN_INFO "Destination IP: %pI4\n", &iph->daddr);
}
// ... 其他代码,比如注册钩子函数来调用print_ip_addresses ...
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/etherdevice.h> // 引入以太网设备相关的定义
void print_mac_addresses(struct sk_buff *skb) {
struct ethhdr *eth;
// 验证skb是否非空,并确认其协议类型是否为以太网帧
if (!skb || skb->protocol != htons(ETH_P_IP)) {
printk(KERN_WARNING "Invalid skb or not an Ethernet frame\n");
return;
}
// 获取以太网头部指针
eth = eth_hdr(skb);
// 打印源MAC和目的MAC地址
printk(KERN_INFO "Source MAC: %pM\n", eth->h_source);
printk(KERN_INFO "Destination MAC: %pM\n", eth->h_dest);
}
// ... 其他代码,可能包括注册钩子函数来调用print_mac_addresses ...
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/etherdevice.h> // 引入以太网设备相关的定义
#include <linux/vlan/vlan.h> // 引入VLAN相关的定义
#include <linux/if_vlan.h>
void print_vlan_info(struct sk_buff *skb) {
struct vlan_hdr *vlan_h;
__be16 protocol;
// 检查skb是否非空
if (!skb) {
printk(KERN_WARNING "Invalid skb\n");
return;
}
// 获取协议类型,它可能是一个VLAN协议
protocol = skb->protocol;
// 检查协议是否为VLAN协议
if (protocol == htons(ETH_P_8021Q) || protocol == htons(ETH_P_8021AD)) {
// 获取VLAN头部指针
vlan_h = (struct vlan_hdr *)skb_mac_header(skb);
// 打印VLAN ID
printk(KERN_INFO "VLAN ID: %hu\n", ntohs(vlan_h->h_vlan_TCI) & VLAN_VID_MASK);
// 如果需要,还可以打印VLAN的其他信息,如优先级等
} else {
printk(KERN_INFO "VLAN tag is not present\n");
}
}
// ... 其他代码,可能包括注册钩子函数来调用print_vlan_info ...
在Linux内核中,若要在sk_buff结构体中打印VLAN(Virtual Local Area Network)信息,首先需要确认数据包是否包含VLAN标签。VLAN标签通常位于以太网头部之后,IP头部之前。
__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
函数进行处理。
三、函数实现细节
- 记录收包时间和设备:
- 函数首先会记录收包时间,并检查是否有包延迟。
- 同时,记录收包设备,即数据包是从哪个网络设备接收到的。
- 重置各层头部:
- 为了后续协议栈的正确处理,函数会重置网络层、传输层和 MAC 层的头部指针。
- 处理 VLAN 报文:
- 如果数据包是 VLAN 报文(即带有 VLAN 标签),函数会去除 VLAN 头,以便后续协议栈能够正确处理。
- 遍历协议处理链表:
- 函数会遍历两个链表:
ptype_all
和ptype_base
。这两个链表上挂载了多个packet_type
结构体,每个结构体对应一个具体的协议处理函数。 - 对于
ptype_all
链表上的每个packet_type
结构体,函数会调用其对应的协议处理函数(但通常最后一个除外,以优化性能)。 - 对于
ptype_base
链表,函数会根据数据包的协议类型选择相应的packet_type
结构体并调用其处理函数。
- 函数会遍历两个链表:
- 减少 skb 复制:
- 为了提高性能,函数在遍历链表时采用了一种优化策略,即利用
pt_prev
变量来减少最后一次协议处理时的 skb 复制。这是通过控制 skb 的引用计数来实现的。
- 为了提高性能,函数在遍历链表时采用了一种优化策略,即利用
- 统计和错误处理:
- 函数会更新处理包数的统计信息。
- 如果在处理过程中遇到错误(如无法分配内存、无法找到合适的协议处理函数等),函数会进行相应的错误处理。
四、总结
__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 这样相当于少调用了一次free pt_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_ACT if (skb->tc_verd & TC_NCLS) { skb->tc_verd = CLR_TC_NCLS(skb->tc_verd); goto ncls; } #endif if (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_ACT if (static_key_false(&ingress_needed)) { skb = handle_ing(skb, &pt_prev, &ret, orig_dev); if (!skb) goto out; } skb->tc_verd = 0; ncls: #endif if (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_free ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);/* 传递到上层*/ } else { drop: if (!deliver_exact) atomic_long_inc(&skb->dev->rx_dropped);//网卡丢包计数 else atomic_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
结构体的定义可能会随着内核版本的不同而有所变化,但通常包括以下几个关键部分:
- 设备基本信息:
char name[IFNAMSIZ]
:网络设备的名称,如eth0
、lo
等。unsigned long state
:设备的状态标志,如是否已启动、是否正在接收数据等。unsigned long flags
:设备的标志位,用于控制设备的行为,如是否支持多播、是否支持巨型帧等。
- 硬件地址:
unsigned char dev_addr[MAX_ADDR_LEN]
:设备的硬件地址(MAC 地址)。
- 统计信息:
- 结构体中包含了一系列的计数器,用于统计接收和发送的数据包数量、错误数量等信息。
- 队列和中断处理:
- 包括接收和发送队列的指针,以及中断处理函数的指针。