当前位置导航:炫浪网>>网络学院>>操作系统>>Linux教程

Linux网桥源码框架分析初

今天处理网桥的STP的问题遇到了麻烦,对这个东东理论的倒是看了不少,没有真真学习到它的源理,来看Linux的实现,手头没有资料,看了两个钟头,只把网桥的框架结构看完,所以想先贴出来,希望有研究这块的大哥们讨论,继续把它写完,九贱好学习一下:

版本:Linux 2.4.18

一、调用
在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 :接收该数据包的端口是网桥端口组的一员
br_handle_frame_hook :定义了网桥处理函数

二、初始化
src/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_handle_frame
ioctl处理函数是:br_ioctl_deviceless_stub

三、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,而当前数据包又是一个BPDU
        (!memcmp(dest, bridge_ula, 5),        unsigned char bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }
        则交由相应函数处理*/
        if (br->stp_enabled &&
/*这里只比较前5个字节,没有仔细研究过STP是使用了全部多播地址(从0 1 : 0 0 : 5 e : 0 0 : 0 0 : 0 0到0 1 : 0 0 : 5 e : 7 f : ff : ff。),还是只使用了一部份,这里看来似乎只是一部份,没去深究了*/
            !memcmp(dest, bridge_ula, 5) &&
            !(dest[5] & 0xF0))                /*01-80-c2-00-00-F0 是一个什么地址?为什么要判断呢?*/
                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);
}

四、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;
        }
       
        /*Linux中的MAC-PORT表是CAM表,这里根据目的地址来查表,以确定由哪个接口把包转发出去
        每一个表项是通过结构struct net_bridge_fdb_entry来描述的:
        struct net_bridge_fdb_entry
        {
                struct net_bridge_fdb_entry        *next_hash;                //用于CAM表连接的链表指针
                struct net_bridge_fdb_entry        **pprev_hash;                //为什么是pprev不是prev呢?还没有仔细去研究
                atomic_t                        use_count;                //此项当前的引用计数器
                mac_addr                        addr;                        //MAC地址
                struct net_bridge_port                *dst;                        //此项所对应的物理端口
                unsigned long                        ageing_timer;                //处理MAC超时
                unsigned                        is_local:1;                //是否是本机的MAC地址
                unsigned                        is_static:1;                //是否是静态MAC地址
        };*/
        dst = br_fdb_get(br, dest);
       
        /*查询CAM表后,如果能够找到表项,并且目的MAC是到本机的虚拟网卡的,那么就需要把这个包提交给上层协议,
        这样,我们就可以通过这个虚拟网卡的地址来远程管理网桥了*/
        if (dst != NULL && dst->is_local) {
                if (!passedup)
                        br_pass_frame_up(br, skb);
                else
                        kfree_skb(skb);
                br_fdb_put(dst);
                goto out;
        }
       
        /*查到表了,且不是本地虚拟网卡的,转发之*/
        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;
}

基本框架就是这样了,与那些讲网桥原理的书上讲的基本差不多……
网桥之所以是网桥,主要靠这两个函数:
br_fdb_insert
br_fdb_get
一个学习,一个查表;
另外,支持STP,处理BPDU,需要用到函数br_stp_handle_bpdu
哪位有这三个函数的细节分析,可否送九贱一份,免得下午那么辛苦再去啃代码……

扫了一下 br_fdb_insert,结构还是很清析,如果当前项已存在于hash表项中,则更新它(__fdb_possibly_replace),如果是新项,则插入,实际是一个双向链表的维护过程(__hash_link):

void br_fdb_insert(struct net_bridge *br,
                   struct net_bridge_port *source,
                   unsigned char *addr,
                   int is_local)
{
        struct net_bridge_fdb_entry *fdb;
        int hash;

        hash = br_mac_hash(addr);

        write_lock_bh(&br->hash_lock);
        fdb = br->hash[hash];
        while (fdb != NULL) {
                if (!fdb->is_local &&
                    !memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
                        __fdb_possibly_replace(fdb, source, is_local);
                        write_unlock_bh(&br->hash_lock);
                        return;
                }

                fdb = fdb->next_hash;
        }

        fdb = kmalloc(sizeof(*fdb), GFP_ATOMIC);
        if (fdb == NULL) {
                write_unlock_bh(&br->hash_lock);
                return;
        }

        memcpy(fdb->addr.addr, addr, ETH_ALEN);
        atomic_set(&fdb->use_count, 1);
        fdb->dst = source;
        fdb->is_local = is_local;
        fdb->is_static = is_local;
        fdb->ageing_timer = jiffies;

        __hash_link(br, fdb, hash);

        write_unlock_bh(&br->hash_lock);
}

同样,查表也是一个遍历链表,进行地址匹配的过程:
struct net_bridge_fdb_entry *br_fdb_get(struct net_bridge *br, unsigned char *addr)
{
        struct net_bridge_fdb_entry *fdb;

        read_lock_bh(&br->hash_lock);
        fdb = br->hash[br_mac_hash(addr)];
        while (fdb != NULL) {
                if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
                        if (!has_expired(br, fdb)) {
                                atomic_inc(&fdb->use_count);
                                read_unlock_bh(&br->hash_lock);
                                return fdb;
                        }

                        read_unlock_bh(&br->hash_lock);
                        return NULL;
                }

