TCPDump应用

151 阅读6分钟

TcpDumper的应用

主要流程:通过pcap_loop来轮询网卡中的数据,将其通过回调callback,对其读写 到共享内存的一个过程,其中加入了环形的共享内存和数据的过滤机制

数据类型bpf_u_int32实际上就是u_int的一个别名,还有吧bpf_int32实际上就是int的别名。当然这个int是32位的,如果操作系统对int的定义不是4字节,bpf_int32就对应另外一种类型,总之,bpf_u_int32就是一个32位的无符号整型。

关键函数:

pcap_lookupnet

int pcap_lookupnet ( const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf ) 
    // 用于获取网卡的网络号和子网掩码。
  • 其中 device 参数是网卡名,netp 和 maskp 表示将要获得的网络号和子网掩码,都是以网络字节序存放的,
  • 比如一个 IP 为10.108.20.0,那么 netp 中存放的这个地址就是:1338378。转换成二进制就是: 00000000 00010100 01101100 00001010 这个数在内存中的存在形式就是: 低地址----------------------------------------》高地址 00001010 01101100 00010100 00000000 对应每个字节的十进制就是: 10 108 20 0 网络字节序和主机字节序比较容易混乱(大端表示和小端表示)。 网络字节序 采用大端表示,即:数据的高位要存放到低地址。 多数 主机字节序 采用小端表示(也有采用大端表示的主机字节序),即:数据的低位放到低地址。 比如无符号整型1338378,的二进制表示为: 数据的高位----------------------------》数据的低位 00000000 00010100 01101100 00001010 所以采用小端表示的主机字节序时,内存中存放的形式为: 低地址----------------------------------------》高地址 00001010 01101100 00010100 00000000 errbuf存放错误信息。失败返回-1 errbuf的大小可以定义为:PCAP_ERRBUF_SIZE

pcap_open_live

pcap_t *pcap_open_live ( const char *device, int snaplen,int promisc, int to_ms, char *errbuf ) 
    // 用于打开网络设备,返回一个pcap_t结构体的指针。

帮助文档中的说法是:用于获取一个数据包捕获的描述符,以便用来查看网络上的数据包。device是网卡名称。snaplen表示捕获的最大字节数,如果这个值小于被捕获的数据包的大小,则只显示前snaplen位(实验表明,后面为全是0),通常来讲数据包的大小不会超过65535。promisc表示是否开启混杂模式。 to_ms 表示读取的超时时间,毫秒为单位,就是说没有必要看到一个数据包这个函数就返回,而是设定一个返回时间,这个时间内可能会读取很多个数据包,然后一起返回。如果这个值为0,这个函数一直等待足够多的数据包到来。errbuf用于存储错误信息。

pcap_compile

int pcap_compile ( pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask ) 
    /* 用于编译字符串str成为一个过路程序,
     * program 参数是一个指向 bpf_program 结构体的指针,这个参数由函数pcap_compile()填充。
     * optimize 参数用于控制是否采用最优化的结果。
     * netmask 用于指定IPv4的网络子网掩码,这个参数仅仅在检查过滤程序中的IPv4广播地址时才会使用。
     */

pcap_setfilter

int pcap_setfilter( pcap_t *p, struct bpf_program *fp ) 
    /*
     * 用于指定一个过滤器程序,fp 是一个指向 bpf_program 结构体的指针,通常是 pcap_compile 返回的结果。
     * p通常是 pcap_open_live() 函数返回的结果。
     */

pcap_dispatch

int pcap_dispatch( pcap_t * p, int cnt, pcap_handler callback, u_char * user );
    /*
     * 这个函数用于收集和加工数据包。
     * cnt 指定用于加工的数据包的最大数量,超过了这个数量函数就会返回。
     * 当读取一个实时捕获的时候,一次仅有一个数据包的缓冲区被读取,所以处理的数据包的个数有可能少于 cnt 个。
     * 当 cnt 的值为-1 时,会处理到达一个缓冲区的所有数据包,或者(如果不是实时捕获而是处理一个 pcap 文件)会处理文件中所有的数据包。
     * callback 指定了一个回调函数。
     */

