当前位置: 首页 > 工具软件 > libpcap > 使用案例 >

libpcap原理分析

何昆
2023-12-01

  

Libpcap是一个比较好用的抓包工具,tcpdump也是基于它实现的,下面介绍一下android上是如何利用它抓包的 

 

调用示例:由于它是基于c的代码,因此使用c++时记得extern "C",下面代码是结合libnet实现的:
#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
int main(int argc, char **argv) {

   char *dev = NULL; /* capture device name */
   char errbuf[PCAP_ERRBUF_SIZE]; /* error buffer */
   pcap_t *handle; /* packet capture handle */

   char filter_exp[] = "port 80"; /* filter expression [3] */
   struct bpf_program fp; /* compiled filter program (expression) */
   bpf_u_int32 mask; /* subnet mask */
   bpf_u_int32 net; /* ip */
   print_app_banner();

   /* check for capture device name on command-line */
   if (argc == 2) {
      dev = argv[1];
   } else if (argc > 2) {
      printf("error: unrecognized command-line options\n\n");
      print_app_usage();
      exit(EXIT_FAILURE);
   } else {
      /* find a capture device if not specified on command-line */
      dev = pcap_lookupdev(errbuf);
      if (dev == NULL) {
         printf("Couldn't find default device: %s\n", errbuf);
         exit(EXIT_FAILURE);
      }
   }

   /* get network number and mask associated with capture device */
   if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
      printf("Couldn't get netmask for device %s: %s\n", dev, errbuf);
      net = 0;
      mask = 0;
   }

   /* print capture info */
   printf("Device: %s\n", dev);
   printf("Filter expression: %s\n", filter_exp);

   /* open capture device */
   handle = pcap_open_live(dev, SNAP_LEN, 1, 1000, errbuf);
   if (handle == NULL) {
      printf("Couldn't open device %s: %s\n", dev, errbuf);
      exit(EXIT_FAILURE);
   }

   /* compile the filter expression */
   if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
      printf("Couldn't parse filter %s: %s\n", filter_exp,
            pcap_geterr(handle));
      exit(EXIT_FAILURE);
   }

   /* apply the compiled filter */
   if (pcap_setfilter(handle, &fp) == -1) {
      printf("Couldn't install filter %s: %s\n", filter_exp,
            pcap_geterr(handle));
      exit(EXIT_FAILURE);
   }

   pcap_setdirection(handle, PCAP_D_IN);

   /* now we can set our callback function */
   pcap_loop(handle, 0, got_packet, NULL);

   /* cleanup */
   pcap_freecode(&fp);
   pcap_close(handle);

   printf("\nCapture complete.\n");

   return 0;
}

上面主要函数有两个,分别是:

一、pcap_lookupdev:这个函数的作用就是寻找当前设备的网卡信息并且

返回第一个合适的网络接口的字符串指针,下面是它的具体实现(libpcap/inet.c):

char *pcap_lookupdev(errbuf)
   register char *errbuf;
{
   pcap_if_t *alldevs;
/* for old BSD systems, including bsdi3 */
#ifndef IF_NAMESIZE
#define IF_NAMESIZE IFNAMSIZ
#endif
   static char device[IF_NAMESIZE + 1];
   char *ret;

   if (pcap_findalldevs(&alldevs, errbuf) == -1)
      return (NULL);

   if (alldevs == NULL || (alldevs->flags & PCAP_IF_LOOPBACK)) {
      /*
       * There are no devices on the list, or the first device
       * on the list is a loopback device, which means there
       * are no non-loopback devices on the list.  This means
       * we can't return any device.
       *
       * XXX - why not return a loopback device?  If we can't
       * capture on it, it won't be on the list, and if it's
       * on the list, there aren't any non-loopback devices,
       * so why not just supply it as the default device?
       */
      (void)strlcpy(errbuf, "no suitable device found",
          PCAP_ERRBUF_SIZE);
      ret = NULL;
   } else {
      /*
       * Return the name of the first device on the list.
       */
      (void)strlcpy(device, alldevs->name, sizeof(device));
      ret = device;
   }

   pcap_freealldevs(alldevs);
   return (ret);
}
 
