Linux内核构造数据包并发送(二)(dev_queue_xmit方式)

本文介绍Linux内核中使用dev_queue_xmit函数直接发送数据包的方法,包括修改MAC地址、调整skb数据结构等步骤,并讨论了不同发送方式下hook函数返回值的区别。

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

linux内核太构造数据包的第二种方式就是直接调用dev_queue_xmit函数,将构造完毕的数据包直接发送到网卡驱动。从NF框架来看,该函数的调用是在 POSTROUTING点之后了,也可以理解为直接通过调用二层的发送函数,将三层构造的数据包发送出去。该函数实际上会调用 skb->dev->hard_start_xmit,即对应网卡的驱动函数,将数据包直接发送的出去。 

       很显然,这个工作在二层的函数,发送数据包(数据包在二层的时候准确叫法应该是帧,我们这里是在三层直接调用的,权且还称作数据包)的方式是不需要再查路由了。 

但是,二层发送的时候是需要根据目的MAC来进行的。在第一种方法构造的数据包中,仅仅交换了IP地址,而没有对MAC做任何修改。这样直接调用 dev_queue_xmit是会产生问题的,并且该函数发送的内容应该是从二层头部开始,到数据包的结束。因此,如果三层构造的数据包,想调用该函数直接发送数据包的话,则需要修改数据包的源和目的MAC,并将skb->data指针指向MAC头部,以及skb->len的值也要加上头部的长度方法。以下是可参考的示例代码: 

 

C代码
  1. unsigned char mac_temp[ETH_ALEN] = {0};     
  2.      
  3.  struct ethhdr *mach = NULL;     
  4.      
  5.  ……     
  6.      
  7.  /*code…… 构造数据包的IP即上层协议及数据*/     
  8.      
  9.  ……     
  10.      
  11. /*交换源和目的MAC*/     
  12.      
  13.   mach = (struct ethhdr *)skb->mac.raw;     
  14.      
  15.    memcpy(mac_temp, (unsigned char *)mach->h_dest, ETH_ALEN);     
  16.      
  17.    memcpy(mach->h_dest, (unsigned char *)mach->h_source, ETH_ALEN);     
  18.      
  19.    memcpy(mach->h_source, mac_temp, ETH_ALEN);     
  20.      
  21. /*修改skb->data指针,使其指向MAC头部,并且增加skb->len*/     
  22.      
  23. skb_push(skb , ETH_HLEN);     
  24.      
  25. /*直接调用该函数,将数据包从网卡上发送出去*/     
  26.      
  27. ret = dev_queue_xmit(skb);    
 这里还要顺便说一下构造的数据包发送完毕之后,对于hook函数的返回值问题。 

   (1)第一种发送数据包的实现,对于send_reset函数的实现中,由于单独申请了nskb的内存,并构造的新的数据包。新数据包接着走NF的流程了。而对于原始的skb,就通过模块的返回值return NF_DROP做出了处理。 
   (2)第二种发送数据包的实现,若是基于已有数据包的基础上重新构造的数据包,那么实际上原始数据包的内容已经不复存在,而且调用完毕 dev_queue_xmit已将同一块缓冲区,只是填充了新数据的数据包发送出去,因此,这里已经没有原始数据包的存在了,需要返回 NF_STOLEN,告诉协议栈不用关心原始的包即可。否则,若是新数据包是单独申请的内存,那么对于原数据包还应该是返回NF_DROP.

