LWN:利用硬體offload來加速netfilter——第二部分

語言: CN / TW / HK

關注了就能看到更多這麼棒的文章哦~

Accelerating netfilter with hardware offload, part 2

January 31, 2020

This article was contributed by Marta Rybczyńska

原文來自:https://lwn.net/Articles/810663/

隨著網路介面速度越來越快,用來處理每個packet的CPU時間就越來越少。好在,許多工作都可以offload給硬體去做,包括packet filtering。不過,Linux kernel還需要許多工作才能利用上這個功能。上一篇文章裡綜述了基於硬體的packet filtering的工作原理,kernel裡已經有了相關的支援。本文則總結了一下packet filtering在netfilter subsystem裡面是如何offload出去的,以及系統管理員該如何利用這個功能。

offload功能是Pablo Neira Ayuso的patch加入的,首先出現在5.3 release裡面,後續也在持續更新。這組patch set的主要目標是允許用一組典型的配置來把netfilter rule的一部分給offload出去,對這些offload rule處理了的packet就不會再執行kernel裡 通用 的packet處理程式碼。目前還不能把所有rule都offload出去,因為這會需要底層硬體支援,netfilter程式碼也需要改動。這個應用場景以及細節在Neira的 2019 Linux Plumbers Conference的pdf裡面有介紹(https://linuxplumbersconf.org/event/4/contributions/463/attachments/286/485/2019-plumbers-lisboa.pdf  )

Background work

這組patch set對程式碼進行了重構,從而讓netflter offload機制可以重用那些此前跟traffic-control (tc) subsystem緊密繫結的一些功能。重構工作利用了現有driver裡的callback。有些模組此前只能在tc subsystem裡面用,現在變得更通用了。

第一個新增的subsystem是“flow block"架構,由2017年引入,用來支援filtering rule的共享,以及優化ternary content-addressable memory (TCAM) 條目的使用。它可以支援兩個或者多個網路介面上共享一組rule規則,這樣就能減少rule offloading所佔用的硬體資源。因為包含有多條物理介面的網絡卡通常會在這些介面上公用TCAM條目。因此在交換機(switch)上,這個優化可以讓系統管理員定義一組公共的filtering rule,然後在多個網路介面上應用。對這組公共rule的修改會在所有相關的網路介面上生效。netfilter offload patch把原來tc subsystem專用的flow block擴充套件了應用範圍,這樣它就可以用在所有需要對packet filtering進行offload的子系統裡面。

flow block的核心功能是一系列driver callback函式,會在硬體裡面寫好的rule發生改變的時候被呼叫。通常每個裝置(常見的網絡卡都是這樣)只有一個條目。對switch(交換機)來說,它裡面的所有網路介面都公用一個callback。如果一個平臺上兩個網路介面共用相同的一組規則,flow-block的列表裡面會包含2個callback(每個網路介面都有一個)。flow-block架構並不限制filtering rule的數量。

這個patch set裡面另一個重要改動,是修改了網絡卡驅動的一個callback函式。callback函式位於struct net_device_ops之中。netfilter offload patch set重複利用了ndo_setup_tc() callback,本來是用來為tc subsystem來配置scheduler, classifier和action的,原型如下:

int (*ndo_setup_tc)(struct net_device *dev, enum tc_setup_type type,
                        void *type_data);

它的一個引數是網絡卡裝置dev,還有需要應用的配置資訊(由enum tc_setup_type定義),以及一個數據值。這個enum定義了不同的action型別。netfilter並沒有定義自己特有的型別,因此它會利用flower classifier已經定義好的TC_SETUP_CLSFLOWER。後面這裡可能會變,因為會有驅動開始同時支援tc和netfilter兩種offloading。

最後,在2019年2月引入了flow-rule API(第6版的patch set裡面有個很長的引言,建議一讀)。它實現了flow-filtering rule的一種中間表達方式(intermediate representation,有些人可能更熟悉IR),可以把驅動相關的實現部分從子系統對它的呼叫給區分開來。具體來說,它實現了一套程式碼路徑,供驅動程式呼叫,來支援ethtool或者flower classifier所配置的access-control-list offload。

在這個flow-rule API裡面,每個flow_rule物件都代表了一個過濾條件。它包含這個rule的匹配條件(struct flow_match),以及相應的action (struct flow_action)。在netfilter程式碼裡面,每個flow_rule都代表了一條被offload給硬體的rule,它會被儲存在flow-block list裡面。當netfilter把一條rule offload給硬體的時候,會對flow block裡面的callback list進行遍歷,逐個呼叫每條callback函式來傳入rule,這樣就可以讓驅動程式來處理了。

Driver API changes

因為tc特有的這些程式碼被改得更加通用了,所以有一些type和定義都被重新命名或者重新組織過了。新加了一個type,flow_block_command,定義了驅動的flow-block setup函式裡要用的命令。它主要包括兩個定義,分別是TC_BLOCK_BIND和TC_BLOCK_UNBIND,分別被改名為FLOW_BLOCK_BIND和FLOW_BLOCK_UNBIND。這樣kernel就可以把flow block給繫結到某個網路介面上,或者解綁。同樣的,flow_block_binder_type定義了offload的型別(輸入的話就是ingress,輸出的話就是egress),它的成員從TCF_BLOCK_BINDER_TYPE_*改名成FLOW_BLOCK_BINDER_TYPE_*

現有的驅動程式都簡單的配置好tc offloading功能了。所以Neira就增加了一個helper function供所有人來用:

    int flow_block_cb_setup_simple(struct flow_block_offload *f,
        			   struct list_head *driver_block_list,
        			   flow_setup_cb_t *cb, void *cb_ident,
				   void *cb_priv, bool ingress_only);

這裡f是offload context,driver_block_list是對於這個特定驅動程式所用的flow block的列表,cb是驅動程式的ndo_setup_tc() callback函式,cb_ident則是context的編號(identification),cb_priv則是要傳遞給cb的context(大多數情況下cb_ident和cb_priv是相同的),ingress_only如果是true則代表要配置的offload是隻支援ingress型別(表示接收),這也是直到5.4 kernel為止的所有驅動的預設配置,在5.5裡面,cxgb4 driver開始支援雙向了。flow_block_cb_setup_simple()會為每個network device來註冊一個callback,這樣大多數驅動程式就夠用了。

每個驅動都應該要儲存一組flow block,也就是flow_block_cb_setup_simple()的driver_block_list引數。如果驅動需要多個callback的話,例如一個用在ingress,另一個用在egress rules,那麼 這個列表是必須的。

每個驅動實現的callback函式,型別是flow_setup_cb_t,定義如下:

    typedef int flow_setup_cb_t(enum tc_setup_type type, void *type_data,
        			void *cb_priv);

驅動程式裡面相應的實現函式會利用提供進來的配置資訊來設定好硬體的過濾功能。引數type定義了要用哪個classifier,type_data則是這個classifer所需的資料(通常是一個指向flow_rule結構的指標),cb_priv是callback函式的私有資料。

如果flow_block_cb_setup_simple()的功能無法滿足這個驅動程式的要求(比如這個驅動是一個交換機上所用),那麼就需要用到那些直接分配flow block的API了。分配和釋放flow block是用這兩個helper函式:flow_block_cb_alloc()和flow_block_cb_free(),原型如下:

    struct flow_block_cb *flow_block_cb_alloc(flow_setup_cb_t *cb,
                            void *cb_ident, void *cb_priv,
                            void (*release)(void *cb_priv));
    void flow_block_cb_free(struct flow_block_cb *block_cb);

這些回撥函式都是由驅動程式定義的,然後通過flow-block的操作方式來傳遞給netfilter。netfilter會維護一個跟每個給定的rule關聯起來的回撥函式列表。

每個flow block都包含一個列表,其中是所有的driver offload回撥函式。驅動可以把他們自己加到這個列表裡或者從中刪除掉,需要使用flow_block_cb_add()和flow_block_cb_remove()函式:

    void flow_block_cb_add(struct flow_block_cb *block_cb,
                           struct flow_block_offload *offload);
    void flow_block_cb_remove(struct flow_block_cb *block_cb,
                              struct flow_block_offload *offload);

驅動程式可以用flow_block_cb_lookup()函式來查詢指定的回撥函式。

    struct flow_block_cb *flow_block_cb_lookup(struct flow_block *block,
        		 flow_setup_cb_t *cb, void *cb_ident);

這個函式會在block context裡面的list中搜索flow-block回撥函式。如果cb callback和cb_ident值對應上了,就把相關的flow-block callback structure返回出來。這個函式主要是供交換機驅動來檢查一個callback函式是否早就已經註冊上來了(再強調一遍,交換機的一個callback會應用在所有網路介面上)。在配置第一個網路介面的時候,如果看到flow_block_cb_lookup()返回NULL,那麼就分配資源並註冊這個callback函式。相應的,其他的網路介面在呼叫這個函式的時候則會返回一個非NULL的值,也就可以直接使用現有的這個callback函數了,只不過需要增加一下引用計數。在unregister一個callback的時候,如果其他的使用者還在使用這個callback戶數,flow_block_cb_lookup()也會返回非NULL值,這樣驅動程式就僅僅減少一下引用計數就好。

操作flow-block引用計數的函式是flow_block_cb_incref()和flow_block_cb_decref()。定義如下:

    void flow_block_cb_incref(struct flow_block_cb *block_cb);
    unsigned int flow_block_cb_decref(struct flow_block_cb *block_cb);

flow_block_cb_decref返回的值是做完這個操作之後的引用計數的值。

還有一個函式flow_block_cb_priv(),允許驅動訪問它的private data。原型如下,非常簡單:

    void *flow_block_cb_priv(struct flow_block_cb *block_cb);

最後,驅動程式還可以使用flow_block_is_busy()來檢查這個callback函式是否已經在使用中了(就是加到列表裡面並且是active狀態)。這個函式的原型如下:

    bool flow_block_cb_is_busy(flow_setup_cb_t *cb, void *cb_ident,
                               struct list_head *driver_block_list);

此函式會如果看到driver_block_list裡面存在一個條目,cb和cb_ident都符合要求,那麼就返回true。這個函式的主要用途是在配置offload的時候,避免同時配置tc和netfilter callback。針對那些同時支援tc和ntetfilter的硬體來說,只要實現好了這兩種功能,它們的驅動程式裡面移除對這個函式的呼叫。

traffic classifier內部的實現已經被改為使用flow-block API裡面存放的filtering,這是通過利用新加的tcf_block_setup()函式來實現的。

Callback list

驅動程式會配置好flow-block物件(flow_block_cb),然後把驅動程式的callback函式加入它們的列表中。每個驅動程式都會把自己的列表傳遞給網路處理核心程式碼去,完成註冊(包括tc和netfilter),並且呼叫驅動程式的callback來真正完成硬體的配置。這個回撥函式會利用引數中傳遞進來的classifier特有的資料,包括操作型別(例如增加或者刪除一個offload)。

Edward Cree問過,為什麼每個驅動程式只有一個列表,而不是每個裝置一個。他說:“Pablo,能否解釋一下(commit message裡面沒有提到)為什麼需要這些per-driver list,有哪些資料、狀態是屬於module範圍的(而不是例如netdevice範圍的)?”

Neira解釋說,這些驅動程式目前只支援一個flow block,後續計劃擴充套件為針對每個subsystem(例如ethtook, tc等等)使用一個flow block。這裡有兩個原因:首先,目前的驅動程式只能支援一個subsystem,等這個限制去掉之後,還有另一個限制,就是為了支援共享功能,所以所有subsystem都必須使用相同的配置。這就意味著,例如eth0和eth1就需要用相同的配置用於tc,同時也還需要一個共同的配置用在netfilter上。Neira覺得今後應該不會需要做成這樣。

The netfilter offload itself

最後一個patch裡面引入了netfilter裡面進行硬體offload的功能。目前只有基本支援,僅僅處理了ingress chain。並且只有在flow的5個元素(協議,源、目標地址,源、目標埠號)完全匹配上的時候才會應用rule。

patch中舉了下面的例子來說明:

    table netdev filter {
        chain ingress {
            type filter hook ingress device eth0 priority 0; flags offload;
            ip daddr 192.168.0.10 tcp dport 22 drop
        }
    }

這條規則會把發往192.168.0.10埠22(一般是ssh)的所有TCP包都丟棄。比起那些沒有被offload的rule來說,主要的區別是增加了flags offload引數。

因為對offload進行控制的許可權釋放給了administrator,所以有可能出現配置錯誤的情況。比如說,如果對一條不可以做offload的規則加上了offload flag,那麼就會返回錯誤程式碼EOPNOTSUPP。假如驅動程式無法處理這條命令,例如TCAM已經滿了,那麼就需要返回驅動程式自己決定的錯誤程式碼。

這個介面給系統管理員賦予了更多能力,也同時需要他們負起責任來,仔細挑選那些能獲得儘量大好處的規則來offload出去。所以,需要非常瞭解系統配置以及系統上主要的網路資料都是什麼,這樣才能更好地利用這個新功能。在撰寫這篇文章的時候,還沒有benchmark資料,也沒有一些通用的建議文件。此外我們還需要慢慢觀察這個offload功能有那些侷限性,例如是否容易能調查驅動程式callback裡傳入的使用者配置錯誤。

Summary

netfilter classification offloading功能可以用來開啟硬體offload功能,這樣應該能夠在某些應用場景下得到非常顯著的效能提升。這個工作引出了對現有程式碼的重構工作,為其他offloading使用者趟平了道路。不過,驅動程式還需要修改之後才能完全利用這個優勢,並且這組API還是比較複雜的,有多種callback函式。系統管理員算是得到了一個強大的工具,不過需要仔細小心地使用好它。。今後,這部分肯定還會有許多工作需要做。

[The author would like to thank Pablo Neira Ayuso for helpful comments]

全文完

LWN文章遵循CC BY-SA 4.0許可協議。

歡迎分享、轉載及基於現有協議再創作~

長按下面二維碼關注,關注LWN深度文章以及開源社群的各種新近言論~