/* * * Wireless daemon for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "linux/nl80211.h" #include "src/iwd.h" #include "src/wiphy.h" #include "src/ie.h" #include "src/scan.h" #define SCAN_MAX_INTERVAL 320 #define SCAN_INIT_INTERVAL 10 struct l_queue *scan_contexts = NULL; struct l_genl_family *nl80211 = NULL; uint32_t scan_id = 0; struct scan_periodic { struct l_timeout *timeout; uint16_t interval; scan_notify_func_t callback; void *userdata; bool rearm:1; bool retry:1; }; struct scan_request { scan_trigger_func_t trigger; scan_notify_func_t callback; void *userdata; scan_destroy_func_t destroy; bool passive:1; /* Active or Passive scan? */ bool triggered:1; }; struct scan_context { uint32_t ifindex; enum scan_state state; struct scan_periodic sp; struct l_queue *requests; }; struct scan_results { uint32_t wiphy; uint32_t ifindex; struct l_queue *bss_list; struct scan_freq_set *freqs; scan_notify_func_t callback; void *userdata; scan_destroy_func_t destroy; }; static void scan_done(struct l_genl_msg *msg, void *userdata); static bool scan_context_match(const void *a, const void *b) { const struct scan_context *sc = a; uint32_t ifindex = L_PTR_TO_UINT(b); return (sc->ifindex == ifindex); } static void scan_request_free(void *data) { struct scan_request *sr = data; free(sr); } static struct scan_context *scan_context_new(uint32_t ifindex) { struct scan_context *sc; sc = l_new(struct scan_context, 1); sc->ifindex = ifindex; sc->state = SCAN_STATE_NOT_RUNNING; sc->requests = l_queue_new(); return sc; } static void scan_context_free(struct scan_context *sc) { l_debug("sc: %p", sc); l_queue_destroy(sc->requests, scan_request_free); if (sc->sp.timeout) l_timeout_remove(sc->sp.timeout); l_free(sc); } bool scan_ifindex_add(uint32_t ifindex) { struct scan_context *sc; sc = l_queue_find(scan_contexts, scan_context_match, L_UINT_TO_PTR(ifindex)); if (sc) return false; sc = scan_context_new(ifindex); l_queue_push_head(scan_contexts, sc); return true; } bool scan_ifindex_remove(uint32_t ifindex) { struct scan_context *sc; sc = l_queue_remove_if(scan_contexts, scan_context_match, L_UINT_TO_PTR(ifindex)); if (!sc) return false; l_info("Removing scan context for ifindex: %u", ifindex); scan_context_free(sc); return true; } static bool __scan_passive_start(struct l_genl_family *nl80211, uint32_t ifindex, scan_func_t callback, void *user_data) { struct l_genl_msg *msg; msg = l_genl_msg_new_sized(NL80211_CMD_TRIGGER_SCAN, 16); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &ifindex); if (!l_genl_family_send(nl80211, msg, callback, user_data, NULL)) { l_error("Starting scheduled scan failed"); return false; } return true; } static void start_next_scan_request(void *userdata) { uint32_t ifindex = L_PTR_TO_UINT(userdata); struct scan_context *sc; struct scan_request *sr; bool r; sc = l_queue_find(scan_contexts, scan_context_match, L_UINT_TO_PTR(ifindex)); if (!sc) return; sr = l_queue_peek_head(sc->requests); r = __scan_passive_start(nl80211, ifindex, scan_done, sc); if (!r) { l_error("Could not send CMD_TRIGGER_SCAN"); if (sr->destroy) sr->destroy(sr->userdata); sr = l_queue_pop_head(sc->requests); scan_request_free(sr); if (!l_queue_isempty(sc->requests)) l_idle_oneshot(start_next_scan_request, L_UINT_TO_PTR(sc->ifindex), NULL); } } static void scan_done(struct l_genl_msg *msg, void *userdata) { struct scan_context *sc = userdata; struct scan_request *sr = l_queue_peek_head(sc->requests); int err; l_debug(""); err = l_genl_msg_get_error(msg); if (err < 0) { /* Scan in progress, defer */ if (err == -EBUSY) return; if (sr->trigger) sr->trigger(err, sr->userdata); if (sr->destroy) sr->destroy(sr->userdata); l_queue_pop_head(sc->requests); scan_request_free(sr); l_error("Received an error during CMD_TRIGGER_SCAN"); if (!l_queue_isempty(sc->requests)) l_idle_oneshot(start_next_scan_request, L_UINT_TO_PTR(sc->ifindex), NULL); return; } sc->state = sr->passive ? SCAN_STATE_PASSIVE : SCAN_STATE_ACTIVE; l_debug("Passive scan triggered for ifindex: %u", sc->ifindex); sr->triggered = true; } bool scan_passive(uint32_t ifindex, scan_trigger_func_t trigger, scan_notify_func_t notify, void *userdata, scan_destroy_func_t destroy) { struct scan_context *sc; struct scan_request *sr; sc = l_queue_find(scan_contexts, scan_context_match, L_UINT_TO_PTR(ifindex)); if (!sc) return false; sr = l_new(struct scan_request, 1); sr->trigger = trigger; sr->callback = notify; sr->userdata = userdata; sr->destroy = destroy; sr->passive = true; if (l_queue_length(sc->requests) > 0) goto done; if (sc->state != SCAN_STATE_NOT_RUNNING) goto done; if (!__scan_passive_start(nl80211, ifindex, scan_done, sc)) { scan_request_free(sr); return false; } done: l_queue_push_tail(sc->requests, sr); return true; } void scan_sched_start(struct l_genl_family *nl80211, uint32_t ifindex, uint32_t scan_interval, scan_func_t callback, void *user_data) { struct l_genl_msg *msg; scan_interval *= 1000; /* in kernel the interval is in msecs */ msg = l_genl_msg_new_sized(NL80211_CMD_START_SCHED_SCAN, 32); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &ifindex); l_genl_msg_append_attr(msg, NL80211_ATTR_SCHED_SCAN_INTERVAL, 4, &scan_interval); l_genl_msg_append_attr(msg, NL80211_ATTR_SOCKET_OWNER, 0, NULL); if (!l_genl_family_send(nl80211, msg, callback, user_data, NULL)) l_error("Starting scheduled scan failed"); } static void scan_periodic_done(struct l_genl_msg *msg, void *user_data) { struct scan_context *sc = user_data; int err; l_debug(""); sc->sp.rearm = true; err = l_genl_msg_get_error(msg); if (err < 0) { /* Scan already in progress */ if (err != -EBUSY) l_warn("Periodic scan could not be triggered: %s (%d)", strerror(-err), -err); sc->sp.retry = true; return; } sc->state = SCAN_STATE_PASSIVE; l_debug("Periodic scan triggered for ifindex: %u", sc->ifindex); } void scan_periodic_start(uint32_t ifindex, scan_notify_func_t func, void *userdata) { struct scan_context *sc; sc = l_queue_find(scan_contexts, scan_context_match, L_UINT_TO_PTR(ifindex)); if (!sc) { l_error("scan_periodic_start called without scan_ifindex_add"); return; } if (sc->sp.interval) return; l_debug("Starting periodic scan for ifindex: %u", ifindex); sc->sp.interval = SCAN_INIT_INTERVAL; sc->sp.callback = func; sc->sp.userdata = userdata; sc->sp.retry = false; sc->sp.rearm = false; __scan_passive_start(nl80211, ifindex, scan_periodic_done, sc); } bool scan_periodic_stop(uint32_t ifindex) { struct scan_context *sc; sc = l_queue_find(scan_contexts, scan_context_match, L_UINT_TO_PTR(ifindex)); if (!sc) return false; if (!sc->sp.interval) return false; l_debug("Stopping periodic scan for ifindex: %u", ifindex); l_timeout_remove(sc->sp.timeout); sc->sp.interval = 0; sc->sp.callback = NULL; sc->sp.userdata = NULL; sc->sp.rearm = false; sc->sp.retry = false; return true; } static void scan_periodic_timeout(struct l_timeout *timeout, void *user_data) { struct scan_context *sc = user_data; l_debug("scan_periodic_timeout: %u", sc->ifindex); sc->sp.interval *= 2; __scan_passive_start(nl80211, sc->ifindex, scan_periodic_done, sc); } static void scan_periodic_rearm(struct scan_context *sc) { l_debug("Arming periodic scan timer: %u", sc->sp.interval); if (sc->sp.timeout) l_timeout_modify(sc->sp.timeout, sc->sp.interval); else sc->sp.timeout = l_timeout_create(sc->sp.interval, scan_periodic_timeout, sc, NULL); sc->sp.rearm = false; } enum scan_ssid_security scan_get_ssid_security( enum ie_bss_capability bss_capability, const struct ie_rsn_info *info) { if (info && (info->akm_suites & IE_RSN_AKM_SUITE_PSK || info->akm_suites & IE_RSN_AKM_SUITE_PSK_SHA256 || info->akm_suites & IE_RSN_AKM_SUITE_FT_USING_PSK || info->akm_suites & IE_RSN_AKM_SUITE_SAE_SHA256 || info->akm_suites & IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256)) return SCAN_SSID_SECURITY_PSK; if (info && (info->akm_suites & IE_RSN_AKM_SUITE_8021X || info->akm_suites & IE_RSN_AKM_SUITE_8021X_SHA256 || info->akm_suites & IE_RSN_AKM_SUITE_FT_OVER_8021X)) return SCAN_SSID_SECURITY_8021X; if (bss_capability & IE_BSS_CAP_PRIVACY) return SCAN_SSID_SECURITY_WEP; return SCAN_SSID_SECURITY_NONE; } static bool scan_parse_bss_information_elements(struct scan_bss *bss, const void *data, uint16_t len) { struct ie_tlv_iter iter; bool have_ssid = false; ie_tlv_iter_init(&iter, data, len); while (ie_tlv_iter_next(&iter)) { uint8_t tag = ie_tlv_iter_get_tag(&iter); switch (tag) { case IE_TYPE_SSID: if (iter.len > 32) return false; memcpy(bss->ssid, iter.data, iter.len); bss->ssid_len = iter.len; have_ssid = true; break; case IE_TYPE_SUPPORTED_RATES: case IE_TYPE_EXTENDED_SUPPORTED_RATES: if (ie_parse_supported_rates(&iter, &bss->supported_rates) < 0) l_warn("Unable to parse [Extended] " "Supported Rates IE"); break; case IE_TYPE_RSN: if (!bss->rsne) bss->rsne = l_memdup(iter.data - 2, iter.len + 2); break; case IE_TYPE_BSS_LOAD: if (ie_parse_bss_load(&iter, NULL, &bss->utilization, NULL) < 0) l_warn("Unable to parse BSS Load IE"); else l_debug("Load: %u/255", bss->utilization); break; case IE_TYPE_VENDOR_SPECIFIC: /* Interested only in WPA IE from Vendor data */ if (!bss->wpa && is_ie_wpa_ie(iter.data, iter.len)) bss->wpa = l_memdup(iter.data - 2, iter.len + 2); break; } } return have_ssid; } static struct scan_bss *scan_parse_attr_bss(struct l_genl_attr *attr) { uint16_t type, len; const void *data; struct scan_bss *bss; bss = l_new(struct scan_bss, 1); bss->utilization = 127; while (l_genl_attr_next(attr, &type, &len, &data)) { switch (type) { case NL80211_BSS_BSSID: if (len != sizeof(bss->addr)) goto fail; memcpy(bss->addr, data, len); break; case NL80211_BSS_CAPABILITY: if (len != sizeof(uint16_t)) goto fail; bss->capability = *((uint16_t *) data); break; case NL80211_BSS_FREQUENCY: if (len != sizeof(uint32_t)) goto fail; bss->frequency = *((uint32_t *) data); break; case NL80211_BSS_SIGNAL_MBM: if (len != sizeof(int32_t)) goto fail; bss->signal_strength = *((int32_t *) data); break; case NL80211_BSS_INFORMATION_ELEMENTS: if (!scan_parse_bss_information_elements(bss, data, len)) goto fail; break; } } return bss; fail: scan_bss_free(bss); return NULL; } static struct scan_freq_set *scan_parse_attr_scan_frequencies( struct l_genl_attr *attr) { uint16_t type, len; const void *data; struct scan_freq_set *set; set = scan_freq_set_new(); while (l_genl_attr_next(attr, &type, &len, &data)) { uint32_t freq; if (len != sizeof(uint32_t)) continue; freq = *((uint32_t *) data); scan_freq_set_add(set, freq); } return set; } static struct scan_bss *scan_parse_result(struct l_genl_msg *msg, uint32_t *out_ifindex, uint64_t *out_wdev) { struct l_genl_attr attr, nested; uint16_t type, len; const void *data; uint32_t ifindex; uint64_t wdev; struct scan_bss *bss = NULL; if (!l_genl_attr_init(&attr, msg)) return NULL; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_IFINDEX: if (len != sizeof(uint32_t)) return NULL; ifindex = *((uint32_t *) data); break; case NL80211_ATTR_WDEV: if (len != sizeof(uint64_t)) return NULL; wdev = *((uint64_t *) data); break; case NL80211_ATTR_BSS: if (!l_genl_attr_recurse(&attr, &nested)) return NULL; bss = scan_parse_attr_bss(&nested); break; } } if (!bss) return NULL; if (out_ifindex) *out_ifindex = ifindex; if (out_wdev) *out_wdev = wdev; return bss; } static void scan_bss_compute_rank(struct scan_bss *bss) { static const double RANK_RSNE_FACTOR = 1.2; static const double RANK_WPA_FACTOR = 1.0; static const double RANK_OPEN_FACTOR = 0.5; static const double RANK_NO_PRIVACY_FACTOR = 0.5; static const double RANK_5G_FACTOR = 1.1; static const double RANK_HIGH_UTILIZATION_FACTOR = 0.8; static const double RANK_LOW_UTILIZATION_FACTOR = 1.2; static const double RANK_MIN_SUPPORTED_RATE_FACTOR = 0.8; static const double RANK_MAX_SUPPORTED_RATE_FACTOR = 1.1; double rank; uint32_t irank; /* * Signal strength is in mBm (100 * dBm) and is negative. * WiFi range is -0 to -100 dBm */ /* Heavily slanted towards signal strength */ rank = 10000 + bss->signal_strength; /* * Prefer RSNE first, WPA second. Open networks are much less * desirable. */ if (bss->rsne) rank *= RANK_RSNE_FACTOR; else if (bss->wpa) rank *= RANK_WPA_FACTOR; else rank *= RANK_OPEN_FACTOR; /* We prefer networks with CAP PRIVACY */ if (!(bss->capability & IE_BSS_CAP_PRIVACY)) rank *= RANK_NO_PRIVACY_FACTOR; /* Prefer 5G networks over 2.4G */ if (bss->frequency > 4000) rank *= RANK_5G_FACTOR; /* Rank loaded APs lower and lighly loaded APs higher */ if (bss->utilization >= 192) rank *= RANK_HIGH_UTILIZATION_FACTOR; else if (bss->utilization <= 63) rank *= RANK_LOW_UTILIZATION_FACTOR; if (bss->supported_rates) { uint8_t max = l_uintset_find_max(bss->supported_rates); double factor = RANK_MAX_SUPPORTED_RATE_FACTOR - RANK_MIN_SUPPORTED_RATE_FACTOR; /* * Maximum rate is 54 Mbps, see DATA_RATE in 802.11-2012, * Section 6.5.5.2 */ factor = factor * max / 108 + RANK_MIN_SUPPORTED_RATE_FACTOR; bss->rank *= factor; } irank = rank; if (irank > USHRT_MAX) bss->rank = USHRT_MAX; else bss->rank = irank; } void scan_bss_free(struct scan_bss *bss) { l_uintset_free(bss->supported_rates); l_free(bss->rsne); l_free(bss->wpa); l_free(bss); } void bss_get_supported_ciphers(struct scan_bss *bss, uint16_t *pairwise_ciphers, uint16_t *group_ciphers) { struct ie_rsn_info ie; *pairwise_ciphers = 0; *group_ciphers = 0; if (bss->rsne) { int res = ie_parse_rsne_from_data(bss->rsne, bss->rsne[1] + 2, &ie); if (res < 0) { l_debug("Cannot parse RSN field (%d, %s)", res, strerror(-res)); return; } } else if (bss->wpa) { int res = ie_parse_wpa_from_data(bss->wpa, bss->wpa[1] + 2, &ie); if (res < 0) { l_debug("Cannot parse WPA IE (%d, %s)", res, strerror(-res)); return; } } else return; *pairwise_ciphers = ie.pairwise_ciphers; *group_ciphers = ie.group_cipher; } int scan_bss_rank_compare(const void *a, const void *b, void *user_data) { const struct scan_bss *new_bss = a, *bss = b; return bss->rank - new_bss->rank; } static void get_scan_callback(struct l_genl_msg *msg, void *user_data) { struct scan_results *results = user_data; struct scan_bss *bss; uint32_t ifindex; l_debug("get_scan_callback"); if (!results->bss_list) results->bss_list = l_queue_new(); bss = scan_parse_result(msg, &ifindex, NULL); if (!bss) return; if (ifindex != results->ifindex) { l_warn("ifindex mismatch in get_scan_callback"); scan_bss_free(bss); return; } scan_bss_compute_rank(bss); l_queue_insert(results->bss_list, bss, scan_bss_rank_compare, NULL); } static void get_scan_done(void *user) { struct scan_results *results = user; bool new_owner = false; l_debug("get_scan_done"); if (!results->bss_list) goto done; l_debug("results->callback: %p", results->callback); if (results->callback) new_owner = results->callback(results->wiphy, results->ifindex, results->bss_list, results->userdata); if (!new_owner) l_queue_destroy(results->bss_list, (l_queue_destroy_func_t) scan_bss_free); done: if (results->freqs) scan_freq_set_free(results->freqs); l_free(results); } static void scan_parse_new_scan_results(struct l_genl_msg *msg, struct scan_results *results) { struct l_genl_attr attr, nested; uint16_t type, len; const void *data; if (!l_genl_attr_init(&attr, msg)) return; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_SCAN_FREQUENCIES: if (!l_genl_attr_recurse(&attr, &nested)) { l_warn("Failed to parse ATTR_SCAN_FREQUENCIES"); break; } results->freqs = scan_parse_attr_scan_frequencies(&nested); break; } } } static void scan_notify(struct l_genl_msg *msg, void *user_data) { struct l_genl_attr attr; uint16_t type, len; const void *data; uint8_t cmd; uint32_t uninitialized_var(attr_ifindex); bool have_ifindex; uint32_t uninitialized_var(attr_wiphy); bool have_wiphy; struct scan_context *sc; bool active_scan = false; cmd = l_genl_msg_get_command(msg); l_debug("Scan notification %u", cmd); if (!l_genl_attr_init(&attr, msg)) return; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_WIPHY: if (len != sizeof(uint32_t)) { l_warn("Invalid wiphy attribute"); return; } have_wiphy = true; attr_wiphy = *((uint32_t *) data); break; case NL80211_ATTR_IFINDEX: if (len != sizeof(uint32_t)) { l_warn("Invalid interface index attribute"); return; } have_ifindex = true; attr_ifindex = *((uint32_t *) data); break; case NL80211_ATTR_SCAN_SSIDS: active_scan = true; break; } } if (!have_wiphy) { l_warn("Scan results do not contain wiphy attribute"); return; } if (!have_ifindex) { l_warn("Scan results do not contain ifindex attribute"); return; } sc = l_queue_find(scan_contexts, scan_context_match, L_UINT_TO_PTR(attr_ifindex)); if (!sc) return; switch (cmd) { case NL80211_CMD_NEW_SCAN_RESULTS: case NL80211_CMD_SCHED_SCAN_RESULTS: { struct l_genl_msg *scan_msg; struct scan_results *results; struct scan_request *sr; results = l_new(struct scan_results, 1); results->wiphy = attr_wiphy; results->ifindex = attr_ifindex; scan_parse_new_scan_results(msg, results); sr = l_queue_peek_head(sc->requests); if (sr && sr->triggered) { results->callback = sr->callback; results->userdata = sr->userdata; results->destroy = sr->destroy; scan_request_free(sr); l_queue_pop_head(sc->requests); } else if (sc->state == SCAN_STATE_PASSIVE && sc->sp.interval != 0) { /* * Can't use active_scan here, since * NEW_SCAN_RESULTS always contains this attribute */ results->callback = sc->sp.callback; results->userdata = sc->sp.userdata; } scan_msg = l_genl_msg_new_sized(NL80211_CMD_GET_SCAN, 8); l_genl_msg_append_attr(scan_msg, NL80211_ATTR_IFINDEX, 4, &attr_ifindex); l_genl_family_dump(nl80211, scan_msg, get_scan_callback, results, get_scan_done); sc->state = SCAN_STATE_NOT_RUNNING; if (!l_queue_isempty(sc->requests)) { l_idle_oneshot(start_next_scan_request, L_UINT_TO_PTR(sc->ifindex), NULL); } else if (sc->sp.retry) { __scan_passive_start(nl80211, sc->ifindex, scan_periodic_done, sc); sc->sp.retry = false; } else if (sc->sp.rearm) { scan_periodic_rearm(sc); } break; } case NL80211_CMD_TRIGGER_SCAN: if (active_scan) sc->state = SCAN_STATE_ACTIVE; else sc->state = SCAN_STATE_PASSIVE; break; } } uint8_t scan_freq_to_channel(uint32_t freq, enum scan_band *out_band) { uint32_t channel = 0; if (freq >= 2412 && freq <= 2484) { if (freq == 2484) channel = 14; else { channel = freq - 2407; if (channel % 5) return 0; channel /= 5; } if (out_band) *out_band = SCAN_BAND_2_4_GHZ; return channel; } if (freq >= 5005 && freq < 5900) { if (channel % 5) return 0; channel = (freq - 5000) / 5; if (out_band) *out_band = SCAN_BAND_5_GHZ; return channel; } if (freq >= 4905 && freq < 5000) { if (channel % 5) return 0; channel = (freq - 4000) / 5; if (out_band) *out_band = SCAN_BAND_5_GHZ; return channel; } return 0; } struct scan_freq_set { uint16_t channels_2ghz; struct l_uintset *channels_5ghz; }; struct scan_freq_set *scan_freq_set_new(void) { struct scan_freq_set *ret = l_new(struct scan_freq_set, 1); /* 802.11-2012, 8.4.2.10 hints that 200 is the largest channel number */ ret->channels_5ghz = l_uintset_new_from_range(1, 200); return ret; } void scan_freq_set_free(struct scan_freq_set *freqs) { l_uintset_free(freqs->channels_5ghz); l_free(freqs); } bool scan_freq_set_add(struct scan_freq_set *freqs, uint32_t freq) { enum scan_band band; uint8_t channel; channel = scan_freq_to_channel(freq, &band); if (!channel) return false; switch (band) { case SCAN_BAND_2_4_GHZ: freqs->channels_2ghz |= 1 << (channel - 1); return true; case SCAN_BAND_5_GHZ: return l_uintset_put(freqs->channels_5ghz, channel); } return false; } bool scan_freq_set_contains(struct scan_freq_set *freqs, uint32_t freq) { enum scan_band band; uint8_t channel; channel = scan_freq_to_channel(freq, &band); if (!channel) return false; switch (band) { case SCAN_BAND_2_4_GHZ: return freqs->channels_2ghz & (1 << (channel - 1)); case SCAN_BAND_5_GHZ: return l_uintset_contains(freqs->channels_5ghz, channel); } return false; } bool scan_init(struct l_genl_family *in) { nl80211 = in; scan_id = l_genl_family_register(nl80211, "scan", scan_notify, NULL, NULL); if (!scan_id) { l_error("Registering for scan notification failed"); return false; } scan_contexts = l_queue_new(); return true; } bool scan_exit() { bool r; if (!nl80211) return false; l_queue_destroy(scan_contexts, (l_queue_destroy_func_t) scan_context_free); scan_contexts = NULL; r = l_genl_family_unregister(nl80211, scan_id); scan_id = 0; nl80211 = 0; return r; }