                fdb = fdb->next_hash;
        }

        read_unlock_bh(&br->hash_lock);
        return NULL;
}

又看了一个函数,继续发上来:
STP的处理函数
/* called under bridge lock */
void br_stp_handle_bpdu(struct sk_buff *skb)
{
        unsigned char *buf;
        struct net_bridge_port *p;

        /*跳过DLC首部*/
        buf = skb->mac.raw + 14;
        p = skb->dev->br_port;
        /*再次做判断*/
        if (!p->br->stp_enabled || memcmp(buf, header, 6)) {
                kfree_skb(skb);
                return;
        }
       
        /*BPDU包有两类,由TYPE字段标志,分为配置和TCN(Topology Change Notification,拓朴改变通告)*/
       
        /*如果是配置类型*/
        if (buf[6] == BPDU_TYPE_CONFIG) {
                /*内核中用struct br_config_bpdu描述一个BPDU包:
                struct br_config_bpdu
                {
                        unsigned        topology_change:1;                //拓朴改变标志
                        unsigned        topology_change_ack:1;                //拓朴改变回应标志
                        bridge_id        root;                                //根ID,用于会聚后的网桥网络中,所有配置 BPDU 中的该字段都应该具有相同值(同VLAN),又可分为两个 BID 子字段:网桥优先级和网桥 MAC 地址
                        int                root_path_cost;                        //路径开销,通向有根网桥(Root Bridge)的所有链路的积累资本
                        bridge_id        bridge_id;                        //创建当前 BPDU 的网桥 BID。对于单交换机(单个 VLAN)发送的所有 BPDU 而言,该字段值都相同,而对于交换机与交换机之间发送的 BPDU 而言,该字段值不同)
                        port_id                port_id;                        //端口ID,每个端口值都是唯一的。端口1/1值为0×8001,而端口1/2 值为0×8002。
                        int                message_age;                        //记录 Root Bridge 生成当前 BPDU 起源信息的所消耗时间
                        int                max_age;                        //保存 BPDU 的最长时间,也反映了拓朴变化通知(Topology Change Notification)过程中的网桥表生存时间情况
                        int                hello_time;                        //指周期性配置 BPDU 间的时间
                        int                forward_delay;                        //用于在 Listening 和 Learning 状态的时间,也反映了拓朴变化通知(Topology Change Notification)过程中的时间情况
                };
                在这个结构中,bpdu包的三个字段没有包含在内:
                Protocol ID ― 协议字段,恒为0。
                Version ― 版本字段,恒为0。
                Type ― 决定该帧中所包含的两种 BPDU 格式类型(配置 BPDU 或 TCN BPDU)。 上面用buf[6]直接访问了,这是
                因为bpdu之前,还有三个字节的LLC头,再加上ProtocolID(2字节),VersionID(1字节),3+2+1,所以是buf[6]
                这是标准的802.3封包方式,与以太网封包略有不同,参见《tcp/ip详解卷一》第二章的第二页的最上面那张图(记得是)
                */
               
                struct br_config_bpdu bpdu;

                /*一个辛苦的解包过程……*/
                bpdu.topology_change = (buf[7] & 0x01) ? 1 : 0;
                bpdu.topology_change_ack = (buf[7] & 0x80) ? 1 : 0;
                bpdu.root.prio[0] = buf[8];
                bpdu.root.prio[1] = buf[9];
                bpdu.root.addr[0] = buf[10];
                bpdu.root.addr[1] = buf[11];
                bpdu.root.addr[2] = buf[12];
                bpdu.root.addr[3] = buf[13];
                bpdu.root.addr[4] = buf[14];
                bpdu.root.addr[5] = buf[15];
                bpdu.root_path_cost =
                        (buf[16] << 24) |
                        (buf[17] << 16) |
                        (buf[18] << |
                        buf[19];
                bpdu.bridge_id.prio[0] = buf[20];
                bpdu.bridge_id.prio[1] = buf[21];
                bpdu.bridge_id.addr[0] = buf[22];
                bpdu.bridge_id.addr[1] = buf[23];
                bpdu.bridge_id.addr[2] = buf[24];
                bpdu.bridge_id.addr[3] = buf[25];
                bpdu.bridge_id.addr[4] = buf[26];
                bpdu.bridge_id.addr[5] = buf[27];
                bpdu.port_id = (buf[28] << | buf[29];

                bpdu.message_age = br_get_ticks(buf+30);
                bpdu.max_age = br_get_ticks(buf+32);
                bpdu.hello_time = br_get_ticks(buf+34);
                bpdu.forward_delay = br_get_ticks(buf+36);

                kfree_skb(skb);
                br_received_config_bpdu(p, &bpdu);                /*调用配置函数*/
                return;
        }

        /*如果是TCN类型*/
        if (buf[6] == BPDU_TYPE_TCN) {
                br_received_tcn_bpdu(p);                        /*调用TCN函数*/
                kfree_skb(skb);
                return;
        }
        kfree_skb(skb);
}

相关内容
赞助商链接