这里可以看到主要函数是调用了pcap_findalldevs,这个函数在不同平台上的实现不一样,这里主要介绍一下android平台的实现,(libpacp/fad-gifc.c):
int pcap_findalldevs(pcap_if_t **alldevsp, char *errbuf)
{
   pcap_if_t *devlist = NULL;
   register int fd;
   register struct ifreq *ifrp, *ifend, *ifnext;
   int n;
   struct ifconf ifc;
   char *buf = NULL;
   unsigned buf_size;
#if defined (HAVE_SOLARIS) || defined (HAVE_HPUX10_20_OR_LATER)
   char *p, *q;
#endif
   struct ifreq ifrflags, ifrnetmask, ifrbroadaddr, ifrdstaddr;
   struct sockaddr *netmask, *broadaddr, *dstaddr;
   size_t netmask_size, broadaddr_size, dstaddr_size;
   int ret = 0;

   /*
    * Create a socket from which to fetch the list of interfaces.
    */
   fd = socket(AF_INET, SOCK_DGRAM, 0);
   if (fd < 0) {
      (void)snprintf(errbuf, PCAP_ERRBUF_SIZE,
          "socket: %s", pcap_strerror(errno));
      return (-1);
   }

   /*
    * Start with an 8K buffer, and keep growing the buffer until
    * we have more than "sizeof(ifrp->ifr_name) + MAX_SA_LEN"
    * bytes left over in the buffer or we fail to get the
    * interface list for some reason other than EINVAL (which is
    * presumed here to mean "buffer is too small").
    */
   buf_size = 8192;
   for (;;) {
      buf = malloc(buf_size);
      if (buf == NULL) {
         (void)snprintf(errbuf, PCAP_ERRBUF_SIZE,
             "malloc: %s", pcap_strerror(errno));
         (void)close(fd);
         return (-1);
      }

      ifc.ifc_len = buf_size;
      ifc.ifc_buf = buf;
      memset(buf, 0, buf_size);
      if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0
          && errno != EINVAL) {
         (void)snprintf(errbuf, PCAP_ERRBUF_SIZE,
             "SIOCGIFCONF: %s", pcap_strerror(errno));
         (void)close(fd);
         free(buf);
         return (-1);
      }
      if (ifc.ifc_len < buf_size &&
          (buf_size - ifc.ifc_len) > sizeof(ifrp->ifr_name) + MAX_SA_LEN)
         break;
      free(buf);
      buf_size *= 2;
   }

   ifrp = (struct ifreq *)buf;
   ifend = (struct ifreq *)(buf + ifc.ifc_len);

   for (; ifrp < ifend; ifrp = ifnext) {
      /*
       * XXX - what if this isn't an IPv4 address?  Can
       * we still get the netmask, etc. with ioctls on
       * an IPv4 socket?
       *
       * The answer is probably platform-dependent, and
       * if the answer is "no" on more than one platform,
       * the way you work around it is probably platform-
       * dependent as well.
       */
      n = SA_LEN(&ifrp->ifr_addr) + sizeof(ifrp->ifr_name);
      if (n < sizeof(*ifrp))
         ifnext = ifrp + 1;
      else
         ifnext = (struct ifreq *)((char *)ifrp + n);

      /*
       * XXX - The 32-bit compatibility layer for Linux on IA-64
       * is slightly broken. It correctly converts the structures
       * to and from kernel land from 64 bit to 32 bit but 
       * doesn't update ifc.ifc_len, leaving it larger than the 
       * amount really used. This means we read off the end 
       * of the buffer and encounter an interface with an 
       * "empty" name. Since this is highly unlikely to ever 
       * occur in a valid case we can just finish looking for 
       * interfaces if we see an empty name.
       */
      if (!(*ifrp->ifr_name))
         break;

      /*
       * Skip entries that begin with "dummy".
       * XXX - what are these?  Is this Linux-specific?
       * Are there platforms on which we shouldn't do this?
       */
      if (strncmp(ifrp->ifr_name, "dummy", 5) == 0)
         continue;

      /*
       * Get the flags for this interface, and skip it if it's
       * not up.
       */
      strncpy(ifrflags.ifr_name, ifrp->ifr_name,
          sizeof(ifrflags.ifr_name));
      if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifrflags) < 0) {
         if (errno == ENXIO)
            continue;
         (void)snprintf(errbuf, PCAP_ERRBUF_SIZE,
             "SIOCGIFFLAGS: %.*s: %s",
             (int)sizeof(ifrflags.ifr_name),
             ifrflags.ifr_name,
             pcap_strerror(errno));
         ret = -1;
         break;
      }
      if (!(ifrflags.ifr_flags & IFF_UP))
         continue;

      /*
       * Get the netmask for this address on this interface.
       */
      strncpy(ifrnetmask.ifr_name, ifrp->ifr_name,
          sizeof(ifrnetmask.ifr_name));
      memcpy(&ifrnetmask.ifr_addr, &ifrp->ifr_addr,
          sizeof(ifrnetmask.ifr_addr));
      if (ioctl(fd, SIOCGIFNETMASK, (char *)&ifrnetmask) < 0) {
         if (errno == EADDRNOTAVAIL) {
            /*
             * Not available.
             */
            netmask = NULL;
            netmask_size = 0;
         } else {
            (void)snprintf(errbuf, PCAP_ERRBUF_SIZE,
                "SIOCGIFNETMASK: %.*s: %s",
                (int)sizeof(ifrnetmask.ifr_name),
                ifrnetmask.ifr_name,
                pcap_strerror(errno));
            ret = -1;
            break;
         }
      } else {
         netmask = &ifrnetmask.ifr_addr;
         netmask_size = SA_LEN(netmask);
      }

      /*
       * Get the broadcast address for this address on this
       * interface (if any).
       */
      if (ifrflags.ifr_flags & IFF_BROADCAST) {
         strncpy(ifrbroadaddr.ifr_name, ifrp->ifr_name,
             sizeof(ifrbroadaddr.ifr_name));
         memcpy(&ifrbroadaddr.ifr_addr, &ifrp->ifr_addr,
             sizeof(ifrbroadaddr.ifr_addr));
         if (ioctl(fd, SIOCGIFBRDADDR,
             (char *)&ifrbroadaddr) < 0) {
            if (errno == EADDRNOTAVAIL) {
               /*
                * Not available.
                */
               broadaddr = NULL;
               broadaddr_size = 0;
            } else {
               (void)snprintf(errbuf, PCAP_ERRBUF_SIZE,
                   "SIOCGIFBRDADDR: %.*s: %s",
                   (int)sizeof(ifrbroadaddr.ifr_name),
                   ifrbroadaddr.ifr_name,
                   pcap_strerror(errno));
               ret = -1;
               break;
            }
         } else {
            broadaddr = &ifrbroadaddr.ifr_broadaddr;
            broadaddr_size = SA_LEN(broadaddr);
         }
      } else {
         /*
          * Not a broadcast interface, so no broadcast
          * address.
          */
         broadaddr = NULL;
         broadaddr_size = 0;
      }

      /*
       * Get the destination address for this address on this
       * interface (if any).
       */
      if (ifrflags.ifr_flags & IFF_POINTOPOINT) {
         strncpy(ifrdstaddr.ifr_name, ifrp->ifr_name,
             sizeof(ifrdstaddr.ifr_name));
         memcpy(&ifrdstaddr.ifr_addr, &ifrp->ifr_addr,
             sizeof(ifrdstaddr.ifr_addr));
         if (ioctl(fd, SIOCGIFDSTADDR,
             (char *)&ifrdstaddr) < 0) {
            if (errno == EADDRNOTAVAIL) {
               /*
                * Not available.
                */
               dstaddr = NULL;
               dstaddr_size = 0;
            } else {
               (void)snprintf(errbuf, PCAP_ERRBUF_SIZE,
                   "SIOCGIFDSTADDR: %.*s: %s",
                   (int)sizeof(ifrdstaddr.ifr_name),
                   ifrdstaddr.ifr_name,
                   pcap_strerror(errno));
               ret = -1;
               break;
            }
         } else {
            dstaddr = &ifrdstaddr.ifr_dstaddr;
            dstaddr_size = SA_LEN(dstaddr);
         }
      } else {
         /*
          * Not a point-to-point interface, so no destination
          * address.
          */
         dstaddr = NULL;
         dstaddr_size = 0;
      }

