深入理解Linux网络技术内幕 第16章 桥接-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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值