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

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

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>我们正在分析`__dev_queue_xmit`和`br_dev_xmit`之间的调用关系。根据之前的上下文,我们知道`__dev_queue_xmit`是网络数据包发送的核心函数,而 `br_dev_xmit`是桥接设备(网桥)的发送函数。关键点:1.当数据包需要通过网桥发送时,会调用网桥设备的发送函数,即`br_dev_xmit`。2.在`br_dev_xmit`函数中,网桥会根据目标MAC地址和转发表决定数据包应该从哪个端口转发出去。3.对于每个需要转发的端口,网桥会克隆数据包(如果需要),然后调用 `dev_queue_xmit`(最终调用 `__dev_queue_xmit`)将数据包发送到该端口对应的网络设备。因此,调用关系是:`br_dev_xmit`在决定转发端口后,会调用`dev_queue_xmit`(进而调用 `__dev_queue_xmit`)来实际发送数据包。具体步骤:1.数据包进入网桥设备(作为桥设备)的发送函数`br_dev_xmit`。2.在`br_dev_xmit`中,网桥处理逻辑(如学习、转发、过滤等)后,对于需要转发的数据包,会遍历需要发送的端口。3.对于每个端口,网桥可能会修改数据包的源MAC地址(如果使用了网桥的MAC地址),然后通过`br_forward`函数将数据包发送到指定端口。4.在 `br_forward`中,最终会调用`__br_forward`,然后调用`deliver_clone`(如果需要克隆)或直接调用 `br_dev_queue_push_xmit`。5.在 `br_dev_queue_push_xmit`中,会设置数据包发送的设备(即网桥端口对应的设备),然后调用`dev_queue_xmit`。因此,`br_dev_xmit`会间接调用 `dev_queue_xmit`(即 `__dev_queue_xmit`)来发送数据包。代码流程示例(基于Linux内核源码,如4.16+):1.`br_dev_xmit`函数(在`net/bridge/br_device.c`中):```cnetdev_tx_tbr_dev_xmit(struct sk_buff*skb,struct net_device *dev){//...网桥处理逻辑if(is_broadcast_ether_addr(dest))br_flood(br,skb, BR_PKT_BROADCAST, false);elseif (is_multicast_ether_addr(dest))br_flood(br,skb, BR_PKT_MULTICAST, false);elsebr_forward(br_get_port(dev,skb->dev),skb, false);// ...}```2. `br_forward`会调用 `__br_forward`,然后:```cvoid__br_forward(const structnet_bridge_port*to, structsk_buff *skb){// ...处理数据包,然后调用:br_dev_queue_push_xmit(to->br,to,skb);}```3.`br_dev_queue_push_xmit`函数:```cvoidbr_dev_queue_push_xmit(struct net_bridge*br, structnet_bridge_port*port,struct sk_buff*skb){//设置数据包发送的设备为端口设备skb->dev= port->dev;//然后调用 dev_queue_xmitdev_queue_xmit(skb);}```4. `dev_queue_xmit`是一个封装函数,它会调用`__dev_queue_xmit`。因此,调用链为:`br_dev_xmit` →`br_forward` →`__br_forward`→ `br_dev_queue_push_xmit` →`dev_queue_xmit`→ `__dev_queue_xmit`注意:在网桥转发过程中,如果数据包需要发送到多个端口(如广播或组播),则会克隆数据包,每个端口发送一个副本。总结:`br_dev_xmit`作为网桥设备的发送函数,在处理完网桥逻辑后,会通过调用 `dev_queue_xmit`(最终调用 `__dev_queue_xmit`)将数据包发送到各个端口设备。相关问题:1.网桥转发过程中如何避免循环?2.网桥在转发数据包时如何进行MAC地址学习?3.当网桥端口设备调用`__dev_queue_xmit`时,数据包会经过哪些排队规则(qdisc)?</think>### `__dev_queue_xmit` 与 `br_dev_xmit` 调用关系分析 在 Linux 网络栈中,`__dev_queue_xmit` 和 `br_dev_xmit` 是网桥转发路径上的关键协作函数,它们的调用关系如下: #### 1. 核心调用关系 ```mermaid graph LR A[br_dev_xmit] --> B[网桥转发决策] B -->|单播| C[br_forward] B -->|广播/组播| D[br_flood] C & D --> E[__br_forward] E --> F[br_dev_queue_push_xmit] F --> G[dev_queue_xmit] G --> H[__dev_queue_xmit] ``` #### 2. 详细调用流程 1. **网桥入口** 当数据包通过网桥设备发送时,内核调用网桥的发送函数: ```c // net/bridge/br_device.c netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev) { // 1. 获取目标MAC和网桥实例 const unsigned char *dest = eth_hdr(skb)->h_dest; struct net_bridge *br = netdev_priv(dev); // 2. 转发决策 if (is_broadcast_ether_addr(dest)) br_flood(br, skb, BR_PKT_BROADCAST); else if (is_multicast_ether_addr(dest)) br_flood(br, skb, BR_PKT_MULTICAST); else br_forward(br_get_port(dev, skb->dev), skb, false); // 单播转发 } ``` 2. **转发执行** 在 `__br_forward()` 中准备最终发送: ```c // net/bridge/br_forward.c void __br_forward(const struct net_bridge_port *to, struct sk_buff *skb) { // 设置出口设备 skb->dev = to->dev; // 最终推送到发送队列 br_dev_queue_push_xmit(to->br, to, skb); } ``` 3. **调用 `__dev_queue_xmit`** 通过 `dev_queue_xmit()` 间接调用: ```c // net/bridge/br_device.c void br_dev_queue_push_xmit(...) { dev_queue_xmit(skb); // 实际调用 __dev_queue_xmit } ``` [^1] #### 3. 关键协作机制 1. **设备切换** - `br_dev_xmit` 接收时 `skb->dev` 是**网桥设备**(如 `br0`) - 在 `__br_forward` 中重定向 `skb->dev` 为**物理端口设备**(如 `eth0`) - 使 `__dev_queue_xmit` 能正确选择物理设备的发送队列 2. **克隆处理** 广播/组播场景下,网桥会克隆数据包: ```c // net/bridge/br_forward.c void br_flood(...) { list_for_each_entry_rcu(p, &br->port_list, list) { struct sk_buff *skb2; if (should_deliver(p, skb)) { skb2 = skb_clone(skb, GFP_ATOMIC); // 克隆数据包 __br_forward(p, skb2); // 每个端口独立发送 } } } ``` 3. **转发控制** - 在调用 `__dev_queue_xmit` 前执行: - MAC 地址学习 (`br_fdb_update`) - STP 状态检查 (`br_forwardable`) - VLAN 标签处理 (`br_handle_vlan`) #### 4. 性能优化 1. **快速路径** 现代内核中通过 `br_handle_frame_finish` 实现快速转发路径,直接调用 `br_forward` 避免额外处理 2. **XDP 支持** 网桥支持 XDP 快速路径: ```c // net/bridge/br_device.c br_dev_xmit() { if (static_branch_unlikely(&br_xdp_needed_key)) { br_handle_xdp(...); // XDP 快速处理 } } ``` 3. **无锁转发** 使用 RCU 保护端口列表,避免转发时的锁竞争 --- ### 相关问题 1. **网桥如何处理目的地址为网桥自身 MAC 的数据包?** 网桥会将其提交给上层协议栈而不是转发[^2]。 2. **当 `__dev_queue_xmit` 返回错误时网桥如何响应?** 网桥会更新端口统计信息释放数据包,不会重试[^1]。 3. **如何查看网桥转发的数据包统计信息?** 使用 `bridge -s link show` 命令查看端口转发计数。 4. **VLAN 网桥转发与普通网桥有何区别?** VLAN 网桥在 `__br_forward` 前会添加/剥离 VLAN 标签(通过 `br_handle_vlan`)。 5. **网桥转发过程中如何防止循环?** 通过生成树协议(STP)阻塞冗余端口,在 `br_forwardable` 中检查端口状态[^2]。 [^1]: 参考内核源码 `net/core/dev.c` 和 `net/bridge/` 相关实现 [^2]: 基于网桥设备处理逻辑和原始套接字捕获机制
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值