diff options
| -rw-r--r-- | package/iwcap/Makefile | 50 | ||||
| -rw-r--r-- | package/iwcap/src/iwcap.c | 582 | 
2 files changed, 632 insertions, 0 deletions
| diff --git a/package/iwcap/Makefile b/package/iwcap/Makefile new file mode 100644 index 000000000..b406bdfb4 --- /dev/null +++ b/package/iwcap/Makefile @@ -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 index 000000000..3a8e7404d --- /dev/null +++ b/package/iwcap/src/iwcap.c @@ -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; +} | 
