Implement support for multiple capture interfaces.
authorEmil Mikulic <emikulic@gmail.com>
Sun, 8 Jul 2012 09:24:42 +0000 (19:24 +1000)
committerEmil Mikulic <emikulic@gmail.com>
Sun, 8 Jul 2012 09:24:42 +0000 (19:24 +1000)
Makefile.in
acct.c
acct.h
cap.c
cap.h
darkstat.c
html.c
localip.c
localip.h
opt.h
queue.h

index a66d4c8..b0dbf2e 100644 (file)
@@ -126,7 +126,7 @@ acct.o: acct.c acct.h decode.h addr.h conv.h daylog.h graph_db.h err.h \
 addr.o: addr.c addr.h
 bsd.o: bsd.c bsd.h config.h cdefs.h
 cap.o: cap.c acct.h cdefs.h cap.h config.h conv.h decode.h addr.h err.h \
- hosts_db.h localip.h now.h opt.h
+ hosts_db.h localip.h now.h opt.h queue.h str.h
 conv.o: conv.c conv.h err.h cdefs.h
 darkstat.o: darkstat.c acct.h cap.h cdefs.h config.h conv.h daylog.h \
  graph_db.h db.h dns.h err.h http.h hosts_db.h addr.h localip.h ncache.h \
diff --git a/acct.c b/acct.c
index c02c8cd..17aa4f0 100644 (file)
--- a/acct.c
+++ b/acct.c
@@ -133,9 +133,8 @@ acct_init_localnet(const char *spec)
    verbosef("   local network mask: %s", addr_to_str(&localmask));
 }
 
-static int
-addr_is_local(const struct addr * const a)
-{
+static int addr_is_local(const struct addr * const a,
+                         const struct local_ips *local_ips) {
    if (is_localip(a, local_ips))
       return 1;
    if (a->family == IPv4 && using_localnet4) {
@@ -149,9 +148,8 @@ addr_is_local(const struct addr * const a)
 }
 
 /* Account for the given packet summary. */
-void
-acct_for(const struct pktsummary * const sm)
-{
+void acct_for(const struct pktsummary * const sm,
+              const struct local_ips * const local_ips) {
    struct bucket *hs = NULL, *hd = NULL;
    struct bucket *ps, *pd;
    int dir_in, dir_out;
@@ -180,18 +178,15 @@ acct_for(const struct pktsummary * const sm)
    acct_total_bytes += sm->len;
 
    /* Graphs. */
-   dir_out = addr_is_local(&(sm->src));
-   dir_in  = addr_is_local(&(sm->dst));
+   dir_out = addr_is_local(&sm->src, local_ips);
+   dir_in  = addr_is_local(&sm->dst, local_ips);
 
    /* Traffic staying within the network isn't counted. */
-   if (dir_in == 1 && dir_out == 1)
-      dir_in = dir_out = 0;
-
-   if (dir_out) {
+   if (dir_out && !dir_in) {
       daylog_acct((uint64_t)sm->len, GRAPH_OUT);
       graph_acct((uint64_t)sm->len, GRAPH_OUT);
    }
-   if (dir_in) {
+   if (dir_in && !dir_out) {
       daylog_acct((uint64_t)sm->len, GRAPH_IN);
       graph_acct((uint64_t)sm->len, GRAPH_IN);
    }
@@ -200,7 +195,7 @@ acct_for(const struct pktsummary * const sm)
 
    /* Hosts. */
    hosts_db_reduce();
-   if (!opt_want_local_only || addr_is_local(&sm->src)) {
+   if (!opt_want_local_only || dir_out) {
       hs = host_get(&(sm->src));
       hs->out   += sm->len;
       hs->total += sm->len;
@@ -208,7 +203,7 @@ acct_for(const struct pktsummary * const sm)
       hs->u.host.last_seen_mono = now_mono();
    }
 
-   if (!opt_want_local_only || addr_is_local(&sm->dst)) {
+   if (!opt_want_local_only || dir_in) {
       hd = host_get(&(sm->dst));
       hd->in    += sm->len;
       hd->total += sm->len;
diff --git a/acct.h b/acct.h
index 3c2828b..b4edb7f 100644 (file)
--- a/acct.h
+++ b/acct.h
@@ -7,10 +7,12 @@
 #include <stdint.h>
 
 struct pktsummary;
+struct local_ips;
 
 extern uint64_t acct_total_packets, acct_total_bytes;
 
 void acct_init_localnet(const char *spec);
-void acct_for(const struct pktsummary * const sm);
+void acct_for(const struct pktsummary * const sm,
+              const struct local_ips * const local_ips);
 
-/* vim:set ts=3 sw=3 tw=78 expandtab: */
+/* vim:set ts=3 sw=3 tw=80 expandtab: */
diff --git a/cap.c b/cap.c
index 21e48fe..dda5cb8 100644 (file)
--- a/cap.c
+++ b/cap.c
@@ -1,7 +1,7 @@
 /* darkstat 3
  * copyright (c) 2001-2011 Emil Mikulic.
  *
- * cap.c: interface to libpcap.
+ * cap.c: capture packets, and hand them off to decode and acct.
  *
  * You may use, modify and redistribute this file under the terms of the
  * GNU General Public License version 2. (see COPYING.GPL)
@@ -18,6 +18,8 @@
 #include "localip.h"
 #include "now.h"
 #include "opt.h"
+#include "queue.h"
+#include "str.h"
 
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <string.h>
 #include <unistd.h>
 
+char *title_interfaces = NULL; /* for html.c */
+
 /* The cap process life-cycle:
- *
- * Init           - cap_init()
- * Fill fd_set    - cap_fd_set()
- * Poll           - cap_poll()
- * Stop           - cap_stop()
+ *  - cap_add_ifname() one or more times
+ *  - cap_add_filter() zero or more times
+ *  - cap_start() once to start listening
+ * Once per main loop:
+ *  - cap_fd_set() to update the select() set
+ *  - cap_poll() to read from ready pcap fds
+ * Shutdown:
+ *  - cap_stop()
  */
 
-/* Globals - only useful within this module. */
-static pcap_t *pcap = NULL;
-static int pcap_fd = -1;
-static const struct linkhdr *linkhdr = NULL;
+struct strnode {
+   STAILQ_ENTRY(strnode) entries;
+   const char *str;
+};
 
-#define CAP_TIMEOUT 500 /* granularity of capture buffer, in milliseconds */
+struct cap_iface {
+   STAILQ_ENTRY(cap_iface) entries;
 
-/* ---------------------------------------------------------------------------
- * Init pcap.  Exits on failure.
- */
-void
-cap_init(const char *device, const char *filter, int promisc)
-{
+   const char *name;
+   const char *filter;
+   pcap_t *pcap;
+   int fd;
+   const struct linkhdr *linkhdr;
+   struct local_ips local_ips;
+};
+
+static STAILQ_HEAD(cli_ifnames_head, strnode) cli_ifnames =
+   STAILQ_HEAD_INITIALIZER(cli_ifnames);
+
+static STAILQ_HEAD(cli_filters_head, strnode) cli_filters =
+   STAILQ_HEAD_INITIALIZER(cli_filters);
+
+static STAILQ_HEAD(cap_ifs_head, cap_iface) cap_ifs =
+   STAILQ_HEAD_INITIALIZER(cap_ifs);
+
+/* The read timeout passed to pcap_open_live() */
+#define CAP_TIMEOUT_MSEC 500
+
+void cap_add_ifname(const char *ifname) {
+   struct strnode *n = xmalloc(sizeof(*n));
+   n->str = ifname;
+   STAILQ_INSERT_TAIL(&cli_ifnames, n, entries);
+}
+
+void cap_add_filter(const char *filter) {
+   struct strnode *n = xmalloc(sizeof(*n));
+   n->str = filter;
+   STAILQ_INSERT_TAIL(&cli_filters, n, entries);
+}
+
+static void cap_set_filter(pcap_t *pcap, const char *filter) {
+   struct bpf_program prog;
+   char *tmp_filter;
+
+   if (filter == NULL)
+      return;
+
+   tmp_filter = xstrdup(filter);
+   if (pcap_compile(
+         pcap,
+         &prog,
+         tmp_filter,
+         1,          /* optimize */
+         0)          /* netmask */
+         == -1)
+      errx(1, "pcap_compile(): %s", pcap_geterr(pcap));
+
+   if (pcap_setfilter(pcap, &prog) == -1)
+      errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));
+
+   pcap_freecode(&prog);
+   free(tmp_filter);
+}
+
+static void cap_start_one(struct cap_iface *iface, const int promisc) {
    char errbuf[PCAP_ERRBUF_SIZE], *tmp_device;
    int linktype, snaplen, waited;
 
-   /* pcap doesn't like device being const */
-   tmp_device = xstrdup(device);
+   /* pcap wants a non-const interface name string */
+   tmp_device = xstrdup(iface->name);
+   if (iface->filter)
+      verbosef("capturing on interface '%s' with filter '%s'",
+         tmp_device, iface->filter);
+   else
+      verbosef("capturing on interface '%s' with no filter", tmp_device);
 
    /* Open packet capture descriptor. */
    waited = 0;
    for (;;) {
       errbuf[0] = '\0'; /* zero length string */
-      pcap = pcap_open_live(
+      iface->pcap = pcap_open_live(
          tmp_device,
          1,          /* snaplen, irrelevant at this point */
          0,          /* promisc, also irrelevant */
-         CAP_TIMEOUT,
+         CAP_TIMEOUT_MSEC,
          errbuf);
-      if (pcap != NULL) break; /* success! */
+      if (iface->pcap != NULL)
+         break; /* success! */
 
       if ((opt_wait_secs != -1) && strstr(errbuf, "device is not up")) {
          if ((opt_wait_secs > 0) && (waited >= opt_wait_secs))
@@ -85,16 +150,16 @@ cap_init(const char *device, const char *filter, int promisc)
    }
 
    /* Work out the linktype and what snaplen we need. */
-   linktype = pcap_datalink(pcap);
+   linktype = pcap_datalink(iface->pcap);
    verbosef("linktype is %d", linktype);
    if ((linktype == DLT_EN10MB) && opt_want_macs)
       hosts_db_show_macs = 1;
-   linkhdr = getlinkhdr(linktype);
-   if (linkhdr == NULL)
+   iface->linkhdr = getlinkhdr(linktype);
+   if (iface->linkhdr == NULL)
       errx(1, "unknown linktype %d", linktype);
-   if (linkhdr->decoder == NULL)
+   if (iface->linkhdr->decoder == NULL)
       errx(1, "no decoder for linktype %d", linktype);
-   snaplen = getsnaplen(linkhdr);
+   snaplen = getsnaplen(iface->linkhdr);
    if (opt_want_pppoe) {
       snaplen += PPPOE_HDR_LEN;
       if (linktype != DLT_EN10MB)
@@ -102,6 +167,11 @@ cap_init(const char *device, const char *filter, int promisc)
    }
    verbosef("calculated snaplen minimum %d", snaplen);
 #ifdef linux
+   /* FIXME: actually due to libpcap moving to mmap (!!!)
+    * work out which version and fix the way we do capture
+    * on linux:
+    */
+
    /* Ubuntu 9.04 has a problem where requesting snaplen <= 60 will
     * give us 42 bytes, and we need at least 54 for TCP headers.
     *
@@ -114,16 +184,16 @@ cap_init(const char *device, const char *filter, int promisc)
    verbosef("using snaplen %d", snaplen);
 
    /* Close and re-open pcap to use the new snaplen. */
-   pcap_close(pcap);
+   pcap_close(iface->pcap);
    errbuf[0] = '\0'; /* zero length string */
-   pcap = pcap_open_live(
+   iface->pcap = pcap_open_live(
       tmp_device,
       snaplen,
       promisc,
-      CAP_TIMEOUT,
+      CAP_TIMEOUT_MSEC,
       errbuf);
 
-   if (pcap == NULL)
+   if (iface->pcap == NULL)
       errx(1, "pcap_open_live(): %s", errbuf);
 
    if (errbuf[0] != '\0') /* not zero length anymore -> warning */
@@ -136,80 +206,115 @@ cap_init(const char *device, const char *filter, int promisc)
    else
       verbosef("capturing in non-promiscuous mode");
 
-   /* Set filter expression, if any. */
-   if (filter != NULL)
-   {
-      struct bpf_program prog;
-      char *tmp_filter = xstrdup(filter);
-      if (pcap_compile(
-            pcap,
-            &prog,
-            tmp_filter,
-            1,          /* optimize */
-            0)          /* netmask */
-            == -1)
-         errx(1, "pcap_compile(): %s", pcap_geterr(pcap));
-
-      if (pcap_setfilter(pcap, &prog) == -1)
-         errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));
-
-      pcap_freecode(&prog);
-      free(tmp_filter);
-   }
-
-   pcap_fd = pcap_fileno(pcap);
+   cap_set_filter(iface->pcap, iface->filter);
+   iface->fd = pcap_fileno(iface->pcap);
 
    /* set non-blocking */
 #ifdef linux
-   if (pcap_setnonblock(pcap, 1, errbuf) == -1)
+   if (pcap_setnonblock(iface->pcap, 1, errbuf) == -1)
       errx(1, "pcap_setnonblock(): %s", errbuf);
 #else
-{ int one = 1;
-   if (ioctl(pcap_fd, FIONBIO, &one) == -1)
-      err(1, "ioctl(pcap_fd, FIONBIO)"); }
+   {
+      int one = 1;
+      if (ioctl(iface->fd, FIONBIO, &one) == -1)
+         err(1, "ioctl(iface->fd, FIONBIO)");
+   }
 #endif
 
 #ifdef BIOCSETWF
-{
-   /* Deny all writes to the socket */
-   struct bpf_insn bpf_wfilter[] = { BPF_STMT(BPF_RET+BPF_K, 0) };
-   int wf_len = sizeof(bpf_wfilter) / sizeof(struct bpf_insn);
-   struct bpf_program pr;
-
-   pr.bf_len = wf_len;
-   pr.bf_insns = bpf_wfilter;
-
-   if (ioctl(pcap_fd, BIOCSETWF, &pr) == -1)
-      err(1, "ioctl(pcap_fd, BIOCSETFW)");
-   verbosef("filtered out BPF writes");
-}
+   {
+      /* Deny all writes to the socket */
+      struct bpf_insn bpf_wfilter[] = { BPF_STMT(BPF_RET+BPF_K, 0) };
+      int wf_len = sizeof(bpf_wfilter) / sizeof(struct bpf_insn);
+      struct bpf_program pr;
+
+      pr.bf_len = wf_len;
+      pr.bf_insns = bpf_wfilter;
+
+      if (ioctl(iface->fd, BIOCSETWF, &pr) == -1)
+         err(1, "ioctl(iface->fd, BIOCSETFW)");
+      verbosef("filtered out BPF writes");
+   }
 #endif
 
 #ifdef BIOCLOCK
    /* set "locked" flag (no reset) */
-   if (ioctl(pcap_fd, BIOCLOCK) == -1)
-      err(1, "ioctl(pcap_fd, BIOCLOCK)");
+   if (ioctl(iface->fd, BIOCLOCK) == -1)
+      err(1, "ioctl(iface->fd, BIOCLOCK)");
    verbosef("locked down BPF for security");
 #endif
 }
 
-/*
- * Set pcap_fd in the given fd_set.
- */
-void
-cap_fd_set(
+void cap_start(const int promisc) {
+   struct str *ifs = str_make();
+
+   assert(STAILQ_EMPTY(&cap_ifs));
+   if (STAILQ_EMPTY(&cli_ifnames))
+      errx(1, "no interfaces specified");
+
+   /* For each ifname */
+   while (!STAILQ_EMPTY(&cli_ifnames)) {
+      struct strnode *ifname, *filter = NULL;
+      struct cap_iface *iface = xmalloc(sizeof(*iface));
+
+      ifname = STAILQ_FIRST(&cli_ifnames);
+      STAILQ_REMOVE_HEAD(&cli_ifnames, entries);
+
+      if (!STAILQ_EMPTY(&cli_filters)) {
+         filter = STAILQ_FIRST(&cli_filters);
+         STAILQ_REMOVE_HEAD(&cli_filters, entries);
+      }
+
+      iface->name = ifname->str;
+      iface->filter = (filter == NULL) ? NULL : filter->str;
+      iface->pcap = NULL;
+      iface->fd = -1;
+      iface->linkhdr = NULL;
+      localip_init(&iface->local_ips);
+      STAILQ_INSERT_TAIL(&cap_ifs, iface, entries);
+      cap_start_one(iface, promisc);
+
+      free(ifname);
+      if (filter) free(filter);
+
+      if (str_len(ifs) == 0)
+         str_append(ifs, iface->name);
+      else
+         str_appendf(ifs, ", %s", iface->name);
+   }
+   verbosef("all capture interfaces prepared");
+
+   /* Deallocate extra filters, if any. */
+   while (!STAILQ_EMPTY(&cli_filters)) {
+      struct strnode *filter = STAILQ_FIRST(&cli_filters);
+
+      verbosef("ignoring extraneous filter '%s'", filter->str);
+      STAILQ_REMOVE_HEAD(&cli_filters, entries);
+      free(filter);
+   }
+
+   str_appendn(ifs, "", 1); /* NUL terminate */
+   {
+      size_t _;
+      str_extract(ifs, &_, &title_interfaces);
+   }
+}
+
 #ifdef linux
-   fd_set *read_set _unused_,
-   int *max_fd _unused_,
-   struct timeval *timeout,
+# define _unused_on_linux_ _unused_
 #else
-   fd_set *read_set,
-   int *max_fd,
-   struct timeval *timeout _unused_,
+# define _unused_on_linux_
 #endif
-   int *need_timeout)
-{
-   assert(*need_timeout == 0); /* we're first to get a shot at this */
+
+/*
+ * Set pcap_fd in the given fd_set.
+ */
+void cap_fd_set(fd_set *read_set _unused_on_linux_,
+                int *max_fd _unused_on_linux_,
+                struct timeval *timeout,
+                int *need_timeout) {
+   assert(*need_timeout == 0); /* we're first to get a shot at the fd_set */
+
 #ifdef linux
    /*
     * Linux's BPF is immediate, so don't select() as it will lead to horrible
@@ -217,36 +322,41 @@ cap_fd_set(
     */
    *need_timeout = 1;
    timeout->tv_sec = 0;
-   timeout->tv_usec = CAP_TIMEOUT * 1000; /* msec->usec */
+   timeout->tv_usec = CAP_TIMEOUT_MSEC * 1000;
 #else
-   /* We have a BSD-like BPF, we can select() on it. */
-   FD_SET(pcap_fd, read_set);
-   *max_fd = MAX(*max_fd, pcap_fd);
+   {
+      struct cap_iface *iface;
+      STAILQ_FOREACH(iface, &cap_ifs, entries) {
+         /* We have a BSD-like BPF, we can select() on it. */
+         FD_SET(iface->fd, read_set);
+         *max_fd = MAX(*max_fd, iface->fd);
+      }
+   }
 #endif
 }
 
 unsigned int cap_pkts_recv = 0, cap_pkts_drop = 0;
 
-static void
-cap_stats_update(void)
-{
-   struct pcap_stat ps;
+static void cap_stats_update(void) {
+   struct cap_iface *iface;
 
-   if (pcap_stats(pcap, &ps) != 0) {
-      warnx("pcap_stats(): %s", pcap_geterr(pcap));
-      return;
+   cap_pkts_recv = 0;
+   cap_pkts_drop = 0;
+   STAILQ_FOREACH(iface, &cap_ifs, entries) {
+      struct pcap_stat ps;
+      if (pcap_stats(iface->pcap, &ps) != 0) {
+         warnx("pcap_stats('%s'): %s", iface->name, pcap_geterr(iface->pcap));
+         return;
+      }
+      cap_pkts_recv += ps.ps_recv;
+      cap_pkts_drop += ps.ps_drop;
    }
-
-   cap_pkts_recv = ps.ps_recv;
-   cap_pkts_drop = ps.ps_drop;
 }
 
-/*
- * Print hexdump of received packet.
- */
-static void
-hexdump(const u_char *buf, const uint32_t len)
-{
+/* Print hexdump of received packet to stdout, for debugging. */
+static void hexdump(const u_char *buf,
+                    const uint32_t len,
+                    const struct linkhdr *linkhdr) {
    uint32_t i, col;
 
    printf("packet of %u bytes:\n", len);
@@ -268,137 +378,145 @@ hexdump(const u_char *buf, const uint32_t len)
 }
 
 /* Callback function for pcap_dispatch() which chains to the decoder specified
- * in linkhdr struct.
+ * in the linkhdr struct.
  */
-static void callback(u_char *user _unused_,
+static void callback(u_char *user,
                      const struct pcap_pkthdr *pheader,
                      const u_char *pdata) {
+   const struct cap_iface * const iface = (struct cap_iface *)user;
    struct pktsummary sm;
 
    if (opt_want_hexdump)
-      hexdump(pdata, pheader->caplen);
+      hexdump(pdata, pheader->caplen, iface->linkhdr);
    memset(&sm, 0, sizeof(sm));
-   if (linkhdr->decoder(pheader, pdata, &sm))
-      acct_for(&sm);
+   if (iface->linkhdr->decoder(pheader, pdata, &sm))
+      acct_for(&sm, &iface->local_ips);
 }
 
-/*
- * Process any packets currently in the capture buffer.
- */
-void
-cap_poll(fd_set *read_set
-#ifdef linux
-   _unused_
-#endif
-)
-{
-   int total, ret;
+/* Process any packets currently in the capture buffer. */
+void cap_poll(fd_set *read_set _unused_on_linux_) {
+   int ret, premature = 1;
+   struct cap_iface *iface;
 
+   STAILQ_FOREACH(iface, &cap_ifs, entries) {
 #ifndef linux /* We don't use select() on Linux. */
-   if (!FD_ISSET(pcap_fd, read_set)) {
-      verbosef("cap_poll premature");
-      return;
-   }
+      if (FD_ISSET(iface->fd, read_set))
+         premature = 0;
+      else
+         continue; /* skip this interface */
 #endif
 
-   /* Once per capture poll, check our IP address.  It's used in accounting
-    * for traffic graphs.
-    */
-   localip_update(opt_interface, local_ips);
-
-   total = 0;
-   for (;;) {
-      struct timespec t;
-
-      timer_start(&t);
-      ret = pcap_dispatch(
-            pcap,
-            -1,               /* count, -1 = entire buffer */
-            callback,
-            NULL);            /* user */
-      if (ret < 0) {
-         warnx("pcap_dispatch(): %s", pcap_geterr(pcap));
-         return;
-      }
-      timer_stop(&t, 2*CAP_TIMEOUT*1000000, "pcap_dispatch took too long");
-
-      /* Despite count = -1, Linux will only dispatch one packet at a time. */
-      total += ret;
+      /* Once per capture poll, check our IP address.  It's used in accounting
+       * for traffic graphs.
+       */
+      localip_update(iface->name, &iface->local_ips);
+
+      for (;;) {
+         struct timespec t;
+
+         timer_start(&t);
+         ret = pcap_dispatch(
+               iface->pcap,
+               -1, /* count = entire buffer */
+               callback,
+               (u_char*)iface); /* user = struct to pass to callback */
+
+         if (ret < 0) {
+            warnx("pcap_dispatch('%s'): %s",
+               iface->name, pcap_geterr(iface->pcap));
+            continue;
+         }
+         timer_stop(&t,
+                    2 * CAP_TIMEOUT_MSEC * 1000000,
+                    "pcap_dispatch took too long");
+
+         if (0) /* debugging */
+            verbosef("iface '%s' got %d pkts", iface->name, ret);
 
 #ifdef linux
-      /* keep looping until we've dispatched all the outstanding packets */
-      if (ret == 0) break;
+         /* keep looping until we've dispatched all the outstanding packets */
+         if (ret == 0)
+            break;
+         else
+            premature = 0;
 #else
-      /* we get them all on the first shot */
-      break;
+         /* we get them all on the first shot */
+         break;
 #endif
+      }
    }
+   if (premature)
+      verbosef("cap_poll() premature");
    cap_stats_update();
 }
 
-void
-cap_stop(void)
-{
-   pcap_close(pcap);
+void cap_stop(void) {
+   while (!STAILQ_EMPTY(&cap_ifs)) {
+      struct cap_iface *iface = STAILQ_FIRST(&cap_ifs);
+
+      STAILQ_REMOVE_HEAD(&cap_ifs, entries);
+      pcap_close(iface->pcap);
+      localip_free(&iface->local_ips);
+      free(iface);
+   }
+   free(title_interfaces);
+   title_interfaces = NULL;
 }
 
 /* Run through entire capfile. */
-void
-cap_from_file(const char *capfile, const char *filter)
-{
+void cap_from_file(const char *capfile) {
    char errbuf[PCAP_ERRBUF_SIZE];
    int linktype, ret;
+   struct cap_iface iface;
+
+   iface.name = NULL;
+   iface.filter = NULL;
+   iface.pcap = NULL;
+   iface.fd = -1;
+   iface.linkhdr = NULL;
+   localip_init(&iface.local_ips);
+
+   /* Process cmdline filters. */
+   if (!STAILQ_EMPTY(&cli_filters))
+      iface.filter = STAILQ_FIRST(&cli_filters)->str;
+   while (!STAILQ_EMPTY(&cli_filters)) {
+      struct strnode *n = STAILQ_FIRST(&cli_filters);
+      STAILQ_REMOVE_HEAD(&cli_filters, entries);
+      free(n);
+   }
 
    /* Open packet capture descriptor. */
    errbuf[0] = '\0'; /* zero length string */
-   pcap = pcap_open_offline(capfile, errbuf);
+   iface.pcap = pcap_open_offline(capfile, errbuf);
 
-   if (pcap == NULL)
+   if (iface.pcap == NULL)
       errx(1, "pcap_open_offline(): %s", errbuf);
 
    if (errbuf[0] != '\0') /* not zero length anymore -> warning */
       warnx("pcap_open_offline() warning: %s", errbuf);
 
    /* Work out the linktype. */
-   linktype = pcap_datalink(pcap);
-   linkhdr = getlinkhdr(linktype);
-   if (linkhdr == NULL)
+   linktype = pcap_datalink(iface.pcap);
+   iface.linkhdr = getlinkhdr(linktype);
+   if (iface.linkhdr == NULL)
       errx(1, "unknown linktype %d", linktype);
-   if (linkhdr->decoder == NULL)
+   if (iface.linkhdr->decoder == NULL)
       errx(1, "no decoder for linktype %d", linktype);
-   if (linktype == DLT_EN10MB) /* FIXME: impossible with capfile? */
-      hosts_db_show_macs = 1;
 
-   /* Set filter expression, if any. */ /* FIXME: factor! */
-   if (filter != NULL)
-   {
-      struct bpf_program prog;
-      char *tmp_filter = xstrdup(filter);
-      if (pcap_compile(
-            pcap,
-            &prog,
-            tmp_filter,
-            1,          /* optimize */
-            0)          /* netmask */
-            == -1)
-         errx(1, "pcap_compile(): %s", pcap_geterr(pcap));
-
-      if (pcap_setfilter(pcap, &prog) == -1)
-         errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));
-
-      pcap_freecode(&prog);
-      free(tmp_filter);
-   }
+   cap_set_filter(iface.pcap, iface.filter);
 
    /* Process file. */
    ret = pcap_dispatch(
-         pcap,
+         iface.pcap,
          -1,               /* count, -1 = entire buffer */
          callback,
-         NULL);            /* user */
+         (u_char*)&iface); /* user */
 
    if (ret < 0)
-      errx(1, "pcap_dispatch(): %s", pcap_geterr(pcap));
+      errx(1, "pcap_dispatch(): %s", pcap_geterr(iface.pcap));
+
+   localip_free(&iface.local_ips);
+   pcap_close(iface.pcap);
 }
 
 /* vim:set ts=3 sw=3 tw=78 expandtab: */
diff --git a/cap.h b/cap.h
index 18f2425..e1a77cf 100644 (file)
--- a/cap.h
+++ b/cap.h
 
 extern unsigned int cap_pkts_recv, cap_pkts_drop;
 
-void cap_init(const char *device, const char *filter, int promisc);
+void cap_add_ifname(const char *ifname); /* call one or more times */
+void cap_add_filter(const char *filter); /* call zero or more times */
+void cap_start(const int promisc);
 void cap_fd_set(fd_set *read_set, int *max_fd,
    struct timeval *timeout, int *need_timeout);
 void cap_poll(fd_set *read_set);
 void cap_stop(void);
 
-void cap_from_file(const char *capfile, const char *filter);
+void cap_from_file(const char *capfile);
 
 /* vim:set ts=3 sw=3 tw=78 expandtab: */
index 6c85b20..1dd3db9 100644 (file)
@@ -43,8 +43,7 @@ static volatile int running = 1;
 static void sig_shutdown(int signum _unused_) { running = 0; }
 
 static volatile int reset_pending = 0, export_pending = 0;
-static void sig_reset(int signum _unused_)
-{
+static void sig_reset(int signum _unused_) {
    reset_pending = 1;
    export_pending = 1;
 }
@@ -52,9 +51,8 @@ static void sig_reset(int signum _unused_)
 static void sig_export(int signum _unused_) { export_pending = 1; }
 
 /* --- Commandline parsing --- */
-static unsigned long
-parsenum(const char *str, unsigned long max /* 0 for no max */)
-{
+static unsigned long parsenum(const char *str,
+                              unsigned long max /* 0 for no max */) {
    unsigned long n;
    char *end;
 
@@ -69,8 +67,13 @@ parsenum(const char *str, unsigned long max /* 0 for no max */)
    return n;
 }
 
-const char *opt_interface = NULL;
-static void cb_interface(const char *arg) { opt_interface = arg; }
+static int opt_iface_seen = 0;
+static void cb_interface(const char *arg) {
+   cap_add_ifname(arg);
+   opt_iface_seen = 1;
+}
+
+static void cb_filter(const char *arg) { cap_add_filter(arg); }
 
 const char *opt_capfile = NULL;
 static void cb_capfile(const char *arg) { opt_capfile = arg; }
@@ -109,9 +112,6 @@ static void cb_port(const char *arg)
 
 static void cb_bindaddr(const char *arg) { http_add_bindaddr(arg); }
 
-const char *opt_filter = NULL;
-static void cb_filter(const char *arg) { opt_filter = arg; }
-
 static int is_localnet_specified = 0;
 static void cb_local(const char *arg)
 {
@@ -212,11 +212,11 @@ struct cmdline_arg {
 };
 
 static struct cmdline_arg cmdline_args[] = {
-   {"-i",             "interface",       cb_interface,    0},
-   {"-r",             "file",            cb_capfile,      0},
+   {"-i",             "interface",       cb_interface,   -1},
+   {"-f",             "filter",          cb_filter,      -1},
+   {"-r",             "capfile",         cb_capfile,      0},
    {"-p",             "port",            cb_port,         0},
    {"-b",             "bindaddr",        cb_bindaddr,    -1},
-   {"-f",             "filter",          cb_filter,       0},
    {"-l",             "network/netmask", cb_local,        0},
    {"--local-only",   NULL,              cb_local_only,   0},
    {"--snaplen",      "bytes",           cb_snaplen,      0},
@@ -246,12 +246,8 @@ static struct cmdline_arg cmdline_args[] = {
    {NULL,             NULL,              NULL,            0}
 };
 
-/*
- * We autogenerate the usage statement from the cmdline_args data structure.
- */
-static void
-usage(void)
-{
+/* We autogenerate the usage statement from the cmdline_args data structure. */
+static void usage(void) {
    static char intro[] = "usage: darkstat ";
    char indent[sizeof(intro)];
    struct cmdline_arg *arg;
@@ -276,9 +272,7 @@ usage(void)
 "documentation and usage examples.\n");
 }
 
-static void
-parse_sub_cmdline(const int argc, char * const *argv)
-{
+static void parse_sub_cmdline(const int argc, char * const *argv) {
    struct cmdline_arg *arg;
 
    if (argc == 0) return;
@@ -317,9 +311,7 @@ parse_sub_cmdline(const int argc, char * const *argv)
    exit(EXIT_FAILURE);
 }
 
-static void
-parse_cmdline(const int argc, char * const *argv)
-{
+static void parse_cmdline(const int argc, char * const *argv) {
    if (argc < 1) {
       /* Not enough args. */
       usage();
@@ -334,17 +326,20 @@ parse_cmdline(const int argc, char * const *argv)
    }
 
    /* start syslogging as early as possible */
-   if (opt_want_syslog) openlog("darkstat", LOG_NDELAY | LOG_PID, LOG_DAEMON);
+   if (opt_want_syslog)
+      openlog("darkstat", LOG_NDELAY | LOG_PID, LOG_DAEMON);
 
    /* some default values */
-   if (opt_chroot_dir == NULL) opt_chroot_dir = CHROOT_DIR;
-   if (opt_privdrop_user == NULL) opt_privdrop_user = PRIVDROP_USER;
+   if (opt_chroot_dir == NULL)
+      opt_chroot_dir = CHROOT_DIR;
+   if (opt_privdrop_user == NULL)
+      opt_privdrop_user = PRIVDROP_USER;
 
    /* sanity check args */
-   if ((opt_interface == NULL) && (opt_capfile == NULL))
+   if (!opt_iface_seen && opt_capfile == NULL)
       errx(1, "must specify either interface (-i) or capture file (-r)");
 
-   if ((opt_interface != NULL) && (opt_capfile != NULL))
+   if (opt_iface_seen && opt_capfile != NULL)
       errx(1, "can't specify both interface (-i) and capture file (-r)");
 
    if ((opt_hosts_max != 0) && (opt_hosts_keep >= opt_hosts_max)) {
@@ -377,22 +372,17 @@ parse_cmdline(const int argc, char * const *argv)
       verbosef("WARNING: --local-only without -l only matches the local host");
 }
 
-static void
-run_from_capfile(void)
-{
+static void run_from_capfile(void) {
+   now_init();
    graph_init();
    hosts_db_init();
-   cap_from_file(opt_capfile, opt_filter);
-   cap_stop();
+   cap_from_file(opt_capfile);
    if (export_fn != NULL) db_export(export_fn);
    hosts_db_free();
    graph_free();
-#ifndef PRIu64
-#warning "PRIu64 is not defined, using qu instead"
-#define PRIu64 "qu"
-#endif
-   verbosef("Total packets: %"PRIu64", bytes: %"PRIu64,
-      acct_total_packets, acct_total_bytes);
+   verbosef("Total packets: %llu, bytes: %llu",
+            (unsigned long long)acct_total_packets,
+            (unsigned long long)acct_total_bytes);
 }
 
 /* --- Program body --- */
@@ -403,10 +393,6 @@ main(int argc, char **argv)
    parse_cmdline(argc-1, argv+1);
 
    if (opt_capfile) {
-      /*
-       * This is very different from a regular run against a network
-       * interface.
-       */
       run_from_capfile();
       return 0;
    }
@@ -424,7 +410,7 @@ main(int argc, char **argv)
 
    /* do this first as it forks - minimize memory use */
    if (opt_want_dns) dns_init(opt_privdrop_user);
-   cap_init(opt_interface, opt_filter, opt_want_promisc); /* needs root */
+   cap_start(opt_want_promisc); /* needs root */
    http_listen(opt_bindport);
    ncache_init(); /* must do before chroot() */
 
@@ -437,9 +423,6 @@ main(int argc, char **argv)
    hosts_db_init();
    if (import_fn != NULL) db_import(import_fn);
 
-   local_ips = localip_make();
-   localip_update(opt_interface, local_ips);
-
    if (signal(SIGTERM, sig_shutdown) == SIG_ERR)
       errx(1, "signal(SIGTERM) failed");
    if (signal(SIGINT, sig_shutdown) == SIG_ERR)
@@ -510,7 +493,6 @@ main(int argc, char **argv)
    if (daylog_fn != NULL) daylog_free();
    ncache_free();
    if (pid_fn) pidfile_unlink();
-   localip_free(local_ips);
    verbosef("shut down");
    return (EXIT_SUCCESS);
 }
diff --git a/html.c b/html.c
index 2d2b7f7..fe5bb68 100644 (file)
--- a/html.c
+++ b/html.c
@@ -32,11 +32,11 @@ void html_open(struct str *buf, const char *title,
         "<!DOCTYPE html>\n"
         "<html>\n"
         "<head>\n"
-         "<title>%s (darkstat3 %s)</title>\n"
+         "<title>%s (darkstat %s)</title>\n"
          "<meta name=\"generator\" content=\"" PACKAGE_STRING "\">\n"
          "<meta name=\"robots\" content=\"noindex, noarchive\">\n"
-         "<link rel=\"stylesheet\" href=\"%s/style.css\" type=\"text/css\">\n"
-        , title, opt_interface, root);
+         "<link rel=\"stylesheet\" href=\"%s/style.css\" type=\"text/css\">\n",
+        title, title_interfaces, root);
 
     if (want_graph_js)
         str_appendf(buf,
@@ -67,4 +67,4 @@ void html_close(struct str *buf)
         "</html>\n");
 }
 
-/* vim:set ts=4 sw=4 tw=78 expandtab: */
+/* vim:set ts=4 sw=4 tw=80 et: */
index dae3d02..ab63d17 100644 (file)
--- a/localip.c
+++ b/localip.c
 # include <sys/ioctl.h>
 #endif
 
-struct local_ips *local_ips;
-
-struct local_ips *localip_make(void) {
-   struct local_ips *ips = xmalloc(sizeof(*ips));
-
+void localip_init(struct local_ips *ips) {
    ips->is_valid = 0;
    ips->last_update_mono = 0;
    ips->num_addrs = 0;
    ips->addrs = NULL;
-   return ips;
 }
 
 void localip_free(struct local_ips *ips) {
    if (ips->addrs != NULL)
       free(ips->addrs);
-   free(ips);
 }
 
 static void add_ip(const char *iface, struct local_ips *ips,
index df8b4d4..a5b48ea 100644 (file)
--- a/localip.h
+++ b/localip.h
@@ -18,9 +18,7 @@ struct local_ips {
    struct addr *addrs;
 };
 
-extern struct local_ips *local_ips;
-
-struct local_ips *localip_make(void);
+void localip_init(struct local_ips *ips);
 void localip_free(struct local_ips *ips);
 
 void localip_update(const char *iface, struct local_ips *ips);
diff --git a/opt.h b/opt.h
index 5d75cf3..34afc36 100644 (file)
--- a/opt.h
+++ b/opt.h
@@ -4,29 +4,22 @@
  * opt.h: global options
  */
 
-/*
- * Capture options.
- */
+/* Capture options. */
 extern int opt_want_pppoe;
 extern int opt_want_macs;
 extern int opt_want_hexdump;
 extern int opt_want_snaplen;
 extern int opt_wait_secs;
 
-/*
- * Error/logging options.
- */
+/* Error/logging options. */
 extern int opt_want_verbose;
 extern int opt_want_syslog;
 
-/*
- * Accounting options.
- */
+/* Accounting options. */
 extern unsigned int opt_highest_port;
 extern int opt_want_local_only;
 
-/*
- * Hosts table reduction - when the number of entries is about to exceed
+/* Hosts table reduction - when the number of entries is about to exceed
  * <max>, we reduce the table to the top <keep> entries.
  */
 extern unsigned int opt_hosts_max;
@@ -34,10 +27,10 @@ extern unsigned int opt_hosts_keep;
 extern unsigned int opt_ports_max;
 extern unsigned int opt_ports_keep;
 
-/*
- * Hosts output options.
- */
+/* Hosts output options. */
 extern int opt_want_lastseen;
-extern const char *opt_interface;
+
+/* Initialized in cap.c, added to <title> */
+extern char *title_interfaces;
 
 /* vim:set ts=3 sw=3 tw=78 expandtab: */
diff --git a/queue.h b/queue.h
index 95a1276..5773a91 100644 (file)
--- a/queue.h
+++ b/queue.h
@@ -49,6 +49,11 @@ struct {                                                             \
 
 #define        STAILQ_FIRST(head)      ((head)->stqh_first)
 
+#define STAILQ_FOREACH(var, head, field)                                \
+        for((var) = STAILQ_FIRST((head));                               \
+           (var);                                                       \
+           (var) = STAILQ_NEXT((var), field))
+
 #define        STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
 
 #ifdef STAILQ_INSERT_TAIL