Linux-網橋原理分析(三)

5網橋數據結構

網橋最主要有三個數據結構:struct net_bridge,struct net_bridge_port,struct net_bridge_fdb_entry,他們之間的關係如下圖:

展開來如下圖:

說明:

1.       其中最左邊的net_device是一個代表網橋的虛擬設備結構,它關聯了一個net_bridge結構,這是網橋設備所特有的數據結構。

2.       net_bridge結構中,port_list成員下掛一個鏈表,鏈表中的每一個節點(net_bridge_port結構)關聯到一個真實的網口設 備的net_device。網口設備也通過其br_port指針做反向的關聯(那麼顯然,一個網口最多隻能同時被綁定到一個網橋)。

3.       net_bridge結構中還維護了一個hash表,是用來處理地址學習的。當網橋準備轉發一個報文時,以報文的目的Mac地址爲key,如果可以在 hash表中索引到一個net_bridge_fdb_entry結構,通過這個結構能找到一個網口設備的net_device,於是報文就應該從這個網 口轉發出去;否則,報文將從所有網口轉發。

 

各個結構體具體內容如下:

struct net_bridge

 

struct net_bridge
{
    spinlock_t            lock; 
//讀寫鎖

    
//網橋所有端口的鏈表,其中每個元素都是一個net_bridge_port結構

    struct list_head        port_list; 
    struct net_device        *dev; 
//網橋對應的設備

    struct net_device_stats        statistics; 
//網橋對應的虛擬網卡的統計數據

    spinlock_t            hash_lock; 
//hash表的鎖

    
/*--CAM: 保存forwarding database的一個hash鏈表(這個也就是地址學習的東東,
    所以通過hash能 快速定位),這裏每個元素都是一個net_bridge_fsb_entry結構--*/

    struct hlist_head        hash[BR_HASH_SIZE]; 
    struct list_head        age_list;

    /* STP */ 
//與stp 協議對應的數據

    bridge_id            designated_root;
    bridge_id            bridge_id;
    u32                root_path_cost;
    unsigned long            max_age;
    unsigned long            hello_time;
    unsigned long            forward_delay;
    unsigned long            bridge_max_age;
    unsigned long            ageing_time;
    unsigned long            bridge_hello_time;
    unsigned long            bridge_forward_delay;

    u16                root_port;
    unsigned char            stp_enabled;
    unsigned char            topology_change;
    unsigned char            topology_change_detected;
    
//stp要用的一些定時器列表。

    struct timer_list        hello_timer;
    struct timer_list        tcn_timer;
    struct timer_list        topology_change_timer;
    struct timer_list        gc_timer;
    struct kobject            ifobj;
}


2.  struct net_bridge_port

 

struct net_bridge_port
{
    struct net_bridge        *br; 
//從屬於的網橋設備

    struct net_device        *dev;
//表示鏈接到這個端口的物理設備

    struct list_head        list;

    /* STP */ 
//stp相關的一些參數。

    u8                priority;
    u8                state;
    u16                port_no; 
//本端口在網橋中的編號

    unsigned char            topology_change_ack;
    unsigned char            config_pending;
    port_id                port_id;
    port_id                designated_port;
    bridge_id            designated_root;
    bridge_id            designated_bridge;
    u32                path_cost;
    u32                designated_cost;
    
//端口定時器,也就是stp控制超時的一些定時器列表

    struct timer_list        forward_delay_timer;
    struct timer_list        hold_timer;
    struct timer_list        message_age_timer;
    struct kobject            kobj;
    struct rcu_head            rcu;
}

3. struct net_bridge_fdb_entry

struct net_bridge_fdb_entry
{
    struct hlist_node        hlist;
    
//橋的端口(最主要的兩個域就是這個域和下面的mac地址域) 

    struct net_bridge_port *dst;
    
    struct rcu_head            rcu; 
//當使用RCU策略,纔用到

    
    atomic_t                use_count; 
//引用計數

    unsigned long            ageing_timer; 
//MAC超時時間

    mac_addr                addr; 
//mac地址。    

    
    unsigned char            is_local; 
//是否爲本機的MAC地址

    unsigned char            is_static; 
//是否爲靜態MAC地址

}

6  網橋數據庫的維護

這裏所說的網橋數據庫指的是CAM表,即struct net_bridge結構中的hash表,數據庫的維護對應的是對結構struct net_bridge_fdb_entry的操作;

衆所周知,網橋需要維護一個MAC地址-端口映射表,端口是指網橋自身提供的端口,而MAC地址是指與端口相連的另一端的MAC地址。當網橋收到一個報文時,先獲取它的源MAC,更新數據庫,然後讀取該報文的目標MAC地址,查找該數據庫,如果找到,根據找到條目的端口進行轉發;否則會把數據包向除入口端口以外的所有端口轉發。

6.1  數據庫的創建和銷燬

數據庫使用kmem_cache_create函數進行創建,使用kmem_cache_desctory進行銷燬。路徑:[/net/bridge/br_fdb.c]:

void __init br_fdb_init(void)
{
    br_fdb_cache = kmem_cache_create("bridge_fdb_cache",
                     sizeof(struct net_bridge_fdb_entry),
                     0,
                     SLAB_HWCACHE_ALIGN, NULL, NULL);
}

6.2  數據庫更新

當網橋收到一個數據包時,它會獲取該數據的源MAC地址,然後對數據庫進行更新。如果該MAC地址不在數庫中,則創新一個數據項。如果存在,更新它的年齡。數據庫使用hash表的結構方式,便於高效查詢。下面是hash功能代碼的分析:

路徑:[/net/bridge/br_fdb.c] 


void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
         const unsigned char *addr)
{
    
/*--br_mac_hash函數是hash表中的hash函數,具體算法過程可參閱該函數代碼;
        br->hash就是數據庫的hash表,每個hash值對應一個鏈表;
        數據庫的每項爲net_bridge_fdb_entry結構--*/

    struct hlist_head *head = &br->hash[br_mac_hash(addr)];
    struct net_bridge_fdb_entry *fdb;

    /* some users want to always flood. */
    if (hold_time(br) == 0)
        return;

    rcu_read_lock();
    fdb = fdb_find(head, addr);
    /*--如果找到對應的fdb,更新fdb->dst,fdb->ageing_timer--*/
    if (likely(fdb)) {
        /* attempt to update an entry for a local interface */
        if (unlikely(fdb->is_local)) {
            if (net_ratelimit()) 
                printk(KERN_WARNING "%s: received packet with "
                 " own address as source address\n",
                 source->dev->name);
        } else {
            /* fastpath: update of existing entry */
            fdb->dst = source;
            fdb->ageing_timer = jiffies;
        }
    } else { /*--沒有找到,則新建一個fdb--*/
        spin_lock_bh(&br->hash_lock);
        if (!fdb_find(head, addr))
            fdb_create(head, source, addr, 0);
        
/* else we lose race and someone else inserts
         * it first, don't bother updating
         */

        spin_unlock_bh(&br->hash_lock);
    }
    rcu_read_unlock();
}


6.3 創建數據項

在更新函數裏面已爲某一MAC找到了它所屬於的Hash鏈表,因此,創建函數只需要在該鏈上添加一個數據項即可。

 

static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
                     struct net_bridge_port *source,
                     const unsigned char *addr, 
                     int is_local)
{
    struct net_bridge_fdb_entry *fdb;

    /*--申請數據區--*/
    fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
    if (fdb) {
        memcpy(fdb->addr.addr, addr, ETH_ALEN);
        atomic_set(&fdb->use_count, 1);
        hlist_add_head_rcu(&fdb->hlist, head); /*--添加到鏈表--*/

        fdb->dst = source;
        fdb->is_local = is_local;
        fdb->is_static = is_local;
        fdb->ageing_timer = jiffies; 
//MAC年齡

    }
    return fdb;
}


