接近崩潰的邊緣,今天這篇文章構思地點在醫院,小小又生病了,寧可吊瓶不吃藥,帶了筆記本卻無法上網,我什麼都不能幹,想瞭解一些東西,只能用3G,不敢開熱點,因為沒人給我報銷流量,本周末我只有一天時間,因為下雨,我還有一個晚上。瞭解了PF_RING之後,我迫切希望做一個實驗,於是跑回家驗證後再回來。
事情的起因是這樣的。
一共有4個問題
1.關於一個網路加速卡
前些日子,接觸到一款網路加速卡,插在PCIe插槽,卡上運行獨立的Linux系統,通過PCIe和主機通訊。這塊卡號稱其特點在於處理抓包的效能非常好,抓包?是的,這就是說它適合做DPI或者透明防火牆了。廠商的技術人員親自為我們示範了一個防火牆的功能,即不能訪問優庫網站,其它的都可以。讓我覺得好玩的是,他們的加速卡上有兩個萬兆光口,一個接一台可以上網的PC,另一個接出口交換器,PC上除了不能訪問優庫,其它的都可以訪問,而兩個萬兆光口既沒有被bridge在一起,又沒有IP,這塊卡是怎麼做到一個口進另一個口出的,不是橋接器,沒有IP,這簡直就不是一個網路裝置,那它是什嗎?
2.最近公司的一次測評
最近,對公司的裝置進行了一次效能測評,結果達標,但是個人覺得還有提升餘地,只是測評事件發生在京城,本人沒有親曆,只能遺憾,加之測試方並非那種酷愛技術之人,搞的我們的人總是在配合他,結果可想而知,見好就收,他們是絕不會讓我們將他們那悍猛的效能測試儀器作為玩具折騰的。
3.公司的那個NAS上網行為管理
如果我在公司瀏覽了一個不太雅的網站,我當然不想讓NAS紀錄,否則那幫管理員一定可以隨時看到,然後背後嘲笑我,實際上管理員都有這種癖好,畢竟在當代,對於隱私,網管的權力是巨大的。後來我聽說這玩意兒是串接在主鏈路的,作為一個比較懂網路且比較好奇的研發人員,一定想知道這玩意兒是怎麼做到二層監控的,如果說是鏡像資料,倒是真沒啥新意,關鍵這玩意兒並不是旁路裝置,確確實實是一個串聯的裝置,也就是說資料包真的就是完全通過了它的處理邏輯,說實話,如果它不想讓你訪問XXYY,而不僅僅是監控,它完全可以作為一個防火牆使用(對於集權的網管來講,如果是我,我一定會放開所有的訪問,只是監控,這樣才能看到更多好玩的)。這需要裝置具有極強的線速,不是一般的Linux可以做到的,它是怎麼做到的,怎麼做到如此高效能的轉寄的,關鍵是它還是個二層裝置。
4.VPN的透明性
這事得從一次恥辱說起。去年我在實施我的VPN時告知客戶我們的產品是一個二層裝置,於是客戶鬆了一大口氣,因為這會省去他大量的維護時間,因為二層裝置不需要配置網路參數,其實我也是這麼認為的,然而他錯了,我也錯了,實施完畢我興高采烈地回家,在後來的一個月甚至幾個月內,我被無數次的“橋接器模式下的路由問題”騷擾,於是我這個研發再次冒充實施工程師向客戶解釋,說“我們將感興趣流量拉到了第三層,不感興趣流量直接二層通過...”這句詩一樣的話簡直是在打自己的臉,因為這明顯屬於事後解釋,搪塞,為什麼一開始不這麼說呢,客戶如果懂技術,會認為這明顯在扯嘛...
關於這些問題的解釋
以上是我曾經提出的幾個問題,對於很多人來講,這些都不是問題,實際上對於我來講,這些也不是什麼問題,但是對於很多人來講,有必要為他們明個理指條路。
一進一出的裝置與PF_RING
上面的幾個問題中提到的裝置也好,卡也罷,除了問題2之外,都是一進一出的裝置,問題2並不是這種裝置,它可以被看作是一台伺服器,並不是轉寄裝置,之所以將它也放在問題中,是想通過它來談一下效能調優的方方面面。
1.PF_RING採用mmap的方式將網路裸資料放在一個使用者態可以直接access的地方,而不是通過socket read/write機制的記憶體拷貝;
2.PF_RING支援下面2.1到2.3三種方式將裸資料放到mmap到使用者態的環形緩衝區以及2.4的DNA方式:
2.1.按照PACKET通訊端的方式從netif_receive_skb函數中抓取資料包,這是一種和PACKET通訊端相容的方式,所不同的是資料包不再通過socket IO進入使用者態,而是通過mmap;
讓人多少有點遺憾的是,2.4的方式並不是可以隨意使用的,根據其License,提供免費下載,但是以二進位形式提供測試版本的庫,如果需要長期使用,需要購買解鎖的代碼。說來挺可憐的,因為人家也需要錢來繼續將研究進行下去。
PF_RING的背後
很多人都只是認為PF_RING只是一個高效能的抓包機制,提供原生資料包鏡像分析,實現網路審計,這隻是按照傳統的思路來解釋的。更進一步,PF_RING機制顛覆了網路中間節點解釋資料包的方式。按照傳統的觀念,中間網路節點只能按照協議棧的層次一層一層地解析資料包,所謂路由器是三層裝置,交換器是二層裝置,防火牆分為二層防火牆和三層防火牆...使用PF_RING的裝置,它可以將資料包直接從網卡的晶片DMA到你機器上的記憶體,僅此而已,然後你通過一個應用程式而不是核心協議棧來處理資料包,至於說你的應用程式怎麼處置資料包,我來列舉幾個:
1.深度解析資料包,按照各種你可以想到的粒度來解析會話,然後記錄審計資訊;
PF_RING迎合了現代高端網卡的多隊列特性,這是實話,但是即便是不支援多隊列的網卡,如果按照現如今的Linux核心協議棧來處理,假設中斷特定的CPU(實際上Linux的中斷balance實在不敢恭維),咱就假定用的是一塊不支援中斷均衡的網卡,一般而言觸發的非強制中斷也會在該CPU上執行,你絲毫沒有辦法,即使你有8核心CPU,你又能如何,可是對於PF_RING而言,由於可以在一塊網卡上對應多個Ring,就可以將不同的流置於不同的Ring,甚至不同的資料包置於不同Ring,然後每一個Ring由綁在特定CPU上的應用程式處理,這實際上就是將所謂的多隊列上推了一個層次,如所示:
對於非轉寄裝置,比如一個APP Server,也就是說流量到本地終止的情形,未來的架構也許是這個樣子,按照所示:
看上面的圖,也許你會問,裝置怎麼就知道資料包就是發到本地的呢?事實上這不是這個裝置的職責,你怎麼知道我DMA到Ring buffer的是一個以太幀而不是一個純粹的HTTP資料包呢?總之,沒有現形的東西,總讓人不可思議。PF_RING最讓人爽的不是它實現了什麼,而是它提供了讓你實現某種事情的機制,至於你能在PF_RING的架構內實現什麼,完全受限於你的想象力,這也是為何很人人只是認為PF_RING只能抓包的原因。
傳統的誤區
和在SDN環境下問“SDN交換器如何處理IP層的路由”這種問題一樣,我也向問題1裡面提到的網路加速卡供應商索要所謂的”使用者態的協議棧“,正是因為以往網路通訊協定都是在所謂的協議棧中被處理的,所以即使對網路的處理移到了使用者態,我仍然需要有一個使用者態的協議棧才安心,網路在使用者態被處理也好,在核心態被處理也罷,關鍵要有一個棧,這樣才算是網路的正常處理方式。然而我得到的回覆就是:如果你們需要自行開發協議棧,我們會全力配合和支援...天啊,自行開發協議棧,難道我要研讀RFC,然後依照強制,建議等規程去寫代碼?去處理複雜的IP路由,處理TCP狀態機器?處理TLS...難道沒有現成的使用者態協議棧?
動手實現一個透明轉寄裝置
以上扯了很多的理論和感悟,外加一些PF_RING高效能的理由,事實上很多時候我們更關心應該怎麼做,也就是說,更關心介面方面的東西,知道了怎麼用,加上你信任底層的實現沒有問題,外帶你對原理深入的理解,基本就無敵了(可是現實中大量的人只滿足介面怎麼用)。這個小節我嘗試實現一個最簡單的透明轉寄裝置,即一根昂貴的網線,最簡單這個詞意味著它只有轉寄功能以及一點點的過濾功能,目的在於展示介面的使用。
拓撲結構很簡單,我的iMac關掉WIFI,網線串連我的Macbook,我的Macbook通過WIFI連線路由器,Macbook內建一個Linux虛擬機器,添加兩塊網卡,同為Bridge模式,一塊Bridge到WIFI,一塊Bridge到乙太網路,如所示:
如果只有一台電腦,這個拓撲還真不好搭建,別告訴我說使用VMWare的LAN Segment,我很討厭那種東西。
以上拓撲的配置如下:
iMac-en0:192.168.1.200/24 default gateway:192.168.1.1
很清楚了吧。
#include <stdio.h> #include <stdlib.h> #include <pfring.h> #include <string.h> #include <getopt.h> int main(int argc, char *argv[]) { pfring *pfring_net1, *pfring_net2; unsigned char *dev1 = NULL; unsigned char *dev2 = NULL; char c; struct option opts[] = { {.name = "net1", .has_arg = 1, .val = 'i'}, {.name = "net2", .has_arg = 1, .val = 'o'}, {NULL} }; while((c = getopt_long(argc, argv, "i:o:", opts, NULL)) != -1) { switch(c) { case 'i': dev1 = strdup(optarg); break; case 'o': dev2 = strdup(optarg); break; } } if(dev1 == NULL || dev2 == NULL) { goto end; } pfring_net1 = pfring_open(dev1, 1518, PF_RING_PROMISC); pfring_net2 = pfring_open(dev2, 1518, PF_RING_PROMISC); if(pfring_net1 == NULL || pfring_net2 == NULL) { goto end; } if (pfring_set_bpf_filter(pfring_net1, "arp or tcp or udp")) { goto end; } if (pfring_set_direction(pfring_net1, rx_only_direction) || pfring_set_direction(pfring_net2, rx_only_direction)) { goto end; } if (pfring_enable_ring(pfring_net1) || pfring_enable_ring(pfring_net2)) { goto end; } while(1) { unsigned char *pkt; struct pfring_pkthdr ring_hdr; if(pfring_recv(pfring_net1, &pkt, 0, &ring_hdr, 0)) { pfring_send(pfring_net2, pkt, ring_hdr.caplen, 1); } if(pfring_recv(pfring_net2, &pkt, 0, &ring_hdr, 0)) { pfring_send(pfring_net1, pkt, ring_hdr.caplen, 1); } } end: if (pfring_net1) { pfring_close(pfring_net1); } if (pfring_net2) { pfring_close(pfring_net2); } return 0; }
gcc test.c -o test -lpcap -lpfring -lrt
《PF_RING使用者指南.V5.4.4.pdf》的那位,一冊在手別無所求,關鍵是其資源分是0分!編譯好的程式執行./test -i eth0 -o eth1,然後在iMac上開啟網頁吧,完全OK,然後ping一下百度呢?不行!為什嗎?因為這一句:
pfring_set_bpf_filter(pfring_net1, "arp or tcp or udp")
只允許arp,tcp,udp通過,icmp不在此列,因此不讓通過。
現代千兆/萬兆乙太網路卡-以Intel 82599為例
如果說NetLib是Tilera架構專有的解決方案,那麼PF_RING就是x86架構上對應的通用解決方案。對於Intel 82599萬兆卡,提供了很多新的機制以及對於老機制有了很多新的擴充,所有新老機制中,最讓人激動的莫過於多隊列的細化,如所示:
那如果說這個多隊列和PF_RING相結合,則會變成下面這幅圖所表現的:
現代猛卡的軟體應對-PF_RING DNA(Derict NIC Access)
網卡晶片,CPU,晶片集,匯流排的效能都在迅猛提升,那麼軟體呢?很遺憾,軟體已經顯得步履有點踉蹌了。然而PF_RING為你做了更進一步的讓步,那就是連唯一的一次記憶體拷貝也省了,即根本不需要將資料包從網卡拷貝到mmap到使用者態的記憶體,而是在物理層收到包的時候,直接將資料包放到使用者指定的地方。具體來講,就是將網卡上儲存空間map到了地址空間的某處,虛擬記憶體真的是個好東西啊。
基於PF_RING的VPN裝置
我可以回答那個關於VPN的問題了,如果使用PF_RING,我會將資料包抓取到使用者態的進程,取出其MAC頭,IP頭備用,加密整個IP資料報,然後用取出的IP頭,MAC頭重新封裝加密後的資料(傳輸模式),或者用新的IP頭,備用的MAC封裝資料(隧道模式),甚至可以直接加密TCP/UDP的載荷而保留TCP/UDP的頭,這樣的話,我的VPN裝置真的連一個IP地址都不用配置卻能實現超級靈活的加解密措施,真的成了一根昂貴的網線。