网桥实现与分析

来源:互联网 发布:ubuntu 查看安装目录 编辑:程序博客网 时间:2024/05/01 05:24

第一部份 源码框架

一、        网桥原理
传统的中继器,如HUB,是一个单纯的物理层设备,它将每一个收到的数据包,在其所有的端口上广播,由接收主机来判断这个数据包是否是给自己的。这样,网络资源被极大的浪费掉了。
网桥之所以不同于中继器,主要在于其除了有中继的作用外,还有一个更重要的作用,就是学习MAC地址,然后根据每个数据包的目的MAC与自身端口的对应,从关联端口发送数据,而不完全地在整个网段中进行广播。所以,网桥的实现中,有两个关键点:
1、        学习MAC地址,起初,网桥是没有任何地址与端口的对应关系的,它发送数据,还是得想HUB一样,但是每发送一个数据,它都会关心数据包的来源MAC是从自己的哪个端口来的,由于学习,建立地址-端口的对照表(CAM表)。
2、        每发送一个数据包,网桥都会提取其目的MAC地址,从自己的地址-端口对照表(CAM表)中查找由哪个端口把数据包发送出去。

二、        Linux网桥源码的实现

1、        调用
在src/net/core/dev.c的软中断函数static void net_rx_action(struct softirq_action *h)中(line 1479)

#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
                        if (skb->dev->br_port != NULL &&
                            br_handle_frame_hook != NULL) {
                                handle_bridge(skb, pt_prev);
                                dev_put(rx_dev);
                                continue;
                        }
#endif
如果定义了网桥或网桥模块,则由handle_bridge函数处理
skb->dev->br_port :接收该数据包的端口是网桥端口组的一员,如果接收当前数据包的接口不是网桥的某一物理端口,则其值为NULL;
br_handle_frame_hook :定义了网桥处理函数

这段代码将数据包进行转向,转向的后的处理函数是钩子函数br_handle_frame_hook,在此之前,handle_bridge函数还要处理一些其它的事情:

static __inline__ int handle_bridge(struct sk_buff *skb,
                                     struct packet_type *pt_prev)
{
        int ret = NET_RX_DROP;

        if (pt_prev) {
                if (!pt_prev->data)
                        ret = deliver_to_old_ones(pt_prev, skb, 0);
                else {
                        atomic_inc(&skb->users);
                        ret = pt_prev->func(skb, skb->dev, pt_prev);
                }
        }

        br_handle_frame_hook(skb);
        return ret;
}

pt_prev用于在共享SKB的时候提高效率,这并不是本文关心的重点,贴子http://www.linuxforum.net/forum/ ... mp;o=all&fpart= 具有很高的参考介值,我就不重复了。

handle_bridge函数最后将控制权交由到了br_handle_frame_hook的手上。

2、        钩子函数的注册
br_handle_frame_hook用于网桥的处理,在网桥的初始化函数中(net/bridge/br.c):
static int __init br_init(void)
{
        printk(KERN_INFO "NET4: Ethernet Bridge 008 for NET4.0/n");

        br_handle_frame_hook = br_handle_frame;
        br_ioctl_hook = br_ioctl_deviceless_stub;
#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
        br_fdb_get_hook = br_fdb_get;
        br_fdb_put_hook = br_fdb_put;
#endif
        register_netdevice_notifier(&br_device_notifier);

        return 0;
}
初始化函数中指明了钩子函数实际上指向的是br_hanlde_frame

