iphdr与tcphdr详解(skb_header_pointer函数分析)

本文深入剖析Linux 2.6.26下获取TCP信息的方法,重点讲解skb_header_pointer函数及其作用。通过分析源码,解释了该函数如何从sk_buff中获取TCP头部信息。同时,详细解读了IP头和TCP头的结构,包括iphdr和tcphdr中的各个字段及其在网络传输中的意义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

linux 2.6.26 下获取tcp信息:  
tcph=skb_header_pointer(skb, protoff, sizeof(tcph), &tcph);    //skb_header_pointer是个库函数
skb_header_pointer函数
该函数本身很简单,定义如下:
/* #include <linux/skbuff.h>*/
static inline void *skb_header_pointer(const struct sk_buff *skb, int offset, int len, void *buffer)
{
 int hlen = skb_headlen(skb);
 if (hlen - offset >= len)
  return skb->data + offset;
 if (skb_copy_bits(skb, offset, buffer, len) < 0)
  return NULL;
 return buffer;
}
其中参数为:
skb:数据包struct sk_buff的指针
offset:相对数据起始头(如IP头)的偏移量
len:数据长度
buffer:缓冲区,大小不小于len

其中skb_headlen()定义为:
/* #include <linux/skbuff.h> */
static inline unsigned int skb_headlen(const struct sk_buff *skb)
{
 return skb->len - skb->data_len;
}
其中skb->len是数据包长度,在IPv4中就是单个完整IP包的总长,但这些数据并不一定都在当前内存页;skb->data_len表示在其他页的数据长度,因此skb->len - skb->data_len表示在当前页的数据大小。

