偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

用戶態(tài) Tcpdump 如何實(shí)現(xiàn)抓到內(nèi)核網(wǎng)絡(luò)包的?

網(wǎng)絡(luò) 通信技術(shù)
在網(wǎng)絡(luò)包的發(fā)送和接收過(guò)程中,絕大部分的工作都是在內(nèi)核態(tài)完成的。那么問(wèn)題來(lái)了,我們常用的運(yùn)行在用戶態(tài)的程序 tcpdump 是那如何實(shí)現(xiàn)抓到內(nèi)核態(tài)的包的呢?

[[422515]]

本文轉(zhuǎn)載自微信公眾號(hào)「開發(fā)內(nèi)功修煉」,作者張彥飛allen。轉(zhuǎn)載本文請(qǐng)聯(lián)系開發(fā)內(nèi)功修煉公眾號(hào)。

大家好,我是飛哥!

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

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

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

tcpdump 是如何工作的?

  • netfilter 過(guò)濾的包 tcpdump 是否可以抓的到?
  • 讓你自己寫一個(gè)抓包程序的話該如何下手?
  • 借助這幾個(gè)問(wèn)題,我們來(lái)展開今天的探索之旅!

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

在圖解Linux網(wǎng)絡(luò)包接收過(guò)程一文中我們?cè)敿?xì)介紹了網(wǎng)絡(luò)包是如何從網(wǎng)卡到達(dá)用戶進(jìn)程中的。這個(gè)過(guò)程我們可以簡(jiǎn)單用如下這個(gè)圖來(lái)表示。

找到 tcpdump 抓包點(diǎn)

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

  1. //file: net/core/dev.c 
  2. static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc) 
  3.     ...... 
  4.     //遍歷 ptype_all (tcpdump 在這里掛了虛擬協(xié)議) 
  5.     list_for_each_entry_rcu(ptype, &ptype_all, list) { 
  6.         if (!ptype->dev || ptype->dev == skb->dev) { 
  7.             if (pt_prev) 
  8.                 ret = deliver_skb(skb, pt_prev, orig_dev); 
  9.             pt_prev = ptype; 
  10.         } 
  11.     } 

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

  1. //file: net/core/dev.c  
  2. static inline int deliver_skb(...) 
  3.  return pt_prev->func(skb, skb->dev, pt_prev, orig_dev); 

對(duì)于 tcpdump 來(lái)說(shuō),就會(huì)進(jìn)入 packet_rcv 了(后面我們?cè)僬f(shuō)為啥是進(jìn)入這個(gè)函數(shù))。這個(gè)函數(shù)在 net/packet/af_packet.c 文件中。

  1. //file: net/packet/af_packet.c 
  2. static int packet_rcv(struct sk_buff *skb, ...) 
  3.  __skb_queue_tail(&sk->sk_receive_queue, skb); 
  4.  ...... 

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

再找 netfilter 過(guò)濾點(diǎn)

為了解釋我們開篇中提到的問(wèn)題,這里我們?cè)偕晕⒌絽f(xié)議層中多看一些。在 ip_rcv 中我們找到了一個(gè) netfilter 相關(guān)的執(zhí)行邏輯。

  1. //file: net/ipv4/ip_input.c 
  2. int ip_rcv(...) 
  3.  ...... 
  4.  return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL
  5.          ip_rcv_finish); 

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

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

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

所以,在接收包的過(guò)程中,netfilter 過(guò)濾并不會(huì)影響 tcpdump 的抓包!

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

我們接著再來(lái)看網(wǎng)絡(luò)包發(fā)送過(guò)程。在25 張圖,一萬(wàn)字,拆解 Linux 網(wǎng)絡(luò)包發(fā)送過(guò)程一文中,我們?cè)敿?xì)描述過(guò)網(wǎng)絡(luò)包的發(fā)送過(guò)程。發(fā)送過(guò)程可以匯總成簡(jiǎn)單的一張圖。

找到 netfilter 過(guò)濾點(diǎn)

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

  1. //file: net/ipv4/ip_output.c   
  2. int ip_local_out(struct sk_buff *skb) 
  3.  //執(zhí)行 netfilter 過(guò)濾 
  4.  err = __ip_local_out(skb); 
  5.  
  6. int __ip_local_out(struct sk_buff *skb) 
  7.  ...... 
  8.  return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL
  9.          skb_dst(skb)->dev, dst_output); 

在這個(gè)文件中,還能看到若干處 netfilter 過(guò)濾邏輯。

