網橋最主要有三個數據結構: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地址
}
|
這裏所說的網橋數據庫指的是CAM表,即struct net_bridge結構中的hash表,數據庫的維護對應的是對結構struct net_bridge_fdb_entry的操作;
衆所周知,網橋需要維護一個MAC地址-端口映射表,端口是指網橋自身提供的端口,而MAC地址是指與端口相連的另一端的MAC地址。當網橋收到一個報文時,先獲取它的源MAC,更新數據庫,然後讀取該報文的目標MAC地址,查找該數據庫,如果找到,根據找到條目的端口進行轉發;否則會把數據包向除入口端口以外的所有端口轉發。
數據庫使用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); }
|
當網橋收到一個數據包時,它會獲取該數據的源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(); }
|
在更新函數裏面已爲某一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; }
|
查找分兩種:一種是數據項更新時候的查找,另一種是轉發報文時候查找,兩者區別是轉發時查找需要判斷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); }
|
橋建立時設置一個定時器,循環檢測,如果發現有過期的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 (i = 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); }
|