把 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, false, true, 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, false, true, 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 后:
eth0 驱动收到帧 → br_handle_frame()br_handle_frame()查 FDB 表,发现目的 MAC 不是网桥自己转发到其他端口(或泛洪) 协议栈收不到这个包
所以 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 19master 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 驱动 → VM1eth0 在整个流程中只扮演"物理端口"角色,不参与任何决策。
排错:为什么主机失联
执行 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 退化成网线"的核心:
收包:eth0 的 rx_handler被替换成br_handle_frame,所有数据帧交给网桥处理发包:协议栈通过 br0 发包,网桥查 FDB 表后,选择 eth0 端口发出 IP 必须在 br0 上:协议栈只能收到 br0 的数据,eth0 的 IP 无效
这是内核源码的真实行为,不是比喻。
夜雨聆风