- 论坛徽章:
- 0
|
[这篇贴子是边看代码边写的,其中有些比较凌乱,有些地方有错误,对于贴子的整理和改正,我会将其陆续贴于我的个人主页上边:http://www.skynet.org.cn/forumdisplay.php?fid=12&page=,希望大家指正。2006-2-12,大年十五]
今天处理网桥的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 编辑 ] |
|