0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

tcpdump如何實現(xiàn)抓內(nèi)核態(tài)的包

Linux愛好者 ? 來源:開發(fā)內(nèi)功修煉 ? 作者:張彥飛allen ? 2021-10-08 10:34 ? 次閱讀

今天聊聊大家工作中經(jīng)常用到的 tcpdump。

網(wǎng)絡(luò)包的發(fā)送和接收過程中,絕大部分的工作都是在內(nèi)核態(tài)完成的。那么問題來了,我們常用的運行在用戶態(tài)的程序 tcpdump 是那如何實現(xiàn)抓到內(nèi)核態(tài)的包的呢?有的同學(xué)知道 tcpdump 是基于 libpcap 的,那么 libpcap 的工作原理又是啥樣的呢。如果讓你裸寫一個抓包程序,你有沒有思路?

按照飛哥的風(fēng)格,不搞到最底層的原理咱是不會罷休的。所以我對相關(guān)的源碼進行了深入分析。通過本文,你將徹底搞清楚了以下這幾個問題。

tcpdump 是如何工作的?

netfilter 過濾的包 tcpdump 是否可以抓的到?

讓你自己寫一個抓包程序的話該如何下手?

借助這幾個問題,我們來展開今天的探索之旅!

一、網(wǎng)絡(luò)包接收過程

在圖解Linux網(wǎng)絡(luò)包接收過程一文中我們詳細(xì)介紹了網(wǎng)絡(luò)包是如何從網(wǎng)卡到達用戶進程中的。這個過程我們可以簡單用如下這個圖來表示。

772732fa-239b-11ec-82a8-dac502259ad0.png

找到 tcpdump 抓包點

我們在網(wǎng)絡(luò)設(shè)備層的代碼里找到了 tcpdump 的抓包入口。在 __netif_receive_skb_core 這個函數(shù)里會遍歷 ptype_all 上的協(xié)議。還記得上文中我們提到 tcpdump 在 ptype_all 上注冊了虛擬協(xié)議。這時就能執(zhí)行的到了。來看函數(shù):

//file: net/core/dev.cstatic int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)

{

。..。..

//遍歷 ptype_all (tcpdump 在這里掛了虛擬協(xié)議)

list_for_each_entry_rcu(ptype, &ptype_all, list) {

if (!ptype-》dev || ptype-》dev == skb-》dev) {

if (pt_prev)

ret = deliver_skb(skb, pt_prev, orig_dev);

pt_prev = ptype;

}

}

}

在上面函數(shù)中遍歷 ptype_all,并使用 deliver_skb 來調(diào)用協(xié)議中的回調(diào)函數(shù)。

//file: net/core/dev.c static inline int deliver_skb(。..)

{

return pt_prev-》func(skb, skb-》dev, pt_prev, orig_dev);

}

對于 tcpdump 來說,就會進入 packet_rcv 了(后面我們再說為啥是進入這個函數(shù))。這個函數(shù)在 net/packet/af_packet.c 文件中。

//file: net/packet/af_packet.cstatic int packet_rcv(struct sk_buff *skb, 。..)

{

__skb_queue_tail(&sk-》sk_receive_queue, skb);

。..。..

}

可見 packet_rcv 把收到的 skb 放到了當(dāng)前 packet socket 的接收隊列里了。這樣后面調(diào)用 recvfrom 的時候就可以獲取到所抓到的包??!

再找 netfilter 過濾點

為了解釋我們開篇中提到的問題,這里我們再稍微到協(xié)議層中多看一些。在 ip_rcv 中我們找到了一個 netfilter 相關(guān)的執(zhí)行邏輯。

//file: net/ipv4/ip_input.cint ip_rcv(。..)

{

。..。..

return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,

ip_rcv_finish);

}

如果你用 NF_HOOK 作為關(guān)鍵詞來搜索,還能搜到不少 netfilter 的過濾點。不過所有的過濾點都是位于 IP 協(xié)議層的。

在接收包的過程中,數(shù)據(jù)包是先經(jīng)過網(wǎng)絡(luò)設(shè)備層然后才到協(xié)議層的。

77693a4c-239b-11ec-82a8-dac502259ad0.png

那么我們開篇中的一個問題就有了答案了。假如我們設(shè)置了 netfilter 規(guī)則,在接收包的過程中,工作在網(wǎng)絡(luò)設(shè)備層的 tcpdump 先開始工作。還沒等 netfilter 過濾,tcpdump 就抓到包了!

所以,在接收包的過程中,netfilter 過濾并不會影響 tcpdump 的抓包!

二、網(wǎng)絡(luò)包發(fā)送過程

我們接著再來看網(wǎng)絡(luò)包發(fā)送過程。在25 張圖,一萬字,拆解 Linux 網(wǎng)絡(luò)包發(fā)送過程一文中,我們詳細(xì)描述過網(wǎng)絡(luò)包的發(fā)送過程。發(fā)送過程可以匯總成簡單的一張圖。

77a4a190-239b-11ec-82a8-dac502259ad0.png

找到 netfilter 過濾點

在發(fā)送的過程中,同樣是在 IP 層進入各種 netfilter 規(guī)則的過濾。

//file: net/ipv4/ip_output.c int ip_local_out(struct sk_buff *skb)

{

//執(zhí)行 netfilter 過濾

err = __ip_local_out(skb);

}

int __ip_local_out(struct sk_buff *skb)

{

。..。..

return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,

skb_dst(skb)-》dev, dst_output);

}

在這個文件中,還能看到若干處 netfilter 過濾邏輯。

找到 tcpdump 抓包點

發(fā)送過程在協(xié)議層處理完畢到達網(wǎng)絡(luò)設(shè)備層的時候,也有 tcpdump 的抓包點。

//file: net/core/dev.cint dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,

struct netdev_queue *txq)

{

。..

if (!list_empty(&ptype_all))

dev_queue_xmit_nit(skb, dev);

}

static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)

{

list_for_each_entry_rcu(ptype, &ptype_all, list) {

if ((ptype-》dev == dev || !ptype-》dev) &&

(!skb_loop_sk(ptype, skb))) {

if (pt_prev) {

deliver_skb(skb2, pt_prev, skb-》dev);

pt_prev = ptype;

continue;

}

。..。..

}

}

}

在上述代碼中我們看到,在 dev_queue_xmit_nit 中遍歷 ptype_all 中的協(xié)議,并依次調(diào)用 deliver_skb。這就會執(zhí)行到 tcpdump 掛在上面的虛擬協(xié)議。

在網(wǎng)絡(luò)包的發(fā)送過程中,和接收過程恰好相反,是協(xié)議層先處理、網(wǎng)絡(luò)設(shè)備層后處理。

77da513c-239b-11ec-82a8-dac502259ad0.png

如果 netfilter 設(shè)置了過濾規(guī)則,那么在協(xié)議層就直接過濾掉了。在下層網(wǎng)絡(luò)設(shè)備層工作的 tcpdump 將無法再捕獲到該網(wǎng)絡(luò)包。

三、TCPDUMP 啟動

前面兩小節(jié)我們說到了內(nèi)核收發(fā)包都通過遍歷 ptype_all 來執(zhí)行抓包的。那么我們現(xiàn)在來看看用戶態(tài)的 tcpdump 是如何掛載協(xié)議到內(nèi) ptype_all 上的。

我們通過 strace 命令我們抓一下 tcpdump 命令的系統(tǒng)調(diào)用,顯示結(jié)果中有一行 socket 系統(tǒng)調(diào)用。Tcpdump 秘密的源頭就藏在這行對 socket 函數(shù)的調(diào)用里。

# strace tcpdump -i eth0

socket(AF_PACKET, SOCK_RAW, 768)

。..。..

socket 系統(tǒng)調(diào)用的第一個參數(shù)表示創(chuàng)建的 socket 所屬的地址簇或者協(xié)議簇,取值以 AF 或者 PF 開頭。在 Linux 里,支持很多種協(xié)議族,在 include/linux/socket.h 中可以找到所有的定義。這里創(chuàng)建的是 packet 類型的 socket。

協(xié)議族和地址族:每一種協(xié)議族都有其對應(yīng)的地址族。比如 IPV4 的協(xié)議族定義叫 PF_INET,其地址族的定義是 AF_INET。它們是一一對應(yīng)的,而且值也完全一樣,所以經(jīng)常混用。