6.4 查找數據項

查找分兩種:一種是數據項更新時候的查找,另一種是轉發報文時候查找,兩者區別是轉發時查找需要判斷MAC地址是否過期,即我們常說的MAC老化;更新時則不用判斷;

網橋更新一MAC地址時,不管該地址是否已經過期了,只需遍歷該MAC地址對應的Hash鏈表,然後更新年齡,此時它肯定不過期了。

網橋要轉發數據時,除了要找到該目標MAC的出口端口外,還要判斷該記錄是否過期了。

更新時查找:

 

static inline struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head,
                         const unsigned char *addr)
{
    struct hlist_node *h;
    struct net_bridge_fdb_entry *fdb;

    /*--遍歷鏈表比較地址--*/
    hlist_for_each_entry_rcu(fdb, h, head, hlist) {
        if (!compare_ether_addr(fdb->addr.addr, addr))
            return fdb;
    }
    return NULL;
}


轉發時查找:

 

struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge *br,
                     const unsigned char *addr)
{
    struct hlist_node *h;
    struct net_bridge_fdb_entry *fdb;

    /*--遍歷鏈表比較地址--*/
    hlist_for_each_entry_rcu(fdb, h, &br->hash[br_mac_hash(addr)], hlist) {
        if (!compare_ether_addr(fdb->addr.addr, addr)) {
            /*--判斷是否過期--*/
            if (unlikely(has_expired(br, fdb)))
                break;
            return fdb;
        }
    }

    return NULL;
}


比較一下,轉發時多了一個函數處理:has_expired, Has_expired函數來決定該數據項是否是過期的,代碼如下: 


/*--數據項的可保留時間根據拓撲結構是否改變來決定,
    改變則爲forward_delay,否則爲ageing_time--*/

/* if topology_changing then use forward_delay (default 15 sec)
 * otherwise keep longer (default 5 minutes)
 */

static __inline__ unsigned long hold_time(const struct net_bridge *br)
{
    return br->topology_change ? br->forward_delay : br->ageing_time;
}

static __inline__ int has_expired(const struct net_bridge *br,
                 const struct net_bridge_fdb_entry *fdb)
{
    
/*--1. 如果該數據項是靜態的,即不是學習過來的,它永遠不會過期。
         因爲它就是網橋自己端口的地址
        2. 如果最近更新時間加上可保留時間大於當前時間,即老化時間還在以後,
         表示尚未過期,time_before_eq返回真,否則返回假
    --*/

    return !fdb->is_static 
        && time_before_eq(fdb->ageing_timer + hold_time(br), jiffies);
}


6.5   MAC地址過期清理

橋建立時設置一個定時器,循環檢測,如果發現有過期的MAC,則清除對應的數據項,MAC地址過期清除由函數br_fdb_cleanup實現:

 

/*--定時器循環檢查MAC地址是否過期
    定時器在橋初始化中定義開啓--*/

void br_fdb_cleanup(unsigned long _data)
{
    struct net_bridge *br = (struct net_bridge *)_data;
    unsigned long delay = hold_time(br);/*--獲取MAC地址可保留時間--*/
    int i;

    spin_lock_bh(&br->hash_lock);
    for (= 0; i < BR_HASH_SIZE; i++) {
        struct net_bridge_fdb_entry *f;
        struct hlist_node *h, *n;

        /*--如果該地址不是靜態的,並且已經過期,則從數據庫中清除該MAC映射--*/
        hlist_for_each_entry_safe(f, h, n, &br->hash[i], hlist) {
            if (!f->is_static && 
             time_before_eq(f->ageing_timer + delay, jiffies)) 
                fdb_delete(f);
        }
    }
    spin_unlock_bh(&br->hash_lock);
    
    /*--更新檢查定時器--*/
    mod_timer(&br->gc_timer, jiffies + HZ/10);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章