詳細講解Linux內(nèi)核角度分析tcpdump原理(1)
一、tcpdump的用途
tcpdump是Linux系統(tǒng)抓包工具,tcpdump基于libpcap庫,根據(jù)使用者的定義對網(wǎng)絡(luò)上的數(shù)據(jù)包進行截獲,tcpdump可以將網(wǎng)絡(luò)中傳送的數(shù)據(jù)包中的"頭"完全截獲下來提供分析,支持針對網(wǎng)絡(luò)層、協(xié)議、主機、網(wǎng)絡(luò)或端口的過濾,并提供and、or、not等邏輯語句來幫助去掉無用的信息。通過tcpdump可以分析很多網(wǎng)絡(luò)行為,比如丟包重傳、詳細報文、tcp分組等,總之通過tcpdunp可以為各種網(wǎng)絡(luò)問題進行排查,可以在服務(wù)器上將捕獲的數(shù)據(jù)包信息以pcap文件保存下來,通過wireshark打開,更直觀地分析。
tcpdump是基于libpcap庫的,數(shù)據(jù)包的過濾是基于BPF(tcpdump依附標準的bpf機器來運行,tcpdump過濾規(guī)則會被轉(zhuǎn)化為一段bpf指令并加載到內(nèi)核中的bpf虛擬機器上執(zhí)行),使用bpf虛擬機的tcpdump完美解決了包過濾問題??傊畉cpdump使用libpcap這種鏈路層旁路處理的形式進行包捕獲,使用bpf機制實現(xiàn)對包的完美過濾。
二、libpcap簡單介紹
libpcap(Packet Capture Library),即數(shù)據(jù)包捕獲函數(shù)庫,是Unix/Linux平臺下的網(wǎng)絡(luò)數(shù)據(jù)包捕獲函數(shù)庫,獨立于系統(tǒng)的用戶層包捕獲的API接口,為底層網(wǎng)絡(luò)監(jiān)測提供了一個可移植的框架。
利用libpcap函數(shù)庫開發(fā)應(yīng)用程序的基本步驟:
捕獲各種數(shù)據(jù)包,例如:網(wǎng)絡(luò)流量統(tǒng)計。
過濾網(wǎng)絡(luò)數(shù)據(jù)包,例如:過濾掉本地上的一些數(shù)據(jù),類似防火墻。
分析網(wǎng)絡(luò)數(shù)據(jù)包,例如:分析網(wǎng)絡(luò)協(xié)議,數(shù)據(jù)的采集。
存儲網(wǎng)絡(luò)數(shù)據(jù)包,例如:保存捕獲的數(shù)據(jù)以為將來進行分析。
libpcap庫在linux上的安裝過程
測試:
編譯:
報錯提醒:
解決:
執(zhí)行 locate libpcap.so.1 , 查看libpcap.so.1在系統(tǒng)中的路徑 , 顯示為 : /usr/local/lib/libpcap.so.1.2.1 以管理員權(quán)限打開編輯 /etc/ld.so.conf 文件, 末尾新一行追加 /usr/local/lib , /usr/local/lib 為 libpcap.so.1.7.4 所在目錄, 保存退出 以管理員權(quán)限執(zhí)行 ldconfig(如果不支持改命令用whereis ldconfig查看并設(shè)置環(huán)境變量)命令, 成功

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)??


