[转载]Linux网桥源码框架分析初步
<P>信息来源: 邪恶八进制信息安全团队</P><P>版本:Linux 2.4.18<BR><BR>一、调用<BR>在src/net/core/dev.c的软中断函数static void net_rx_action(struct softirq_action *h)中:<BR>line 1479<BR><BR>#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)<BR>if (skb->dev->br_port != NULL &&<BR>br_handle_frame_hook != NULL) {<BR>handle_bridge(skb, pt_prev);<BR>dev_put(rx_dev);<BR>continue;<BR>}<BR>#endif<BR>如果定义了网桥或网桥模块,则由handle_bridge函数处理<BR>skb->dev->br_port :接收该数据包的端口是网桥端口组的一员<BR>br_handle_frame_hook :定义了网桥处理函数<BR><BR>二、初始化<BR>src/net/bridge/br.c:<BR>static int __init br_init(void)<BR>{<BR>printk(KERN_INFO "NET4: Ethernet Bridge 008 for NET4.0\n";<BR><BR>br_handle_frame_hook = br_handle_frame;<BR>br_ioctl_hook = br_ioctl_deviceless_stub;<BR>#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)<BR>br_fdb_get_hook = br_fdb_get;<BR>br_fdb_put_hook = br_fdb_put;<BR>#endif<BR>register_netdevice_notifier(&br_device_notifier);<BR><BR>return 0;<BR>}<BR>初始化函数指明了网桥的处理函数是br_handle_frame<BR>ioctl处理函数是:br_ioctl_deviceless_stub<BR><BR>三、br_handle_frame(br_input.c)<BR>/*网桥处理函数*/<BR>void br_handle_frame(struct sk_buff *skb)<BR>{<BR>struct net_bridge *br;<BR>unsigned char *dest;<BR>struct net_bridge_port *p;<BR><BR>/*获取目的MAC地址*/<BR>dest = skb->mac.ethernet->h_dest;<BR><BR>/*skb->dev->br_port用于指定接收该数据包的端口,若不是属于网桥的端口,则为NULL*/<BR>p = skb->dev->br_port;<BR>if (p == NULL) /*端口不是网桥组端口中*/<BR>goto err_nolock;<BR><BR>/*本端口所属的网桥组*/<BR>br = p->br;<BR><BR>/*加锁,因为在转发中需要读CAM表,所以必须加读锁,避免在这个过程中另外的内核控制路径(如多处理机上另外一个CPU上的系统调用)修改CAM表*/<BR>read_lock(&br->lock);<BR>if (skb->dev->br_port == NULL) /*前面判断过的*/<BR>goto err;<BR><BR>/*br->dev是网桥的虚拟网卡,如果它未UP,或网桥DISABLED,p->state实际上是桥的当前端口的STP计算判断后的状态*/<BR>if (!(br->dev.flags & IFF_UP) ||<BR>p->state == BR_STATE_DISABLED)<BR>goto err;<BR><BR>/*源MAC地址为255.X.X.X,即源MAC是多播或广播,丢弃之*/<BR>if (skb->mac.ethernet->h_source[0] & 1)<BR>goto err;<BR><BR>/*众所周之,网桥之所以是网桥,比HUB更智能,是因为它有一个MAC-PORT的表,这样转发数据就不用广播,而查表定端口就可以了<BR>每次收到一个包,网桥都会学习其来源MAC,添加进这个表。Linux中这个表叫CAM表(这个名字是其它资料上看的)。<BR>如果桥的状态是LEARNING或FORWARDING(学习或转发),则学习该包的源地址skb->mac.ethernet->h_source,<BR>将其添加到CAM表中,如果已经存在于表中了,则更新定时器,br_fdb_insert完成了这一过程*/<BR>if (p->state == BR_STATE_LEARNING ||<BR>p->state == BR_STATE_FORWARDING)<BR>br_fdb_insert(br, p, skb->mac.ethernet->h_source, 0);<BR><BR>/*STP协议的BPDU包的目的MAC采用的是多播目标MAC地址:从01-80-c2-00-00-00(Bridge_group_addr:网桥组多播地址)开始<BR>所以这里是如果开启了STP,而当前数据包又是一个BPDU<BR>(!memcmp(dest, bridge_ula, 5), unsigned char bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 },<BR>则交由相应函数处理*/<BR>if (br->stp_enabled &&<BR>/*这里只比较前5个字节,没有仔细研究过STP是使用了全部多播地址(从0 1 : 0 0 : 5 e : 0 0 : 0 0 : 0 0到0 1 : 0 0 : 5 e : 7 f : ff : ff。),还是只使用了一部份,这里看来似乎只是一部份,没去深究了*/<BR>!memcmp(dest, bridge_ula, 5) &&<BR>!(dest[5] & 0xF0)) /*01-80-c2-00-00-F0 是一个什么地址?为什么要判断呢?*/<BR>goto handle_special_frame;<BR><BR>/*处理钩子函数,然后转交br_handle_frame_finish函数继续处理*/<BR>if (p->state == BR_STATE_FORWARDING) {<BR>NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,<BR>br_handle_frame_finish);<BR>read_unlock(&br->lock);<BR>return;<BR>}<BR><BR>err:<BR>read_unlock(&br->lock);<BR>err_nolock:<BR>kfree_skb(skb);<BR>return;<BR><BR>handle_special_frame:<BR>if (!dest[5]) {<BR>br_stp_handle_bpdu(skb);<BR>return;<BR>}<BR><BR>kfree_skb(skb);<BR>}<BR><BR>四、br_handle_frame_finish<BR><BR>static int br_handle_frame_finish(struct sk_buff *skb)<BR>{<BR>struct net_bridge *br;<BR>unsigned char *dest;<BR>struct net_bridge_fdb_entry *dst;<BR>struct net_bridge_port *p;<BR>int passedup;<BR><BR>/*前面基本相同*/<BR>dest = skb->mac.ethernet->h_dest;<BR><BR><BR>p = skb->dev->br_port;<BR>if (p == NULL)<BR>goto err_nolock;<BR><BR>br = p->br;<BR>read_lock(&br->lock);<BR>if (skb->dev->br_port == NULL)<BR>goto err;<BR><BR>passedup = 0;<BR><BR>/*如果网桥的虚拟网卡处于混杂模式,那么每个接收到的数据包都需要克隆一份<BR>送到AF_PACKET协议处理体(网络软中断函数net_rx_action中ptype_all链的处理)。*/<BR>if (br->dev.flags & IFF_PROMISC) {<BR>struct sk_buff *skb2;<BR><BR>skb2 = skb_clone(skb, GFP_ATOMIC);<BR>if (skb2 != NULL) {<BR>passedup = 1;<BR>br_pass_frame_up(br, skb2);<BR>}<BR>}<BR><BR>/*目的MAC为广播或多播,则需要向本机的上层协议栈传送这个数据包,这里有一个标志变量passedup<BR>用于表示是否传送过了,如果已传送过,那就算了*/<BR>if (dest[0] & 1) {<BR>br_flood_forward(br, skb, !passedup);<BR>if (!passedup)<BR>br_pass_frame_up(br, skb);<BR>goto out;<BR>}<BR><BR>/*Linux中的MAC-PORT表是CAM表,这里根据目的地址来查表,以确定由哪个接口把包转发出去<BR>每一个表项是通过结构struct net_bridge_fdb_entry来描述的:<BR>struct net_bridge_fdb_entry<BR>{<BR>struct net_bridge_fdb_entry *next_hash; //用于CAM表连接的链表指针<BR>struct net_bridge_fdb_entry **pprev_hash; //为什么是pprev不是prev呢?还没有仔细去研究<BR>atomic_t use_count; //此项当前的引用计数器<BR>mac_addr addr; //MAC地址<BR>struct net_bridge_port *dst; //此项所对应的物理端口<BR>unsigned long ageing_timer; //处理MAC超时<BR>unsigned is_local:1; //是否是本机的MAC地址<BR>unsigned is_static:1; //是否是静态MAC地址<BR>};*/<BR>dst = br_fdb_get(br, dest);<BR><BR>/*查询CAM表后,如果能够找到表项,并且目的MAC是到本机的虚拟网卡的,那么就需要把这个包提交给上层协议,<BR>这样,我们就可以通过这个虚拟网卡的地址来远程管理网桥了*/<BR>if (dst != NULL && dst->is_local) {<BR>if (!passedup)<BR>br_pass_frame_up(br, skb);<BR>else<BR>kfree_skb(skb);<BR>br_fdb_put(dst);<BR>goto out;<BR>}<BR><BR>/*查到表了,且不是本地虚拟网卡的,转发之*/<BR>if (dst != NULL) {<BR>br_forward(dst->dst, skb);<BR>br_fdb_put(dst);<BR>goto out;<BR>}<BR><BR>/*如果表里边查不到,那么只好学习学习HUB了……*/<BR>br_flood_forward(br, skb, 0);<BR><BR>out:<BR>read_unlock(&br->lock);<BR>return 0;<BR><BR>err:<BR>read_unlock(&br->lock);<BR>err_nolock:<BR>kfree_skb(skb);<BR>return 0;<BR>}<BR><BR>基本框架就是这样了,与那些讲网桥原理的书上讲的基本差不多……<BR>网桥之所以是网桥,主要靠这两个函数:<BR>br_fdb_insert<BR>br_fdb_get<BR>一个学习,一个查表;<BR>另外,支持STP,处理BPDU,需要用到函数br_stp_handle_bpdu<BR>哪位有这三个函数的细节分析,可否送九贱一份,免得下午那么辛苦再去啃代码……<BR><BR>扫了一下 br_fdb_insert,结构还是很清析,如果当前项已存在于hash表项中,则更新它(__fdb_possibly_replace),如果是新项,则插入,实际是一个双向链表的维护过程(__hash_link):<BR><BR>void br_fdb_insert(struct net_bridge *br,<BR>struct net_bridge_port *source,<BR>unsigned char *addr,<BR>int is_local)<BR>{<BR>struct net_bridge_fdb_entry *fdb;<BR>int hash;<BR><BR>hash = br_mac_hash(addr);<BR><BR>write_lock_bh(&br->hash_lock);<BR>fdb = br->hash[hash];<BR>while (fdb != NULL) {<BR>if (!fdb->is_local &&<BR>!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {<BR>__fdb_possibly_replace(fdb, source, is_local);<BR>write_unlock_bh(&br->hash_lock);<BR>return;<BR>}<BR><BR>fdb = fdb->next_hash;<BR>}<BR><BR>fdb = kmalloc(sizeof(*fdb), GFP_ATOMIC);<BR>if (fdb == NULL) {<BR>write_unlock_bh(&br->hash_lock);<BR>return;<BR>}<BR><BR>memcpy(fdb->addr.addr, addr, ETH_ALEN);<BR>atomic_set(&fdb->use_count, 1);<BR>fdb->dst = source;<BR>fdb->is_local = is_local;<BR>fdb->is_static = is_local;<BR>fdb->ageing_timer = jiffies;<BR><BR>__hash_link(br, fdb, hash);<BR><BR>write_unlock_bh(&br->hash_lock);<BR>}<BR><BR>同样,查表也是一个遍历链表,进行地址匹配的过程:<BR>struct net_bridge_fdb_entry *br_fdb_get(struct net_bridge *br, unsigned char *addr)<BR>{<BR>struct net_bridge_fdb_entry *fdb;<BR><BR>read_lock_bh(&br->hash_lock);<BR>fdb = br->hash[br_mac_hash(addr)];<BR>while (fdb != NULL) {<BR>if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {<BR>if (!has_expired(br, fdb)) {<BR>atomic_inc(&fdb->use_count);<BR>read_unlock_bh(&br->hash_lock);<BR>return fdb;<BR>}<BR><BR>read_unlock_bh(&br->hash_lock);<BR>return NULL;<BR>}<BR><BR>fdb = fdb->next_hash;<BR>}<BR><BR>read_unlock_bh(&br->hash_lock);<BR>return NULL;<BR>}<!-- / message --></P>
页:
[1]