写自己的sniff,with pcap

来源:互联网 发布:派尔玻璃优化排版软件 编辑:程序博客网 时间:2024/05/16 05:07

这是从winpcap的官方网站上翻译下来的

Get Started: The format of a pcap application

首先让我们了解一个pcap应用程序的常用设计。

代码的流程如下所示:

1、  首先决定将要用来sniff的网络接口。在Linux下可能是eth0在BSD下可能是xl1等。我们要么在一个字符串(char *)中定义这个设备,或用用户在命令行直接指定用来sniff的设备接口名。

2、  初始化pcap。这是明确指定用来sniff的的网络接口的地方。当然我们可以在多个接口设备上sniff。通过句柄(handle)我们可以区分这些不同的sniff设备接口。就像我们打开一个用来读或写的文件一样,我们必须命名我们的sniffer session 以便于区别这些不同的session。

3、  通常情况下我们只希望sniff特定的网络通信(比如:tcp数据包,所有发往23端口的tcp数据包)。通常我们制定这样一个定义特定网络通信的规则集,将其编译以后加载(apply to)pcap引擎上。这是编写pcap应用程序最主要的步骤,而且必须紧密关联。规则被保存在一个字符串中,通过编译被转换成pcap引擎能够识别的格式。事实上所谓的编译不过是在我们自己的程序中调用特定的函数就可以完成,并不涉及到任何外部的应用程序。然后我们可以告诉pcap引擎应用编译的规则作为我们sniff的规则(filter)。4、  最后我们通知pcap引擎进入主要的处理流程:pcap接受并处理匹配指定规则的制定数目的数据包。每当捕获一个新的数据包,pcap调用自定义的回调函数进行相应的处理。在回调函数中可以做任何我们想做的事情:解剖捕获的数据包并打印到用户控制台,或者保存到文件中,当然也可以什么也不做

5、  结束sniff并关闭pcap会话句柄。 事实上,是用pcap编程是一个非常简单的过程,一共5个步骤,而且令你备感困惑的第3步还是可选的。详细实现如下。 Setting the Device这是一个极其简单的操作(原文:This is terribly simple)。

有两种方法可以设置用来sniff的网络接口。1、 用户在命令行指定定监听的网络接口:

#include <stdio.h>
#include <pcap.h>
int main(int argc, char *argv[])
{
    char *dev = argv[1];
    printf("Device: %s/n", dev);
    return(0);
}用户通过命令行参数传入监听接口。译注:在实际的项目开发中务必对命令行参数进行判断:     if (argc < 2) {         printf(“Usage: %s <option>/n”, argv[0]);         exit(1);     } 2、  通过pcap引擎设定监听的网络接口:#include <stdio.h>
#include <pcap.h>
int main()
{
    char *dev, errbuf[PCAP_ERRBUF_SIZE];
    dev = pcap_lookupdev(errbuf);
    printf("Device: %s/n", dev);
    return(0);
}在这种情况下,pcap引擎自己设置用来监听的接口。但是errbuf字符串用来做什么呢?大多数的pcap函数允许我们传递这样一个字符串作为其参数。这个字符串参数用来在pcap函数调用失败以后用来设置出错信息。在上面的例子中,如果pcap_lookup函数调用失败,出错信息将被保存在errbuf中。

译注:增加的错误检查的代码如下:    i

f (NULL == (dev = pcap_lookupdev(errbuf)))

{       fprintf(stderr, “pcap_lookupdev() error: %s/n”, errbuf);       exit(-1);    }   

printf(“Device: %s/n”, dev); Opening the device for sniffing创建sniff会话的任务非常简单。

我们使用pcap_open_live()创建sniff会话。函数原型:pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)    device:上节中我们制定的监听设备接口;    snaplen:制定pcap捕获的最大数目的网络数据包;    promisc:>0指定device接口工作在混杂模式(promiscous Mode);    to_ms:制定经过特定时间(ms)后读超时;0表示遇到错误退出,-1指定永不超时;    ebuf:制定用来存储出错信息的字符串    pcap_t:返回值为用于监听的pcap会话。示例代码:#include <pcap.h>
    ...
    pcap_t *handle;
    handle = pcap_open_live(somedev, BUFSIZ, 1, 0, errbuf);上面的代码打开somedev指定的设备并读取(捕获)BUFSIZ字节,同时我们设置接口工作在混杂模式,一直监听到有任何错误发生则退出,并将出错信息保存在errbuf指定的字符串中。 关于混杂模式vs.非混杂模式:通常情况在非混杂模式下仅监听直接发往主机的数据包:发往、源自或通过主机路由的数据包都将被pcap捕获;混杂模式下,所有发送到物理链路上的数据包都将被捕获。在一个共享式的网络环境中,这将导致整个网络的数据流被监听。混合监听模式是可以被检测的:可以通过测试强可靠性来发现网络中是否有主机正在以混合模式监听,另外混杂工作模式仅仅在非交换式的网络中有效,而且在一个高负载的网络环境中,混杂模式将消耗大量的系统资源。 Filter traffic通常我们只对特定网络通信感兴趣。比如我们只打算监听Telnet服务(port 23)以捕获用户名和口令信息。获知对FTP(port 21)或DNS(UDP port 53)数据流感兴趣。