幾個重要的API
pcap_lookupdev():函數(shù)用于查找網(wǎng)絡(luò)設(shè)備,返回可被 pcap_open_live() 函數(shù)調(diào)用的網(wǎng)絡(luò)設(shè)備名指針。
pcap_lookupnet():函數(shù)獲得指定網(wǎng)絡(luò)設(shè)備的網(wǎng)絡(luò)號和掩碼。
pcap_open_live(): 函數(shù)用于打開網(wǎng)絡(luò)設(shè)備,并且返回用于捕獲網(wǎng)絡(luò)數(shù)據(jù)包的數(shù)據(jù)包捕獲描述字。對于此網(wǎng)絡(luò)設(shè)備的操作都要基于此網(wǎng)絡(luò)設(shè)備描述字。
pcap_compile(): 函數(shù)用于將用戶制定的過濾策略編譯到過濾程序中。
pcap_setfilter():函數(shù)用于設(shè)置過濾器。
pcap_loop():函數(shù) pcap_dispatch() 函數(shù)用于捕獲數(shù)據(jù)包,捕獲后還可以進行處理,此外 pcap_next() 和 pcap_next_ex() 兩個函數(shù)也可以用來捕獲數(shù)據(jù)包。
pcap_close():函數(shù)用于關(guān)閉網(wǎng)絡(luò)設(shè)備,釋放資源。
三、tcpdump實現(xiàn)抓包原理剖析
使用strace追蹤

