• 基于Linux C的socketEthereal程序和Package分析 (一个)


    

    执行测试平台:CentOS 6.5发行版,内核版本号3.11


    1. Linux抓包源程序

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

    进行网络通信时。源主机通过socket(或其他)应用程序产生IP报文,经过各个OSI层层封装,数据包以Ethernet帧的形式进入物理层。Ethernet帧包括源主机地址、IP报文、目标地址(IP地址、port号或映射的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程序。

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

    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <linux/if_ether.h>
    #include <linux/in.h>
    
    #define BUFFER_MAX 2048
     
    int main(int argc, char *argv[]){
         int  SOCKET_SRC;
         char buf[BUFFER_MAX];
         int n_rd;
    
         if( (SOCKET_SRC = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0 ){
             fprintf(stderr, "create socket error.
    ");
             exit(0);
         }
         while(1){
             n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);
         	 if (n_rd<46) {
             	 perror("recvfrom():");
              	printf("Incomplete packet (errno is %d)
    ",  errno);
              	close(SOCKET_SRC);
              	exit(0);
        	 }
        	 /* An Ethernet frame was written to buf, frame analysis can be processed here */
        	 /* Termination control */
         }
         close(SOCKET_SRC);
         return 0;
    }
    


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

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

    以太网帧(RFC894)格式


    程序通过运行一次

    n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);

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


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

    typedef struct mac_frm_hdr {
         char dest_addr[6];	//destination MAC address shall be defined first.
         char src_addr[6];
         short type;
     }__attribute__((packed)) MAC_FRM_HDR;

    定义该结构体时,须要注意下面几点。

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

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

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

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

    typedef struct ip_hdr{ 	//header of IPV4
        #ifdef __LITTLE_ENDIAN_BIFIELD
            u_char ip_len:4, ip_ver:4;
        #else
            u_char ip_ver:4, ip_len:4;
        #endif
    
        u_char  ip_tos;
        u_short ip_total_len;
        u_short ip_id;
        u_short ip_flags;
        u_char  ip_ttl;
        u_char  ip_protocol;
        u_short ip_chksum;
        u_int32 ip_src;
        u_int32 ip_dest;
    }__attribute__((packed)) IP_HDR;
    

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

    typedef int int32;
    typedef unsigned int u_int32;
    typedef unsigned char u_char;
    typedef unsigned short u_short;

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

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


    以下是对以太网帧进行解析的详细代码。


    MAC_FRM_HDR *mac_hdr; //define a Ethernet frame header
    IP_HDR *ip_hdr;       //define a IP header
    char *tmp1, *tmp2;
    int AND_LOGIC = 0xFF;
    
    mac_hdr = buf;	//buf is what we got from the socket program
    ip_hdr = buf + sizeof(MAC_FRM_HDR);
    //udp_hdr = buf + sizeof(MAC_FRM_HDR) + sizeof(IP_HDR); //if we want to analyses the UDP/TCP
    
    tmp1 = mac_hdr->src_addr;
    tmp2 = mac_hdr->dest_addr;
    /* print the MAC addresses of source and receiving host */
    printf("MAC: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X==>" "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X",
                tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,
                tmp1[4]&AND_LOGIC, tmp1[5]&AND_LOGIC,
                tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC,
                tmp2[4]&AND_LOGIC, tmp2[5]&AND_LOGIC);
    
    tmp1 = (char*)&ip_hdr->ip_src;
    tmp2 = (char*)&ip_hdr->ip_dest;
    /* print the IP addresses of source and receiving host */
    printf("IP: %d.%d.%d.%d => %d.%d.%d.%d",
                 tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,
                 tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC);
    /* print the IP protocol which was used by the socket communication */
    switch(ip_hdr->ip_protocol) {
             case IPPROTO_ICMP: LOGI("ICMP"); break;
             case IPPROTO_IGMP: LOGI("IGMP"); break;
             case IPPROTO_IPIP: LOGI("IPIP"); break;
             case IPPROTO_TCP:
     	 case IPPROTO_UDP:
                                LOGI("Protocol: %s", ip_hdr->ip_protocol == IPPROTO_TCP ? "TCP" : "UDP");
                                LOGI("Source port: %u, destination port: %u", udp_hdr->s_port, udp_hdr->d_port);
                                break;
             case IPPROTO_RAW: LOGI("RAW"); break;
             default: printf("Unknown, please query in inclued/linux/in.h
    "); break;
    }

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

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

    

    版权声明:本文博客原创文章。博客,未经同意,不得转载。

  • 相关阅读:
    git 常用命令 command 1.0(本地 local repository 对远程仓库 remote repository 的操作)
    git 常用命令 command
    git .gitignore 忽略列表
    节点的类型
    Error.name 六种值对应的信息
    js或jquery中的验证
    转载:JavaWeb学习总结(五十三)——Web应用中使用JavaMail发送邮件
    转载:JavaWeb学习总结(五十二)——使用JavaMail创建邮件和发送邮件
    转载:JavaWeb学习总结(五十一)——邮件的发送与接收原理
    转载:JavaWeb学习总结(五十)——文件上传和下载
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4654811.html
Copyright © 2020-2023  润新知