Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Packet Capture With libpcap

Для кого эта статья: Для понимания этой статьи необходим минимум знаний по сетям. Например , что такое пакет , как он отсылается ,network layers и т.д. Также должны иметь место быть базовые знания по си. Если вы c/c++ master, то это просто супер.

man 3 pcap Вам нужен компилятор и библиотека libpcap. Мы сконцентрируемя на одном слое - Ethernet datalink layer. Если вы используете несколько сетевых интерфейсов , например token ring, вы должны будете использовать другие хидеры - datalink headers. Все примеры этой статьи были оттестированы на линуксе с ядром 2.2.14. Будет ли это компилироваться и работать на других версиях - автор ничего не гарантирует.

Intro: Сразу возникает несколько вопросов: "Что мы подразумеваем под захватом пакетов ?!" и "Что такое libpcap!?"

  • Packet Capture, означает просто "grab packets" или "захват пакетов".
    Нам нужно понять , как мы можем использовать возможности операционной системы для этого. Рассмотрим сетевую карту , которая принимает пакеты из сети. Ось должна определить , какого типа этот пакет, поставить ему в соответствие Ethernet header. Пусть это UDP packet.Ему ставится в соответствие UDP-хидер , и пакет загружается приложением, которому он был послан. Т.о. мы можем захватить любой пакет и глянуть на его хидер. Мы можем узнать порт, на который он пришел, хост , с которого он пришел, и т.д.
  • libpcap "обеспечивает независимый от реализации захват пакетов на основе свойств операционной системы (Stevens, UNP page. 707). libpcap - это библиотека , которую мы собираемся использовать для захвата пакетов с сетевой карты напрямую. Тут есть несколько подходов: BPF (Berkeley Packet Filter), DLPI (Data Link Provider Interface) и SOCKET_PACKET type sockets (Linux only).

Getting Started Итак , у нас есть сетевая карта. Рассмотрим первую программу:

%>gcc ldev.c -lpcap

 
 /* ldev.c
    Martin Casado
    
    To compile:
    >gcc ldev.c -lpcap
 
    Looks for an interface, and lists the network ip
    and mask associated with that interface.
 */
 #include <stdio.h>
 #include <stdlib.h>
 #include <pcap.h>  /* GIMME a libpcap plz! */
 #include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
 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;
 }
 
 

Если запустить эту программу , то она может распечатать примерно следующее:

DEV: eth0
NET: 192.168.12.0
MASK: 255.255.255.0

Если ваше DEV не eth0, не eth1 и т.д. , то вас ждут проблемы.

В этой программе мы попросили libpcap распечатать некоторые спецификации сетевого интерфейса.
Так , в Unix eth0 - сетевая карта номер 1. И мы будем грабить именно это устройство.

Capturing Our First Packet

Итак , мы определили интерфейс и готовы к захвату пакетов. Рассмотрим пример - testpcap1.c:

/***************************************************
 * file:     testpcap1.c
 * Date:     Thu Mar 08 17:14:36 MST 2001 
 * Author:   Martin Casado
 * Location: LAX Airport (hehe)
 *
 * Simple single packet capture program
 *****************************************************/
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <pcap.h> /* if this gives you an error try pcap/pcap.h */
 #include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netinet/if_ether.h> /* includes net/ethernet.h */
 
 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 */
 
     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@pepe libpcap]# ./testpcap1
 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]# 
 

destination address - в данном случае должен быть гейтом. Проверим роутинг:
[root@pepe libpcap]# /sbin/route 
 Kernel IP routing table
 Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
 192.168.1.0     *               255.255.255.0   U     0      0        0 eth0
 127.0.0.0       *               255.0.0.0       U     0      0        0 lo
 default         192.168.1.1     0.0.0.0         UG    0      0        0 eth0
 
теперь наберем команду arp для получения адреса ethernet:
[root@pepe libpcap]# /sbin/arp 
 Address			HWtype	HWaddress	    Flags Mask		  Iface
 192.168.1.1            	ether   00:20:78:D1:E8:01   C                     eth0
 