可以通过pcap_compile()和pcap_setfilter来设置数据流过滤规则(filter)函数原型:    int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)p:表示pcap会话句柄;fp:存放编译以后的规则;str:规则表达式格式的过滤规则(filter),同tcpdump中的filter;optimize:制定优化选项:0 false, 1 true;netmask:监听接口的网络掩码;返回值:-1表示操作失败,其他值表成功。int pcap_setfilter(pcap_t *p, struct bpf_program *fp)    p:表示pcap的会话句柄;    fp:表示经过编译后的过滤规则;    返回值:-1表示操作失败,其他值表成功。 示例代码:#include <pcap.h>
    ...
    pcap_t *handle;                           /* Session handle */
    char dev[] = "rl0";                        /* Device to sniff on */
    char errbuf[PCAP_ERRBUF_SIZE];    /* Error string */
    struct bpf_program filter;               /* The compiled filter expression */
    char filter_app[] = "port 23";          /* The filter expression */
    bpf_u_int32 mask;                        /* The netmask of our sniffing device */
    bpf_u_int32 net;                           /* The IP of our sniffing device */
    pcap_lookupnet(dev, &net, &mask, errbuf);
    handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
    pcap_compile(handle, &filter, filter_app, 0, net);
    pcap_setfilter(handle, &filter); 

上面的代码设备rl0上以混杂模式监听所有发往或源自端口23的数据包。Pcap_lookupnet()函数返回给定接口的IP地址和子网掩码。 The actual sniffing现在我们开始准备捕获数据包:有两种方法可以用来捕获数据包。要么一次捕获一个满足条件的数据包,要么进入一个循环过程捕获指定数量数据包然后退出。首先来了解使用pcap_next()一次捕获单一数据包。函数原型:u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)    p:pcap会话句柄;    h:指向pcap_pkthdr接口的指针,在此结构中保存了所捕获的数据包的通用信息。包括:时间信息、数据包的长度和包头部分的长度(结构定义在后面定义)。    返回值:返回指向实际捕获的数据包的u_char *型指针。

代码示例:

    #include <pcap.h>
    #include <stdio.h>
    int main()
    {
        pcap_t *handle;                        /* Session handle */
        char *dev;                                /* The device to sniff on */
        char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */
        struct bpf_program filter;            /* The compiled filter */
        char filter_app[] = "port 23";       /* The filter expression */
        bpf_u_int32 mask;                     /* Our netmask */
        bpf_u_int32 net;                        /* Our IP */
        struct pcap_pkthdr header;          /* The header that pcap gives us */
        const u_char *packet;                 /* The actual packet */
        /* Define the device */
        dev = pcap_lookupdev(errbuf);
        /* Find the properties for the device */
        pcap_lookupnet(dev, &net, &mask, errbuf);
        /* Open the session in promiscuous mode */
        handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
        /* Compile and apply the filter */
        pcap_compile(handle, &filter, filter_app, 0, net);
        pcap_setfilter(handle, &filter);
        /* Grab a packet */
        packet = pcap_next(handle, &header);
        /* Print its length */
        printf("Jacked a packet with length of [%d]/n", header.len);
        /* And close the session */
        pcap_close(handle);
        return(0);
    }

