add iwcap - a small radiotap capture utility for background monitoring of wireless...
authorJo-Philipp Wich <jow@openwrt.org>
Mon, 27 Feb 2012 22:57:15 +0000 (22:57 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Mon, 27 Feb 2012 22:57:15 +0000 (22:57 +0000)
SVN-Revision: 30747

package/iwcap/Makefile [new file with mode: 0644]
package/iwcap/src/iwcap.c [new file with mode: 0644]

diff --git a/package/iwcap/Makefile b/package/iwcap/Makefile
new file mode 100644 (file)
index 0000000..b406bdf
--- /dev/null
@@ -0,0 +1,50 @@
+#
+# Copyright (C) 2012 Jo-Philipp Wich <jow@openwrt.org>
+#
+# This is free software, licensed under the Apache 2 license.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=iwcap
+PKG_RELEASE:=1
+
+include $(INCLUDE_DIR)/package.mk
+
+
+define Package/iwcap
+  SECTION:=utils
+  CATEGORY:=Utilities
+  TITLE:=Simple radiotap capture utility
+  MAINTAINER:=Jo-Philipp Wich <jow@openwrt.org>
+endef
+
+define Package/iwcap/description
+  The iwcap utility receives radiotap packet data from wifi monitor interfaces
+  and outputs it to pcap format. It gathers recived packets in a fixed ring
+  buffer to dump them on demand which is useful for background monitoring.
+  Alternatively the utility can stream the data to stdout to act as remote
+  capture drone for Wireshark or similar programs.
+endef
+
+
+define Build/Prepare
+       $(INSTALL_DIR) $(PKG_BUILD_DIR)
+       $(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+       $(TARGET_CC) $(TARGET_CFLAGS) \
+               -o $(PKG_BUILD_DIR)/iwcap $(PKG_BUILD_DIR)/iwcap.c
+endef
+
+
+define Package/iwcap/install
+       $(INSTALL_DIR) $(1)/usr/sbin
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/iwcap $(1)/usr/sbin/iwcap
+endef
+
+$(eval $(call BuildPackage,iwcap))
diff --git a/package/iwcap/src/iwcap.c b/package/iwcap/src/iwcap.c
new file mode 100644 (file)
index 0000000..3a8e740
--- /dev/null
@@ -0,0 +1,582 @@
+/*
+ * iwcap.c - A simply radiotap capture utility outputting pcap sig_dumps
+ *
+ *    Copyright 2012 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <syslog.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_packet.h>
+
+#define ARPHRD_IEEE80211_RADIOTAP      803
+
+#define DLT_IEEE802_11_RADIO           127
+#define LEN_IEEE802_11_HDR                     32
+
+#define FRAMETYPE_MASK                         0xFC
+#define FRAMETYPE_BEACON                       0x80
+#define FRAMETYPE_DATA                         0x08
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define le16(x) __bswap_16(x)
+#else
+#define le16(x) (x)
+#endif
+
+uint8_t run_dump   = 0;
+uint8_t run_stop   = 0;
+uint8_t run_daemon = 0;
+
+uint32_t frames_captured = 0;
+uint32_t frames_filtered = 0;
+
+int capture_sock = -1;
+const char *ifname = NULL;
+
+
+struct ringbuf {
+       uint32_t len;            /* number of slots */
+       uint32_t fill;           /* last used slot */
+       uint32_t slen;           /* slot size */
+       void *buf;               /* ring memory */
+};
+
+struct ringbuf_entry {
+       uint32_t len;            /* used slot memory */
+       uint32_t olen;           /* original data size */
+       uint32_t sec;            /* epoch of slot creation */
+       uint32_t usec;                   /* epoch microseconds */
+};
+
+typedef struct pcap_hdr_s {
+       uint32_t magic_number;   /* magic number */
+       uint16_t version_major;  /* major version number */
+       uint16_t version_minor;  /* minor version number */
+       int32_t  thiszone;       /* GMT to local correction */
+       uint32_t sigfigs;        /* accuracy of timestamps */
+       uint32_t snaplen;        /* max length of captured packets, in octets */
+       uint32_t network;        /* data link type */
+} pcap_hdr_t;
+
+typedef struct pcaprec_hdr_s {
+       uint32_t ts_sec;         /* timestamp seconds */
+       uint32_t ts_usec;        /* timestamp microseconds */
+       uint32_t incl_len;       /* number of octets of packet saved in file */
+       uint32_t orig_len;       /* actual length of packet */
+} pcaprec_hdr_t;
+
+typedef struct ieee80211_radiotap_header {
+       u_int8_t  it_version;    /* set to 0 */
+       u_int8_t  it_pad;
+       u_int16_t it_len;        /* entire length */
+       u_int32_t it_present;    /* fields present */
+} __attribute__((__packed__)) radiotap_hdr_t;
+
+
+int check_type(void)
+{
+       struct ifreq ifr;
+
+       strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+
+       if (ioctl(capture_sock, SIOCGIFHWADDR, &ifr) < 0)
+               return -1;
+
+       return (ifr.ifr_hwaddr.sa_family == ARPHRD_IEEE80211_RADIOTAP);
+}
+
+int set_promisc(int on)
+{
+       struct ifreq ifr;
+
+       strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+
+       if (ioctl(capture_sock, SIOCGIFFLAGS, &ifr) < 0)
+               return -1;
+
+       if (on && !(ifr.ifr_flags & IFF_PROMISC))
+       {
+               ifr.ifr_flags |= IFF_PROMISC;
+
+               if (ioctl(capture_sock, SIOCSIFFLAGS, &ifr))
+                       return -1;
+
+               return 1;
+       }
+       else if (!on && (ifr.ifr_flags & IFF_PROMISC))
+       {
+               ifr.ifr_flags &= ~IFF_PROMISC;
+
+               if (ioctl(capture_sock, SIOCSIFFLAGS, &ifr))
+                       return -1;
+
+               return 1;
+       }
+
+       return 0;
+}
+
+
+void sig_dump(int sig)
+{
+       run_dump = 1;
+}
+
+void sig_teardown(int sig)
+{
+       run_stop = 1;
+}
+
+
+void write_pcap_header(FILE *o)
+{
+       pcap_hdr_t ghdr = {
+               .magic_number  = 0xa1b2c3d4,
+               .version_major = 2,
+               .version_minor = 4,
+               .thiszone      = 0,
+               .sigfigs       = 0,
+               .snaplen       = 0xFFFF,
+               .network       = DLT_IEEE802_11_RADIO
+       };
+
+       fwrite(&ghdr, 1, sizeof(ghdr), o);
+}
+
+void write_pcap_frame(FILE *o, uint32_t *sec, uint32_t *usec,
+                                         uint16_t len, uint16_t olen)
+{
+       struct timeval tv;
+       pcaprec_hdr_t fhdr;
+
+       if (!sec || !usec)
+       {
+               gettimeofday(&tv, NULL);
+       }
+       else
+       {
+               tv.tv_sec  = *sec;
+               tv.tv_usec = *usec;
+       }
+
+       fhdr.ts_sec   = tv.tv_sec;
+       fhdr.ts_usec  = tv.tv_usec;
+       fhdr.incl_len = len;
+       fhdr.orig_len = olen;
+
+       fwrite(&fhdr, 1, sizeof(fhdr), o);
+}
+
+
+struct ringbuf * ringbuf_init(uint32_t num_item, uint16_t len_item)
+{
+       static struct ringbuf r;
+
+       if (len_item <= 0)
+               return NULL;
+
+       r.buf = malloc(num_item * (len_item + sizeof(struct ringbuf_entry)));
+
+       if (r.buf)
+       {
+               r.len = num_item;
+               r.fill = 0;
+               r.slen = (len_item + sizeof(struct ringbuf_entry));
+
+               memset(r.buf, 0, num_item * len_item);
+
+               return &r;
+       }
+
+       return NULL;
+}
+
+struct ringbuf_entry * ringbuf_add(struct ringbuf *r)
+{
+       struct timeval t;
+       struct ringbuf_entry *e;
+
+       gettimeofday(&t, NULL);
+
+       e = r->buf + (r->fill++ * r->slen);
+       r->fill %= r->len;
+
+       memset(e, 0, r->slen);
+
+       e->sec = t.tv_sec;
+       e->usec = t.tv_usec;
+
+       return e;
+}
+
+struct ringbuf_entry * ringbuf_get(struct ringbuf *r, int i)
+{
+       struct ringbuf_entry *e = r->buf + (((r->fill + i) % r->len) * r->slen);
+
+       if (e->len > 0)
+               return e;
+
+       return NULL;
+}
+
+void ringbuf_free(struct ringbuf *r)
+{
+       free(r->buf);
+       memset(r, 0, sizeof(*r));
+}
+
+
+void msg(const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+
+       if (run_daemon)
+               vsyslog(LOG_INFO | LOG_USER, fmt, ap);
+       else
+               vfprintf(stderr, fmt, ap);
+
+       va_end(ap);
+}
+
+
+int main(int argc, char **argv)
+{
+       int i, n;
+       struct ringbuf *ring;
+       struct ringbuf_entry *e;
+       struct sockaddr_ll local = {
+               .sll_family   = AF_PACKET,
+               .sll_protocol = htons(ETH_P_ALL)
+       };
+
+       radiotap_hdr_t *rhdr;
+
+       uint8_t frametype;
+       uint8_t pktbuf[0xFFFF];
+       ssize_t pktlen;
+
+       FILE *o;
+
+       int opt;
+
+       uint8_t promisc        = 0;
+       uint8_t streaming      = 0;
+       uint8_t foreground     = 0;
+       uint8_t filter_data    = 0;
+       uint8_t filter_beacon  = 0;
+       uint8_t header_written = 0;
+
+       uint32_t ringsz   = 1024 * 1024; /* 1 Mbyte ring buffer */
+       uint16_t pktcap   = 256;                 /* truncate frames after 265KB */
+
+       const char *output = NULL;
+
+
+       while ((opt = getopt(argc, argv, "i:r:c:o:sfhBD")) != -1)
+       {
+               switch (opt)
+               {
+               case 'i':
+                       ifname = optarg;
+                       if (!(local.sll_ifindex = if_nametoindex(ifname)))
+                       {
+                               msg("Unknown interface '%s'\n", ifname);
+                               return 2;
+                       }
+                       break;
+
+               case 'r':
+                       ringsz = atoi(optarg);
+                       if (ringsz < (3 * pktcap))
+                       {
+                               msg("Ring size of %d bytes is too short, "
+                                       "must be at least %d bytes\n", ringsz, 3 * pktcap);
+                               return 3;
+                       }
+                       break;
+
+               case 'c':
+                       pktcap = atoi(optarg);
+                       if (pktcap <= (sizeof(radiotap_hdr_t) + LEN_IEEE802_11_HDR))
+                       {
+                               msg("Packet truncate after %d bytes is too short, "
+                                       "must be at least %d bytes\n",
+                                       pktcap, sizeof(radiotap_hdr_t) + LEN_IEEE802_11_HDR);
+                               return 4;
+                       }
+                       break;
+
+               case 's':
+                       streaming = 1;
+                       break;
+
+               case 'o':
+                       output = optarg;
+                       break;
+
+               case 'B':
+                       filter_beacon = 1;
+                       break;
+
+               case 'D':
+                       filter_data = 1;
+                       break;
+
+               case 'f':
+                       foreground = 1;
+                       break;
+
+               case 'h':
+                       msg(
+                               "Usage:\n"
+                               "  %s -i {iface} -s [-b] [-d]\n"
+                               "  %s -i {iface} -o {file} [-r len] [-c len] [-B] [-D] [-f]\n"
+                               "\n"
+                               "  -i iface\n"
+                               "    Specify interface to use, must be in monitor mode and\n"
+                               "    produce IEEE 802.11 Radiotap headers.\n\n"
+                               "  -s\n"
+                               "    Stream to stdout instead of Dumping to file on USR1.\n\n"
+                               "  -o file\n"
+                               "    Write current ringbuffer contents to given output file\n"
+                               "    on receipt of SIGUSR1.\n\n"
+                               "  -r len\n"
+                               "    Specify the amount of bytes to use for the ringbuffer.\n"
+                               "    The default length is %d bytes.\n\n"
+                               "  -c len\n"
+                               "    Truncate captured packets after given amount of bytes.\n"
+                               "    The default size limit is %d bytes.\n\n"
+                               "  -B\n"
+                               "    Don't store beacon frames in ring, default is keep.\n\n"
+                               "  -D\n"
+                               "    Don't store data frames in ring, default is keep.\n\n"
+                               "  -f\n"
+                               "    Do not daemonize but keep running in foreground.\n\n"
+                               "  -h\n"
+                               "    Display this help.\n\n",
+                               argv[0], argv[0], ringsz, pktcap);
+
+                       return 1;
+               }
+       }
+
+       if (!streaming && !output)
+       {
+               msg("No output file specified\n");
+               return 1;
+       }
+
+       if (streaming && output)
+       {
+               msg("The -s and -o options are exclusive\n");
+               return 1;
+       }
+
+       if (streaming && isatty(1))
+       {
+               msg("Refusing to stream into a terminal\n");
+               return 1;
+       }
+
+       if (!local.sll_ifindex)
+       {
+               msg("No interface specified\n");
+               return 2;
+       }
+
+       if (!check_type())
+       {
+               msg("Bad interface: not ARPHRD_IEEE80211_RADIOTAP\n");
+               return 2;
+       }
+
+       if ((capture_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0)
+       {
+               msg("Unable to create raw socket: %s\n",
+                               strerror(errno));
+               return 6;
+       }
+
+       if (bind(capture_sock, (struct sockaddr *)&local, sizeof(local)) == -1)
+       {
+               msg("Unable to bind to interface: %s\n",
+                       strerror(errno));
+               return 7;
+       }
+
+       if (!streaming)
+       {
+               if (!foreground)
+               {
+                       switch (fork())
+                       {
+                               case -1:
+                                       msg("Unable to fork: %s\n", strerror(errno));
+                                       return 8;
+
+                               case 0:
+                                       umask(0700);
+                                       chdir("/");
+                                       freopen("/dev/null", "r", stdin);
+                                       freopen("/dev/null", "w", stdout);
+                                       freopen("/dev/null", "w", stderr);
+                                       run_daemon = 1;
+                                       break;
+
+                               default:
+                                       msg("Daemon launched ...\n");
+                                       return 0;
+                       }
+               }
+
+               msg("Monitoring interface %s ...\n", ifname);
+
+               if (!(ring = ringbuf_init(ringsz / pktcap, pktcap)))
+               {
+                       msg("Unable to allocate ring buffer: %s\n",
+                               strerror(errno));
+                       return 5;
+               }
+
+               msg(" * Using %d bytes ringbuffer with %d slots\n", ringsz, ring->len);
+               msg(" * Truncating frames at %d bytes\n", pktcap);
+               msg(" * Dumping data to file %s\n", output);
+
+               signal(SIGUSR1, sig_dump);
+       }
+       else
+       {
+               msg("Monitoring interface %s ...\n", ifname);
+               msg(" * Streaming data to stdout\n");
+       }
+
+       msg(" * Beacon frames are %sfiltered\n", filter_beacon ? "" : "not ");
+       msg(" * Data frames are %sfiltered\n", filter_data ? "" : "not ");
+
+       signal(SIGINT, sig_teardown);
+       signal(SIGTERM, sig_teardown);
+
+       promisc = set_promisc(1);
+
+       /* capture loop */
+       while (1)
+       {
+               if (run_stop)
+               {
+                       msg("Shutting down ...\n");
+
+                       if (promisc)
+                               set_promisc(0);
+
+                       if (ring)
+                               ringbuf_free(ring);
+
+                       return 0;
+               }
+               else if (run_dump)
+               {
+                       msg("Dumping ring to %s ...\n", output);
+
+                       if (!(o = fopen(output, "w")))
+                       {
+                               msg("Unable to open %s: %s\n",
+                                       output, strerror(errno));
+                       }
+                       else
+                       {
+                               write_pcap_header(o);
+
+                               /* sig_dump packet buffer */
+                               for (i = 0, n = 0; i < ring->len; i++)
+                               {
+                                       if (!(e = ringbuf_get(ring, i)))
+                                               continue;
+
+                                       write_pcap_frame(o, &(e->sec), &(e->usec), e->len, e->olen);
+                                       fwrite((void *)e + sizeof(*e), 1, e->len, o);
+                                       n++;
+                               }
+
+                               fclose(o);
+
+                               msg(" * %d frames captured\n", frames_captured);
+                               msg(" * %d frames filtered\n", frames_filtered);
+                               msg(" * %d frames dumped\n", n);
+                       }
+
+                       run_dump = 0;
+               }
+
+               pktlen = recvfrom(capture_sock, pktbuf, sizeof(pktbuf), 0, NULL, 0);
+               frames_captured++;
+
+               /* check received frametype, if we should filter it, rewind the ring */
+               rhdr = (radiotap_hdr_t *)pktbuf;
+
+               if (pktlen <= sizeof(radiotap_hdr_t) || le16(rhdr->it_len) >= pktlen)
+               {
+                       frames_filtered++;
+                       continue;
+               }
+
+               frametype = *(uint8_t *)(pktbuf + le16(rhdr->it_len));
+
+               if ((filter_data   && (frametype & FRAMETYPE_MASK) == FRAMETYPE_DATA) ||
+                   (filter_beacon && (frametype & FRAMETYPE_MASK) == FRAMETYPE_BEACON))
+               {
+                       frames_filtered++;
+                       continue;
+               }
+
+               if (streaming)
+               {
+                       if (!header_written)
+                       {
+                               write_pcap_header(stdout);
+                               header_written = 1;
+                       }
+
+                       write_pcap_frame(stdout, NULL, NULL, pktlen, pktlen);
+                       fwrite(pktbuf, 1, pktlen, stdout);
+                       fflush(stdout);
+               }
+               else
+               {
+                       e = ringbuf_add(ring);
+                       e->olen = pktlen;
+                       e->len = (pktlen > pktcap) ? pktcap : pktlen;
+
+                       memcpy((void *)e + sizeof(*e), pktbuf, e->len);
+               }
+       }
+
+       return 0;
+}