| /* |
| * iwcap.c - A simply radiotap capture utility outputting pcap dumps |
| * |
| * Copyright 2012 Jo-Philipp Wich <jo@mein.io> |
| * |
| * 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 <byteswap.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(0077); |
| 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_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; |
| } |
| if (run_stop) |
| { |
| msg("Shutting down ...\n"); |
| |
| if (promisc) |
| set_promisc(0); |
| |
| if (ring) |
| ringbuf_free(ring); |
| |
| return 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; |
| } |