上面的代码将所有从pcap_lookupdev()返回的接口置于混杂模式监听状态。Pcap捕获端口23的一个数据包并打印该包的长度。然后调用pcap_close()关闭pcap会话。 当然我们可以使用更复杂和更强大的功能pcap_loop和pcap_dispatch。通常很少有sniffer使用pcap_next,他们更通常的使用pcap_loop或pcap_dispatch。为便于理解这两个函数,需要现了解回调函数的概念。 回调函数并不是一个新概念,在很多的API中都使用了回调函数的概念。可以通过pcap_loop或pcap_dispatch定义用户自己的回调函数。事实上pcap_loop和pcap_dispatch的功能非常相似,当pcap捕获的满足规则的数据包时,着两个函数将调用我们自己定义的回调函数执行我们自己的处理。 函数原型:int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)p:pcap会话句柄;cnt:定义sniff捕获的数据包的数目;callback:自定义的回调函数句柄;user:传递给回调函数的参数,如没有参数可以设为NULL;函数pcap_dispatch和pcap_loop的用法几乎相同,两者之间的唯一的差别是处理超时的方式不同(在pcap_open_live()中设置的超时参数将在这里起作用:pcap_loop将忽略超时参数而pcap_dispatch在制定时间到时将产生读超时的错误)。

查阅pcap的帮助获得更多信息。

回调函数的原型:

void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);    args:对应于pcap_loop中的最后一个参数;    header:指向pcap数据包包头的指针;    packet:指向pcap捕获到的数据包的指针,packet指针指向的字符串包含了整个数据包;    返回值:回调函数不能返回任何值。定义回调函数时,需要严格遵守原型定义,否则pcap_loop将不能正确调用回调函数。    pcap_pkthdr结构的定义如下:   

 struct pcap_pkthdr {
              struct timeval ts; /* time stamp */
              bpf_u_int32 caplen; /* length of portion present */
              bpf_u_int32 len; /* length this packet (off wire) */ }; 

    怎样使用(处理)packet指针变量呢?一个packet指针所指的结构包含了很多属性,它并不是一个真正的字符串,而是多个结构组成的集合(比如:一个TCP/IP数据包包括以太网头、IP包头、TCP头和数据包中有效的数据负载)。首先需要定义这些结构:

/* Ethernet header */
struct sniff_ethernet {
  u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */
  u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */
  u_short ether_type; /* IP? ARP? RARP? etc */
};
/* IP header */
struct sniff_ip {
  #if BYTE_ORDER == LITTLE_ENDIAN
  u_int ip_hl:4, /* header length */
  ip_v:4; /* version */
  #if BYTE_ORDER == BIG_ENDIAN
  u_int ip_v:4, /* version */
  ip_hl:4; /* header length */
  #endif
  #endif /* not _IP_VHL */
  u_char ip_tos; /* type of service */
  u_short ip_len; /* total length */
  u_short ip_id; /* identification */
  u_short ip_off; /* fragment offset field */
  #define IP_RF 0x8000 /* reserved fragment flag */
  #define IP_DF 0x4000 /* dont fragment flag */
  #define IP_MF 0x2000 /* more fragments flag */
  #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
  u_char ip_ttl; /* time to live */
  u_char ip_p; /* protocol */
  u_short ip_sum; /* checksum */
  struct in_addr ip_src,ip_dst; /* source and dest address */
};
/* TCP header */
struct sniff_tcp {
  u_short th_sport; /* source port */
  u_short th_dport; /* destination port */
  tcp_seq th_seq; /* sequence number */
  tcp_seq th_ack; /* acknowledgement number */
  #if BYTE_ORDER == LITTLE_ENDIAN
  u_int th_x2:4, /* (unused) */
  th_off:4; /* data offset */
  #endif
  #if BYTE_ORDER == BIG_ENDIAN
  u_int th_off:4, /* data offset */
  th_x2:4; /* (unused) */
  #endif
  u_char th_flags;
  #define TH_FIN 0x01
  #define TH_SYN 0x02
  #define TH_RST 0x04
  #define TH_PUSH 0x08
  #define TH_ACK 0x10
  #define TH_URG 0x20
  #define TH_ECE 0x40
  #define TH_CWR 0x80
  #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
  u_short th_win; /* window */
  u_short th_sum; /* checksum */
  u_short th_urp; /* urgent pointer */
};

注:这些结构定义在不同的系统实现中可能存在差异,请查阅相关文档。

另:搞不懂作者什么意思,为什么非要自己定义这些结构,干嘛不用系统自己定义的实现呢?

 

省略了原作者关于定义这些结构的描述…

 

假设我们通过以太网处理TCP/IP数据包(其他的物理网络类似),如下代码将packet指针所指的结构分解为不同的结构体:

const struct sniff_ethernet *ethernet; /* The ethernet header */
const struct sniff_ip *ip; /* The IP header */
const struct sniff_tcp *tcp; /* The TCP header */
const char *payload; /* Packet payload */
/* For readability, we'll make variables for the sizes of each of the structures */
int size_ethernet = sizeof(struct sniff_ethernet);
int size_ip = sizeof(struct sniff_ip);
int size_tcp = sizeof(struct sniff_tcp);