3、br_handle_frame(br_input.c)
/*网桥处理函数*/
void br_handle_frame(struct sk_buff *skb)
{
        struct net_bridge *br;
        unsigned char *dest;
        struct net_bridge_port *p;

        /*获取目的MAC地址*/
        dest = skb->mac.ethernet->h_dest;

        /*skb->dev->br_port用于指定接收该数据包的端口,若不是属于网桥的端口,则为NULL*/
        p = skb->dev->br_port;
        if (p == NULL)                /*端口不是网桥组端口中*/
                goto err_nolock;

        /*本端口所属的网桥组*/
        br = p->br;
       
        /*加锁,因为在转发中需要读CAM表,所以必须加读锁,避免在这个过程中另外的内核控制路径(如多处理机上另外一个CPU上的系统调用)修改CAM表*/
        read_lock(&br->lock);
        if (skb->dev->br_port == NULL)                /*前面判断过的*/
                goto err;
       
        /*br->dev是网桥的虚拟网卡,如果它未UP,或网桥DISABLED,p->state实际上是桥的当前端口的STP计算判断后的状态*/
        if (!(br->dev.flags & IFF_UP) ||
            p->state == BR_STATE_DISABLED)
                goto err;
       
        /*源MAC地址为255.X.X.X,即源MAC是多播或广播,丢弃之*/
        if (skb->mac.ethernet->h_source[0] & 1)
                goto err;

        /*众所周之,网桥之所以是网桥,比HUB更智能,是因为它有一个MAC-PORT的表,这样转发数据就不用广播,而查表定端口就可以了
        每次收到一个包,网桥都会学习其来源MAC,添加进这个表。Linux中这个表叫CAM表(这个名字是其它资料上看的)。
        如果桥的状态是LEARNING或FORWARDING(学习或转发),则学习该包的源地址skb->mac.ethernet->h_source,
        将其添加到CAM表中,如果已经存在于表中了,则更新定时器,br_fdb_insert完成了这一过程*/
        if (p->state == BR_STATE_LEARNING ||
            p->state == BR_STATE_FORWARDING)
                br_fdb_insert(br, p, skb->mac.ethernet->h_source, 0);
       
        /*
* STP协议的BPDU包的目的MAC采用的是多播目标MAC地址:
* 01-80-c2-00-00-00(Bridge_group_addr:网桥组多播地址),这里先判断网桥是否
* 开启了STP(由用户层来控制,如brctl),如果开启了,则比较目的地址前5位
* 是否与多播目标MAC地址相同:
* (!memcmp(dest, bridge_ula, 5)
* 如果相同,如果地址第6位非空
* !(dest[5] & 0xF0))
* 那么这确定是一个STP的BPDU包,则跳转到handle_special_frame,将处理权
* 将给函数br_stp_handle_bpdu
       */
        if (br->stp_enabled &&
            !memcmp(dest, bridge_ula, 5) &&
            !(dest[5] & 0xF0))
goto handle_special_frame;
       
        /*处理钩子函数,然后转交br_handle_frame_finish函数继续处理*/
        if (p->state == BR_STATE_FORWARDING) {
                NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
                        br_handle_frame_finish);
                read_unlock(&br->lock);
                return;
        }

err:
        read_unlock(&br->lock);
err_nolock:
        kfree_skb(skb);
        return;

handle_special_frame:
        if (!dest[5]) {
                br_stp_handle_bpdu(skb);
                return;
        }

        kfree_skb(skb);
}

可见,这个函数中有三个重要的地方:
1、        地址学习:br_fdb_insert
2、        STP的处理:br_stp_handle_bpdu
3、        br_handle_frame_finish,我们还没有查CAM表,转发数据呢……
我们先来看网桥的进一步处理br_handle_frame_finish,地址学习等内容,后面再来分析。

4、br_handle_frame_finish
        static int br_handle_frame_finish(struct sk_buff *skb)
{
        struct net_bridge *br;
        unsigned char *dest;
        struct net_bridge_fdb_entry *dst;
        struct net_bridge_port *p;
        int passedup;

        /*前面基本相同*/
        dest = skb->mac.ethernet->h_dest;


        p = skb->dev->br_port;
        if (p == NULL)
                goto err_nolock;

        br = p->br;
        read_lock(&br->lock);
        if (skb->dev->br_port == NULL)
                goto err;

        passedup = 0;
       
        /*
* 如果网桥的虚拟网卡处于混杂模式,那么每个接收到的数据包都需要克隆一份
        * 送到AF_PACKET协议处理体(网络软中断函数net_rx_action中ptype_all链的
* 处理)。
*/
        if (br->dev.flags & IFF_PROMISC) {
                struct sk_buff *skb2;

                skb2 = skb_clone(skb, GFP_ATOMIC);
                if (skb2 != NULL) {
                        passedup = 1;
                        br_pass_frame_up(br, skb2);
                }
        }

        /*
* 目的MAC为广播或多播,则需要向本机的上层协议栈传送这个数据包,这里
* 有一个标志变量passedup,用于表示是否传送过了,如果已传送过,那就算了
*/
        if (dest[0] & 1) {
                br_flood_forward(br, skb, !passedup);
                if (!passedup)
                        br_pass_frame_up(br, skb);
                goto out;
        }
       
        /*
* 用户层常常需要用到一个虚拟的地址来管理网桥,如果目的地址非常,且为本
* 地址地址,则交由上层函数处理
*/
        if (dst != NULL && dst->is_local) {
                if (!passedup)
                        br_pass_frame_up(br, skb);
                else
                        kfree_skb(skb);
                br_fdb_put(dst);
                goto out;
        }
       
        /*查询CAM表,如果查到表了,转发之*/
        if (dst != NULL) {
                br_forward(dst->dst, skb);
                br_fdb_put(dst);
                goto out;
        }

        /*如果表里边查不到,那么只好学习学习HUB了……*/
        br_flood_forward(br, skb, 0);

out:
        read_unlock(&br->lock);
        return 0;

err:
        read_unlock(&br->lock);
err_nolock:
        kfree_skb(skb);
        return 0;
}