主要过程是:
1、打开一个socket: fd = socket(AF_INET, SOCK_DGRAM, 0);AF_INET代表ipv4, SOCK_DGRAM代表数据包格式 
2、获取所有网络接口:ioctl(fd, SIOCGIFCONF, (char *)&ifc),SIOCGIFCONF就是获取所有接口的命令,ioctl是系统提供的和驱动交流的一种接口,看过android框架的应该比较清楚 
3、继续调用ioctl接口获取特定网络接口的相关信息,具体可以看代码注释 
4、注意的是打开socket需要访问网络权限<uses-permission android:name="android.permission.INTERNET" /> 
5、另外拥有网络权限你可能也会遇到“no suitable devices”错误,这个是因为你还需要root权限 
 
二、pcap_open_live函数,它的作用就是用你上一个函数返回的字符串指针打开网络接口,并且在dev目录下创建文件(bpf...)来存放网络数据,下面是它的实现:
pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms,
    char *ebuf)
{
   int fd;
   struct ifreq ifr;
   struct bpf_version bv;
#ifdef BIOCGDLTLIST
   struct bpf_dltlist bdl;
#endif
#if defined(BIOCGHDRCMPLT) && defined(BIOCSHDRCMPLT)
   u_int spoof_eth_src = 1;
#endif
   u_int v;
   pcap_t *p;
   struct bpf_insn total_insn;
   struct bpf_program total_prog;
   struct utsname osinfo;

#ifdef HAVE_DAG_API
   if (strstr(device, "dag")) {
      return dag_open_live(device, snaplen, promisc, to_ms, ebuf);
   }
#endif /* HAVE_DAG_API */

#ifdef BIOCGDLTLIST
   memset(&bdl, 0, sizeof(bdl));
#endif

   p = (pcap_t *)malloc(sizeof(*p));
   if (p == NULL) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
          pcap_strerror(errno));
      return (NULL);
   }
   memset(p, 0, sizeof(*p));
   fd = bpf_open(p, ebuf);
   if (fd < 0)
      goto bad;

   p->fd = fd;
   p->snapshot = snaplen;

   if (ioctl(fd, BIOCVERSION, (caddr_t)&bv) < 0) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s",
          pcap_strerror(errno));
      goto bad;
   }
   if (bv.bv_major != BPF_MAJOR_VERSION ||
       bv.bv_minor < BPF_MINOR_VERSION) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE,
          "kernel bpf filter out of date");
      goto bad;
   }

   /*
    * Try finding a good size for the buffer; 32768 may be too
    * big, so keep cutting it in half until we find a size
    * that works, or run out of sizes to try.  If the default
    * is larger, don't make it smaller.
    *
    * XXX - there should be a user-accessible hook to set the
    * initial buffer size.
    */
   if ((ioctl(fd, BIOCGBLEN, (caddr_t)&v) < 0) || v < 32768)
      v = 32768;
   for ( ; v != 0; v >>= 1) {
      /* Ignore the return value - this is because the call fails
       * on BPF systems that don't have kernel malloc.  And if
       * the call fails, it's no big deal, we just continue to
       * use the standard buffer size.
       */
      (void) ioctl(fd, BIOCSBLEN, (caddr_t)&v);

      (void)strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));
      if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) >= 0)
         break; /* that size worked; we're done */

      if (errno != ENOBUFS) {
         snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSETIF: %s: %s",
             device, pcap_strerror(errno));
         goto bad;
      }
   }

   if (v == 0) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE,
          "BIOCSBLEN: %s: No buffer size worked", device);
      goto bad;
   }

   /* Get the data link layer type. */
   if (ioctl(fd, BIOCGDLT, (caddr_t)&v) < 0) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGDLT: %s",
          pcap_strerror(errno));
      goto bad;
   }
