Linux网桥源码框架分析初步

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

版本:Linux2.4.18

一、调用
在src/net/core/dev.c的软中断函数staticvoidnet_rx_action(structsoftirq_action*h)中:
line1479


#ifdefined(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:

staticint__initbr_init(void)

{

printk(KERN_INFO"NET4:EthernetBridge008forNET4.0/n");



br_handle_frame_hook=br_handle_frame;

br_ioctl_hook=br_ioctl_deviceless_stub;

#ifdefined(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);



return0;

}


初始化函数指明了网桥的处理函数是br_handle_frame
ioctl处理函数是:br_ioctl_deviceless_stub

三、br_handle_frame(br_input.c)

/*网桥处理函数*/

voidbr_handle_frame(structsk_buff*skb)

{

structnet_bridge*br;

unsignedchar*dest;

structnet_bridge_port*p;



/*获取目的MAC地址*/

dest=skb->mac.ethernet->h_dest;



/*skb->dev->br_port用于指定接收该数据包的端口,若不是属于网桥的端口,则为NULL*/

p=skb->dev->br_port;

if(p==NULL) /*端口不是网桥组端口中*/

gotoerr_nolock;



/*本端口所属的网桥组*/

br=p->br;



/*加锁,因为在转发中需要读CAM表,所以必须加读锁,避免在这个过程中另外的内核控制路径(如多处理机上另外一个CPU上的系统调用)修改CAM表*/

read_lock(&br->lock);

if(skb->dev->br_port==NULL) /*前面判断过的*/

gotoerr;



/*br->dev是网桥的虚拟网卡,如果它未UP,或网桥DISABLED,p->state实际上是桥的当前端口的STP计算判断后的状态*/

if(!(br->dev.flags&IFF_UP)||

p->state==BR_STATE_DISABLED)

gotoerr;



/*源MAC地址为255.X.X.X,即源MAC是多播或广播,丢弃之*/

if(skb->mac.ethernet->h_source[0]&1)

gotoerr;



/*众所周之,网桥之所以是网桥,比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), unsignedcharbridge_ula[6]={0x01,0x80,0xc2,0x00,0x00,0x00};),

则交由相应函数处理*/

if(br->stp_enabled&&

/*这里只比较前5个字节,没有仔细研究过STP是使用了全部多播地址(从01:00:5e:00:00:00到01:00:5e:7f:ff:ff。),还是只使用了一部份,这里看来似乎只是一部份,没去深究了*/

!memcmp(dest,bridge_ula,5)&&

!(dest[5]&0xF0)) /*01-80-c2-00-00-F0是一个什么地址?为什么要判断呢?*/

gotohandle_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

staticintbr_handle_frame_finish(structsk_buff*skb)

{

structnet_bridge*br;

unsignedchar*dest;

structnet_bridge_fdb_entry*dst;

structnet_bridge_port*p;

intpassedup;



/*前面基本相同*/

dest=skb->mac.ethernet->h_dest;





p=skb->dev->br_port;

if(p==NULL)

gotoerr_nolock;



br=p->br;

read_lock(&br->lock);

if(skb->dev->br_port==NULL)

gotoerr;



passedup=0;



/*如果网桥的虚拟网卡处于混杂模式,那么每个接收到的数据包都需要克隆一份

送到AF_PACKET协议处理体(网络软中断函数net_rx_action中ptype_all链的处理)。*/

if(br->dev.flags&IFF_PROMISC){

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

gotoout;

}



/*Linux中的MAC-PORT表是CAM表,这里根据目的地址来查表,以确定由哪个接口把包转发出去

每一个表项是通过结构structnet_bridge_fdb_entry来描述的:

structnet_bridge_fdb_entry

{

structnet_bridge_fdb_entry *next_hash; //用于CAM表连接的链表指针

structnet_bridge_fdb_entry **pprev_hash; //为什么是pprev不是prev呢?还没有仔细去研究

atomic_t use_count; //此项当前的引用计数器

mac_addr addr; //MAC地址

structnet_bridge_port *dst; //此项所对应的物理端口

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

gotoout;

}



/*查到表了,且不是本地虚拟网卡的,转发之*/

if(dst!=NULL){

br_forward(dst->dst,skb);

br_fdb_put(dst);

gotoout;

}



/*如果表里边查不到,那么只好学习学习HUB了……*/

br_flood_forward(br,skb,0);



out:

read_unlock(&br->lock);

return0;



err:

read_unlock(&br->lock);

err_nolock:

kfree_skb(skb);

return0;

}


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

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

voidbr_fdb_insert(structnet_bridge*br,

structnet_bridge_port*source,

unsignedchar*addr,

intis_local)

{

structnet_bridge_fdb_entry*fdb;

inthash;



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

}


同样,查表也是一个遍历链表,进行地址匹配的过程:

structnet_bridge_fdb_entry*br_fdb_get(structnet_bridge*br,unsignedchar*addr)

{

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

returnfdb;

}



read_unlock_bh(&br->hash_lock);

returnNULL;

}



fdb=fdb->next_hash;

}



read_unlock_bh(&br->hash_lock);

returnNULL;

}



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



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

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


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

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



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

又看了一个函数,继续发上来:
STP的处理函数

/*calledunderbridgelock*/

voidbr_stp_handle_bpdu(structsk_buff*skb)

{

unsignedchar*buf;

structnet_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(TopologyChangeNotification,拓朴改变通告)*/



/*如果是配置类型*/

if(buf[6]==BPDU_TYPE_CONFIG){

/*内核中用structbr_config_bpdu描述一个BPDU包:

structbr_config_bpdu

{

unsigned topology_change:1; //拓朴改变标志

unsigned topology_change_ack:1; //拓朴改变回应标志

bridge_id root; //根ID,用于会聚后的网桥网络中,所有配置BPDU中的该字段都应该具有相同值(同VLAN),又可分为两个BID子字段:网桥优先级和网桥MAC地址

int root_path_cost; //路径开销,通向有根网桥(RootBridge)的所有链路的积累资本

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; //记录RootBridge生成当前BPDU起源信息的所消耗时间

int max_age; //保存BPDU的最长时间,也反映了拓朴变化通知(TopologyChangeNotification)过程中的网桥表生存时间情况

int hello_time; //指周期性配置BPDU间的时间

int forward_delay; //用于在Listening和Learning状态的时间,也反映了拓朴变化通知(TopologyChangeNotification)过程中的时间情况

};

在这个结构中,bpdu包的三个字段没有包含在内:

ProtocolID―协议字段,恒为0。

Version―版本字段,恒为0。

Type―决定该帧中所包含的两种BPDU格式类型(配置BPDU或TCNBPDU)。上面用buf[6]直接访问了,这是

因为bpdu之前,还有三个字节的LLC头,再加上ProtocolID(2字节),VersionID(1字节),3+2+1,所以是buf[6]

这是标准的802.3封包方式,与以太网封包略有不同,参见《tcp/ip详解卷一》第二章的第二页的最上面那张图(记得是)

*/



structbr_config_bpdubpdu;



/*一个辛苦的解包过程……*/

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

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值