標籤:
CORE網路資料包接收傳遞過程分析
能夠接收實際網路流量是CORE的一個顯著優點,這使得已有的系統能方便地接入虛擬網路進行類比。CORE對網路裝置的虛擬是通過LXC技術來實現的,而對網路的虛擬則是通過虛擬網卡(veth)、橋接器(Bridge)、Quagga來實現的。本文檔主要通過分析CORE中網路資料傳遞過程,來理解CORE網路類比。
拓撲結構
為了方便描述,以1所示拓撲結構為例子,分析資料流從網卡eth0到虛擬節點n2的過程。
圖1 樣本拓撲
虛擬網路建立由CORE後台根據前台的拓撲結構和配置,執行相應的命令進行實現,如下:
#建立容器n1
/usr/sbin/vnoded -v -c /tmp/pycore.52385/n1 -l /tmp/pycore.52385/n1.log -p /tmp/pycore.52385/n1.pid -C /tmp/pycore.52385/n1.conf
#建立容器n2
/usr/sbin/vnoded -v -c /tmp/pycore.52385/n2 -l /tmp/pycore.52385/n2.log -p /tmp/pycore.52385/n2.pid -C /tmp/pycore.52385/n2.conf
#建立橋接器br.38079.56309
/usr/sbin/brctl addbr b.38079.56309
#關閉spanning tree protocol and forwarding delay並啟動橋接器
/usr/sbin/brctl stp b.38079.56309 off
/usr/sbin/brctl setfd b.38079.56309 0
/sbin/ip link set b.38079.56309 up
#建立網路轉寄過濾器ebtables,規則為接收和轉寄
/sbin/ebtables -N b.38079.56309 -P ACCEPT
/sbin/ebtables -A FORWARD --logical- in b.38079.56309 –j b.38079.56309
#通過vcmd執行,關閉多播探測
echo‘0’> /sys/devices/virtual/net/b.38079.56311/bridge/multicast_snooping
#建立虛擬網卡n1.eth0.44
/sbin/ip link add name n1.eth0.44 type veth peer name n1.0.44
#將虛擬網卡加入到容器的命名空間中,並命名為eth0
/sbin/ip link set n1.0.44 netns 17122 #17122為節點n1的vnoded進程的ID
/sbin/ip link set n1.0.44 name eth0
/sbin/ip link set n1.eth0.44 up
#將虛擬網卡綁定到橋接器上
/usr/sbin/brctl addif b. 38079.56309 n1.eth0.44
/sbin/ip link set n1.eth0.44 up
#通過vcmd執行,設定網卡mac地址
/sbin/ip link set dev eth0 address 00:00:00:aa:00:00 #node內執行
/sbin/ip addr add 10.0.0.1/24 dev eth0 #node內執行
/sbin/ip link set eth0 up #node內執行
#通過vcmd執行,在容器n1啟動路由服務
sh quaggaboot.sh zebra
sh quaggaboot.sh ospfd
sh quaggaboot.sh vtysh
不難發現樣本拓撲內部結構為圖2。
圖2 樣本拓撲內部結構
在樣本拓撲中,CORE建立了兩個容器(命名空間),兩個橋接器,三個虛擬網卡對(其中一端放進了容器中),並在命名空間中虛擬路由層,為了接收資料,命名空間中的虛擬網卡被串連到橋接器上,而橋接器可以串連物理網卡或其它虛擬網卡。這樣就實現了虛擬節點、物理網卡之間的互聯。
資料流分析
以樣本拓撲結構為例,第一步是網卡接收資料包,在網卡硬體中斷響應中完成。為了儘可能快速處理,網卡接收中斷響應函數會將接收資料產生skb結構體,將通過調用netif_rx(skb)將任務以非強制中斷的方式插入到CPU調度隊列,然後立即返回。第二步,ksoftirqd處理非強制中斷。這個過程,ksoftirqd根據非強制中斷類型,調用不同的響應函數。本例中ksoftirqd將調用netif_receive_skb函數對skb包進行處理。netif_receive_skb可以理解為從物理層接收資料包,因此它也算是鏈路層的入口函數。在這個函數中,會調用到handle_bridge函數,將skb交給網卡進行處理(如果配置了橋接器),調用br_handle_frame_hook。第三步,橋接器會依據ebtables規則對skb作轉寄、廣播、丟棄等操作。CORE中定義了ebtables規則為FORWARDING,因此橋接器會將ebtables轉寄給串連到它的另一個(虛擬)網卡,調用veth_xmit。第四步,對於虛擬網卡而言,發送就是接收,於是在veth_xmit中,它將skb重新以非強制中斷的方式插入到CPU調度隊列,此時skb已在容器中運行。第五步,ksoftirqd調用netif_receive_skb函數,由於容器中沒有配置橋接器,netif_receive_skb函數會調用packet_type成員.func = ip_rcv將資料包送至L3層路由層。第六步,路由層從ip_rcv接收skb,調用ip_rcv_finish對skb進行路由(調用skb_rtable),決定本地接收還是轉寄skb。
圖3 資料流接收轉寄過程
各組成部分
物理網卡eth0
物理網卡,也叫乙太網路卡,在核心的表現形式為ethernet_device(簡寫為eth0)。它的實現是由網卡驅動提供的。網卡既是PCI裝置,也是網路裝置。它在開啟時需要註冊中斷響應函數xxx_interrupt。
request_irq(dev->irq, &xxx_interrupt,
lp->shared_irq ? IRQF_SHARED : 0, dev->name,
(void *)dev)
當接收資料包時,會觸發網卡中斷,網卡驅動會為新到來的資料包申請skb, 並將網卡中的資料讀到skb, 並通過netif_rx發送到核心協議棧上層。
if (rtl8169_rx_vlan_skb(tp, desc, skb) < 0)
netif_rx(skb);
dev->stats.rx_bytes += pkt_size;
dev->stats.rx_packets++;
這裡有必要說一下skb,它用於儲存網路資料包,結構。skb由網卡產生。由於它包含了net_device,協議類型,地址等。每一層在遇到skb時都能根據自己的商務邏輯進行處理。
圖4 skb結構
橋接器Bridge
橋接器(Bridge)是L2層(資料連結層)裝置,用於實現乙太網路裝置之間的橋接。橋接器可以綁定若干個乙太網路介面裝置(利用brctl addif命令),從而實現網卡之間的互聯。
(1) 模組初始化
在Linux中,橋接器由動態載入模組Bridge完成。模組初始化時,除了完成自身模組初始化外,最重要一點就是向系統註冊橋接器處理HOOK。該HOOK將在handle_bridgge中調用。
br_handle_frame_hook = br_handle_frame;
(2) 與網卡綁定
圖4 net_bridge結構體
橋接器與網卡綁定通過與橋接器連接埠綁定來實現。4所示,net_bridge結構體中維護了一個net_bridge_port結構體鏈表,而net_device(包含在eth裝置結構體中)有指向net_bridge_port結構體的指標,該指標可以指向net_bridge_port結構體鏈表中的元素。
(3) 橋接處理
橋接處理一方面是接收資料,另一方面是發送資料。本例中沒有通過橋接器來發送資料,在此不作討論。接收資料從br_handle_frame開始。
const unsigned char *dest = eth_hdr(skb)->h_dest;
int (*rhook)(struct sk_buff *skb);
//判斷是否為有效物理地址,非全0地址以及非廣播位址
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))goto drop;
//判斷skb包是否被共用skb->users != 1,若是,則複製一份,否則直接返回
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb) return NULL;
const unsigned char *dest = eth_hdr(skb)->h_dest;
int (*rhook)(struct sk_buff *skb);
//判斷是否為有效物理地址,非全0地址以及非廣播位址
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))goto drop;
//判斷skb包是否被共用skb->users != 1,若是,則複製一份,否則直接返回
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb) return NULL;
//這個函數是在判斷是否為鏈路本地多播地址,01:80:c2:00:00:0x
if (unlikely(is_link_local(dest))) {
/* Pause frames shouldn‘t be passed up by driver anyway */
if (skb->protocol == htons(ETH_P_PAUSE)) goto drop;
/* If STP is turned off, then forward */
if (p->br->stp_enabled == BR_NO_STP && dest[5] == 0) goto forward;
if (NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
NULL, br_handle_local_finish)) return NULL; /* frame consumed by filter */
else return skb; /* continue processing */
}
forward:
switch (p->state) {
case BR_STATE_FORWARDING:
//如果橋接器處於forwarding狀態,且該報文要走L3層進行轉寄,則直接返回
//br_should_route_hook鉤子函數在ebtable裡面設定為ebt_broute函數,它根據使用者的規決定該報文是否要通過L3層來轉寄;一般rhook為空白
rhook = rcu_dereference(br_should_route_hook);
if (rhook != NULL) {
if (rhook(skb))
return skb;
dest = eth_hdr(skb)->h_dest;
}
/* fall through */
case BR_STATE_LEARNING:
//如果資料包的目的mac地址為虛擬橋接器裝置的mac地址,則標記為host
if (!compare_ether_addr(p->br->dev->dev_addr, dest))
skb->pkt_type = PACKET_HOST;
//調用橋接器在NF_BR_PREROUTING處掛載的鉤子函數,
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
break;
default:
drop:
kfree_skb(skb);
}
return NULL;
FORWARDING以及LEARNING為橋接器的狀態,橋接器連接埠一般有5種狀態:
1) disable 被管理員禁用
2) blcok 休息,不參與資料包轉寄
3) listening 監聽
4) learning 學習ARP資訊,準備向工作狀態改變
5) forwarding 正常工作,轉寄資料包
(4) 橋接器過濾器ebtables_filter
橋接器提供可配置的過濾轉寄規則,這一功能通過ebtables_filter來實現。ebtables_filter,ebtables和xt_tables都是可裝載模組。ebtables用於儲存過濾規則,每條過濾規則都對應ebtables中一個表項。5所示,在ebtables_filter模組初始化時向Bridge註冊了各類HOOK函數。Bridge在接受、發送、轉寄資料包時調用相應的HOOK函數,實現過濾功能。
圖5 net_filter結構體
虛擬網卡對veth pair
為了與容器中的節點通訊,CORE會為每個節點建立一個虛擬網卡對,並將一端移至容器中。
#將虛擬網卡對
/sbin/ip link add name n1.eth0.44 type veth peer name n1.0.44
#將虛擬網卡對的一端加入到容器的命名空間中,並命名為eth0
/sbin/ip link set n1.0.44 netns 17122 #17122為節點n1的vnoded進程的ID
虛擬網卡是去掉了硬體相關操作的網路卡驅動程式。與物理網卡不一樣,它沒有接收外部資料的功能,只能由別的模組給它發送資料,通過調用veth_xmit實現。
stats->tx_bytes += length;
stats->tx_packets++;
rcv_stats->rx_bytes += length;
rcv_stats->rx_packets++;
netif_rx(skb);
在veth_xmit實現中,veth會同時將自己的接收和發送資料統計增加,並調用netif_rx(skb)將skb包交給上層處理。
丟包分析
CORE在虛擬網路裡傳遞資料包時,都是通過Linux的非強制中斷機制進行。當資料流量特別大的時候,非強制中斷守護進程(ksoftirqd)負載會很大,有時候會達到100%,這時候可能發生丟包。這種丟包與網路通訊協定會對格式、校正進行檢查後做的丟棄壞包不一樣,是CORE網路模擬系統的瓶頸。我們看一下netif_rx函數。
int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags;
/* if netpoll wants it, pretend we never saw it */
if (netpoll_rx(skb)) return NET_RX_DROP;
if (!skb->tstamp.tv64) net_timestamp(skb); /* 時間戳記處理*/
local_irq_save(flags); /* 儲存當前中斷狀態,關中斷*/
queue = &__get_cpu_var(softnet_data); /* 時間戳記處理*/
__get_cpu_var(netdev_rx_stat).total++;
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) { /* 插入調度隊列*/
enqueue:
__skb_queue_tail(&queue->input_pkt_queue, skb); /* 插入調度隊列*/
local_irq_restore(flags); /* 開啟中斷*/
return NET_RX_SUCCESS;
}
napi_schedule(&queue->backlog); /*喚醒ksoftirqd進行隊列處理*/
goto enqueue;
}
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags); /* 開啟中斷*/
kfree_skb(skb);
return NET_RX_DROP;
}
netif_rx函數會將skb包加入到當前cpu的softnet_data隊列,如果隊列為滿,則丟棄資料包操作。可以通過cpu的netdev_rx_stat狀態查看到丟包的狀態。
CORE網路資料包接收傳遞過程分析