桥数据包处理函数——br_handle_frame_finish(七)

本文详细解析了Linux桥接器中br_handle_frame_finish函数的工作原理,包括数据包接收条件判断、数据包类型的处理策略及转发逻辑。通过具体代码示例介绍了数据包如何根据桥接器状态和类型进行转发或丢弃。

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

上一节我们了解到,数据包如何走进桥,这一节我们简单看看,入口帧处理函数br_handle_frame_finish.
作用:br_handle_frame_finish函数主要是决策将不同类别的数据包做不同的分发路径。

其函数处理的过程如下图所示:




首先判断该数据包是否符合桥转发的条件:
    (1)桥端口状态是否是开启状态,如果没有开启则丢掉数据包
    (2)是否允许从该桥上转发,如果不允许,则直接返回0
获得桥转发的条件以后,开始判断数据包的类型:
    (1)判断此时桥的标志为允许做哪些事情,学习还是扩展
  如果学习的标志位被至位,则更新数据转发表。否则继续向下走
    (2)根据多播或者广播报文的类型决定数据包的去留
(3)判断此时端口的状态,如果是学习状态,则将数据包丢弃
  (要注意的是:桥的端口状态(和上面的flag不冲突,上面的flag表示网桥可以做的事情)state表示网桥端口所处于的状态)
在处理完一些需要预备的事情之后,就要为数据包的转发开始做准备了
    (1)网桥设备是否处于混杂模式,如果是则建立副本,为发往本地做个备份
  (注意的是,所有网桥端口绑定的设备都会处于混杂模式,因为 网桥运行必须此模式。但除非明确的对其进行配置,否则网桥自己是不会处于混杂模式                的)
(2)在次判断广播还是多播地址
   广播地址:仅仅设置副本,进行广播转发和发往本地
多播地址:先查多播地址转发表,如果存在,设置副本,进行多播转发,原始数据包指向NULL,如果已经传送至本地,则会释放副本,不进行本地转发,否则重新转发到本地
(3)不是广播或者多播
   判断是否本地地址,如果是本地地址,则将原始数据包指向NULL,发往本地。
    否则进行数据包转发


/* note: already called with rcu_read_lock */
int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	const unsigned char *dest = eth_hdr(skb)->h_dest;
	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
	struct net_bridge *br;
	struct net_bridge_fdb_entry *dst;
	struct net_bridge_mdb_entry *mdst;
	struct sk_buff *skb2;
	bool unicast = true;
	u16 vid = 0;

	if (!p || p->state == BR_STATE_DISABLED)
		goto drop;
    /*判断是否允许进入桥内,如果没有开启vlan则所有的数据包都可以进入,
      如果开启了vlan则根据vlan相应的规则,从桥上进行数据包转发*/
	if (!br_allowed_ingress(p->br, nbp_vlan_group_rcu(p), skb, &vid))
		goto out;

	/* insert into forwarding database after filtering to avoid spoofing */
	br = p->br;
	/*如果可以学习,则学习数据包的原地址*/
	if (p->flags & BR_LEARNING)
		br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, false);

	if (!is_broadcast_ether_addr(dest) && is_multicast_ether_addr(dest) &&
	    br_multicast_rcv(br, p, skb, vid))
		goto drop;
    /*桥的端口状态(和上面的flag不冲突,上面的flag表示网桥可以做的事情)
	  state表示网桥端口所处于的状态*/
	if (p->state == BR_STATE_LEARNING)
		goto drop;

	BR_INPUT_SKB_CB(skb)->brdev = br->dev;

	/* The packet skb2 goes to the local host (NULL to skip). */
	skb2 = NULL;

	if (br->dev->flags & IFF_PROMISC)
		skb2 = skb;

	dst = NULL;

	if (IS_ENABLED(CONFIG_INET) && skb->protocol == htons(ETH_P_ARP))
		br_do_proxy_arp(skb, br, vid, p);

	if (is_broadcast_ether_addr(dest)) {
		skb2 = skb;
		unicast = false;
	} else if (is_multicast_ether_addr(dest)) {
		mdst = br_mdb_get(br, skb, vid);
		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
		    br_multicast_querier_exists(br, eth_hdr(skb))) {
			if ((mdst && mdst->mglist) ||
			    br_multicast_is_router(br))
				skb2 = skb;
			br_multicast_forward(mdst, skb, skb2);
			skb = NULL;
			if (!skb2)
				goto out;
		} else
			skb2 = skb;

		unicast = false;
		br->dev->stats.multicast++;
	} else if ((dst = __br_fdb_get(br, dest, vid)) &&
			dst->is_local) {
		skb2 = skb;
		/* Do not forward the packet since it's local. */
		skb = NULL;
	}

	if (skb) {
		if (dst) {
			dst->used = jiffies;
			br_forward(dst->dst, skb, skb2);
		} else
			br_flood_forward(br, skb, skb2, unicast);/*扩撒帧*/
	}
    /*将副本传入本地*/
	if (skb2)
		return br_pass_frame_up(skb2);

out:
	return 0;
drop:
	kfree_skb(skb);
	goto out;
}

数据包发送有两个地方,一个是转发出去br_forward或者br_flood_forward,一个是发往本地br_pass_frame_up,关于数据包的转发,会在下节看到


