<rp id="y59xa"></rp>

    1. 您好:歡迎來到大同基礎教育網!
       您的位置:首頁 >> 專題報道 >> 網絡安全教育

      捕獲TCP/IP協議棧數據包的原理

      作者:徐毅森 來源: 發表日期:2021-05-13 訪問數:0 

      捕獲TCP/IP協議棧數據包的原理

      wireshark或tcpdump相信大家都用過,這些工具看起來都很酷,因為我們平時都是在界面看到應用層的數據,這些工具居然可以讓我們看到tcp/ip協議棧每層的數據。

      本文轉載自微信公眾號「編程雜技」,作者 theanarkh   。轉載本文請聯系編程雜技公眾號。

      wireshark或tcpdump相信大家都用過,這些工具看起來都很酷,因為我們平時都是在界面看到應用層的數據,這些工具居然可以讓我們看到tcp/ip協議棧每層的數據。本文介紹一下查看tcp/ip協議棧數據的方法。并實現一個簡陋的sniffer,通過nodejs暴露出來使用。我們先看實現。

      1. #include <stdio.h> 
      2. #include <errno.h>  
      3. #include <unistd.h> 
      4. #include <sys/socket.h> 
      5. #include <sys/types.h>   
      6. #include <linux/in.h> 
      7. #include <linux/if_ether.h> 
      8. #include <stdlib.h> 
      9. #include <node_api.h> 
      10. #define DATA_LEN 500 
      11.  
      12. static napi_value start(napi_env env, napi_callback_info info) { 
      13.   int sockfd; 
      14.   int bytes; 
      15.   char data[DATA_LEN]; 
      16.   unsigned char *ipHeader; 
      17.   unsigned char *macHeader; 
      18.   unsigned char *transportHeader; 
      19.   // 對ETH_P_IP協議的數據包感興趣,PF_PACKET在早期內核是AF_INET 
      20.   sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); 
      21.   if (sockfd < 0) { 
      22.     printf("創建socket錯誤"); 
      23.     exit(1); 
      24.   } 
      25.  
      26.   while (1) { 
      27.     bytes = recvfrom(sockfd,data,DATA_LEN,0,NULL,NULL); 
      28.     printf("讀到字節數:%d\n",bytes); 
      29.     macHeader = data; 
      30.     printf("MAC報文----------\n"); 
      31.     printf("源Mac地址: %02x:%02x:%02x:%02x:%02x:%02x\n"
      32.            macHeader[0],macHeader[1],macHeader[2], 
      33.            macHeader[3],macHeader[4],macHeader[5]); 
      34.     printf("目的Mac地址: %02x:%02x:%02x:%02x:%02x:%02x\n"
      35.            macHeader[6],macHeader[7],macHeader[8], 
      36.            macHeader[9],macHeader[10],macHeader[11]); 
      37.     printf("上層協議: %04x\n"
      38.            (macHeader[12] << 8) + macHeader[13]); 
      39.     // 跳過Mac頭 
      40.     ipHeader = data + 6 + 6 + 2; 
      41.     printf("IP報文--------\n"); 
      42.     printf("ip協議版本:%d\n"
      43.              (ipHeader[0] & 0xF0) >> 4);  
      44.     int ipHeaderLen = (ipHeader[0] & 0x0F) << 2; 
      45.     printf("首部長度:%d\n"
      46.          ipHeaderLen); 
      47.     printf("區分服務:%d\n"
      48.          ipHeader[1]);      
      49.     printf("總長度:%d\n"
      50.          (ipHeader[2]<<8)+ipHeader[3]);  
      51.     printf("標識:%d\n"
      52.          (ipHeader[4]<<8)+ipHeader[5]); 
      53.     printf("標志:%d\n"
      54.          (ipHeader[6] & 0xE0) >> 5);      
      55.     printf("片偏移:%d\n"
      56.          (ipHeader[6] & 0x11) + ipHeader[7]);   
      57.     printf("TTL:%d\n"
      58.          ipHeader[8]); 
      59.     printf("上層協議:%d\n"
      60.          ipHeader[9]);      
      61.     printf("首部校驗和:%x%x\n"
      62.          ipHeader[10]+ipHeader[11]);                           
      63.     printf("源ip:%d.%d.%d.%d\n"
      64.          ipHeader[12],ipHeader[13], 
      65.          ipHeader[14],ipHeader[15]); 
      66.     printf("目的ip:%d.%d.%d.%d\n"
      67.          ipHeader[16],ipHeader[17], 
      68.          ipHeader[18],ipHeader[19]); 
      69.  
      70.     transportHeader = ipHeader + ipHeaderLen; 
      71.     printf("傳輸層報文-----------\n"); 
      72.     printf("源端口:%d\n"
      73.          (transportHeader[0]<<8)+transportHeader[1]); 
      74.     printf("目的端口:%d\n"
      75.          (transportHeader[2]<<8)+transportHeader[3]); 
      76.     printf("序列號:%ud%ud%ud%ud\n"
      77.          transportHeader[4],transportHeader[5],transportHeader[6],transportHeader[7]); 
      78.     printf("確認號:%ud\n"
      79.          (transportHeader[8]<<24)+(transportHeader[9]<<16)+(transportHeader[10]<<8)+(transportHeader[11])); 
      80.     printf("傳輸層首部長度:%d\n"
      81.         ((transportHeader[12] & 0xF0) >> 4) * 4); 
      82.     printf("FIN:%d\n"
      83.         transportHeader[13] & 0x01); 
      84.     printf("SYN:%d\n"
      85.         (transportHeader[13] & 0x02) >> 1); 
      86.     printf("RST:%d\n"
      87.         (transportHeader[13] & 0x04) >> 2); 
      88.     printf("PSH:%d\n"
      89.         (transportHeader[13] & 0x08) >> 3); 
      90.     printf("ACK:%d\n"
      91.         (transportHeader[13] & 0x016) >> 4); 
      92.     printf("URG:%d\n"
      93.         (transportHeader[13] & 0x32) >> 5); 
      94.     printf("窗口大小:%d\n"
      95.         (transportHeader[14] << 8) + transportHeader[15]); 
      96.     }} 
      97.  
      98. napi_value Init(napi_env env, napi_value exports) { 
      99.   napi_value func; 
      100.   napi_create_function(env, 
      101.                     NULL
      102.                     NAPI_AUTO_LENGTH, 
      103.                     start, 
      104.                     NULL
      105.                     &func); 
      106.   napi_set_named_property(env, exports, "start", func); 
      107.   return exports; 
      108.  
      109. NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) 

      我們看到實現并不復雜,首先創建一個socket,然后接收socket上面的數據進行分析就行。上面的代碼可以捕獲到所有發給本機的tcp/ip包,下面我們看看效果(有些字段還沒有仔細處理)。

      下面我們來看看底層的實現(2.6.13.1內核)。我們從socket函數的實現開始分析。

      1. asmlinkage long sys_socket(int family, int type, int protocol){ 
      2.   int retval; 
      3.   struct socket *sock; 
      4.   // 創建一個socket 
      5.   retval = sock_create(family, type, protocol, &sock); 
      6.   // 返回文件描述符給用戶 
      7.   retval = sock_map_fd(sock); 

      接著看sock_create。

      1. int sock_create(int family, int type, int protocol, struct socket **res){ 
      2.   return __sock_create(family, type, protocol, res, 0); 
      3.  
      4. static int __sock_create(int family, int type, int protocol, struct socket **res, int kern){ 
      5.   int err; 
      6.   struct socket *sock; 
      7.   // 分配一個socket 
      8.   if (!(sock = sock_alloc())) { 
      9.     // ... 
      10.   } 
      11.   // socket類型 
      12.   sock->type  = type; 
      13.   err = -EAFNOSUPPORT; 
      14.   // 根據協議簇拿到對應的函數集,然后調用create函數 
      15.   if ((err = net_families[family]->create(sock, protocol)) < 0) 
      16.     goto out_module_put; 

      我們看到__sock_create的邏輯很簡單,根據協議簇拿到對應的函數集,然后執行其create函數。我們看看PF_PACKET協議簇對應的函數集。PF_PACKET協議簇通過packet_init注冊了對應的函數集。

      1. static int __init packet_init(void){ 
      2.   sock_register(&packet_family_ops); 
      3.  
      4. static struct net_proto_family packet_family_ops = { 
      5.   .family = PF_PACKET, 
      6.   .create = packet_create, 
      7.   .owner  = THIS_MODULE, 
      8. }; 

      我們看到create函數的值是packet_create。

      1. static int packet_create(struct socket *sock, int protocol){ 
      2.   struct sock *sk; 
      3.   struct packet_sock *po; 
      4.   int err; 
      5.   // 分配一個packet_sock結構體 
      6.   sk = sk_alloc(PF_PACKET, GFP_KERNEL, &packet_proto, 1); 
      7.   // 賦值函數集 
      8.   sock->ops = &packet_ops; 
      9.   // 關聯socket和sock 
      10.   sock_init_data(sock, sk); 
      11.   // 拿到一個packet_sock結構體,第一個字段是sock結構體(struct packet_sock *po) 
      12.   po = pkt_sk(sk); 
      13.   sk->sk_family = PF_PACKET; 
      14.   // 接收數據包的函數 
      15.   po->prot_hook.func = packet_rcv; 
      16.   po->prot_hook.af_packet_priv = sk; 
      17.  
      18.   if (protocol) { 
      19.     po->prot_hook.type = protocol; 
      20.     dev_add_pack(&po->prot_hook); 
      21.     sock_hold(sk); 
      22.     po->running = 1; 
      23.   } 

      packet_create首先創建了一個packet_sock結構體并初始化,最后調用dev_add_pack。

      1. static struct list_head ptype_base[16];  
      2.  
      3. void dev_add_pack(struct packet_type *pt){ 
      4.   int hash; 
      5.  
      6.   spin_lock_bh(&ptype_lock); 
      7.   if (pt->type == htons(ETH_P_ALL)) { 
      8.     netdev_nit++; 
      9.     list_add_rcu(&pt->list, &ptype_all); 
      10.   } else { 
      11.     hash = ntohs(pt->type) & 15; 
      12.     list_add_rcu(&pt->list, &ptype_base[hash]); 
      13.   } 
      14.   spin_unlock_bh(&ptype_lock); 

      我們看到dev_add_pack的邏輯是往ptype_base對應的隊列加入一個節點。接著我們看看網卡收到數據包的時候是如何處理的。

      1. int netif_receive_skb(struct sk_buff *skb){ 
      2.   type = skb->protocol; 
      3.   list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) { 
      4.     if (ptype->type == type && 
      5.         (!ptype->dev || ptype->dev == skb->dev)) { 
      6.       if (pt_prev)  
      7.         ret = deliver_skb(skb, pt_prev); 
      8.       pt_prev = ptype; 
      9.     } 
      10.   } 
      11.   ret = pt_prev->func(skb, skb->dev, pt_prev); 

      netif_receive_skb的邏輯中會根據收到mac包中上層協議字段找到對應的處理函數,比如本文的packet。最后執行func。從剛才的create函數我們看到func的值是packet_rcv。

      1. static int packet_rcv(struct sk_buff *skb, struct net_device *dev,  struct packet_type *pt) { 
      2.   __skb_queue_tail(&sk->sk_receive_queue, skb); 
      3.   sk->sk_data_ready(sk, skb->len); 

      packet_rcv首先把收到的數據包插入socket的接收隊列,然后調用sk_data_ready通知socket,對應函數是sock_def_readable。

      1. static void sock_def_readable(struct sock *sk, int len){ 
      2.   if (sk->sk_sleep && waitqueue_active(sk->sk_sleep)) 
      3.     wake_up_interruptible(sk->sk_sleep); 

      sock_def_readable會喚醒阻塞在該socket的進程。那么這個隊列里有什么呢?我們回到文章開始的代碼,我們創建socket后阻塞在recvfrom。recvfrom通過層層調用最后執行對應函數集的recvmsg。

      1. static int packet_recvmsg(struct kiocb *iocb, struct socket *sock, 
      2.         struct msghdr *msg, size_t len, int flags){ 
      3.  
      4.   struct sk_buff *skb; 
      5.   skb=skb_recv_datagram(sk,flags,flags&MSG_DONTWAIT,&err); 

      packet_recvmsg從socket的接收隊列取出一個數據包,我們看看skb_recv_datagram。

      1. struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, 
      2.           int noblock, int *err){ 
      3.  
      4.   struct sk_buff *skb; 
      5.   long timeo; 
      6.   /* 
      7.     static inline long sock_rcvtimeo(const struct sock *sk, int noblock) 
      8.     { 
      9.       return noblock ? 0 : sk->sk_rcvtimeo; 
      10.     } 
      11.     獲取沒有數據包時等待的超時時間 
      12.   */ 
      13.   timeo = sock_rcvtimeo(sk, noblock); 
      14.  
      15.   do { 
      16.     skb = skb_dequeue(&sk->sk_receive_queue); 
      17.     // 有則返回 
      18.     if (skb) 
      19.       return skb; 
      20.  
      21.     // 沒有 
      22.     error = -EAGAIN; 
      23.     // 不等待則直接返回 
      24.     if (!timeo) 
      25.       goto no_packet; 
      26.   // 否則等待一段時間 
      27.   } while (!wait_for_packet(sk, err, &timeo)); 

      我們看到沒有數據包的時候會等待一段時間,我們看看這個時間是多少。

      1. sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT; 
      2. #define MAX_SCHEDULE_TIMEOUT  LONG_MAX 

      我們看到超時時間非常長,當然這個值我們可以通過setsockopt的SO_RCVTIMEO選項設置。接著我們看等待的邏輯wait_for_packet。

      1. #define DEFINE_WAIT(name)           \ 
      2.   wait_queue_t name = {           \ 
      3.     .private  = current,        \ 
      4.     .func   = autoremove_wake_function,   \ 
      5.     .task_list  = LIST_HEAD_INIT((name).task_list), \ 
      6.   } 
      7.  
      8. static int wait_for_packet(struct sock *sk, int *err, long *timeo_p){ 
      9.   DEFINE_WAIT(wait); 
      10.   prepare_to_wait_exclusive(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE); 
      11.   int error = 0; 
      12.   *timeo_p = schedule_timeout(*timeo_p); 
      13. out
      14.   finish_wait(sk->sk_sleep, &wait); 
      15.   return error 

      wait_for_packet首先把當前進程插入對應的等待隊列并修改進程狀態為非就緒(TASK_INTERRUPTIBLE)

      1. void fastcall prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state){ 
      2.   // 把當前進程插入等待隊列 
      3.   if (list_empty(&wait->task_list)) 
      4.     __add_wait_queue_tail(q, wait); 
      5.   // 修改進程狀態 
      6.   set_current_state(state); 

      接著執行進程調度schedule_timeout。

      1. fastcall signed long __sched schedule_timeout(signed long timeout){ 
      2.   struct timer_list timer; 
      3.   unsigned long expire; 
      4.   // 超時時間 
      5.   expire = timeout + jiffies; 
      6.   // 開啟定時器 
      7.   init_timer(&timer); 
      8.   timer.expires = expire; 
      9.   timer.data = (unsigned long) current
      10.   timer.function = process_timeout; 
      11.   // 啟動定時器 
      12.   add_timer(&timer); 
      13.   // 進程調度 
      14.   schedule(); 
      15.   timeout = expire - jiffies; 
      16.  
      17.  out
      18.   return timeout < 0 ? 0 : timeout; 

      以上就是實現捕獲tcp/ip協議棧數據包的底層原理。代碼倉庫https://github.com/theanarkh/node-sniffer

      上一篇新聞:

      下一篇新聞:

          

      舉辦單位:大同市電化教育館  

      地址:山西省大同市育才北路179號    郵箱:dtdjgwlb@163.com    聯系方式:0352-2537438

      技術支持:鄭州威科姆科技股份有限公司

      晉ICP備18001045號    晉公網安備 14020202000129號

      国产国语熟妇视频在线观看