Обратите внимание на то , что адрес гейта - 192.168.1.1 -соответствует destination address пакета. Все пакеты , посылаемые с вашей машины , если они не прдназначены для конкретной машины, посылаются через гейт.А откуда наш компьютер знает адрес гейта? Хороший вопрос. Компьютер должен знать адрес гейта по определению. Это же нам показала команда arp, которая берет его из специальной внутренней таблицы-кеша, в которой каждый ip-адрес поставлен в соответствие hardware-адресу.

Этот Hardware-адрес получается с использованием Address Resolution Protocol или ARP. ARP описан в RFC826. Когда вы посылаете пакет, ядро проверяет arp cache , если там destination address. Давайте удалим destination address из кеша:

[root@pepe libpcap]# /sbin/arp -n    # look at arp cache 
 Address			HWtype	HWaddress	    Flags Mask		  Iface
 192.168.1.1            	ether   00:20:78:D1:E8:01   C                     eth0
 
 [root@pepe libpcap]# /sbin/arp -n -d 192.168.1.1  #delete gateqay entrance
 [root@pepe libpcap]# /sbin/arp -n   #make sure gateway hardware addy is empty             
 Address			HWtype	HWaddress	    Flags Mask		  Iface
 192.168.1.1            	        (incomplete)                              eth0
 [root@pepe libpcap]# ./a.out
 DEV: eth0
 Grabbed packet of length 42
 Recieved at time..... Tue Mar 13 00:36:49 2001
 
 Ethernet address length is 14
 Ethernet type hex:806 dec:2054 is an ARP packet
  Destination Address:   ff:ff:ff:ff:ff:ff
  Source Address:   0:a0:cc:56:c2:91
 [root@pepe libpcap]#echo YAY 
 
После чего компьютер пытается сделать широковещательный запрос - broadcast (ff:ff:ff:ff:ff:ff), пытаясь отыскать IP 192.168.1.1.

Допустим наша сетка имеет пропускную способность в 10Mb/s...

/* 10Mb/s ethernet header */
 
 struct ether_header
 {
   u_int8_t  ether_dhost[ETH_ALEN];	/* destination eth addr	*/
   u_int8_t  ether_shost[ETH_ALEN];	/* source ether addr	*/
   u_int16_t ether_type;		        /* packet type ID field	*/
 } __attribute__ ((__packed__));
 
Первое поле ETH_ALEN есть destination ethernet address пакета. Второе поле ETH_ALEN - источник. Третье поле - тип пакета. id-шники протоколов из net/ethernet.h
/* Ethernet protocol ID's */
 #define	ETHERTYPE_PUP		0x0200      /* Xerox PUP */
 #define	ETHERTYPE_IP		0x0800		/* IP */
 #define	ETHERTYPE_ARP		0x0806		/* Address resolution */
 #define	ETHERTYPE_REVARP	0x8035		/* Reverse ARP */
 
Нам итересен прежде всего IP и немного ARP.

Writing a Basic Packet Capture Engine

В этом разделе обсуждается такая тема,как написание енжина по захвату пакетов. Нам нужно научиться анализировать и фильтровать пакеты.

Рассмотрим следующий библиотечный метод pcap :

  • int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
Он будет использован нами как один из базовых. Во время цикла pcap_loop(..) будет происходить захват пакетов, которые будут передаваться в callback function типа pcap_handler.

 typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *);
 
Здесь представляют интерес аргументы 2 и 3, хидер для pcap packet и константа u_char.

В качестве примера напишем программу , которая захватывает в цикле n пакетов.

/**********************************************************************
 * file:   testpcap2.c
 * date:   2001-Mar-14 12:14:19 AM 
 * Author: Martin Casado
 * Last Modified:2001-Mar-14 12:14:11 AM
 *
 * Description: Q&D proggy to demonstrate the use of pcap_loop
 *
 **********************************************************************/
 
 #include <pcap.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netinet/if_ether.h>
 
 /* 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)*/
     /* If you are wondering what the user argument is all about, so am I!!   */
     pcap_loop(descr,atoi(argv[1]),my_callback,NULL);
 
     fprintf(stdout,"\nDone processing packets... wheew!\n");
     return 0;
 }
 

Allright then, lets give her a whirl!

[root@pepe libpcap]# gcc testpcap2.c -lpcap
 [root@pepe libpcap]# ./a.out 7
  1, 2, 3, 4, Come on baby sayyy you love me!!! 5, 6, 7, Tiiimmmeesss!!  
 Done processing packets... wheew!
 [root@pepe libpcap]# 
 