//file: include/linux/socket.h#define AF_UNSPEC 0#define AF_UNIX 1 /* Unix domain sockets */#define AF_LOCAL 1 /* POSIX name for AF_UNIX */#define AF_INET 2 /* Internet IP Protocol */#define AF_INET6 10 /* IP version 6 */#define AF_PACKET 17 /* Packet family */

。..。..

另外上面第三個參數(shù) 768 代表的是 ETH_P_ALL,socket.htons(ETH_P_ALL) = 768。

我們來展開看這個 packet 類型的 socket 創(chuàng)建的過程中都干了啥,找到 socket 創(chuàng)建源碼。

//file: net/socket.c

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

{

。..。..

retval = sock_create(family, type, protocol, &sock);

}

int __sock_create(struct net *net, int family, int type, 。..)

{

。..。..

pf = rcu_dereference(net_families[family]);

err = pf-》create(net, sock, protocol, kern);

}

在 __sock_create 中,從 net_families 中獲取了指定協(xié)議。并調(diào)用了它的 create 方法來完成創(chuàng)建。

net_families 是一個數(shù)組,除了我們常用的 PF_INET( ipv4 ) 外,還支持很多種協(xié)議族。比如 PF_UNIX、PF_INET6(ipv6)、PF_PACKET等等。每一種協(xié)議族在 net_families 數(shù)組的特定位置都可以找到其 family 類型。在這個 family 類型里,成員函數(shù) create 指向該協(xié)議族的對應(yīng)創(chuàng)建函數(shù)。

780b9012-239b-11ec-82a8-dac502259ad0.png

根據(jù)上圖,我們看到對于 packet 類型的 socket,pf-》create 實際調(diào)用到的是 packet_create 函數(shù)。我們進入到這個函數(shù)中來一探究竟,這是理解 tcpdump 工作原理的關(guān)鍵!

//file: packet/af_packet.cstatic int packet_create(struct net *net, struct socket *sock, int protocol,

int kern)

{

。..

po = pkt_sk(sk);

po-》prot_hook.func = packet_rcv;

//注冊鉤子

if (proto) {

po-》prot_hook.type = proto;

register_prot_hook(sk);

}

}

static void register_prot_hook(struct sock *sk)

{

struct packet_sock *po = pkt_sk(sk);

dev_add_pack(&po-》prot_hook);

}

在 packet_create 中設(shè)置回調(diào)函數(shù)為 packet_rcv,再通過 register_prot_hook =》 dev_add_pack 完成注冊。注冊完后,是在全局協(xié)議 ptype_all 鏈表中添加了一個虛擬的協(xié)議進來。

78583390-239b-11ec-82a8-dac502259ad0.png

我們再來看下 dev_add_pack 是如何注冊協(xié)議到 ptype_all 中的?;仡櫸覀冮_頭看到的 socket 函數(shù)調(diào)用,第三個參數(shù) proto 傳入的是 ETH_P_ALL。那 dev_add_pack 其實最后是把 hook 函數(shù)添加到了 ptype_all 里了,代碼如下。

//file: net/core/dev.cvoid dev_add_pack(struct packet_type *pt)

{

struct list_head *head = ptype_head(pt);

list_add_rcu(&pt-》list, head);

}

static inline struct list_head *ptype_head(const struct packet_type *pt)

{

if (pt-》type == htons(ETH_P_ALL))

return &ptype_all;

else

return &ptype_base[ntohs(pt-》type) & PTYPE_HASH_MASK];

}

我們整篇文章都以 ETH_P_ALL 為例,但其實有的時候也會有其它情況。在別的情況下可能會注冊協(xié)議到 ptype_base 里了,而不是 ptype_all。同樣, ptype_base 中的協(xié)議也會在發(fā)送和接收的過程中被執(zhí)行到。

總結(jié):tcpdump 啟動的時候內(nèi)部邏輯其實很簡單,就是在 ptype_all 中注冊了一個虛擬協(xié)議而已。

四、總結(jié)

現(xiàn)在我們再回頭看開篇提到的幾個問題。

1. tcpdump是如何工作的

用戶態(tài) tcpdump 命令是通過 socket 系統(tǒng)調(diào)用,在內(nèi)核源碼中用到的 ptype_all 中掛載了函數(shù)鉤子上去。無論是在網(wǎng)絡(luò)包接收過程中,還是在發(fā)送過程中,都會在網(wǎng)絡(luò)設(shè)備層遍歷 ptype_all 中的協(xié)議,并執(zhí)行其中的回調(diào)。tcpdump 命令就是基于這個底層原理來工作的。

