• (转载)基于Linux C的socket抓包程序和Package分析


    转载自 https://blog.csdn.net/kleguan/article/details/27538031

    1. Linux抓包源程序

    OSI七层模型中,网卡工作在物理层和数据链路层的MAC子层。

    进行网络通信时,源主机通过socket(或其它)应用程序产生IP报文,经过各个OSI层层封装,数据包以Ethernet帧的形式进入物理层。Ethernet帧包含源主机地址、IP报文、目标地址(IP地址、端口号或映射的6字节MAC地址)和需要传送到目标主机的其它信息。

     

    目标的MAC地址是哪里来的呢?这牵扯到一个ARP协议(介乎于网络层和数据链路层的一个协议)。第一次传送某个目的IP地址的数据的时候,先会发出一个ARP包,其MAC的目标地址是广播地址,里面说到:"谁是xxx.xxx.xxx.xxx这个IP地址的主人?"因为是广播包,所有这个局域网的主机都收到了这个ARP请求。收到请求的主机将这个IP地址和自己的相比较,如果不相同就不予理会,如果相同就发出ARP响应包。这个IP地址的主机收到这个ARP请求包后回复的ARP响应里说到:"我是这个IP地址的主人"。这个包里面就包括了他的MAC地址。以后的给这个IP地址的帧的目标MAC地址就被确定了。

     

    就这样,以太网帧开始在数据链路层传播。Ethernet帧在链路层基于广播方式传播,即网段内的所有网卡都能观察该帧,但只有一个网卡通过对比6字节MAC地址发现与自己相符,然后它就接收该帧。而其它网卡则放弃该帧。(其它网卡也可以接受该帧,即实际的网络Sniffer,可进行信息窃取等操作)

    网卡得到Ethernet帧后,通过网络驱动程序和上层协议对其进行还原操作,即层层剥离报文头后将数据交由目标主机的socket(或其它)应用程序使用。

    如果我们需要原始的以太网帧,以便观察与目标主机进行通信的源主机信息,则可以通过建立基于PF_PACKETsocket应用程序实现。PF_PACKET协议簇允许应用程序直接获得网络驱动程序得到的数据帧信息。PF_PACKET支持SOCK_DGRAMSOCK_RAW两种socket类型,前者利用操作系统处理报文头,而后者则将以太网帧直接交由应用程序处理。需要注意到是,只有root权限用户才能使用PF_PACKET程序。

    下面的代码即可实现由应用程序直接获得以太网帧的需求。

    [cpp] view plain copy
     
    1. #include <unistd.h>  
    2. #include <sys/socket.h>  
    3. #include <sys/types.h>  
    4. #include <linux/if_ether.h>  
    5. #include <linux/in.h>  
    6.   
    7. #define BUFFER_MAX 2048  
    8.    
    9. int main(int argc, char *argv[]){  
    10.      int  SOCKET_SRC;  
    11.      char buf[BUFFER_MAX];  
    12.      int n_rd;  
    13.   
    14.      if( (SOCKET_SRC = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0 ){  
    15.          fprintf(stderr, "create socket error. ");  
    16.          exit(0);  
    17.      }  
    18.      while(1){  
    19.          n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);  
    20.          if (n_rd<46) {  
    21.              perror("recvfrom():");  
    22.             printf("Incomplete packet (errno is %d) ",  errno);  
    23.             close(SOCKET_SRC);  
    24.             exit(0);  
    25.          }  
    26.          /* An Ethernet frame was written to buf, frame analysis can be processed here */  
    27.          /* Termination control */  
    28.      }  
    29.      close(SOCKET_SRC);  
    30.      return 0;  
    31. }  

    2. 数据包(以太网帧)分析

    一个以太网帧(RFC894)的数据格式如下图所示。

    以太网帧(RFC894)格式

     

    程序通过执行一次

    [cpp] view plain copy
     
    1. n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);  

    就将上面一条以太网帧写入buf中。

    (1) 为了从buf中提取以太网报文头,我们可以定义如下结构体。

    [cpp] view plain copy
     
    1. typedef struct mac_frm_hdr {  
    2.      char dest_addr[6]; //destination MAC address shall be defined first.  
    3.      char src_addr[6];  
    4.      short type;  
    5.  }__attribute__((packed)) MAC_FRM_HDR;  


    定义该结构体时,需要注意以下几点。

    a.属性需按帧格式的出现顺序定义

    b.数据类型长度必须和帧中相应区域的长度相同。

    c. 使用__attribute__((packed))取消编译器自动优化对齐结构体,也是为了保证属性长度和帧中相应区域的长度相同。

    (2)为了提取IP报文头,根据IP报文头的格式,我们可定义如下结构体。

    [cpp] view plain copy
     
    1. typedef struct ip_hdr{  //header of IPV4  
    2.     #ifdef __LITTLE_ENDIAN_BIFIELD  
    3.         u_char ip_len:4, ip_ver:4;  
    4.     #else  
    5.         u_char ip_ver:4, ip_len:4;  
    6.     #endif  
    7.   
    8.     u_char  ip_tos;  
    9.     u_short ip_total_len;  
    10.     u_short ip_id;  
    11.     u_short ip_flags;  
    12.     u_char  ip_ttl;  
    13.     u_char  ip_protocol;  
    14.     u_short ip_chksum;  
    15.     u_int32 ip_src;  
    16.     u_int32 ip_dest;  
    17. }__attribute__((packed)) IP_HDR;  


    为保证各属性长度与IP报文头中一致,我们应该在定义该结构体前作如下声明。

    [cpp] view plain copy
     
    1. typedef int int32;  
    2. typedef unsigned int u_int32;  
    3. typedef unsigned char u_char;  
    4. typedef unsigned short u_short;  


    注意事项参考定义以太网帧结构体。

    (3)为了提取UDP/TCP报文头,可根据UDP/TCP报文头格式,定义相应结构体,这里不作赘述。

     

    下面是对以太网帧进行解析的具体代码。

    [cpp] view plain copy
     
    1. MAC_FRM_HDR *mac_hdr; //define a Ethernet frame header  
    2. IP_HDR *ip_hdr;       //define a IP header  
    3. char *tmp1, *tmp2;  
    4. int AND_LOGIC = 0xFF;  
    5.   
    6. mac_hdr = buf;  //buf is what we got from the socket program  
    7. ip_hdr = buf + sizeof(MAC_FRM_HDR);  
    8. //udp_hdr = buf + sizeof(MAC_FRM_HDR) + sizeof(IP_HDR); //if we want to analyses the UDP/TCP  
    9.   
    10. tmp1 = mac_hdr->src_addr;  
    11. tmp2 = mac_hdr->dest_addr;  
    12. /* print the MAC addresses of source and receiving host */  
    13. printf("MAC: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X==>" "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X",  
    14.             tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,  
    15.             tmp1[4]&AND_LOGIC, tmp1[5]&AND_LOGIC,  
    16.             tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC,  
    17.             tmp2[4]&AND_LOGIC, tmp2[5]&AND_LOGIC);  
    18.   
    19. tmp1 = (char*)&ip_hdr->ip_src;  
    20. tmp2 = (char*)&ip_hdr->ip_dest;  
    21. /* print the IP addresses of source and receiving host */  
    22. printf("IP: %d.%d.%d.%d => %d.%d.%d.%d",  
    23.              tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,  
    24.              tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC);  
    25. /* print the IP protocol which was used by the socket communication */  
    26. switch(ip_hdr->ip_protocol) {  
    27.          case IPPROTO_ICMP: LOGI("ICMP"); break;  
    28.          case IPPROTO_IGMP: LOGI("IGMP"); break;  
    29.          case IPPROTO_IPIP: LOGI("IPIP"); break;  
    30.          case IPPROTO_TCP:  
    31.      case IPPROTO_UDP:  
    32.                             LOGI("Protocol: %s", ip_hdr->ip_protocol == IPPROTO_TCP ? "TCP" : "UDP");  
    33.                             LOGI("Source port: %u, destination port: %u", udp_hdr->s_port, udp_hdr->d_port);  
    34.                             break;  
    35.          case IPPROTO_RAW: LOGI("RAW"); break;  
    36.          default: printf("Unknown, please query in inclued/linux/in.h "); break;  
    37. }  


    /*************************************************************************
        > Author: kleguan
        >  如用不当之处,欢迎指正

        >  转载请注明出处
       ************************************************************************/

  • 相关阅读:
    JavaSE-方法覆盖的注意事项
    underscore.js源码研究(4)
    underscore.js源码研究(3)
    移动端font-size适配方案
    控制台引入想要的库
    页面布局与编写(续3)
    underscore.js源码研究(2)
    全屏使用swiper.js过程中遇到的坑
    模块加载
    underscore.js源码研究(1)
  • 原文地址:https://www.cnblogs.com/gremount/p/8831271.html
Copyright © 2020-2023  润新知