Linux網橋源碼的實現(獨孤九賊)


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函數

相關文章
相關標籤/搜索