我们知道netdevice有一个priv域,这个域用来保存设备的私有数据,当这个设备是一个网桥的时候,priv域也就指向一个struct net_bridge.
接下来看net_bridge以及相关的数据结构:
通过下面的图能更好的理解这个结构:
接下来简要的介绍一下网桥的初始化。
网桥的初始化和一般网络设备的初始化很相似,只不过由于它是虚拟设备,因此这里还有一点不同。
首先来看内核的网络模块的初始化br_init,也就是初始化上面介绍的数据结构:
我们新建一个网桥,使用br_add_bridge,在这个函数中,主要是调用new_bridge_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。我们来看这个函数的实现:
当这个函数执行完毕后,会再次调用netif_receive_skb,他则会再次调用handle_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);
}
{
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,而此时由于设备已经替换为虚拟的网桥设备,因此就会直接将包发往下层正确的协议处理。