#ifdef _AIX
   /*
    * AIX's BPF returns IFF_ types, not DLT_ types, in BIOCGDLT.
    */
   switch (v) {

   case IFT_ETHER:
   case IFT_ISO88023:
      v = DLT_EN10MB;
      break;

   case IFT_FDDI:
      v = DLT_FDDI;
      break;

   case IFT_ISO88025:
      v = DLT_IEEE802;
      break;

   case IFT_LOOP:
      v = DLT_NULL;
      break;

   default:
      /*
       * We don't know what to map this to yet.
       */
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "unknown interface type %u",
          v);
      goto bad;
   }
#endif
#if _BSDI_VERSION - 0 >= 199510
   /* The SLIP and PPP link layer header changed in BSD/OS 2.1 */
   switch (v) {

   case DLT_SLIP:
      v = DLT_SLIP_BSDOS;
      break;

   case DLT_PPP:
      v = DLT_PPP_BSDOS;
      break;

   case 11:   /*DLT_FR*/
      v = DLT_FRELAY;
      break;

   case 12:   /*DLT_C_HDLC*/
      v = DLT_CHDLC;
      break;
   }
#endif
#ifdef PCAP_FDDIPAD
   if (v == DLT_FDDI)
      p->fddipad = PCAP_FDDIPAD;
   else
      p->fddipad = 0;
