* based on previous works of:
+ *
+ * Monitor-mode support for mac80211 includes code taken from the iw
+ * command; the copyright notice for that code is
+ *
+ * Copyright (c) 2007, 2008 Johannes Berg
+ * Copyright (c) 2007 Andy Lutomirski
+ * Copyright (c) 2007 Mike Kershaw
+ * Copyright (c) 2008 Gábor Stefanik
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
*/
#ifndef lint
*/
+#define _GNU_SOURCE
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
+#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
+#include <limits.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
*/
#ifdef HAVE_LINUX_WIRELESS_H
#include <linux/wireless.h>
-#endif
+#endif /* HAVE_LINUX_WIRELESS_H */
+
+/*
+ * Got libnl?
+ */
+#ifdef HAVE_LIBNL
+#include <linux/nl80211.h>
+
+#include <netlink/genl/genl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+#endif /* HAVE_LIBNL */
#include "pcap-int.h"
#include "pcap/sll.h"
static int pcap_setdirection_linux(pcap_t *, pcap_direction_t);
static void pcap_cleanup_linux(pcap_t *);
+union thdr {
+ struct tpacket_hdr *h1;
+ struct tpacket2_hdr *h2;
+ void *raw;
+};
+
#ifdef HAVE_PACKET_RING
#define RING_GET_FRAME(h) (((union thdr **)h->buffer)[h->offset])
static int pcap_setfilter_linux_mmap(pcap_t *, struct bpf_program *);
static int pcap_setnonblock_mmap(pcap_t *p, int nonblock, char *errbuf);
static int pcap_getnonblock_mmap(pcap_t *p, char *errbuf);
+static void pcap_oneshot_mmap(u_char *user, const struct pcap_pkthdr *h,
+ const u_char *bytes);
#endif
/*
#ifdef IW_MODE_MONITOR
static int has_wext(int sock_fd, const char *device, char *ebuf);
#endif /* IW_MODE_MONITOR */
-static int enter_rfmon_mode_wext(pcap_t *handle, int sock_fd,
+static int enter_rfmon_mode(pcap_t *handle, int sock_fd,
const char *device);
#endif /* HAVE_PF_PACKET_SOCKETS */
static int iface_bind_old(int fd, const char *device, char *ebuf);
#ifdef SO_ATTACH_FILTER
-static int fix_program(pcap_t *handle, struct sock_fprog *fcode);
+static int fix_program(pcap_t *handle, struct sock_fprog *fcode,
+ int is_mapped);
static int fix_offset(struct bpf_insn *p);
static int set_kernel_filter(pcap_t *handle, struct sock_fprog *fcode);
static int reset_kernel_filter(pcap_t *handle);
return handle;
}
+#ifdef HAVE_LIBNL
+/*
+ *
+ * If interface {if} is a mac80211 driver, the file
+ * /sys/class/net/{if}/phy80211 is a symlink to
+ * /sys/class/ieee80211/{phydev}, for some {phydev}.
+ *
+ * On Fedora 9, with a 2.6.26.3-29 kernel, my Zydas stick, at
+ * least, has a "wmaster0" device and a "wlan0" device; the
+ * latter is the one with the IP address. Both show up in
+ * "tcpdump -D" output. Capturing on the wmaster0 device
+ * captures with 802.11 headers.
+ *
+ * airmon-ng searches through /sys/class/net for devices named
+ * monN, starting with mon0; as soon as one *doesn't* exist,
+ * it chooses that as the monitor device name. If the "iw"
+ * command exists, it does "iw dev {if} interface add {monif}
+ * type monitor", where {monif} is the monitor device. It
+ * then (sigh) sleeps .1 second, and then configures the
+ * device up. Otherwise, if /sys/class/ieee80211/{phydev}/add_iface
+ * is a file, it writes {mondev}, without a newline, to that file,
+ * and again (sigh) sleeps .1 second, and then iwconfig's that
+ * device into monitor mode and configures it up. Otherwise,
+ * you can't do monitor mode.
+ *
+ * All these devices are "glued" together by having the
+ * /sys/class/net/{device}/phy80211 links pointing to the same
+ * place, so, given a wmaster, wlan, or mon device, you can
+ * find the other devices by looking for devices with
+ * the same phy80211 link.
+ *
+ * To turn monitor mode off, delete the monitor interface,
+ * either with "iw dev {monif} interface del" or by sending
+ * {monif}, with no NL, down /sys/class/ieee80211/{phydev}/remove_iface
+ *
+ * Note: if you try to create a monitor device named "monN", and
+ * there's already a "monN" device, it fails, as least with
+ * the netlink interface (which is what iw uses), with a return
+ * value of -ENFILE. (Return values are negative errnos.) We
+ * could probably use that to find an unused device.
+ *
+ * Yes, you can have multiple monitor devices for a given
+ * physical device.
+*/
+
+/*
+ * Is this a mac80211 device? If so, fill in the physical device path and
+ * return 1; if not, return 0. On an error, fill in handle->errbuf and
+ * return PCAP_ERROR.
+ */
+static int
+get_mac80211_phydev(pcap_t *handle, const char *device, char *phydev_path,
+ size_t phydev_max_pathlen)
+{
+ char *pathstr;
+ ssize_t bytes_read;
+
+ /*
+ * Generate the path string for the symlink to the physical device.
+ */
+ if (asprintf(&pathstr, "/sys/class/net/%s/phy80211", device) == -1) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: Can't generate path name string for /sys/class/net device",
+ device);
+ return PCAP_ERROR;
+ }
+ bytes_read = readlink(pathstr, phydev_path, phydev_max_pathlen);
+ if (bytes_read == -1) {
+ if (errno == ENOENT || errno == EINVAL) {
+ /*
+ * Doesn't exist, or not a symlink; assume that
+ * means it's not a mac80211 device.
+ */
+ free(pathstr);
+ return 0;
+ }
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: Can't readlink %s: %s", device, pathstr,
+ strerror(errno));
+ free(pathstr);
+ return PCAP_ERROR;
+ }
+ free(pathstr);
+ phydev_path[bytes_read] = '\0';
+ return 1;
+}
+
+struct nl80211_state {
+ struct nl_handle *nl_handle;
+ struct nl_cache *nl_cache;
+ struct genl_family *nl80211;
+};
+
+static int
+nl80211_init(pcap_t *handle, struct nl80211_state *state, const char *device)
+{
+ state->nl_handle = nl_handle_alloc();
+ if (!state->nl_handle) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: failed to allocate netlink handle", device);
+ return PCAP_ERROR;
+ }
+
+ if (genl_connect(state->nl_handle)) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: failed to connect to generic netlink", device);
+ goto out_handle_destroy;
+ }
+
+ state->nl_cache = genl_ctrl_alloc_cache(state->nl_handle);
+ if (!state->nl_cache) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: failed to allocate generic netlink cache", device);
+ goto out_handle_destroy;
+ }
+
+ state->nl80211 = genl_ctrl_search_by_name(state->nl_cache, "nl80211");
+ if (!state->nl80211) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: nl80211 not found", device);
+ goto out_cache_free;
+ }
+
+ return 0;
+
+out_cache_free:
+ nl_cache_free(state->nl_cache);
+out_handle_destroy:
+ nl_handle_destroy(state->nl_handle);
+ return PCAP_ERROR;
+}
+
+static void
+nl80211_cleanup(struct nl80211_state *state)
+{
+ genl_family_put(state->nl80211);
+ nl_cache_free(state->nl_cache);
+ nl_handle_destroy(state->nl_handle);
+}
+
+static int
+add_mon_if(pcap_t *handle, int sock_fd, struct nl80211_state *state,
+ const char *device, const char *mondevice)
+{
+ int ifindex;
+ struct nl_msg *msg;
+ int err;
+
+ ifindex = iface_get_id(sock_fd, device, handle->errbuf);
+ if (ifindex == -1)
+ return PCAP_ERROR;
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: failed to allocate netlink msg", device);
+ return PCAP_ERROR;
+ }
+
+ genlmsg_put(msg, 0, 0, genl_family_get_id(state->nl80211), 0,
+ 0, NL80211_CMD_NEW_INTERFACE, 0);
+ NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
+ NLA_PUT_STRING(msg, NL80211_ATTR_IFNAME, mondevice);
+ NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MONITOR);
+
+ err = nl_send_auto_complete(state->nl_handle, msg);
+ if (err < 0) {
+ if (err == -ENFILE) {
+ /*
+ * Device not available; our caller should just
+ * keep trying.
+ */
+ nlmsg_free(msg);
+ return 0;
+ } else {
+ /*
+ * Real failure, not just "that device is not
+ * available.
+ */
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: nl_send_auto_complete failed adding %s interface: %s",
+ device, mondevice, strerror(-err));
+ nlmsg_free(msg);
+ return PCAP_ERROR;
+ }
+ }
+ err = nl_wait_for_ack(state->nl_handle);
+ if (err < 0) {
+ if (err == -ENFILE) {
+ /*
+ * Device not available; our caller should just
+ * keep trying.
+ */
+ nlmsg_free(msg);
+ return 0;
+ } else {
+ /*
+ * Real failure, not just "that device is not
+ * available.
+ */
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: nl_wait_for_ack failed adding %s interface: %s",
+ device, mondevice, strerror(-err));
+ nlmsg_free(msg);
+ return PCAP_ERROR;
+ }
+ }
+
+ /*
+ * Success.
+ */
+ nlmsg_free(msg);
+ return 1;
+
+nla_put_failure:
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: nl_put failed adding %s interface",
+ device, mondevice);
+ nlmsg_free(msg);
+ return PCAP_ERROR;
+}
+
+static int
+del_mon_if(pcap_t *handle, int sock_fd, struct nl80211_state *state,
+ const char *device, const char *mondevice)
+{
+ int ifindex;
+ struct nl_msg *msg;
+ int err;
+
+ ifindex = iface_get_id(sock_fd, mondevice, handle->errbuf);
+ if (ifindex == -1)
+ return PCAP_ERROR;
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: failed to allocate netlink msg", device);
+ return PCAP_ERROR;
+ }
+
+ genlmsg_put(msg, 0, 0, genl_family_get_id(state->nl80211), 0,
+ 0, NL80211_CMD_DEL_INTERFACE, 0);
+ NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
+
+ err = nl_send_auto_complete(state->nl_handle, msg);
+ if (err < 0) {
+ if (err == -ENFILE) {
+ /*
+ * Device not available; our caller should just
+ * keep trying.
+ */
+ nlmsg_free(msg);
+ return 0;
+ } else {
+ /*
+ * Real failure, not just "that device is not
+ * available.
+ */
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: nl_send_auto_complete failed deleting %s interface: %s",
+ device, mondevice, strerror(-err));
+ nlmsg_free(msg);
+ return PCAP_ERROR;
+ }
+ }
+ err = nl_wait_for_ack(state->nl_handle);
+ if (err < 0) {
+ if (err == -ENFILE) {
+ /*
+ * Device not available; our caller should just
+ * keep trying.
+ */
+ nlmsg_free(msg);
+ return 0;
+ } else {
+ /*
+ * Real failure, not just "that device is not
+ * available.
+ */
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: nl_wait_for_ack failed adding %s interface: %s",
+ device, mondevice, strerror(-err));
+ nlmsg_free(msg);
+ return PCAP_ERROR;
+ }
+ }
+
+ /*
+ * Success.
+ */
+ nlmsg_free(msg);
+ return 1;
+
+nla_put_failure:
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: nl_put failed deleting %s interface",
+ device, mondevice);
+ nlmsg_free(msg);
+ return PCAP_ERROR;
+}
+
+static int
+enter_rfmon_mode_mac80211(pcap_t *handle, int sock_fd, const char *device)
+{
+ int ret;
+ char phydev_path[PATH_MAX+1];
+ struct nl80211_state nlstate;
+ struct ifreq ifr;
+ u_int n;
+
+ /*
+ * Is this a mac80211 device?
+ */
+ ret = get_mac80211_phydev(handle, device, phydev_path, PATH_MAX);
+ if (ret < 0)
+ return ret; /* error */
+ if (ret == 0)
+ return 0; /* no error, but not mac80211 device */
+
+ /*
+ * XXX - is this already a monN device?
+ * If so, we're done.
+ * Is that determined by old Wireless Extensions ioctls?
+ */
+
+ /*
+ * OK, it's apparently a mac80211 device.
+ * Try to find an unused monN device for it.
+ */
+ ret = nl80211_init(handle, &nlstate, device);
+ if (ret != 0)
+ return ret;
+ for (n = 0; n < UINT_MAX; n++) {
+ /*
+ * Try mon{n}.
+ */
+ char mondevice[3+10+1]; /* mon{UINT_MAX}\0 */
+
+ snprintf(mondevice, sizeof mondevice, "mon%u", n);
+ ret = add_mon_if(handle, sock_fd, &nlstate, device, mondevice);
+ if (ret == 1) {
+ handle->md.mondevice = strdup(mondevice);
+ goto added;
+ }
+ if (ret < 0) {
+ /*
+ * Hard failure. Just return ret; handle->errbuf
+ * has already been set.
+ */
+ nl80211_cleanup(&nlstate);
+ return ret;
+ }
+ }
+
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: No free monN interfaces", device);
+ nl80211_cleanup(&nlstate);
+ return PCAP_ERROR;
+
+added:
+
+#if 0
+ /*
+ * Sleep for .1 seconds.
+ */
+ delay.tv_sec = 0;
+ delay.tv_nsec = 500000000;
+ nanosleep(&delay, NULL);
+#endif
+
+ /*
+ * Now configure the monitor interface up.
+ */
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, handle->md.mondevice, sizeof(ifr.ifr_name));
+ if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr) == -1) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: Can't get flags for %s: %s", device,
+ handle->md.mondevice, strerror(errno));
+ del_mon_if(handle, sock_fd, &nlstate, device,
+ handle->md.mondevice);
+ nl80211_cleanup(&nlstate);
+ return PCAP_ERROR;
+ }
+ ifr.ifr_flags |= IFF_UP|IFF_RUNNING;
+ if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr) == -1) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "%s: Can't set flags for %s: %s", device,
+ handle->md.mondevice, strerror(errno));
+ del_mon_if(handle, sock_fd, &nlstate, device,
+ handle->md.mondevice);
+ nl80211_cleanup(&nlstate);
+ return PCAP_ERROR;
+ }
+
+ /*
+ * Success. Clean up the libnl state.
+ */
+ nl80211_cleanup(&nlstate);
+
+ /*
+ * Note that we have to delete the monitor device when we close
+ * the handle.
+ */
+ handle->md.must_do_on_close |= MUST_DELETE_MONIF;
+
+ /*
+ * Add this to the list of pcaps to close when we exit.
+ */
+ pcap_add_to_pcaps_to_close(handle);
+
+ return 1;
+}
+#endif /* HAVE_LIBNL */
+
static int
-pcap_can_set_rfmon_linux(pcap_t *p)
+pcap_can_set_rfmon_linux(pcap_t *handle)
{
+#ifdef HAVE_LIBNL
+ char phydev_path[PATH_MAX+1];
+ int ret;
+#endif
#ifdef IW_MODE_MONITOR
int sock_fd;
struct iwreq ireq;
#endif
- if (strcmp(p->opt.source, "any") == 0) {
+ if (strcmp(handle->opt.source, "any") == 0) {
/*
* Monitor mode makes no sense on the "any" device.
*/
return 0;
}
+#ifdef HAVE_LIBNL
+ /*
+ * Bleah. There doesn't seem to be a way to ask a mac80211
+ * device, through libnl, whether it supports monitor mode;
+ * we'll just check whether the device appears to be a
+ * mac80211 device and, if so, assume the device supports
+ * monitor mode.
+ *
+ * wmaster devices don't appear to support the Wireless
+ * Extensions, but we can create a mon device for a
+ * wmaster device, so we don't bother checking whether
+ * a mac80211 device supports the Wireless Extensions.
+ */
+ ret = get_mac80211_phydev(handle, handle->opt.source, phydev_path,
+ PATH_MAX);
+ if (ret < 0)
+ return ret; /* error */
+ if (ret == 1)
+ return 1; /* mac80211 device */
+#endif
+
#ifdef IW_MODE_MONITOR
/*
* Bleah. There doesn't appear to be an ioctl to use to ask
*/
sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock_fd == -1) {
- (void)snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
+ (void)snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
"socket: %s", pcap_strerror(errno));
return PCAP_ERROR;
}
/*
* Attempt to get the current mode.
*/
- strncpy(ireq.ifr_ifrn.ifrn_name, p->opt.source,
+ strncpy(ireq.ifr_ifrn.ifrn_name, handle->opt.source,
sizeof ireq.ifr_ifrn.ifrn_name);
ireq.ifr_ifrn.ifrn_name[sizeof ireq.ifr_ifrn.ifrn_name - 1] = 0;
if (ioctl(sock_fd, SIOCGIWMODE, &ireq) != -1) {
static void pcap_cleanup_linux( pcap_t *handle )
{
struct ifreq ifr;
+#ifdef HAVE_LIBNL
+ struct nl80211_state nlstate;
+ int ret;
+#endif /* HAVE_LIBNL */
#ifdef IW_MODE_MONITOR
struct iwreq ireq;
-#endif
+#endif /* IW_MODE_MONITOR */
- if (handle->md.must_clear != 0) {
+ if (handle->md.must_do_on_close != 0) {
/*
* There's something we have to do when closing this
* pcap_t.
*/
- if (handle->md.must_clear & MUST_CLEAR_PROMISC) {
+ if (handle->md.must_do_on_close & MUST_CLEAR_PROMISC) {
/*
* We put the interface into promiscuous mode;
* take it out of promiscuous mode.
}
}
+#ifdef HAVE_LIBNL
+ if (handle->md.must_do_on_close & MUST_DELETE_MONIF) {
+ ret = nl80211_init(handle, &nlstate, handle->md.device);
+ if (ret >= 0) {
+ ret = del_mon_if(handle, handle->fd, &nlstate,
+ handle->md.device, handle->md.mondevice);
+ nl80211_cleanup(&nlstate);
+ }
+ if (ret < 0) {
+ fprintf(stderr,
+ "Can't delete monitor interface %s (%s).\n"
+ "Please delete manually.\n",
+ handle->md.mondevice, handle->errbuf);
+ }
+ }
+#endif /* HAVE_LIBNL */
+
#ifdef IW_MODE_MONITOR
- if (handle->md.must_clear & MUST_CLEAR_RFMON) {
+ if (handle->md.must_do_on_close & MUST_CLEAR_RFMON) {
/*
* We put the interface into rfmon mode;
* take it out of rfmon mode.
strerror(errno));
}
}
-#endif
+#endif /* IW_MODE_MONITOR */
/*
* Take this pcap out of the list of pcaps for which we
pcap_remove_from_pcaps_to_close(handle);
}
+ if (handle->md.mondevice != NULL) {
+ free(handle->md.mondevice);
+ handle->md.mondevice = NULL;
+ }
if (handle->md.device != NULL) {
free(handle->md.device);
handle->md.device = NULL;
* Kernel doesn't support it - just continue
* with non-memory-mapped access.
*/
+ status = 0;
break;
case -1:
* Attach the given BPF code to the packet capture device.
*/
static int
-pcap_setfilter_linux(pcap_t *handle, struct bpf_program *filter)
+pcap_setfilter_linux_common(pcap_t *handle, struct bpf_program *filter,
+ int is_mmapped)
{
#ifdef SO_ATTACH_FILTER
struct sock_fprog fcode;
*
* Oh, and we also need to fix it up so that all "ret"
* instructions with non-zero operands have 65535 as the
- * operand, and so that, if we're in cooked mode, all
- * memory-reference instructions use special magic offsets
- * in references to the link-layer header and assume that
- * the link-layer payload begins at 0; "fix_program()"
- * will do that.
+ * operand if we're not capturing in memory-mapped modee,
+ * and so that, if we're in cooked mode, all memory-reference
+ * instructions use special magic offsets in references to
+ * the link-layer header and assume that the link-layer
+ * payload begins at 0; "fix_program()" will do that.
*/
- switch (fix_program(handle, &fcode)) {
+ switch (fix_program(handle, &fcode, is_mmapped)) {
case -1:
default:
return 0;
}
+static int
+pcap_setfilter_linux(pcap_t *handle, struct bpf_program *filter)
+{
+ return pcap_setfilter_linux_common(handle, filter, 0);
+}
+
+
/*
* Set direction flag: Which packets do we accept on a forwarding
* single device? IN, OUT or both?
#ifdef HAVE_PF_PACKET_SOCKETS
const char *device = handle->opt.source;
int is_any_device = (strcmp(device, "any") == 0);
- int sock_fd = -1, arptype, val;
+ int sock_fd = -1, arptype;
+#ifdef HAVE_PACKET_AUXDATA
+ int val;
+#endif
int err = 0;
struct packet_mreq mr;
* because entering monitor mode could change
* the link-layer type.
*/
- err = enter_rfmon_mode_wext(handle, sock_fd, device);
+ err = enter_rfmon_mode(handle, sock_fd, device);
if (err < 0) {
/* Hard failure */
close(sock_fd);
close(sock_fd);
return PCAP_ERROR_RFMON_NOTSUP;
}
+
+ /*
+ * Either monitor mode has been turned on for
+ * the device, or we've been given a different
+ * device to open for monitor mode. If we've
+ * been given a different device, use it.
+ */
+ if (handle->md.mondevice != NULL)
+ device = handle->md.mondevice;
}
arptype = iface_get_arptype(sock_fd, device, handle->errbuf);
if (arptype < 0) {
#ifdef HAVE_PACKET_RING
int ret;
+ /*
+ * Attempt to allocate a buffer to hold the contents of one
+ * packet, for use by the oneshot callback.
+ */
+ handle->md.oneshot_buffer = malloc(handle->snapshot);
+ if (handle->md.oneshot_buffer == NULL) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "can't allocate oneshot buffer: %s",
+ pcap_strerror(errno));
+ return PCAP_ERROR;
+ }
+
if (handle->opt.buffer_size == 0) {
/* by default request 2M for the ring buffer */
handle->opt.buffer_size = 2*1024*1024;
}
ret = prepare_tpacket_socket(handle);
- if (ret != 1)
+ if (ret != 1) {
+ free(handle->md.oneshot_buffer);
return ret;
+ }
ret = create_ring(handle);
- if (ret != 1)
+ if (ret != 1) {
+ free(handle->md.oneshot_buffer);
return ret;
+ }
/* override some defaults and inherit the other fields from
* activate_new
handle->setfilter_op = pcap_setfilter_linux_mmap;
handle->setnonblock_op = pcap_setnonblock_mmap;
handle->getnonblock_op = pcap_getnonblock_mmap;
+ handle->oneshot_callback = pcap_oneshot_mmap;
handle->selectable_fd = handle->fd;
return 1;
#else /* HAVE_PACKET_RING */
return 1;
}
-static void
-compute_ring_block(int frame_size, unsigned *block_size, unsigned *frames_per_block)
-{
- /* compute the minumum block size that will handle this frame.
- * The block has to be page size aligned.
- * The max block size allowed by the kernel is arch-dependent and
- * it's not explicitly checked here. */
- *block_size = getpagesize();
- while (*block_size < frame_size)
- *block_size <<= 1;
-
- *frames_per_block = *block_size/frame_size;
-}
-
static int
create_ring(pcap_t *handle)
{
- unsigned i, j, ringsize, frames_per_block;
+ unsigned i, j, frames_per_block;
struct tpacket_req req;
/* Note that with large snapshot (say 64K) only a few frames
TPACKET_ALIGN(handle->md.tp_hdrlen) +
sizeof(struct sockaddr_ll));
req.tp_frame_nr = handle->opt.buffer_size/req.tp_frame_size;
- compute_ring_block(req.tp_frame_size, &req.tp_block_size, &frames_per_block);
- req.tp_block_nr = req.tp_frame_nr / frames_per_block;
- /* req.tp_frame_nr is requested to match frames_per_block*req.tp_block_nr */
- req.tp_frame_nr = req.tp_block_nr * frames_per_block;
+ /* compute the minumum block size that will handle this frame.
+ * The block has to be page size aligned.
+ * The max block size allowed by the kernel is arch-dependent and
+ * it's not explicitly checked here. */
+ req.tp_block_size = getpagesize();
+ while (req.tp_block_size < req.tp_frame_size)
+ req.tp_block_size <<= 1;
+
+ frames_per_block = req.tp_block_size/req.tp_frame_size;
/* ask the kernel to create the ring */
retry:
+ req.tp_block_nr = req.tp_frame_nr / frames_per_block;
+
+ /* req.tp_frame_nr is requested to match frames_per_block*req.tp_block_nr */
+ req.tp_frame_nr = req.tp_block_nr * frames_per_block;
+
if (setsockopt(handle->fd, SOL_PACKET, PACKET_RX_RING,
(void *) &req, sizeof(req))) {
- /* try to reduce requested ring size to prevent memory failure */
if ((errno == ENOMEM) && (req.tp_block_nr > 1)) {
- req.tp_frame_nr >>= 1;
- req.tp_block_nr = req.tp_frame_nr/frames_per_block;
+ /*
+ * Memory failure; try to reduce the requested ring
+ * size.
+ *
+ * We used to reduce this by half -- do 5% instead.
+ * That may result in more iterations and a longer
+ * startup, but the user will be much happier with
+ * the resulting buffer size.
+ */
+ if (req.tp_frame_nr < 20)
+ req.tp_frame_nr -= 1;
+ else
+ req.tp_frame_nr -= req.tp_frame_nr/20;
goto retry;
}
if (errno == ENOPROTOOPT) {
}
/* memory map the rx ring */
- ringsize = req.tp_block_nr * req.tp_block_size;
- handle->bp = mmap(0, ringsize, PROT_READ| PROT_WRITE, MAP_SHARED,
- handle->fd, 0);
- if (handle->bp == MAP_FAILED) {
+ handle->md.mmapbuflen = req.tp_block_nr * req.tp_block_size;
+ handle->md.mmapbuf = mmap(0, handle->md.mmapbuflen,
+ PROT_READ|PROT_WRITE, MAP_SHARED, handle->fd, 0);
+ if (handle->md.mmapbuf == MAP_FAILED) {
snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
"can't mmap rx ring: %s", pcap_strerror(errno));
/* fill the header ring with proper frame ptr*/
handle->offset = 0;
for (i=0; i<req.tp_block_nr; ++i) {
- void *base = &handle->bp[i*req.tp_block_size];
+ void *base = &handle->md.mmapbuf[i*req.tp_block_size];
for (j=0; j<frames_per_block; ++j, ++handle->offset) {
RING_GET_FRAME(handle) = base;
base += req.tp_frame_size;
(void *) &req, sizeof(req));
/* if ring is mapped, unmap it*/
- if (handle->bp) {
- /* need to re-compute the ring size */
- unsigned frames_per_block, block_size;
- compute_ring_block(handle->bufsize, &block_size, &frames_per_block);
-
- /* do not perform sanity check here: we can't recover any error */
- munmap(handle->bp, block_size * handle->cc / frames_per_block);
- handle->bp = 0;
+ if (handle->md.mmapbuf) {
+ /* do not test for mmap failure, as we can't recover from any error */
+ munmap(handle->md.mmapbuf, handle->md.mmapbuflen);
+ handle->md.mmapbuf = NULL;
}
}
+/*
+ * Special one-shot callback, used for pcap_next() and pcap_next_ex(),
+ * for Linux mmapped capture.
+ *
+ * The problem is that pcap_next() and pcap_next_ex() expect the packet
+ * data handed to the callback to be valid after the callback returns,
+ * but pcap_read_linux_mmap() has to release that packet as soon as
+ * the callback returns (otherwise, the kernel thinks there's still
+ * at least one unprocessed packet available in the ring, so a select()
+ * will immediately return indicating that there's data to process), so,
+ * in the callback, we have to make a copy of the packet.
+ *
+ * Yes, this means that, if the capture is using the ring buffer, using
+ * pcap_next() or pcap_next_ex() requires more copies than using
+ * pcap_loop() or pcap_dispatch(). If that bothers you, don't use
+ * pcap_next() or pcap_next_ex().
+ */
+static void
+pcap_oneshot_mmap(u_char *user, const struct pcap_pkthdr *h,
+ const u_char *bytes)
+{
+ struct pkt_for_oneshot *sp = (struct pkt_for_oneshot *)user;
+ bpf_u_int32 copylen;
+
+ *sp->hdr = *h;
+ memcpy(sp->pd->md.oneshot_buffer, bytes, h->caplen);
+ *sp->pkt = sp->pd->md.oneshot_buffer;
+}
+
static void
pcap_cleanup_linux_mmap( pcap_t *handle )
{
destroy_ring(handle);
+ if (handle->md.oneshot_buffer != NULL) {
+ free(handle->md.oneshot_buffer);
+ handle->md.oneshot_buffer = NULL;
+ }
pcap_cleanup_linux(handle);
}
/* map each value to the corresponding 2's complement, to
* preserve the timeout value provided with pcap_set_timeout */
if (nonblock) {
- if (p->md.timeout > 0)
+ if (p->md.timeout >= 0) {
+ /*
+ * Timeout is non-negative, so we're not already
+ * in non-blocking mode; set it to the 2's
+ * complement, to make it negative, as an
+ * indication that we're in non-blocking mode.
+ */
p->md.timeout = p->md.timeout*-1 - 1;
- } else
- if (p->md.timeout < 0)
+ }
+ } else {
+ if (p->md.timeout < 0) {
+ /*
+ * Timeout is negative, so we're not already
+ * in blocking mode; reverse the previous
+ * operation, to make the timeout non-negative
+ * again.
+ */
p->md.timeout = (p->md.timeout+1)*-1;
+ }
+ }
return 0;
}
return h.raw;
}
-static inline void
-pcap_release_previous_ring_frame(pcap_t *handle)
-{
- if (handle->prev_pkt.raw != NULL) {
- switch (handle->md.tp_version) {
- case TPACKET_V1:
- handle->prev_pkt.h1->tp_status = TP_STATUS_KERNEL;
- break;
-#ifdef HAVE_TPACKET2
- case TPACKET_V2:
- handle->prev_pkt.h2->tp_status = TP_STATUS_KERNEL;
- break;
-#endif
- }
- handle->prev_pkt.raw = NULL;
- }
-}
-
static int
pcap_read_linux_mmap(pcap_t *handle, int max_packets, pcap_handler callback,
u_char *user)
unsigned int tp_sec;
unsigned int tp_usec;
- /*
- * Check for break loop condition; a callback might have
- * set it.
- */
- if (handle->break_loop) {
- handle->break_loop = 0;
- return -2;
- }
-
h.raw = pcap_get_ring_frame(handle, TP_STATUS_USER);
if (!h.raw)
break;
- /*
- * We have a packet; release the previous packet,
- * if any.
- *
- * Libpcap has never guaranteed that, if we get a
- * packet from the underlying packet capture
- * mechanism, the data passed to callbacks for
- * any previous packets is still valid. It did
- * implicitly guarantee that the data will still
- * be available after the callback returns, by
- * virtue of implementing pcap_next() by calling
- * pcap_dispatch() with a count of 1 and a callback
- * that fills in a structure with a pointer to
- * the packet data, meaning that pointer is
- * expected to point to valid data after the
- * callback returns and pcap_next() returns,
- * so we can't release the packet when the
- * callback returns.
- *
- * Therefore, we remember the packet that
- * needs to be released after handing it
- * to the callback, and release it up here.
- */
- pcap_release_previous_ring_frame(handle);
-
switch (handle->md.tp_version) {
case TPACKET_V1:
tp_len = h.h1->tp_len;
}
#endif
+ /*
+ * The only way to tell the kernel to cut off the
+ * packet at a snapshot length is with a filter program;
+ * if there's no filter program, the kernel won't cut
+ * the packet off.
+ *
+ * Trim the snapshot length to be no longer than the
+ * specified snapshot length.
+ */
+ if (pcaphdr.caplen > handle->snapshot)
+ pcaphdr.caplen = handle->snapshot;
+
/* pass the packet to the user */
pkts++;
callback(user, &pcaphdr, bp);
handle->md.packets_read++;
skip:
- /*
- * As per the comment above, we can't yet release this
- * packet, even though the callback has returned, as
- * some users of pcap_loop() and pcap_dispatch() - such
- * as pcap_next() and pcap_next_ex() - expect the packet
- * to be available until the next pcap_dispatch() call.
- */
- handle->prev_pkt = h;
+ /* next packet */
+ switch (handle->md.tp_version) {
+ case TPACKET_V1:
+ h.h1->tp_status = TP_STATUS_KERNEL;
+ break;
+#ifdef HAVE_TPACKET2
+ case TPACKET_V2:
+ h.h2->tp_status = TP_STATUS_KERNEL;
+ break;
+#endif
+ }
if (++handle->offset >= handle->cc)
handle->offset = 0;
pcap_setfilter_linux_mmap(pcap_t *handle, struct bpf_program *filter)
{
int n, offset;
- int ret = pcap_setfilter_linux(handle, filter);
+ int ret;
+
+ /*
+ * Don't rewrite "ret" instructions; we don't need to, as
+ * we're not reading packets with recvmsg(), and we don't
+ * want to, as, by not rewriting them, the kernel can avoid
+ * copying extra data.
+ */
+ ret = pcap_setfilter_linux_common(handle, filter, 1);
if (ret < 0)
return ret;
return PCAP_ERROR_NO_SUCH_DEVICE;
return 0;
}
-#endif
/*
* Per me si va ne la citta dolente,
static int
enter_rfmon_mode_wext(pcap_t *handle, int sock_fd, const char *device)
{
-#ifdef IW_MODE_MONITOR
/*
* XXX - at least some adapters require non-Wireless Extensions
* mechanisms to turn monitor mode on.
strncpy(ireq.ifr_ifrn.ifrn_name, device,
sizeof ireq.ifr_ifrn.ifrn_name);
ireq.ifr_ifrn.ifrn_name[sizeof ireq.ifr_ifrn.ifrn_name - 1] = 0;
- ireq.u.data.pointer = args;
+ ireq.u.data.pointer = (void *)args;
ireq.u.data.length = 0;
ireq.u.data.flags = 0;
if (ioctl(sock_fd, SIOCGIWPRIV, &ireq) != -1) {
"malloc: %s", pcap_strerror(errno));
return PCAP_ERROR;
}
- ireq.u.data.pointer = priv;
+ ireq.u.data.pointer = (void *)priv;
if (ioctl(sock_fd, SIOCGIWPRIV, &ireq) == -1) {
snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
"%s: SIOCGIWPRIV: %s", device, pcap_strerror(errno));
* Hostap driver, use this one.
* Set monitor mode first.
* You can set it to 0 to get DLT_IEEE80211,
- * 1 to get DLT_PRISM, or 2 to get
- * DLT_IEEE80211_RADIO_AVS.
+ * 1 to get DLT_PRISM, 2 to get
+ * DLT_IEEE80211_RADIO_AVS, and, with more
+ * recent versions of the driver, 3 to get
+ * DLT_IEEE80211_RADIO.
*/
if ((priv[i].set_args & IW_PRIV_TYPE_MASK) != IW_PRIV_TYPE_INT)
break;
* Note that we have to put the old mode back
* when we close the device.
*/
- handle->md.must_clear |= MUST_CLEAR_RFMON;
+ handle->md.must_do_on_close |= MUST_CLEAR_RFMON;
/*
* Add this to the list of pcaps to close
case MONITOR_HOSTAP:
/*
- * Select the AVS header if we can, otherwise
- * select the Prism header.
+ * Try to select the radiotap header.
+ */
+ memset(&ireq, 0, sizeof ireq);
+ strncpy(ireq.ifr_ifrn.ifrn_name, device,
+ sizeof ireq.ifr_ifrn.ifrn_name);
+ ireq.ifr_ifrn.ifrn_name[sizeof ireq.ifr_ifrn.ifrn_name - 1] = 0;
+ args[0] = 3; /* request radiotap header */
+ memcpy(ireq.u.name, args, sizeof (int));
+ if (ioctl(sock_fd, cmd, &ireq) != -1)
+ break; /* success */
+
+ /*
+ * That failed. Try to select the AVS header.
*/
memset(&ireq, 0, sizeof ireq);
strncpy(ireq.ifr_ifrn.ifrn_name, device,
ireq.ifr_ifrn.ifrn_name[sizeof ireq.ifr_ifrn.ifrn_name - 1] = 0;
args[0] = 2; /* request AVS header */
memcpy(ireq.u.name, args, sizeof (int));
- if (ioctl(sock_fd, cmd, &ireq) == -1) {
- /*
- * Failure - try the Prism header.
- */
- memset(&ireq, 0, sizeof ireq);
- strncpy(ireq.ifr_ifrn.ifrn_name, device,
- sizeof ireq.ifr_ifrn.ifrn_name);
- ireq.ifr_ifrn.ifrn_name[sizeof ireq.ifr_ifrn.ifrn_name - 1] = 0;
- args[0] = 1; /* request Prism header */
- memcpy(ireq.u.name, args, sizeof (int));
- ioctl(sock_fd, cmd, &ireq);
- }
+ if (ioctl(sock_fd, cmd, &ireq) != -1)
+ break; /* success */
+
+ /*
+ * That failed. Try to select the Prism header.
+ */
+ memset(&ireq, 0, sizeof ireq);
+ strncpy(ireq.ifr_ifrn.ifrn_name, device,
+ sizeof ireq.ifr_ifrn.ifrn_name);
+ ireq.ifr_ifrn.ifrn_name[sizeof ireq.ifr_ifrn.ifrn_name - 1] = 0;
+ args[0] = 1; /* request Prism header */
+ memcpy(ireq.u.name, args, sizeof (int));
+ ioctl(sock_fd, cmd, &ireq);
break;
case MONITOR_PRISM:
* Note that we have to put the old mode back when we
* close the device.
*/
- handle->md.must_clear |= MUST_CLEAR_RFMON;
+ handle->md.must_do_on_close |= MUST_CLEAR_RFMON;
/*
* Add this to the list of pcaps to close when we exit.
pcap_add_to_pcaps_to_close(handle);
return 1;
-#else
+}
+#endif /* IW_MODE_MONITOR */
+
+/*
+ * Try various mechanisms to enter monitor mode.
+ */
+static int
+enter_rfmon_mode(pcap_t *handle, int sock_fd, const char *device)
+{
+#if defined(HAVE_LIBNL) || defined(IW_MODE_MONITOR)
+ int ret;
+#endif
+
+#ifdef HAVE_LIBNL
+ ret = enter_rfmon_mode_mac80211(handle, sock_fd, device);
+ if (ret < 0)
+ return ret; /* error attempting to do so */
+ if (ret == 1)
+ return 1; /* success */
+#endif /* HAVE_LIBNL */
+
+#ifdef IW_MODE_MONITOR
+ ret = enter_rfmon_mode_wext(handle, sock_fd, device);
+ if (ret < 0)
+ return ret; /* error attempting to do so */
+ if (ret == 1)
+ return 1; /* success */
+#endif /* IW_MODE_MONITOR */
+
/*
- * We don't have the Wireless Extensions available, so we can't
- * do monitor mode.
+ * Either none of the mechanisms we know about work or none
+ * of those mechanisms are available, so we can't do monitor
+ * mode.
*/
return 0;
-#endif
}
#endif /* HAVE_PF_PACKET_SOCKETS */
pcap_strerror(errno));
return PCAP_ERROR;
}
- handle->md.must_clear |= MUST_CLEAR_PROMISC;
+ handle->md.must_do_on_close |= MUST_CLEAR_PROMISC;
/*
* Add this to the list of pcaps
#ifdef SO_ATTACH_FILTER
static int
-fix_program(pcap_t *handle, struct sock_fprog *fcode)
+fix_program(pcap_t *handle, struct sock_fprog *fcode, int is_mmapped)
{
size_t prog_size;
register int i;
case BPF_RET:
/*
- * It's a return instruction; is the snapshot
- * length a constant, rather than the contents
- * of the accumulator?
+ * It's a return instruction; are we capturing
+ * in memory-mapped mode?
*/
- if (BPF_MODE(p->code) == BPF_K) {
+ if (!is_mmapped) {
/*
- * Yes - if the value to be returned,
- * i.e. the snapshot length, is anything
- * other than 0, make it 65535, so that
- * the packet is truncated by "recvfrom()",
- * not by the filter.
- *
- * XXX - there's nothing we can easily do
- * if it's getting the value from the
- * accumulator; we'd have to insert
- * code to force non-zero values to be
- * 65535.
+ * No; is the snapshot length a constant,
+ * rather than the contents of the
+ * accumulator?
*/
- if (p->k != 0)
- p->k = 65535;
+ if (BPF_MODE(p->code) == BPF_K) {
+ /*
+ * Yes - if the value to be returned,
+ * i.e. the snapshot length, is
+ * anything other than 0, make it
+ * 65535, so that the packet is
+ * truncated by "recvfrom()",
+ * not by the filter.
+ *
+ * XXX - there's nothing we can
+ * easily do if it's getting the
+ * value from the accumulator; we'd
+ * have to insert code to force
+ * non-zero values to be 65535.
+ */
+ if (p->k != 0)
+ p->k = 65535;
+ }
}
break;