由于Linux系統(tǒng)強大的網(wǎng)絡管理功能,很多網(wǎng)絡設備都是基于Linux開發(fā)的,通過Linux自帶的netfilter模塊,以及豐富的開源軟件可以打造出功能完善的網(wǎng)絡接入設備。其中典型的運用就是虛擬子網(wǎng)VLAN,在交換機上劃分多個VLAN子網(wǎng),并且匯聚到中繼口Trunk上,同時網(wǎng)關啟用VLAN模塊,通過配置對應的VLAN虛擬網(wǎng)卡實現(xiàn)與交換機的互聯(lián)通訊。目前大多數(shù)網(wǎng)卡為了提高數(shù)據(jù)包的處理能力,直接對報文的VLAN Tag進行移除和插入操作,因此到達內(nèi)核中的數(shù)據(jù)包已經(jīng)不包含VLAN Tag。為了實現(xiàn)對數(shù)據(jù)包的分析審計等需求,需要知道每個數(shù)據(jù)包是屬于哪個VLAN的?;谶@個目的本文通過對數(shù)據(jù)包的收發(fā)流程,抓包原理的分析,提出了一種通過直接修改內(nèi)核讓數(shù)據(jù)包帶上VLAN ID的方法。
現(xiàn)在使用最廣泛的VLAN協(xié)議標準是 IEEE 802.1Q,許多廠家的交換機,路由器產(chǎn)品都支持IEEE 802.1Q標準。802.1Q Tag的長度是4 bytes,它位于以太網(wǎng)幀中源MAC地址和長度類型之間。802.1Q Tag包含4個字段。
(1)Type:長度為2 bytes,表示幀類型,802.1Q tag幀中Type字段取固定值 0x8100,如果不支持 802.1Q的設備收到802.1Q幀,則將其丟棄。
(2)PRI:priority字段,長度為3 bit,表示 以太網(wǎng)幀的優(yōu)先級,取值范圍是0~7,數(shù)值越大,優(yōu)先級越高。當交換機,路由器發(fā)生傳輸擁塞時,優(yōu)先發(fā)送優(yōu)先級高的數(shù)據(jù)幀。
(3)CFI:Canonical Format Indicator,長度為 1bit,表示MAC地址是否是經(jīng)典格式。CFI為0說明是經(jīng)典格式,CFI為1表示為非經(jīng)典格式。該字段用于區(qū)分以太網(wǎng)幀、FDDI幀和令牌環(huán)網(wǎng)幀,在以太網(wǎng)幀中,CFI取值為0。
(4)VID:VLAN ID,長度為12 bit,取值范圍是0~4095,其中0和4095是保留值,不能給用戶使用。
圖1 VLAN Tag報文格式
數(shù)據(jù)包到達網(wǎng)卡以后,網(wǎng)卡判斷數(shù)據(jù)包是否為802.1Q報文,如果是的話則移除VLAN Tag,修改以太網(wǎng)幀,同時把VLAN信息儲存在 sk_buff中。網(wǎng)卡驅(qū)動從網(wǎng)卡收到的報文,已經(jīng)不帶VLAN tag了。接著通過 netif_rx進入?yún)f(xié)議棧,netif_rx調(diào)用napi_schedule調(diào)度,發(fā)送 NET_RX_SOFTIRQ軟中斷,觸發(fā)net_rx_action執(zhí)行其 poll操作 process_backlog,接著調(diào)用netif_receive_skb來處理收到的skb數(shù)據(jù)。VLAN模塊通過調(diào)用dev_add_pack注冊了802.1Q的協(xié)議類型,將自己的prot_hook 掛到了ptype_all鏈表中。在netif_receive_skb遍歷ptype_all找到對應的VLAN鉤子,于是進入了vlan的接收函數(shù)vlan_skb_recv,這個函數(shù)會移除 VLAN Tag重置以太網(wǎng)層的 proto,再調(diào)用netif_rx重新進入?yún)f(xié)議棧處理流程。由于VLAN Tag已經(jīng)在網(wǎng)卡上被移除了,實際上并不會進入vlan_skb_recv。不過網(wǎng)卡只會移除第一層VLAN Tag,如果你的報文是QINQ這種雙層VLAN的協(xié)議,那么第二層VLAN Tag將由vlan_skb_recv來移除。
數(shù)據(jù)包進入ip_crv以后,根據(jù)目的IP進行路由判斷,如果是發(fā)往本地的報文則進入ip_local_deliver后發(fā)往本地應用。如果目的IP不是本機則根據(jù)默認路由進入轉(zhuǎn)發(fā)模塊ip_forward,如果需要 NAT則內(nèi)核修改數(shù)據(jù)包的源 IP等信息,接著協(xié)議棧通過dev_queue_xmit調(diào)用 dev_hard_start_xmit,關聯(lián)到了具體的網(wǎng)絡設備處理函數(shù),從而調(diào)用了vlan_dev_hard_start_xmit,在這里插入VLAN Tag頭,并壓入sk_buffer中,找到真實的網(wǎng)絡設備,再次調(diào)用dev_queue_xmit,進入網(wǎng)卡驅(qū)動,這里只有雙層VLAN協(xié)議才會由內(nèi)核插入內(nèi)層的VLAN TAG,最外層的VLAN TAG由網(wǎng)卡完成插入。
Libpcap通過創(chuàng)建 AF_PACKET類型的套接字,把自己的prot_hook掛到了ptype_all鏈表上,AF_PACKET套接字注冊的鉤子函數(shù)是packet_rcv??梢钥闯鰺o論是接收還是發(fā)送的報文,內(nèi)核都會對ptype_all鏈表進行遍歷,最終會調(diào)用到packet_rcv函數(shù),把數(shù)據(jù)包加入到了 AF_PACKET套接字的接收隊列。當應用程式調(diào)用recv接收數(shù)據(jù)包時內(nèi)核最終會調(diào)用packet_recvmsg從接收隊列中取出skb,將數(shù)據(jù)包內(nèi)容skb->data拷貝到用戶空間。
通過上面的分析,我們只要在packet_recvmsg函數(shù)里在將數(shù)據(jù)包拷貝到用戶空間的時候?qū)LAN ID插入到報文的結(jié)尾,再通過libpcap等AF_PACKET套接字接口獲取數(shù)據(jù)的時候就可以得到VLAN ID。代碼修改如下。
修改前:
修改后:
以發(fā)送帶有VLAN ID為100的報文為例,下圖是交換機發(fā)出的報文:
圖2 VLAN報文
Linux網(wǎng)關接收到數(shù)據(jù)包以后將VLAN ID插入結(jié)尾:
圖3 修改后的VLAN報文
通過修改內(nèi)核的方式使得 AF_PACKET套接字獲取的報文攜帶VLAN ID,為數(shù)據(jù)包的分析審計提供了基礎。