乐于分享
好东西不私藏

Linux Bridge 源码剖析:eth0 是如何“退化”成一根网线的?

Linux Bridge 源码剖析:eth0 是如何“退化”成一根网线的?

把 eth0 加入网桥后,它就不再是网卡,而是网桥上的一个端口。

这个变化发生在内核里,不是比喻。eth0 的收包函数被替换成网桥的处理函数,发包函数也不再被协议栈直接调用。

br_add_if:注册网桥端口

执行 ip link set eth0 master br0 时,内核调用 br_add_if()

源码路径:net/bridge/br_if.c:673

intbr_add_if(struct net_bridge *br, struct net_device *dev,struct netlink_ext_ack *extack){structnet_bridge_port *p;// 创建端口结构体     p = new_nbp(br, dev);// 注册收包处理函数    err = netdev_rx_handler_register(dev, br_get_rx_handler(dev), p);// 标记为网桥端口     dev->priv_flags |= IFF_BRIDGE_PORT;// 加入端口列表     list_add_rcu(&p->list, &br->port_list);return0;}

关键代码就一行:

netdev_rx_handler_register(dev, br_get_rx_handler(dev), p);

这行代码把 eth0 的收包函数替换成 br_handle_frame()。从此,eth0 收到的所有数据帧都交给网桥处理。

netdev_rx_handler_register:替换收包函数

源码路径:net/core/dev.c:5570

intnetdev_rx_handler_register(struct net_device *dev,rx_handler_func_t *rx_handler,void *rx_handler_data){if (dev->rx_handler)return -EBUSY;     rcu_assign_pointer(dev->rx_handler, rx_handler);     rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);return0;}

dev->rx_handler 原本是 NULL,现在指向 br_handle_frame

收包流程:eth0 → 网桥

  

物理网卡收到数据帧后,驱动把数据封装成 sk_buff,然后调用 netif_receive_skb()

