网桥在内核的实现

我们知道netdevice有一个priv域,这个域用来保存设备的私有数据,当这个设备是一个网桥的时候,priv域也就指向一个struct net_bridge.

接下来看net_bridge以及相关的数据结构:
struct net_bridge
{
///自旋锁
	spinlock_t			lock;
///网桥所有端口的链表,其中每个元素都是一个net_bridge_port结构。
	struct list_head		port_list;
///加到这个网桥的物理设备
	struct net_device		*dev;
///这个锁是用来保护下面的那个hash链表。
	spinlock_t			hash_lock;
///保存forwarding database的一个hash链表(这个也就是地址学习的东东,所以通过hash能 快速定位),这里每个元素都是一个net_bridge_fsb_entry结构
	struct hlist_head		hash[BR_HASH_SIZE];
///这个结构没有被使用
	struct list_head		age_list;
	unsigned long			feature_mask;
#ifdef CONFIG_BRIDGE_NETFILTER
	struct rtable 			fake_rtable;
#endif
	unsigned long			flags;
#define BR_SET_MAC_ADDR		0x00000001

///stp相关的一些东西
	bridge_id			designated_root;
	bridge_id			bridge_id;
	u32				root_path_cost;
	unsigned long			max_age;
	unsigned long			hello_time;
	unsigned long			forward_delay;
	unsigned long			bridge_max_age;
	unsigned long			ageing_time;
	unsigned long			bridge_hello_time;
	unsigned long			bridge_forward_delay;

	u8				group_addr[ETH_ALEN];
	u16				root_port;
///当前使用的协议。
	enum {
		BR_NO_STP, 		/* no spanning tree */
		BR_KERNEL_STP,		/* old STP in kernel */
		BR_USER_STP,		/* new RSTP in userspace */
	} stp_enabled;

	unsigned char			topology_change;
	unsigned char			topology_change_detected;

///stp要用的一些定时器列表。
	struct timer_list		hello_timer;
	struct timer_list		tcn_timer;
	struct timer_list		topology_change_timer;
	struct timer_list		gc_timer;
	struct kobject			*ifobj;
};


struct net_bridge_port
{
///从属于的网桥设备
	struct net_bridge		*br;
///表示链接到这个端口的物理设备
	struct net_device		*dev;
	struct list_head		list;
///stp相关的一些参数。
	u8				priority;
	u8				state;
	u16				port_no;
	unsigned char			topology_change_ack;
	unsigned char			config_pending;
	port_id				port_id;
	port_id				designated_port;
	bridge_id			designated_root;
	bridge_id			designated_bridge;
	u32				path_cost;
	u32				designated_cost;
///端口定时器,也就是stp控制超时的一些定时器列表.(详细的需要去看stp的协议).
	struct timer_list		forward_delay_timer;
	struct timer_list		hold_timer;
	struct timer_list		message_age_timer;
	struct kobject			kobj;
	struct rcu_head			rcu;
};


struct net_bridge_fdb_entry
{
	struct hlist_node		hlist;
///桥的端口(最主要的两个域就是这个域和下面的mac地址域)
	struct net_bridge_port		*dst;
///当使用RCU策略,才用到
	struct rcu_head			rcu;
///引用计数
	atomic_t			use_count;
	unsigned long			ageing_timer;
///mac地址。
	mac_addr			addr;
	unsigned char			is_local;
	unsigned char			is_static;
};

通过下面的图能更好的理解这个结构:



接下来简要的介绍一下网桥的初始化。

网桥的初始化和一般网络设备的初始化很相似,只不过由于它是虚拟设备,因此这里还有一点不同。

首先来看内核的网络模块的初始化br_init,也就是初始化上面介绍的数据结构:

static int __init br_init(void)
{
	int err;
///stp的注册。
	err = stp_proto_register(&br_stp_proto);
	if (err < 0) {
		printk(KERN_ERR "bridge: can't register sap for STP\n");
		return err;
	}

///forwarding database的初始化
	err = br_fdb_init();
	if (err)
		goto err_out;
///网桥的netfilter钩子函数的初始化。
	err = br_netfilter_init();
	if (err)
		goto err_out1;
///注册到netdevice的通知链上
	err = register_netdevice_notifier(&br_device_notifier);
	if (err)
		goto err_out2;

	err = br_netlink_init();
	if (err)
		goto err_out3;
///安装网络设备的do_ioctl函数,也就是提供给用户空间ioctl接口。
	brioctl_set(br_ioctl_deviceless_stub);
	br_handle_frame_hook = br_handle_frame;

	br_fdb_get_hook = br_fdb_get;
	br_fdb_put_hook = br_fdb_put;

	return 0;
.........................................
	return err;
}


我们新建一个网桥,使用br_add_bridge,在这个函数中,主要是调用new_bridge_dev函数,下面我们主要就来看这个函数:

static struct net_device *new_bridge_dev(const char *name)
{
	struct net_bridge *br;
	struct net_device *dev;

///这里看到setup回调函数,是br_dev_setup(也就是网桥设备专用的)。setup函数的用途,可以看我以前写的网络设备初始化的blog。
	dev = alloc_netdev(sizeof(struct net_bridge), name,
			   br_dev_setup);

	if (!dev)
		return NULL;
///得到priv数据。
	br = netdev_priv(dev);

///接下来初始化br数据结构。
	br->dev = dev;

	spin_lock_init(&br->lock);
	INIT_LIST_HEAD(&br->port_list);
	spin_lock_init(&br->hash_lock);

///网桥优先级 32768(也就是默认是0x8000)
	br->bridge_id.prio[0] = 0x80;
	br->bridge_id.prio[1] = 0x00;

	memcpy(br->group_addr, br_group_address, ETH_ALEN);

	br->feature_mask = dev->features;
	br->stp_enabled = BR_NO_STP;
	br->designated_root = br->bridge_id;
	br->root_path_cost = 0;
	br->root_port = 0;
	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->topology_change = 0;
	br->topology_change_detected = 0;
	br->ageing_time = 300 * HZ;
///初始化网桥设备的netfilter相关域。
	br_netfilter_rtable_init(br);

	INIT_LIST_HEAD(&br->age_list);

	br_stp_timer_init(br);

	return dev;
}


加一个新端口到一个网桥使用br_add_if方法。这里就不详细介绍这个方法了,不过这里要注意,他会在sys文件系统下,生成一些相关的东西。要看sysfs的介绍,去看kernel的文档。

最后来看一下网桥的子系统在这个网络子系统的位置:




可以看到这里有很多的hook在ip层,基本都是netfilter子系统的东西。

这里网桥的输入帧的处理是通过br_handle_frame来处理的。而网桥的输出帧是通过br_dev_xmit来处理的。

当网络帧通过NIC的设备驱动被接收了之后,skb->dev被实例化为真实的设备,然后这个帧被放入网络栈,然后当be_handle_frame_finish之后调用br_pass_frame_up。我们来看这个函数的实现:

引用
static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
{
struct net_device *indev, *brdev = br->dev;

brdev->stats.rx_packets++;
brdev->stats.rx_bytes += skb->len;

indev = skb->dev;
///这步将真实的物理设备替换为虚拟的网桥设备。因此对3层来说就完全不知道物理设备的存在了。
skb->dev = brdev;
///调用netfiltel的相关hook。
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
netif_receive_skb);
}


当这个函数执行完毕后,会再次调用netif_receive_skb,他则会再次调用handle_bridge,而此时由于设备已经替换为虚拟的网桥设备,因此就会直接将包发往下层正确的协议处理。



你可能感兴趣的:(数据结构,.net,linux,网络协议,UP)