libpcap的用法入門


 前一段時間,由於需要自己寫一個路由器程序,開始學習libpcap和netlink,也在網上看了很多牛人寫得文章,發現中文的pcap和netlink資料真的還是蠻少的(比較而言,pcap可能多一點,netlink幾乎沒有)。所以決定寫幾篇文章,和大家一起交流一下。


libpcap是一個與實現無關的訪問操作系統所提供的分組捕獲機制的分組捕獲函數庫,目前只支持分組的讀取,當然增加一些代碼之後,也可以寫數據鏈路分組(如果有人對句話有疑問,去找已故的Richard Steven討論吧,這是他說的:P)。
libcap支持大多數Unix系統,當然也支持Linux,而且在現在linux的發行版中都有集成,大名鼎鼎的tcpdump就用到了它。libcap大約是由25個函數組成的,我在這裏只是介紹一下比較簡單的使用,至於大家需要深入專研的,在自己的linux上面輸入 man 3 pcap就可以了。
有一點要說明的是,libpcap是用來抓取以太網包的,至於能不能抓取ppp鏈路的包,我覺得是不能的,因爲我看過一個在專有網絡中抓包的程序,人家是專門寫了一個類來讀取com設備的。至於理論上爲什麼不行,我也很難說出個所以然來,等我哪天搞清楚了再寫一篇blog好了。

好了,廢話少說,Let's begin!看看我們的第一個程序。。。。
/*pcap_1.c*/
#include
#include
#include   /* 如果沒有pcap的系統,要自己下載一個 */
#include
#include
#include
#include
int main(int argc, char **argv)
{
  char *dev; /* name of the device to use */
  char *net; /* dot notation of the network address */
  char *mask;/* dot notation of the network mask    */
  int ret;   /* return code */
  char errbuf[PCAP_ERRBUF_SIZE];
  bpf_u_int32 netp; /* ip          */
  bpf_u_int32 maskp;/* subnet mask */
  struct in_addr addr;
  /* ask pcap to find a valid device for use to sniff on */
  dev = pcap_lookupdev(errbuf);
  /* error checking */
  if(dev == NULL)
  {
   printf("%s\n",errbuf);
   exit(1);
  }
  /* print out device name */
  printf("DEV: %s\n",dev);
  /* ask pcap for the network address and mask of the device */
  ret = pcap_lookupnet(dev,&netp,&maskp,errbuf);
  if(ret == -1)
  {
   printf("%s\n",errbuf);
   exit(1);
  }
  /* get the network address in a human readable form */
  addr.s_addr = netp;
  net = inet_ntoa(addr);
  if(net == NULL)/* thanks Scott :-P */
  {
    perror("inet_ntoa");
    exit(1);
  }
  printf("NET: %s\n",net);
  /* do the same as above for the device's mask */
  addr.s_addr = maskp;
  mask = inet_ntoa(addr);
  
  if(mask == NULL)
  {
    perror("inet_ntoa");
    exit(1);
  }
  
  printf("MASK: %s\n",mask);
  return 0;
}
然後gcc -o pcap_1 pcap_1.c -lpcap(一定要-lpcap參數)
編譯ok~,執行./pcap_1,可以看到:
DEV: eth0
NET: 192.168.12.0
MASK: 255.255.255.0
好了,第一個pcap程序出爐了。。。。。

但是(當然有但是了,要不然我後面寫啥),上面那個程序除了向我們展現pcap_lookupdev和pcap_lookupnet之外什麼都沒有幹,好,我們接着來,動手編寫我們的第一個抓包程序。

/*pcap_2.c*/
#include
#include
#include  /* if this gives you an error try pcap/pcap.h */
#include
#include
#include
#include
#include  /* includes net/ethernet.h */
int main(int argc, char **argv)
{
    int i;
    char *dev;
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t* descr;      /*you can man it*/
    const u_char *packet;
    struct pcap_pkthdr hdr;     /* pcap.h */
    struct ether_header *eptr;  /* net/ethernet.h */
    u_char *ptr; /* printing out hardware header info */
    /* grab a device to peak into... */
    dev = pcap_lookupdev(errbuf);
    if(dev == NULL)
    {
        printf("%s\n",errbuf);
        exit(1);
    }
    printf("DEV: %s\n",dev);
    /* open the device for sniffing.
       pcap_t *pcap_open_live(char *device,int snaplen, int prmisc,int to_ms,
       char *ebuf)
       snaplen - maximum size of packets to capture in bytes
       promisc - set card in promiscuous mode?
       to_ms   - time to wait for packets in miliseconds before read
       times out
       errbuf  - if something happens, place error string here
       Note if you change "prmisc" param to anything other than zero, you will
       get all packets your device sees, whether they are intendeed for you or
       not!! Be sure you know the rules of the network you are running on
       before you set your card in promiscuous mode!!     */
    descr = pcap_open_live(dev,BUFSIZ,0,-1,errbuf);
    if(descr == NULL)
    {
        printf("pcap_open_live(): %s\n",errbuf);
        exit(1);
    }
    /*
       grab a packet from descr (yay!)                    
       u_char *pcap_next(pcap_t *p,struct pcap_pkthdr *h)
       so just pass in the descriptor we got from         
       our call to pcap_open_live and an allocated        
       struct pcap_pkthdr                                 */
    packet = pcap_next(descr,&hdr);
    if(packet == NULL)
    {/* dinna work *sob* */
        printf("Didn't grab packet\n");
        exit(1);
    }
    /*  struct pcap_pkthdr {
        struct timeval ts;   time stamp
        bpf_u_int32 caplen;  length of portion present
        bpf_u_int32;         lebgth this packet (off wire)
        }
     */
    printf("Grabbed packet of length %d\n",hdr.len);
    printf("Recieved at ..... %s\n",ctime((const time_t*)&hdr.ts.tv_sec));
    printf("Ethernet address length is %d\n",ETHER_HDR_LEN);
    /* lets start with the ether header... */
    eptr = (struct ether_header *) packet;
    /* Do a couple of checks to see what packet type we have..*/
    if (ntohs (eptr->ether_type) == ETHERTYPE_IP)
    {
        printf("Ethernet type hex:%x dec:%d is an IP packet\n",
                ntohs(eptr->ether_type),
                ntohs(eptr->ether_type));
    }else  if (ntohs (eptr->ether_type) == ETHERTYPE_ARP)
    {
        printf("Ethernet type hex:%x dec:%d is an ARP packet\n",
                ntohs(eptr->ether_type),
                ntohs(eptr->ether_type));
    }else {
        printf("Ethernet type %x not IP", ntohs(eptr->ether_type));
        exit(1);
    }
    /* THANK YOU RICHARD STEVENS!!! RIP*/
    ptr = eptr->ether_dhost;
    i = ETHER_ADDR_LEN;
    printf(" Destination Address:  ");
    do{
        printf("%s%x",(i == ETHER_ADDR_LEN) ? " " : ":",*ptr++);
    }while(--i>0);
    printf("\n");
    ptr = eptr->ether_shost;
    i = ETHER_ADDR_LEN;
    printf(" Source Address:  ");
    do{
        printf("%s%x",(i == ETHER_ADDR_LEN) ? " " : ":",*ptr++);
    }while(--i>0);
    printf("\n");
    return 0;
}
好了,編譯運行!
[root@norman libpcap]# ./pcap_2
DEV: eth0
Grabbed packet of length 76
Recieved at time..... Mon Mar 12 22:23:29 2001
Ethernet address length is 14
Ethernet type hex:800 dec:2048 is an IP packet
Destination Address:   0:20:78:d1:e8:1
Source Address:   0:a0:cc:56:c2:91
[root@pepe libpcap]#

可能有人等了半天都沒有一個包過來,有個好辦法,再開一個控制檯,ping一下某個網站,比如google~~,呵呵
馬上就有反應了~~

這個程序是一個老外寫的,大家看看註釋應該沒有問題吧~
但是大家也發現了一個問題,就是上面的程序只能捕捉一個包,要不停的捕捉包怎麼辦,用循環??libpcap提供了一個更好的方法:
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);
這個函數能夠不停的捕捉以太網的包,cnt就是捕捉的次數,callback是處理函數,這個處理函數怎麼寫,看看pcap_3.c就知道了。user參數是幹什麼的?不要問我,我也不知道。

/*pcap_3.c*/
#include
#include
#include
#include
#include
#include
#include
#include
/* callback function that is passed to pcap_loop(..) and called each time
* a packet is recieved                                                    */
void my_callback(u_char *useless,const struct pcap_pkthdr* pkthdr,const u_char*
        packet)
{
    static int count = 1;
    fprintf(stdout,"%d, ",count);
    if(count == 4)
        fprintf(stdout,"Come on baby sayyy you love me!!! ");
    if(count == 7)
        fprintf(stdout,"Tiiimmmeesss!! ");
    fflush(stdout);
    count++;
}
int main(int argc,char **argv)
{
    int i;
    char *dev;
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t* descr;
    const u_char *packet;
    struct pcap_pkthdr hdr;     /* pcap.h */
    struct ether_header *eptr;  /* net/ethernet.h */
    if(argc != 2){ fprintf(stdout,"Usage: %s numpackets\n",argv[0]);return 0;}
    /* grab a device to peak into... */
    dev = pcap_lookupdev(errbuf);
    if(dev == NULL)
    { printf("%s\n",errbuf); exit(1); }
    /* open device for reading */
    descr = pcap_open_live(dev,BUFSIZ,0,-1,errbuf);
    if(descr == NULL)
    { printf("pcap_open_live(): %s\n",errbuf); exit(1); }
    /* allright here we call pcap_loop(..) and pass in our callback function */
    /* int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)*/
    pcap_loop(descr,atoi(argv[1]),my_callback,NULL);
    fprintf(stdout,"\nDone processing packets... wheew!\n");
    return 0;
}

運行./pcap_3 7
1, 2, 3, 4, Come on baby sayyy you love me!!! 5, 6, 7, Tiiimmmeesss!!  
Done processing packets... wheew!

pcap_loop確實很好用,但是如果沒有包包過來,只有乾等在那裏,pcap_dispatch就含有一個超時的功能,下面是man裏面的一段話:
pcap_dispatch() is used to collect and process packets. cnt specifies the maximum number of packets to process before returning. A cnt of -1 processes all the packets received in one buffer. A cnt of 0 processes all packets until an error occurs, EOF is reached, or the read times out (when doing live reads and a non-zero read timeout is specified). callback specifies a routine to be called with three arguments: a u_char pointer which is passed in from pcap_dispatch(), a pointer to the pcap_pkthdr struct (which precede the actual network headers and data), and a u_char pointer to the packet data. The number of packets read is returned. Zero is returned when EOF is reached in a ``savefile.'' A return of -1 indicates an error in which case pcap_perror() or pcap_geterr() may be used to display the error text.

另外的問題是,我們可能對抓取的包包太多而很頭痛,可能很多都不是我們感興趣的包,別急,pcap_compile和pcap_setfilter能幫我們解決問題。
/*pcap_4.c*/
#include
#include
#include
#include
#include
#include
#include
#include  
/* just print a count every time we have a packet...                        */
void my_callback(u_char *useless,const struct pcap_pkthdr* pkthdr,const u_char*
        packet)
{
    static int count = 1;
    fprintf(stdout,"%d, ",count);
    fflush(stdout);
    count++;
}
int main(int argc,char **argv)
{
    int i;
    char *dev;
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t* descr;
    const u_char *packet;
    struct pcap_pkthdr hdr;     /* pcap.h                    */
    struct ether_header *eptr;  /* net/ethernet.h            */
    struct bpf_program fp;      /* hold compiled program     */
    bpf_u_int32 maskp;          /* subnet mask               */
    bpf_u_int32 netp;           /* ip                        */
    if(argc != 2){ fprintf(stdout,"Usage: %s \"filter program\"\n"
            ,argv[0]);return 0;}
    /* grab a device to peak into... */
    dev = pcap_lookupdev(errbuf);
    if(dev == NULL)
    { fprintf(stderr,"%s\n",errbuf); exit(1); }
    /* ask pcap for the network address and mask of the device */
    pcap_lookupnet(dev,&netp,&maskp,errbuf);
    /* open device for reading this time lets set it in promiscuous
     * mode so we can monitor traffic to another machine             */
    descr = pcap_open_live(dev,BUFSIZ,1,-1,errbuf);
    if(descr == NULL)
    { printf("pcap_open_live(): %s\n",errbuf); exit(1); }
    /* Lets try and compile the program.. non-optimized */
    if(pcap_compile(descr,&fp,argv[1],0,netp) == -1)
    { fprintf(stderr,"Error calling pcap_compile\n"); exit(1); }
    /* set the compiled program as the filter */
    if(pcap_setfilter(descr,&fp) == -1)
    { fprintf(stderr,"Error setting filter\n"); exit(1); }
    /* ... and loop */
    pcap_loop(descr,-1,my_callback,NULL);
    return 0;
}
運行./pcap_4.c "host
www.google.com
"
然後在另外一個控制檯下面ping
www.baidu.com
哈哈
沒有反應吧
接着再ping
www.google.com
就看到1, 2, 3, 4, 5, 6,
ok
you got it!!

好了,就寫到這裏了

後面的事情就要靠自己去多寫程序了,有什麼問題就man 3 pcap吧~~


來源:http://linux.chinaunix.net/techdoc/beginner/2006/03/21/929567.shtml

發佈了6 篇原創文章 · 獲贊 3 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章