在这个函数中,涉及到两个重要方面:
1、        查表:br_forward
2、        网桥数据转发:br_fdb_put。

另外,网桥的处理中,还涉及到内核中一些重要的数据结构:

对Linux上所有接口进行网桥划分,可以把一组端口划分到一个网桥之中,同时一个系统上
允许有多个网桥。内核描述一个网桥,使用了struct net_bridge结构:

struct net_bridge
{
        struct net_bridge                *next;                        //下一个网桥
        rwlock_t                        lock;                        //读写锁
        struct net_bridge_port                *port_list;                //桥组中的端口列表
       
        /*网桥都会有一个虚拟设备用来进行管理,就是它了。说到这里,我想到
        了以前一个没有解决的问题:对网桥管理IP配置后,发现其虚拟的MAC地址是动态生成的,取的是桥组中某一个物理端口的MAC地址(好像是
        第一个),这样,如果远程管理时就有麻烦:如果你动态调整网桥中的端口,如删除某个网卡出去,用于管理的虚拟网卡的地址就有可以改
        变,导致不能远程管理,盼指点如何解决此问题呢?也许看完整个代码就会也答案……*/
        struct net_device                dev;                       
        struct net_device_stats                statistics;                //网桥虚拟网卡的统计数据
        rwlock_t                        hash_lock;                //hash表的读写锁,这个表就是用于存放桥的MAC-PORT对应表
        struct net_bridge_fdb_entry        *hash[BR_HASH_SIZE];        //就是这张表了,也叫CAM表
        struct timer_list                tick;

        /*以下定义了STP协议所使用的信息,参见STP协议的相关定义,我的小站上
        http://www.skynet.org.cn/viewthread.php?tid=90&fpage=1也有对协议的相关分析 */
        bridge_id                        designated_root;
        int                                root_path_cost;
        int                                root_port;
        int                                max_age;
        int                                hello_time;
        int                                forward_delay;
        bridge_id                        bridge_id;
        int                                bridge_max_age;
        int                                bridge_hello_time;
        int                                bridge_forward_delay;
        unsigned                        stp_enabled:1;
        unsigned                        topology_change:1;
        unsigned                        topology_change_detected:1;

        struct br_timer                        hello_timer;
        struct br_timer                        tcn_timer;
        struct br_timer                        topology_change_timer;
        struct br_timer                        gc_timer;

        int                                ageing_time;
        int                                gc_interval;
};

可以看出,桥中有几个重要的地方:
1、桥的端口成员:struct net_bridge_port                *port_list;
2、桥的CAM表:struct net_bridge_fdb_entry        *hash[BR_HASH_SIZE];
3、桥的虚拟网卡
4、STP

桥的虚拟网卡是一个struct net_device设备,它在2.4中是如此庞大,要对它在这里进行分析无疑是非常困难的,改天大家一起讨论吧。
STP的相关成员的定义与STP包的结构是紧密相关的,看了其包结构,可以分析出这些成员了,不再一一列举了。

网桥中的端口,用struct net_bridge结构表示,它实际上表示的是接收该数据包的网桥的端口的相关信息:
struct net_bridge_port
{
        struct net_bridge_port                *next;                //网桥端口组中的下一个端口
        struct net_bridge                *br;                //当前端口(接收数据包这个)所在的桥组
        struct net_device                *dev;                //本端口所指向的物理网卡
        int                                port_no;        //本端口在网桥中的编号


        port_id                                port_id;       
        int                                state;
        int                                path_cost;
        bridge_id                        designated_root;
        int                                designated_cost;
        bridge_id                        designated_bridge;
        port_id                                designated_port;
        unsigned                        topology_change_ack:1;
        unsigned                        config_pending:1;
        int                                priority;

        struct br_timer                        forward_delay_timer;
        struct br_timer                        hold_timer;
        struct br_timer                        message_age_timer;
};
这个结构对应了内核缓存中的skb->dev->br_port;