diff options
author | Andrew Zaborowski <andrew.zaborowski@intel.com> | 2020-04-25 11:09:24 +0200 |
---|---|---|
committer | Denis Kenzior <denkenz@gmail.com> | 2020-04-24 22:16:43 -0500 |
commit | 3d4725870d72e166f695894e1b982cbbf46a5f86 (patch) | |
tree | 161117b406f8d8706a6c9c1730d343626491f981 | |
parent | f0e3defa3e26dab7ca2bb2a2fa629b734724fa10 (diff) | |
download | iwd-3d4725870d72e166f695894e1b982cbbf46a5f86.tar.gz |
p2p: Add the Listen State
Start a remain-on-channel cmd implementing the Listen State, after each
the Scan Phase implemented as an active scan.
-rw-r--r-- | src/p2p.c | 342 |
1 files changed, 340 insertions, 2 deletions
@@ -70,13 +70,19 @@ struct p2p_device { uint8_t listen_country[3]; uint8_t listen_oper_class; uint32_t listen_channel; + unsigned int scan_interval; + time_t next_scan_ts; + struct l_timeout *scan_timeout; uint32_t scan_id; unsigned int chans_per_scan; unsigned int scan_chan_idx; + uint64_t roc_cookie; + unsigned int listen_duration; struct l_queue *discovery_users; struct l_queue *peer_list; bool enabled : 1; + bool have_roc_cookie : 1; }; struct p2p_discovery_user { @@ -105,6 +111,11 @@ static struct l_queue *p2p_device_list; static const int channels_social[] = { 1, 6, 11 }; static const int channels_scan_2_4_other[] = { 2, 3, 4, 5, 7, 8, 9, 10 }; +enum { + FRAME_GROUP_DEFAULT = 0, + FRAME_GROUP_LISTEN, +}; + static bool p2p_device_match(const void *a, const void *b) { const struct p2p_device *dev = a; @@ -252,9 +263,156 @@ static void p2p_scan_destroy(void *user_data) dev->scan_id = 0; } +#define SCAN_INTERVAL_MAX 3 +#define SCAN_INTERVAL_STEP 1 #define CHANS_PER_SCAN_INITIAL 2 #define CHANS_PER_SCAN 2 +static bool p2p_device_scan_start(struct p2p_device *dev); +static void p2p_device_roc_start(struct p2p_device *dev); + +static void p2p_device_roc_timeout(struct l_timeout *timeout, void *user_data) +{ + struct p2p_device *dev = user_data; + + l_timeout_remove(dev->scan_timeout); + + if (time(NULL) < dev->next_scan_ts) { + /* + * dev->scan_timeout destroy function will have been called + * by now so it won't overwrite the new timeout set by + * p2p_device_roc_start. + */ + p2p_device_roc_start(dev); + return; + } + + p2p_device_scan_start(dev); +} + +static void p2p_device_roc_cancel(struct p2p_device *dev) +{ + struct l_genl_msg *msg; + + if (!dev->have_roc_cookie) + return; + + l_debug(""); + + msg = l_genl_msg_new_sized(NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL, 32); + l_genl_msg_append_attr(msg, NL80211_ATTR_WDEV, 8, &dev->wdev_id); + l_genl_msg_append_attr(msg, NL80211_ATTR_COOKIE, 8, &dev->roc_cookie); + l_genl_family_send(dev->nl80211, msg, NULL, NULL, NULL); + + dev->have_roc_cookie = false; +} + +static void p2p_scan_timeout_destroy(void *user_data) +{ + struct p2p_device *dev = user_data; + + dev->scan_timeout = NULL; + + if (dev->nl80211) { + /* + * Most likely when the timer expires the ROC period + * has finished but send a cancel command to make sure, + * as well as handle situations like disabling P2P. + */ + p2p_device_roc_cancel(dev); + } +} + +static void p2p_device_roc_cb(struct l_genl_msg *msg, void *user_data) +{ + struct p2p_device *dev = user_data; + uint64_t cookie; + int error = l_genl_msg_get_error(msg); + + l_debug("ROC: %s (%i)", strerror(-error), -error); + + if (error) + return; + + if (nl80211_parse_attrs(msg, NL80211_ATTR_COOKIE, &cookie, + NL80211_ATTR_UNSPEC) < 0) + return; + + dev->roc_cookie = cookie; + dev->have_roc_cookie = true; + + /* + * Has the command taken so long that P2P has been since disabled + * or the timeout otherwise ran out? + */ + if (!dev->scan_timeout) + p2p_device_roc_cancel(dev); +} + +static void p2p_device_roc_start(struct p2p_device *dev) +{ + struct l_genl_msg *msg; + uint32_t listen_freq; + uint32_t duration; + uint32_t cmd_id; + + l_debug(""); + + /* + * One second granularity is fine here because some randomess + * is desired and the intervals don't have strictly defined + * limits. + */ + duration = (dev->next_scan_ts - time(NULL)) * 1000; + + if (duration < 200) + duration = 200; + + /* + * Driver max duration seems to be 5000ms or more for all drivers + * except mac80211_hwsim where it is only 1000ms. + */ + if (duration > wiphy_get_max_roc_duration(dev->wiphy)) + duration = wiphy_get_max_roc_duration(dev->wiphy); + + /* + * Some drivers seem to miss fewer frames if we start new requests + * often. + */ + if (duration > 1000) + duration = 1000; + + listen_freq = scan_channel_to_freq(dev->listen_channel, + SCAN_BAND_2_4_GHZ); + + msg = l_genl_msg_new_sized(NL80211_CMD_REMAIN_ON_CHANNEL, 64); + l_genl_msg_append_attr(msg, NL80211_ATTR_WDEV, 8, &dev->wdev_id); + l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &listen_freq); + l_genl_msg_append_attr(msg, NL80211_ATTR_DURATION, 4, &duration); + + cmd_id = l_genl_family_send(dev->nl80211, msg, p2p_device_roc_cb, dev, + NULL); + if (!cmd_id) + l_genl_msg_unref(msg); + + /* + * Time out after @duration ms independent of whether we were able to + * start the ROC command. If we receive the CMD_REMAIN_ON_CHANNEL + * event we'll update the timeout to give the ROC command enough time + * to finish. On an error or if we time out before the ROC command + * even starts, we'll just retry after @duration ms so we don't even + * need to handle errors specifically. + */ + dev->scan_timeout = l_timeout_create_ms(duration, + p2p_device_roc_timeout, dev, + p2p_scan_timeout_destroy); + dev->listen_duration = duration; + dev->have_roc_cookie = false; + + l_debug("started a ROC command on channel %i for %i ms", + (int) dev->listen_channel, (int) duration); +} + static bool p2p_device_peer_add(struct p2p_device *dev, struct p2p_peer *peer) { if (!strlen(peer->name) || !l_utf8_validate( @@ -391,7 +549,32 @@ static bool p2p_scan_notify(int err, struct l_queue *bss_list, l_queue_destroy(bss_list, NULL); schedule: - /* TODO: move to listen state */ + /* + * Calculate interval between now and when we want the next active + * scan to start. Keep issuing Remain-on-Channel commands of + * maximum duration until it's time to start the new scan. + * The listen periods are actually like a passive scan except that + * instead of listening for Beacons only, we also look at Probe + * Requests and Probe Responses because they, too, carry P2P IEs + * with all the information we need about peer devices. Beacons + * also do, in case of GOs, but we will already get the same + * information from the Probe Responses and (even if we can + * receive the beacons in userspace in the first place) we don't + * want to handle so many frames. + * + * According to 3.1.2.1.1 we shall be available in listen state + * during Find for at least 500ms continuously at least once in + * every 5s. According to 3.1.2.1.3, the Listen State lasts for + * between 1 and 3 one-hundred TU Intervals. + * + * The Search State duration is implementation dependent. + */ + if (dev->scan_interval < SCAN_INTERVAL_MAX) + dev->scan_interval += SCAN_INTERVAL_STEP; + + dev->next_scan_ts = time(NULL) + dev->scan_interval; + + p2p_device_roc_start(dev); return true; } @@ -475,11 +658,118 @@ static bool p2p_device_scan_start(struct p2p_device *dev) return dev->scan_id != 0; } +static void p2p_device_probe_cb(const struct mmpdu_header *mpdu, + const void *body, size_t body_len, + int rssi, void *user_data) +{ + struct p2p_device *dev = user_data; + struct p2p_peer *peer; + struct p2p_probe_req p2p_info; + struct wsc_probe_request wsc_info; + int r; + uint8_t *wsc_payload; + ssize_t wsc_len; + struct scan_bss *bss; + struct p2p_channel_attr *channel; + enum scan_band band; + uint32_t frequency; + + l_debug(""); + + if (!dev->scan_timeout && !dev->scan_id) + return; + + wsc_payload = ie_tlv_extract_wsc_payload(body, body_len, &wsc_len); + if (!wsc_payload) /* Not a P2P Probe Req, ignore */ + return; + + r = wsc_parse_probe_request(wsc_payload, wsc_len, &wsc_info); + l_free(wsc_payload); + + if (r < 0) { + l_error("Probe Request WSC IE parse error %s (%i)", + strerror(-r), -r); + return; + } + + r = p2p_parse_probe_req(body, body_len, &p2p_info); + if (r < 0) { + if (r == -ENOENT) /* Not a P2P Probe Req, ignore */ + return; + + l_error("Probe Request P2P IE parse error %s (%i)", + strerror(-r), -r); + return; + } + + /* + * We don't currently have a use case for replying to Probe Requests + * except when waiting for a GO Negotiation Request from our target + * peer. + */ + + /* + * The peer's listen frequency may be different from ours. + * The Listen Channel attribute is optional but if neither + * it nor the Operating Channel are set then we have no way + * to contact that peer. Ignore such peers. + */ + if (p2p_info.listen_channel.country[0]) + channel = &p2p_info.listen_channel; + else if (p2p_info.operating_channel.country[0]) + channel = &p2p_info.operating_channel; + else + goto p2p_free; + + band = scan_oper_class_to_band((const uint8_t *) channel->country, + channel->oper_class); + frequency = scan_channel_to_freq(channel->channel_num, band); + if (!frequency) + goto p2p_free; + + bss = scan_bss_new_from_probe_req(mpdu, body, body_len, frequency, + rssi); + if (!bss) + goto p2p_free; + + bss->time_stamp = l_time_now(); + + if (p2p_peer_update_existing(bss, dev->peer_list, dev->peer_list)) + goto p2p_free; + + peer = l_new(struct p2p_peer, 1); + peer->dev = dev; + peer->bss = bss; + peer->name = l_strdup(wsc_info.device_name); + peer->primary_device_type = wsc_info.primary_device_type; + peer->group = !!(p2p_info.capability.group_caps & P2P_GROUP_CAP_GO); + /* + * The Device Info attribute is present conditionally so we can't get + * the Device Address from there. In theory only P2P Devices send + * out Probe Requests, not P2P GOs, so we assume the source address + * is the Device Address. + */ + peer->device_addr = bss->addr; + + if (!p2p_device_peer_add(dev, peer)) + p2p_peer_free(peer); + + /* + * TODO: check SSID/BSSID are wildcard values if present and + * reply with a Probe Response -- not useful in our current usage + * scenarios but required by the spec. + */ + +p2p_free: + p2p_clear_probe_req(&p2p_info); +} + static void p2p_device_discovery_start(struct p2p_device *dev) { - if (dev->scan_id) + if (dev->scan_timeout || dev->scan_id) return; + dev->scan_interval = 1; dev->chans_per_scan = CHANS_PER_SCAN_INITIAL; dev->scan_chan_idx = 0; @@ -493,13 +783,24 @@ static void p2p_device_discovery_start(struct p2p_device *dev) dev->listen_channel = channels_social[l_getrandom_uint32() % L_ARRAY_SIZE(channels_social)]; + frame_watch_add(dev->wdev_id, FRAME_GROUP_LISTEN, 0x0040, + (uint8_t *) "", 0, p2p_device_probe_cb, dev, NULL); + p2p_device_scan_start(dev); } static void p2p_device_discovery_stop(struct p2p_device *dev) { + dev->scan_interval = 0; + if (dev->scan_id) scan_cancel(dev->wdev_id, dev->scan_id); + + if (dev->scan_timeout) + l_timeout_remove(dev->scan_timeout); + + p2p_device_roc_cancel(dev); + frame_watch_group_remove(dev->wdev_id, FRAME_GROUP_LISTEN); } static void p2p_device_enable_cb(struct l_genl_msg *msg, void *user_data) @@ -576,6 +877,39 @@ static void p2p_device_start_stop(struct p2p_device *dev, } } +static void p2p_mlme_notify(struct l_genl_msg *msg, void *user_data) +{ + struct p2p_device *dev = user_data; + uint64_t wdev_id; + uint64_t cookie; + + if (nl80211_parse_attrs(msg, NL80211_ATTR_WDEV, &wdev_id, + NL80211_ATTR_COOKIE, &cookie, + NL80211_ATTR_UNSPEC) < 0 || + wdev_id != dev->wdev_id) + return; + + switch (l_genl_msg_get_command(msg)) { + case NL80211_CMD_REMAIN_ON_CHANNEL: + if (!dev->have_roc_cookie || cookie != dev->roc_cookie) + break; + + if (!dev->scan_timeout) + break; + + /* + * The Listen phase is actually starting here, update the + * timeout so we know more or less when it ends. + */ + l_debug("ROC started"); + l_timeout_modify_ms(dev->scan_timeout, dev->listen_duration); + break; + case NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL: + /* TODO */ + break; + } +} + #define P2P_SUPPORTED_METHODS ( \ WSC_CONFIGURATION_METHOD_LABEL | \ WSC_CONFIGURATION_METHOD_KEYPAD | \ @@ -746,6 +1080,10 @@ struct p2p_device *p2p_device_update_from_genl(struct l_genl_msg *msg, scan_wdev_add(dev->wdev_id); + if (!l_genl_family_register(dev->nl80211, NL80211_MULTICAST_GROUP_MLME, + p2p_mlme_notify, dev, NULL)) + l_error("Registering for MLME notifications failed"); + if (!l_dbus_object_add_interface(dbus_get_bus(), p2p_device_get_path(dev), IWD_P2P_INTERFACE, dev)) |