/* net/core/skbuff.c */
/* Copy some data bits from skb to kernel buffer. */
int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
{
 int i, copy;
 int start = skb_headlen(skb);
 if (offset > (int)skb->len - len)
  goto fault;
 /* Copy header. */
 if ((copy = start - offset) > 0) {
// 拷贝在当前页面中的部分
  if (copy > len)
   copy = len;
  memcpy(to, skb->data + offset, copy);
  if ((len -= copy) == 0)
   return 0;
  offset += copy;
  to     += copy;
 }
// 拷贝本skb中其他碎片中的部分
 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  int end;
  BUG_TRAP(start <= offset + len);
  end = start + skb_shinfo(skb)->frags[i].size;
  if ((copy = end - offset) > 0) {
   u8 *vaddr;
   if (copy > len)
    copy = len;
   vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
   memcpy(to,
          vaddr + skb_shinfo(skb)->frags[i].page_offset+
          offset - start, copy);
   kunmap_skb_frag(vaddr);
   if ((len -= copy) == 0)
    return 0;
   offset += copy;
   to     += copy;
  }
  start = end;
 

<think>我们正在讨论Linux内核网络处理中的skb_reset_network_header函数的使用。这个函数用于重置套接字缓冲区(skb)中的网络层头部指针。 根据之前提供的引用[3],我们看到了alloc_skb函数的实现,它用于分配skb。而引用[4]提到了IPoIB头部结构,这涉及到网络层头部的处理。 因此,我们需要解释skb_reset_network_header的作用以及如何使用它。 在Linux内核中,sk_buff结构(简称skb)是网络数据包在内核中的表示。一个skb包含多个指针,用于标记数据包中各个层次的头部位置: - 传输层头部(如TCP/UDP) - 网络层头部(如IP) - 链路层头部(如以太网头部) skb_reset_network_header函数用于将网络层头部指针(network_header)重置为数据区的起始位置(即head指针指向的位置加上data指针的偏移量,但通常我们会在构建数据包时调整这些指针)。 函数原型(在include/linux/skbuff.h中): static inline void skb_reset_network_header(struct sk_buff *skb) 使用场景:当我们分配了一个skb并准备构建网络层数据包时,通常需要设置网络层头部的位置。例如,在分配skb后,我们可能将数据包的负载(payload)放入skb的数据区,然后设置网络层头部指针指向数据区的某个位置(即网络层头部的起始位置)。如果我们已经将网络层头部放在数据区的开头,那么就可以调用skb_reset_network_header(skb)来将network_header指向数据区的起始位置(即skb->data)。 注意:在调用skb_reset_network_header之前,通常需要确保skb->data指向网络层头部的起始位置。 步骤: 1. 分配skb(例如使用alloc_skb)。 2. 使用skb_reserve调整data指针,为各层头部预留空间(可选,但常见做法)。 3. 将网络层协议的数据(如IP头)复制到skb的数据区中,并确保skb->data指向该网络层头部的起始位置。 4. 调用skb_reset_network_header(skb)来设置network_header。 示例代码片段(假设我们正在构建一个IP数据包): ```c #include <linux/skbuff.h> #include <linux/ip.h> struct sk_buff *skb; struct iphdr *iph; // 分配一个足够大的skb,包括链路层头部、IP头部和负载 skb = alloc_skb(len + LL_HEADER_LEN + IP_HEADER_LEN, GFP_ATOMIC); if (!skb) return -ENOMEM; // 预留空间:将data指针向后移动,为链路层头部和网络层头部预留空间 skb_reserve(skb, LL_HEADER_LEN + IP_HEADER_LEN); // 现在,将data指针向前移动IP_HEADER_LEN,这样我们就可以开始填充IP头了 skb_push(skb, IP_HEADER_LEN); // 获取指向IP头部的指针 iph = (struct iphdr *)skb->data; // 填充IP头部 iph->version = 4; iph->ihl = 5; // 5个32位字,即20字节 // ... 其他字段 // 重置网络层头部指针:此时skb->data指向IP头部的起始位置,所以我们可以重置网络层头部 skb_reset_network_header(skb); // 现在我们可以使用网络层头部相关的辅助函数,例如ip_hdr(skb)来获取IP头 // 注意:在设置好网络层头部后,我们可以继续添加传输层头部和负载 // 例如,添加TCP头部和负载 skb_push(skb, TCP_HEADER_LEN); // 填充TCP头部... // 设置传输层头部 skb_reset_transport_header(skb); // 设置传输层头部指针 ``` 注意:上面的示例中,我们使用skb_push来调整data指针,以便为IP头部预留空间(在之前已经预留了链路层和IP层的基础上,因为我们之前预留了LL_HEADER_LEN+IP_HEADER_LEN,然后通过skb_push(skb, IP_HEADER_LEN)将data指针向前移动IP_HEADER_LEN,这样我们就有了IP头部的空间)。 另外,我们也可以不在一开始预留空间,而是分步进行。但通常,预留空间是常见的做法。 重要辅助函数: - skb_reset_network_header: 将network_header设置为当前data指针的位置。 - skb_set_network_header: 可以设置network_header为相对于head指针的偏移量(如果你知道确切的偏移量)。 - ip_hdr(skb): 返回指向IP头部的指针(在已经设置好network_header后使用)。 引用[3]提到了alloc_skb函数,引用[4]提到了网络层头部结构(ipoib_header),这都我们的主题相关。 因此,skb_reset_network_header的使用是在构建或修改网络数据包时,确保网络层头部指针正确指向数据包中的网络层头部起始位置。 注意:在接收路径中,当网络设备驱动接收到一个数据包并分配了skb后,通常也会设置这些头部指针,以便上层协议栈处理。但此时,驱动可能会调用类似eth_type_trans的函数来设置协议类型,并设置头部指针。 相关问题: 1. 在Linux内核中,如何分配一个套接字缓冲区(skb)? 2. 除了skb_reset_network_header,还有哪些用于设置skb头部指针的函数? 3. 在接收数据包时,网络设备驱动是如何处理skb的头部指针的? 4. 如何访问skb中的传输层头部? 我们将在回答中引用相关的引用[3]和[4]。</think>### Linux 内核中 `skb_reset_network_header` 的使用 在 Linux 内核网络协议栈中,`skb_reset_network_header` 是处理套接字缓冲区(`struct sk_buff`,简称 **skb**)的关键函数,用于重置网络层头部指针。以下是详细说明: --- #### **功能作用** - **核心目的**:将 `skb->network_header` 指针重置为当前 `skb->data` 的位置,表明**网络层头部起始于此**。 - **使用场景**: - 构建新的网络层数据包(如 IP 报文)时。 - 修改或重组 skb 后需重新定位网络层头部。 - 在驱动接收路径中初始化 skb 的协议头位置(参考引用[4]的 IPOIB 头部处理流程)[^4]。 --- #### **函数原型依赖** ```c #include <linux/skbuff.h> void skb_reset_network_header(struct sk_buff *skb); ``` - **参数**:`skb` 目标套接字缓冲区。 - **关联操作**: - `skb_push(skb, len)`:扩展数据区头部空间(常用于添加协议头)。 - `skb_set_network_header(skb, offset)`:直接设置 `network_header` 的偏移量。 --- #### **典型使用流程** 以下示例展示如何构建一个 IP 数据包: ```c struct sk_buff *skb; struct iphdr *iph; // 1. 分配 skb(参考引用[3]的 alloc_skb 优化)[^3] skb = alloc_skb(len + LL_HEADER_LEN + IP_HEADER_LEN, GFP_ATOMIC); if (!skb) return -ENOMEM; // 2. 预留空间:链路层头 + IP 头 skb_reserve(skb, LL_HEADER_LEN + IP_HEADER_LEN); // 3. 填充负载数据(如 TCP/UDP 报文) skb_put(skb, payload_len); memcpy(skb->data, payload, payload_len); // 4. 添加 IP 头部空间 skb_push(skb, IP_HEADER_LEN); iph = (struct iphdr *)skb->data; // 5. 填充 IP 头字段 iph->version = 4; iph->ihl = 5; iph->tot_len = htons(skb->len); // ... 其他字段初始化 // 6. 关键步骤:重置网络层头部指针 skb_reset_network_header(skb); // 此时 network_header = data // 7. 设置协议类型(可选) skb->protocol = htons(ETH_P_IP); ``` --- #### **关键注意事项** 1. **顺序要求**: - 必须在 `skb_push` 扩展头部空间**后调用**,确保 `skb->data` 指向网络层头部起始位置。 - 若未正确重置,上层协议栈(如 IP 层)无法通过 `ip_hdr(skb)` 获取头部。 2. **性能优化**: - `alloc_skb` 类似(引用[3]),此函数设计为 `inline` 以减少函数调用开销[^3]。 3. **接收路径处理**: - 在网卡驱动中,收到数据包后需根据协议类型调用 `skb_reset_network_header`,以便协议栈解析(参考引用[4]的 IPOIB 处理流程)[^4]。 --- #### **常见问题分析** - **Q**: 为何调用后 `ip_hdr(skb)` 仍返回错误地址? **A**: 检查 `skb->data` 是否指向 IP 头部起始位置。可能需要先执行 `skb_push` 或调整偏移量。 - **Q**: 能否用于传输层头部重置? **A**: 否!传输层需使用 `skb_reset_transport_header`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值