找到 tcpdump 抓包點(diǎn)

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

  1. //file: net/core/dev.c 
  2. int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, 
  3.    struct netdev_queue *txq) 
  4.  ... 
  5.  if (!list_empty(&ptype_all)) 
  6.   dev_queue_xmit_nit(skb, dev); 
  7.  
  8. static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev) 
  9.  list_for_each_entry_rcu(ptype, &ptype_all, list) { 
  10.   if ((ptype->dev == dev || !ptype->dev) && 
  11.       (!skb_loop_sk(ptype, skb))) { 
  12.    if (pt_prev) { 
  13.     deliver_skb(skb2, pt_prev, skb->dev); 
  14.     pt_prev = ptype; 
  15.     continue
  16.    } 
  17.   ...... 
  18.   } 
  19.  }  

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

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

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

三、TCPDUMP 啟動(dòng)

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

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

  1. # strace tcpdump -i eth0 
  2. socket(AF_PACKET, SOCK_RAW, 768) 
  3. ...... 

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

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

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

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

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

  1. //file: net/socket.c 
  2. SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)  
  3.  ...... 
  4.  retval = sock_create(family, type, protocol, &sock);  
  5.  
  6. int __sock_create(struct net *net, int family, int type, ...) 
  7.  ...... 
  8.  pf = rcu_dereference(net_families[family]); 
  9.  err = pf->create(net, sock, protocol, kern); 

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

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

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

  1. //file: packet/af_packet.c 
  2. static int packet_create(struct net *net, struct socket *sock, int protocol, 
  3.     int kern) 
  4.  ... 
  5.  po = pkt_sk(sk); 
  6.  po->prot_hook.func = packet_rcv; 
  7.  
  8.  //注冊(cè)鉤子 
  9.  if (proto) { 
  10.   po->prot_hook.type = proto; 
  11.   register_prot_hook(sk); 
  12.  } 
  13.  
  14. static void register_prot_hook(struct sock *sk) 
  15.  struct packet_sock *po = pkt_sk(sk); 
  16.  dev_add_pack(&po->prot_hook); 

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

我們?cè)賮?lái)看下 dev_add_pack 是如何注冊(cè)協(xié)議到 ptype_all 中的。回顧我們開頭看到的 socket 函數(shù)調(diào)用,第三個(gè)參數(shù) proto 傳入的是 ETH_P_ALL。那 dev_add_pack 其實(shí)最后是把 hook 函數(shù)添加到了 ptype_all 里了,代碼如下。

  1. //file: net/core/dev.c 
  2. void dev_add_pack(struct packet_type *pt) 
  3.  struct list_head *head = ptype_head(pt); 
  4.  list_add_rcu(&pt->list, head); 
  5.  
  6. static inline struct list_head *ptype_head(const struct packet_type *pt) 
  7.  if (pt->type == htons(ETH_P_ALL)) 
  8.   return &ptype_all; 
  9.  else 
  10.   return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK]; 

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

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

四、總結(jié)

現(xiàn)在我們?cè)倩仡^看開篇提到的幾個(gè)問(wèn)題。

1. tcpdump是如何工作的

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

2. netfilter 過(guò)濾的包 tcpdump是否可以抓的到

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

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

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

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

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

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

  1. # gcc -o main main.c 
  2. # ./main  

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

 

最后,還是求再看,求轉(zhuǎn)發(fā)!

 

責(zé)任編輯:武曉燕 來(lái)源: 開發(fā)內(nèi)功修煉
相關(guān)推薦

2021-09-17 11:59:21

tcpdump網(wǎng)絡(luò)包Linux

2023-03-01 23:56:11

2023-03-10 14:56:37

Linuxconnect系統(tǒng)

2023-03-01 23:53:30

Linuxshutdown進(jìn)程

2021-12-20 09:53:51

用戶態(tài)內(nèi)核態(tài)應(yīng)用程序

2023-10-26 11:39:54

Linux系統(tǒng)CPU

2025-03-07 08:30:00

pwruLinux網(wǎng)絡(luò)包追蹤

2023-03-28 15:51:20

2023-03-06 15:43:56

2024-08-22 14:47:50

開源Linux網(wǎng)絡(luò)抓包工具

2009-07-16 09:02:38

LINUX 2.4.x網(wǎng)絡(luò)安全LINUX開發(fā)

2022-03-25 12:31:49

Linux根文件內(nèi)核

2021-08-31 07:54:24

TCPIP協(xié)議

2023-01-06 08:04:10

GPU容器虛擬化

2021-08-10 16:50:37

內(nèi)核內(nèi)存管理

2023-05-12 07:27:24

Linux內(nèi)核網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)

2017-08-16 16:20:01

Linux內(nèi)核態(tài)搶占用戶態(tài)搶占

2020-10-29 15:05:31

Linux網(wǎng)絡(luò)包代碼

2024-11-07 09:38:43

PodCAP特權(quán)

2022-07-01 08:14:28

Dubbo異步代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)