add iwcap - a small radiotap capture utility for background monitoring of wireless traffic or use as a remote capture drone
SVN-Revision: 30747
This commit is contained in:
parent
7d7118b8bf
commit
448f4696f1
2 changed files with 632 additions and 0 deletions
50
package/iwcap/Makefile
Normal file
50
package/iwcap/Makefile
Normal file
|
@ -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))
|
582
package/iwcap/src/iwcap.c
Normal file
582
package/iwcap/src/iwcap.c
Normal file
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue