http://www-900.ibm.com/developerWorks/cn/linux/kernel/l-netbr/index.shtml
Linux網橋的實現分析與使用
本文分析了linux 2.4.x內核的網橋的實現方法,並且描述了如何使用2.4中的網橋。
網橋,類似於中繼器,連接局域網中兩個或者多個網段。它與中繼器的不同之處就在於它能夠解析它收發的資料,讀取目標位址資訊(MAC),並決定是否向所連接網路的其他網段轉發資料包。為了能夠決策向那個網段發送資料包,網橋學習接收到資料包的源MAC位址,在本地建立一個以MAC和埠為記錄項的資訊資料庫。
一、Linux內核網橋的實現分析
Linux 內核分別在2.2 和 2.4內核中實現了網橋。但是2.2 內核和 2.4內核的實現有很大的區別,2.4中的實現幾乎是全部重寫了所有的實現代碼。本文以2.4.0內核版本為例進行分析。
在分析具體的實現之前,先描述幾個概念,有助於對網橋的功能及實現有更深的理解。
1. 衝突域
一個衝突域由所有能夠看到同一個衝突或者被該衝突涉及到的設備組成。乙太網使用C S M A / C D(Carrier Sense Multiple Access with Collision Detection,帶有衝突監測的載波偵聽多址訪問)技術來保證同一時刻,只有一個節點能夠在衝突域內傳送資料。網橋或者交換機,構成了一個衝突域的邊界。缺省情況下,網橋中的每個埠實際上就是一個衝突域的結束點。
2. 廣播域
一個廣播域由所有能夠看到一個廣播資料包的設備組成。一個路由器,構成一個廣播域的邊界。網橋能夠延伸到的最大範圍就是一個廣播域。缺省的情況下,一個網橋或交換機的所有埠在同一個廣播域中。VLAN技術可以把交換機或者網橋的不同埠分割成不同的廣播域。一般情況下, 一個廣播域代表一個邏輯網段。
3. 網橋中的CAM表
網橋和交換機一樣,為了能夠實現對資料包的轉發,網橋保存著許多(MAC,埠)項。所有的這些項組成一個表,叫做CAM表。每個項有超時機制,如果一定時間內未接收到以這個MAC為源MAC位址的資料包,這個項就會被刪除。
圖1:一個交換網路的邏輯圖
在Linux內核網橋的實現中,一個邏輯網段用net_bridge結構體表示。一個邏輯網段需要保留的資訊有:
1. 本邏輯網段中所有的埠(port_list)
每個埠用net_bridge_port結構體來表示,從net_bridge_port結構體中可以看出,它主要有:
1. 邏輯網段中的下一個埠(next)
2. 本埠所屬的邏輯網段(br)
3. 本埠所指向的物理網卡(dev)
4. 本埠在網橋中的編號(port_no)
5. 用於生成樹管理的資訊
一個邏輯網段中可以具有很多個埠,所有的埠都掛在以port_list為鏈表頭的鏈表上。
2. 本網段中CAM表(hash[BR_HASH_SIZE])
CAM表中的每個項用net_bridge_fdb_entry結構體代表,每項中有:
1. 用於CAM表連接的鏈表指針(next_hash,pprev_hash)
2. 此項當前的引用計數(use_count)
3. MAC地址(addr)
4. 此項所對應的埠(dst)
5. 處理MAC超時(ageing_timer)
6. 是否是本機的MAC地址(is_local)
7. 是否是靜態MAC位址(is_static)
一個邏輯網段中的所有表項形成一個CAM表,他們之間的組織關係是一個HASH鏈表。HASH鏈的個數為BR_HASH_SIZE(256)。
3. 本邏輯網段用於和外部通信的虛擬網路設備(dev)
Linux網橋可以在網橋上為每個邏輯網段配置一個IP,用於和外部通信。實際上這個IP不是配置在一個特定的物理網卡上面, 而是建立一個虛擬的網卡,虛擬網卡可以附在每個同一邏輯網段的物理網卡上,讓這個網卡可以象所有的物理網卡一樣工作。從而使網橋可以和外部通信。
4. 本邏輯網段虛擬網卡的統計資料(statistics)
按照Linux網卡驅動的介面,一個網卡的統計資訊是由每個網卡的私有資料處理的。一般的寫法是用dev->priv來指向每個網卡的統計資料。網卡的get_stats方法就是用來讀取統計資料。
5. 用戶一個網段的生成樹(STP)資訊
以上對幾個結構體的描述和分析可以通過下圖來表示:
圖2:Linux網橋資料結構描述圖
描述了網橋的資料結構後,就可以開始資料包處理流程的分析。
網橋處理包遵循著以下幾條原則:
1. 在一個介面上接收到的包不會再在那個介面上發送這個資料包。
2. 每個接收到的資料包都要學習其源MAC位址。
3. 如果資料包是多播包或廣播包,則要在同一個網段中除了接收埠外的其他所有埠發送這個資料包,如果上層協定棧對多播包感興趣,則需要把資料包提交給上層協定棧。
4. 如果資料包的目的MAC位址不能在CAM表中找到,則要在同一個網段中除了接收埠外的其他所有埠發送這個資料包。
5. 如果能夠在CAM表中查詢到目的MAC位址,則在特定的埠上發送這個資料包,如果發送埠和接收埠是同一埠,則不發送。
在網路軟中斷處理函數net_rx_action中,嵌入了handle_bridge用於把資料包skb送入網橋模組處理。
////////////////////////////////////////////// #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
//////////////////////////////////////////////
br_handle_frame_hook是網橋處理接收到資料包的中入口,網橋初始化(br_init)的時候,把br_handle_frame_hook賦值為br_handle_frame。skb->dev->br_port用於判斷接收到這個資料包的介面是否是網橋中的一個埠,如果是,skb->dev->br_port不為NULL,那麼資料包應該由網橋處理。反之,資料包由上層協定棧處理。網橋中虛擬網卡對應的資料包就是在這個判斷點時不再進入網橋。(實際上虛擬網卡並不會自己主動接收資料包,而是在網橋處理中把資料包向本地上層協定棧提交,並且修改了skb->dev,使得資料包不會多次進入橋處理代碼)。
前面提到,網橋處理接收包的入口是br_handle_frame(net/bridge/br_input.c)函數。
br_handle_frame函數首先從skb中獲得這個包屬於的邏輯網段。然後調用__br_handle_frame進行轉發處理。 br_handle_frame函數裏有一個值得瞭解的地方,裏面有一個加讀鎖。因為在轉發中需要讀CAM表,所以必須加讀鎖,避免在這個過程中另外的內核控制路徑(如多處理機上另外一個CPU上的系統調用)修改CAM表。
對輸入包的轉發決策都是在__br_handle_frame函數中。這個函數的處理可以分為以下幾個部分:
a. 如果網橋的虛擬網卡處於混雜模式,那麼每個接收到的資料包都需要克隆一份送到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) {
passedup = 1;
br_pass_frame_up(br, skb2);
}
}
//////////////////////////////////////////////
b. 如果源MAC位址是多播或者是廣播位址,那麼這個包格式是錯誤的,簡單的丟棄。 //////////////////////////////////////////////
if (skb->mac.ethernet->h_source[0] & 1)
goto freeandout;
////////////////////////////////////////////// c. 如果是一個多播包,則需要向本機的上層協定棧傳送這個資料包(如果在之前沒有向上提交的話,即passedup為0。如果為1,則前面已經發送了,現在就不需要提交了,在後面中的處理都是一樣的)。
////////////////////////////////////////////// if (!passedup &&
(dest[0] & 1) &&
(br->dev.flags & IFF_ALLMULTI || br->dev.mc_list != NULL)) {
struct sk_buff *skb2; skb2 = skb_clone(skb, GFP_ATOMIC);
if (skb2) {
passedup = 1;
br_pass_frame_up(br, skb2);
}
}
////////////////////////////////////////////// d. 如果啟動了生成樹協議,一個生成樹包需要由生成樹協定處理模組單獨處理。如果不支援,則這個包的目的MAC肯定在CAM中查詢不到,所以是向所有的埠發送(除接收口)。這樣才不會影響整個網路的生成樹協定運行。
////////////////////////////////////////////// if (br->stp_enabled &&
!memcmp(dest, bridge_ula, 5) &&
!(dest[5] & 0xF0))
goto handle_special_frame; ////////////////////////////////////////////// e. 如果接收埠不是處於LEARNING或者FORWARDING,那麼就學習這個包的源MAC位址,或者更新CAM表中相應項的計時器。 ////////////////////////////////////////////// if (p->state == BR_STATE_LEARNING ||
p->state == BR_STATE_FORWARDING)
br_fdb_insert(br, p, skb->mac.ethernet->h_source, 0); ////////////////////////////////////////////// f. 如果是一個多播包或廣播包,則調用br_flood函數向每個口發送(除接收口)這個資料包。如果之前沒有提交上層協定,則需要克隆一個包提交上層協議。
////////////////////////////////////////////// if (dest[0] & 1) {
br_flood(br, skb, 1);
if (!passedup)
br_pass_frame_up(br, skb);
else
kfree_skb(skb);
return;
} //////////////////////////////////////////////
g. 用接收到資料包的目的MAC位址查詢CAM表。
//////////////////////////////////////////////
dst = br_fdb_get(br, dest);
//////////////////////////////////////////////
h. 查詢CAM表後,如果能夠找到表項,並且目的MAC是到本機的虛擬網卡的,那麼就需要把這個包提交給上層協議。網橋就是通過這個地方的處理和外部通信,實現遠端管理的目的。
////////////////////////////////////////////// if (dst != NULL && dst->is_local) {
if (!passedup)
br_pass_frame_up(br, skb);
else
kfree_skb(skb);
br_fdb_put(dst);
return;
} //////////////////////////////////////////////
i. 如果查詢CAM表有結果,並且目的MAC不是到本地的,那麼就通過調用br_forward發送到特定的埠。
////////////////////////////////////////////// if (dst != NULL) {
br_forward(dst->dst, skb);
br_fdb_put(dst);
return;
} ////////////////////////////////////////////// j. 如果在CAM表中查詢不到資料包的目的MAC位址,那麼就需要向別的每個埠發送這個資料包。調用br_flood來進行這個處理。
//////////////////////////////////////////////
br_flood(br, skb, 0);
return; //////////////////////////////////////////////
在br_forward和br_flood函數中都必須判斷源介面和目的介面是否是同一個,如果是同一埠,就不發送這個資料包。資料包的最後發送都是通過統一的發送介面dev_queue_xmit函數來完成的。
以下就是資料包的處理流程:
圖3:資料包處理流程圖
前面多次提到網橋的虛擬網卡,實際上在網橋中,這個網卡存在著一個net_device結構(在net_bridge裏),但是不存在著實際的物理設備,而是附在網橋中每個物理網卡上面。這個虛擬網卡的支援函數在(br_device.c)。因為是虛擬的網卡,所以沒有物理中斷產生,每個需要發送到這個設備的資料包都是靠判斷資料包的目的MAC位址來決定是否需要提交到本地上層協定棧(在__br_handle_frame判斷)。
如果資料包需要向上層協定提交,都調用br_pass_frame_up函數來處理。在這個函數中,首先把skb->dev設置成br->dev。然後再類比在中斷中處理資料包一樣,進行相應的處理, 然後調用netif_rx放入接收佇列。這裏有一個要十分注意的地方,這個資料包的skb->dev已經變成br->dev。所以在網路接收軟中斷處理函數net_rx_action中不會再次進入handle_bridge了。
////////////////////////////////////////////// static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
{
br->statistics.rx_packets ;
br->statistics.rx_bytes = skb->len; skb->dev = &br->dev;
skb->pkt_type = PACKET_HOST;
skb_pull(skb, skb->mac.raw - skb->data);
skb->protocol = eth_type_trans(skb, &br->dev);
netif_rx(skb);
} //////////////////////////////////////////////
二、配置內核 2.4 Linux 網橋
要配置網橋,首先需要網橋的配置工具bridge-utils。這個配置程式的源代碼可以在http://bridge.sourceforge.net/bridge-utils/ 下載。編譯成功之後,就可以生成網橋配置的主要工具brctl。
下面,我們將用brctl對以下網路拓撲配置網橋,使Linux能夠對資料包進行交換。
上圖中,有五台主機。其中中間那台主機裝有linux ,安裝了網橋模組,而且有四塊物理網卡,分別連接同一網段的其他主機。我們希望其成為一個網橋,為其他四台主機(IP分別為192.168.1.2 ,192.168.1.3,192.168.1.4,192.168.1.5) 之間轉發資料包。同時,為了方便管理,希望網橋能夠有一個IP(192.168.1.1),那樣管理員就可以在192.168.1.0/24網段內的主機上telnet到網橋,對其進行配置,實現遠端管理。
前一節中提到,網橋在同一個邏輯網段轉發資料包。針對上面的拓撲,這個邏輯網段就是192.168.1.0/24網段。我們為這個邏輯網段一個名稱,br_192。首先需要配置這樣一個邏輯網段。
////////////////////////////////////////////// # brctl addbr br_192 (建立一個邏輯網段,名稱為br_192) //////////////////////////////////////////////
實際上,我們可以把邏輯網段192.168.1.0/24看作使一個VLAN ,而br_192則是這個VLAN的名稱。
建立一個邏輯網段之後,我們還需要為這個網段分配特定的埠。在Linux中,一個埠實際上就是一個物理網卡。而每個物理網卡的名稱則分別為eth0,eth1,eth2,eth3。我們需要把每個網卡一一和br_192這個網段聯繫起來,作為br_192中的一個埠。
////////////////////////////////////////////// # brctl addif br_192 eth0 (讓eth0成為br_192的一個埠)
# brctl addif br_192 eth1 (讓eth1成為br_192的一個埠)
# brctl addif br_192 eth0 (讓eth2成為br_192的一個埠)
# brctl addif br_192 eth3 (讓eth3成為br_192的一個埠) //////////////////////////////////////////////
網橋的每個物理網卡作為一個埠,運行於混雜模式,而且是在鏈路層工作,所以就不需要IP了。 # ifconfig eth0 0.0.0.0
# ifconfig eth1 0.0.0.0
# ifconfig eth2 0.0.0.0
# ifconfig eth3 0.0.0.0
然後給br_192的虛擬網卡配置IP:192.168.1.1。那樣就能遠端管理網橋。 # ifconfig br_192 192.168.1.1
給br_192配置了IP之後,網橋就能夠工作了。192.168.1.0/24網段內的主機都可以telnet到網橋上對其進行配置。
以上配置的是一個邏輯網段,實際上Linux網橋也能配置成多個邏輯網段(相當於交換機中劃分多個VLAN)。具體的方法可以參考bridge-util中的HOWTO。
三、總結
本文分析了Linux網橋的實現,並且舉例說明如何配置網橋。 通過學習網橋的實現,就能夠瞭解網路中二層交換的原理。
網橋和交換機的功能非常相似,所以在分析網橋的時候,絕大多數情況下可以用交換機的處理方法來分析網橋的動作。
About the author
祝順民,網名:getmoon。目前從事防火牆開發,致力於網路的研究和開發,分析linux內核。經常出沒於www.linuxforum.net的內核板塊。希望于愛好者們共同探討。email:getmoon@163.com