<think>我们正在讨论的是在Linux内核网络子系统中,当使用dev_queue_xmit()发送skb失败时的资源处理问题。 根据内核网络栈的处理机制,当dev_queue_xmit()返回错误时,调用者需要负责释放skb。 原因: dev_queue_xmit()函数在发送数据包时,如果成功将skb放入队列(或者直接发送),则不会返回错误,且后续的释放将由网络栈在发送完成后处理(例如,在发送完成中断中)。 但是,如果dev_queue_xmit()返回错误(例如,返回NET_XMIT_DROP或其他错误码),则意味着skb没有被设备队列接受,因此调用者必须负责释放skb。 具体处理方式: 1. 检查dev_queue_xmit()的返回值。 2. 如果返回值不为0(表示错误),则需要释放skb。 示例代码: 在内核驱动中,我们通常可以看到如下模式: int err = dev_queue_xmit(skb); if (err) { // 发送失败,释放skb kfree_skb(skb); // 或者使用其他统计和错误处理 dev->stats.tx_dropped++; } 注意:在有些情况下,可能不是直接调用dev_queue_xmit,而是通过netif_rx等函数,但原理类似,当发送失败时需要释放skb。 引用内核代码中的例子(例如,在drivers/net/ethernet/xxx/xxx_tx.c中): 很多网络驱动在发送函数(ndo_start_xmit)中,如果返回NETDEV_TX_BUSY,则上层会调用dev_queue_xmit失败,然后会调用释放。 另外,在协议栈中,例如ip_finish_output2函数中,我们可以看到: if (dev_queue_xmit(skb) < 0) goto tx_error; 然后在tx_error标签处会进行统计和释放skb。 因此,总结:当dev_queue_xmit()返回错误时,调用者必须负责释放skb,以避免内存泄漏。 注意:在Linux内核中,释放skb的标准函数是kfree_skb()。 相关问题: 1. 在Linux内核中,dev_queue_xmit()的返回值有哪些?分别代表什么含义? 2. 除了dev_queue_xmit(),还有哪些发送网络数据包的函数?它们失败时如何处理skb? 3. 如何在内核模块中正确统计网络发送失败的数据包? 4. 在发送网络数据包时,如何避免频繁的发送失败(例如,队列满)?</think>### Linux内核中`dev_queue_xmit()`失败时的SKB资源处理 在Linux内核网络编程中,当使用`dev_queue_xmit()`发送SKB失败时,**必须手动释放SKB资源**,否则会导致内存泄漏。以下是详细处理流程: --- ### 一、失败处理机制 #### 1. 核心原则 ```c if (dev_queue_xmit(skb) != NETDEV_TX_OK) { kfree_skb(skb); // 必须手动释放SKB stats->tx_dropped++; // 更新统计计数器 } ``` - **NETDEV_TX_OK** (0)发送成功,SKB将由网络栈后续释放 - **非零返回值**:发送失败,调用者负责释放资源[^1] #### 2. 典型错误码及含义 | **错误码** | **值** | **原因** | 处理方式 | |----------------------|--------|---------------------------|------------------------| | `NETDEV_TX_BUSY` | 1 | 设备队列已满 | 延迟重试或立即释放 | | `NETDEV_TX_LOCKED` | 2 | 设备队列被锁 | 需等待解锁 | | `NET_XMIT_DROP` | 3 | 数据包被主动丢弃 | 立即释放 | | `NET_XMIT_CN` | 4 | 显式拥塞通知 | 需调整流量控制策略 | --- ### 、完整处理流程 #### 1. 发送失败时的标准操作 ```c int err = dev_queue_xmit(skb); switch (err) { case NETDEV_TX_OK: // 发送成功 // SKB将由net_tx_action()或设备中断处理程序释放 break; case NETDEV_TX_BUSY: // 队列满 netif_stop_queue(dev); // 暂停队列防止持续失败[^1] schedule_delayed_work(&my_retry_work, HZ/10); // 10ms后重试 break; default: // 其他错误 kfree_skb(skb); // 立即释放资源 dev->stats.tx_dropped++; // 更新统计 break; } ``` #### 2. 重试机制实现 ```c // 重试工作队列函数 void retry_work(struct work_struct *work) { struct my_device *priv = container_of(work, ...); if (netif_queue_stopped(priv->dev)) { netif_wake_queue(priv->dev); // 唤醒队列[^1] priv->retry_count++; if (priv->retry_count > MAX_RETRIES) { kfree_skb(priv->pending_skb); // 超限释放 priv->stats.tx_aborted_errors++; } } } ``` --- ### 三、资源释放原理 #### 1. SKB生命周期管理 ```mermaid graph LR A[alloc_skb] --> B[填充数据] B --> C{dev_queue_xmit} C -->|成功| D[网络栈释放] C -->|失败| E[kfree_skb] ``` #### 2. `kfree_skb()`内部操作 1. 减少SKB引用计数 2. 释放数据区内存(`skb->data`) 3. 释放SKB结构体(`struct sk_buff`) 4. 更新`sock_wmem_alloc`等计数 --- ### 四、最佳实践 1. **错误统计** 在释放前更新网络设备统计: ```c dev->stats.tx_errors++; // 总错误计数 dev->stats.tx_dropped++; // 丢弃计数 dev->stats.tx_fifo_errors++; // 队列满错误 ``` 2. **调试支持** 启用CONFIG_DEBUG_NET可检测未释放的SKB: ```bash echo 1 > /proc/sys/net/core/skb_debug dmesg | grep 'skb leak' ``` 3. **拥塞控制** 当连续失败时,应主动降低发送速率: ```c if (dev->stats.tx_dropped > DROP_THRESHOLD) { dev->tx_queue_len /= 2; // 减半队列长度 pr_warn("Congestion control activated on %s\n", dev->name); } ``` --- ### 五、与其他发送函数对比 | **函数** | 失败时是否需释放SKB | 适用场景 | |----------------------|----------------------|------------------------| | `dev_queue_xmit()` | **是** | 通用网络设备发送 | | `netif_rx()` | 否 | 软中断接收路径 | | `netif_receive_skb()`| 否 | 协议栈入口 | | `dev_direct_xmit()` | **是** | XDP快速路径发送 | > ⚠️ **关键区别**:直接操作设备队列的函数(如`dev_queue_xmit`)失败时需手动释放,而走协议栈接收路径的函数由内核自动管理[^1]。 --- ### 相关问题 1. 当`dev_queue_xmit()`返回`NETDEV_TX_BUSY`时,如何实现高效的重试机制? 2. 在多队列网卡场景下,`dev_queue_xmit()`失败处理有何不同? 3. 如何通过`/proc/net/dev`监控`tx_dropped`统计? 4. SKB的`destructor`回调在资源释放中起什么作用? [^1]: xxx_tx_timeout() 函数会调用 linux内核提供的netif_wake_queue()函数,重新启动设备发送队列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值