Linux網橋源碼的實現
一、調用
在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 :接收該數據包的端口是網橋端口組的一員,若是接收當前數據包的接口不是網橋的某一物理端口,則其值爲NULL;
br_handle_frame_hook :定義了網橋處理函數這段代碼將數據包進行轉向,轉向的後的處理函數是鉤子函數br_handle_frame_hook,在此以前,handle_bridge函數還要處理一些其它的事情:
算法
static __inline__ int handle_bridge(struct sk_buff *skb, struct packet_type *pt_prev) { int ret = NET_RX_DROP; if (pt_prev) { if (!pt_prev->data) ret = deliver_to_old_ones(pt_prev, skb, 0); else { atomic_inc(&skb->users); ret = pt_prev->func(skb, skb->dev, pt_prev); } } br_handle_frame_hook(skb); return ret; }
pt_prev用於在共享SKB的時候提升效率,handle_bridge函數最後將控制權交由到了br_handle_frame_hook的手上。
數組
二、鉤子函數的註冊
br_handle_frame_hook用於網橋的處理,在網橋的初始化函數中(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_hanlde_frame
網絡
三、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(由用戶層來控制,如brctl),若是開啓了,則比較目的地址前5位 * 是否與多播目標MAC地址相同: * (!memcmp(dest, bridge_ula, 5) * 若是相同,若是地址第6位非空 * !(dest[5] & 0xF0)) * 那麼這肯定是一個STP的BPDU包,則跳轉到handle_special_frame,將處理權 * 將給函數br_stp_handle_bpdu */ if (br->stp_enabled && !memcmp(dest, bridge_ula, 5) && !(dest[5]& 0xF0)) 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_fdb_insert
二、STP的處理:br_stp_handle_bpdu
三、br_handle_frame_finish,咱們尚未查CAM表,轉發數據呢……
咱們先來看網橋的進一步處理br_handle_frame_finish,地址學習等內容,後面再來分析。
框架
四、br_handle_frame_finish
less
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; } /* * 用戶層經常須要用到一個虛擬的地址來管理網橋,若是目的地址很是,且爲本 * 地址地址,則交由上層函數處理 */ if (dst != NULL && dst->is_local) { if (!passedup) br_pass_frame_up(br, skb); else kfree_skb(skb); br_fdb_put(dst); goto out; } /*查詢CAM表,若是查到表了,轉發之*/ 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_forward
二、網橋數據轉發:br_fdb_put。
另外,網橋的處理中,還涉及到內核中一些重要的數據結構:
對Linux上全部接口進行網橋劃分,能夠把一組端口劃分到一個網橋之中,同時一個系統上容許有多個網橋。內核描述一個網橋,使用了struct net_bridge結構:
ide
struct net_bridge { struct net_bridge *next; //下一個網橋 rwlock_t lock; //讀寫鎖 struct net_bridge_port *port_list; //橋組中的端口列表 /* 網橋都會有一個虛擬設備用來進行管理,就是它了。說到這裏,我想到了之前一個沒有解決的問題:對網橋管理IP配置後,發現其虛擬的MAC地址是動態生成的,取的是橋組中某一個物理端口的MAC地址(好像是第一個),這樣,若是遠程管理時就有麻煩:若是你動態調整網橋中的端口,如刪除某個網卡出去,用於管理的虛擬網卡的地址就有能夠改變,致使不能遠程管理,盼指點如何解決此問題呢?也許看完整個代碼就會也答案……*/ struct net_device dev; struct net_device_stats statistics; //網橋虛擬網卡的統計數據 rwlock_t hash_lock; //hash表的讀寫鎖,這個表就是用於存放橋的MAC-PORT對應表 struct net_bridge_fdb_entry *hash[BR_HASH_SIZE]; //就是這張表了,也叫CAM表 struct timer_list tick; /*如下定義了STP協議所使用的信息,參見STP協議的相關定義*/ bridge_id designated_root; int root_path_cost; int root_port; int max_age; int hello_time; int forward_delay; bridge_id bridge_id; int bridge_max_age; int bridge_hello_time; int bridge_forward_delay; unsigned stp_enabled:1; unsigned topology_change:1; unsigned topology_change_detected:1; struct br_timer hello_timer; struct br_timer tcn_timer; struct br_timer topology_change_timer; struct br_timer gc_timer; int ageing_time; int gc_interval; };
能夠看出,橋中有幾個重要的地方:
一、橋的端口成員:struct net_bridge_port *port_list;
二、橋的CAM表:struct net_bridge_fdb_entry *hash[BR_HASH_SIZE];
三、橋的虛擬網卡
四、STP
橋的虛擬網卡是一個struct net_device設備,它在2.4中是如此龐大,要對它在這裏進行分析無疑是很是困難的,改天你們一塊兒討論吧。
STP的相關成員的定義與STP包的結構是緊密相關的,看了其包結構,能夠分析出這些成員了,再也不一一列舉了。
網橋中的端口,用struct net_bridge結構表示,它實際上表示的是接收該數據包的網橋的端口的相關信息:
函數
struct net_bridge_port { struct net_bridge_port *next; //網橋端口組中的下一個端口 struct net_bridge *br; //當前端口(接收數據包這個)所在的橋組 struct net_device *dev; //本端口所指向的物理網卡 int port_no; //本端口在網橋中的編號 port_id port_id; int state; int path_cost; bridge_id designated_root; int designated_cost; bridge_id designated_bridge; port_id designated_port; unsigned topology_change_ack:1; unsigned config_pending:1; int priority; struct br_timer forward_delay_timer; struct br_timer hold_timer; struct br_timer message_age_timer; };
這個結構對應了內核緩存中的skb->dev->br_port;
整個網橋的源碼框架就這樣了,學習,查表,進行STP處理,數據傳送。
第二部份,CAM表的學習與查找
前一章說過,CAM表的學習,是經過br_fdb_insert函數,而查找,則是調用了br_forward函數
一、CAM表的結構
每個地址-端口對應的項稱爲fdb項,內核中使用鏈表來組織fdb,它是一個struct net_bridge_fdb_entry
類型:
#define BR_HASH_BITS 8 #define BR_HASH_SIZE (1 << BR_HASH_BITS) 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地址 };
內核中,整個CAM表是用br->hash[hash_value]這個數組來存儲的,其中hash_value是根據源MAC地址進行hash運算得出的一個值,
這樣,br->hash[hash]就指向了此源MAC地址對應的fdb項所在的鏈表的首部。這樣說可能有點複雜,可用下圖來表示:
br->hash[hash_0]->fdb1->fdb2->fdb3……
br->hash[hash_1]->fdb1->fdb2->fdb3……
br->hash[hash_2]->fdb1->fdb2->fdb3……
br->hash[hash_3]->fdb1->fdb2->fdb3……
……
其中的hash_0、hash_1……是經過對源MAC地址進行hash運算求出的。so easy……
二、br_fdb_insert
/* * Function:br_fdb_insert * Purpose:網橋CAM表的學習,查詢新收到的源MAC-端口在原來表中是否有變化,以便更新CAM表 * Arguments: * struct net_bridge *br=>當前網橋 * struct net_bridge_port *source=>源端口 * unsigned char *addr=>源地址 * int is_local=>是否爲本地 * Return: * void */ 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; /* * CAM表是一個數組,每一個數組元素又是一個鏈表,這裏根據源地址,求對應的hash值,也就是當前源地址在表中的對應的編號id, * 這樣,就能夠經過br->hash[id]來訪問該地址對應的fdb項的鏈表了。 */ hash = br_mac_hash(addr); write_lock_bh(&br->hash_lock); /*加鎖*/ fdb = br->hash[hash]; /*取得當前源地址對應的fdb項鍊表*/ /*若是鏈表不爲空,則遍歷該鏈表,找到地址匹配的項,而後替換它*/ 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項分配空間,構建fdb項,而後構建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值已計算出來了,則直接將本項給當前橋br*/ __hash_link(br, fdb, hash); write_unlock_bh(&br->hash_lock); /*解鎖*/ }
這個函數中涉及到三個重要函數:
一、br_mac_hask:計算地址對應的hash值;
二、__fdb_possibly_replace:替換fdb項;
三、__hash_link:將當前項fdb插入hash表中;
A、br_mac_hask
函數用於計算地址對應的hash值。
將MAC地址逐字節左移兩位,而後與下一字節值求異或,完成以後,再將高8位和低8位再異或,最後使用return x & (BR_HASH_SIZE - 1);將hash值限定在指定範圍以內。
static __inline__ int br_mac_hash(unsigned char *mac) { unsigned long x; x = mac[0]; x = (x << 2) ^ mac[1]; x = (x << 2) ^ mac[2]; x = (x << 2) ^ mac[3]; x = (x << 2) ^ mac[4]; x = (x << 2) ^ mac[5]; x ^= x >> 8; /* * #define BR_HASH_BITS 8 * #define BR_HASH_SIZE (1 << BR_HASH_BITS) */ return x & (BR_HASH_SIZE - 1); }
B、__fdb_possibly_replace
由於在鏈表的循環查找中,發現當前源地址已在表項中存在,因此,須要更新它,這是一個單純的替換操做:
static __inline__ void __fdb_possibly_replace(struct net_bridge_fdb_entry *fdb, struct net_bridge_port *source, int is_local) { if (!fdb->is_static || is_local) { fdb->dst = source; /*更新當前地址所對應的端口*/ fdb->is_local = is_local; fdb->is_static = is_local; fdb->ageing_timer = jiffies; } }
C、__hash_link
函數將待插入項ent插入到hash值對應的橋的br->hash[hash]的鏈表的第一個項
static __inline__ void __hash_link(struct net_bridge *br, struct net_bridge_fdb_entry *ent, int hash) { /*讓ent->next指向鏈表首部,這樣後邊br->hash[hash]=ent,因而鏈首指針就指向ent了*/ ent->next_hash = br->hash[hash]; if (ent->next_hash != NULL) ent->next_hash->pprev_hash = &ent->next_hash; /*回指上一個元素*/ br->hash[hash] = ent; ent->pprev_hash =& br->hash[hash]; /*ent->pprev回指鏈首指針*/ }
三、br_forward
/* * Function:br_fdb_insert * Purpose:網橋CAM表的查找,查找待發送數據包目的MAC地址對應的fdb 表項 * Arguments: * struct net_bridge *br=>當前網橋 * unsigned char *addr=>待查找地址 * Return: * net_bridge_fdb_entry *=>查找到的fdb項,未查到則爲NULL */ 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)]; /*計算地址對應的hash值*/ /*遍歷鏈表,查找與地址相匹配的fdb項*/ 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,那麼全過程就是這樣,固然,若是網橋
打開了STP開關,則網橋須要進行STP的相關處理,STP的處理,是網橋中的一個重要部份,將在下一章進行分析。
第三部份,STP的實現分析初步
1、STP的框架結構
STP發送的是BPDU包,該包有全部兩種類型:配置和TCN(拓樸變動通知);
對於BPDU包的處理,有兩種:接收和發送(廢話),
對於配置類型的BPDU包的發送,它是靠定時器來完成的,參BPDU包的幾個定時器參數;
對於TCP類型的BPDU包的發送,從名字能夠看出來,它是當發現拓樸結構發生變動時發送的,如本機網橋配置的變化,物理接口的變更,分析其它機器變更後發出來的STP包等等。
BPDU的封包採用的是IEEE802封包(本想把封包結構的圖片貼上來,找不着在哪兒上傳圖片)。
前面分析過, br_handle_frame函數中,當網橋開啓了STP,且根據目的物理地址判斷出這是一個STP包,則交給br_stp_handle_bpdu函數處理。
br_stp_handle_bpdu函數主要是判斷是哪一種類型的BPDU包,而後調用相關的處理函數,即:
if(type==config) { br_received_config_bpdu(); } else if(type==tcn) { br_received_tcn_bpdu(); }
這是對接收到BPDU包的處理,關於config類型的BPDU包的發送,後面再分析;TCN包的發送,有一部份是在接收包處理過程當中處理的(由於分析config類型的BPDU包的時候,發現拓樸變動,固然要發送TCN包了),因此這裏一塊兒來分析。
2、Config類型的BPDU包的接收處理
這個處理過程是在拆完BPDU包後,調用br_received_config_bpdu函數完成的。
仍是得先交待一些理論的東西:
STP協議最終是爲了在網絡中生成一棵無環狀的樹,以期消除廣播風暴以及單播數據幀對網絡的影響。它始終在選舉三樣東東:
一、根網橋;
二、根端口;
三、「指定端口」和「指定網橋」
(這三個概念很是重要,若是你還不清楚,建議查閱相關文檔先,不然下邊的代碼分析也無從談起了)
而後再根據選舉出來的這三個東東,肯定端口的狀態:阻塞、轉發、學習、監聽、禁用……
要選舉出這三樣東東,得有一個判斷標誌,即算法,STP的判斷標準是:
一、判斷根橋ID,以最小的爲優;
二、判斷到根橋的最小路徑開銷;
三、肯定最小發送發BID(Sender BID)
四、肯定最小的端口ID
若是前面你查閱了BPDU的封包結構,根橋ID、最小路徑開銷、發送方網橋的ID、端口ID這幾個概念應該沒有問題了,不過這裏仍是簡單交一下:
一、根橋ID,咱們配置了網橋後,用brctl命令會發現8000.XXXXXX這樣一串,這就是網橋的ID號,用一標識每個網橋,後面的XXXX通常的橋的MAC地址,這樣ID值就不會重複。根橋ID,是指網絡中全部網橋的ID值最小的那一個,對應的具備根橋ID的橋,固然也是網絡的根橋了;
二、最小路徑開銷
動態路由中也相似這個概念,不過這裏用的不是跳數(局域網不比廣域網,不必定跳數大就慢,好比跳數小,是10M鏈路,跳數大的倒是千兆鏈路),最初的開銷定義爲1000M/鏈種帶寬,固然,這種方式不適用於萬兆網了……因此後來又有一個新的,對每一種鏈路定義一個常數值——詳請請查閱相關資料;
三、發送方ID
網橋以前要收斂出一個無環狀拓樸,就須要互相發送BPDU包,固然須要把本身的ID告訴對方,這樣對方好拿來互相比較;
四、端口ID
端口ID由優先級+端口編號組成,用於標識某個橋的某個端口,後面比較時好用。
生成樹算法就是利用上述四個參數在判斷,判斷過程老是相同的:
一、肯定根橋,橋ID最小的(即把包中的橋ID,同本身之前記錄的那個最小的橋ID相比,機器加電時,老是以本身的橋ID爲根橋ID)的爲根橋;
二、肯定最小路徑開銷;
三、肯定最小發送方ID;
四、肯定最小的端口ID:
這四步很是地重要,後面的因此比較都是這四個步驟。
有了這些概念,來看看對config類型的BPDU包的處理:
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); /*本身是根橋嗎?用本身的br_ID和BPDU包中的根ID相比較*/ was_root = br_is_root_bridge(br); /*比橋BPDU包中的信息(bpdu)和原先的對應的信息(p),若是須要更新,返回1,相同返回0,不需更新返回-1*/ if (br_supersedes_port_info(p, bpdu)) { /*刷新本身的相關信息*/ br_record_config_information(p, bpdu); /*進行root_bridge、port的選舉*/ br_configuration_update(br); /*設置端口狀態*/ br_port_state_selection(br);
以上這一段的邏輯概念很簡單:
一、把收到的BPDU包中的參數同本身原先記錄的相比較,(遵循前面說的四個比較步驟),以判斷是否須要進行更新——br_supersedes_port_info(p, bpdu)。
二、若是判斷須要進行更新,即上述四個步驟中,有任意一項有變更,則刷新本身的保存記錄:br_record_config_information(p, bpdu);
三、由於有變更,就須要改變本身的配置了:br_configuration_update(br);即前面說的,根據四步判斷後選舉根橋(注:根橋不是在這裏選舉的,前文說過,它是定時器定時發送BPDU包,而後收到的機器只需改變本身的記錄便可)、根端口、指定端口;
四、設置物理端口的轉發狀態:br_port_state_selection
2.1 br_supersedes_port_info(p, bpdu)
/* 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, ; 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; /*第三步,要同兩個橋ID比:已記錄的最小發送ID和本身的ID*/ t = memcmp(&bpdu->bridge_id, &p->designated_bridge, ; if (t < 0) return 1; else if (t > 0) return 0; if (memcmp(&bpdu->bridge_id, &p->br->bridge_id, ) return 1; /*第四步*/ if (bpdu->port_id <= p->designated_port) return 1; return 0; }
2.2 br_record_config_information
若是檢測到有變更,則刷新本身的記錄先:
/* 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; /*設置時間戳,關於STP的時間處理,後面來分析*/ br_timer_set(&p->message_age_timer, jiffies - bpdu->message_age); }
p對應的四個成員的概念對照BPDU封包結構,不難理解其含義:
p->designated_root: 指定的根網橋的網橋ID
p->designated_cost : 指定的到根橋的鏈路花銷
p->designated_bridge: 指定的發送當前BPDU包的網橋的ID
p->designated_port: 指定的發送當前BPDU包的網橋的端口的ID
2。3 br_configuration_update
前面說過,根橋的選舉不是在這裏進行,這裏進行根端口和指定端口的選舉
/* called under bridge lock */ void br_configuration_update(struct net_bridge *br) { br_root_selection(br);/*選舉根端口*/ br_designated_port_selection(br);/*選舉指定端口*/ }
2.3.1 根端口的選舉br_root_selection
根端口的選舉一樣是以上四個步驟,只是有一點小技巧:它逐個遍歷橋的每個所屬端口,找出一個符合條件的,保存下來,再用下一個來與之作比較,用變量root_port 來標誌:
/* called under bridge lock */ static void br_root_selection(struct net_bridge *br) { struct net_bridge_port *p; int root_port; root_port = 0; /*得到橋的所屬端口列表*/ p = br->port_list; /*這個循環很是重要,它遍歷橋的每個端口,進行以上四步判斷,找到一個,將其「保存」下來,而後再用下一個與保存的相比較,直至遍歷完,找到最優的那個,這個「保存」打了引號,是由於它僅僅是記當了端口編號:root_port = p->port_no;,而後再將其傳遞給比較函數br_should_become_root_port*/ while (p != NULL) { if (br_should_become_root_port(p, root_port)) root_port = p->port_no; p = p->next; } br->root_port = root_port; /*找完了尚未找到,則認爲本身就是根橋……*/ if (!root_port) { br->designated_root = br->bridge_id; br->root_path_cost = 0; } /*不然記錄相應的值*/ else { p = br_get_port(br, root_port); br->designated_root = p->designated_root; br->root_path_cost = p->designated_cost + p->path_cost; } }
br_should_become_root_port函數用以判斷端口p是否應該變成根端口,與它相比較的是原來那個根端口,函數第二個參數則爲此的ID號,在函數中調用 br_get_port獲取該端口:
/* called under bridge lock */ static int br_should_become_root_port(struct net_bridge_port *p, int root_port) { struct net_bridge *br; struct net_bridge_port *rp; int t; br = p->br; /*若當前端口是關閉狀態或爲一個指定端口,則不參與選舉,返回*/ if (p->state == BR_STATE_DISABLED || br_is_designated_port(p)) return 0; /*在根端口的選舉中,根橋是沒有選舉權的*/ if (memcmp(&br->bridge_id, &p->designated_root, <= 0) return 0; /*沒有指定等比較的端口ID(由於第一次它初始化爲0的)*/ if (!root_port) return 1; /*獲取待比較的根端口*/ rp = br_get_port(br, root_port); /*又是四大步,像打藍球*/ t = memcmp(&p->designated_root, &rp->designated_root, ; if (t < 0) return 1; else if (t > 0) return 0; if (p->designated_cost + p->path_cost < rp->designated_cost + rp->path_cost) return 1; else if (p->designated_cost + p->path_cost > rp->designated_cost + rp->path_cost) return 0; t = memcmp(&p->designated_bridge, &rp->designated_bridge, ; if (t < 0) return 1; else if (t > 0) return 0; if (p->designated_port < rp->designated_port) return 1; else if (p->designated_port > rp->designated_port) return 0; if (p->port_id < rp->port_id) return 1; return 0; }
這樣,遍歷完成後,根端口就被選出來了。
/* called under bridge lock */ static void br_designated_port_selection(struct net_bridge *br) { struct net_bridge_port *p; p = br->port_list; while (p != NULL) { if (p->state != BR_STATE_DISABLED && br_should_become_designated_port(p)) br_become_designated_port(p); p = p->next; } }
事實上這個過程與根端口的選舉過程極爲相似,沒有分析的必要了!
2。3。3 端口狀態選擇
/* 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; } }
函數的邏輯結構也很簡單:
遍歷整個橋所屬端口:
while (p != NULL)
若是端口已經DISABLED,則沒有判斷的必要了:
p->state != BR_STATE_DISABLED
若是端口是根端口,或者是指定端口,就讓讓它forwarding,不然就讓它blocking:
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); } /* called under bridge lock */ static void br_make_forwarding(struct net_bridge_port *p) { if (p->state == BR_STATE_BLOCKING) { printk(KERN_INFO "%s: port %i(%s) entering %s state\n", p->br->dev.name, p->port_no, p->dev->name, "listening"); p->state = BR_STATE_LISTENING; br_timer_set(&p->forward_delay_timer, jiffies); } } /* called under bridge lock */ static void br_make_blocking(struct net_bridge_port *p) { if (p->state != BR_STATE_DISABLED && p->state != BR_STATE_BLOCKING) { if (p->state == BR_STATE_FORWARDING || p->state == BR_STATE_LEARNING) br_topology_change_detection(p->br); printk(KERN_INFO "%s: port %i(%s) entering %s state\n", p->br->dev.name, p->port_no, p->dev->name, "blocking"); p->state = BR_STATE_BLOCKING; br_timer_clear(&p->forward_delay_timer); } }
都是設置p->state 相應狀態位就能夠了!!
3、選舉完成以後
實在不會取名字了,前面分析了br_received_config_bpdu中前面的判斷、刷新、選舉、設置端口狀態的過程,然而,若是橋認爲當前這個BPDU是一個「最優的」(即符合前面判斷四步中的某一步),所做的動做不止於此:
一、若是由於這個BPDU致使拓樸變化了,如本身之前是根橋,如今不是了,須要發送TCN包,進行通告;
二、須要把這個BPDU包繼續轉發下去(若是本身收到數據的端口是根端口的話,那麼就有可能有許多交換機(網橋)串在本身的指定端口下邊,總得把這個包能過指定端口再發給它們吧,不然交換機就不叫交換機了)
指下來繼續看代碼:
/*前面說的第1步*/ 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); } } /*前面說的第2步*/ 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); }
tcn包的發送,呆會單獨來分析,先來看br_config_bpdu_generation函數,這個函數也很簡單:遍歷橋的全部端口,若是是指定端口,就發送一個config 類型的BPDU包:
/* called under bridge lock */ void br_config_bpdu_generation(struct net_bridge *br) { struct net_bridge_port *p; p = br->port_list; while (p != NULL) { if (p->state != BR_STATE_DISABLED && br_is_designated_port(p)) br_transmit_config(p); p = p->next; } }
而後就是層層函數調用,組包,最終是調用dev_queue_xmit函數發送出去的。
若是收到這個BPDU包,不是「最優」的,而接收數據包的接口不是根端口,直接將轉發出去就能夠了,起箇中繼的做用:
else if (br_is_designated_port(p)) { br_reply(p); }
br_reply一樣調用了br_transmit_config函數