3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-12-21 03:32:42 +01:00

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.
This commit is contained in:
Andrew Zaborowski 2020-04-25 11:09:24 +02:00 committed by Denis Kenzior
parent f0e3defa3e
commit 3d4725870d

344
src/p2p.c
View File

@ -70,13 +70,19 @@ struct p2p_device {
uint8_t listen_country[3]; uint8_t listen_country[3];
uint8_t listen_oper_class; uint8_t listen_oper_class;
uint32_t listen_channel; uint32_t listen_channel;
unsigned int scan_interval;
time_t next_scan_ts;
struct l_timeout *scan_timeout;
uint32_t scan_id; uint32_t scan_id;
unsigned int chans_per_scan; unsigned int chans_per_scan;
unsigned int scan_chan_idx; unsigned int scan_chan_idx;
uint64_t roc_cookie;
unsigned int listen_duration;
struct l_queue *discovery_users; struct l_queue *discovery_users;
struct l_queue *peer_list; struct l_queue *peer_list;
bool enabled : 1; bool enabled : 1;
bool have_roc_cookie : 1;
}; };
struct p2p_discovery_user { 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_social[] = { 1, 6, 11 };
static const int channels_scan_2_4_other[] = { 2, 3, 4, 5, 7, 8, 9, 10 }; 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) static bool p2p_device_match(const void *a, const void *b)
{ {
const struct p2p_device *dev = a; const struct p2p_device *dev = a;
@ -252,9 +263,156 @@ static void p2p_scan_destroy(void *user_data)
dev->scan_id = 0; dev->scan_id = 0;
} }
#define SCAN_INTERVAL_MAX 3
#define SCAN_INTERVAL_STEP 1
#define CHANS_PER_SCAN_INITIAL 2 #define CHANS_PER_SCAN_INITIAL 2
#define CHANS_PER_SCAN 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) static bool p2p_device_peer_add(struct p2p_device *dev, struct p2p_peer *peer)
{ {
if (!strlen(peer->name) || !l_utf8_validate( 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); l_queue_destroy(bss_list, NULL);
schedule: 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; return true;
} }
@ -475,11 +658,118 @@ static bool p2p_device_scan_start(struct p2p_device *dev)
return dev->scan_id != 0; return dev->scan_id != 0;
} }
static void p2p_device_discovery_start(struct p2p_device *dev) static void p2p_device_probe_cb(const struct mmpdu_header *mpdu,
const void *body, size_t body_len,
int rssi, void *user_data)
{ {
if (dev->scan_id) 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; 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_timeout || dev->scan_id)
return;
dev->scan_interval = 1;
dev->chans_per_scan = CHANS_PER_SCAN_INITIAL; dev->chans_per_scan = CHANS_PER_SCAN_INITIAL;
dev->scan_chan_idx = 0; 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() % dev->listen_channel = channels_social[l_getrandom_uint32() %
L_ARRAY_SIZE(channels_social)]; 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); p2p_device_scan_start(dev);
} }
static void p2p_device_discovery_stop(struct p2p_device *dev) static void p2p_device_discovery_stop(struct p2p_device *dev)
{ {
dev->scan_interval = 0;
if (dev->scan_id) if (dev->scan_id)
scan_cancel(dev->wdev_id, 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) 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 ( \ #define P2P_SUPPORTED_METHODS ( \
WSC_CONFIGURATION_METHOD_LABEL | \ WSC_CONFIGURATION_METHOD_LABEL | \
WSC_CONFIGURATION_METHOD_KEYPAD | \ 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); 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(), if (!l_dbus_object_add_interface(dbus_get_bus(),
p2p_device_get_path(dev), p2p_device_get_path(dev),
IWD_P2P_INTERFACE, dev)) IWD_P2P_INTERFACE, dev))