Как вы видите , my_callback(...) была вызвана 7 раз. Весь анализ пакетов мы положили внутрь my_callback. Но это не совсем правильное решение. Первая проблема в том , что pcap_loop(..) может подвиснуть в случае отсутствия пакетов. Было бы неплохо установить тайм-аут на чтение. Одним из параметров функции pcap_open_live(..) является тайм-аут в милли-секундах. Функция pcap_loop игнорирует этот аргумент, но pcap_dispatch(..) нет! Смотрите описание pcap_dispatch() в man page


************
pcap_dispatch() используется для сбора пакетов. cnt - определяет максимальное число пакетов. cnt = -1 говорит о том , что все пакеты возвращаются в одном буфере. cnt = 0 обрабатывает пакеты до тех пор , пока не произойдет ошибка. callback - подпрограмма , вызываемая 3-м аргументом:u_char - указатель на нее, туда входит структура pcap_pkthdr и данные. pcap_dispatch() возвращает число прочитанных пакетов. -1 означает ошибку, для ее вывода можно использовать pcap_perror() или pcap_geterr().
************

Нам неинтересны все пакеты,гуляющие по сети. Что нужно для того , чтобы захватить пакеты , зная номер хоста и номер порта ? Использовать pcap_compile(..) и pcap_setfilter(...).