可以看到tcpdump抓包創(chuàng)建的的套接字類型AF_PACKET
在libpcap庫源碼中也可以看到有調(diào)用socket系統(tǒng)調(diào)用:
AF_PACKET和socket應(yīng)用結(jié)合一般都是用于抓包分析,packet套接字提供的是L2的抓包能力。
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
系統(tǒng)調(diào)用:
socket創(chuàng)建函數(shù):
調(diào)用:__sock_create
sock_create 函數(shù)主要就是創(chuàng)建了socket . 同時根據(jù)之前PF_PACKET 模塊注冊到全局變量net_families 。 找到af_packet.c 中初始化的 static const struct net_proto_family packet_family_ops。而sock_create 函數(shù)中 err = pf->create(net, sock, protocol, kern); 最終就會調(diào)用 packet_family_ops 里的packet_create
Linux內(nèi)核中定義了net_proto_family結(jié)構(gòu)體,用來指明不同的協(xié)議族對應(yīng)的socket創(chuàng)建函數(shù),family字段是協(xié)議族的類型,create是創(chuàng)建socket的函數(shù),如下是PF_PACKET對應(yīng)結(jié)構(gòu)體。
找到AF_PACKET協(xié)議族對應(yīng)的create函數(shù):可以看到po->prot_hook.func = packet_rcv;po->prot_hook其實packet_type,packet_type結(jié)構(gòu)體: packet_type 結(jié)構(gòu)體第一個type 很重要,對應(yīng)鏈路層中2個字節(jié)的以太網(wǎng)類型。而dev.c 鏈路層抓取的包上報給對應(yīng)模塊,就是根據(jù)抓取的鏈路層類型,然后給對應(yīng)的模塊處理,例如socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL)); ETH_P_ALL表示所有的底層包都會給到PF_PACKET 模塊的處理函數(shù),這里處理函數(shù)就是packet_rcv 函數(shù)。
設(shè)置了回調(diào)函數(shù):packet_rcv,并通過register_prot_hook(sk)完成了注冊,其中注冊過程將再下面分析
展開注冊函數(shù)register_prot_hook(sk)
展開dev_add_pack
綜上:tcpdump在剛開始工作時創(chuàng)建了PF_PACKET套接字,并在全局的ptype_all中掛載了該套接字的pt(packet_type *pt),其中pt的字段func設(shè)置了相應(yīng)的回調(diào)函數(shù)packet_rcv(后面將分析該函數(shù)),到此tcpdump抓包的socket(AF_PACKET)創(chuàng)建完成,相應(yīng)的準備工作完成。
網(wǎng)絡(luò)收包時tcpdump進行抓包
函數(shù)調(diào)用關(guān)系
調(diào)用關(guān)系:netif_receive_skb-->netif_receive_skb-->netif_receive_skb_internal->__netif_receive_skb-->__netif_receive_skb_core
核心函數(shù)__netif_receive_skb_core
__netif_receive_skb_core函數(shù)在遍歷ptype_all時,同時也執(zhí)行了deliver_skb(skb, pt_prev, orig_dev);deliver函數(shù)調(diào)用了paket_type.func(),也就是packet_rcv ,如下源碼所示:
下面將展開packet_rcv函數(shù)進行分析;函數(shù)接收到鏈路層網(wǎng)口的數(shù)據(jù)包后,會根據(jù)應(yīng)用層設(shè)置的bpf過濾數(shù)據(jù)包,符合要求的最終會加到struct sock sk 的接收緩存中。使用BPF過濾過程將在后面進行分析。
綜上一旦關(guān)聯(lián)上鏈路層抓到的包就會copy一份給上層接口(即PF_PACKET 注冊的回調(diào)函數(shù)packet_rev). 而回調(diào)函數(shù)會根據(jù)應(yīng)用層設(shè)置的bpf過濾數(shù)據(jù)包,最終放入接收緩存的數(shù)據(jù)包肯定是符合應(yīng)用層想截取的數(shù)據(jù)。因此最后一步recvfrom 也就是從接收緩存的數(shù)據(jù)包copy給應(yīng)用層,如下源碼:
到這,網(wǎng)絡(luò)接收數(shù)據(jù)包時的抓包過程就結(jié)束了
網(wǎng)絡(luò)發(fā)包時tcpdump進行抓包
Linux協(xié)議棧中提供的報文發(fā)送函數(shù)有兩個,一個是鏈路層提供給網(wǎng)絡(luò)層的發(fā)包函數(shù)dev_queue_xmit(),另一個就說軟中斷發(fā)吧包函數(shù)之間調(diào)用的sch_direct_xmit(),這兩個函數(shù)最終都會調(diào)用dev_hard_start_xmit()
xmit_one():發(fā)送一個到多個數(shù)據(jù)包
dev_queue_xmit_nit():將數(shù)據(jù)包發(fā)送給driver
在遍歷ptype_all時,同時也執(zhí)行了deliver_skb(skb, pt_prev, orig_dev);deliver函數(shù)調(diào)用了paket_type.func(),也就是packet_rcv ,如下源碼所示:
下面的流程就和網(wǎng)絡(luò)收包時tcpdump進行抓包一樣了(packet_rcv函數(shù)中會將用戶設(shè)置的過濾條件,通過BPF進行過濾,并將過濾的數(shù)據(jù)包添加到接收隊列中,應(yīng)用層在libpcap庫中調(diào)用recvfrom 。 PF_PACKET 協(xié)議簇模塊調(diào)用packet_recvmsg 將接收隊列中的數(shù)據(jù)copy應(yīng)用層)
tcpdump進行抓包的內(nèi)核流程梳理
應(yīng)用層通過libpcap庫:調(diào)用系統(tǒng)調(diào)用創(chuàng)建socket,sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));tcpdump在socket創(chuàng)建過程中創(chuàng)建packet_type(struct packet_type),并掛載到全局的ptype_all鏈表上。(同時在packet_type設(shè)置回調(diào)函數(shù)packet_rcv
網(wǎng)絡(luò)收包/發(fā)包時,會在各自的處理函數(shù)(收包時:__netif_receive_skb_core,發(fā)包時:dev_queue_xmit_nit)中遍歷ptype_all鏈表,并同時執(zhí)行其回調(diào)函數(shù),這里tcpdump的注冊的回調(diào)函數(shù)就是packet_rcv
packet_rcv函數(shù)中會將用戶設(shè)置的過濾條件,通過BPF進行過濾,并將過濾的數(shù)據(jù)包添加到接收隊列中
應(yīng)用層調(diào)用recvfrom 。 PF_PACKET 協(xié)議簇模塊調(diào)用packet_recvmsg 將接收隊列中的數(shù)據(jù)copy應(yīng)用層,到此將數(shù)據(jù)包捕獲到。
總結(jié)
本文主要從tcpdump抓包時調(diào)用的libpcap庫開始梳理,從socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))進入系統(tǒng)調(diào)用,再從內(nèi)核角度對Tcpdump在收包和發(fā)包的流程分析了一遍,其實還有一個重點:BPF的過濾過程,如下源碼所示:run_filter(skb, sk, snaplen),下次文章將對BPF的過濾過程進行一些分析。
