歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> 更多Linux

Linux網橋源碼框架分析初探

  今天處理網橋的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; } 又看了一個函數,繼續發上來: 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] << 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] << 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); }



/*如果是TCN類型*/ if (buf[6] == BPDU_TYPE_TCN) { br_received_tcn_bpdu(p); /*調用TCN函數*/ kfree_skb(skb); return; } kfree_skb(skb); }



Copyright © Linux教程網 All Rights Reserved