桥接-Linux实现
网桥设备抽象
网桥对Linux来说是虚拟设备,我们需要把一个或者多个真实设备绑定到网桥设备上,否则无法接收和传输报文。
当创建一个网桥时,必须告诉内核这个网桥绑定了那些接口。比如建立一个网桥br0,然后把eth0和eth1指派给br0,。此时eth0和eth1是网桥接口,它们不需要配置IP地址,可以把IP信息指定给网桥设备。
前面章节提到设备上传输报文要调用dev_queue_xmit执行,dev_queue_xmit函数会调用驱动程序的hard_start_xmit函数,网桥驱动程序使用这个函数查询要转发的数据库,选出正确的设备,如果查找失败就在网桥绑定的网卡上扩散报文。
桥接程序的初始化
桥接功能在br_init函数中初始化。初始化主要做以下事情:
- 函数首先调用stp_proto_register注册生成树协议。
- 初始化转发数据缓存结构net_bridge_fdb_entry。
- 注册netdeivce通知链
- 调用register_pernet_subsys函数注册网络子空间模块
- 调用br_netlink_init函数初始化NETLINK相关功能。
static int __init br_init(void)
{
int err;
BUILD_BUG_ON(sizeof(struct br_input_skb_cb) > FIELD_SIZEOF(struct sk_buff, cb));
err = stp_proto_register(&br_stp_proto);
if (err < 0) {
pr_err("bridge: can't register sap for STP\n");
return err;
}
err = br_fdb_init();
if (err)
goto err_out;
err = register_pernet_subsys(&br_net_ops);
if (err)
goto err_out1;
err = br_nf_core_init();
if (err)
goto err_out2;
err = register_netdevice_notifier(&br_device_notifier);
if (err)
goto err_out3;
err = register_switchdev_notifier(&br_switchdev_notifier);
if (err)
goto err_out4;
err = br_netlink_init();
if (err)
goto err_out5;
brioctl_set(br_ioctl_deviceless_stub);
#if IS_ENABLED(CONFIG_ATM_LANE)
br_fdb_test_addr_hook = br_fdb_test_addr;
#endif
#if IS_MODULE(CONFIG_BRIDGE_NETFILTER)
pr_info("bridge: filtering via arp/ip/ip6tables is no longer available "
"by default. Update your scripts to load br_netfilter if you "
"need this.\n");
#endif
return 0;
err_out5:
unregister_switchdev_notifier(&br_switchdev_notifier);
err_out4:
unregister_netdevice_notifier(&br_device_notifier);
err_out3:
br_nf_core_fini();
err_out2:
unregister_pernet_subsys(&br_net_ops);
err_out1:
br_fdb_fini();
err_out:
stp_proto_unregister(&br_stp_proto);
return err;
}
建立一个新网桥设备
br_add_bridge函数负责建立一个网桥设备。
br_del_bridge函数负责删除指定网桥设备。
br_add_if函数负责为网桥设备添加端口。
br_del_if函数负责为网桥设备删除端口。
网桥设备是虚拟设备,初始化会多一些额外的初始化工作。下面分析br_add_bridge函数代码:
- 使用alloc_netdev分配网络设备描述符,分配该结构时传入参数私有参数的长度是sizeof(struct net_bridge),同时传入专有的br_dev_setup函数对net_device相关字段初始化。
- 使用register_netdev函数向内核注册网络设备。
int br_add_bridge(struct net *net, const char *name)
{
struct net_device *dev;
int res;
dev = alloc_netdev(sizeof(struct net_bridge), name, NET_NAME_UNKNOWN,
br_dev_setup);
if (!dev)
return -ENOMEM;
dev_net_set(dev, net);
dev->rtnl_link_ops = &br_link_ops;
res = register_netdev(dev);
if (res)
free_netdev(dev);
return res;
}
注册后网桥设备核心数据结构组织成下图所示结构:
前边提到使用网桥设备在注册时使用br_dev_setup函数作为参数传递给alloc_netdev函数,所以br_dev_setup函数在申请netdevice_dev时被调用。
br_dev_setup函数调用ether_setup函数初始化相关字段。
将网卡的操作函数初始化为br_netdev_ops,包括启动关闭设备,报文发送和接收等。
设置网桥设备标记IFF_EBRIDGE | IFF_NO_QUEUE,即网桥默认没有队列机制。
void br_dev_setup(struct net_device *dev)
{
struct net_bridge *br = netdev_priv(dev);
eth_hw_addr_random(dev);
ether_setup(dev);
dev->netdev_ops = &br_netdev_ops;
dev->needs_free_netdev = true;
dev->ethtool_ops = &br_ethtool_ops;
SET_NETDEV_DEVTYPE(dev, &br_type);
dev->priv_flags = IFF_EBRIDGE | IFF_NO_QUEUE;
接下来初始化端口列表和转发数据库,并设置网桥ID。
br->dev = dev;
spin_lock_init(&br->lock);
INIT_LIST_HEAD(&br->port_list);
INIT_HLIST_HEAD(&br->fdb_list);
spin_lock_init(&br->hash_lock);
br->bridge_id.prio[0] = 0x80;
br->bridge_id.prio[1] = 0x00;
最后初始化br的MAC地址。并设置网桥的一些超时参数。
调用br_stp_timer_init初始化生成树协议定时器。
为br_fdb_cleanup函数初始化tasklet,用于清理转发数据库过期数据。
ether_addr_copy(br->group_addr, eth_stp_addr);
br->stp_enabled = BR_NO_STP;
br->group_fwd_mask = BR_GROUPFWD_DEFAULT;
br->group_fwd_mask_required = BR_GROUPFWD_DEFAULT;
br->designated_root = br->bridge_id;
br->bridge_max_age = br->max_age = 20 * HZ;
br->bridge_hello_time = br->hello_time = 2 * HZ;
br->bridge_forward_delay = br->forward_delay = 15 * HZ;
br->bridge_ageing_time = br->ageing_time = BR_DEFAULT_AGEING_TIME;
dev->max_mtu = ETH_MAX_MTU;
br_netfilter_rtable_init(br);
br_stp_timer_init(br);
br_multicast_init(br);
INIT_DELAYED_WORK(&br->gc_work, br_fdb_cleanup);
给网桥添加端口
给网桥添加端口使用br_add_if函数实现,这个函数首先对要添加到网桥的网口设备进行合法性检查。
/* called with RTNL */
int br_add_if(struct net_bridge *br, struct net_device *dev,
struct netlink_ext_ack *extack)
{
struct net_bridge_port *p;
int err = 0;
unsigned br_hr, dev_hr;
bool changed_addr;
/* Don't allow bridging non-ethernet like devices, or DSA-enabled
* master network devices since the bridge layer rx_handler prevents
* the DSA fake ethertype handler to be invoked, so we do not strip off
* the DSA switch tag protocol header and the bridge layer just return
* RX_HANDLER_CONSUMED, stopping RX processing for these frames.
*/
if ((dev->flags & IFF_LOOPBACK) ||
dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN