aboutsummaryrefslogtreecommitdiffstats
path: root/iw_scan.c
diff options
context:
space:
mode:
Diffstat (limited to 'iw_scan.c')
-rw-r--r--iw_scan.c264
1 files changed, 196 insertions, 68 deletions
diff --git a/iw_scan.c b/iw_scan.c
index c701718..740bdff 100644
--- a/iw_scan.c
+++ b/iw_scan.c
@@ -519,7 +519,7 @@ static int iw_extract_event_stream(struct stream_descr *stream,
return 1;
}
-static void iw_extract_ie(struct iw_event *iwe, struct scan_result *sr)
+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;
@@ -549,64 +549,77 @@ static void iw_extract_ie(struct iw_event *iwe, struct scan_result *sr)
/*----------------- End of code copied from iwlib -----------------------*/
/*
- * Ordering functions for scan results
+ * Ordering functions for scan results: all return true for a < b.
*/
-/* Order by ascending frequency. */
-static int cmp_chan(const struct scan_result *a, const struct scan_result *b)
+
+/* Order by frequency. */
+static bool cmp_freq(const struct scan_entry *a, const struct scan_entry *b)
{
return a->freq < b->freq;
}
-static int cmp_chan_rev(const struct scan_result *a, const struct scan_result *b)
+/* Order by signal strength. */
+static bool cmp_sig(const struct scan_entry *a, const struct scan_entry *b)
{
- return cmp_chan(b, a);
+ return a->qual.level < b->qual.level;
}
-/* Order by descending signal strength. */
-static int cmp_sig(const struct scan_result *a, const struct scan_result *b)
+/* Order by ESSID, organize entries with same ESSID by frequency and signal. */
+static bool cmp_essid(const struct scan_entry *a, const struct scan_entry *b)
{
- return a->qual.level - b->qual.level;
+ int res = strncmp(a->essid, b->essid, IW_ESSID_MAX_SIZE);
+
+ return res == 0 ? (a->freq == b->freq ? cmp_sig(a, b) : cmp_freq(a, b))
+ : res < 0;
}
-/* Order by ascending frequency first, then by descending signal strength. */
-static int cmp_chan_sig(const struct scan_result *a, const struct scan_result *b)
+/* Order by frequency, grouping channels by ESSID. */
+static bool cmp_chan(const struct scan_entry *a, const struct scan_entry *b)
{
- return a->freq == b->freq ? cmp_sig(a, b) : cmp_chan(a, b);
+ return a->freq == b->freq ? cmp_essid(a, b) : cmp_freq(a, b);
}
-/* Show open access points first, ordered by mode. */
-static int cmp_open(const struct scan_result *a, const struct scan_result *b)
+/* Order by frequency first, then by signal strength. */
+static bool cmp_chan_sig(const struct scan_entry *a, const struct scan_entry *b)
{
- return a->has_key > b->has_key;
+ return a->freq == b->freq ? cmp_sig(a, b) : cmp_chan(a, b);
}
-/* Sort (open) access points by descending signal strength. */
-static int cmp_open_sig(const struct scan_result *a, const struct scan_result *b)
+/* Order by openness (open access points frist). */
+static bool cmp_open(const struct scan_entry *a, const struct scan_entry *b)
{
- return a->has_key == b->has_key ? cmp_sig(a, b) : cmp_open(a, b);
+ return a->has_key < b->has_key;
}
-/* Sort (open) access points by ascending frequency, then by descending signal. */
-static int cmp_open_chan_sig(const struct scan_result *a, const struct scan_result *b)
+/* Sort (open) access points by signal strength. */
+static bool cmp_open_sig(const struct scan_entry *a, const struct scan_entry *b)
{
- return a->has_key == b->has_key ? cmp_chan_sig(a, b) : cmp_open(a, b);
+ return a->has_key == b->has_key ? cmp_sig(a, b) : cmp_open(a, b);
}
-static int (*scan_cmp[])(const struct scan_result *, const struct scan_result *) = {
+static bool (*scan_cmp[])(const struct scan_entry *, const struct scan_entry *) = {
[SO_CHAN] = cmp_chan,
- [SO_CHAN_REV] = cmp_chan_rev,
[SO_SIGNAL] = cmp_sig,
+ [SO_ESSID] = cmp_essid,
[SO_OPEN] = cmp_open,
[SO_CHAN_SIG] = cmp_chan_sig,
- [SO_OPEN_SIG] = cmp_open_sig,
- [SO_OPEN_CH_SI] = cmp_open_chan_sig
+ [SO_OPEN_SIG] = cmp_open_sig
};
-struct scan_result *get_scan_list(int skfd, const char *ifname, int we_version)
+/**
+ * Produce ranked list of scan results.
+ * @ifname: interface name to run scan on
+ * @we_version: version of the WE extensions (needed internally)
+ */
+static struct scan_entry *get_scan_list(const char *ifname, int we_version)
{
- struct scan_result *head = NULL;
+ 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
@@ -620,7 +633,7 @@ struct scan_result *get_scan_list(int skfd, const char *ifname, int we_version)
memset(&wrq, 0, sizeof(wrq));
strncpy(wrq.ifr_ifrn.ifrn_name, ifname, IFNAMSIZ);
if (ioctl(skfd, SIOCSIWSCAN, &wrq) < 0)
- return NULL;
+ goto done;
/* Larger initial timeout of 250ms between set and first get */
for (wait = 250; (waited += wait) < MAX_SCAN_WAIT; wait = 100) {
@@ -641,7 +654,7 @@ struct scan_result *get_scan_list(int skfd, const char *ifname, int we_version)
if (wrq.u.data.length) {
struct iw_event iwe;
struct stream_descr stream;
- struct scan_result *new = NULL;
+ struct scan_entry *new = NULL;
int f = 0; /* Idea taken from waproamd */
memset(&stream, 0, sizeof(stream));
@@ -686,37 +699,53 @@ struct scan_result *get_scan_list(int skfd, const char *ifname, int we_version)
break;
}
if (f == 127) {
- struct scan_result *cur = head, **prev = &head;
-
- while (cur && scan_cmp[conf.scan_sort_order](cur, new) > 0)
- prev = &cur->next, cur = cur->next;
-
- *prev = new;
- new->next = cur;
- new = NULL;
- f = 0;
+ f = 0;
+ *tailp = new;
+ tailp = &new->next;
+ new = NULL;
}
}
- free(new); /* may have been allocated but not filled in */
+ free(new); /* may have been allocated, but not filled in */
}
+done:
+ close(skfd);
return head;
}
-void free_scan_result(struct scan_result *head)
+/*
+ * Simple sort routine.
+ * FIXME: use hash or tree to store entries, a list to display them.
+ */
+void sort_scan_list(struct scan_entry **headp)
+{
+ struct scan_entry *head = NULL, *cur, *new = *headp, **prev;
+
+ while (new) {
+ for (cur = head, prev = &head; cur &&
+ conf.scan_sort_asc == scan_cmp[conf.scan_sort_order](cur, new);
+ prev = &cur->next, cur = cur->next)
+ ;
+ *prev = new;
+ new = new->next;
+ (*prev)->next = cur;
+ }
+ *headp = head;
+}
+
+static void free_scan_list(struct scan_entry *head)
{
if (head) {
- free_scan_result(head->next);
+ free_scan_list(head->next);
free(head);
}
}
/*
- * Channel statistics
+ * Channel statistics shown at the bottom of scan screen.
*/
-
/*
- * For lfind, it compares key value with array member, needs to
+ * For lsearch, it compares key value with array member, needs to
* return 0 if they are the same, non-0 otherwise.
*/
static int cmp_key(const void *a, const void *b)
@@ -727,41 +756,140 @@ static int cmp_key(const void *a, const void *b)
/* For quick-sorting the array in descending order of counts */
static int cmp_cnt(const void *a, const void *b)
{
- if (conf.scan_sort_order == SO_CHAN_REV)
+ if (conf.scan_sort_order == SO_CHAN && !conf.scan_sort_asc)
return ((struct cnt *)a)->count - ((struct cnt *)b)->count;
return ((struct cnt *)b)->count - ((struct cnt *)a)->count;
}
-struct cnt *channel_stats(struct scan_result *head,
- struct iw_range *iw_range, int *max_cnt)
+/**
+ * Fill in sr->channel_stats (must not have been allocated yet).
+ */
+static void compute_channel_stats(struct scan_result *sr)
{
- struct scan_result *cur;
- struct cnt *arr = NULL, *bin, key = {0, 0};
- size_t cnt = 0, n = 0;
-
- for (cur = head; cur; cur = cur->next)
- cnt++;
- if (!cnt)
- return NULL;
-
- arr = calloc(cnt, sizeof(key));
- for (cur = head; cur && n < *max_cnt; cur = cur->next) {
- key.val = freq_to_channel(cur->freq, iw_range);
-
- if (key.val >= 0) {
- bin = lsearch(&key, arr, &n, sizeof(key), cmp_key);
+ struct scan_entry *cur;
+ struct cnt *bin, key = {0, 0};
+ size_t n = 0;
+
+ if (!sr->num.entries)
+ return;
+
+ sr->channel_stats = calloc(sr->num.entries, sizeof(key));
+ for (cur = sr->head; cur; cur = cur->next) {
+ if (cur->chan >= 0) {
+ key.val = cur->chan;
+ bin = lsearch(&key, sr->channel_stats, &n, sizeof(key), cmp_key);
if (bin)
bin->count++;
}
}
- if (n < *max_cnt)
- *max_cnt = n;
if (n > 0) {
- qsort(arr, n, sizeof(key), cmp_cnt);
+ qsort(sr->channel_stats, n, sizeof(key), cmp_cnt);
} else {
- free(arr);
- return NULL;
+ free(sr->channel_stats);
+ sr->channel_stats = NULL;
}
- return arr;
+ sr->num.ch_stats = n < MAX_CH_STATS ? n : MAX_CH_STATS;
+}
+
+/*
+ * Scan results.
+ */
+void scan_result_init(struct scan_result *sr)
+{
+ memset(sr, 0, sizeof(*sr));
+ iw_getinf_range(conf_ifname(), &sr->range);
+ pthread_mutex_init(&sr->mutex, NULL);
+}
+
+void scan_result_fini(struct scan_result *sr)
+{
+ free_scan_list(sr->head);
+ free(sr->channel_stats);
+ pthread_mutex_destroy(&sr->mutex);
+}
+
+/** The actual scan thread. */
+void *do_scan(void *sr_ptr)
+{
+ struct scan_result *sr = (struct scan_result *)sr_ptr;
+ struct scan_entry *cur;
+
+ 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())
+ 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:
+ 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));
+ }
+ }
+
+ 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;
}