免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: 独孤九贱
打印 上一主题 下一主题

Linux网桥源码框架分析初步 [复制链接]

论坛徽章:
0
31 [报告]
发表于 2006-01-17 10:57 |只看该作者
原帖由 keyinwind 于 2006-1-16 19:27 发表
希望LZ先写好STP的文档.... 赞一个!


好,那就来写STP的先,由于水平原因,很多地方估计会有错,贴出来,为的是共同分享,共同学习,关于STP的BPDU包的封包结构,不作分析了,大家可以在网上找一大堆出来:

Linux的网桥中的STP的实现分析初步

一、STP的框架结构
STP发送的是BPDU包,该包有所有两种类型:配置和TCN(拓朴变更通知);
对于BPDU包的处理,有两种:接收和发送(废话),
对于配置类型的BPDU包的发送,它是靠定时器来完成的,参BPDU包的几个定时器参数;
对于TCP类型的BPDU包的发送,从名字可以看出来,它是当发现拓朴结构发生变更时发送的,如本机网桥配置的变化,物理接口的变动,分析其它机器变动后发出来的STP包等等。

BPDU的封包采用的是IEEE802封包(本想把封包结构的图片贴上来,找不着在哪儿上传图片)。

前面分析过, br_handle_frame函数中,当网桥开启了STP,且根据目的物理地址判断出这是一个STP包,则交给br_stp_handle_bpdu函数处理。
br_stp_handle_bpdu函数主要是判断是哪种类型的BPDU包,然后调用相关的处理函数,即:
if(type==config)
{
    br_received_config_bpdu();
}
else if(type==tcn)
{
    br_received_tcn_bpdu();
}

这是对接收到BPDU包的处理,关于config类型的BPDU包的发送,后面再分析;TCN包的发送,有一部份是在接收包处理过程中处理的(因为分析config类型的BPDU包的时候,发现拓朴变更,当然要发送TCN包了),所以这里一起来分析。

二、Config类型的BPDU包的接收处理
这个处理过程是在拆完BPDU包后,调用br_received_config_bpdu函数完成的。
还是得先交待一些理论的东西:

STP协议最终是为了在网络中生成一棵无环状的树,以期消除广播风暴以及单播数据帧对网络的影响。它始终在选举三样东东:
1、根网桥;
2、根端口;
3、“指定端口”和“指定网桥”

(这三个概念非常重要,如果你还不清楚,建议查阅相关文档先,否则下边的代码分析也无从谈起了)
然后再根据选举出来的这三个东东,确定端口的状态:阻塞、转发、学习、监听、禁用……
要选举出这三样东东,得有一个判断标志,即算法,STP的判断标准是:
1、判断根桥ID,以最小的为优;
2、判断到根桥的最小路径开销;
3、确定最小发送发BID(Sender BID)
4、确定最小的端口ID

如果前面你查阅了BPDU的封包结构,根桥ID、最小路径开销、发送方网桥的ID、端口ID这几个概念应该没有问题了,不过这里还是简单交一下:
1、根桥ID,我们配置了网桥后,用brctl命令会发现8000.XXXXXX这样一串,这就是网桥的ID号,用一标识每一个网桥,后面的XXXX一般的桥的MAC地址,这样ID值就不会重复。根桥ID,是指网络中所有网桥的ID值最小的那一个,对应的具有根桥ID的桥,当然也是网络的根桥了;

2、最小路径开销
动态路由中也类似这个概念,不过这里用的不是跳数(局域网不比广域网,不一定跳数大就慢,比如跳数小,是10M链路,跳数大的却是千兆链路),最初的开销定义为1000M/链种带宽,当然,这种方式不适用于万兆网了……所以后来又有一个新的,对每一种链路定义一个常数值——详请请查阅相关资料;

3、发送方ID
网桥之前要收敛出一个无环状拓朴,就需要互相发送BPDU包,当然需要把自己的ID告诉对方,这样对方好拿来互相比较;

4、端口ID
端口ID由优先级+端口编号组成,用于标识某个桥的某个端口,后面比较时好用。

生成树算法就是利用上述四个参数在判断,判断过程总是相同的:
1、确定根桥,桥ID最小的(即把包中的桥ID,同自己以前记录的那个最小的桥ID相比,机器加电时,总是以自己的桥ID为根桥ID)的为根桥;

