今天处理网桥的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;
}


[  本帖最后由 platinum 于 2006-6-22 10:05 编辑 ]



 snow_insky 回复于:2006-01-12 13:00:27

继续,支持一把,大家看过以后,一定要顶一把,这样作者才可能把更精彩的内容给大家,否则,谁还愿意与大家分享知识,就这么一点要求,你们也不愿意????


 独孤九贱 回复于:2006-01-12 13:32:19

引用: 原帖由 snow_insky 于 2006-1-12 13:00 发表
继续,支持一把,大家看过以后,一定要顶一把,这样作者才可能把更精彩的内容给大家,否则,谁还愿意与大家分享知识,就这么一点要求,你们也不愿意???? 



我没有更精彩的了内容了,只是本着处理我遇到问题的思路来看一个实现而已,发贴的目的是希望研究这块的牛人写出更精彩的文章,吾辈好学习一二……

又看了一个函数,继续发上来:
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] << 8) |
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] << 8) | 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);
}


[  本帖最后由 platinum 于 2006-6-22 10:06 编辑 ]


 guotie 回复于:2006-01-12 13:59:42

to独孤九贱:
请教个问题,有什么简单的方式可以获得当前系统的arp与ip地址的对应表和路由表?


 独孤九贱 回复于:2006-01-12 14:12:23

引用: 原帖由 guotie 于 2006-1-12 13:59 发表
to独孤九贱:
请教个问题,有什么简单的方式可以获得当前系统的arp与ip地址的对应表和路由表? 



我分析网桥中提到的MAC地址表与arp表是两个概念,完全不同。

“获得当前系统的arp与ip地址的对应表和路由表”读proc就OK了,参见nettools或busybox的源码……
不过最简单的方式还是system(……)


 guotie 回复于:2006-01-12 14:32:16

hehe,这是用户空间的工具,如果在内核里需要知道arp表呢


 独孤九贱 回复于:2006-01-12 14:41:37

引用: 原帖由 guotie 于 2006-1-12 14:32 发表
hehe,这是用户空间的工具,如果在内核里需要知道arp表呢 


sorry,我以为是用户空间……内核里我还没有仔细去看呢……


 独孤九贱 回复于:2006-01-12 15:08:14

详细的STP协议就不在这里贴了,RFC有现成的。
继续来分析config BPDU:

还是先来大概说说STP的运作流程:
STP需要确定root bridge,root port,designate port,
所以,需要在确定之间进行判断,判断的原则是:
1. 最小的root BID(所有交换机中有最小BID的成为root bridge)
2. 最小的到root bridge路径开销(确定root port)
3. 最小的发送BID(确定指向端口)
4. 最小的端口ID(如果其他标准都相同,根据端口ID确定选择标准,较小的优先)
所以,网桥需要在每收到一个BPDU包的时候,将包中的这些值,与自己原先保存的值相对比,对应的函数是:
br_supersedes_port_info

在确定好这些值后,就需要根据这些值进行选举root bridge,root port,designate port,
运作流程是:
1. 选择root bridge,选举范围是整个网络,选择的流程是交换机相互交换BPDU,
选择依据是根据BID判断谁的BID比较小(优先级小,桥MAC小)
2. 选择root port,选举范围是每个nonbridge的和其他交换机相连的端口之间(同一个交换机上的连接其他交换机的端口)
选择依据是path cost较小,每个nonbridge一个root port,可以收发数据。
3. 选择designate port,选择范围是连接每个网段之间的端口(端口在不同交换机上)
选择依据也是path cost较小,如果相同,进一步比较BID,designate port每个网段一个,可以收发数据。
4. 通过上述选择,没有成为任何角色的端口称作nondesignate port,端口设置为block状态,可以接收数据,但不转发数据。

前面三步是选择的过程,对应函数是br_configuration_update,
第四步是根据选举后的结果,决定端口的状态,对应的函数是:br_port_state_selection
开启STP的交换机端口可能处于5种状态:
1. Block:阻断状态,接收但不转发数据。
2. Listening:侦听状态,不转发数据,可以收发BPDU,执行选举root bridge,root port,designate port等动作。
3. Learning:学习状态,不转发数据,开始学习MAC,为数据转发作准备
4. Forward:转发状态,转发数据。
5. Disable:禁用状态,既不参与STP计算,也不转发数据。
在进行选举之前,需要先用传送过来的BPUD中的相关值,更新自己对应的相关值,对应的函数是:br_record_config_information

对应源码:
/* lock-safe */
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);