And now we do our magical typecasting:
ethernet = (struct sniff_ethernet*)(packet);
ip = (struct sniff_ip*)(packet + size_ethernet);
tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp);

如果packet值(该指针变量所指的地址)为X,则上面所述的结构在内存中的布局如下所示:

Variable
Location (in bytes)

sniff_ethernet
X

sniff_ip
X + 14

sniff_tcp
X + 14 + 20

payload
X + 14 + 20 + 20

 


Wrapping up

到此为止,我们已经可以用pcap编写一个sniffer应用程序了。我们已经了解了pcap编程的基础知识,包括打开一个pcap会话句柄,处理pcap会话句柄的属性,监听数据包,应用过滤规则,并使用回调函数定义我们自己的处理过程。随原文提供的示例程序:sniffer.c

 

This document is Copyright 2002 Tim Carstens. All rights reserved. Redistribution and use, with or without modification, are permitted provided that the following conditions are met: 1. Redistribution must retain the above copyright notice and this list of conditions. 2. The name of Tim Carstens may not be used to endorse or promote products derived from this document without specific prior written permission.
/* Insert 'wh00t' for the BSD license here */

 

附录:例用pcap编写的示例程序

/*

    编译:gcc –Wall –o testpcap testpcap.c -lpcap

*/

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <pcap.h>

#include <sys/types.h>

#include <netinet/ip.h>

#include <netinet/ether.h>

#include <net/ethernet.h>

#include <netinet/udp.h>

#include <netinet/tcp.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

/* MACRO to print debug info */

//#define DEBUG 1

#ifdef DEBUG 

#define debug(stderr, msg) fprintf(stderr, msg)

#define _ ,

#else /* if no define DEBUG */

#define debug(stderr, msg)

#endif /* end of BEBUG */

 

 

#define LOOKUPDEV_ERR -1

#define OPEN_LIVE_ERR -2

#define COMPILE_ERR  -3

 

/* protocol ID's */

#define IPPRO 8 /* IP protocol */

 

/* call back function invoke by pcap_loop, major process for ourselves */

void

got_packet(u_char *args, const struct pcap_pkthdr *header,

     const u_char *packet);

/* handle ethernet header */

u_int16_t

handle_ethernet(u_char *args,const struct pcap_pkthdr* pkthdr,

     const u_char* packet);

/* handle IP header */

void

handle_IP(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char* packet);

    

   

 

int

main(int argc, char *argv[])

{

   char *dev = NULL; /* device to sniff on */

   char errbuf[PCAP_ERRBUF_SIZE]; /* buffer to store error msg */

   pcap_t *handle = NULL; /* pcap session handle */

 

   struct bpf_program filter; //compiled filter expression

   char filter_app[] = "port 23"; /* filter ruler for sniffing */

   bpf_u_int32 mask; //netmask of our sniffing device

   bpf_u_int32 net; //the ip of our sniffing device

  

   int num = 0; /* number of packets captured */

 

   /* variables for getopt */

   long total = -1; /* total packets to sniff */

   char *flter = filter_app; /* filter ruler for sniffing */

   int c; /* temprory char variable */

 

   while ((c = getopt(argc, argv, "n:f:")) != -1) {

     switch(c) {

       case 'n':

          total = atoi(optarg);

          break;

 

       case 'f':

          flter = optarg;

          break;

    

       case '?':

          fprintf(stderr, "Usage: %s -n <num> -f <filter string>/n", argv[0]);

          exit(1);

       default:

          fprintf(stdout, "Using fitler: port 23 and sniffing util interrupt by console!/n");

     }

   }

 

   if (NULL == (dev = pcap_lookupdev(errbuf))) {

     fprintf(stderr, "pcap_lookupdev() error: %s/n", errbuf);

     exit(LOOKUPDEV_ERR);

   }

   fprintf(stdout, "Sniffing on device: %s/n/n", dev);

 

   pcap_lookupnet(dev, &net, &mask, errbuf);

 

   /* open a new pcap session */

   if (NULL == (handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf))) {

     fprintf(stderr, "pcap_open_live() error: %s/n", errbuf);

     exit(OPEN_LIVE_ERR);

   }

   /* compile capture rule */

   if (-1 == pcap_compile(handle, &filter, flter, 1, net)) {

     fprintf(stderr, "pcap_compile() error!/n");

     exit(COMPILE_ERR);

   }

   pcap_setfilter(handle, &filter);

 