Мы можем захватить все пакеты и отсортировать их. Но трафик может быть нешуточным. В библиотеке libpcap есть интерфейс,позволяющий определить точно , какие пакеты нас интересуют. Для этого нужно передать фильтр в форме строки функции pcap_compile(). Есть волшебная программа tcpdump и ее man page. Как правило,эта программа стоит по умолчанию везде. Tcpdump - это фактически враппер для libpcap. tcpdump man page явно описывает синтаксис и семантику языка фильтров. Вот часть этого мана:

              The expression consists of one or more primitives.  Primitives usu
               ally  consist  of  an  id  (name or number) preceded by one or more
               qualifiers.  There are three different kinds of qualifier:
 
               type   qualifiers say what kind of thing  the  id  name  or  number
                      refers  to.   Possible  types are host, net and port.  E.g.,
                      `host foo', `net 128.3', `port 20'.  If  there  is  no  type
                      qualifier, host is assumed.
 
               dir    qualifiers specify a particular transfer direction to and/or
                      from id.  Possible directions are src, dst, src or  dst  and
                      src  and dst.  E.g., `src foo', `dst net 128.3', `src or dst
                      port ftp-data'.  If there is no dir qualifier, src or dst is
                      assumed.  For `null' link layers (i.e. point to point proto
                      cols such as slip) the inbound and outbound  qualifiers  can
                      be used to specify a desired direction.
 
               proto  qualifiers  restrict  the  match  to  a particular protocol.
                      Possible protos are: ether, fddi,  ip,  arp,  rarp,  decnet,
                      lat, sca, moprc, mopdl, tcp and udp.  E.g., `ether src foo',
                      `arp net 128.3', `tcp port 21'.  If there is no proto quali
                      fier,  all  protocols  consistent with the type are assumed.
                      E.g., `src foo' means `(ip or arp or rarp) src foo'  (except
                      the latter is not legal syntax), `net bar' means `(ip or arp
                      or rarp) net bar' and `port 53' means  `(tcp  or  udp)  port
                      53'.
 
               In  addition  to the above, there are some special `primitive' key
               words that don't follow  the  pattern:  gateway,  broadcast,  less,
               greater  and  arithmetic  expressions.   All of these are described
               below.
 
               More complex filter expressions are built up  by  using  the  words
               and,  or  and  not  to combine primitives.  E.g., `host foo and not
               port ftp and not port ftp-data'.  To save typing, identical  quali
               fier  lists can be omitted.  E.g., `tcp dst port ftp or ftp-data or
               domain' is exactly the same as `tcp dst port ftp or  tcp  dst  port
               ftp-data or tcp dst port domain'.
 
               Allowable primitives are:
 
               dst host host
                      True  if  the  IP  destination  field of the packet is host,
                      which may be either an address or a name.
 
               src host host
                      True if the IP source field of the packet is host.
 
               host host
                      True if either the IP source or destination of the packet is
                      host.   Any  of  the above host expressions can be prepended
                      with the keywords, ip, arp, or rarp as in:
                           ip host host
                      which is equivalent to:
                           ether proto \ip and host host
                      If host is a name with multiple IP addresses,  each  address
                      will be checked for a match.
 
               ether dst ehost
                      True  if  the  ethernet destination address is ehost.  Ehost
                      may be either a name  from  /etc/ethers  or  a  number  (see
                      ethers(3N) for numeric format).
 
               ether src ehost
                      True if the ethernet source address is ehost.
 
               ether host ehost
                      True if either the ethernet source or destination address is
                      ehost.
 
               gateway host
                      True if the packet used host as a gateway.  I.e., the ether
                      net  source  or destination address was host but neither the
                      IP source nor the IP destination was host.  Host must  be  a
                      name  and  must be found in both /etc/hosts and /etc/ethers.
                      (An equivalent expression is
                           ether host ehost and not host host
                      which can be used with either names or numbers  for  host  /
                      ehost.)
 
               dst net net
                      True  if the IP destination address of the packet has a net
                      work number of net. Net may be either a name from  /etc/net
                      works or a network number (see networks(4) for details).
 
               src net net
                      True  if  the  IP source address of the packet has a network
                      number of net.
 
               net net
                      True if either the IP source or destination address  of  the
                      packet has a network number of net.
 
               net net mask mask
                      True  if  the  IP address matches net with the specific net
                      mask.  May be qualified with src or dst.
 
               net net/len
                      True if the IP address matches net a netmask len bits  wide.
                      May be qualified with src or dst.
 
               dst port port
                      True if the packet is ip/tcp or ip/udp and has a destination
                      port value of port.  The port can be a number or a name used
                      in  /etc/services  (see  tcp(4P) and udp(4P)).  If a name is
                      used, both the port number and protocol are checked.   If  a
                      number  or  ambiguous  name is used, only the port number is
                      checked (e.g., dst port 513 will print both tcp/login  traf
                      fic  and  udp/who  traffic,  and port domain will print both
                      tcp/domain and udp/domain traffic).
 
               src port port
                      True if the packet has a source port value of port.
 
               port port
                      True if either the source or destination port of the  packet
                      is port.  Any of the above port expressions can be prepended
                      with the keywords, tcp or udp, as in:
                           tcp src port port
                      which matches only tcp packets whose source port is port.
 
               less length
                      True if the packet has  a  length  less  than  or  equal  to
                      length.  This is equivalent to:
                           len <= length.
 
               greater length
                      True  if  the  packet  has a length greater than or equal to
                      length.  This is equivalent to:
                           len >= length.
 
               ip proto protocol
                      True if the packet is an ip packet (see ip(4P)) of  protocol
                      type protocol.  Protocol can be a number or one of the names
                      icmp, igrp, udp, nd, or tcp.  Note that the identifiers tcp,
                      udp,  and  icmp  are  also  keywords and must be escaped via
                      backslash (\), which is \\ in the C-shell.
 
               ether broadcast
                      True if the packet is an  ethernet  broadcast  packet.   The
                      ether keyword is optional.
 
               ip broadcast
                      True if the packet is an IP broadcast packet.  It checks for
                      both the all-zeroes and all-ones broadcast conventions,  and
                      looks up the local subnet mask.
 
               ether multicast
                      True  if  the  packet  is an ethernet multicast packet.  The
                      ether keyword is optional.  This is shorthand for  `ether[0]
                      & 1 != 0'.
 
               ip multicast
                      True if the packet is an IP multicast packet.
 
               ether proto protocol
                      True  if the packet is of ether type protocol.  Protocol can
                      be a number or a name like ip, arp,  or  rarp.   Note  these
                      identifiers  are also keywords and must be escaped via back
                      slash (\).  [In the  case  of  FDDI  (e.g.,  `fddi  protocol
                      arp'), the protocol identification comes from the 802.2 Log
                      ical Link Control (LLC) header, which is usually layered  on
                      top  of the FDDI header.  Tcpdump assumes, when filtering on
                      the protocol identifier, that all FDDI  packets  include  an
                      LLC  header,  and  that  the LLC header is in so-called SNAP
                      format.]
 
               ip, arp, rarp, decnet
                      Abbreviations for:
                           ether proto p where p is one of the above protocols.
 
               tcp, udp, icmp
                      Abbreviations for:
                           ip proto p
                      where p is one of the above protocols.
 
               expr relop expr
                      True  if the relation holds, where relop is one of >,
                      <, >=, <=, =, !=, and expr is an arithmetic
                      expression composed  of integer constants (expressed in
                      standard C syntax), the nor mal binary operators [+, -,
                      *, /, &, |], a length  operator, and  special  packet
                      data accessors.  To access data inside the packet, use the
                      following syntax: proto [ expr : size ] Proto is one of
                      ether, fddi, ip, arp,  rarp,  tcp,  udp,  or icmp,  and
                      indicates the protocol layer for the index opera tion.
                      The byte offset, relative to the  indicated  protocol
                      layer, is given by expr.  Size is optional and indicates
                      the number of bytes in the field of interest; it can  be
                      either one,  two,  or four, and defaults to one.  The
                      length opera tor, indicated by the keyword len, gives the
                      length  of  the packet.
 
                      For example, `ether[0] & 1 != 0' catches all multicast traf
                      fic.  The expression `ip[0] & 0xf != 5' catches all IP pack
                      ets  with  options.  The  expression  `ip[6:2] & 0x1fff = 0'
                      catches only unfragmented datagrams and frag zero  of  frag
                      mented  datagrams.   This check is implicitly applied to the
                      tcp and udp index operations.  For instance,  tcp[0]  always
                      means  the first byte of the TCP header, and never means the
                      first byte of an intervening fragment.
 
    Primitives may be combined using:
 
                      A parenthesized group of primitives and operators (parenthe
                      ses are special to the Shell and must be escaped).
 
                      Negation (`!' or `not').
 
                      Concatenation (`&&' or `and').
 
                      Alternation (`||' or `or').
 
               Negation  has  highest  precedence.   Alternation and concatenation
               have equal precedence and  associate  left  to  right.   Note  that
               explicit  and  tokens, not juxtaposition, are now required for con
               catenation.
 
               If an identifier is given without a keyword, the most  recent  key
               word is assumed.  For example,
                    not host vs and ace
               is short for
                    not host vs and host ace
               which should not be confused with
                    not ( host vs or ace )
 
               Expression  arguments  can  be passed to tcpdump as either a single
               argument or as multiple arguments, whichever  is  more  convenient.
               Generally,  if  the expression contains Shell metacharacters, it is
               easier to pass it as a single, quoted argument.  Multiple arguments
               are concatenated with spaces before being parsed.
 
 EXAMPLES
        To print all packets arriving at or departing from sundown:
               tcpdump host sundown
 
        To print traffic between helios and either hot or ace:
               tcpdump host helios and \( hot or ace \)
 
        To print all IP packets between ace and any host except helios:
               tcpdump ip host ace and not helios
 
        To print all traffic between local hosts and hosts at Berkeley:
               tcpdump net ucb-ether
 
        To  print  all  ftp  traffic through internet gateway snup: (note that the
        expression is quoted to prevent  the  shell  from  (mis-)interpreting  the
        parentheses):
               tcpdump 'gateway snup and (port ftp or ftp-data)'
 
        To print traffic neither sourced from nor destined for local hosts (if you
        gateway to one other net, this stuff should never make it onto your  local
        net).
               tcpdump ip and not net localnet
 
        To  print  the start and end packets (the SYN and FIN packets) of each TCP
        conversation that involves a non-local host.
               tcpdump 'tcp[13] & 3 != 0 and not src and dst net localnet'
 
        To print IP packets longer than 576 bytes sent through gateway snup:
               tcpdump 'gateway snup and ip[2:2] > 576'
 
        To print IP broadcast or multicast packets that were not sent via ethernet
        broadcast or multicast:
               tcpdump 'ether[0] & 1 = 0 and ip[16] >= 224'
 
        To  print  all  ICMP packets that are not echo requests/replies (i.e., not
        ping packets):
               tcpdump 'icmp[0] != 8 and icmp[0] != 0"
 
 

Используем эту информацию в качестве отправной точки. Допустим,в моей локальной сетке 2 машины - линуксовая и виндовая, которые соединены через хаб. Если я сетевую карту на линуксовой машине установлю в promiscuous mode, я смогу увидеть весь трафик,идущий через виндовую машину.

Рассмотрим следующую программу :
/**********************************************************************
 * file:   testpcap3.c
 * date:   Sat Apr 07 23:23:02 PDT 2001  
 * Author: Martin Casado
 * Last Modified:2001-Apr-07 11:23:05 PM
 *
 * Investigate using filter programs with pcap_compile() and
 * pcap_setfilter()
 *
 **********************************************************************/
 
 #include <pcap.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netinet/if_ether.h> 
 
 /* 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;
 }
 

Эта программа принимает строку от пользователя, компилирует ее и устанавливает ее в качестве фильтра- при этом нужно сделать запрос на google.com:

[root@localhost libpcap]# gcc testpcap3.c -lpcap
 [root@localhost libpcap]# ./a.out "host www.google.com"
 (** try and ping www.slashdot.org ... nothing **)
 (** try and ping www.google.com **)
 1, 2, 3, 4, 5, 6, 
 (** hurray! **)
 

Все указывает на то , что фильтр работает. Попробуем теперь захватить пакеты с виндовой машины, в то время как она пытается сделать запрос на battle.net:

[root@localhost libpcap]# ./a.out "src 192.168.1.104"
 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 
 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
 
Похоже, и тут мы попали в точку. Теперь неплохо бы расшифровать полученную информацию. В следующем разделе рассмотрим анализ пакетов.

Packet Analysis

В этом разделе мы затронем тему выделения информации. Для этого неплохо бы знать соответственные RFC: RFC 791 (IP),RFC 768 (UDP),RFC 826 (ARP),RFC 792 (ICMPv4) и конечно RFC 793 (TCPv4).

Настоятельно рекомендую вам использовать другой сниффер для подтверждения всего того, что я тут наговорил.Например,tcpdump или ethereal. Оба этих снифера могут анализировать как пакеты, так и сами данные.

Следующая программа будет построена на основе предыдущих, с небольшим изменением callback function, которая передается в pcap_loop() или pcap_dispatch(). Рассмотрим следующий пример:

/**********************************************************************
 * file:   pcap_main.c
 * date:   Tue Jun 19 20:07:49 PDT 2001  
 * Author: Martin Casado
 * Last Modified:2001-Jun-23 12:55:45 PM
 *
 * Description: 
 * main program to test different call back functions
 * to pcap_loop();
 *
 * Compile with:
 * gcc -Wall -pedantic pcap_main.c -lpcap (-o foo_err_something) 
 *
 * Usage:
 * a.out (# of packets) "filter string"
 *
 **********************************************************************/
 
 #include <pcap.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netinet/if_ether.h>
 #include <net/ethernet.h>
 #include <netinet/ether.h>
 
 /*
  * workhorse function, we will be modifying this function 
  */
 void my_callback(u_char *args,const struct pcap_pkthdr* pkthdr,c
 u_char*
         packet)
 {
 }
 
 
 int main(int argc,char **argv)
 { 
     char *dev; 
     char errbuf[PCAP_ERRBUF_SIZE];
     pcap_t* de
     struct bpf_program fp;      /* hold compiled program     */
     bpf_u_int32 maskp;          /* subnet mask               */
     bpf_u_int32 netp;           /* ip                        */
     u_char* args = NULL;
 
     /* Options must be passed in as a string because I am lazy */
     if(argc < 2){ 
         fprintf(stdout,"Usage: %s numpackets \"options\"\n",argv[0]);
         return 0;
     }
 
     /* grab a device to peak into... */
     dev = pcap_lookupdev(errbuf);
     if(dev == NULL)
     { printf("%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. NOTE: defaulting to
      * promiscuous mode*/
     descr = pcap_open_live(dev,BUFSIZ,1,-1,errbuf);
     if(descr == NULL)
     { printf("pcap_open_live(): %s\n",errbuf); exit(1); }
 
 
     if(argc > 2)
     {
         /* Lets try and compile the program.. non-optimized */
         if(pcap_compile(descr,&fp,argv[2],0,netp) ==
         { 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,atoi(argv[1]),my_callback,args);
 
     fprintf(stdout,"\nfinished\n");
     return 0;
 }
 

Эта программа будет использована в качестве заготовки для дальнейших программ. Вы могли заметить, что я передаю u_char* ( NULL) в pcap_loop(). Это делается для передачи первого аргумента. Итак, нам нужно прочитать тело TCP packets.

Важнейшим элементом любого хидера является тип пакета. Структура struct ether_header из net/ethernet.h:

/* This is a name for the 48 bit ethernet address available on many
    systems.  */
 
 struct ether_addr
 {
   u_int8_t ether_addr_octet[ETH_ALEN];
 } __attribute__ ((__packed__));
 
 /* 10Mb/s ethernet header */
 struct ether_header
 {
   u_int8_t  ether_dhost[ETH_ALEN];	/* destination eth addr	*/
   u_int8_t  ether_shost[ETH_ALEN];	/* source ether addr	*/
   u_int16_t ether_type;		        /* packet type ID field	*/
 } __attribute__ ((__packed__));
 
Это дает нам возможность конвертации ethernet headers в читаемый ascii.
/* Convert 48 bit Ethernet ADDRess to ASCII.  */
 
 extern char *ether_ntoa (__const struct ether_addr *__addr) __THROW;
 extern char *ether_ntoa_r (__const struct ether_addr *__addr, char *__buf)
      __THROW;
 
 /* Convert ASCII string S to 48 bit Ethernet address.  */
 extern struct ether_addr *ether_aton (__const char *__asc) __THROW;
 extern struct ether_addr *ether_aton_r (__const char *__asc,
 					struct ether_addr *__addr) __THROW;
 
 
конвертация ethernet address в HOSTNAME
/* Map HOSTNAME to 48 bit Ethernet address.  */
 
 extern int ether_hostton (__const char *__hostname, struct ether_addr *__addr)
      __THROW;
 
 
Кое-что тут было позаимствовано из небезизвестного Steven's-а. А вот и callback function для управления ethernet headers, распечатки source и destination - адресов:

 
 u_int16_t handle_ethernet
         (u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
         packet);
 
 /* looking at ethernet headers */
 
 void my_callback(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
         packet)
 {
     u_int16_t type = handle_ethernet(args,pkthdr,packet);
 
     if(type == ETHERTYPE_IP)
     {/* handle IP packet */
     }else if(type == ETHERTYPE_ARP)
     {/* handle arp packet */
     }
     else if(type == ETHERTYPE_REVARP)
     {/* handle reverse arp packet */
     }/* ignorw */
 }
 
 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,"ethernet header source: %s"
             ,ether_ntoa(eptr->ether_shost));
     fprintf(stdout," destination: %s "
             ,ether_ntoa(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);
     }
     fprintf(stdout,"\n");
 
     return eptr->ether_type;
 }
 
 
 
В исходниках вы найдете пример disect1.c.

Давайте теперь подробнее посмотрим на IP-хидер:

IP:
RFC 791 :

 3.1 Internet Header Format
 
        A summary of the contents of the internet header follows: 
 
            0                   1                   2                   3   
            0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           |Version|  IHL  |Type of Service|          Total Length         |
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           |         Identification        |Flags|      Fragment Offset    |
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           |  Time to Live |    Protocol   |         Header Checksum       |
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           |                       Source Address                          |
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           |                    Destination Address                        |
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           |                    Options                    |    Padding    |
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         
                            Example Internet Datagram Header
         
                                       Figure 4.
         
          Note that each tick mark represents one bit position.
 
netinet/ip.h
 
 struct ip
   {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
     unsigned int ip_hl:4;		/* header length */
     unsigned int ip_v:4;		/* version */
 #endif
 #if __BYTE_ORDER == __BIG_ENDIAN
     unsigned int ip_v:4;		/* version */
     unsigned int ip_hl:4;		/* header length */
 #endif
     u_int8_t 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_int8_t ip_ttl;			/* time to live */
     u_int8_t ip_p;			/* protocol */
     u_short ip_sum;			/* checksum */
     struct in_addr ip_src, ip_dst;	/* source and dest address */
   };
 
 
tcpdump-ская версия длины хидера:
 
 struct my_ip 
 	u_int8_t	ip_vhl;		/* header length, version */
 #define IP_V(ip)	(((ip)->ip_vhl & 0xf0) >> 4)
 #define IP_HL(ip)	((ip)->ip_vhl & 0x0f)
 	u_int8_t	ip_tos;		/* type of service */
 	u_int16_t	ip_len;		/* total length */
 	u_int16_t	ip_id;		/* identification */
 	u_int16_t	ip_off;		/* fragment offset field */
 #define	IP_DF 0x4000			/* dont fragment flag */
 #define	IP_MF 0x2000			/* more fragments flag */
 #define	IP_OFFMASK 0x1fff		/* mask for fragmenting bits */
 	u_int8_t	ip_ttl;		/* time to live */
 	u_int8_t	ip_p;		/* protocol */
 	u_int16_t	ip_sum;		/* checksum */
 	struct	in_addr ip_src,ip_dst;	/* source and dest address */
 };
 
Теперь фрагмент из следующей программы - disect2.c:
 
 u_char* handle_IP
         (u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
         packet)
 {
     const struct my_ip* ip;
     u_int length = pkthdr-&len;
     u_int hlen,off,version;
     int i;
 
     int len;
 
     /* jump pass the ethernet header */
     ip = (struct my_ip*)(packet + sizeof(struct ether_header));
     length -= sizeof(struct ether_header); 
 
     /* check to see we have a packet of valid length */
     if (length < sizeof(struct my_ip))
     {
         printf("truncated ip %d",length);
         return NULL;
     }
 
     len     = ntohs(ip->ip_len);
     hlen    = IP_HL(ip); /* header length */
     version = IP_V(ip);/* ip version */
 
     /* check version */
     if(version != 4)
     {
       fprintf(stdout,"Unknown version %d\n",version);
       return NULL;
     }
 
     /* 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->ip_off);
     if((off &apm; 0x1fff) == 0 )/* aka no 1's in first 13 bits */
     {/* print SOURCE DESTINATION hlen version len offset */
         fprintf(stdout,"IP: ");
         fprintf(stdout,"%s ",
                 inet_ntoa(ip->ip_src));
         fprintf(stdout,"%s %d %d %d %d\n",
                 inet_ntoa(ip->ip_dst),
                 hlen,version,len,off);
     }
 
     return NULL;
 }
 
 
Вывод программы - в это время я пытаюсь за-телнетиться на 134.114.90.1:
[root@localhost libpcap]# ./a.out 5
 ETH: 0:10:a4:8b:d3:b4 ff:ff:ff:ff:ff:ff (ARP) 42
 ETH: 0:20:78:d1:e8:1 0:10:a4:8b:d3:b4 (ARP) 60
 ETH: 0:10:a4:8b:d3:b4 0:20:78:d1:e8:1 (IP) 74
 IP: 192.168.1.100 134.114.90.1 5 4 60 16384
 ETH: 0:20:78:d1:e8:1 0:10:a4:8b:d3:b4 (IP) 60
 IP: 134.114.90.1 192.168.1.100 5 4 40 0
 
  • my computer:gateways IP (192.168.1.100)?
    ETH: 0:10:a4:8b:d3:b4 ff:ff:ff:ff:ff:ff (ARP) 42
  • gateway: совпало!
    ETH: 0:20:78:d1:e8:1 0:10:a4:8b:d3:b4 (ARP) 60
  • my computer(through gateway): Hello Mr. 134.114.90.1 can we talk?
    ETH: 0:10:a4:8b:d3:b4 0:20:78:d1:e8:1 (IP) 74 IP: 192.168.1.100 134.114.90.1 5 4 60 16384
  • 134.114.90.1: No, piss off, I'm not listening
    ETH: 0:20:78:d1:e8:1 0:10:a4:8b:d3:b4 (IP) 60 IP: 134.114.90.1 192.168.1.100 5 4 40 0

Теперь можно обобщить :

  • Все пакеты посылаются через ethernet
  • ethernet header определяет тип протокола
  • IP - один из этих типов
Полезно взглянуть на исходники tcpdump и на следующие методы:
  • ether_if_print (print-ether.c)
  • ip_print (print-ip.c)
  • tcp_print (print-tcp.c)
  • udp_print (print-udp.c)

Тут лежат исходники к данной статье

Тут лежит пример реализации сниффера

Тут лежат исходники tcpdump

Тут лежит библиотека libpcap-1.0.0

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье