內核版本:2.6.34
802.1q
1. 註冊vlan網絡系統子空間,
err = register_pernet_subsys(&vlan_net_ops);
static struct pernet_operations vlan_net_ops = {
.init = vlan_init_net,
.exit = vlan_exit_net,
.id = &vlan_net_id,
.size = sizeof(struct vlan_net),
};
每個子空間註冊成功都會分配一個ID,在register_pernet_subsys() -> register_pernet_operations() -> ida_get_new_above()獲得,而vlan_net_ops中的vlan_net_id記錄了這個ID。註冊子空間的最後會調用子空間的初始化函數vlan_init_net(),它會把vlan_net(有關vlan的proc文件系統信息)加到全局的net->gen->ptr數組中去,下標爲之前分配的ID。這樣,通過vlan_net_id便可隨時查到vlan_net的信息,主要與proc有關。
2. 註冊vlan_notifier_block
err = register_netdevice_notifier(&vlan_notifier_block);
static struct notifier_block vlan_notifier_block __read_mostly = {
.notifier_call = vlan_device_event,
};
err = raw_notifier_chain_register(&netdev_chain, nb);
然後通知事件NETDEV_REGISTER和NETDEV_UP事件到網絡系統的中的每個設備:
此時nb就是vlan_notifier_block,調用通知函數vlan_device_event()。假設此時主機上擁有設備lo[環回接口], eth1[網卡], eth1.1[虛擬接口],來看vlan_device_event()函數:
判斷是否爲vlan虛擬接口,則執行__vlan_device_event(),這個函數的作用就是在proc文件系統中添加或刪除vlan虛擬設備的相應項。顯然,符合條件的是eth1.1,而事件NETDEV_REGISTER會在/proc/net目錄下創建eth1.1的文件。
然後判斷dev是否在vlan_group_hash表中[參考最後”vlan設備組織結構”],它以dev->ifindex爲hash值,顯然,只有eth1纔有正確的ifindex,lo和eth1.1會因查詢失敗而退出vlan_device_event。
grp = __vlan_find_group(dev);
if (!grp)
goto out;
下面的事件處理只有eth1會執行,以NETDEV_UP爲例,通過vlan_group_hash表可以根據eth1查到所有在其上創建的虛擬網卡接口,如果這些網卡接口沒有開啓,則開啓它,這裏開啓用到的是dev_change_flags(vlandev, flgs | IFF_UP)。跟蹤該函數可以發現它僅僅是修改flags後,通知NETDEV_UP事件,等待設備去處理。這裏的含義可以這樣理解,如果ifconfig eth1 up,則在eth1上創建的所有vlan網卡接口都會被up。
可以看出,vlan_device_event最後都是操作的vlan虛擬接口,這點是很重要的,不要越權處理其它設備。
3. 添加協議模塊vlan_packet_type到ptype_base中
在[net\8021q]目錄,主要是關於報文接收的
vlan_skb_recv() [net\8021q\vlan.c]
檢查skb是否被多個協議模塊引用,如果是則拷貝一份,並遞減計數,必要時釋放skb,這部分要和netif_receive_skb()中的pt_prev連起來理解,就明白爲什麼要使用pt_prev而不是直接使用ptype。如果使用ptype,則會多出一次拷貝。
skb_share_check()會調用3個函數:skb_sharde(), skb_clone(), kfree_skb(),都很重要。
skb_shared()檢查skb->users數目是否爲1,不爲1則表示有多個協議棧模塊要處理它,此時就需要使用skb_clone()來複制一份skb;kfree_skb()並不一定釋放skb,只有當skb->users爲1時,纔會釋放;否則只是遞減skb->users。
這一步是核心,此時skb->dev爲真正的設備,經過vlan處理後,報文應該被上層協議看作是由vlan虛擬設備接收的,因此這裏設置skb->dev爲虛擬的vlan設備。
skb->dev = __find_vlan_dev(dev, vlan_id);
以收到ARP請求報文後迴應爲例,看下skb->dev的變化,使得報文在協議棧中流轉:
更新網卡接收報文的信息:
設置skb->len和skb->data指針,從而跑過vlan標籤,而對skb->csum的計算會忽略,因爲在網卡驅動收到報文時,skb->ip_summed== CHECKSUM_NONE。
重置skb->protocol爲vlan標籤後面接的協議類型,之前的protocol爲0x8100(即ETH_P_8021Q)
最後調用netif_rx(),它會將skb重新放入接收隊列中,讓skb在協議棧中繼續向上走。要注意的是這時候skb->protocol已經是vlan標籤後的協議標識,因此重新進入netif_receive_skb()時會被更上一層的ptype處理掉。
此時協議模塊802.1q已經處理完,此時skb會被釋放掉,此時skb->users是1。
netif_rx()這個函數很重要,可以說是各個協議模塊之前報文流向的紐帶,這裏詳細講解下:
獲取當前CPU的softnet_data結構體
softnet_data這個結構體在設備初始化時會被賦值,見[net\core\dev.c中net_dev_init()];對於每個CPU,都會分配一個softnet_data,裏面重要的是backlog.poll = process_backlog;在軟中斷處理中,會調用poll_list鏈表上的poll方法,在稍後會看到加入poll_list鏈表的是queue->backlog,因此當再次在軟中斷中處理該報文時,會使用process_backlog()函數;作爲對比,可以看在網卡驅動中加入poll_list鏈表的是bp->napi,這時候的poll方法是網卡驅動自己的b44_poll()。
判斷input_pkt_queue隊列長度,如果長度爲0,則將queue->backlog加入poll_list中,並觸發軟中斷,同時也將skb加入input_pkt_queue隊列;如果長度>=1,則表明input_pkt_queue隊列中還有未處理的skb,並且隊列頭的skb已經觸發了軟中斷,只是還未被處理,因此此時只需將skb加入input_pkt_queue隊列,而不用再次觸發軟中斷。
這裏有兩個地方要注意,第一是skb加入的鏈隊是input_pkt_queue,但加入poll_list的卻是backlog,這是因爲在軟中斷中調用的是backlog.poll方法,而它會處理input_pkt_queue;第二是軟中斷的觸發只在隊列爲空時再發生,因爲每次軟中斷net_rx_action()中,不只是處理一個skb,而是隊列上所有的skb:while (!list_empty(list))。
整體流程如圖所示:
4. 添加ioctl供用戶空間調用
添加IOCTL選 項,供用戶空間進行內核的vlan配置,比如ADD_VLAN_CMD會創建vlan虛擬接口;DEL_VLAN_CMD會刪除vlan虛擬接口。
VLAN設備的組織結構
如果只是vlan模塊的接收與發送,那瞭解到vlan_skb_recv()與vlan_dev_hard_start_xmit()函數就可以了。但vlan的實現考慮的要多很多,比如:新創建的eth1.1存儲在哪裏?eth1.1和eth1如果進行關聯?這些都是下面要講的。
數據結構vlan_group_hash是vlan虛擬網卡存儲與關聯的核心結構:
static struct hlist_head vlan_group_hash[VLAN_GRP_HASH_SIZE]; [net\8021q\vlan.c]
當通過vconfig創建了eth1.1, eth1.2, eth1.100三個虛擬網卡後,vlan_group_hash的整體結構如圖所示,先有個整體印象:
vlan_group_hash是大小爲32的hash表,所用的hash函數是:
而傳入參數idx就是dev->ifindex,比如eth1的就是1。因此可以這樣理解,vlan_group_hash表插入的是真實網卡設備信息(eth1)。對於一般主機來說,網卡不會太多,32個表項的hash表是完全足夠的。
在添加vlan時,會創建新的vlan虛擬網卡:
register_vlan_device() -> register_vlan_dev()
首先查找網卡是否已存在,這裏的real_dev一般是真實的網卡如eth1等。以real_dev->ifindex值作hash,取出vlan_group_hash的表項,由於可能存在多個網卡的hash值相同,因此還要匹配表項的real_dev是否與real_dev相同。
如果不存在相應的表項,則分配表項struct vlan_group,並加入vlan_group_hash:
結構定義如下,它可以代表在vlan下真實網卡的信息。real_dev指向真實網卡如eth1;nr_vlans表示網卡下創建的vlan數;vlan_devices_arrays用於存儲創建的vlan虛擬網卡:
創建完表項vlan_group,緊接初始化vlan_devices_arrays二維數組中相應元素
最後,設置vlan_devices_arrays相應元素指向創建的vlan虛擬網卡(如eth1.1)的struct net_device。這裏值得注意的是vlan_devices_arrays是二維數組,內核支持的最大vlan數是4096,爲了查找效率,應用了二級目錄的概念。vlan_devices_arrays指向大小512的數組,數組中每個再指向大小8的數組,像eth1.100則位於第12組的第5個(vlan_devices_arrays[11][4])。
以一個例子來說明,當主機收到報文,交由vlan協議模塊處理後(vlan_rcv),此時需要更換skb->dev所指向的設備,以使上層協議認爲報文是來自於虛擬網卡(比如eth1.1),而不知道網卡eth1的存在。更換設備就需要知道skb->dev更換的目標。這由兩個因素決定:skb->dev和vlan_id。skb->dev即報文來自主機的哪個網卡,如來自eth1,則skb->dev->name=”eth1”;vlan_id即vlan號,這在報文中的vlan報文中可以提取出。有了這兩個信息,從vlan_group_hash出發,首先根據skb->dev->ifindex查找vlan_group_hash的相應項(eth1),取出vlan_group;然後,根據vlan_id,在vlan_devices_array中查找到虛擬網卡設備(eth1.1)。
一般支持的最大vlan數是4096,爲了查詢效率,vlan_devices_array並不是一個4096的數組,而是二維數組,將每8個vlan分爲一組,共512組,像eth1.100則位於第12組的第5個。
看完了路由表,重新回到netif_receive_skb ()函數,在提交給上層協議處理前,會執行下面一句,這就是網橋的相關操作,也是這篇要講解的內容。
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
網橋可以簡單理解爲交換機,以下圖爲例,一臺linux機器可以看作網橋和路由的結合,網橋將物理上的兩個局域網LAN1、LAN2當作一個局域網處理,路由連接了兩個子網1.0和2.0。從eth0和eth1網卡收到的報文在Bridge模塊中會被處理成是由Bridge收到的,因此Bridge也相當於一個虛擬網卡。
STP五種狀態
DISABLED
BLOCKING
LISTENING
LEARNING
FORWARDING
創建新的網橋br_add_bridge [net\bridge\br_if.c]
當使用SIOCBRADDBR調用ioctl時,會創建新的網橋br_add_bridge。
首先是創建新的網橋:
然後設置dev->dev.type爲br_type,而br_type是個全局變量,只初始化了一個名字變量
然後註冊新創建的設備dev,網橋就相當一個虛擬網卡設備,註冊過的設備用ifconfig就可查看到:
最後在sysfs文件系統中也創建相應項,便於查看和管理:
ret = br_sysfs_addbr(dev);
將端口加入網橋br_add_if() [net\bridge\br_if.c]
當使用SIOCBRADDIF調用ioctl時,會向網卡加入新的端口br_add_if。
創建新的net_bridge_port p,會從br->port_list中分配一個未用的port_no,p->br會指向br,p->state設爲BR_STATE_DISABLED。這裏的p實際代表的就是網卡設備。
將新創建的p加入CAM表中,CAM表是用來記錄mac地址與物理端口的對應關係;而剛剛創建了p,因此也要加入CAM表中,並且該表項應是local的[關係如下圖],可以看到,CAM表在實現中作爲net_bridge的hash表,以addr作爲hash值,鏈入net_bridge_fdb_entry,再由它的dst指向net_bridge_port。
err = br_fdb_insert(br, p, dev->dev_addr);
設備的br_port指向p。這裏要明白的是,net_bridge可以看作全局量,是網橋,而net_bridge_port則是與網卡相對應的端口,因此每個設備dev有個指針br_port指向該端口。
將新創建的net_bridge_port加入br的鏈表port_list中,在創建新的net_bridge_port時,會分配一個未用的port_no,而這個port_no就是根據br->port_list中的已經添加的net_bridge_port來找到未用的port_no的[具體如下圖]。
重新計算網橋的ID,這裏根據br->port_list鏈表中的net_bridge_port的最小的addr來作爲網橋的ID。
網卡設備的刪除br_del_bridge()與端口的移除add_del_if()與添加差不多,不再詳述。
熟悉了網橋的創建與添加,再來看下網橋是如何工作的。
當收到數據包,通過netif_receive_skb()->handle_bridge()處理網橋:
1. 如果報文來自lo設備,或者dev->br_port爲空(skb->dev是收到報文的網卡設備,而在向網橋添加端口時,dev->br_port被賦予了創建的與網卡相對應的端口p),此時不需要網橋處理,直接返回報文;
2. 如果報文匹配了之前的ptype_all中的協議,則pt_prev不爲空,此時要先進行ptype_all中協議的處理,再進行網橋的處理;
3. br_handle_frame_hook是網橋處理鉤子函數,在br_init() [net\bridge\br.c]中
br_handle_frame_hook = br_handle_frame;
br_handle_frame() [net\bridge\br_input.c]是真正的網橋處理函數,
下面進入br_handle_frame()開始網橋部分的處理:
與前面802.1q講的一樣,首先檢查users來決定是否複製報文:
skb = skb_share_check(skb, GFP_ATOMIC);
如果報文的目的地址是01:80:c2:00:00:0X,則是發往STP的多播地址,此時調用br_handle_local_finish()來完成報文的進一步處理:
而br_handle_local_finish()所做的內容很簡單,因爲是多播報文,主機要做的僅僅是更新報文的源mac與接收端口的關係(在CAM表中)。
接着br_handle_frame()繼續往下看,然後根據端口的狀態來處理報文,如果端口state= BR_STATE_FORWARDING且設置了br_should_route_hook,則轉發後返回skb;否則繼續往下執行state=BR_STATE_LEARNING段的代碼:
如果端口state= BR_STATE_LEARNING,如果是發往本機的報文,則設置pkt_type爲PACKET_HOST,然後執行br_handle_frame_finish來完成報文的進一步處理。要注意的是,這裏將報文發往本機的報文設爲PACKET_HOST,實現了經過網橋處理後,再次進入netif_receive_skb()時,不會再被網橋處理(結果進入網橋的條件理解):
除此之外,端口處於不可用狀態,此時丟棄掉報文:
下面來詳細看下br_handle_frame_finish()函數。
首先還是會根據收到報文的源mac和端口更新CAM表,這是交換機區別於hub的重要特徵:
br_fdb_update(br, p, eth_hdr(skb)->h_source);
然後如果端口處於LEARNING狀態,則只是學習到CAM表中,而不對報文作任何處理,所以丟棄掉報文:
否則端口已處於FORWARDING狀態,此時分情況:
1. 如果報文是多播的,則br_flood_forward(br, skb, skb2);
2. 如果報文是單播的,但不在網橋的CAM表中,則br_flood_forward(br, skb, skb2);
3. 如果報文是單播的,在網橋的CAM表中,但不是發往本機,則br_forward(dst->dst, skb, skb2);
4. 如果報文是單播的,在網橋的CAM表中,且是發往本機,則br_pass_frame_upbr_pass_frame_up(skb2);
br_handle_frame_finish()處理完後,順着最後一種情況繼續往下走,br_pass_frame_up()。
該函數比較簡單,我們知道,在底層報文的向上傳遞就是通過設備的更換來進行的(參考802.1q),這裏將skb的設備換成網橋設備,使上層協議不知道報文來自網卡,而是認爲報文來自於網橋;然後調用netif_receive_skb()再次進入接收棧:
經過網橋處理後,再次進入netif_receive_skb()->handle_bridge(),此時skb->dev已經不是網卡設備了,而是網橋設備,注意到在向網橋添加端口時,是相應網卡dev->br_port賦值爲創建的端口,網橋設備是沒有的,因此其br_port爲空,在這一句會直接返回,進入正常的協議棧流程:
當發送數據報文時,會調用br_dev_xmit()[net\bridge\br_device.c],大致會根據目的地址調用br_multicast_deliver()或br_flood_deliver()或br_deliver(),在其過程中會將skb->dev由原來的網橋設備brdev換面網卡設備dev,然後通過網卡變更向下傳遞報文;
內核協議棧中,發送與接收是分離的,接收像是報文脫殼的過程,發送則是函數的嵌套調用。有關發送的流程,稍後專門詳述。