2. netfilter 過濾的包 tcpdump是否可以抓的到

關(guān)于這個問題,得分接收和發(fā)送過程分別來看。在網(wǎng)絡(luò)包接收的過程中,由于 tcpdump 近水樓臺先得月,所以完全可以捕獲到命中 netfilter 過濾規(guī)則的包。

77693a4c-239b-11ec-82a8-dac502259ad0.png

但是在發(fā)送的過程中,恰恰相反。網(wǎng)絡(luò)包先經(jīng)過協(xié)議層,這時候被 netfilter 過濾掉的話,底層工作的 tcpdump 還沒等看見就啥也沒了。

77da513c-239b-11ec-82a8-dac502259ad0.png

3. 讓你自己寫一個抓包程序的話該如何下手

如果你想自己寫一段類似 tcpdump 的抓包程序的話,使用 packet socket 就可以了。我用 c 寫了一段抓包,并且解析源 IP 和目的 IP 的簡單 demo。

源碼地址:https://github.com/yanfeizhang/coder-kung-fu/blob/main/tests/network/test04/main.c

編譯一下,注意運行需要 root 權(quán)限。

# gcc -o main main.c# 。/main

運行結(jié)果預(yù)覽如下。

78f00468-239b-11ec-82a8-dac502259ad0.png

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1372

    瀏覽量

    40300
  • 網(wǎng)絡(luò)
    +關(guān)注

    關(guān)注

    14

    文章

    7570

    瀏覽量

    88833

原文標(biāo)題:用戶態(tài) tcpdump 如何實現(xiàn)抓到內(nèi)核網(wǎng)絡(luò)包的?