这个回调函数有三个参数:

  • 一个 u_char 类型的指针,这个参数是通过 pcap_dispatch() 函数传递的(应该是提供用户使用的), 一个指向 pcap_pkthdr 结构体常量的指针。这个结构体含有三个域,
struct pcap_pkthdr
{
    struct timeval ts; // ts是一个结构struct timeval,它有两个部分,第一部分是1900开始以来的秒数,第二部分是当前秒之后的毫秒数。表示数据包捕获的时间。
    bpf_u_int32 caplen; // 表示抓到的数据长度
    bpf_u_int32 len; // 表示数据包的实际长度
}
  • 第二个域是一个 u_char 常量的指针,指向的一个数据包的前 caplen 个字节。

  • 成功返回 0, 失败返回 -1可以用pcap_perror来打印错误信息。

注意:当读取一个实时捕获时,当读取超时时 pcap_dispatch() 函数没有必要返回。

pcap_loop () 函数和pcap_dispatch() 用法相似。

int pcap_loop ( pcap_t *p, int cnt, pcap_handler callback, u_char *user )
    // 这个函数当读取超时时也不会返回。
void pcap_close ( pcap_t * p );
    // 用于关闭pcap_open_live()获取的包捕捉句柄,释放相关资源。

一个典型的过程:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <pcap.h>

#include <arpa/inet.h>
#include <unistd.h> //getopt
#include <sys/types.h> //u_char

void get_packet(u_char *user, const struct pcap_pkthdr *pkthdr, const u_char *packet)
{
    //    prase_packet(packet, pkthdr->len);
}

int main(int argc,char* argv[])  
{
    char* device=NULL;
    char ch;
    //getopt()
    if((ch = getopt(argc,argv,"d:"))==-1)
    {
        printf("no options!!!\n");
        exit(1);
    }
    optind=1;
    while ((ch = getopt(argc, argv, "d:")) != -1)
    {
        switch (ch) 
        {    
        	case 'd':
            if(optarg!=NULL)
            {
                if(  (  device  =  (char*)malloc(strlen(optarg) ) ) !=NULL)//remeber to free
                {
                    strcpy(device,optarg);
                    //printf("%s\n",device);
                }
                else
                {
                    printf("malloc error\n");
                    exit(1);
                }
                        
            }
            else
            {
                exit(1);
            }   
            break;
     
            case '?':
            default:
                printf("Unknown option: %c\n",(char)optopt);
                exit(1);                    
        }
    }
           
    //capture
    if(!device)
    {
        printf("no device\n");
        exit(1);
    }
            
    char errBuf[PCAP_ERRBUF_SIZE]; //error Buff
  
   // struct pcap_pkthdr packet;  The header that pcap gives us
    pcap_t *phandle; //network interface
       
    bpf_u_int32 netp, maskp; //网络号和子网掩码
    char *net, *mask;
    struct bpf_program fcode;
    struct in_addr addr;
    //look up device network addr and mask
    //获取网络参数

    if(pcap_lookupnet(device, &netp, &maskp, errBuf)==-1) 
    {
    	printf("get net failure\n");
        exit(1);
    }

    addr.s_addr = netp;
    printf("%u\n",netp);
    net = inet_ntoa(addr);
    printf("network: %s\n", net);
    
    addr.s_addr = maskp;
    mask = inet_ntoa(addr);
    printf("mask: %s\n", mask);
   
    //open network device for packet capture
    phandle = pcap_open_live(device, 65535, 1, 500, errBuf);
    if(NULL == phandle) 
    {
    	printf("open %s failure\n", device);
        perror(errBuf);
        exit(1);
    }
    char* filterString="ether src D8:5D:4C:36:76:83 or ether dst D8:5D:4C:36:76:83";
    
    if(pcap_compile(phandle,&fcode,filterString,0,maskp)==-1)
    {
    	fprintf(stderr,"pcap_compile: %s\n",pcap_geterr(phandle));
        exit(1);
    }

    if(pcap_setfilter(phandle,&fcode)==-1)
    {
        fprintf(stderr,"pcap_setfilter: %s\n",pcap_geterr(phandle));
        exit(1);
    }
        
    pcap_loop(phandle, -1, get_packet, NULL);
    
        //close device
    pcap_close(phandle);

    return 0; 
}