Linux bridge模塊的一次重構(讓bridge歸bridge讓eth0歸eth0)

勾起了回憶,就想記錄點什麼。

再看劉經理的需求:

  • 被bonding的eth0可以獨立工作,eth0作爲類似帶內管理接口。

當然,現在看來,用macvlan實現這個非常容易:

ip link add link eth0 man0 type macvlan
brctl addbr br0
brctl addif br0 man0
brctl addif br0 eth1
ifconfig br0 1.1.1.1/8 up
ifconfig man0 4.4.4.4/8 up

OK,現在經理可以通過eth0來作爲管理口訪問管理地址4.4.4.4了,而eth0同時也和eth1一起作爲br0的port存在。

但是如果不配置macvlan則何如?

我當年被告知不能變更 錯誤的配置腳本 ,因此我必須去適配類似下面的邏輯:

brctl addbr br0
brctl addif br0 eth0
brctl addif br0 eth1
ifconfig br0 1.1.1.1/8 up
ifconfig eth0 4.4.4.4/8 up

很明顯,這個配置是錯誤的,eth0已經被br0給覆蓋掉了,它作爲br0的port不再對外可見,而且我也不能通過udev修改網卡的名字,總之就是系統的配置, 我不能動!

於是,我構建了超級複雜且不靈活的腳本化方案:

  • 用arptables修改arp請求和回覆。
  • 用netfilter修改數據包。
  • 結合iproute2用腳本聯動手工配置arp條目。

好像在這個blog裏還能找到這篇文章,但我懶得去翻了。

很顯然,經理是不會同意我這種自己一旦離職便無人可維護的trick的,直到現在我依然熱衷於這種完全不可維護的奇技淫巧,眼前就有標準化的方案,在我看來卻是無法展示技術水平的low點!這便是我的硬傷,我因此無法成爲經理。

在當時,我真的是不能用macvlan啊,所以我纔想到去炫耀arptables/netfilter/iproute2的,被否決了之後,我必須得想個稍微正經點的方案了。

但我可以動二進制代碼,我可以重新編一版bridge.ko內核模塊!

我覺得這個改動是有意義的,時隔這麼多年,我還是覺得它是有意義的。

怎麼說呢?

我不認爲Linux bridge因爲作爲一個“可以通過IP訪問本地接口”存在!

bridge就是個bridge啊,若干或物理的或虛擬的ethernet類型的網卡作爲port連接到它,那麼br0是什麼?它本身也是一個port嗎?這無疑增加了實現的複雜性。

在維護MAC/port映射表的時候,不得不區分is_local和!is_local,於是bridge的實現中,特意準備了下面這個函數:

static int br_pass_frame_up(struct sk_buff *skb);

用於區分這個幀是is_local的!

bridge的代碼因此看起來一點都不清爽,是的,爲此,到處都是if語句。

我當時在夏日很冷的機房忍受這巨大溫差帶來的刺到骨頭裏的難受之所以沒有抱怨,是因爲我受夠了Linux bridge的實現!我決定就在這個令人難受的環境裏修改掉它,帶來一點舒服的感覺。嗯,現場編程!

我希望:

  • 當有幀訪問該bridge的port另一端的MAC時,bridge負責forward它;
  • 當有幀訪問該bridge的port本身的MAC時,像正常訪問該網卡一樣,bridge並不處理它。
  • 我不引入外部任何類似macvlan之類的虛擬網卡的東西。

好吧,做法其實很簡單:

  • 讓所有br_pass_frame_up的調用直接返回0。
  • 在br_handle_frame中特殊bypass掉訪問本port的流量。

br_handle_frame的取消很簡單,return 0即可,br_handle_frame的改動也不麻煩:

rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
{
	...
    if (unlikely(ether_addr_equal(skb->dev->dev_addr, dest))) {
        return RX_HANDLER_PASS;
    }
    p = br_port_get_rcu(skb->dev);
	...
    case BR_STATE_LEARNING:
        if (ether_addr_equal(p->br->dev->dev_addr, dest))
            skb->pkt_type = PACKET_HOST;

        if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest))
        		// 增加引用,準備二次處理。
        		// 或者內部特殊處理br_pass_frame_up的NF_HOOK,不再stolen
                atomic_inc(&skb->users); 
        NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, NULL, skb,
            skb->dev, NULL,
            br_handle_frame_finish);
        // 除了轉發之外,讓eth網卡自己也處理一份ARP,
        if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest))
                return RX_HANDLER_PASS; // 二次處理,直接返回netif_receive_skb
        break;
    default:
}

OK,就是如此:

brctl addbr br0
brctl addif br0 eth0
brctl addif br0 eth1
ifconfig br0 1.1.1.1/8 up
ifconfig eth0 4.4.4.4/8 up

4.4.4.4可以通了,但是br0的1.1.1.1/8地址不再可達,爲此,如果你非要它通,則需要將它配置在eth0即可:

brctl addbr br0
brctl addif br0 eth0
brctl addif br0 eth1
ifconfig br0 up
ip add add dev eth0 1.1.1.1/8 
ip add add dev eth0 4.4.4.4/8 

讓bridge的歸bridge,讓eth0歸eth0,這顯然看起來更加清爽,於是我就再也不會爲 “Linux bridge竟然可以自己把自己接入bridge本身” 而賽里布瑞特了。這很完美得實現了所謂的 帶內管理 ,即讓處理數據面的網卡同時跑管理流量和控制流量,對於接入層的中小型設備,中小型設備,中小型設備,中小型設備,即插即管,非常方便,而且ACL也可以灌輸進去,實現帶內安全策略。

當然了,這些可能一點意義都沒有,沒有就沒有吧,本來就沒什麼意義。


浙江溫州皮鞋溼,下雨進水不會胖!

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