源码路径:net/core/dev.c:5539

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc,struct packet_type **pt_prev){structnet_device *orig_dev = skb->dev;// 调用注册的 rx_handlerif (orig_dev->rx_handler) {        ret = orig_dev->rx_handler(&skb);if (ret == RX_HANDLER_CONSUMED)return NET_RX_SUCCESS;   }return NET_RX_SUCCESS;}

如果 orig_dev->rx_handler 不为空(即 eth0 已加入网桥),就调用 rx_handler,也就是 br_handle_frame()

br_handle_frame:网桥接管收包

源码路径:net/bridge/br_input.c:220(简化版)

rx_handler_result_tbr_handle_frame(struct sk_buff **pskb){structsk_buff *skb = *pskb;structnet_bridge_port *p = br_port_get_rcu(skb->dev);// 学习源 MAC   br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, 0);// 查目的 MAC   dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);if (dst) {// 转发到其他端口      br_forward(dst->dst, skb, false, vid);   } else {// 泛洪      br_flood(br, skb, BR_PKT_UNICAST, falsetrue, vid);   }return RX_HANDLER_CONSUMED;}

eth0 收到的数据帧,在这里被网桥接管。eth0 自己不做任何处理,只是把数据交给网桥,由网桥查 FDB 表决定转发到哪个端口。

br_pass_frame_up:上送协议栈

如果目的 MAC 是网桥自己(br0 的 MAC),调用 br_pass_frame_up()

源码路径:net/bridge/br_input.c:36

staticintbr_pass_frame_up(struct sk_buff *skb){structnet_device *brdev = BR_INPUT_SKB_CB(skb)->brdev;// 切换 dev   skb->dev = brdev;// 上送协议栈return netif_receive_skb(skb);}

关键:skb->dev 从 eth0 切换到 br0。协议栈看到的设备是 br0,不是 eth0。

这就是为什么 eth0 没有IP:协议栈根本收不到 eth0 的数据,只能收到 br0 的数据。

发包流程:协议栈 → br0 → eth0

  

主机协议栈发包时,调用 br0 的发送函数 br_dev_xmit()

源码路径:net/bridge/br_device.c:38

netdev_tx_tbr_dev_xmit(struct sk_buff *skb, struct net_device *dev){structnet_bridge *br = netdev_priv(dev);structnet_bridge_fdb_entry *dst;constunsignedchar *dest;// 提取目的 MAC   dest = eth_hdr(skb)->h_dest;// 查 FDB 表   dst = br_fdb_find_rcu(br, dest, vid);if (dst) {// 转发到对应端口      br_forward(dst->dst, skb, false, vid);   } else {// 泛洪      br_flood(br, skb, BR_PKT_UNICAST, falsetrue, vid);   }return NETDEV_TX_OK;}

协议栈发包给 br0,br0 查 FDB 表,决定从哪个端口发出。如果查到目的 MAC 在 eth0 端口,就调用 br_forward(eth0, skb)

br_forward:发送到端口

源码路径:net/bridge/br_forward.c:42

staticvoidbr_forward(struct net_bridge_port *to, struct sk_buff *skb,bool local_rcv, u16 vid){if (to->state == BR_STATE_FORWARDING) {       br_dev_queue_push_xmit(to, skb);   }}

最终调用 dev_queue_xmit(),把数据交给 eth0 的驱动发送。

为什么 eth0 不能有 IP

加入网桥后,eth0 的收包函数被替换,协议栈收不到 eth0 的数据。

如果 eth0 配了 IP,ping eth0 的 IP 地址,数据帧到达 eth0 后:

  1. eth0 驱动收到帧 → br_handle_frame()
  2. br_handle_frame() 查 FDB 表,发现目的 MAC 不是网桥自己
  3. 转发到其他端口(或泛洪)
  4. 协议栈收不到这个包

所以 eth0 的 IP 无效。主机 IP 必须配在 br0 上。

验证:eth0 的 rx_handler

可以用 crash 或 /proc 验证:

# 查看 eth0 的 rx_handlercat /proc/net/dev | grep eth0# 查看网桥端口bridge link show# 输出示例2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> master br0 state forwarding priority 32 cost 19

master br0 表示 eth0 已加入网桥,state forwarding 表示端口状态正常。

数据包完整路径

以 VM1(tap0)ping 网关为例:

① 外部网络发包到主机

网关 → 物理交换机 → eth0 驱动

② eth0 驱动收到帧

eth0 驱动 → netif_receive_skb() → __netif_receive_skb_core()

③ 调用 rx_handler

__netif_receive_skb_core() → dev->rx_handler (br_handle_frame)

④ 网桥查 FDB 表

br_handle_frame() → br_fdb_find_rcu(网关 MAC)

⑤ 转发到 tap0

br_fdb_find_rcu() 返回 tap0 → br_forward(tap0, skb) → tap0 驱动 → VM1

eth0 在整个流程中只扮演"物理端口"角色,不参与任何决策。

排错:为什么主机失联

执行 ip link set eth0 master br0 后,主机网络断了。

原因:IP 还在 eth0 上,但 eth0 已经是网桥端口,协议栈收不到 eth0 的数据。

解决:

# 删除 eth0 的 IPip addr flush eth0# 给 br0 配 IPip addr add 172.16.0.10/16 dev br0ip route add default via 172.16.0.1 dev br0

验证:

ping 172.16.0.1

通了就说明配置正确。

总结

"eth0 退化成网线"的核心:

  1. 收包:eth0 的 rx_handler 被替换成 br_handle_frame,所有数据帧交给网桥处理
  2. 发包:协议栈通过 br0 发包,网桥查 FDB 表后,选择 eth0 端口发出
  3. IP 必须在 br0 上:协议栈只能收到 br0 的数据,eth0 的 IP 无效

这是内核源码的真实行为,不是比喻。