#endif
   p->linktype = v;

#ifdef BIOCGDLTLIST
   /*
    * We know the default link type -- now determine all the DLTs
    * this interface supports.  If this fails with EINVAL, it's
    * not fatal; we just don't get to use the feature later.
    */
   if (ioctl(fd, BIOCGDLTLIST, (caddr_t)&bdl) == 0) {
      u_int i;
      int is_ethernet;

      bdl.bfl_list = (u_int *) malloc(sizeof(u_int) * (bdl.bfl_len + 1));
      if (bdl.bfl_list == NULL) {
         (void)snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
             pcap_strerror(errno));
         goto bad;
      }

      if (ioctl(fd, BIOCGDLTLIST, (caddr_t)&bdl) < 0) {
         (void)snprintf(ebuf, PCAP_ERRBUF_SIZE,
             "BIOCGDLTLIST: %s", pcap_strerror(errno));
         free(bdl.bfl_list);
         goto bad;
      }

      /*
       * OK, for real Ethernet devices, add DLT_DOCSIS to the
       * list, so that an application can let you choose it,
       * in case you're capturing DOCSIS traffic that a Cisco
       * Cable Modem Termination System is putting out onto
       * an Ethernet (it doesn't put an Ethernet header onto
       * the wire, it puts raw DOCSIS frames out on the wire
       * inside the low-level Ethernet framing).
       *
       * A "real Ethernet device" is defined here as a device
       * that has a link-layer type of DLT_EN10MB and that has
       * no alternate link-layer types; that's done to exclude
       * 802.11 interfaces (which might or might not be the
       * right thing to do, but I suspect it is - Ethernet <->
       * 802.11 bridges would probably badly mishandle frames
       * that don't have Ethernet headers).
       */
      if (p->linktype == DLT_EN10MB) {
         is_ethernet = 1;
         for (i = 0; i < bdl.bfl_len; i++) {
            if (bdl.bfl_list[i] != DLT_EN10MB) {
               is_ethernet = 0;
               break;
            }
         }
         if (is_ethernet) {
            /*
             * We reserved one more slot at the end of
             * the list.
             */
            bdl.bfl_list[bdl.bfl_len] = DLT_DOCSIS;
            bdl.bfl_len++;
         }
      }
      p->dlt_count = bdl.bfl_len;
      p->dlt_list = bdl.bfl_list;
   } else {
      if (errno != EINVAL) {
         (void)snprintf(ebuf, PCAP_ERRBUF_SIZE,
             "BIOCGDLTLIST: %s", pcap_strerror(errno));
         goto bad;
      }
   }