/* using while + pcap_next instead of pcap_loop or pcap_dispatch */

/*  while (1) {

     debug(stderr, "in pcap_next while/n");

     packet = pcap_next(handle, &header);

     printf("Captured a packet with lengthen of [%d]/n", header.len);

     debug(stderr, "The packet captured: %s/n" _ packet + header.caplen);

   }

*/

 

   num = pcap_loop(handle, total, got_packet, NULL);

   if (-1 == num) {

     pcap_perror(handle, "pcap_loop error: ");

   }  

   if (-2 == num) {

     pcap_perror(handle, "pcap_loop break by pcap_breakloop: ");

   }

 

   pcap_close(handle);

 

   return 0;

}

 

void

got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)

{

  u_int16_t type;

 

   type = handle_ethernet(args, header, packet);

 

   debug(stderr, "protocol type: %i/n" _ type);

 

  switch(type) {

     case IPPRO:

       debug(stderr, "protocol type: IP/n");

       handle_IP(args, header, packet);

       break;

 

     case ETHERTYPE_ARP:

       /* handle arp protocol */ 

       break;

     case ETHERTYPE_REVARP:

       /* handle rarp protocol */ 

       break;

     default:

       fprintf(stdout, "Protocol is ignored/n");

   }

 

     fprintf(stdout,"/n");

   return;

} //end of got_packet

 

u_int16_t

handle_ethernet(u_char *args,const struct pcap_pkthdr* pkthdr,

     const u_char* packet)

{

   struct ether_header *eptr; /* net/ethernet.h */

 

  /* lets start with the ether header... */

  eptr = (struct ether_header *) packet;

 

  fprintf(stdout,"ETH: %s --> "

      ,ether_ntoa((struct ether_addr *)(eptr->ether_shost)));

  fprintf(stdout,"%s "

      ,ether_ntoa((struct ether_addr *)(eptr->ether_dhost)));

 

  /* check to see if we have an ip packet */

  if (ntohs (eptr->ether_type) == ETHERTYPE_IP)

  {

    fprintf(stdout,"(IP)");

  }else if (ntohs (eptr->ether_type) == ETHERTYPE_ARP)

  {

    fprintf(stdout,"(ARP)");

  }else if (ntohs (eptr->ether_type) == ETHERTYPE_REVARP)

  {

    fprintf(stdout,"(RARP)");

  }else {

    fprintf(stdout,"(?)");

    exit(1);

  }

 

  return eptr->ether_type;

} //end of handle_ethernet

 

void

handle_IP(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char* packet)

{

   const struct iphdr *ip = (const struct iphdr *)(packet + sizeof(struct ether_header));

 

   u_int length = pkthdr->len;

   u_int hlen,off,version;

   int len;

   struct in_addr in;

 

   debug(stderr, "total pcap pkt length: %i/n" _ length);

   debug(stderr, "total pcap pkt header length: %i/n" _ pkthdr->caplen);

 

  /* jump pass the ethernet header */

  length =- sizeof(struct ether_header);

 

  /* check to see we have a packet of valid length */

  if (length < sizeof(struct iphdr))

  {

    printf("truncated ip %d",length);

    return;

  }

 

  len   = ntohs(ip->tot_len);

   debug(stderr, "total ip pkt length: %i/n" _ len);

  hlen  = ip->ihl; /* header length */

   debug(stderr, "ip header length: %i/n" _ hlen);

  version = ip->version;/* ip version */

   debug(stderr, "ip version: %i/n" _ version);

 

  /* check version */

  if(version != 4)

  {

   fprintf(stdout,"Unknown version %d/n",version);

   return ;

  }

 

  /* check header length */

  if(hlen < 5 )

  {

    fprintf(stdout,"bad-hlen %d /n",hlen);

  }

 

  /* see if we have as much packet as we should */

  if(length < len)

    printf("/ntruncated IP - %d bytes missing/n",len - length);

 

  /* Check to see if we have the first fragment */

  off = ntohs(ip->frag_off);

  if((off & 0x1fff) == 0 )/* aka no 1's in first 13 bits */

  {/* print SOURCE DESTINATION hlen version len offset */

    fprintf(stdout,"/nIP: ");

   in.s_addr = ip->saddr;

    fprintf(stdout,"%s ",inet_ntoa(in));

   in.s_addr = ip->daddr;

    fprintf(stdout,"%s %d %d %d %d/n",

        inet_ntoa(in),

        hlen,version,len,off);

  }
  return;
}