First cut at packet_mmap support.
authorEmil Mikulic <emikulic@gmail.com>
Sun, 17 Jul 2011 08:46:34 +0000 (18:46 +1000)
committerEmil Mikulic <emikulic@gmail.com>
Sun, 17 Jul 2011 08:46:34 +0000 (18:46 +1000)
README
cap.c

diff --git a/README b/README
index 6413832..627799e 100644 (file)
--- a/README
+++ b/README
@@ -1,3 +1,5 @@
+*** This is the experimental PACKET_MMAP branch of darkstat ***
+
 darkstat is a network statistics gatherer.
 
 It sniffs packets on a specified interface, accumulates statistics, and
diff --git a/cap.c b/cap.c
index d07bf9c..23f4cfe 100644 (file)
--- a/cap.c
+++ b/cap.c
 /* darkstat 3
  * copyright (c) 2001-2011 Emil Mikulic.
  *
- * cap.c: interface to libpcap.
+ * cap.c: packet capture
  *
  * You may use, modify and redistribute this file under the terms of the
  * GNU General Public License version 2. (see COPYING.GPL)
  */
 
+#ifndef linux
+#error "PACKET_MMAP is only for linux"
+#endif
+
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/user.h> // PAGE_SIZE
+#include <sys/mman.h> // mmap()
+#include <linux/if_ether.h> // ETH_P_ALL
+#include <linux/if_packet.h> // struct tpacket_req
+#include <net/if.h> // struct ifreq
+#include <assert.h>
+#include <string.h> // memset
+#include <pcap.h>
+
+#include "cap.h"
+#include "decode.h"
+#include "err.h"
+#include "localip.h"
+#include "opt.h"
+
+static int cap_fd = -1;
+static unsigned char *cap_buf;
+static const struct linkhdr *linkhdr = NULL;
+
+/* FIXME: twiddle these */
+int cap_pages = 2, cap_blocks = 2;
+static int frame_size, frame_num;
+
+/* Initialize capture, or exit on failure. */
+void cap_init(const char *device, const char *filter, int promisc _unused_) {
+   int over_hdr, over_sll, over_mac, snaplen;
+   struct sockaddr_ll sll;
+   struct ifreq ifr;
+   struct tpacket_req req;
+
+   verbosef("using experimental PACKET_MMAP capture");
+   if (filter != NULL)
+      warnx("BPF filters are not supported in PACKET_MMAP mode");
+
+   if ((cap_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1)
+      err(1, "cap socket");
+
+   over_hdr = TPACKET_ALIGN(sizeof(struct tpacket_hdr));
+   over_sll = TPACKET_ALIGN(sizeof(struct sockaddr_ll));
+   /* FIXME: assumes ethernet */
+   over_mac = TPACKET_ALIGN(6 + 6 + 2);
+   linkhdr = getlinkhdr(DLT_EN10MB);
+
+   snaplen = 96;
+   if (opt_want_snaplen > -1)
+      snaplen = opt_want_snaplen;
+   verbosef("using snaplen %d", snaplen);
+
+   req.tp_block_size = PAGE_SIZE * cap_pages;
+   req.tp_block_nr = cap_blocks;
+   req.tp_frame_size = over_hdr + over_sll + over_mac + TPACKET_ALIGN(snaplen);
+   req.tp_frame_nr = (req.tp_block_size / req.tp_frame_size) * req.tp_block_nr;
+
+   assert(req.tp_frame_size > TPACKET_HDRLEN);
+   assert(req.tp_block_nr <= 131072 / sizeof(void*));
+
+   verbosef("%d(hdr) + %d(sll) + %d(mac) + %d(snaplen) + %d(pad) = %d total\n",
+      over_hdr, over_sll, over_mac, snaplen,
+      TPACKET_ALIGN(snaplen) - snaplen,
+      req.tp_frame_size);
+
+   frame_num = req.tp_frame_nr;
+   frame_size = req.tp_frame_size;
+   verbosef("tpacket_req: %d blocks x %d bytes, %d frames x %d bytes\n",
+      req.tp_block_nr, req.tp_block_size,
+      req.tp_frame_nr, req.tp_frame_size);
+
+   if (setsockopt(cap_fd, SOL_PACKET, PACKET_RX_RING,
+                  (void *)&req, sizeof(req)) == -1)
+      err(1, "cap setsockopt");
+
+   if ((cap_buf = mmap(NULL, req.tp_block_size * req.tp_block_nr,
+                       PROT_READ|PROT_WRITE, MAP_SHARED, cap_fd,
+                       0)) == MAP_FAILED)
+      err(1, "cap mmap");
+
+   /* get interface index */
+   memset(&ifr, 0, sizeof(ifr));
+   strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));
+   if (ioctl(cap_fd, SIOCGIFINDEX, &ifr) == -1)
+      err(1, "ifr ioctl");
+   verbosef("interface %s has index %d", device, (int)ifr.ifr_ifindex);
+
+   /* bind */
+   memset(&sll, 0, sizeof(sll));
+   sll.sll_family = AF_PACKET;
+   sll.sll_protocol = htons(ETH_P_ALL);
+   sll.sll_ifindex = ifr.ifr_ifindex;
+   if (bind(cap_fd, (struct sockaddr *)&sll, sizeof(sll)) == -1)
+      err(1, "cap bind");
+
+   verbosef("PACKET_MMAP initialized");
+}
+
+/* Set cap_fd in the given fd_set. */
+void cap_fd_set(fd_set *read_set,
+                int *max_fd,
+                struct timeval *timeout _unused_,
+                int *need_timeout) {
+   assert(*need_timeout == 0); /* we're first to get a shot at this */
+   FD_SET(cap_fd, read_set);
+   *max_fd = MAX(*max_fd, cap_fd);
+}
+
+unsigned int cap_pkts_recv = 0, cap_pkts_drop = 0;
+
+static void
+cap_stats_update(void)
+{
+   struct tpacket_stats tp;
+   socklen_t len = sizeof(tp);
+   if (getsockopt(cap_fd, SOL_PACKET, PACKET_STATISTICS, &tp, &len) == -1)
+      warn("getsockopt(PACKET_STATISTICS) failed");
+
+   cap_pkts_recv = tp.tp_packets;
+   cap_pkts_drop = tp.tp_drops;
+}
+
+/* FIXME: duplicated */
+/* Print hexdump of received packet. */
+static void
+hexdump(const u_char *buf, const uint32_t len)
+{
+   uint32_t i, col;
+
+   printf("packet of %u bytes:\n", len);
+   for (i=0, col=0; i<len; i++) {
+      if (col == 0) printf(" ");
+      printf("%02x", buf[i]);
+      if (i+1 == linkhdr->hdrlen)
+         printf("[");
+      else if (i+1 == linkhdr->hdrlen + IP_HDR_LEN)
+         printf("]");
+      else printf(" ");
+      col += 3;
+      if (col >= 72) {
+         printf("\n");
+         col = 0;
+      }
+   }
+   if (col != 0) printf("\n");
+   printf("\n");
+}
+
+/*
+ * Callback function for pcap_dispatch() which chains to the decoder specified
+ * in linkhdr struct.
+ */
+static void
+callback(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
+{
+   if (opt_want_hexdump) hexdump(bytes, h->caplen);
+   linkhdr->handler(user, h, bytes);
+}
+
+/* Process any packets currently in the capture buffer. */
+void cap_poll(fd_set *read_set _unused_) {
+   int total, i;
+
+   /* Once per capture poll, check our IP address. */
+   localip_update(); /* FIXME: this might even be too often */
+
+   total = 0;
+   for (i = 0; i<frame_num; i++) {
+      struct tpacket_hdr *hdr = (struct tpacket_hdr *)(
+         cap_buf + i * frame_size);
+      //struct sockaddr_ll *sll = (struct sockaddr_ll *)(
+      //   (char*)hdr + TPACKET_ALIGN(sizeof(*hdr)));
+      unsigned char *data;
+      struct pcap_pkthdr pcap_hdr;
+
+      if (hdr->tp_status != TP_STATUS_USER)
+         continue; /* owned by kernel */
+
+      /* FIXME: ethernet specific? */
+      data = (unsigned char *)hdr + hdr->tp_mac;
+
+      pcap_hdr.caplen = hdr->tp_snaplen;
+      pcap_hdr.ts.tv_sec = hdr->tp_sec;
+
+      /* This is where the magic happens! */
+      callback(NULL, &pcap_hdr, data); /* <-- */
+
+      hdr->tp_status = TP_STATUS_KERNEL; /* done with this frame */
+      total++;
+   }
+
+   verbosef("%d frames", total);
+   cap_stats_update();
+}
+
+void cap_stop(void) {
+   // FIXME: munmap(cap_buf)
+   close(cap_fd);
+}
+
+/* Run through entire capfile. */
+void
+cap_from_file(const char *capfile _unused_, const char *filter _unused_)
+{
+   errx(1, "unimplemented");
+}
+
+#if 0
 #include "cdefs.h"
 #include "cap.h"
 #include "config.h"
@@ -410,4 +620,5 @@ cap_from_file(const char *capfile, const char *filter)
       errx(1, "pcap_dispatch(): %s", pcap_geterr(pcap));
 }
 
+#endif
 /* vim:set ts=3 sw=3 tw=78 expandtab: */