From 0b0aac6ce21bcb38d7e03dc2b3ff419861476a24 Mon Sep 17 00:00:00 2001 From: Jonathan McCrohan Date: Sat, 30 Jan 2016 17:08:40 +0000 Subject: Imported Upstream version 0.8.0 --- iw_scan.c | 959 +++++++++++++++++--------------------------------------------- 1 file changed, 259 insertions(+), 700 deletions(-) (limited to 'iw_scan.c') 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 /* lsearch(3) */ +#include +#include +#include +#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; -- cgit v1.2.3