<think>好的,用户现在想要了解Linux内核中`__dev_forward_skb`函数的源码分析。首先,我需要回忆一下这个函数的作用和在内核网络栈中的位置。 用户之前问过关于`use_napi`的问题,现在转向另一个内核函数,可能是对网络数据包处理流程感兴趣。需要确认用户的基础,是否熟悉网络驱动的基本概念,比如SKB、网络设备结构等。 首先,我需要查找`__dev_forward_skb`的源码位置。根据之前的知识,这个函数通常用于在网络设备之间转发数据包,可能在`net/core/dev.c`文件中。接下来,我需要分析这个函数的参数、返回值以及具体实现步骤。 用户可能需要了解这个函数在内核中的调用场景,比如接、路由或者虚拟设备的数据转发。需要解释函数的关键步骤,比如克隆SKB、设置设备、更新统计信息等。 同时,要注意用户可能的深层需求,比如调试网络问题或者开发内核模块,这时候需要指出相关的注意事项,比如并发处理、内存管理、性能影响等。 需要确保回答结构清晰,分点说明,包括函数作用、源码分析、调用场景、注意事项以及示例调用链。还要检查是否需要对比其他函数,比如`dev_forward_skb`和`__dev_forward_skb`的区别,是否有下划线的函数是内部使用的。 最后,要确认回答是否准确,避免技术错误,比如参数是否正确,函数是否真的执行克隆操作,以及错误处理流程是否恰当。可能需要参考最新的内核源码或文档来验证信息的正确性。</think>在Linux内核网络子系统中,`__dev_forward_skb` 是实现网络设备间数据包转发的核心函数。以下从源码层面进行详细分析(基于Linux 5.x内核版本): --- ### 一、函数定义 ```c // 文件位置:net/core/dev.c int __dev_forward_skb(struct net_device *dev, struct sk_buff *skb) { return ____dev_forward_skb(dev, skb, false); } ``` 该函数实际是`____dev_forward_skb`的包装,第三个参数`false`表示不需要校验MAC头(L2头) --- ### 二、核心实现(____dev_forward_skb) ```c static int ____dev_forward_skb(struct net_device *dev, struct sk_buff *skb, bool check_mac_header) { /* 1. 校验网络设备状态 */ if (!(dev->flags & IFF_UP)) return -ENETDOWN; /* 2. 数据包克隆(避免原始skb被修改)*/ skb = skb_clone(skb, GFP_ATOMIC); if (!skb) return -ENOMEM; /* 3. 重置skb控制块 */ skb->dev = dev; // 设置目标设备 skb->skb_iif = dev->ifindex; // 更新输入接口索引 skb->tstamp = 0; // 清除时间戳 skb->pkt_type = PACKET_HOST; // 重置包类型 /* 4. MAC头校验(根据参数决定)*/ if (check_mac_header && !skb_valid_dst(skb)) skb_reset_mac_header(skb); /* 5. 递交给网络栈 */ return netif_rx_internal(skb); // 最终通过软中断处理 } ``` --- ### 三、关键操作解析 1. **设备状态检查** 通过`IFF_UP`标志确认目标设备是否处于激活状态。如果设备未启动(如`ifconfig down`),立即返回`-ENETDOWN`错误 2. **SKB克隆必要性** 使用`skb_clone`创建浅拷贝(仅复制控制结构,数据区共享),原因: - 原始skb可能被其他路径继续使用(如协议栈处理) - 转发过程可能需要修改skb元数据(如目标MAC地址) 3. **控制块重置细节** - `skb->dev`:将数据包归属设备切换至目标设备 - `skb_iif`:更新输入接口索引用于路由决策 - 清除`tstamp`避免时间戳混淆 4. **MAC头处理策略** 当`check_mac_header=true`时(如`dev_forward_skb`调用),会执行: ```c skb_reset_mac_header(skb); // 重置mac_header指针 skb->mac_len = skb->network_header - skb->mac_header; // 重新计算MAC头长度 ``` --- ### 四、典型调用场景 1. **网络接转发** 在`br_forward.c`中,当网桥决定转发时: ```c void br_flood(struct net_bridge *br, struct sk_buff *skb,...) { __dev_forward_skb(br_port->dev, skb); } ``` 2. **VLAN设备跨虚拟接口转发** 处理802.1Q标签时,在`8021q`模块中跨VLAN设备传递数据 3. **MACVLAN/IPVLAN设备** 实现容器网络虚拟化时,将数据从物理设备转发到虚拟接口 --- ### 五、内存管理注意事项 1. **引用计数变化** - 克隆操作使`skb->users`计数+1 - 最终`netif_rx_internal`会消耗这个引用 2. **错误处理流程** 若克隆失败(内存不足): ```c kfree_skb(skb); // 释放原始skb dev->stats.tx_dropped++; // 更新设备统计 ``` --- ### 六、性能影响点 1. **原子内存分配** `GFP_ATOMIC`标志在中断上下文使用,可能因内存压力导致丢包 2. **无DMA重映射** 直接复用原始数据区,适合本地转发但需注意: - 物理设备需要支持同一内存区域多设备访问 - 某些NIC硬件可能要求数据对齐方式 --- ### 、相关函数对比 | 函数 | 区别点 | |-----|-------| | `dev_forward_skb` | 调用`____dev_forward_skb`时设置`check_mac_header=true` | | `dev_queue_xmit` | 用于上层协议发送,包含完整的排队规则处理 | | `netif_receive_skb` | 处理接收路径,包含协议分发逻辑 | --- ### 八、典型调用链示例 ``` br_handle_frame_finishbr_flood → __dev_forward_skb → netif_rx_internal → enqueue_to_backlog (softirq上下文) → ____napi_schedule ``` 通过`perf-tools`观察实际调用: ```bash perf probe --add '__dev_forward_skb' perf record -e probe:__dev_forward_skb -aR sleep 10 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值