was_root = br_is_root_bridge(br);
if (br_supersedes_port_info(p, bpdu)) {
br_record_config_information(p, bpdu);
br_configuration_update(br);
br_port_state_selection(br);

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);
}
}

                                /*这个判断的作用不是太明白,盼指点……*/
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);
}

                /*如果当前端口是designate port,则根据当前配置信息,生成BPDU,发送出去*/
                else if (br_is_designated_port(p)) {
br_reply(p);
}

read_unlock(&br->lock);
}
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);
}
br_reply就是一个提取前前的信息,组包发包的过程。


br_supersedes_port_info这个判断,就是把包中的值,同先前指定的对应值进行判断和比较,经确定是否需要更新:
/* 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, 8);
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;

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

if (memcmp(&bpdu->bridge_id, &p->br->bridge_id, 8))
return 1;

if (bpdu->port_id <= p->designated_port)
return 1;

return 0;
}

在进行更新之前,先把包中对应的值拷过来:
/* 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;

br_timer_set(&p->message_age_timer, jiffies - bpdu->message_age);
}

然后就是进行STP的选举,它们对应的协议的含义前面已经叙述了:
/* called under bridge lock */
void br_configuration_update(struct net_bridge *br)
{
br_root_selection(br);
br_designated_port_selection(br);
}

接着设置端口的状态:
/* 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;
}
}

如果原来自己是根桥,现在不是了,即拓朴已改变,需要发送一个TCN类型的BPDU包,通告更新(另外有一种情况就是自己原来不是根,现在变成了根,在前面br_configuration_update函数调用中,会有类似的处理):
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);
}
}

后面那个判断不是很明白,盼指点一下……

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


 Pagliuca 回复于:2006-01-12 15:28:24

九贱大侠,最近也在看这个,主要做的工作就是linux下的网桥,以前实现过一个没有生成树的也无操作系统的简单网桥。不过现在有几个弱智的问题,望解答一下:
 
/*获取目的MAC地址*/dest = skb->mac.ethernet->h_dest

这条语句,我判断skb应该是收到的帧,但是是放在缓冲区里的吗?

这句话应该是获取了帧的目的MAC地址,然后为什么会在生成树那条语句
 if (br->stp_enabled &&
            !memcmp(dest, bridge_ula, 5) &&
            !(dest[5] & 0xF0))                /*01-80-c2-00-F0-00 是一个什么地址?为什么要判断呢?*/
                goto handle_special_frame;
就是目的MAC地址的高4位应该为0?为什么有这个要求?
还有01-80-c2-00-F0-00 是一个什么地址?这也是你没有找出答案的地方?
难道通过这个来判断是否是BPDU,不太明白

还有就是系统是如何调用网桥处理函数br_handle_frame(struct sk_buff *skb)的呢?


 独孤九贱 回复于:2006-01-12 15:35:02

引用: 原帖由 Pagliuca 于 2006-1-12 15:28 发表
九贱大侠,最近也在看这个,主要做的工作就是linux下的网桥,以前实现过一个没有生成树的也无操作系统的简单网桥。不过现在有几个弱智的问题,望解答一下:
 
/*获取目的MAC地址*/dest = skb->mac.ethernet- ... 


凑巧,因为处理一个工程问题,被STP难住了,今天上午才来看看这个协议的实现,也是初学者,不是大虾,以后大家一起讨论:

1、第一个问题,二层拆包后的结构是放在skb中的啊……
2、就是目的MAC地址的高4位应该为0?为什么有这个要求?
这个是什么意思?不明白你的意思,不过STP协议中,目的MAC,都是使用的多播目标MAC地址:01-80-c2-00-00-00(Bridge_group_addr:网桥组多播地址)
3、程序判断!(dest[5] & 0xF0)) ,我翻了RFC文档,google了一下,没有找到为何要做此判断,正在查资料中……
4、函数的调用,我一开始就说了吧……

[  本帖最后由 独孤九贱 于 2006-1-12 15:37 编辑 ]


 Pagliuca 回复于:2006-01-12 15:52:53

你的意思是说memcmp(dest, bridge_ula, 5) 就是判断帧的目的地址是多播目标MAC地址:01-80-c2-00-00-00中的一个吗?
还有!(dest[5] & 0xF0)),大侠觉得在802。1d协议里会有交代吗?


 独孤九贱 回复于:2006-01-12 16:00:46

