一、Linux內核網橋的實現分析
Linux 內核分別在2.2 和 2.4內核中實現了網橋。但是2.2 內核和 2.4內核的實現有很大的區別,2.4中的實現幾乎是全部重寫了所有的實現代碼。本文以2.4.0內核版本為例進行分析。
在分析具體的實現之前,先描述幾個概念,有助於對網橋的功能及實現有更深的理解。
- 沖突域
一個沖突域由所有能夠看到同一個沖突或者被該沖突涉及到的設備組成。以太網使用C S M A / C D(Carrier Sense Multiple Access with Collision Detection,帶有沖突監測的載波偵聽多址訪問)技術來保證同一時刻,只有一個節點能夠在沖突域內傳送數據。網橋或者交換機,構成了一個沖突域的邊界。缺省情況下,網橋中的每個端口實際上就是一個沖突域的結束點。
- 廣播域
一個廣播域由所有能夠看到一個廣播數據包的設備組成。一個路由器,構成一個廣播域的邊界。網橋能夠延伸到的最大范圍就是一個廣播域。缺省的情況下,一個網橋或交換機的所有端口在同一個廣播域中。VLAN技術可以把交換機或者網橋的不同端口分割成不同的廣播域。一般情況下, 一個廣播域代表一個邏輯網段。
- 網橋中的CAM表
網橋和交換機一樣,為了能夠實現對數據包的轉發,網橋保存着許多(MAC,端口)項。所有的這些項組成一個表,叫做CAM表。每個項有超時機制,如果一定時間內未接收到以這個MAC為源MAC地址的數據包,這個項就會被刪除。
圖1:一個交換網絡的邏輯圖
在Linux內核網橋的實現中,一個邏輯網段用net_bridge結構體表示。一個邏輯網段需要保留的信息有:
- 本邏輯網段中所有的端口(port_list)
每個端口用net_bridge_port結構體來表示,從net_bridge_port結構體中可以看出,它主要有:
- 邏輯網段中的下一個端口(next)
- 本端口所屬的邏輯網段(br)
- 本端口所指向的物理網卡(dev)
- 本端口在網橋中的編號(port_no)
- 用於生成樹管理的信息
-
一個邏輯網段中可以具有很多個端口,所有的端口都掛在以port_list為鏈表頭的鏈表上。
本網段中CAM表(hash[BR_HASH_SIZE])CAM表中的每個項用net_bridge_fdb_entry結構體代表,每項中有:
- 用於CAM表連接的鏈表指針(next_hash,pprev_hash)
- 此項當前的引用計數(use_count)
- MAC地址(addr)
- 此項所對應的端口(dst)
- 處理MAC超時(ageing_timer)
- 是否是本機的MAC地址(is_local)
- 是否是靜態MAC地址(is_static)
-
一個邏輯網段中的所有表項形成一個CAM表,他們之間的組織關系是一個HASH鏈表。HASH鏈的個數為BR_HASH_SIZE(256)。
本邏輯網段用於和外部通信的虛擬網絡設備(dev)Linux網橋可以在網橋上為每個邏輯網段配置一個IP,用於和外部通信。實際上這個IP不是配置在一個特定的物理網卡上面, 而是建立一個虛擬的網卡,虛擬網卡可以附在每個同一邏輯網段的物理網卡上,讓這個網卡可以象所有的物理網卡一樣工作。從而使網橋可以和外部通信。
- 本邏輯網段虛擬網卡的統計數據(statistics)
按照Linux網卡驅動的接口,一個網卡的統計信息是由每個網卡的私有數據處理的。一般的寫法是用dev->priv來指向每個網卡的統計數據。網卡的get_stats方法就是用來讀取統計數據。
- 用戶一個網段的生成樹(STP)信息
以上對幾個結構體的描述和分析可以通過下圖來表示:
圖2:Linux網橋數據結構描述圖
描述了網橋的數據結構后,就可以開始數據包處理流程的分析。
網橋處理包遵循着以下幾條原則:
- 在一個接口上接收到的包不會再在那個接口上發送這個數據包。
- 每個接收到的數據包都要學習其源MAC地址。
- 如果數據包是多播包或廣播包,則要在同一個網段中除了接收端口外的其他所有端口發送這個數據包,如果上層協議棧對多播包感興趣,則需要把數據包提交給上層協議棧。
- 如果數據包的目的MAC地址不能在CAM表中找到,則要在同一個網段中除了接收端口外的其他所有端口發送這個數據包。
- 如果能夠在CAM表中查詢到目的MAC地址,則在特定的端口上發送這個數據包,如果發送端口和接收端口是同一端口,則不發送。
在網絡軟中斷處理函數net_rx_action中,嵌入了handle_bridge用於把數據包skb送入網橋模塊處理。
1
2
3
4
5
6
7
8
|
#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函數中。這個函數的處理可以分為以下幾個部分:
- 如果網橋的虛擬網卡處於混雜模式,那么每個接收到的數據包都需要克隆一份送到AF_PACKET協議處理體(網絡軟中斷函數net_rx_action中ptype_all鏈的處理)。
12345678if (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);
}
}
- 如果源MAC地址是多播或者是廣播地址,那么這個包格式是錯誤的,簡單的丟棄。
12if (skb->mac.ethernet->h_source[0] & 1)
goto freeandout;
- 如果是一個多播包,則需要向本機的上層協議棧傳送這個數據包(如果在之前沒有向上提交的話,即passedup為0。如果為1,則前面已經發送了,現在就不需要提交了,在后面中的處理都是一樣的)。
12345678910if (!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);
}
}
- 如果啟動了生成樹協議,一個生成樹包需要由生成樹協議處理模塊單獨處理。如果不支持,則這個包的目的MAC肯定在CAM中查詢不到,所以是向所有的端口發送(除接收口)。這樣才不會影響整個網絡的生成樹協議運行。
1234if (br->stp_enabled &&
!memcmp(dest, bridge_ula, 5) &&
!(dest[5] & 0xF0))
goto handle_special_frame;
- 如果接收端口不是處於LEARNING或者FORWARDING,那么就學習這個包的源MAC地址,或者更新CAM表中相應項的定時器。
123if (p->state == BR_STATE_LEARNING ||
p->state == BR_STATE_FORWARDING)
br_fdb_insert(br, p, skb->mac.ethernet->h_source, 0);
- 如果是一個多播包或廣播包,則調用br_flood函數向每個口發送(除接收口)這個數據包。如果之前沒有提交上層協議,則需要克隆一個包提交上層協議。
12345678if (dest[0] & 1) {
br_flood(br, skb, 1);
if (!passedup)
br_pass_frame_up(br, skb);
else
kfree_skb(skb);
return;
}
- 用接收到數據包的目的MAC地址查詢CAM表。
1dst = br_fdb_get(br, dest);
- 查詢CAM表后,如果能夠找到表項,並且目的MAC是到本機的虛擬網卡的,那么就需要把這個包提交給上層協議。網橋就是通過這個地方的處理和外部通信,實現遠程管理的目的。
12345678if (dst != NULL && dst->is_local) {
if (!passedup)
br_pass_frame_up(br, skb);
else
kfree_skb(skb);
br_fdb_put(dst);
return;
}
- 如果查詢CAM表有結果,並且目的MAC不是到本地的,那么就通過調用br_forward發送到特定的端口。
12345if (dst != NULL) {
br_forward(dst->dst, skb);
br_fdb_put(dst);
return;
}
- 如果在CAM表中查詢不到數據包的目的MAC地址,那么就需要向別的每個端口發送這個數據包。調用br_flood來進行這個處理。
12br_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了。
1
2
3
4
5
6
7
8
9
10
|
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。首先需要配置這樣一個邏輯網段。
1
|
# brctl addbr br_192 (建立一個邏輯網段,名稱為br_192)
|
實際上,我們可以把邏輯網段192.168.1.0/24看作使一個VLAN ,而br_192則是這個VLAN的名稱。
建立一個邏輯網段之后,我們還需要為這個網段分配特定的端口。在Linux中,一個端口實際上就是一個物理網卡。而每個物理網卡的名稱則分別為eth0,eth1,eth2,eth3。我們需要把每個網卡一一和br_192這個網段聯系起來,作為br_192中的一個端口。
1
2
3
4
|
# 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了。
1
2
3
4
|
# 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。那樣就能遠程管理網橋。
1
|
# ifconfig br_192 192.168.1.1
|
給br_192配置了IP之后,網橋就能夠工作了。192.168.1.0/24網段內的主機都可以telnet到網橋上對其進行配置。
以上配置的是一個邏輯網段,實際上Linux網橋也能配置成多個邏輯網段(相當於交換機中划分多個VLAN)。具體的方法可以參考bridge-util中的HOWTO。
三、總結
本文分析了Linux網橋的實現,並且舉例說明如何配置網橋。 通過學習網橋的實現,就能夠了解網絡中二層交換的原理。
網橋和交換機的功能非常相似,所以在分析網橋的時候,絕大多數情況下可以用交換機的處理方法來分析網橋的動作。
轉自:https://www.ibm.com/developerworks/cn/linux/kernel/l-netbr/index.html