#endif

   /*
    * If this is an Ethernet device, and we don't have a DLT_ list,
    * give it a list with DLT_EN10MB and DLT_DOCSIS.  (That'd give
    * 802.11 interfaces DLT_DOCSIS, which isn't the right thing to
    * do, but there's not much we can do about that without finding
    * some other way of determining whether it's an Ethernet or 802.11
    * device.)
    */
   if (p->linktype == DLT_EN10MB && p->dlt_count == 0) {
      p->dlt_list = (u_int *) malloc(sizeof(u_int) * 2);
      /*
       * If that fails, just leave the list empty.
       */
      if (p->dlt_list != NULL) {
         p->dlt_list[0] = DLT_EN10MB;
         p->dlt_list[1] = DLT_DOCSIS;
         p->dlt_count = 2;
      }
   }
      
#if defined(BIOCGHDRCMPLT) && defined(BIOCSHDRCMPLT)
   /*
    * Do a BIOCSHDRCMPLT, if defined, to turn that flag on, so
    * the link-layer source address isn't forcibly overwritten.
    * (Should we ignore errors?  Should we do this only if
    * we're open for writing?)
    *
    * XXX - I seem to remember some packet-sending bug in some
    * BSDs - check CVS log for "bpf.c"?
    */
   if (ioctl(fd, BIOCSHDRCMPLT, &spoof_eth_src) == -1) {
      (void)snprintf(ebuf, PCAP_ERRBUF_SIZE,
          "BIOCSHDRCMPLT: %s", pcap_strerror(errno));
      goto bad;
   }
#endif
   /* set timeout */
   if (to_ms != 0) {
      /*
       * XXX - is this seconds/nanoseconds in AIX?
       * (Treating it as such doesn't fix the timeout
       * problem described below.)
       */
      struct timeval to;
      to.tv_sec = to_ms / 1000;
      to.tv_usec = (to_ms * 1000) % 1000000;
      if (ioctl(p->fd, BIOCSRTIMEOUT, (caddr_t)&to) < 0) {
         snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSRTIMEOUT: %s",
             pcap_strerror(errno));
         goto bad;
      }
   }

#ifdef _AIX
#ifdef BIOCIMMEDIATE
   /*
    * Darren Reed notes that
    *
    * On AIX (4.2 at least), if BIOCIMMEDIATE is not set, the
    * timeout appears to be ignored and it waits until the buffer
    * is filled before returning.  The result of not having it
    * set is almost worse than useless if your BPF filter
    * is reducing things to only a few packets (i.e. one every
    * second or so).
    *
    * so we turn BIOCIMMEDIATE mode on if this is AIX.
    *
    * We don't turn it on for other platforms, as that means we
    * get woken up for every packet, which may not be what we want;
    * in the Winter 1993 USENIX paper on BPF, they say:
    *
    * Since a process might want to look at every packet on a
    * network and the time between packets can be only a few
    * microseconds, it is not possible to do a read system call
    * per packet and BPF must collect the data from several
    * packets and return it as a unit when the monitoring
    * application does a read.
    *
    * which I infer is the reason for the timeout - it means we
    * wait that amount of time, in the hopes that more packets
    * will arrive and we'll get them all with one read.
    *
    * Setting BIOCIMMEDIATE mode on FreeBSD (and probably other
    * BSDs) causes the timeout to be ignored.
    *
    * On the other hand, some platforms (e.g., Linux) don't support
    * timeouts, they just hand stuff to you as soon as it arrives;
    * if that doesn't cause a problem on those platforms, it may
    * be OK to have BIOCIMMEDIATE mode on BSD as well.
    *
    * (Note, though, that applications may depend on the read
    * completing, even if no packets have arrived, when the timeout
    * expires, e.g. GUI applications that have to check for input
    * while waiting for packets to arrive; a non-zero timeout
    * prevents "select()" from working right on FreeBSD and
    * possibly other BSDs, as the timer doesn't start until a
    * "read()" is done, so the timer isn't in effect if the
    * application is blocked on a "select()", and the "select()"
    * doesn't get woken up for a BPF device until the buffer
    * fills up.)
    */
   v = 1;
   if (ioctl(p->fd, BIOCIMMEDIATE, &v) < 0) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCIMMEDIATE: %s",
          pcap_strerror(errno));
      goto bad;
   }
