+/*
+ * 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;
+}