引用: 原帖由 Pagliuca 于 2006-1-12 15:52 发表
你的意思是说memcmp(dest, bridge_ula, 5) 就是判断帧的目的地址是多播目标MAC地址:01-80-c2-00-00-00中的一个吗?
还有!(dest[5] & 0xF0)),大侠觉得在802。1d协议里会有交代吗? 



你把函数看错了吧?
你看看bridge_ula的定义:
unsigned char bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 };
应该就是判断当前是目的MAC是否是“01-80-c2-00-00-00到01-80-c2-00-00-FF”中的一个,而且不能是01-80-c2-00-00-F0,(事实上多播地址段是从01-80-c2-00-00-00到01-80-c2-7F-FF-FF,这里的代表似乎表了STP只用了其中一部份),以确定是否是STP协议的包

我看了RFC文档,没有看到0xF0,正在找最新的看

[  本帖最后由 独孤九贱 于 2006-1-12 16:14 编辑 ]


 独孤九贱 回复于:2006-01-12 16:52:42

引用: 原帖由 Pagliuca 于 2006-1-12 15:52 发表
还有!(dest[5] & 0xF0)),大侠觉得在802。1d协议里会有交代吗? 



偶真是一个愚蠢的人,想歪了,!(dest[5] & 0xF0)),偶把它想成!(dest[5] = 0xF0)),根本不存在什么特殊地址的问题,只是“Linux允许这个地址的第六个字节的低四位非空”而已……


 albcamus 回复于:2006-01-12 17:02:04

看不懂:(


 独孤九贱 回复于:2006-01-12 17:03:44

引用: 原帖由 albcamus 于 2006-1-12 17:02 发表
看不懂:( 


哎,主要是写的人水平太烂……:em02:


 albcamus 回复于:2006-01-12 18:23:38

引用: 原帖由 独孤九贱 于 2006-1-12 17:03 发表

哎,主要是写的人水平太烂……:em02: 



晕! 是真不会, 偶明年要学习网络了, 不然就成软肋了:(
不灌了,您继续:)


 bend 回复于:2006-01-12 23:15:11

为什么要看2.4的呢?2.6的性能,会比2.4的强很多


 platinum 回复于:2006-01-13 08:13:40

引用: 原帖由 bend 于 2006-1-12 23:15 发表
为什么要看2.4的呢?2.6的性能,会比2.4的强很多 


在进程调度、内核抢占方面的确如此,网络部分呢?


 独孤九贱 回复于:2006-01-13 09:31:23

引用: 原帖由 bend 于 2006-1-12 23:15 发表
为什么要看2.4的呢?2.6的性能,会比2.4的强很多 



首先网络部份,底层上变化不大……
其次实际,2.6的性能和2.4无论是用测试仪还是实地测试,均没有显示过人之处……
况且偶是为了学习网桥和STP而看一个系统的实现,所以是Linux还是Unix,是2.4还是2.6都没有多大区别的。

难道没有研究网络的朋友啊?真希望有同道中人一起学习,没有人帮助,没有任何参考资料,看起来太累了!


 zhangjiakouzf 回复于:2006-01-13 10:05:42

俺贴两个网址,也许对楼主又帮助,我在看桥代码时用过的
http://www.ee.unimelb.edu.au/staff/lha/Linux_network_stack_walkthrough.html
http://www.ecsl.cs.sunysb.edu/elibrary/linux/network/recvpath.pdf


 zhangjiakouzf 回复于:2006-01-13 10:09:00

还有一个
http://www.freekernel.org/cgi-bin/NeoBoard/NeoBoard.cgi?Db=pub


 独孤九贱 回复于:2006-01-13 10:19:17

引用: 原帖由 zhangjiakouzf 于 2006-1-13 10:05 发表
俺贴两个网址,也许对楼主又帮助,我在看桥代码时用过的
http://www.ee.unimelb.edu.au/staff/lha/Linux_network_stack_walkthrough.html
http://www.ecsl.cs.sunysb.edu/elibrary/linux/netwo ... 



谢谢大家!!
前面忘了贴桥的数据结构的分析了,补上来:
数据结构:

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

struct net_bridge
{
struct net_bridge *next; //下一个网桥
rwlock_t lock; //读写锁
struct net_bridge_port *port_list; //桥组中的端口列表

/*网桥都会有一个虚拟设备用来进行管理,就是它了。说到这里,我想到了以前一个没有解决的问题:对网桥管理IP配置后,发现其虚拟的MAC地址是动态生成的,取的是桥组中某一个物理端口的MAC地址(好像是第一个网卡的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;
};
[color=Red]这个结构对应了内核缓存中的skb->dev->br_port;[/color]

桥的CAM表是一个struct net_bridge_fdb_entry类型的数组,数组最大为:
#define BR_HASH_BITS 8
#define BR_HASH_SIZE (1 << BR_HASH_BITS)
256个项。
网桥根据目的地址来查表,以确定由哪个接口把包转发出去:
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地址
};

事实上,整个桥的处理,就是一个根据源地址学习(写表),根据目的地址确定发送端口(查表)的过程。当然,还有一个插曲,如处理发给本机的数据,需要交到上层,如果开启了STP,还要处理STP协议

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


 cwtunix 回复于:2006-01-13 22:05:36

顶,哥们,继续!


 liuzhuan23 回复于:2006-01-16 09:49:38

呵呵,谢谢分享知识


 lingangdan 回复于:2006-01-16 10:33:11

强人,不断向您学习~~~


 Pagliuca 回复于:2006-01-16 11:06:26

大侠,在STP的处理函数br_stp_handle_bpdu(struct sk_buff *skb)里的
        buf = skb->mac.raw + 14;
 是什么意思啊,raw是指的什么东西呢


 独孤九贱 回复于:2006-01-16 11:30:05

引用: 原帖由 Pagliuca 于 2006-1-16 11:06 发表
大侠,在STP的处理函数br_stp_handle_bpdu(struct sk_buff *skb)里的
        buf = skb->mac.raw + 14;
 是什么意思啊,raw是指的什么东西呢 



原始的二层数据

这样skb->mac.raw + 14就表示跳过DLC包头:
如果是IEEE802的话,则就802.3包头,即:dst(6)+src(6)+len(2)=14;
如果是以太的话,则就是以太包头:dst(6)+src(6)+type(2)=14;

以STP协议的BPDU包来说,用的IEEE802封装,加14后,就指向了802.2LLC头部了,这样,用
buf[6]就可以指向BPDU的类型字段,前面五个分别是:
DSAP(1)+SSAP(1)+cntl(1)————802.2LLC部份三个字节
BPDU protocol ID(2)+BPDU version(1)——BPDU的开头部份,二个字节

tcp/ip详解卷一第二章第二页的最上边那幅图有很清楚的描述,记得是……

[  本帖最后由 独孤九贱 于 2006-1-16 11:31 编辑 ]


 Pagliuca 回复于:2006-01-16 16:28:31

kfree_skb(skb);
是用来清空buf中的数据skb吗?


 独孤九贱 回复于:2006-01-16 16:55:42

引用: 原帖由 Pagliuca 于 2006-1-16 16:28 发表
kfree_skb(skb);
是用来清空buf中的数据skb吗? 



建议你还是找专门分析Linux skb buffer的资料吧,我在这里一时半会说不清楚,或者等我把STP拿下来后,专门贴篇分析skb的贴子吧


 keyinwind 回复于:2006-01-16 19:27:13

希望LZ先写好STP的文档.... 赞一个!


 独孤九贱 回复于:2006-01-17 10:57:55

引用: 原帖由 keyinwind 于 2006-1-16 19:27 发表
希望LZ先写好STP的文档.... 赞一个! 



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

[size=3][color=Red]Linux的网桥中的STP的实现分析初步[/color][/size]

[color=Red][size=3]一、STP的框架结构[/size][/color]
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包了),所以这里一起来分析。

[size=3][color=Red]二、Config类型的BPDU包的接收处理[/color][/size]
这个处理过程是在拆完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

[size=3][color=Red]2.1 br_supersedes_port_info(p, bpdu)[/color][/size]


/* 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, 8);
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, 8);
if (t < 0)
return 1;
else if (t > 0)
return 0;

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

return 0;
}

[size=3][color=Red]2.2 br_record_config_information[/color][/size]
如果检测到有变动,则刷新自己的记录先:
/* 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

[size=3][color=Red]2。3 br_configuration_update[/color][/size]前面说过,根桥的选举不是在这里进行,这里进行根端口和指定端口的选举
/* called under bridge lock */
void br_configuration_update(struct net_bridge *br)
{

                br_root_selection(br);/*选举根端口*/
br_designated_port_selection(br);/*选举指定端口*/
}

[size=3][color=Red]2.3.1 根端口的选举br_root_selection[/color][/size]根端口的选举同样是以上四个步骤,只是有一点小技巧:它逐个遍历桥的每一个所属端口,找出一个符合条件的,保存下来,再用下一个来与之做比较,用变量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, 8) <= 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, 8);
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, 8);
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;
}

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

[size=3][color=Red]2。3。2 指定端口的选举br_designated_port_selection[/color][/size]
/* 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 编辑 ]


 guotie 回复于:2006-01-17 11:14:57

赞一个先!!


 独孤九贱 回复于:2006-01-17 11:17:13

[color=Red]2。3。3 端口状态选择[/color]
 /* 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 相应状态位就可以了!!


 独孤九贱 回复于:2006-01-17 11:40:11

[size=3][color=Red]三、选举完成之后[/color][/size]
实在不会取名字了,前面分析了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);
[color=Red]br_transmit_tcn(br);[/color]
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函数


 Pagliuca 回复于:2006-01-17 16:22:39

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


 独孤九贱 回复于:2006-01-17 17:11:50

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



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


 Pagliuca 回复于:2006-01-18 15:30:14

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呢?
多谢大侠指教!


 独孤九贱 回复于:2006-01-18 17:32:39

引用: 原帖由 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有两次……


 Pagliuca 回复于:2006-01-18 18:15:13

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 编辑 ]


 独孤九贱 回复于:2006-01-19 09:39:02

引用: 原帖由 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就是待断判断的端口……而不是根桥,桥之类的东东,请仔细看我上面的代码分析吧……


 Pagliuca 回复于:2006-01-19 11:22:56

如果对于有3个以上端口的网桥,designated port是可能有2个或者更多个的,难道LINUX网桥就是指的2个端口的网桥吗?


 独孤九贱 回复于:2006-01-19 11:45:56

引用: 原帖由 Pagliuca 于 2006-1-19 11:22 发表
如果对于有3个以上端口的网桥,designated port是可能有2个或者更多个的,难道LINUX网桥就是指的2个端口的网桥吗? 



端口是不限的……所以有个遍历所有端口的过程:
br_configuration_update->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;
}
}
br_should_become_designated_port为判断,br_become_designated_port为指定,请注意这个while循环,它是遍历桥中所有的端口;


 casonic 回复于:2006-01-19 18:31:50

精彩!
不知道大侠有没有研究过brctl这个工具?
可不可以解释一下这个工具
里面有命令类似brctl addif br0 eth1 //eth1 is one of my NIC card
为什么不是brctl addif br0 0x8000


 独孤九贱 回复于:2006-01-20 08:57:31

引用: 原帖由 casonic 于 2006-1-19 18:31 发表
精彩!
不知道大侠有没有研究过brctl这个工具?
可不可以解释一下这个工具
里面有命令类似brctl addif br0 eth1 //eth1 is one of my NIC card
为什么不是brctl addif br0 0x8000 



brctl是一个很简单的网桥管理工具,就是通过ioctl与内核交互,我自己重写了一个图形界面的,运行得很好^o^


 Pagliuca 回复于:2006-01-25 16:51:12

/* 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);
}
这几天没有看网桥,今天有时间又看了一下,觉得对于判断指定端口这个函数还是有些疑惑,希望大侠能说得清楚一些:
    上面的判断应该是:第一步,看端口的所在的网桥ID是否是指定网桥;第二步,看端口ID是否是指定端口ID。
    我看的资料理解的是:判断是否是指定端口,是看这个网桥的BPDU的信息:1根端口ID,2开销,3发送网桥ID。这3个组成的BPDU是否优于网桥从这个端口收到的BPDU。如果网桥的BPDU优,则这个端口是网桥的指定端口,网桥会往这些指定端口发送自己的BPDU。这些指定端口和根端口都是生成树的一部分。
    怎么跟这个函数写的意思不太一样呢?

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


 macrodba 回复于:2006-01-25 17:17:44

支持一下了


 独孤九贱 回复于:2006-01-26 11:08:47

引用: 原帖由 Pagliuca 于 2006-1-25 16:51 发表
/* called under bridge lock */
int br_is_designated_port(struct net_bridge_port *p)
{
        return !memcmp(&p->designated_bridge, &p->br->bridge_id, 8) &&
            ... 



前阵子好像讨论过吧,这个函数只是“查看”,不是“判断”……,如果我没有看错的话,最近在忙一个东东,没有时间来搞这个了!


 zong9047 回复于:2006-05-24 23:13:41

我现在linux中,用的无线芯片是philip的bgw200,用它能否构建无线接入点?如果不行,能不能用linux内置的网桥功能构建一个网桥,实现无线网和有线网的桥接?请高手指点!