2、确定最小路径开销;

3、确定最小发送方ID;

4、确定最小的端口ID:

这四步非常地重要,后面的所以比较都是这四个步骤。

有了这些概念,来看看对config类型的BPDU包的处理:

void br_received_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
{
        struct net_bridge *br;
        int was_root;

        if (p->state == BR_STATE_DISABLED)
                return;

        br = p->br;
        read_lock(&br->lock);

        /*自己是根桥吗?用自己的br_ID和BPDU包中的根ID相比较*/
        was_root = br_is_root_bridge(br);
       
        /*比桥BPDU包中的信息(bpdu)和原先的对应的信息(p),如果需要更新,返回1,相同返回0,不需更新返回-1*/
        if (br_supersedes_port_info(p, bpdu)) {
                /*刷新自己的相关信息*/
                br_record_config_information(p, bpdu);
                /*进行root_bridge、port的选举*/
                br_configuration_update(br);
                /*设置端口状态*/
                br_port_state_selection(br);

以上这一段的逻辑概念很简单:
1、把收到的BPDU包中的参数同自己原先记录的相比较,(遵循前面说的四个比较步骤),以判断是否需要进行更新——br_supersedes_port_info(p, bpdu)。
2、如果判断需要进行更新,即上述四个步骤中,有任意一项有变动,则刷新自己的保存记录:br_record_config_information(p, bpdu);
3、因为有变动,就需要改变自己的配置了:br_configuration_update(br);即前面说的,根据四步判断后选举根桥(注:根桥不是在这里选举的,前文说过,它是定时器定时发送BPDU包,然后收到的机器只需改变自己的记录即可)、根端口、指定端口;
4、设置物理端口的转发状态:br_port_state_selection

2.1 br_supersedes_port_info(p, bpdu)


/* called under bridge lock */
static int br_supersedes_port_info(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
{
        int t;
/*第一步*/
        t = memcmp(&bpdu->root, &p->designated_root, ;
        if (t < 0)
                return 1;
        else if (t > 0)
                return 0;
/*第二步*/
        if (bpdu->root_path_cost < p->designated_cost)
                return 1;
        else if (bpdu->root_path_cost > p->designated_cost)
                return 0;
/*第三步,要同两个桥ID比:已记录的最小发送ID和自己的ID*/
        t = memcmp(&bpdu->bridge_id, &p->designated_bridge, ;
        if (t < 0)
                return 1;
        else if (t > 0)
                return 0;

        if (memcmp(&bpdu->bridge_id, &p->br->bridge_id, )
                return 1;
/*第四步*/
        if (bpdu->port_id <= p->designated_port)
                return 1;

        return 0;
}

2.2 br_record_config_information
如果检测到有变动,则刷新自己的记录先:
/* called under bridge lock */
static void br_record_config_information(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
{
        p->designated_root = bpdu->root;
        p->designated_cost = bpdu->root_path_cost;
        p->designated_bridge = bpdu->bridge_id;
        p->designated_port = bpdu->port_id;
/*设置时间戳,关于STP的时间处理,后面来分析*/
        br_timer_set(&p->message_age_timer, jiffies - bpdu->message_age);
}

p对应的四个成员的概念对照BPDU封包结构,不难理解其含义:
        p->designated_root:                指定的根网桥的网桥ID
        p->designated_cost :                指定的到根桥的链路花销
        p->designated_bridge:                指定的发送当前BPDU包的网桥的ID
        p->designated_port:                指定的发送当前BPDU包的网桥的端口的ID

2。3 br_configuration_update前面说过,根桥的选举不是在这里进行,这里进行根端口和指定端口的选举
/* called under bridge lock */
void br_configuration_update(struct net_bridge *br)
{
       
                br_root_selection(br);/*选举根端口*/
        br_designated_port_selection(br);/*选举指定端口*/
}

2.3.1 根端口的选举br_root_selection根端口的选举同样是以上四个步骤,只是有一点小技巧:它逐个遍历桥的每一个所属端口,找出一个符合条件的,保存下来,再用下一个来与之做比较,用变量root_port 来标志:
/* called under bridge lock */
static void br_root_selection(struct net_bridge *br)
{
        struct net_bridge_port *p;
        int root_port;

        root_port = 0;
/*获得桥的所属端口列表*/
        p = br->port_list;
/*这个循环非常重要,它遍历桥的每一个端口,进行以上四步判断,找到一个,将其“保存”下来,然后再用下一个与保存的相比较,直至遍历完,找到最优的那个,这个“保存”打了引号,是因为它仅仅是记当了端口编号:root_port = p->port_no;,然后再将其传递给比较函数br_should_become_root_port*/
        while (p != NULL) {
                if (br_should_become_root_port(p, root_port))
                        root_port = p->port_no;

                p = p->next;
        }

        br->root_port = root_port;
/*找完了还没有找到,则认为自己就是根桥……*/
        if (!root_port) {
                br->designated_root = br->bridge_id;
                br->root_path_cost = 0;
        }
/*否则记录相应的值*/
               else {
                p = br_get_port(br, root_port);
                br->designated_root = p->designated_root;
                br->root_path_cost = p->designated_cost + p->path_cost;
        }
}

br_should_become_root_port函数用以判断端口p是否应该变成根端口,与它相比较的是原来那个根端口,函数第二个参数则为此的ID号,在函数中调用 br_get_port获取该端口:

/* called under bridge lock */
static int br_should_become_root_port(struct net_bridge_port *p, int root_port)
{
        struct net_bridge *br;
        struct net_bridge_port *rp;
        int t;

        br = p->br;
/*若当前端口是关闭状态或为一个指定端口,则不参与选举,返回*/
        if (p->state == BR_STATE_DISABLED ||
            br_is_designated_port(p))
                return 0;
/*在根端口的选举中,根桥是没有选举权的*/
        if (memcmp(&br->bridge_id, &p->designated_root, <= 0)
                return 0;

/*没有指定等比较的端口ID(因为第一次它初始化为0的)*/
        if (!root_port)
                return 1;

/*获取待比较的根端口*/
        rp = br_get_port(br, root_port);

/*又是四大步,像打蓝球*/
        t = memcmp(&p->designated_root, &rp->designated_root, ;
        if (t < 0)
                return 1;
        else if (t > 0)
                return 0;

        if (p->designated_cost + p->path_cost <
            rp->designated_cost + rp->path_cost)
                return 1;
        else if (p->designated_cost + p->path_cost >
                 rp->designated_cost + rp->path_cost)
                return 0;

        t = memcmp(&p->designated_bridge, &rp->designated_bridge, ;
        if (t < 0)
                return 1;
        else if (t > 0)
                return 0;

        if (p->designated_port < rp->designated_port)
                return 1;
        else if (p->designated_port > rp->designated_port)
                return 0;

        if (p->port_id < rp->port_id)
                return 1;

        return 0;
}

这样,遍历完成后,根端口就被选出来了。

2。3。2 指定端口的选举br_designated_port_selection
/* called under bridge lock */
static void br_designated_port_selection(struct net_bridge *br)
{
        struct net_bridge_port *p;

        p = br->port_list;
        while (p != NULL) {
                if (p->state != BR_STATE_DISABLED &&
                    br_should_become_designated_port(p))
                        br_become_designated_port(p);

                p = p->next;
        }
}
事实上这个过程与根端口的选举过程极为类似,没有分析的必要了!

[ 本帖最后由 独孤九贱 于 2006-1-17 10:59 编辑 ]

论坛徽章:
0
32 [报告]
发表于 2006-01-17 11:14 |只看该作者
赞一个先!!

论坛徽章:
0
33 [报告]
发表于 2006-01-17 11:17 |只看该作者
2。3。3 端口状态选择
/* called under bridge lock */
void br_port_state_selection(struct net_bridge *br)
{
        struct net_bridge_port *p;

        p = br->port_list;
        while (p != NULL) {
                if (p->state != BR_STATE_DISABLED) {
                        if (p->port_no == br->root_port) {
                                p->config_pending = 0;
                                p->topology_change_ack = 0;
                                br_make_forwarding(p);
                        } else if (br_is_designated_port(p)) {
                                br_timer_clear(&p->message_age_timer);
                                br_make_forwarding(p);
                        } else {
                                p->config_pending = 0;
                                p->topology_change_ack = 0;
                                br_make_blocking(p);
                        }
                }

                p = p->next;
        }
}

函数的逻辑结构也很简单:
遍历整个桥所属端口:
while (p != NULL)
如果端口已经DISABLED,则没有判断的必要了:
p->state != BR_STATE_DISABLED

如果端口是根端口,或者是指定端口,就让让它forwarding,否则就让它blocking:

                        if (p->port_no == br->root_port) {
                                p->config_pending = 0;
                                p->topology_change_ack = 0;
                                br_make_forwarding(p);
                        } else if (br_is_designated_port(p)) {
                                br_timer_clear(&p->message_age_timer);
                                br_make_forwarding(p);
                        } else {
                                p->config_pending = 0;
                                p->topology_change_ack = 0;
                                br_make_blocking(p);
                        }

/* called under bridge lock */
static void br_make_forwarding(struct net_bridge_port *p)
{
        if (p->state == BR_STATE_BLOCKING) {
                printk(KERN_INFO "%s: port %i(%s) entering %s state\n",
                       p->br->dev.name, p->port_no, p->dev->name, "listening");

                p->state = BR_STATE_LISTENING;
                br_timer_set(&p->forward_delay_timer, jiffies);
        }
}

/* called under bridge lock */
static void br_make_blocking(struct net_bridge_port *p)
{
        if (p->state != BR_STATE_DISABLED &&
            p->state != BR_STATE_BLOCKING) {
                if (p->state == BR_STATE_FORWARDING ||
                    p->state == BR_STATE_LEARNING)
                        br_topology_change_detection(p->br);

                printk(KERN_INFO "%s: port %i(%s) entering %s state\n",
                       p->br->dev.name, p->port_no, p->dev->name, "blocking");

                p->state = BR_STATE_BLOCKING;
                br_timer_clear(&p->forward_delay_timer);
        }
}

都是设置p->state 相应状态位就可以了!!

论坛徽章:
0
34 [报告]
发表于 2006-01-17 11:40 |只看该作者
三、选举完成之后
实在不会取名字了,前面分析了br_received_config_bpdu中前面的判断、刷新、选举、设置端口状态的过程,然而,如果桥认为当前这个BPDU是一个“最优的”(即符合前面判断四步中的某一步),所作的动作不止于此:
1、如果因为这个BPDU导致拓朴变化了,如自己以前是根桥,现在不是了,需要发送TCN包,进行通告;
2、需要把这个BPDU包继续转发下去(如果自己收到数据的端口是根端口的话,那么就有可能有许多交换机(网桥)串在自己的指定端口下边,总得把这个包能过指定端口再发给它们吧,否则交换机就不叫交换机了)

指下来继续看代码:
/*前面说的第1步*/
                     if (!br_is_root_bridge(br) && was_root) {
                        br_timer_clear(&br->hello_timer);
                        if (br->topology_change_detected) {
                                br_timer_clear(&br->topology_change_timer);
                                br_transmit_tcn(br);
                                br_timer_set(&br->tcn_timer, jiffies);
                        }
                }
/*前面说的第2步*/
                if (p->port_no == br->root_port) {
                        br_record_config_timeout_values(br, bpdu);
                        br_config_bpdu_generation(br);
                        if (bpdu->topology_change_ack)
                                br_topology_change_acknowledged(br);
                }

tcn包的发送,呆会单独来分析,先来看br_config_bpdu_generation函数,这个函数也很简单:遍历桥的所有端口,如果是指定端口,就发送一个config 类型的BPDU包:
/* called under bridge lock */
void br_config_bpdu_generation(struct net_bridge *br)
{
        struct net_bridge_port *p;

        p = br->port_list;
        while (p != NULL) {
                if (p->state != BR_STATE_DISABLED &&
                    br_is_designated_port(p))
                        br_transmit_config(p);

                p = p->next;
        }
}
然后就是层层函数调用,组包,最终是调用dev_queue_xmit函数发送出去的。

如果收到这个BPDU包,不是“最优”的,而接收数据包的接口不是根端口,直接将转发出去就可以了,起个中继的作用:
else if (br_is_designated_port(p))
{               
                br_reply(p);               
}
br_reply同样调用了br_transmit_config函数

论坛徽章:
0
35 [报告]
发表于 2006-01-17 16:22 |只看该作者

回复 31楼 独孤九贱 的帖子

大侠好,呵呵,你是不是已经把stp看完了啊,昨天蒙大侠指点,看了一下sk_buf的资料,现在也在啃stp程序中,大侠都看完了,俺们就方便多了
/*比桥BPDU包中的信息(bpdu)和原先的对应的信息(p),如果需要更新,返回1,相同返回0,不需更新返回-1*/
        if (br_supersedes_port_info(p, bpdu)) {
这里是不是应该是:如果需要更新返回1,不需更新返回0啊

论坛徽章:
0
36 [报告]
发表于 2006-01-17 17:11 |只看该作者
原帖由 Pagliuca 于 2006-1-17 16:22 发表
大侠好,呵呵,你是不是已经把stp看完了啊,昨天蒙大侠指点,看了一下sk_buf的资料,现在也在啃stp程序中,大侠都看完了,俺们就方便多了
/*比桥BPDU包中的信息(bpdu)和原先的对应的信息(p),如果需要更新,返回 ...


呵呵,笔误,源码中就只有1和0,当时写的时候不知看到哪段代码上面去了……

论坛徽章:
0
37 [报告]
发表于 2006-01-18 15:30 |只看该作者
struct net_bridge_port
{
        struct net_bridge_port                *next;   //网桥的下一个端口
        struct net_bridge                *br;      //端口所在的桥
        struct net_device                *dev;     //端口所指向的物理网卡
        int                                port_no;     //端口在网桥中的编号,即端口号

        /* STP */
        port_id                                port_id;   //端口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;
};
网桥端口的数据结构中的STP部分,我这样觉得:
1、port_id是端口ID
2、path_cost
3、designated_root是根桥ID
4、designated_cost是到根网桥的费用
5、designated_bridge指定网桥ID
6、designated_port是指定端口ID
以上理解对吗,还有path_cost是指的什么呢?怎么接收到的BPDU会有两个花销呢?
designated_bridge是不是该理解发送这个BPDU的网桥的ID中最小ID呢?
多谢大侠指教!

论坛徽章:
0
38 [报告]
发表于 2006-01-18 17:32 |只看该作者
原帖由 Pagliuca 于 2006-1-18 15:30 发表
struct net_bridge_port
{
        struct net_bridge_port                *next;   //网桥的下一个端口
        struct net_bridge                *br;      //端口所在的桥
        struct net_device                *dev;     //端口所指向的物理网卡
        int                                port_no; ...


STP部份我在代码分析中都分析了吧,只是有一点,它不光要记录拆包来的值,还要保存一个值,以便对比两次的值,所以,不只是cost有两次……

论坛徽章:
0
39 [报告]
发表于 2006-01-18 18:15 |只看该作者
br_is_designated_port函数的是看当前port是否就是designate port:
/* called under bridge lock */
int br_is_designated_port(struct net_bridge_port *p)
{
        return !memcmp(&p->designated_bridge, &p->br->bridge_id, 8) &&
                (p->designated_port == p->port_id);
}
1、这个指定端口是什么意思,如果理解成是BPDU要发送的那些端口,我觉得这个指定端口对于这个网桥可能有很多值的,而在这只有一个指定端口值,port_id和designated_port到底什么区别,1个端口只会有1个ID啊

2、还有,我觉得&p->designated_bridge是这个端口收到的所有BPDU中发送桥的ID最小的,而不是根桥ID啊

希望大侠指教

[ 本帖最后由 Pagliuca 于 2006-1-25 16:34 编辑 ]

论坛徽章:
0
40 [报告]
发表于 2006-01-19 09:39 |只看该作者
原帖由 Pagliuca 于 2006-1-18 18:15 发表
br_is_designated_port函数的是看当前桥是否就是指定的根桥,并且当前port 是否就是designate port:
/* called under bridge lock */
int br_is_designated_port(struct net_bridge_port *p)
{
        retu ...


br_is_designated_port函数是看当前端口是否是指定端口,struct net_bridge_port 用来描述一个端口,p就是待断判断的端口……而不是根桥,桥之类的东东,请仔细看我上面的代码分析吧……
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP