歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux下使用虛擬網卡的ingress流控(入口流控)

Linux下使用虛擬網卡的ingress流控(入口流控)

日期:2017/2/28 14:37:58   编辑:Linux教程

Linux內核實現了數據包的隊列機制,配合多種不同的排隊策略,可以實現完美的流量控制和流量整形(以下統稱流控)。流控可以在兩個地方實現,分別為egress和ingress,egress是在數據包發出前的動作觸發點,而ingress是在數據包接收後的動作觸發點。Linux的流控在這兩個位置實現的並不對稱,即Linux並沒有在ingress這個位置實現隊列機制。那麼在ingress上就幾乎不能實現流控了。

雖然使用iptables也能模擬流控,但是如果你就是想用真正的隊列實現流控的話,還真要想想辦法。也許,就像電子郵件的核心思想一樣,你總是能完美控制發送,卻對接收毫無控制力,如果吸收了這個思想,就可以理解ingress隊列流控的難度了,然而,僅僅是也許而已。

Linux在ingress位置使用非隊列機制實現了一個簡單的流控。姑且不談非隊列機制相比隊列機制的弊端,僅就ingress的位置就能說明我們對它的控制力幾乎為0。ingress處在數據進入IP層之前,此處不能掛接任何IP層的鉤子,Netfilter的PREROUTING也在此之後,因此在這個位置,你無法自定義任何鉤子,甚至連IPMARK也看不到,更別提關聯socket了,因此你就很難去做排隊策略,你幾乎只能看到IP地址和端口信息。

一個現實的ingress流控的需求就是針對本地服務的客戶端數據上傳控制,比如上傳大文件到服務器。一方面可以在底層釋放CPU壓力,提前丟掉CPU處理能力以外的數據,另一方面,可以讓用戶態服務的IO更加平滑或者更加不平滑,取決於策略。

既然有需求,就要想法子滿足需求。目前我們知道的是,只能在egress做流控,但是又不能讓數據真的outgoing,另外,我們需要可以做很多策略,這些策略遠不是僅由IP,協議,端口這5元組可以給出。那麼一個顯而易見的方案就是用虛擬網卡來完成,圖示如下:

以上的原理圖很簡單,但是實施起來還真有幾個細節。其中最關鍵是路由的細節,我們知道,即使是策略路由,也必須無條件從local表開始查找,在目標地址是本機情況下,如果希望數據按照以上流程走的話,就必須將該地址從local表刪除,然而一旦刪除,本機將不再會對該地址回應ARP請求。因此可以用幾個方案:

1.使用靜態ARP或者使用ebtables更改ARP,或者使用arping主動廣播arp配置;

2.使用一個非本機的地址,然後修改虛擬網卡的xmit函數,內部使其DNAT成本機地址,這就繞開了local表的問題。

不考慮細節,僅就上述原理圖討論的話,你可以在常規路徑的PREROUTING中做很多事情,比如使用socket match關聯socket,也可以使用IPMARK。

下面,我們就可以按照上述圖示,實際實現一個能用的。首先先要實現一個虛擬網卡。仿照loopback接口做一個用於流控的虛擬接口,首先創建一個用於ingress流控的虛擬網卡設備

dev = alloc_netdev(0, "ingress_tc", tc_setup);

然後初始化其關鍵字段

static const struct net_device_ops tc_ops = {
.ndo_init = tc_dev_init,
.ndo_start_xmit= tc_xmit,
};
static void tc_setup(struct net_device *dev)
{
ether_setup(dev);
dev->mtu = (16 * 1024) + 20 + 20 + 12;
dev->hard_header_len = ETH_HLEN; /* 14 */
dev->addr_len = ETH_ALEN; /* 6 */
dev->tx_queue_len = 0;
dev->type = ARPHRD_LOOPBACK; /* 0x0001*/
dev->flags = IFF_LOOPBACK;
dev->priv_flags &= ~IFF_XMIT_DST_RELEASE;
dev->features = NETIF_F_SG | NETIF_F_FRAGLIST
| NETIF_F_TSO
| NETIF_F_NO_CSUM
| NETIF_F_HIGHDMA
| NETIF_F_LLTX
| NETIF_F_NETNS_LOCAL;
dev->ethtool_ops = &tc_ethtool_ops;
dev->netdev_ops = &tc_ops;
dev->destructor = tc_dev_free;
}

接著構建其xmit函數

static netdev_tx_t tc_xmit(struct sk_buff *skb,
struct net_device *dev)
{
skb_orphan(skb);
// 直接通過第二層!
skb->protocol = eth_type_trans(skb, dev);
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb->mac_len = skb->network_header - skb->mac_header;
// 本地接收
ip_local_deliver(skb);

return NETDEV_TX_OK;
}

接下來考慮如何將數據包導入到該虛擬網卡。有3種方案可選:

方案1:如果不想設置arp相關的東西,就要修改內核了。在此我引入了一個路由標志,RT_F_INGRESS_TC,凡是有該標志的路由,全部將其導入到構建的虛擬網卡中,為了策略化,我並沒有在代碼中這麼寫,而是改變了RT_F_INGRESS_TC路由的查找順序,優先查找策略路由表,然後再查找local表,這樣就可以用策略路由將數據包導入到虛擬網卡了。

方案2:構建一個Netfilter HOOK,在其target中將希望流控的數據NF_QUEUE到虛擬網卡,即在queue的handler中設置skb->dev為虛擬網卡,調用dev_queue_xmit(skb)即可,而該虛擬網卡則不再符合上面的圖示,新的原理圖比較簡單,只需要在虛擬網卡的hard_xmit中reinject數據包即可。(事實上,後來我才知道,原來IMQ就是這麼實現的,幸虧沒有動手去做無用功)

方案3:這是個快速測試方案,也就是我最原始的想法,即將目標IP地址從local表刪除,然後手動arping,我的測試也是基於這個方案,效果不錯。

不管上面的方案如何變化,終究都是一個效果,那就是既然網卡的ingress不能流控,那就在egress上做,而又不能使用物理網卡,虛擬網卡恰好可以自定義其實現,可以滿足任意需求。我們可以看到,虛擬網卡是多麼的功能強大,tun,lo,nvi,tc...所有這一切,精妙之處全在各自不同的xmit函數。

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2013-12/93744p2.htm

Copyright © Linux教程網 All Rights Reserved