#endif /* BIOCIMMEDIATE */
#endif /* _AIX */

   if (promisc) {
      /* set promiscuous mode, okay if it fails */
      if (ioctl(p->fd, BIOCPROMISC, NULL) < 0) {
         snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCPROMISC: %s",
             pcap_strerror(errno));
      }
   }

   if (ioctl(fd, BIOCGBLEN, (caddr_t)&v) < 0) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s",
          pcap_strerror(errno));
      goto bad;
   }
   p->bufsize = v;
   p->buffer = (u_char *)malloc(p->bufsize);
   if (p->buffer == NULL) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
          pcap_strerror(errno));
      goto bad;
   }
#ifdef _AIX
   /* For some strange reason this seems to prevent the EFAULT 
    * problems we have experienced from AIX BPF. */
   memset(p->buffer, 0x0, p->bufsize);
#endif

   /*
    * If there's no filter program installed, there's
    * no indication to the kernel of what the snapshot
    * length should be, so no snapshotting is done.
    *
    * Therefore, when we open the device, we install
    * an "accept everything" filter with the specified
    * snapshot length.
    */
   total_insn.code = (u_short)(BPF_RET | BPF_K);
   total_insn.jt = 0;
   total_insn.jf = 0;
   total_insn.k = snaplen;

   total_prog.bf_len = 1;
   total_prog.bf_insns = &total_insn;
   if (ioctl(p->fd, BIOCSETF, (caddr_t)&total_prog) < 0) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSETF: %s",
          pcap_strerror(errno));
      goto bad;
   }

   /*
    * On most BPF platforms, either you can do a "select()" or
    * "poll()" on a BPF file descriptor and it works correctly,
    * or you can do it and it will return "readable" if the
    * hold buffer is full but not if the timeout expires *and*
    * a non-blocking read will, if the hold buffer is empty
    * but the store buffer isn't empty, rotate the buffers
    * and return what packets are available.
    *
    * In the latter case, the fact that a non-blocking read
    * will give you the available packets means you can work
    * around the failure of "select()" and "poll()" to wake up
    * and return "readable" when the timeout expires by using
    * the timeout as the "select()" or "poll()" timeout, putting
    * the BPF descriptor into non-blocking mode, and read from
    * it regardless of whether "select()" reports it as readable
    * or not.
    *
    * However, in FreeBSD 4.3 and 4.4, "select()" and "poll()"
    * won't wake up and return "readable" if the timer expires
    * and non-blocking reads return EWOULDBLOCK if the hold
    * buffer is empty, even if the store buffer is non-empty.
    *
    * This means the workaround in question won't work.
    *
    * Therefore, on FreeBSD 4.3 and 4.4, we set "p->selectable_fd"
    * to -1, which means "sorry, you can't use 'select()' or 'poll()'
    * here".  On all other BPF platforms, we set it to the FD for
    * the BPF device; in NetBSD, OpenBSD, and Darwin, a non-blocking
    * read will, if the hold buffer is empty and the store buffer
    * isn't empty, rotate the buffers and return what packets are
    * there (and in sufficiently recent versions of OpenBSD
    * "select()" and "poll()" should work correctly).
    *
    * XXX - what about AIX?
    */
   p->selectable_fd = p->fd;  /* assume select() works until we know otherwise */
   if (uname(&osinfo) == 0) {
      /*
       * We can check what OS this is.
       */
      if (strcmp(osinfo.sysname, "FreeBSD") == 0) {
         if (strncmp(osinfo.release, "4.3-", 4) == 0 ||
              strncmp(osinfo.release, "4.4-", 4) == 0)
            p->selectable_fd = -1;
      }
   }

   p->read_op = pcap_read_bpf;
   p->inject_op = pcap_inject_bpf;
   p->setfilter_op = pcap_setfilter_bpf;
   p->setdirection_op = pcap_setdirection_bpf;
   p->set_datalink_op = pcap_set_datalink_bpf;
   p->getnonblock_op = pcap_getnonblock_fd;
   p->setnonblock_op = pcap_setnonblock_fd;
   p->stats_op = pcap_stats_bpf;
   p->close_op = pcap_close_common;

   return (p);
 bad:
   (void)close(fd);
   if (p->dlt_list != NULL)
      free(p->dlt_list);
   free(p);
   return (NULL);
}


