aboutsummaryrefslogtreecommitdiffstats
path: root/iw_scan.c
diff options
context:
space:
mode:
Diffstat (limited to 'iw_scan.c')
-rw-r--r--iw_scan.c959
1 files changed, 259 insertions, 700 deletions
diff --git a/iw_scan.c b/iw_scan.c
index 740bdff..6426f68 100644
--- a/iw_scan.c
+++ b/iw_scan.c
@@ -6,547 +6,14 @@
*/
#include "iw_if.h"
#include <search.h> /* lsearch(3) */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "iw_nl80211.h"
-#define MAX_SCAN_WAIT 10000 /* maximum milliseconds spent waiting */
+/* GLOBAL VARIABLES */
+static struct nl_sock *scan_wait_sk;
-/*
- * Meta-data about all the additional standard Wireless Extension events
- * we know about.
- */
-/* Type of headers we know about (basically union iwreq_data) */
-#define IW_HEADER_TYPE_NULL 0 /* Not available */
-#define IW_HEADER_TYPE_CHAR 2 /* char [IFNAMSIZ] */
-#define IW_HEADER_TYPE_UINT 4 /* __u32 */
-#define IW_HEADER_TYPE_FREQ 5 /* struct iw_freq */
-#define IW_HEADER_TYPE_ADDR 6 /* struct sockaddr */
-#define IW_HEADER_TYPE_POINT 8 /* struct iw_point */
-#define IW_HEADER_TYPE_PARAM 9 /* struct iw_param */
-#define IW_HEADER_TYPE_QUAL 10 /* struct iw_quality */
-
-/* Size (in bytes) of various events */
-static const int event_type_size[] = {
- [IW_HEADER_TYPE_NULL] = IW_EV_LCP_PK_LEN,
- [IW_HEADER_TYPE_CHAR] = IW_EV_CHAR_PK_LEN,
- [IW_HEADER_TYPE_UINT] = IW_EV_UINT_PK_LEN,
- [IW_HEADER_TYPE_FREQ] = IW_EV_FREQ_PK_LEN,
- [IW_HEADER_TYPE_ADDR] = IW_EV_ADDR_PK_LEN,
- /*
- * Fix IW_EV_POINT_PK_LEN: some wireless.h versions define this
- * erroneously as IW_EV_LCP_LEN + 4 (e.g. ESSID will disappear).
- * The value below is from wireless tools 30.
- */
- [IW_HEADER_TYPE_POINT] = IW_EV_LCP_PK_LEN + 4,
- [IW_HEADER_TYPE_PARAM] = IW_EV_PARAM_PK_LEN,
- [IW_HEADER_TYPE_QUAL] = IW_EV_QUAL_PK_LEN
-};
-
-/* Handling flags */
-#define IW_DESCR_FLAG_NONE 0x0000 /* Obvious */
-/* Wrapper level flags */
-#define IW_DESCR_FLAG_DUMP 0x0001 /* Not part of the dump command */
-#define IW_DESCR_FLAG_EVENT 0x0002 /* Generate an event on SET */
-#define IW_DESCR_FLAG_RESTRICT 0x0004 /* GET : request is ROOT only */
- /* SET : Omit payload from generated iwevent */
-#define IW_DESCR_FLAG_NOMAX 0x0008 /* GET : no limit on request size */
-/* Driver level flags */
-#define IW_DESCR_FLAG_WAIT 0x0100 /* Wait for driver event */
-
-struct iw_ioctl_description {
- __u8 header_type; /* NULL, iw_point or other */
- __u8 token_type; /* Future */
- __u16 token_size; /* Granularity of payload */
- __u16 min_tokens; /* Min acceptable token number */
- __u16 max_tokens; /* Max acceptable token number */
- __u32 flags; /* Special handling of the request */
-};
-
-/*
- * Meta-data about all the standard Wireless Extension request we
- * know about.
- */
-static const struct iw_ioctl_description standard_ioctl_descr[] = {
- [SIOCSIWCOMMIT - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_NULL,
- },
- [SIOCGIWNAME - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_CHAR,
- .flags = IW_DESCR_FLAG_DUMP,
- },
- [SIOCSIWNWID - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- .flags = IW_DESCR_FLAG_EVENT,
- },
- [SIOCGIWNWID - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- .flags = IW_DESCR_FLAG_DUMP,
- },
- [SIOCSIWFREQ - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_FREQ,
- .flags = IW_DESCR_FLAG_EVENT,
- },
- [SIOCGIWFREQ - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_FREQ,
- .flags = IW_DESCR_FLAG_DUMP,
- },
- [SIOCSIWMODE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_UINT,
- .flags = IW_DESCR_FLAG_EVENT,
- },
- [SIOCGIWMODE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_UINT,
- .flags = IW_DESCR_FLAG_DUMP,
- },
- [SIOCSIWSENS - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCGIWSENS - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCSIWRANGE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_NULL,
- },
- [SIOCGIWRANGE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = sizeof(struct iw_range),
- .flags = IW_DESCR_FLAG_DUMP,
- },
- [SIOCSIWPRIV - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_NULL,
- },
- [SIOCGIWPRIV - SIOCIWFIRST] = { /* (handled directly by us) */
- .header_type = IW_HEADER_TYPE_NULL,
- },
- [SIOCSIWSTATS - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_NULL,
- },
- [SIOCGIWSTATS - SIOCIWFIRST] = { /* (handled directly by us) */
- .header_type = IW_HEADER_TYPE_NULL,
- .flags = IW_DESCR_FLAG_DUMP,
- },
- [SIOCSIWSPY - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = sizeof(struct sockaddr),
- .max_tokens = IW_MAX_SPY,
- },
- [SIOCGIWSPY - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = sizeof(struct sockaddr) +
- sizeof(struct iw_quality),
- .max_tokens = IW_MAX_SPY,
- },
- [SIOCSIWTHRSPY - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = sizeof(struct iw_thrspy),
- .min_tokens = 1,
- .max_tokens = 1,
- },
- [SIOCGIWTHRSPY - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = sizeof(struct iw_thrspy),
- .min_tokens = 1,
- .max_tokens = 1,
- },
- [SIOCSIWAP - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_ADDR,
- },
- [SIOCGIWAP - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_ADDR,
- .flags = IW_DESCR_FLAG_DUMP,
- },
- [SIOCSIWMLME - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .min_tokens = sizeof(struct iw_mlme),
- .max_tokens = sizeof(struct iw_mlme),
- },
- [SIOCGIWAPLIST - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = sizeof(struct sockaddr) +
- sizeof(struct iw_quality),
- .max_tokens = IW_MAX_AP,
- .flags = IW_DESCR_FLAG_NOMAX,
- },
- [SIOCSIWSCAN - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .min_tokens = 0,
- .max_tokens = sizeof(struct iw_scan_req),
- },
- [SIOCGIWSCAN - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_SCAN_MAX_DATA,
- .flags = IW_DESCR_FLAG_NOMAX,
- },
- [SIOCSIWESSID - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_ESSID_MAX_SIZE + 1,
- .flags = IW_DESCR_FLAG_EVENT,
- },
- [SIOCGIWESSID - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_ESSID_MAX_SIZE + 1,
- .flags = IW_DESCR_FLAG_DUMP,
- },
- [SIOCSIWNICKN - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_ESSID_MAX_SIZE + 1,
- },
- [SIOCGIWNICKN - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_ESSID_MAX_SIZE + 1,
- },
- [SIOCSIWRATE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCGIWRATE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCSIWRTS - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCGIWRTS - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCSIWFRAG - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCGIWFRAG - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCSIWTXPOW - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCGIWTXPOW - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCSIWRETRY - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCGIWRETRY - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCSIWENCODE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_ENCODING_TOKEN_MAX,
- .flags = IW_DESCR_FLAG_EVENT | IW_DESCR_FLAG_RESTRICT,
- },
- [SIOCGIWENCODE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_ENCODING_TOKEN_MAX,
- .flags = IW_DESCR_FLAG_DUMP | IW_DESCR_FLAG_RESTRICT,
- },
- [SIOCSIWPOWER - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCGIWPOWER - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
-#ifdef SIOCSIWMODUL
- [SIOCSIWMODUL - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
-#endif
-#ifdef SIOCGIWMODUL
- [SIOCGIWMODUL - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
-#endif
- [SIOCSIWGENIE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_GENERIC_IE_MAX,
- },
- [SIOCGIWGENIE - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_GENERIC_IE_MAX,
- },
- [SIOCSIWAUTH - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCGIWAUTH - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_PARAM,
- },
- [SIOCSIWENCODEEXT - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .min_tokens = sizeof(struct iw_encode_ext),
- .max_tokens = sizeof(struct iw_encode_ext) +
- IW_ENCODING_TOKEN_MAX,
- },
- [SIOCGIWENCODEEXT - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .min_tokens = sizeof(struct iw_encode_ext),
- .max_tokens = sizeof(struct iw_encode_ext) +
- IW_ENCODING_TOKEN_MAX,
- },
- [SIOCSIWPMKSA - SIOCIWFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .min_tokens = sizeof(struct iw_pmksa),
- .max_tokens = sizeof(struct iw_pmksa),
- },
-};
-
-static const struct iw_ioctl_description standard_event_descr[] = {
- [IWEVTXDROP - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_ADDR,
- },
- [IWEVQUAL - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_QUAL,
- },
- [IWEVCUSTOM - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_CUSTOM_MAX,
- },
- [IWEVREGISTERED - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_ADDR,
- },
- [IWEVEXPIRED - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_ADDR,
- },
- [IWEVGENIE - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_GENERIC_IE_MAX,
- },
- [IWEVMICHAELMICFAILURE - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = sizeof(struct iw_michaelmicfailure),
- },
- [IWEVASSOCREQIE - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_GENERIC_IE_MAX,
- },
- [IWEVASSOCRESPIE - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = IW_GENERIC_IE_MAX,
- },
- [IWEVPMKIDCAND - IWEVFIRST] = {
- .header_type = IW_HEADER_TYPE_POINT,
- .token_size = 1,
- .max_tokens = sizeof(struct iw_pmkid_cand),
- },
-};
-
-struct stream_descr {
- char *current; /* Current event in stream of events */
- char *value; /* Current value in event */
- char *end; /* End of the stream */
-};
-
-/*
- * Extract the next event from the event stream.
- */
-static int iw_extract_event_stream(struct stream_descr *stream,
- struct iw_event *iwe, int we_version)
-{
- const struct iw_ioctl_description *descr = NULL;
- int event_type;
- unsigned int event_len = 1; /* Invalid */
- unsigned cmd_index; /* *MUST* be unsigned */
- char *pointer;
-
- if (stream->current + IW_EV_LCP_PK_LEN > stream->end)
- return 0;
-
- /* Extract the event header to get the event id.
- * Note : the event may be unaligned, therefore copy... */
- memcpy((char *)iwe, stream->current, IW_EV_LCP_PK_LEN);
-
- if (iwe->len <= IW_EV_LCP_PK_LEN)
- return -1;
-
- /* Get the type and length of that event */
- if (iwe->cmd <= SIOCIWLAST) {
- cmd_index = iwe->cmd - SIOCIWFIRST;
- if (cmd_index < ARRAY_SIZE(standard_ioctl_descr))
- descr = standard_ioctl_descr + cmd_index;
- } else {
- cmd_index = iwe->cmd - IWEVFIRST;
- if (cmd_index < ARRAY_SIZE(standard_event_descr))
- descr = standard_event_descr + cmd_index;
- }
-
- /* Unknown events -> event_type = 0 => IW_EV_LCP_PK_LEN */
- event_type = descr ? descr->header_type : 0;
- event_len = event_type_size[event_type];
-
- /* Check if we know about this event */
- if (event_len <= IW_EV_LCP_PK_LEN) {
- stream->current += iwe->len; /* Skip to next event */
- return 2;
- }
- event_len -= IW_EV_LCP_PK_LEN;
-
- /* Fixup for earlier version of WE */
- if (we_version <= 18 && event_type == IW_HEADER_TYPE_POINT)
- event_len += IW_EV_POINT_OFF;
-
- if (stream->value != NULL)
- pointer = stream->value; /* Next value in event */
- else
- pointer = stream->current + IW_EV_LCP_PK_LEN; /* First value in event */
-
- /* Copy the rest of the event (at least, fixed part) */
- if (pointer + event_len > stream->end) {
- stream->current += iwe->len; /* Skip to next event */
- return -2;
- }
-
- /* Fixup for WE-19 and later: pointer no longer in the stream */
- /* Beware of alignment. Dest has local alignment, not packed */
- if (we_version > 18 && event_type == IW_HEADER_TYPE_POINT)
- memcpy((char *)iwe + IW_EV_LCP_LEN + IW_EV_POINT_OFF, pointer, event_len);
- else
- memcpy((char *)iwe + IW_EV_LCP_LEN, pointer, event_len);
-
- /* Skip event in the stream */
- pointer += event_len;
-
- /* Special processing for iw_point events */
- if (event_type == IW_HEADER_TYPE_POINT) {
- unsigned int extra_len = iwe->len - (event_len + IW_EV_LCP_PK_LEN);
-
- if (extra_len > 0) {
- /* Set pointer on variable part (warning : non aligned) */
- iwe->u.data.pointer = pointer;
-
- /* Check that we have a descriptor for the command */
- if (descr == NULL) {
- /* Can't check payload -> unsafe... */
- iwe->u.data.pointer = NULL; /* Discard paylod */
- } else {
- unsigned int token_len = iwe->u.data.length * descr->token_size;
- /*
- * Ugly fixup for alignment issues.
- * If the kernel is 64 bits and userspace 32 bits, we have an extra 4 + 4
- * bytes. Fixing that in the kernel would break 64 bits userspace.
- */
- if (token_len != extra_len && extra_len >= 4) {
- union iw_align_u16 {
- __u16 value;
- unsigned char byte[2];
- } alt_dlen;
- unsigned int alt_token_len;
-
- /* Userspace seems to not always like unaligned access,
- * so be careful and make sure to align value.
- * I hope gcc won't play any of its aliasing tricks... */
- alt_dlen.byte[0] = *(pointer);
- alt_dlen.byte[1] = *(pointer + 1);
- alt_token_len = alt_dlen.value * descr->token_size;
-
- /* Verify that data is consistent if assuming 64 bit alignment... */
- if (alt_token_len + 8 == extra_len) {
-
- /* Ok, let's redo everything */
- pointer -= event_len;
- pointer += 4;
-
- /* Dest has local alignment, not packed */
- memcpy((char *)iwe + IW_EV_LCP_LEN + IW_EV_POINT_OFF, pointer, event_len);
- pointer += event_len + 4;
- token_len = alt_token_len;
-
- /* We may have no payload */
- if (alt_token_len)
- iwe->u.data.pointer = pointer;
- else
- iwe->u.data.pointer = NULL;
- }
- }
-
- /* Discard bogus events which advertise more tokens than they carry ... */
- if (token_len > extra_len)
- iwe->u.data.pointer = NULL; /* Discard paylod */
-
- /* Check that the advertised token size is not going to
- * produce buffer overflow to our caller... */
- if (iwe->u.data.length > descr->max_tokens
- && !(descr->flags & IW_DESCR_FLAG_NOMAX))
- iwe->u.data.pointer = NULL; /* Discard payload */
-
- /* Same for underflows... */
- if (iwe->u.data.length < descr->min_tokens)
- iwe->u.data.pointer = NULL; /* Discard paylod */
- }
- } else {
- /* No data */
- iwe->u.data.pointer = NULL;
- }
-
- stream->current += iwe->len; /* Go to next event */
- } else {
- /*
- * Ugly fixup for alignment issues.
- * If the kernel is 64 bits and userspace 32 bits, we have an extra 4 bytes.
- * Fixing that in the kernel would break 64 bits userspace.
- */
- if (stream->value == NULL &&
- ((iwe->len - IW_EV_LCP_PK_LEN) % event_len == 4 ||
- (iwe->len == 12 && (event_type == IW_HEADER_TYPE_UINT ||
- event_type == IW_HEADER_TYPE_QUAL)))) {
-
- pointer -= event_len;
- pointer += 4;
-
- /* Beware of alignment. Dest has local alignment, not packed */
- memcpy((char *)iwe + IW_EV_LCP_LEN, pointer, event_len);
- pointer += event_len;
- }
-
- if (pointer + event_len <= stream->current + iwe->len) {
- stream->value = pointer; /* Go to next value */
- } else {
- stream->value = NULL;
- stream->current += iwe->len; /* Go to next event */
- }
- }
- return 1;
-}
-
-static void iw_extract_ie(struct iw_event *iwe, struct scan_entry *sr)
-{
- const uint8_t wpa1_oui[3] = { 0x00, 0x50, 0xf2 };
- uint8_t *buffer = iwe->u.data.pointer;
- int ielen = 0, ietype, i;
-
- /* Loop on each IE, each is min. 2 bytes TLV: IE-ID - Length - Value */
- for (i = 0; i <= iwe->u.data.length - 2; i += ielen + 2) {
- ietype = buffer[i];
- ielen = buffer[i + 1];
-
- switch (ietype) {
- case 0x30:
- if (ielen < 4) /* make sure we have enough data */
- continue;
- sr->flags |= IW_ENC_CAPA_WPA2;
- break;
- case 0xdd:
- /* Not all IEs that start with 0xdd are WPA1 */
- if (ielen < 8 || memcmp(buffer + i + 2, wpa1_oui, 3) ||
- buffer[i + 5] != 1)
- continue;
- sr->flags |= IW_ENC_CAPA_WPA;
- break;
- }
- }
-}
-/*----------------- End of code copied from iwlib -----------------------*/
/*
* Ordering functions for scan results: all return true for a < b.
@@ -561,7 +28,9 @@ static bool cmp_freq(const struct scan_entry *a, const struct scan_entry *b)
/* Order by signal strength. */
static bool cmp_sig(const struct scan_entry *a, const struct scan_entry *b)
{
- return a->qual.level < b->qual.level;
+ if (!a->bss_signal && !b->bss_signal)
+ return a->bss_signal_qual < b->bss_signal_qual;
+ return a->bss_signal < b->bss_signal;
}
/* Order by ESSID, organize entries with same ESSID by frequency and signal. */
@@ -573,6 +42,12 @@ static bool cmp_essid(const struct scan_entry *a, const struct scan_entry *b)
: res < 0;
}
+/* Order by MAC address */
+static bool cmp_mac(const struct scan_entry *a, const struct scan_entry *b)
+{
+ return memcmp(&a->ap_addr, &b->ap_addr, sizeof(a->ap_addr)) < 0;
+}
+
/* Order by frequency, grouping channels by ESSID. */
static bool cmp_chan(const struct scan_entry *a, const struct scan_entry *b)
{
@@ -600,116 +75,202 @@ static bool cmp_open_sig(const struct scan_entry *a, const struct scan_entry *b)
static bool (*scan_cmp[])(const struct scan_entry *, const struct scan_entry *) = {
[SO_CHAN] = cmp_chan,
[SO_SIGNAL] = cmp_sig,
+ [SO_MAC] = cmp_mac,
[SO_ESSID] = cmp_essid,
[SO_OPEN] = cmp_open,
[SO_CHAN_SIG] = cmp_chan_sig,
[SO_OPEN_SIG] = cmp_open_sig
};
+/*
+ * Scan event handling
+ */
+
+/* Callback event handler */
+static int wait_event(struct nl_msg *msg, void *arg)
+{
+ struct wait_event *wait = arg;
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ int i;
+
+ for (i = 0; i < wait->n_cmds; i++) {
+ if (gnlh->cmd == wait->cmds[i])
+ wait->cmd = gnlh->cmd;
+ }
+ return NL_SKIP;
+}
+
/**
- * Produce ranked list of scan results.
- * @ifname: interface name to run scan on
- * @we_version: version of the WE extensions (needed internally)
+ * Wait for scan result notification sent by the kernel
+ * Returns true if scan results are available, false if scan was aborted.
+ * Taken from iw:event.c:__do_listen_events
*/
-static struct scan_entry *get_scan_list(const char *ifname, int we_version)
+static bool wait_for_scan_events(struct scan_result *sr)
{
- struct scan_entry *head = NULL, **tailp = &head;
- struct iwreq wrq;
- int wait, waited = 0;
- int skfd = socket(AF_INET, SOCK_DGRAM, 0);
-
- if (skfd < 0)
- err_sys("%s: can not open socket", __func__);
- /*
- * Some drivers may return very large scan results, either because there
- * are many cells, or there are many large elements. Do not bother to
- * guess buffer size, use maximum u16 wrq.u.data.length size.
- */
- char scan_buf[0xffff];
-
- /* We are checking errno when returning NULL, so reset it here */
- errno = 0;
-
- memset(&wrq, 0, sizeof(wrq));
- strncpy(wrq.ifr_ifrn.ifrn_name, ifname, IFNAMSIZ);
- if (ioctl(skfd, SIOCSIWSCAN, &wrq) < 0)
- goto done;
-
- /* Larger initial timeout of 250ms between set and first get */
- for (wait = 250; (waited += wait) < MAX_SCAN_WAIT; wait = 100) {
- struct timeval tv = { 0, wait * 1000 };
-
- while (select(0, NULL, NULL, NULL, &tv) < 0)
- if (errno != EINTR && errno != EAGAIN)
- return NULL;
-
- wrq.u.data.pointer = scan_buf;
- wrq.u.data.length = sizeof(scan_buf);
- wrq.u.data.flags = 0;
-
- if (ioctl(skfd, SIOCGIWSCAN, &wrq) == 0)
- break;
+ static const uint32_t cmds[] = {
+ NL80211_CMD_NEW_SCAN_RESULTS,
+ NL80211_CMD_SCAN_ABORTED,
+ };
+ struct wait_event wait_ev = {
+ .cmds = cmds,
+ .n_cmds = ARRAY_SIZE(cmds),
+ .cmd = 0
+ };
+ struct nl_cb *cb;
+
+ if (!scan_wait_sk)
+ scan_wait_sk = alloc_nl_mcast_sk("scan");
+
+ cb = nl_cb_alloc(IW_NL_CB_DEBUG ? NL_CB_DEBUG : NL_CB_DEFAULT);
+ if (!cb)
+ err_sys("failed to allocate netlink callbacks");
+
+ /* no sequence checking for multicast messages */
+ nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL);
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, wait_event, &wait_ev);
+
+ while (!wait_ev.cmd)
+ nl_recvmsgs(scan_wait_sk, cb);
+ nl_cb_put(cb);
+
+ return wait_ev.cmd == NL80211_CMD_NEW_SCAN_RESULTS;
+}
+
+/**
+ * Scan result handler. Stolen from iw:scan.c
+ * This also updates the scan-result statistics.
+ */
+int scan_dump_handler(struct nl_msg *msg, void *arg)
+{
+ struct scan_result *sr = (struct scan_result *)arg;
+ struct scan_entry *new = calloc(1, sizeof(*new));
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *tb[NL80211_ATTR_MAX + 1];
+ struct nlattr *bss[NL80211_BSS_MAX + 1];
+ static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
+ [NL80211_BSS_TSF] = { .type = NLA_U64 },
+ [NL80211_BSS_FREQUENCY] = { .type = NLA_U32 },
+ [NL80211_BSS_BSSID] = { },
+ [NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 },
+ [NL80211_BSS_CAPABILITY] = { .type = NLA_U16 },
+ [NL80211_BSS_INFORMATION_ELEMENTS] = { },
+ [NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 },
+ [NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 },
+ [NL80211_BSS_STATUS] = { .type = NLA_U32 },
+ [NL80211_BSS_SEEN_MS_AGO] = { .type = NLA_U32 },
+ [NL80211_BSS_BEACON_IES] = { },
+ };
+
+ nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!tb[NL80211_ATTR_BSS])
+ return NL_SKIP;
+
+ if (nla_parse_nested(bss, NL80211_BSS_MAX,
+ tb[NL80211_ATTR_BSS],
+ bss_policy))
+ return NL_SKIP;
+
+ if (!bss[NL80211_BSS_BSSID])
+ return NL_SKIP;
+
+ new = calloc(1, sizeof(*new));
+ if (!new)
+ err_sys("failed to allocate scan entry");
+
+ memcpy(&new->ap_addr, nla_data(bss[NL80211_BSS_BSSID]), sizeof(new->ap_addr));
+
+ if (bss[NL80211_BSS_FREQUENCY]) {
+ new->freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
+ new->chan = ieee80211_frequency_to_channel(new->freq);
}
- if (wrq.u.data.length) {
- struct iw_event iwe;
- struct stream_descr stream;
- struct scan_entry *new = NULL;
- int f = 0; /* Idea taken from waproamd */
+ if (bss[NL80211_BSS_SIGNAL_UNSPEC])
+ new->bss_signal_qual = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
- memset(&stream, 0, sizeof(stream));
- stream.current = scan_buf;
- stream.end = scan_buf + wrq.u.data.length;
- while (iw_extract_event_stream(&stream, &iwe, we_version) > 0) {
- if (!new)
- new = calloc(1, sizeof(*new));
+ if (bss[NL80211_BSS_SIGNAL_MBM]) {
+ int s = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]);
+ new->bss_signal = s / 100;
+ }
- switch (iwe.cmd) {
- case SIOCGIWAP:
- f = 1;
- memcpy(&new->ap_addr, &iwe.u.ap_addr.sa_data, sizeof(new->ap_addr));
- break;
- case SIOCGIWESSID:
- f |= 2;
- memset(new->essid, 0, sizeof(new->essid));
+ if (bss[NL80211_BSS_CAPABILITY]) {
+ new->bss_capa = nla_get_u16(bss[NL80211_BSS_CAPABILITY]);
+ new->has_key = (new->bss_capa & WLAN_CAPABILITY_PRIVACY) != 0;
+ }
- if (iwe.u.essid.flags && iwe.u.essid.pointer && iwe.u.essid.length)
- memcpy(new->essid, iwe.u.essid.pointer, iwe.u.essid.length);
- break;
- case SIOCGIWMODE:
- new->mode = iwe.u.mode;
- f |= 4;
- break;
- case SIOCGIWFREQ:
- f |= 8;
- new->freq = freq_to_hz(&iwe.u.freq);
- break;
- case SIOCGIWENCODE:
- f |= 16;
- new->has_key = !(iwe.u.data.flags & IW_ENCODE_DISABLED);
- break;
- case IWEVQUAL:
- f |= 32;
- memcpy(&new->qual, &iwe.u.qual, sizeof(struct iw_quality));
+ if (bss[NL80211_BSS_SEEN_MS_AGO])
+ new->last_seen = nla_get_u32(bss[NL80211_BSS_SEEN_MS_AGO]);
+
+ if (bss[NL80211_BSS_TSF])
+ new->tsf = nla_get_u64(bss[NL80211_BSS_TSF]);
+
+ if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) {
+ uint8_t *ie = nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
+ int ielen = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
+ uint8_t len = ie[1];
+
+ while (ielen >= 2 && ielen >= ie[1]) {
+ switch (ie[0]) {
+ case 0: /* SSID */
+ if (len > 0 && len <= 32)
+ print_ssid_escaped(new->essid, sizeof(new->essid),
+ ie+2, len);
break;
- case IWEVGENIE:
- f |= 64;
- iw_extract_ie(&iwe, new);
+ case 11: /* BSS Load */
+ if (len >= 5) {
+ new->bss_sta_count = ie[3] << 8 | ie[2];
+ new->bss_chan_usage = ie[4];
+ }
break;
}
- if (f == 127) {
- f = 0;
- *tailp = new;
- tailp = &new->next;
- new = NULL;
- }
- }
- free(new); /* may have been allocated, but not filled in */
+ ielen -= ie[1] + 2;
+ ie += ie[1] + 2;
+ }
}
-done:
- close(skfd);
- return head;
+
+ /* Update stats */
+ new->next = sr->head;
+ sr->head = new;
+ if (str_is_ascii(new->essid))
+ sr->max_essid_len = clamp(strlen(new->essid),
+ sr->max_essid_len,
+ IW_ESSID_MAX_SIZE);
+
+ if (new->freq > 45000) /* 802.11ad 60GHz spectrum */
+ err_quit("FIXME: can not handle %d MHz spectrum yet", new->freq);
+ else if (new->freq >= 5000)
+ sr->num.five_gig++;
+ else if (new->freq >= 2000)
+ sr->num.two_gig++;
+ sr->num.entries += 1;
+ sr->num.open += !new->has_key;
+
+ return NL_SKIP;
+}
+
+static int iw_nl80211_scan_trigger(void)
+{
+ static struct cmd cmd_trigger_scan = {
+ .cmd = NL80211_CMD_TRIGGER_SCAN,
+ };
+
+ return handle_cmd(&cmd_trigger_scan);
+}
+
+static int iw_nl80211_get_scan_data(struct scan_result *sr)
+{
+ static struct cmd cmd_scan_dump = {
+ .cmd = NL80211_CMD_GET_SCAN,
+ .flags = NLM_F_DUMP,
+ .handler = scan_dump_handler
+ };
+
+ memset(sr, 0, sizeof(*sr));
+ cmd_scan_dump.handler_arg = sr;
+
+ return handle_cmd(&cmd_scan_dump);
}
/*
@@ -740,6 +301,19 @@ static void free_scan_list(struct scan_entry *head)
}
}
+static void clear_scan_list(struct scan_result *sr)
+{
+ pthread_mutex_lock(&sr->mutex);
+ free_scan_list(sr->head);
+ free(sr->channel_stats);
+ sr->head = NULL;
+ sr->channel_stats = NULL;
+ sr->msg[0] = '\0';
+ sr->max_essid_len = MAX_ESSID_LEN;
+ memset(&(sr->num), 0, sizeof(sr->num));
+ pthread_mutex_unlock(&sr->mutex);
+}
+
/*
* Channel statistics shown at the bottom of scan screen.
*/
@@ -797,13 +371,18 @@ static void compute_channel_stats(struct scan_result *sr)
*/
void scan_result_init(struct scan_result *sr)
{
+ pthread_mutexattr_t ma;
+
memset(sr, 0, sizeof(*sr));
- iw_getinf_range(conf_ifname(), &sr->range);
- pthread_mutex_init(&sr->mutex, NULL);
+ pthread_mutexattr_init(&ma);
+ if (pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST) < 0)
+ err_sys("Failed to set the mutex robust attribute");
+ pthread_mutex_init(&sr->mutex, &ma);
}
void scan_result_fini(struct scan_result *sr)
{
+ /* FIXME: this may have a bug on resource de-allocation, if the main thread still holds the lock */
free_scan_list(sr->head);
free(sr->channel_stats);
pthread_mutex_destroy(&sr->mutex);
@@ -812,83 +391,63 @@ void scan_result_fini(struct scan_result *sr)
/** The actual scan thread. */
void *do_scan(void *sr_ptr)
{
- struct scan_result *sr = (struct scan_result *)sr_ptr;
- struct scan_entry *cur;
+ struct scan_result *sr = sr_ptr;
+ sigset_t blockmask;
+ int ret = 0;
- pthread_detach(pthread_self());
+ /* SIGWINCH is supposed to be handled in the main thread. */
+ sigemptyset(&blockmask);
+ sigaddset(&blockmask, SIGWINCH);
+ pthread_sigmask(SIG_BLOCK, &blockmask, NULL);
+ pthread_detach(pthread_self());
do {
- pthread_mutex_lock(&sr->mutex);
-
- free_scan_list(sr->head);
- free(sr->channel_stats);
-
- sr->head = NULL;
- sr->channel_stats = NULL;
- sr->msg[0] = '\0';
- sr->max_essid_len = MAX_ESSID_LEN;
- memset(&(sr->num), 0, sizeof(sr->num));
-
- sr->head = get_scan_list(conf_ifname(), sr->range.we_version_compiled);
- if (!sr->head) {
- switch(errno) {
- case EPERM:
- /* Don't try to read leftover results, it does not work reliably. */
- if (!has_net_admin_capability())
+ clear_scan_list(sr);
+
+ ret = iw_nl80211_scan_trigger();
+ switch(-ret) {
+ case 0:
+ case EBUSY:
+ /* Trigger returns -EBUSY if a scan request is pending or ready. */
+ if (!wait_for_scan_events(sr)) {
+ snprintf(sr->msg, sizeof(sr->msg), "Waiting for scan data...");
+ } else {
+ pthread_mutex_lock(&sr->mutex);
+ ret = iw_nl80211_get_scan_data(sr);
+ if (ret < 0) {
snprintf(sr->msg, sizeof(sr->msg),
- "This screen requires CAP_NET_ADMIN permissions");
- break;
- case EFAULT:
- /*
- * EFAULT can occur after a window resizing event and is temporary.
- * It may also occur when the interface is down, hence defer handling.
- */
- break;
- case EINTR:
- case EBUSY:
- case EAGAIN:
- /* Temporary errors. */
- snprintf(sr->msg, sizeof(sr->msg), "Waiting for scan data on %s ...", conf_ifname());
- break;
- case ENETDOWN:
+ "Scan failed on %s: %s", conf_ifname(), strerror(-ret));
+ } else if (!sr->head) {
+ snprintf(sr->msg, sizeof(sr->msg), "Empty scan results on %s", conf_ifname());
+ }
+ compute_channel_stats(sr);
+ pthread_mutex_unlock(&sr->mutex);
+ }
+ break;
+ case EPERM:
+ if (!has_net_admin_capability())
+ snprintf(sr->msg, sizeof(sr->msg),
+ "This screen requires CAP_NET_ADMIN permissions");
+ return NULL;
+ case EFAULT:
+ /* EFAULT can occur after a window resizing event: temporary, fall through. */
+ case EINTR:
+ case EAGAIN:
+ /* Temporary errors. */
+ snprintf(sr->msg, sizeof(sr->msg), "Waiting for device to become ready ...");
+ break;
+ case ENETDOWN:
+ if (!if_is_up(conf_ifname())) {
snprintf(sr->msg, sizeof(sr->msg), "Interface %s is down - setting it up ...", conf_ifname());
if (if_set_up(conf_ifname()) < 0)
err_sys("Can not bring up interface '%s'", conf_ifname());
break;
- case E2BIG:
- /*
- * This is a driver issue, since already using the largest possible
- * scan buffer. See comments in iwlist.c of wireless tools.
- */
- snprintf(sr->msg, sizeof(sr->msg),
- "No scan on %s: Driver returned too much data", conf_ifname());
- break;
- case 0:
- snprintf(sr->msg, sizeof(sr->msg), "Empty scan results on %s", conf_ifname());
- break;
- default:
- snprintf(sr->msg, sizeof(sr->msg),
- "Scan failed on %s: %s", conf_ifname(), strerror(errno));
}
+ /* fall through */
+ default:
+ snprintf(sr->msg, sizeof(sr->msg),
+ "Scan trigger failed on %s: %s", conf_ifname(), strerror(-ret));
}
-
- for (cur = sr->head; cur; cur = cur->next) {
- if (str_is_ascii(cur->essid))
- sr->max_essid_len = clamp(strlen(cur->essid),
- sr->max_essid_len,
- IW_ESSID_MAX_SIZE);
- iw_sanitize(&sr->range, &cur->qual, &cur->dbm);
- cur->chan = freq_to_channel(cur->freq, &sr->range);
- if (cur->freq >= 5e9)
- sr->num.five_gig++;
- else if (cur->freq >= 2e9)
- sr->num.two_gig++;
- sr->num.entries += 1;
- sr->num.open += !cur->has_key;
- }
- compute_channel_stats(sr);
-
- pthread_mutex_unlock(&sr->mutex);
} while (usleep(conf.stat_iv * 1000) == 0);
return NULL;