Linux下的Bridge也是一種虛擬設備,這多少和vlan有點相似,它依賴於一個或多個從設備。與VLAN不同的是,它不是虛擬出和從設備同一層次的鏡像設備,而是虛擬出一個高一層次的設備,並把從設備虛擬化為端口port,且同時處理各個從設備的數據收發及轉發,再加上netfilter框架的一些東西,使得它的實現相比vlan復雜得多。
1.Bridge的功能框圖
它是Linux下虛擬出來bridge設備,Linux下可用brctl命令創建br設備,如下
brctl addbr brname
然后添加port,並進行相應配置,就可以使用了
brctl addif brname eth0
brctl addif brname eth1
ifconfig brname IP up
ifconfig eth0 0.0.0.0 up
ifconfig eth1 0.0.0.0 up
可見br設備是建立在從設備之上的(這些從設備可以是實際設備,也可以是vlan設備等),並且可以為br准備一個IP(br設備的MAC地址是它所有從設備中最小的MAC地址),這樣該主機就可以通過這個br設備與網絡中的其它主機通信了(詳見發送功能框圖)。
另外它的從設備被虛擬化為端口port,它們的IP及MAC都不再可用,且它們被設置為接收任何包,最終由bridge設備來決定數據包的去向:接收到本機、轉發、丟棄(詳見接收功能框圖)。
發送功能 接收功能
簡單的數據結構框圖如下所示。
2.Bridge設備的創建
和Vlan一樣,bridge也被當成一個module加載進內核,它的module_init()函數和vlan差不多,進行一些namespace的注冊,特殊的是它還注冊了一個netfilter_ops,在內核全局的HOOK函數表中增加了7個函數,其中5個的pf=Bridge,另兩個的pf分別為INET、INET6,它們主要用於bridge中的netfilter操作(后面會細講)。
最后,也是我們這最關心的是,它注冊了一個ioctl函數br_ioctl_deviceless_stub(),該ioctl函數和vlan的一樣,都會作為sock_ioctl()的特殊情況被調用。映射到應用層,它應該是對某個socket插口進行ioctl操作,詳見brctl源碼。該ioctl函數中最主要的就是br_add_bridge(net,buf),用於創建bridge設備,如下圖所示:
該函數調用netdev_alloc(),申請net_device(),並分配私有空間net_bridge()結構,指明初始化函數為br_dev_setup(),最后register_netdev()把該設備組冊進內核,可見bridge設備和一般的設備差不多。
主要看其中br_dev_setup(),首先初始化設備的type、flags為bridge,然后最關鍵的是設置其dev->netdev_ops = br_netdev_ops,即內核為bridge設備准備好了一套通用的驅動函數,這個直接關系到bridge的工作方法,后面再細講。然后初始化私有空間net_bridge()結構,設置bridge的本地設備及從設備的list(當然這是還沒有從設備加進來),然后設置了橋的group_address,即上一節所說的特殊的MAC地址,最后還初始化了timer相關的。
這時bridge還不完整,還需添加port從設備,由命令brctl addif brname portdev完成,但要注意,雖然還是brctl命令,但此時的操作對象是已經存在的bridge設備,映射到內核中就是br_netdev_ops->ioctl()中的br_add_if()(它是br設備的ioctl操作,和之前那個sock_ioctl的分支不是一個層次上的)。至於怎么從應用層直接操作底層的net_device設備的,可以參見brctl源碼,以后再看吧,先看看這里的br_add_if(),如下圖:
首先判斷dev從設備必須不是loopback,不是bridge,不是其他bridge的port,且要是ethernet設備,才能繼續;然后根據br、dev選擇一個index號,並分配一個新的net_bridge_port結構,初始化之,並將它加入bridge的port_list中;最后br的一些物理參數,其MAC地址為所有從設備中MAC最小的(由上一節知,從設備被設置成全接收模式,其IP和MAC都沒有了),且其MTU也為所有從設備中最小的。
上面設置br的相關參數,下面還要設置從設備,首先使dev->master=br_dev(實際上就是構成上一節數據結構中的索引關系);然后設置dev->prive_flags加上IFF_BRIDGE_PORT,這樣它就不能再作為其他br的從設備了;最后也是最關鍵的,設置dev->rx_handler為br_handler_frame(),為數據接收作准備。
3.Bridge設備的發送流程
前面也講過了,Linux下的bridge設備,對下層而言是一個橋設備,進行數據的轉發(實際上對下也有接收能力,下一節講)。而對上層而言,它就像普通的ethernet設備一樣,有自己的IP和MAC地址,那么上層當然可以把它加入路由系統,並利用它發送數據啦,並且很容易想到,它的發射函數最終肯定是利用某個從設備的驅動去完成實際的發送的,這個和VLAN是相通的。具體看代碼:
上層根據目的IP地址,路由選擇了該br_dev設備發送,並且由ARP緩存中得到了對應的目的MAC,填寫在了skb中,然后啟動了發送流程dev_queue_xmit(skb)。因為此時的skb->dev為br_dev,無queue,直接去調用br設備的發送函數,該函數就是br_netdev_ops中定義的br_dev_xmit(skb,br_dev)。
該函數首先根據目的MAC地址,確定是廣播還是單播,這里僅討論單播時,根據DMAC在net_bridge的fdb_hash中找到相應的net_bridge_fdb_entry項,並索引到對應的端口net_bridge_port。最后利用該端口的從設備來發送數據,注意,這里是直接調用dev->ops->ndo_start_xmit(skb,dev)的,一放面這里的dev已經是從設備了,另一方面,這里沒有像VLAN中那樣重定位skb->dev,並重啟發送流程dev_queue_xmit(),是因為一個從設備只能作為一個bridge的port,沒有其它身份,不存在競爭問題。
4.Bridge設備的接收流程
和VLAN一樣,實際接收由硬件設備完成,最終通過netif_receive_skb(skb)函數提交給上層,而在該函數中會處理vlan、bridge這類特殊設備。與LVAN的僅是把skb設備重定位以實現對上層透明的要求不同,Bridge接受過程復雜得多,因此專門注冊了一個函數來處理,即前面提到的rx_handler(),它被注冊在port設備的net_device結構中(這算是port設備失去自身IP、MAC的一個補償吧J),如下圖所示。只有作為bridge的從設備才會注冊rx_handler(),並在這里執行,處理橋接,普通的設備不會執行到這里。
這里兩種典型的返回值是RX_CONSUMED、RX_PASS,后者表示處理了一下回來,繼續之前的流程,實際上就是對應的接收功能框圖中的第一種情況;前者表示該skb已不再屬於這個從設備了,而是被提交給了br設備,所以本次netif_receive_skb()就不用管啦,直接goto out,這里還要再分兩種情況,一是轉發的,這時br就真的充當了橋的角色,二是由br提交給上層的,這時br充當的是一個以太網設備,如前面所述。
要處理這么多情況,代碼需設計得很巧妙,這里的rx_handler被設置為br_handle_frame(**pskb),看具體代碼:
功能框架清楚了,代碼流程就清楚了,就不細看了,有幾個注意的地方:一是bridge的端口處於FORWARD或LEARNING狀態沒多大區別,只是FORWARD要多執行一個二層防火牆,所有用了一個中間沒有break的switch結構;二是要時刻記着,bridge本身除了有轉發端口外,自己也是一個設備,廣播(多播)時也要發一份給自己,且是以br_dev的身份遞交上層;三是所有的HOOK函數都會比較復雜,因為內核的netfilter框架建立在網絡層,而bridge在鏈路層就轉發了,相當於跳過了netfilter,所以在這些hook中都會去調用INET域的hook函數。
5.小結
通過對Bridge和vlan的學習,了解了網絡棧底層的工作方式,發送這個主動過程相對簡單,而接收過程則相對復雜,用到BH模型,NAPI等。
Vlan和bridge功能有所不同,但相似處很多,更重要的是:它們都對上層透明,所以不會牽扯到協議域的問題。