| /* |
| * |
| * Wireless daemon for Linux |
| * |
| * Copyright (C) 2015-2019 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 <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <time.h> |
| #include <sys/socket.h> |
| #include <limits.h> |
| #include <linux/if.h> |
| #include <linux/if_ether.h> |
| #include <ell/ell.h> |
| |
| #include "ell/useful.h" |
| #include "linux/nl80211.h" |
| #include "src/iwd.h" |
| #include "src/module.h" |
| #include "src/wiphy.h" |
| #include "src/ie.h" |
| #include "src/common.h" |
| #include "src/network.h" |
| #include "src/knownnetworks.h" |
| #include "src/nl80211cmd.h" |
| #include "src/nl80211util.h" |
| #include "src/util.h" |
| #include "src/p2putil.h" |
| #include "src/mpdu.h" |
| #include "src/scan.h" |
| |
| /* User configurable options */ |
| static double RANK_5G_FACTOR; |
| static uint32_t SCAN_MAX_INTERVAL; |
| static uint32_t SCAN_INIT_INTERVAL; |
| |
| static struct l_queue *scan_contexts; |
| |
| static struct l_genl_family *nl80211; |
| |
| struct scan_context; |
| |
| struct scan_periodic { |
| struct l_timeout *timeout; |
| uint16_t interval; |
| scan_trigger_func_t trigger; |
| scan_notify_func_t callback; |
| void *userdata; |
| bool retry:1; |
| uint32_t id; |
| bool needs_active_scan:1; |
| }; |
| |
| struct scan_request { |
| struct scan_context *sc; |
| scan_trigger_func_t trigger; |
| scan_notify_func_t callback; |
| void *userdata; |
| scan_destroy_func_t destroy; |
| bool passive:1; /* Active or Passive scan? */ |
| struct l_queue *cmds; |
| /* The time the current scan was started. Reported in TRIGGER_SCAN */ |
| uint64_t start_time_tsf; |
| struct wiphy_radio_work_item work; |
| }; |
| |
| struct scan_context { |
| uint64_t wdev_id; |
| /* |
| * Tells us whether a scan, our own or external, is running. |
| * Set when scan gets triggered, cleared when scan done and |
| * before actual results are queried. |
| */ |
| enum scan_state state; |
| struct scan_periodic sp; |
| struct l_queue *requests; |
| /* Non-zero if SCAN_TRIGGER is still running */ |
| unsigned int start_cmd_id; |
| /* Non-zero if GET_SCAN is still running */ |
| unsigned int get_scan_cmd_id; |
| /* |
| * Special request used for getting scan results after the firmware |
| * roamed automatically. |
| */ |
| unsigned int get_fw_scan_cmd_id; |
| /* |
| * Whether the top request in the queue has triggered the current |
| * scan. May be set and cleared multiple times during a single |
| * request. May be false when the current request is waiting due |
| * to an EBUSY or an external scan (sr->cmds non-empty), when |
| * start_cmd_id is non-zero and for a brief moment when GET_SCAN |
| * is running. |
| */ |
| bool triggered:1; |
| /* Whether any commands from current request's queue have started */ |
| bool started:1; |
| bool work_started:1; |
| struct wiphy *wiphy; |
| }; |
| |
| struct scan_results { |
| struct scan_context *sc; |
| struct l_queue *bss_list; |
| struct scan_freq_set *freqs; |
| uint64_t time_stamp; |
| struct scan_request *sr; |
| }; |
| |
| static bool start_next_scan_request(struct wiphy_radio_work_item *item); |
| static void scan_periodic_rearm(struct scan_context *sc); |
| |
| static bool scan_context_match(const void *a, const void *b) |
| { |
| const struct scan_context *sc = a; |
| const uint64_t *wdev_id = b; |
| |
| return sc->wdev_id == *wdev_id; |
| } |
| |
| static bool scan_request_match(const void *a, const void *b) |
| { |
| const struct scan_request *sr = a; |
| uint32_t id = L_PTR_TO_UINT(b); |
| |
| return sr->work.id == id; |
| } |
| |
| static void scan_request_free(struct wiphy_radio_work_item *item) |
| { |
| struct scan_request *sr = l_container_of(item, struct scan_request, |
| work); |
| |
| if (sr->destroy) |
| sr->destroy(sr->userdata); |
| |
| l_queue_destroy(sr->cmds, (l_queue_destroy_func_t) l_genl_msg_unref); |
| |
| l_free(sr); |
| } |
| |
| static void scan_request_failed(struct scan_context *sc, |
| struct scan_request *sr, int err) |
| { |
| l_queue_remove(sc->requests, sr); |
| |
| if (sr->trigger) |
| sr->trigger(err, sr->userdata); |
| else if (sr->callback) |
| sr->callback(err, NULL, NULL, sr->userdata); |
| |
| wiphy_radio_work_done(sc->wiphy, sr->work.id); |
| } |
| |
| static struct scan_context *scan_context_new(uint64_t wdev_id) |
| { |
| struct wiphy *wiphy = wiphy_find_by_wdev(wdev_id); |
| struct scan_context *sc; |
| |
| if (!wiphy) |
| return NULL; |
| |
| sc = l_new(struct scan_context, 1); |
| |
| sc->wdev_id = wdev_id; |
| sc->wiphy = wiphy; |
| sc->state = SCAN_STATE_NOT_RUNNING; |
| sc->requests = l_queue_new(); |
| |
| return sc; |
| } |
| |
| static void scan_request_cancel(void *data) |
| { |
| struct scan_request *sr = data; |
| |
| wiphy_radio_work_done(sr->sc->wiphy, sr->work.id); |
| } |
| |
| static void scan_context_free(struct scan_context *sc) |
| { |
| l_debug("sc: %p", sc); |
| |
| l_queue_destroy(sc->requests, scan_request_cancel); |
| |
| if (sc->sp.timeout) |
| l_timeout_remove(sc->sp.timeout); |
| |
| if (sc->start_cmd_id && nl80211) |
| l_genl_family_cancel(nl80211, sc->start_cmd_id); |
| |
| if (sc->get_scan_cmd_id && nl80211) |
| l_genl_family_cancel(nl80211, sc->get_scan_cmd_id); |
| |
| if (sc->get_fw_scan_cmd_id && nl80211) |
| l_genl_family_cancel(nl80211, sc->get_fw_scan_cmd_id); |
| |
| l_free(sc); |
| } |
| |
| static void scan_request_triggered(struct l_genl_msg *msg, void *userdata) |
| { |
| struct scan_context *sc = userdata; |
| struct scan_request *sr = l_queue_peek_head(sc->requests); |
| int err; |
| |
| sc->start_cmd_id = 0; |
| |
| err = l_genl_msg_get_error(msg); |
| if (err < 0) { |
| /* Scan in progress, assume another scan is running */ |
| if (err == -EBUSY) { |
| sc->state = SCAN_STATE_PASSIVE; |
| return; |
| } |
| |
| l_queue_remove(sc->requests, sr); |
| |
| scan_request_failed(sc, sr, err); |
| |
| l_error("Received error during CMD_TRIGGER_SCAN: %s (%d)", |
| strerror(-err), -err); |
| |
| return; |
| } |
| |
| sc->state = sr->passive ? SCAN_STATE_PASSIVE : SCAN_STATE_ACTIVE; |
| l_debug("%s scan triggered for wdev %" PRIx64, |
| sr->passive ? "Passive" : "Active", sc->wdev_id); |
| |
| sc->triggered = true; |
| sc->started = true; |
| l_genl_msg_unref(l_queue_pop_head(sr->cmds)); |
| |
| if (sr->trigger) { |
| sr->trigger(0, sr->userdata); |
| |
| /* |
| * Reset callback for the consequent scan triggerings of the |
| * multi-segmented scans. |
| */ |
| sr->trigger = NULL; |
| } |
| } |
| |
| struct scan_freq_append_data { |
| struct l_genl_msg *msg; |
| int count; |
| }; |
| |
| static void scan_freq_append(uint32_t freq, void *user_data) |
| { |
| struct scan_freq_append_data *data = user_data; |
| |
| l_genl_msg_append_attr(data->msg, data->count++, 4, &freq); |
| } |
| |
| static void scan_build_attr_scan_frequencies(struct l_genl_msg *msg, |
| struct scan_freq_set *freqs) |
| { |
| struct scan_freq_append_data append_data = { msg, 0 }; |
| |
| l_genl_msg_enter_nested(msg, NL80211_ATTR_SCAN_FREQUENCIES); |
| |
| scan_freq_set_foreach(freqs, scan_freq_append, &append_data); |
| |
| l_genl_msg_leave_nested(msg); |
| } |
| |
| static void scan_build_attr_ie(struct l_genl_msg *msg, |
| struct scan_context *sc, |
| const struct scan_parameters *params) |
| { |
| struct iovec iov[3]; |
| unsigned int iov_elems = 0; |
| const uint8_t *ext_capa; |
| uint8_t interworking[3]; |
| |
| ext_capa = wiphy_get_extended_capabilities(sc->wiphy, |
| NL80211_IFTYPE_STATION); |
| /* |
| * If adding IE's here ensure that ordering is not broken for |
| * probe requests (IEEE Std 802.11-2016 Table 9-33). |
| */ |
| /* Order 9 - Extended Capabilities */ |
| iov[iov_elems].iov_base = (void *) ext_capa; |
| iov[iov_elems].iov_len = ext_capa[1] + 2; |
| iov_elems++; |
| |
| if (test_bit(&ext_capa[2 + 3], 7)) { |
| /* Order 12 - Interworking */ |
| interworking[0] = IE_TYPE_INTERWORKING; |
| interworking[1] = 1; |
| /* Private network, INet=0,ASRA=0,ESR=0,UESA=0 */ |
| interworking[2] = 0; |
| |
| iov[iov_elems].iov_base = interworking; |
| iov[iov_elems].iov_len = 3; |
| iov_elems++; |
| } |
| |
| /* Order Last (assuming WSC vendor specific) */ |
| if (params->extra_ie && params->extra_ie_size) { |
| iov[iov_elems].iov_base = (void *) params->extra_ie; |
| iov[iov_elems].iov_len = params->extra_ie_size; |
| iov_elems++; |
| } |
| |
| l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, iov_elems); |
| } |
| |
| static bool scan_mac_address_randomization_is_disabled(void) |
| { |
| const struct l_settings *config = iwd_get_config(); |
| bool disabled; |
| |
| if (!l_settings_get_bool(config, "Scan", |
| "DisableMacAddressRandomization", |
| &disabled)) |
| return false; |
| |
| return disabled; |
| } |
| |
| static struct l_genl_msg *scan_build_cmd(struct scan_context *sc, |
| bool ignore_flush_flag, bool is_passive, |
| const struct scan_parameters *params) |
| { |
| struct l_genl_msg *msg; |
| uint32_t flags = 0; |
| |
| msg = l_genl_msg_new(NL80211_CMD_TRIGGER_SCAN); |
| |
| l_genl_msg_append_attr(msg, NL80211_ATTR_WDEV, 8, &sc->wdev_id); |
| |
| if (wiphy_get_max_scan_ie_len(sc->wiphy)) |
| scan_build_attr_ie(msg, sc, params); |
| |
| if (params->freqs) |
| scan_build_attr_scan_frequencies(msg, params->freqs); |
| |
| if (params->flush && !ignore_flush_flag) |
| flags |= NL80211_SCAN_FLAG_FLUSH; |
| |
| if (!is_passive && params->randomize_mac_addr_hint && |
| wiphy_can_randomize_mac_addr(sc->wiphy) && |
| !scan_mac_address_randomization_is_disabled()) |
| /* |
| * Randomizing 46 bits (locally administered 1 and multicast 0 |
| * is assumed). |
| */ |
| flags |= NL80211_SCAN_FLAG_RANDOM_ADDR; |
| |
| if (!is_passive && params->source_mac && |
| wiphy_can_randomize_mac_addr(sc->wiphy)) { |
| static const uint8_t mask[6] = /* No random bits */ |
| { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; |
| |
| flags |= NL80211_SCAN_FLAG_RANDOM_ADDR; |
| l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, 6, |
| params->source_mac); |
| l_genl_msg_append_attr(msg, NL80211_ATTR_MAC_MASK, 6, |
| mask); |
| } |
| |
| if (!is_passive && wiphy_has_ext_feature(sc->wiphy, |
| NL80211_EXT_FEATURE_SCAN_RANDOM_SN)) |
| flags |= NL80211_SCAN_FLAG_RANDOM_SN; |
| |
| if (flags) |
| l_genl_msg_append_attr(msg, NL80211_ATTR_SCAN_FLAGS, 4, &flags); |
| |
| if (params->no_cck_rates) { |
| static const uint8_t b_rates[] = { 2, 4, 11, 22 }; |
| uint8_t *scan_rates; |
| const uint8_t *supported; |
| unsigned int num_supported; |
| unsigned int count; |
| unsigned int i; |
| |
| l_genl_msg_append_attr(msg, NL80211_ATTR_TX_NO_CCK_RATE, 0, |
| NULL); |
| |
| /* |
| * Assume if we're sending the probe requests at OFDM bit |
| * rates we don't want to advertise support for 802.11b rates. |
| */ |
| if (L_WARN_ON(!(supported = wiphy_get_supported_rates(sc->wiphy, |
| NL80211_BAND_2GHZ, |
| &num_supported)))) |
| goto done; |
| |
| scan_rates = l_malloc(num_supported); |
| |
| for (count = 0, i = 0; i < num_supported; i++) |
| if (!memchr(b_rates, supported[i], |
| L_ARRAY_SIZE(b_rates))) |
| scan_rates[count++] = supported[i]; |
| |
| if (L_WARN_ON(!count)) { |
| l_free(scan_rates); |
| goto done; |
| } |
| |
| l_genl_msg_enter_nested(msg, NL80211_ATTR_SCAN_SUPP_RATES); |
| l_genl_msg_append_attr(msg, NL80211_BAND_2GHZ, |
| count, scan_rates); |
| l_genl_msg_leave_nested(msg); |
| l_free(scan_rates); |
| } |
| |
| if (wiphy_has_ext_feature(sc->wiphy, |
| NL80211_EXT_FEATURE_SET_SCAN_DWELL)) { |
| if (params->duration) |
| l_genl_msg_append_attr(msg, |
| NL80211_ATTR_MEASUREMENT_DURATION, |
| 2, ¶ms->duration); |
| |
| if (params->duration_mandatory) |
| l_genl_msg_append_attr(msg, |
| NL80211_ATTR_MEASUREMENT_DURATION_MANDATORY, |
| 0, NULL); |
| } |
| |
| done: |
| return msg; |
| } |
| |
| struct l_genl_msg *scan_build_trigger_scan_bss(uint32_t ifindex, |
| struct wiphy *wiphy, |
| uint32_t frequency, |
| const uint8_t *ssid, |
| uint32_t ssid_len) |
| { |
| struct l_genl_msg *msg = l_genl_msg_new(NL80211_CMD_TRIGGER_SCAN); |
| uint32_t flags = 0; |
| |
| l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &ifindex); |
| |
| l_genl_msg_enter_nested(msg, NL80211_ATTR_SCAN_FREQUENCIES); |
| l_genl_msg_append_attr(msg, 0, 4, &frequency); |
| l_genl_msg_leave_nested(msg); |
| |
| if (wiphy_has_ext_feature(wiphy, NL80211_EXT_FEATURE_SCAN_RANDOM_SN)) |
| flags |= NL80211_SCAN_FLAG_RANDOM_SN; |
| |
| if (flags) |
| l_genl_msg_append_attr(msg, NL80211_ATTR_SCAN_FLAGS, 4, &flags); |
| |
| /* direct probe request scan */ |
| l_genl_msg_enter_nested(msg, NL80211_ATTR_SCAN_SSIDS); |
| l_genl_msg_append_attr(msg, 0, ssid_len, ssid); |
| l_genl_msg_leave_nested(msg); |
| |
| return msg; |
| } |
| |
| struct scan_cmds_add_data { |
| struct scan_context *sc; |
| const struct scan_parameters *params; |
| struct l_queue *cmds; |
| struct l_genl_msg **cmd; |
| uint8_t max_ssids_per_scan; |
| uint8_t num_ssids_can_append; |
| }; |
| |
| static bool scan_cmds_add_hidden(const struct network_info *network, |
| void *user_data) |
| { |
| struct scan_cmds_add_data *data = user_data; |
| |
| if (!network->is_hidden) |
| return true; |
| |
| l_genl_msg_append_attr(*data->cmd, NL80211_ATTR_SSID, |
| strlen(network->ssid), network->ssid); |
| data->num_ssids_can_append--; |
| |
| if (!data->num_ssids_can_append) { |
| l_genl_msg_leave_nested(*data->cmd); |
| l_queue_push_tail(data->cmds, *data->cmd); |
| |
| data->num_ssids_can_append = data->max_ssids_per_scan; |
| |
| /* |
| * Create a consecutive scan trigger in the batch of scans. |
| * The 'flush' flag is ignored, this allows to get the results |
| * of all scans in the batch after the last scan is finished. |
| */ |
| *data->cmd = scan_build_cmd(data->sc, true, false, |
| data->params); |
| l_genl_msg_enter_nested(*data->cmd, NL80211_ATTR_SCAN_SSIDS); |
| } |
| |
| return true; |
| } |
| |
| static void scan_cmds_add(struct l_queue *cmds, struct scan_context *sc, |
| bool passive, |
| const struct scan_parameters *params) |
| { |
| struct l_genl_msg *cmd; |
| struct scan_cmds_add_data data = { |
| sc, |
| params, |
| cmds, |
| &cmd, |
| wiphy_get_max_num_ssids_per_scan(sc->wiphy), |
| }; |
| |
| cmd = scan_build_cmd(sc, false, passive, params); |
| |
| if (passive) { |
| /* passive scan */ |
| l_queue_push_tail(cmds, cmd); |
| return; |
| } |
| |
| l_genl_msg_enter_nested(cmd, NL80211_ATTR_SCAN_SSIDS); |
| |
| if (params->ssid) { |
| /* direct probe request scan */ |
| l_genl_msg_append_attr(cmd, NL80211_ATTR_SSID, |
| strlen(params->ssid), params->ssid); |
| l_genl_msg_leave_nested(cmd); |
| |
| l_queue_push_tail(cmds, cmd); |
| return; |
| } |
| |
| data.num_ssids_can_append = data.max_ssids_per_scan; |
| known_networks_foreach(scan_cmds_add_hidden, &data); |
| |
| l_genl_msg_append_attr(cmd, NL80211_ATTR_SSID, 0, NULL); |
| l_genl_msg_leave_nested(cmd); |
| l_queue_push_tail(cmds, cmd); |
| } |
| |
| static int scan_request_send_trigger(struct scan_context *sc, |
| struct scan_request *sr) |
| { |
| struct l_genl_msg *cmd = l_queue_peek_head(sr->cmds); |
| if (!cmd) |
| return -ENOMSG; |
| |
| sc->start_cmd_id = l_genl_family_send(nl80211, cmd, |
| scan_request_triggered, sc, |
| NULL); |
| if (sc->start_cmd_id) { |
| l_genl_msg_ref(cmd); |
| |
| return 0; |
| } |
| |
| l_error("Scan request: failed to trigger scan."); |
| |
| return -EIO; |
| } |
| |
| static const struct wiphy_radio_work_item_ops work_ops = { |
| .do_work = start_next_scan_request, |
| .destroy = scan_request_free, |
| }; |
| |
| static uint32_t scan_common(uint64_t wdev_id, bool passive, |
| const struct scan_parameters *params, |
| 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, &wdev_id); |
| |
| if (!sc) |
| return 0; |
| |
| sr = l_new(struct scan_request, 1); |
| sr->sc = sc; |
| sr->trigger = trigger; |
| sr->callback = notify; |
| sr->userdata = userdata; |
| sr->destroy = destroy; |
| sr->passive = passive; |
| sr->cmds = l_queue_new(); |
| |
| scan_cmds_add(sr->cmds, sc, passive, params); |
| |
| l_queue_push_tail(sc->requests, sr); |
| |
| return wiphy_radio_work_insert(sc->wiphy, &sr->work, 2, &work_ops); |
| } |
| |
| uint32_t scan_passive(uint64_t wdev_id, struct scan_freq_set *freqs, |
| scan_trigger_func_t trigger, scan_notify_func_t notify, |
| void *userdata, scan_destroy_func_t destroy) |
| { |
| struct scan_parameters params = { .freqs = freqs }; |
| |
| return scan_common(wdev_id, true, ¶ms, trigger, notify, |
| userdata, destroy); |
| } |
| |
| uint32_t scan_passive_full(uint64_t wdev_id, |
| const struct scan_parameters *params, |
| scan_trigger_func_t trigger, |
| scan_notify_func_t notify, void *userdata, |
| scan_destroy_func_t destroy) |
| { |
| return scan_common(wdev_id, true, params, trigger, |
| notify, userdata, destroy); |
| } |
| |
| uint32_t scan_active(uint64_t wdev_id, uint8_t *extra_ie, size_t extra_ie_size, |
| scan_trigger_func_t trigger, |
| scan_notify_func_t notify, void *userdata, |
| scan_destroy_func_t destroy) |
| { |
| struct scan_parameters params = {}; |
| |
| params.extra_ie = extra_ie; |
| params.extra_ie_size = extra_ie_size; |
| |
| return scan_common(wdev_id, false, ¶ms, |
| trigger, notify, userdata, destroy); |
| } |
| |
| uint32_t scan_active_full(uint64_t wdev_id, |
| const struct scan_parameters *params, |
| scan_trigger_func_t trigger, scan_notify_func_t notify, |
| void *userdata, scan_destroy_func_t destroy) |
| { |
| return scan_common(wdev_id, false, params, |
| trigger, notify, userdata, destroy); |
| } |
| |
| bool scan_cancel(uint64_t wdev_id, uint32_t id) |
| { |
| struct scan_context *sc; |
| struct scan_request *sr; |
| |
| l_debug("Trying to cancel scan id %u for wdev %" PRIx64, id, wdev_id); |
| |
| sc = l_queue_find(scan_contexts, scan_context_match, &wdev_id); |
| if (!sc) |
| return false; |
| |
| sr = l_queue_find(sc->requests, scan_request_match, L_UINT_TO_PTR(id)); |
| if (!sr) |
| return false; |
| |
| /* If already triggered, just zero out the callback */ |
| if (sr == l_queue_peek_head(sc->requests) && sc->triggered) { |
| l_debug("Scan is at the top of the queue and triggered"); |
| |
| sr->callback = NULL; |
| |
| if (sr->destroy) { |
| sr->destroy(sr->userdata); |
| sr->destroy = NULL; |
| } |
| |
| return true; |
| } |
| |
| /* If we already sent the trigger command, cancel the scan */ |
| if (sr == l_queue_peek_head(sc->requests)) { |
| l_debug("Scan is at the top of the queue, but not triggered"); |
| |
| if (sc->start_cmd_id) |
| l_genl_family_cancel(nl80211, sc->start_cmd_id); |
| |
| if (sc->get_scan_cmd_id) |
| l_genl_family_cancel(nl80211, sc->get_scan_cmd_id); |
| |
| sc->start_cmd_id = 0; |
| l_queue_remove(sc->requests, sr); |
| sc->started = false; |
| sc->work_started = false; |
| } else |
| l_queue_remove(sc->requests, sr); |
| |
| wiphy_radio_work_done(sc->wiphy, sr->work.id); |
| |
| return true; |
| } |
| |
| static void scan_periodic_triggered(int err, void *user_data) |
| { |
| struct scan_context *sc = user_data; |
| |
| if (err) { |
| scan_periodic_rearm(sc); |
| return; |
| } |
| |
| l_debug("Periodic scan triggered for wdev %" PRIx64, sc->wdev_id); |
| |
| if (sc->sp.trigger) |
| sc->sp.trigger(0, sc->sp.userdata); |
| } |
| |
| static bool scan_periodic_notify(int err, struct l_queue *bss_list, |
| const struct scan_freq_set *freqs, |
| void *user_data) |
| { |
| struct scan_context *sc = user_data; |
| |
| scan_periodic_rearm(sc); |
| |
| if (sc->sp.callback) |
| return sc->sp.callback(err, bss_list, freqs, sc->sp.userdata); |
| |
| return false; |
| } |
| |
| static bool scan_periodic_queue(struct scan_context *sc) |
| { |
| if (!l_queue_isempty(sc->requests)) { |
| sc->sp.retry = true; |
| return false; |
| } |
| |
| if (sc->sp.needs_active_scan && known_networks_has_hidden()) { |
| struct scan_parameters params = { |
| .randomize_mac_addr_hint = true |
| }; |
| |
| sc->sp.needs_active_scan = false; |
| |
| sc->sp.id = scan_active_full(sc->wdev_id, ¶ms, |
| scan_periodic_triggered, |
| scan_periodic_notify, sc, NULL); |
| } else |
| sc->sp.id = scan_passive(sc->wdev_id, NULL, |
| scan_periodic_triggered, |
| scan_periodic_notify, sc, NULL); |
| |
| return sc->sp.id != 0; |
| } |
| |
| static bool scan_periodic_is_disabled(void) |
| { |
| const struct l_settings *config = iwd_get_config(); |
| bool disabled; |
| |
| if (!l_settings_get_bool(config, "Scan", "DisablePeriodicScan", |
| &disabled)) |
| return false; |
| |
| return disabled; |
| } |
| |
| void scan_periodic_start(uint64_t wdev_id, scan_trigger_func_t trigger, |
| scan_notify_func_t func, void *userdata) |
| { |
| struct scan_context *sc; |
| |
| if (scan_periodic_is_disabled()) |
| return; |
| |
| sc = l_queue_find(scan_contexts, scan_context_match, &wdev_id); |
| |
| if (!sc) { |
| l_error("scan_periodic_start called without scan_wdev_add"); |
| return; |
| } |
| |
| if (sc->sp.interval) |
| return; |
| |
| l_debug("Starting periodic scan for wdev %" PRIx64, wdev_id); |
| |
| sc->sp.interval = SCAN_INIT_INTERVAL; |
| sc->sp.trigger = trigger; |
| sc->sp.callback = func; |
| sc->sp.userdata = userdata; |
| |
| /* If nothing queued, start the first periodic scan */ |
| scan_periodic_queue(sc); |
| } |
| |
| bool scan_periodic_stop(uint64_t wdev_id) |
| { |
| struct scan_context *sc; |
| |
| sc = l_queue_find(scan_contexts, scan_context_match, &wdev_id); |
| |
| if (!sc) |
| return false; |
| |
| if (!sc->sp.interval) |
| return false; |
| |
| l_debug("Stopping periodic scan for wdev %" PRIx64, wdev_id); |
| |
| if (sc->sp.timeout) |
| l_timeout_remove(sc->sp.timeout); |
| |
| if (sc->sp.id) { |
| scan_cancel(wdev_id, sc->sp.id); |
| sc->sp.id = 0; |
| } |
| |
| sc->sp.interval = 0; |
| sc->sp.trigger = NULL; |
| sc->sp.callback = NULL; |
| sc->sp.userdata = NULL; |
| sc->sp.retry = false; |
| sc->sp.needs_active_scan = false; |
| |
| return true; |
| } |
| |
| uint64_t scan_get_triggered_time(uint64_t wdev_id, uint32_t id) |
| { |
| struct scan_context *sc; |
| struct scan_request *sr; |
| |
| sc = l_queue_find(scan_contexts, scan_context_match, &wdev_id); |
| if (!sc) |
| return 0; |
| |
| if (!sc->triggered) |
| return 0; |
| |
| sr = l_queue_find(sc->requests, scan_request_match, L_UINT_TO_PTR(id)); |
| if (!sr) |
| return 0; |
| |
| return sr->start_time_tsf; |
| } |
| |
| static void scan_periodic_timeout(struct l_timeout *timeout, void *user_data) |
| { |
| struct scan_context *sc = user_data; |
| |
| l_debug("scan_periodic_timeout: %" PRIx64, sc->wdev_id); |
| |
| sc->sp.interval *= 2; |
| if (sc->sp.interval > SCAN_MAX_INTERVAL) |
| sc->sp.interval = SCAN_MAX_INTERVAL; |
| |
| scan_periodic_queue(sc); |
| } |
| |
| static void scan_periodic_timeout_destroy(void *user_data) |
| { |
| struct scan_context *sc = user_data; |
| |
| sc->sp.timeout = NULL; |
| } |
| |
| 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, |
| scan_periodic_timeout_destroy); |
| } |
| |
| static bool start_next_scan_request(struct wiphy_radio_work_item *item) |
| { |
| struct scan_request *sr = l_container_of(item, |
| struct scan_request, work); |
| struct scan_context *sc = sr->sc; |
| |
| sc->work_started = true; |
| |
| if (sc->state != SCAN_STATE_NOT_RUNNING) |
| return false; |
| |
| if (!scan_request_send_trigger(sc, sr)) |
| return false; |
| |
| sc->work_started = false; |
| |
| scan_request_failed(sc, sr, -EIO); |
| |
| if (sc->sp.retry) { |
| sc->sp.retry = false; |
| scan_periodic_queue(sc); |
| } |
| |
| return true; |
| } |
| |
| static bool scan_parse_vendor_specific(struct scan_bss *bss, const void *data, |
| uint16_t len) |
| { |
| if (!bss->wpa && is_ie_wpa_ie(data, len)) |
| bss->wpa = l_memdup(data - 2, len + 2); |
| else if (!bss->osen && is_ie_wfa_ie(data, len, IE_WFA_OI_OSEN)) |
| bss->osen = l_memdup(data - 2, len + 2); |
| else if (is_ie_wfa_ie(data, len, IE_WFA_OI_HS20_INDICATION)) { |
| if (ie_parse_hs20_indication_from_data(data - 2, len + 2, |
| &bss->hs20_version, NULL, NULL) < 0) |
| return false; |
| |
| bss->hs20_capable = true; |
| } else |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * Fully parses the Advertisement Protocol Element. The only thing being looked |
| * for is the ANQP protocol ID, but this could be buried behind several other |
| * advertisement tuples so the entire IE may need to be parsed. |
| */ |
| static bool scan_parse_advertisement_protocol(struct scan_bss *bss, |
| const void *data, uint16_t len) |
| { |
| const uint8_t *ptr = data; |
| |
| l_debug(""); |
| |
| while (len) { |
| /* |
| * TODO: Store query info for GAS response length verification |
| */ |
| uint8_t id = ptr[1]; |
| |
| switch (id) { |
| /* |
| * IEEE 802.11-2016 Section 11.25.3.3.1 |
| * |
| * "A non-AP STA shall not transmit an ANQP request to |
| * an AP for any ANQP-element unless the ANQP |
| * Advertisement Protocol ID is included..." |
| */ |
| case IE_ADVERTISEMENT_ANQP: |
| bss->anqp_capable = true; |
| return true; |
| case IE_ADVERTISEMENT_MIH_SERVICE: |
| case IE_ADVERTISEMENT_MIH_DISCOVERY: |
| case IE_ADVERTISEMENT_EAS: |
| case IE_ADVERTISEMENT_RLQP: |
| len -= 2; |
| ptr += 2; |
| break; |
| case IE_ADVERTISEMENT_VENDOR_SPECIFIC: |
| /* IEEE 802.11-2016 Section 9.4.2.26 */ |
| len -= ptr[3]; |
| ptr += ptr[3]; |
| break; |
| default: |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| 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_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 for " |
| MAC, MAC_STR(bss->addr)); |
| else |
| l_debug("Load: %u/255", bss->utilization); |
| |
| break; |
| case IE_TYPE_VENDOR_SPECIFIC: |
| /* Interested only in WPA/WFA IE from Vendor data */ |
| scan_parse_vendor_specific(bss, iter.data, iter.len); |
| break; |
| case IE_TYPE_MOBILITY_DOMAIN: |
| if (!bss->mde_present && iter.len == 3) { |
| memcpy(bss->mde, iter.data, iter.len); |
| bss->mde_present = true; |
| } |
| |
| break; |
| case IE_TYPE_RM_ENABLED_CAPABILITIES: |
| if (iter.len != 5) |
| break; |
| |
| /* Only interested in Neighbor Reports */ |
| |
| bss->cap_rm_neighbor_report = |
| (iter.data[0] & IE_RM_CAP_NEIGHBOR_REPORT) > 0; |
| break; |
| case IE_TYPE_COUNTRY: |
| if (bss->cc_present || iter.len < 6) |
| break; |
| |
| bss->cc[0] = iter.data[0]; |
| bss->cc[1] = iter.data[1]; |
| bss->cc[2] = iter.data[2]; |
| bss->cc_present = true; |
| |
| break; |
| case IE_TYPE_HT_CAPABILITIES: |
| bss->ht_capable = true; |
| break; |
| case IE_TYPE_VHT_CAPABILITIES: |
| bss->vht_capable = true; |
| break; |
| case IE_TYPE_ADVERTISEMENT_PROTOCOL: |
| if (iter.len < 2) |
| return false; |
| |
| scan_parse_advertisement_protocol(bss, iter.data, |
| iter.len); |
| break; |
| case IE_TYPE_INTERWORKING: |
| /* |
| * No bits indicate if venue/HESSID is included, so the |
| * length is the only way to know. |
| * (IEEE 802.11-2016 - Figure 9-439) |
| */ |
| if (iter.len == 9) |
| memcpy(bss->hessid, iter.data + 3, 6); |
| else if (iter.len == 7) |
| memcpy(bss->hessid, iter.data + 1, 6); |
| break; |
| case IE_TYPE_ROAMING_CONSORTIUM: |
| if (iter.len < 2) |
| return false; |
| |
| bss->rc_ie = l_memdup(iter.data - 2, iter.len + 2); |
| |
| break; |
| } |
| } |
| |
| bss->wsc = ie_tlv_extract_wsc_payload(data, len, &bss->wsc_size); |
| |
| switch (bss->source_frame) { |
| case SCAN_BSS_PROBE_RESP: |
| bss->p2p_probe_resp_info = l_new(struct p2p_probe_resp, 1); |
| |
| if (p2p_parse_probe_resp(data, len, bss->p2p_probe_resp_info) == |
| 0) |
| break; |
| |
| l_free(bss->p2p_probe_resp_info); |
| bss->p2p_probe_resp_info = NULL; |
| break; |
| case SCAN_BSS_PROBE_REQ: |
| bss->p2p_probe_req_info = l_new(struct p2p_probe_req, 1); |
| |
| if (p2p_parse_probe_req(data, len, bss->p2p_probe_req_info) == |
| 0) |
| break; |
| |
| l_free(bss->p2p_probe_req_info); |
| bss->p2p_probe_req_info = NULL; |
| break; |
| case SCAN_BSS_BEACON: |
| { |
| /* |
| * Beacon and Probe Response P2P IE subelement formats are |
| * mutually incompatible and can help us distinguish one frame |
| * subtype from the other if the driver is not exposing enough |
| * information. As a result of trusting the frame contents on |
| * this, no critical code should depend on the |
| * bss->source_frame information being right. |
| */ |
| struct p2p_beacon info; |
| int r; |
| |
| r = p2p_parse_beacon(data, len, &info); |
| if (r == 0) { |
| bss->p2p_beacon_info = l_memdup(&info, sizeof(info)); |
| break; |
| } |
| |
| if (r == -ENOENT) |
| break; |
| |
| bss->p2p_probe_resp_info = l_new(struct p2p_probe_resp, 1); |
| |
| if (p2p_parse_probe_resp(data, len, bss->p2p_probe_resp_info) == |
| 0) { |
| bss->source_frame = SCAN_BSS_PROBE_RESP; |
| break; |
| } |
| |
| l_free(bss->p2p_probe_resp_info); |
| bss->p2p_probe_resp_info = NULL; |
| break; |
| } |
| } |
| |
| bss->wfd = ie_tlv_extract_wfd_payload(data, len, &bss->wfd_size); |
| |
| return have_ssid; |
| } |
| |
| static struct scan_bss *scan_parse_attr_bss(struct l_genl_attr *attr, |
| struct wiphy *wiphy, |
| uint32_t *out_seen_ms_ago) |
| { |
| uint16_t type, len; |
| const void *data; |
| struct scan_bss *bss; |
| const uint8_t *ies = NULL; |
| size_t ies_len; |
| const uint8_t *beacon_ies = NULL; |
| size_t beacon_ies_len; |
| |
| bss = l_new(struct scan_bss, 1); |
| bss->utilization = 127; |
| bss->source_frame = SCAN_BSS_BEACON; |
| |
| 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: |
| ies = data; |
| ies_len = len; |
| break; |
| case NL80211_BSS_PARENT_TSF: |
| if (len != sizeof(uint64_t)) |
| goto fail; |
| |
| bss->parent_tsf = l_get_u64(data); |
| break; |
| case NL80211_BSS_PRESP_DATA: |
| bss->source_frame = SCAN_BSS_PROBE_RESP; |
| break; |
| case NL80211_BSS_BEACON_IES: |
| beacon_ies = data; |
| beacon_ies_len = len; |
| break; |
| case NL80211_BSS_SEEN_MS_AGO: |
| if (L_WARN_ON(len != sizeof(uint32_t))) |
| break; |
| |
| *out_seen_ms_ago = l_get_u32(data); |
| break; |
| case NL80211_BSS_LAST_SEEN_BOOTTIME: |
| if (L_WARN_ON(len != sizeof(uint64_t))) |
| break; |
| |
| bss->time_stamp = l_get_u64(data) / L_NSEC_PER_USEC; |
| break; |
| } |
| } |
| |
| /* |
| * Try our best at deciding whether the IEs come from a Probe |
| * Response based on the hints explained in nl80211.h |
| * (enum nl80211_bss). |
| */ |
| if (bss->source_frame == SCAN_BSS_BEACON && ies && ( |
| !beacon_ies || |
| ies_len != beacon_ies_len || |
| memcmp(ies, beacon_ies, ies_len))) |
| bss->source_frame = SCAN_BSS_PROBE_RESP; |
| |
| /* Set data rate to something low, just in case estimation fails */ |
| bss->data_rate = 2000000; |
| |
| if (ies) { |
| if (!scan_parse_bss_information_elements(bss, ies, ies_len)) |
| goto fail; |
| |
| L_WARN_ON(wiphy_estimate_data_rate(wiphy, ies, ies_len, bss, |
| &bss->data_rate) < 0); |
| } |
| |
| 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, |
| struct wiphy *wiphy, |
| uint32_t *out_seen_ms_ago) |
| { |
| struct l_genl_attr attr, nested; |
| uint16_t type; |
| struct scan_bss *bss = NULL; |
| |
| if (!l_genl_attr_init(&attr, msg)) |
| return NULL; |
| |
| while (l_genl_attr_next(&attr, &type, NULL, NULL)) { |
| switch (type) { |
| case NL80211_ATTR_BSS: |
| if (!l_genl_attr_recurse(&attr, &nested)) |
| return NULL; |
| |
| bss = scan_parse_attr_bss(&nested, wiphy, |
| out_seen_ms_ago); |
| break; |
| } |
| } |
| |
| return bss; |
| } |
| |
| static void scan_bss_compute_rank(struct scan_bss *bss) |
| { |
| static const double RANK_HIGH_UTILIZATION_FACTOR = 0.8; |
| static const double RANK_LOW_UTILIZATION_FACTOR = 1.2; |
| double rank; |
| uint32_t irank; |
| /* |
| * Maximum rate is 2340Mbps (VHT) |
| */ |
| double max_rate = 2340000000; |
| |
| rank = (double)bss->data_rate / max_rate * USHRT_MAX; |
| |
| /* 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; |
| |
| irank = rank; |
| |
| if (irank > USHRT_MAX) |
| bss->rank = USHRT_MAX; |
| else |
| bss->rank = irank; |
| } |
| |
| struct scan_bss *scan_bss_new_from_probe_req(const struct mmpdu_header *mpdu, |
| const uint8_t *body, |
| size_t body_len, |
| uint32_t frequency, int rssi) |
| |
| { |
| struct scan_bss *bss; |
| |
| bss = l_new(struct scan_bss, 1); |
| memcpy(bss->addr, mpdu->address_2, 6); |
| bss->utilization = 127; |
| bss->source_frame = SCAN_BSS_PROBE_REQ; |
| bss->frequency = frequency; |
| bss->signal_strength = rssi; |
| |
| if (!scan_parse_bss_information_elements(bss, body, body_len)) |
| goto fail; |
| |
| return bss; |
| |
| fail: |
| scan_bss_free(bss); |
| return NULL; |
| } |
| |
| void scan_bss_free(struct scan_bss *bss) |
| { |
| l_free(bss->rsne); |
| l_free(bss->wpa); |
| l_free(bss->wsc); |
| l_free(bss->osen); |
| l_free(bss->rc_ie); |
| l_free(bss->wfd); |
| |
| switch (bss->source_frame) { |
| case SCAN_BSS_PROBE_RESP: |
| if (!bss->p2p_probe_resp_info) |
| break; |
| |
| p2p_clear_probe_resp(bss->p2p_probe_resp_info); |
| l_free(bss->p2p_probe_resp_info); |
| break; |
| case SCAN_BSS_PROBE_REQ: |
| if (!bss->p2p_probe_req_info) |
| break; |
| |
| p2p_clear_probe_req(bss->p2p_probe_req_info); |
| l_free(bss->p2p_probe_req_info); |
| break; |
| case SCAN_BSS_BEACON: |
| if (!bss->p2p_beacon_info) |
| break; |
| |
| p2p_clear_beacon(bss->p2p_beacon_info); |
| l_free(bss->p2p_beacon_info); |
| break; |
| } |
| |
| l_free(bss); |
| } |
| |
| int scan_bss_get_rsn_info(const struct scan_bss *bss, struct ie_rsn_info *info) |
| { |
| /* |
| * If both an RSN and a WPA elements are present currently |
| * RSN takes priority and the WPA IE is ignored. |
| */ |
| if (bss->rsne) { |
| int res = ie_parse_rsne_from_data(bss->rsne, bss->rsne[1] + 2, |
| info); |
| if (res < 0) { |
| l_debug("Cannot parse RSN field (%d, %s)", |
| res, strerror(-res)); |
| return res; |
| } |
| } else if (bss->wpa) { |
| int res = ie_parse_wpa_from_data(bss->wpa, bss->wpa[1] + 2, |
| info); |
| if (res < 0) { |
| l_debug("Cannot parse WPA IE (%d, %s)", |
| res, strerror(-res)); |
| return res; |
| } |
| } else if (bss->osen) { |
| int res = ie_parse_osen_from_data(bss->osen, bss->osen[1] + 2, |
| info); |
| if (res < 0) { |
| l_debug("Cannot parse OSEN IE (%d, %s)", |
| res, strerror(-res)); |
| return res; |
| } |
| } else |
| return -ENOENT; |
| |
| return 0; |
| } |
| |
| 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) ? 1 : -1; |
| } |
| |
| static void get_scan_callback(struct l_genl_msg *msg, void *user_data) |
| { |
| struct scan_results *results = user_data; |
| struct scan_context *sc = results->sc; |
| struct scan_bss *bss; |
| uint64_t wdev_id; |
| uint32_t seen_ms_ago = 0; |
| |
| l_debug("get_scan_callback"); |
| |
| if (nl80211_parse_attrs(msg, NL80211_ATTR_WDEV, &wdev_id, |
| NL80211_ATTR_UNSPEC) < 0) |
| return; |
| |
| if (wdev_id != sc->wdev_id) { |
| l_warn("wdev mismatch in get_scan_callback"); |
| return; |
| } |
| |
| bss = scan_parse_result(msg, sc->wiphy, &seen_ms_ago); |
| if (!bss) |
| return; |
| |
| if (!bss->time_stamp) |
| bss->time_stamp = results->time_stamp - |
| seen_ms_ago * L_USEC_PER_MSEC; |
| |
| scan_bss_compute_rank(bss); |
| l_queue_insert(results->bss_list, bss, scan_bss_rank_compare, NULL); |
| } |
| |
| static void discover_hidden_network_bsses(struct scan_context *sc, |
| struct l_queue *bss_list) |
| { |
| const struct l_queue_entry *bss_entry; |
| |
| for (bss_entry = l_queue_get_entries(bss_list); bss_entry; |
| bss_entry = bss_entry->next) { |
| struct scan_bss *bss = bss_entry->data; |
| |
| if (!util_ssid_is_hidden(bss->ssid_len, bss->ssid)) |
| continue; |
| |
| sc->sp.needs_active_scan = true; |
| } |
| } |
| |
| static void scan_finished(struct scan_context *sc, |
| int err, struct l_queue *bss_list, |
| const struct scan_freq_set *freqs, |
| struct scan_request *sr) |
| { |
| bool new_owner = false; |
| |
| if (bss_list) |
| discover_hidden_network_bsses(sc, bss_list); |
| |
| if (sr) { |
| l_queue_remove(sc->requests, sr); |
| sc->started = false; |
| sc->work_started = false; |
| |
| if (sr->callback) |
| new_owner = sr->callback(err, bss_list, |
| freqs, sr->userdata); |
| |
| /* |
| * Can start a new scan now that we've removed this one from |
| * the queue. If this were an external scan request (sr NULL) |
| * then the SCAN_FINISHED or SCAN_ABORTED handler would have |
| * taken care of sending the next command for a new or ongoing |
| * scan, or scheduling the next periodic scan. |
| */ |
| wiphy_radio_work_done(sc->wiphy, sr->work.id); |
| } else if (sc->sp.callback) |
| new_owner = sc->sp.callback(err, bss_list, |
| freqs, sc->sp.userdata); |
| |
| if (bss_list && !new_owner) |
| l_queue_destroy(bss_list, |
| (l_queue_destroy_func_t) scan_bss_free); |
| } |
| |
| static void get_scan_done(void *user) |
| { |
| struct scan_results *results = user; |
| struct scan_context *sc = results->sc; |
| |
| l_debug("get_scan_done"); |
| |
| sc->get_scan_cmd_id = 0; |
| |
| if (l_queue_peek_head(sc->requests) == results->sr) |
| scan_finished(sc, 0, results->bss_list, |
| results->freqs, results->sr); |
| else |
| l_queue_destroy(results->bss_list, |
| (l_queue_destroy_func_t) scan_bss_free); |
| |
| if (results->freqs) |
| scan_freq_set_free(results->freqs); |
| |
| l_free(results); |
| } |
| |
| static bool scan_parse_flush_flag_from_msg(struct l_genl_msg *msg) |
| { |
| struct l_genl_attr attr; |
| uint16_t type, len; |
| const void *data; |
| |
| if (!l_genl_attr_init(&attr, msg)) |
| return false; |
| |
| while (l_genl_attr_next(&attr, &type, &len, &data)) |
| if (type == NL80211_SCAN_FLAG_FLUSH) |
| return true; |
| |
| return false; |
| } |
| |
| 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; |
| uint64_t wdev_id; |
| uint32_t wiphy_id; |
| struct scan_context *sc; |
| bool active_scan = false; |
| uint64_t start_time_tsf = 0; |
| struct scan_request *sr; |
| |
| cmd = l_genl_msg_get_command(msg); |
| |
| l_debug("Scan notification %s(%u)", nl80211cmd_to_string(cmd), cmd); |
| |
| if (nl80211_parse_attrs(msg, NL80211_ATTR_WDEV, &wdev_id, |
| NL80211_ATTR_WIPHY, &wiphy_id, |
| NL80211_ATTR_UNSPEC) < 0) |
| return; |
| |
| sc = l_queue_find(scan_contexts, scan_context_match, &wdev_id); |
| if (!sc) |
| return; |
| |
| if (!l_genl_attr_init(&attr, msg)) |
| return; |
| |
| while (l_genl_attr_next(&attr, &type, &len, &data)) { |
| switch (type) { |
| case NL80211_ATTR_SCAN_SSIDS: |
| active_scan = true; |
| break; |
| case NL80211_ATTR_SCAN_START_TIME_TSF: |
| if (len != sizeof(uint64_t)) |
| return; |
| |
| start_time_tsf = l_get_u64(data); |
| break; |
| } |
| } |
| |
| sr = l_queue_peek_head(sc->requests); |
| |
| switch (cmd) { |
| case NL80211_CMD_NEW_SCAN_RESULTS: |
| { |
| struct l_genl_msg *scan_msg; |
| struct scan_results *results; |
| bool send_next = false; |
| bool get_results = false; |
| |
| if (sc->state == SCAN_STATE_NOT_RUNNING) |
| break; |
| |
| sc->state = SCAN_STATE_NOT_RUNNING; |
| |
| /* Was this our own scan or an external scan */ |
| if (sc->triggered) { |
| sc->triggered = false; |
| |
| if (!sr->callback) { |
| scan_finished(sc, -ECANCELED, NULL, NULL, sr); |
| break; |
| } |
| |
| /* |
| * If this was the last command for the current request |
| * avoid starting the next request until the GET_SCAN |
| * dump callback so that any current request is always |
| * at the top of the queue and handling is simpler. |
| */ |
| if (l_queue_isempty(sr->cmds)) |
| get_results = true; |
| else |
| send_next = true; |
| } else { |
| if (sc->get_scan_cmd_id) |
| break; |
| |
| if (sc->sp.callback) |
| get_results = true; |
| |
| /* An external scan may have flushed our results */ |
| if (sc->started && scan_parse_flush_flag_from_msg(msg)) |
| scan_finished(sc, -EAGAIN, NULL, NULL, sr); |
| else |
| send_next = true; |
| |
| sr = NULL; |
| } |
| |
| /* |
| * Send the next command of an ongoing request, or continue with |
| * a previously busy scan attempt due to an external scan. A |
| * temporary scan_request object is used (rather than 'sr') |
| * because the state of 'sr' tells us if the results should |
| * be used either as normal scan results, or to take advantage |
| * of the external scan as a 'free' periodic scan of sorts. |
| */ |
| if (sc->work_started && send_next) { |
| struct scan_request *next = l_queue_peek_head( |
| sc->requests); |
| |
| if (next) |
| start_next_scan_request(&next->work); |
| } |
| |
| if (!get_results) |
| break; |
| |
| results = l_new(struct scan_results, 1); |
| results->sc = sc; |
| results->time_stamp = l_time_now(); |
| results->sr = sr; |
| results->bss_list = l_queue_new(); |
| |
| scan_parse_new_scan_results(msg, results); |
| |
| scan_msg = l_genl_msg_new_sized(NL80211_CMD_GET_SCAN, 8); |
| l_genl_msg_append_attr(scan_msg, NL80211_ATTR_WDEV, 8, |
| &sc->wdev_id); |
| sc->get_scan_cmd_id = l_genl_family_dump(nl80211, scan_msg, |
| get_scan_callback, |
| results, get_scan_done); |
| |
| break; |
| } |
| |
| case NL80211_CMD_TRIGGER_SCAN: |
| if (active_scan) |
| sc->state = SCAN_STATE_ACTIVE; |
| else |
| sc->state = SCAN_STATE_PASSIVE; |
| |
| if (sr) |
| sr->start_time_tsf = start_time_tsf; |
| |
| break; |
| |
| case NL80211_CMD_SCAN_ABORTED: |
| if (sc->state == SCAN_STATE_NOT_RUNNING) |
| break; |
| |
| sc->state = SCAN_STATE_NOT_RUNNING; |
| |
| if (sc->triggered) { |
| sc->triggered = false; |
| |
| scan_finished(sc, -ECANCELED, NULL, NULL, sr); |
| } else { |
| /* |
| * If this was an external scan that got aborted |
| * we may be able to now queue our own scan although |
| * the abort could also have been triggered by the |
| * hardware or the driver because of another activity |
| * starting in which case we should just get an EBUSY. |
| */ |
| if (sc->work_started) |
| start_next_scan_request(&sr->work); |
| } |
| |
| break; |
| } |
| } |
| |
| static void get_fw_scan_done(void *userdata) |
| { |
| struct scan_results *results = userdata; |
| struct scan_request *sr = results->sr; |
| struct scan_context *sc = results->sc; |
| int err = l_queue_length(results->bss_list) == 0 ? -ENOENT : 0; |
| bool new_owner = false; |
| |
| sc->get_fw_scan_cmd_id = 0; |
| |
| if (sr->callback) |
| new_owner = sr->callback(err, results->bss_list, NULL, |
| sr->userdata); |
| |
| if (!new_owner) |
| l_queue_destroy(results->bss_list, |
| (l_queue_destroy_func_t) scan_bss_free); |
| |
| if (sr->destroy) |
| sr->destroy(sr->userdata); |
| |
| l_free(sr); |
| l_free(results); |
| } |
| |
| bool scan_get_firmware_scan(uint64_t wdev_id, scan_notify_func_t notify, |
| void *userdata, scan_destroy_func_t destroy) |
| { |
| struct l_genl_msg *scan_msg; |
| struct scan_results *results; |
| struct scan_request *sr; |
| struct scan_context *sc = l_queue_find(scan_contexts, |
| scan_context_match, &wdev_id); |
| |
| if (!sc) |
| return false; |
| |
| sr = l_new(struct scan_request, 1); |
| sr->callback = notify; |
| sr->destroy = destroy; |
| sr->userdata = userdata; |
| |
| results = l_new(struct scan_results, 1); |
| results->sc = sc; |
| results->time_stamp = l_time_now(); |
| results->bss_list = l_queue_new(); |
| results->sr = sr; |
| |
| scan_msg = l_genl_msg_new_sized(NL80211_CMD_GET_SCAN, 8); |
| l_genl_msg_append_attr(scan_msg, NL80211_ATTR_WDEV, 8, &sc->wdev_id); |
| |
| sc->get_fw_scan_cmd_id = l_genl_family_dump(nl80211, scan_msg, |
| get_scan_callback, |
| results, |
| get_fw_scan_done); |
| if (!sc->get_fw_scan_cmd_id) { |
| l_queue_destroy(results->bss_list, |
| (l_queue_destroy_func_t) scan_bss_free); |
| l_free(results); |
| l_free(sr); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| 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 (freq % 5) |
| return 0; |
| |
| channel = (freq - 5000) / 5; |
| |
| if (out_band) |
| *out_band = SCAN_BAND_5_GHZ; |
| |
| return channel; |
| } |
| |
| if (freq >= 4905 && freq < 5000) { |
| if (freq % 5) |
| return 0; |
| |
| channel = (freq - 4000) / 5; |
| |
| if (out_band) |
| *out_band = SCAN_BAND_5_GHZ; |
| |
| return channel; |
| } |
| |
| return 0; |
| } |
| |
| uint32_t scan_channel_to_freq(uint8_t channel, enum scan_band band) |
| { |
| if (band == SCAN_BAND_2_4_GHZ) { |
| if (channel >= 1 && channel <= 13) |
| return 2407 + 5 * channel; |
| |
| if (channel == 14) |
| return 2484; |
| } |
| |
| if (band == SCAN_BAND_5_GHZ) { |
| if (channel >= 1 && channel <= 179) |
| return 5000 + 5 * channel; |
| |
| if (channel >= 181 && channel <= 199) |
| return 4000 + 5 * channel; |
| } |
| |
| return 0; |
| } |
| |
| static const char *const oper_class_us_codes[] = { |
| "US", "CA" |
| }; |
| |
| static const char *const oper_class_eu_codes[] = { |
| "AL", "AM", "AT", "AZ", "BA", "BE", "BG", "BY", "CH", "CY", "CZ", "DE", |
| "DK", "EE", "EL", "ES", "FI", "FR", "GE", "HR", "HU", "IE", "IS", "IT", |
| "LI", "LT", "LU", "LV", "MD", "ME", "MK", "MT", "NL", "NO", "PL", "PT", |
| "RO", "RS", "RU", "SE", "SI", "SK", "TR", "UA", "UK" |
| }; |
| |
| /* Annex E, table E-1 */ |
| static const uint8_t oper_class_us_to_global[] = { |
| [1] = 115, [2] = 118, [3] = 124, [4] = 121, |
| [5] = 125, [6] = 103, [7] = 103, [8] = 102, |
| [9] = 102, [10] = 101, [11] = 101, [12] = 81, |
| [13] = 94, [14] = 95, [15] = 96, [22] = 116, |
| [23] = 119, [24] = 122, [25] = 126, [26] = 126, |
| [27] = 117, [28] = 120, [29] = 123, [30] = 127, |
| [31] = 127, [32] = 83, [33] = 84, [34] = 180, |
| /* 128 - 130 is a 1 to 1 mapping */ |
| }; |
| |
| /* Annex E, table E-2 */ |
| static const uint8_t oper_class_eu_to_global[] = { |
| [1] = 115, [2] = 118, [3] = 121, [4] = 81, |
| [5] = 116, [6] = 119, [7] = 122, [8] = 117, |
| [9] = 120, [10] = 123, [11] = 83, [12] = 84, |
| [17] = 125, [18] = 130, |
| /* 128 - 130 is a 1 to 1 mapping */ |
| }; |
| |
| /* Annex E, table E-3 */ |
| static const uint8_t oper_class_jp_to_global[] = { |
| [1] = 115, [2] = 112, [3] = 112, [4] = 112, |
| [5] = 112, [6] = 112, [7] = 109, [8] = 109, |
| [9] = 109, [10] = 109, [11] = 109, [12] = 113, |
| [13] = 113, [14] = 113, [15] = 113, [16] = 110, |
| [17] = 110, [18] = 110, [19] = 110, [20] = 110, |
| [21] = 114, [22] = 114, [23] = 114, [24] = 114, |
| [25] = 111, [26] = 111, [27] = 111, [28] = 111, |
| [29] = 111, [30] = 81, [31] = 82, [32] = 118, |
| [33] = 118, [34] = 121, [35] = 121, [36] = 116, |
| [37] = 119, [38] = 119, [39] = 122, [40] = 122, |
| [41] = 117, [42] = 120, [43] = 120, [44] = 123, |
| [45] = 123, [46] = 104, [47] = 104, [48] = 104, |
| [49] = 104, [50] = 104, [51] = 105, [52] = 105, |
| [53] = 105, [54] = 105, [55] = 105, [56] = 83, |
| [57] = 84, [58] = 121, [59] = 180, |
| /* 128 - 130 is a 1 to 1 mapping */ |
| }; |
| |
| /* Annex E, table E-4 (only 2.4GHz and 4.9 / 5GHz bands) */ |
| static const enum scan_band oper_class_to_band_global[] = { |
| [81 ... 84] = SCAN_BAND_2_4_GHZ, |
| [104 ... 130] = SCAN_BAND_5_GHZ, |
| }; |
| |
| /* Annex E, table E-5 */ |
| static const uint8_t oper_class_cn_to_global[] = { |
| [1] = 115, [2] = 118, [3] = 125, [4] = 116, |
| [5] = 119, [6] = 126, [7] = 81, [8] = 83, |
| [9] = 84, |
| /* 128 - 130 is a 1 to 1 mapping */ |
| }; |
| |
| enum scan_band scan_oper_class_to_band(const uint8_t *country, |
| uint8_t oper_class) |
| { |
| unsigned int i; |
| int table = 0; |
| |
| if (country && country[2] >= 1 && country[2] <= 5) |
| table = country[2]; |
| else if (country) { |
| for (i = 0; i < L_ARRAY_SIZE(oper_class_us_codes); i++) |
| if (!memcmp(oper_class_us_codes[i], country, 2)) { |
| /* Use table E-1 */ |
| table = 1; |
| break; |
| } |
| |
| for (i = 0; i < L_ARRAY_SIZE(oper_class_eu_codes); i++) |
| if (!memcmp(oper_class_eu_codes[i], country, 2)) { |
| /* Use table E-2 */ |
| table = 2; |
| break; |
| } |
| |
| if (!memcmp("JP", country, 2)) |
| /* Use table E-3 */ |
| table = 3; |
| |
| if (!memcmp("CN", country, 2)) |
| /* Use table E-5 */ |
| table = 5; |
| } |
| |
| switch (table) { |
| case 1: |
| if (oper_class < L_ARRAY_SIZE(oper_class_us_to_global)) |
| oper_class = oper_class_us_to_global[oper_class]; |
| break; |
| case 2: |
| if (oper_class < L_ARRAY_SIZE(oper_class_eu_to_global)) |
| oper_class = oper_class_eu_to_global[oper_class]; |
| break; |
| case 3: |
| if (oper_class < L_ARRAY_SIZE(oper_class_jp_to_global)) |
| oper_class = oper_class_jp_to_global[oper_class]; |
| break; |
| case 5: |
| if (oper_class < L_ARRAY_SIZE(oper_class_cn_to_global)) |
| oper_class = oper_class_cn_to_global[oper_class]; |
| break; |
| } |
| |
| if (oper_class < L_ARRAY_SIZE(oper_class_to_band_global)) |
| return oper_class_to_band_global[oper_class]; |
| else |
| 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(const 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; |
| } |
| |
| uint32_t scan_freq_set_get_bands(struct scan_freq_set *freqs) |
| { |
| uint32_t bands = 0; |
| uint32_t max; |
| |
| if (freqs->channels_2ghz) |
| bands |= SCAN_BAND_2_4_GHZ; |
| |
| max = l_uintset_get_max(freqs->channels_5ghz); |
| |
| if (l_uintset_find_min(freqs->channels_5ghz) <= max) |
| bands |= SCAN_BAND_5_GHZ; |
| |
| return bands; |
| } |
| |
| static void scan_channels_5ghz_add(uint32_t channel, void *user_data) |
| { |
| struct l_uintset *to = user_data; |
| |
| l_uintset_put(to, channel); |
| } |
| |
| void scan_freq_set_merge(struct scan_freq_set *to, |
| const struct scan_freq_set *from) |
| { |
| to->channels_2ghz |= from->channels_2ghz; |
| |
| l_uintset_foreach(from->channels_5ghz, scan_channels_5ghz_add, |
| to->channels_5ghz); |
| } |
| |
| bool scan_freq_set_isempty(const struct scan_freq_set *set) |
| { |
| if (set->channels_2ghz == 0 && l_uintset_isempty(set->channels_5ghz)) |
| return true; |
| |
| return false; |
| } |
| |
| struct channels_5ghz_foreach_data { |
| scan_freq_set_func_t func; |
| void *user_data; |
| }; |
| |
| static void scan_channels_5ghz_frequency(uint32_t channel, void *user_data) |
| { |
| const struct channels_5ghz_foreach_data *channels_5ghz_data = user_data; |
| uint32_t freq; |
| |
| freq = scan_channel_to_freq(channel, SCAN_BAND_5_GHZ); |
| |
| channels_5ghz_data->func(freq, channels_5ghz_data->user_data); |
| } |
| |
| void scan_freq_set_foreach(const struct scan_freq_set *freqs, |
| scan_freq_set_func_t func, void *user_data) |
| { |
| struct channels_5ghz_foreach_data data = { }; |
| uint8_t channel; |
| uint32_t freq; |
| |
| if (unlikely(!freqs || !func)) |
| return; |
| |
| data.func = func; |
| data.user_data = user_data; |
| |
| l_uintset_foreach(freqs->channels_5ghz, scan_channels_5ghz_frequency, |
| &data); |
| |
| if (!freqs->channels_2ghz) |
| return; |
| |
| for (channel = 1; channel <= 14; channel++) { |
| if (freqs->channels_2ghz & (1 << (channel - 1))) { |
| freq = scan_channel_to_freq(channel, SCAN_BAND_2_4_GHZ); |
| |
| func(freq, user_data); |
| } |
| } |
| } |
| |
| void scan_freq_set_constrain(struct scan_freq_set *set, |
| const struct scan_freq_set *constraint) |
| { |
| struct l_uintset *intersection; |
| |
| intersection = l_uintset_intersect(constraint->channels_5ghz, |
| set->channels_5ghz); |
| if (!intersection) |
| /* This shouldn't ever be the case. */ |
| return; |
| |
| l_uintset_free(set->channels_5ghz); |
| set->channels_5ghz = intersection; |
| |
| set->channels_2ghz &= constraint->channels_2ghz; |
| } |
| |
| bool scan_wdev_add(uint64_t wdev_id) |
| { |
| struct scan_context *sc; |
| |
| if (l_queue_find(scan_contexts, scan_context_match, &wdev_id)) |
| return false; |
| |
| sc = scan_context_new(wdev_id); |
| if (!sc) |
| return false; |
| |
| l_queue_push_head(scan_contexts, sc); |
| |
| if (l_queue_length(scan_contexts) > 1) |
| goto done; |
| |
| nl80211 = l_genl_family_new(iwd_get_genl(), NL80211_GENL_NAME); |
| l_genl_family_register(nl80211, "scan", scan_notify, NULL, NULL); |
| |
| done: |
| return true; |
| } |
| |
| bool scan_wdev_remove(uint64_t wdev_id) |
| { |
| struct scan_context *sc; |
| |
| sc = l_queue_remove_if(scan_contexts, scan_context_match, &wdev_id); |
| |
| if (!sc) |
| return false; |
| |
| l_info("Removing scan context for wdev %" PRIx64, wdev_id); |
| scan_context_free(sc); |
| |
| if (l_queue_isempty(scan_contexts)) { |
| l_genl_family_free(nl80211); |
| nl80211 = NULL; |
| } |
| |
| return true; |
| } |
| |
| static int scan_init(void) |
| { |
| const struct l_settings *config = iwd_get_config(); |
| |
| scan_contexts = l_queue_new(); |
| |
| if (!l_settings_get_double(config, "Rank", "BandModifier5Ghz", |
| &RANK_5G_FACTOR)) |
| RANK_5G_FACTOR = 1.0; |
| |
| if (!l_settings_get_uint(config, "Scan", "InitialPeriodicScanInterval", |
| &SCAN_INIT_INTERVAL)) |
| SCAN_INIT_INTERVAL = 10; |
| |
| if (SCAN_INIT_INTERVAL > UINT16_MAX) |
| SCAN_INIT_INTERVAL = UINT16_MAX; |
| |
| if (!l_settings_get_uint(config, "Scan", "MaximumPeriodicScanInterval", |
| &SCAN_MAX_INTERVAL)) |
| SCAN_MAX_INTERVAL = 300; |
| |
| if (SCAN_MAX_INTERVAL > UINT16_MAX) |
| SCAN_MAX_INTERVAL = UINT16_MAX; |
| |
| return 0; |
| } |
| |
| static void scan_exit() |
| { |
| l_queue_destroy(scan_contexts, |
| (l_queue_destroy_func_t) scan_context_free); |
| scan_contexts = NULL; |
| l_genl_family_free(nl80211); |
| nl80211 = NULL; |
| } |
| |
| IWD_MODULE(scan, scan_init, scan_exit) |