我们看到首先通过fd = bpf_open(p, ebuf);来得到一个文件操作符,这个操作符就是创建的/dev/bpf..文件,目的是将这个文件用来存储网络接口数据,然后通过一系列ioctl命令设置网络参数,下面是这些设置参数值:
#define BIOCGBLEN  _IOR(B,102, u_int)
#define    BIOCSBLEN  _IOWR(B,102, u_int)
#define    BIOCSETF   _IOW(B,103, struct bpf_program)
#define    BIOCFLUSH  _IO(B,104)
#define BIOCPROMISC    _IO(B,105)
#define    BIOCGDLT   _IOR(B,106, u_int)
#define BIOCGETIF  _IOR(B,107, struct ifreq)
#define BIOCSETIF  _IOW(B,108, struct ifreq)
#define BIOCSRTIMEOUT  _IOW(B,109, struct timeval)
#define BIOCGRTIMEOUT  _IOR(B,110, struct timeval)
#define BIOCGSTATS _IOR(B,111, struct bpf_stat)
#define BIOCIMMEDIATE  _IOW(B,112, u_int)
#define BIOCVERSION    _IOR(B,113, struct bpf_version)
#define BIOCSTCPF  _IOW(B,114, struct bpf_program)
#define BIOCSUDPF  _IOW(B,115, struct bpf_program)
#else
#define    BIOCGBLEN  _IOR('B',102, u_int)
#define    BIOCSBLEN  _IOWR('B',102, u_int)
#define    BIOCSETF   _IOW('B',103, struct bpf_program)
#define    BIOCFLUSH  _IO('B',104)
#define BIOCPROMISC    _IO('B',105)
#define    BIOCGDLT   _IOR('B',106, u_int)
#define BIOCGETIF  _IOR('B',107, struct ifreq)
#define BIOCSETIF  _IOW('B',108, struct ifreq)
#define BIOCSRTIMEOUT  _IOW('B',109, struct timeval)
#define BIOCGRTIMEOUT  _IOR('B',110, struct timeval)
#define BIOCGSTATS _IOR('B',111, struct bpf_stat)
#define BIOCIMMEDIATE  _IOW('B',112, u_int)
#define BIOCVERSION    _IOR('B',113, struct bpf_version)
#define BIOCSTCPF  _IOW('B',114, struct bpf_program)
#define BIOCSUDPF  _IOW('B',115, struct bpf_program)
#endif
 
后面只要监听前面的/dev/bpf..文件读取数据就行了,注意的是这里读取的数据是链路层的帧数据,需要自己解析
 
 
 类似资料: