http://www.chinaunix.net 作者:独孤九贱 发表于:2006-05-24 这篇贴子是边看代码边写的,其中有些比较凌乱,有些地方有错误,对于贴子的整理和改正,我会将其陆续贴于我的个人主页上边:http://www.skynet.org.cn/forumdisplay.php?fid=12&page =,希望大家指正。 今天处理网桥的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; } [ 本帖最后由 独孤九贱 于 2006-2-12 11:16 编辑 ] 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); } 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,正在找最新的看