文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Linux運維必備技能:手把手教你用tcpdump精準(zhǔn)

    簡介 網(wǎng)絡(luò)數(shù)據(jù)截獲分析工具。支持針對網(wǎng)絡(luò)層、協(xié)議、主機、網(wǎng)絡(luò)或端口的過濾。并提供and、or、not等邏輯語句幫助去除無用的信息。 tcpdump - dump traffic on a
    的頭像 發(fā)表于 12-24 11:20 ?455次閱讀

    了解和使用GNU Radio軟件的功能并制作藍牙

    制作一個FM receiver來加深對GNU Radio的了解。在這一小節(jié)中我們將更加深入的了解和使用GNU Radio軟件的功能,并制作一個簡單的藍牙器。 GNU Radio使用 Bits的打包
    的頭像 發(fā)表于 11-15 16:10 ?515次閱讀
    了解和使用GNU Radio軟件的功能并制作藍牙<b class='flag-5'>抓</b><b class='flag-5'>包</b>器

    CentOS中使用tcpdump

    CentOS中使用tcpdump
    的頭像 發(fā)表于 10-28 14:48 ?254次閱讀

    一種利用wireshark對遠(yuǎn)程服務(wù)器/路由器網(wǎng)絡(luò)方法

    一種利用wireshark對遠(yuǎn)程服務(wù)器/路由器網(wǎng)絡(luò)方法
    的頭像 發(fā)表于 09-21 08:03 ?2995次閱讀
    一種利用wireshark對遠(yuǎn)程服務(wù)器/路由器網(wǎng)絡(luò)<b class='flag-5'>抓</b><b class='flag-5'>包</b>方法

    linux驅(qū)動程序如何加載進內(nèi)核

    在Linux系統(tǒng)中,驅(qū)動程序是內(nèi)核與硬件設(shè)備之間的橋梁。它們允許內(nèi)核與硬件設(shè)備進行通信,從而實現(xiàn)對硬件設(shè)備的控制和管理。 驅(qū)動程序的編寫 驅(qū)動程序的編寫是Linux驅(qū)動開發(fā)的基礎(chǔ)。在編寫驅(qū)動程序之前
    的頭像 發(fā)表于 08-30 15:02 ?486次閱讀

    態(tài)輸出門可以實現(xiàn)線與功能嗎

    態(tài)輸出門(Tri-State Output Gate)是一種特殊類型的邏輯門,它具有三個狀態(tài):高電平、低電平和高阻抗?fàn)顟B(tài)(也稱為高阻抗或浮空狀態(tài))。這種門在數(shù)字電路設(shè)計中非常有用,因為它可以實現(xiàn)
    的頭像 發(fā)表于 07-30 15:32 ?1788次閱讀

    經(jīng)典藍牙解析說明

    在無線通信協(xié)議的開發(fā)過程中,器是工程師們不可或缺的工具。掌握器的使用,就如同擁有了能夠洞察無線電波的“火眼金睛”。這不僅使我們能夠驗證發(fā)出的數(shù)據(jù)
    的頭像 發(fā)表于 07-24 09:04 ?2007次閱讀
    經(jīng)典藍牙<b class='flag-5'>抓</b><b class='flag-5'>包</b>解析說明

    AirKiss配網(wǎng)完成后,向10000端口發(fā)送UDP廣播的長度疑問求解

    ;random。實際發(fā)現(xiàn)設(shè)備會發(fā)送7個字節(jié),除了ack->random外還附帶有芯片本身的MAC地址。實際測試發(fā)現(xiàn)微信軟件確實會正確識別并跳轉(zhuǎn)到局域網(wǎng)設(shè)備發(fā)現(xiàn)頁面。 請確認(rèn)這樣是否有
    發(fā)表于 07-19 07:47

    ESP8266 UDP數(shù)據(jù)具有額外的字節(jié),為什么?

    數(shù)據(jù)長度字段正確無誤,不會反映額外的字節(jié)。 這是 Linux 接收器站點的 tcpdump,當(dāng)發(fā)送 8 個字節(jié)時(ESP 是 IP 地址 192.168.1.137)。接收器只是回顯它接收到的內(nèi)容
    發(fā)表于 07-18 06:59

    TTL三態(tài)輸出門能否實現(xiàn)“線與”?為什么?

    TTL三態(tài)輸出門是一種特殊的數(shù)字邏輯門,它具有高電平、低電平和高阻抗(三態(tài))三種輸出狀態(tài)。
    的頭像 發(fā)表于 05-28 16:14 ?4290次閱讀

    STM8單片機的IO口是否可實現(xiàn)態(tài):輸出高電平、低電平、高阻態(tài)?

    大家好: 請教一下大家,STM8單片機的IO口是否可實現(xiàn)態(tài):輸出高電平、低電平、高阻態(tài)。
    發(fā)表于 05-07 07:07

    使用nucleo_f767做USB CDC的測試,bus hound不到USB的怎么解決?

    現(xiàn)在使用nucleo_f767做USB CDC的測試。驅(qū)動一直有問題,所以想通過bus hound查看一下枚舉過程是不是有問題。但bus hound一直不到任何數(shù)據(jù)。在 插入usb的時候,沒有數(shù)據(jù)產(chǎn)生。請問各位有什么好辦法嗎?謝謝。
    發(fā)表于 04-30 07:00

    OpenBSD 7.5發(fā)布,新增內(nèi)核強化、安全升級、預(yù)裝軟件等諸多功能

    首先,在內(nèi)核層面進行優(yōu)化加強,通過更有效的CPU緩存調(diào)用進行處理性能提升;其次,在SMP下對多線程UDP數(shù)據(jù)傳送、并發(fā)訪問應(yīng)用等功能有了顯著提高;
    的頭像 發(fā)表于 04-08 14:58 ?727次閱讀

    tcpdump命令介紹

    1.命令簡介tcpdump 是一款類 Unix/Linux 環(huán)境下的包工具,允許用戶截獲和顯示發(fā)送或收到的網(wǎng)絡(luò)數(shù)據(jù)。tcpdump 是一個在 BSD 許可證下發(fā)布的自由軟件。 2.
    發(fā)表于 04-08 06:02

    DPDK在AI驅(qū)動的高效數(shù)據(jù)處理應(yīng)用

    傳統(tǒng)的數(shù)據(jù)處理方式是數(shù)據(jù)先到內(nèi)核最后再到用戶層進行處理。這種方式會增加額外的延遲和CPU開銷,嚴(yán)重影響數(shù)據(jù)處理的性能。 DPDK 繞過內(nèi)核
    的頭像 發(fā)表于 02-25 11:28 ?953次閱讀
    DPDK在AI驅(qū)動的高效數(shù)據(jù)<b class='flag-5'>包</b>處理應(yīng)用