public inbox for iwd@lists.linux.dev
 help / color / mirror / Atom feed
* [PATCH v3 06/11] dpp: send presence announcements on StartEnrollee
@ 2021-12-15 17:58 James Prestwood
  0 siblings, 0 replies; only message in thread
From: James Prestwood @ 2021-12-15 17:58 UTC (permalink / raw)
  To: iwd 

[-- Attachment #1: Type: text/plain, Size: 9723 bytes --]

The presence procedure implemented is a far cry from what the spec
actually wants. There are two reason for this: a) the kernels offchannel
support is not at a level where it will work without rather annoying
work arounds, and b) doing the procedure outlined in the spec will
result in terrible discovery performance.

Because of this a simpler single channel announcement is done by default
and the full presence procedure is left out until/if it is needed.
---
 src/dpp.c | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 242 insertions(+)

v3:
 * Removed old headers left from previous patch set
 * memdup freqs since its on the stack
 * Few other minor fixes

diff --git a/src/dpp.c b/src/dpp.c
index dbab71c1..13a1ed75 100644
--- a/src/dpp.c
+++ b/src/dpp.c
@@ -24,15 +24,32 @@
 #include <config.h>
 #endif
 
+#include <errno.h>
+
 #include <ell/ell.h>
 
+#include "linux/nl80211.h"
+
 #include "src/dbus.h"
 #include "src/netdev.h"
 #include "src/module.h"
 #include "src/dpp-util.h"
 #include "src/band.h"
+#include "src/frame-xchg.h"
+#include "src/offchannel.h"
+#include "src/wiphy.h"
+#include "src/ie.h"
+#include "src/iwd.h"
+#include "src/util.h"
 
 static uint32_t netdev_watch;
+static struct l_genl_family *nl80211;
+static uint8_t broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+enum dpp_state {
+	DPP_STATE_NOTHING,
+	DPP_STATE_PRESENCE,
+};
 
 struct dpp_sm {
 	struct netdev *netdev;
@@ -48,14 +65,119 @@ struct dpp_sm {
 	size_t nonce_len;
 	struct l_ecc_scalar *boot_private;
 	struct l_ecc_point *boot_public;
+
+	enum dpp_state state;
+
+	uint32_t *freqs;
+	size_t freqs_len;
+	size_t freqs_idx;
+	uint32_t dwell;
+	uint32_t current_freq;
+	struct scan_freq_set *presence_list;
+
+	uint32_t offchannel_id;
 };
 
+static void dpp_send_frame_cb(struct l_genl_msg *msg, void *user_data)
+{
+	if (l_genl_msg_get_error(msg) < 0)
+		l_error("Error sending frame");
+}
+
+static void dpp_send_frame(uint64_t wdev_id, struct iovec *iov, size_t iov_len,
+			uint32_t freq)
+{
+	struct l_genl_msg *msg;
+
+	msg = l_genl_msg_new_sized(NL80211_CMD_FRAME, 512);
+	l_genl_msg_append_attr(msg, NL80211_ATTR_WDEV, 8, &wdev_id);
+	l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &freq);
+	l_genl_msg_append_attr(msg, NL80211_ATTR_OFFCHANNEL_TX_OK, 0, NULL);
+	l_genl_msg_append_attrv(msg, NL80211_ATTR_FRAME, iov, iov_len);
+
+	l_debug("Sending frame on frequency %u", freq);
+
+	if (!l_genl_family_send(nl80211, msg, dpp_send_frame_cb, NULL, NULL))
+		l_error("Could not send CMD_FRAME");
+}
+
+static size_t dpp_append_attr(uint8_t *to, enum dpp_attribute_type type,
+				void *attr, size_t attr_len)
+{
+	l_put_le16(type, to);
+	l_put_le16(attr_len, to + 2);
+	memcpy(to + 4, attr, attr_len);
+
+	return attr_len + 4;
+}
+
+static size_t dpp_build_header(const uint8_t *src, const uint8_t *dest,
+				enum dpp_frame_type type,
+				uint8_t buf[static 32])
+{
+	uint8_t *ptr = buf + 24;
+
+	memset(buf, 0, 32);
+
+	l_put_le16(0x00d0, buf);
+	memcpy(buf + 4, dest, 6);
+	memcpy(buf + 10, src, 6);
+	memcpy(buf + 16, broadcast, 6);
+
+	*ptr++ = 0x04;			/* Category: Public */
+	*ptr++ = 0x09;			/* Action: Vendor specific usage */
+	memcpy(ptr, wifi_alliance_oui, 3);
+	ptr += 3;
+	*ptr++ = 0x1a;			/* WiFi Alliance DPP OI type */
+	*ptr++ = 1;			/* Cryptosuite */
+	*ptr++ = type;
+
+	return ptr - buf;
+}
+
+static void dpp_presence_announce(struct dpp_sm *dpp)
+{
+	struct netdev *netdev = dpp->netdev;
+	uint8_t hdr[32];
+	uint8_t attrs[32 + 4];
+	uint8_t hash[32];
+	uint8_t *ptr = attrs;
+	const uint8_t *addr = netdev_get_address(netdev);
+	struct iovec iov[2];
+
+	iov[0].iov_len = dpp_build_header(addr, broadcast,
+					DPP_FRAME_PRESENCE_ANNOUNCEMENT, hdr);
+	iov[0].iov_base = hdr;
+
+	dpp_hash(L_CHECKSUM_SHA256, hash, 2, "chirp", strlen("chirp"),
+			dpp->pub_asn1, dpp->pub_asn1_len);
+
+	ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH, hash, 32);
+
+	iov[1].iov_base = attrs;
+	iov[1].iov_len = ptr - attrs;
+
+	l_debug("Sending presense annoucement on frequency %u and waiting %u",
+		dpp->current_freq, dpp->dwell);
+
+	dpp_send_frame(netdev_get_wdev_id(netdev), iov, 2, dpp->current_freq);
+}
+
+static void dpp_roc_started(void *user_data)
+{
+	struct dpp_sm *dpp = user_data;
+
+	dpp_presence_announce(dpp);
+}
+
 static void dpp_create(struct netdev *netdev)
 {
 	struct l_dbus *dbus = dbus_get_bus();
 	struct dpp_sm *dpp = l_new(struct dpp_sm, 1);
 
 	dpp->netdev = netdev;
+	dpp->state = DPP_STATE_NOTHING;
+	dpp->wdev_id = netdev_get_wdev_id(netdev);
 	dpp->curve = l_ecc_curve_from_ike_group(19);
 	dpp->key_len = l_ecc_curve_get_scalar_bytes(dpp->curve);
 	dpp->nonce_len = dpp_nonce_len_from_key_len(dpp->key_len);
@@ -93,12 +215,22 @@ static void dpp_reset(struct dpp_sm *dpp)
 		l_ecc_scalar_free(dpp->boot_private);
 		dpp->boot_private = NULL;
 	}
+
+	dpp->state = DPP_STATE_NOTHING;
 }
 
 static void dpp_free(struct dpp_sm *dpp)
 {
 	dpp_reset(dpp);
 
+	if (dpp->freqs)
+		l_free(dpp->freqs);
+
+	if (dpp->offchannel_id) {
+		offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
+		dpp->offchannel_id = 0;
+	}
+
 	l_free(dpp);
 }
 
@@ -124,6 +256,98 @@ static void dpp_netdev_watch(struct netdev *netdev,
 	}
 }
 
+static void dpp_presence_timeout(int error, void *user_data)
+{
+	struct dpp_sm *dpp = user_data;
+
+	if (dpp->state != DPP_STATE_PRESENCE) {
+		l_debug("DPP state changed, stopping presence announcements");
+		dpp->freqs_idx = 0;
+		return;
+	}
+
+	dpp->freqs_idx++;
+
+	if (dpp->freqs_idx >= dpp->freqs_len) {
+		l_debug("Max retries on presence announcements");
+		dpp->freqs_idx = 0;
+	}
+
+	dpp->current_freq = dpp->freqs[dpp->freqs_idx];
+
+	l_debug("Presence timeout, moving to next frequency %u, duration %u",
+			dpp->current_freq, dpp->dwell);
+
+	dpp->offchannel_id = offchannel_start(netdev_get_wdev_id(dpp->netdev),
+			dpp->current_freq, dpp->dwell, dpp_roc_started,
+			dpp, dpp_presence_timeout);
+}
+
+/*
+ * EasyConnect 2.0 - 6.2.2
+ */
+static uint32_t *dpp_add_default_channels(struct dpp_sm *dpp, size_t *len_out)
+{
+	struct wiphy *wiphy = wiphy_find_by_wdev(
+					netdev_get_wdev_id(dpp->netdev));
+	const struct scan_freq_set *list = wiphy_get_supported_freqs(wiphy);
+	uint32_t freq;
+
+	if (!dpp->presence_list)
+		dpp->presence_list = scan_freq_set_new();
+
+	scan_freq_set_add(dpp->presence_list, band_channel_to_freq(6,
+						BAND_FREQ_2_4_GHZ));
+	/*
+	 * "5 GHz: Channel 44 (5.220 GHz) if local regulations permit operation
+	 * only in the 5.150 - 5.250 GHz band and Channel 149 (5.745 GHz)
+	 * otherwise"
+	 */
+	freq = band_channel_to_freq(149, BAND_FREQ_5_GHZ);
+
+	if (scan_freq_set_contains(list, freq))
+		scan_freq_set_add(dpp->presence_list, freq);
+	else
+		scan_freq_set_add(dpp->presence_list,
+				band_channel_to_freq(44, BAND_FREQ_5_GHZ));
+
+	/* TODO: 60GHz: Channel 2 */
+
+	return scan_freq_set_to_fixed_array(dpp->presence_list, len_out);
+}
+
+/*
+ * TODO: There is an entire procedure defined in the spec where you increase
+ * the ROC timeout with each unsuccessful iteration of channels, wait on channel
+ * for long periods of time etc. Due to offchannel issues in the kernel this
+ * procedure is not being fully implemented. In reality doing this would result
+ * in quite terrible DPP performance anyways.
+ */
+static void dpp_start_presence_announcements(struct dpp_sm *dpp,
+						uint32_t *limit_freqs,
+						size_t limit_len)
+{
+	uint32_t max_roc = wiphy_get_max_roc_duration(
+					wiphy_find_by_wdev(dpp->wdev_id));
+
+	if (2000 < max_roc)
+		max_roc = 2000;
+
+	if (limit_freqs) {
+		dpp->freqs = l_memdup(limit_freqs, sizeof(uint32_t) * limit_len);
+		dpp->freqs_len = limit_len;
+	} else
+		dpp->freqs = dpp_add_default_channels(dpp, &dpp->freqs_len);
+
+	dpp->dwell = max_roc;
+	dpp->freqs_idx = 0;
+	dpp->current_freq = dpp->freqs[0];
+
+	dpp->offchannel_id = offchannel_start(netdev_get_wdev_id(dpp->netdev),
+			dpp->current_freq, dpp->dwell, dpp_roc_started,
+			dpp, dpp_presence_timeout);
+}
+
 static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus,
 						struct l_dbus_message *message,
 						void *user_data)
@@ -132,12 +356,24 @@ static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus,
 	uint32_t freq = band_channel_to_freq(6, BAND_FREQ_2_4_GHZ);
 	struct l_dbus_message *reply;
 
+	if (dpp->state != DPP_STATE_NOTHING)
+		return dbus_error_busy(message);
+
 	dpp->uri = dpp_generate_uri(dpp->pub_asn1, dpp->pub_asn1_len, 2,
 					netdev_get_address(dpp->netdev), &freq,
 					1, NULL, NULL);
 
+	dpp->state = DPP_STATE_PRESENCE;
+
 	l_debug("DPP Start Enrollee: %s", dpp->uri);
 
+	/*
+	 * Going off spec here. Select a single channel to send presence
+	 * announcements on. This will be advertised in the URI. The full
+	 * presence procedure can be implemented if it is ever needed.
+	 */
+	dpp_start_presence_announcements(dpp, &freq, 1);
+
 	reply = l_dbus_message_new_method_return(message);
 
 	l_dbus_message_set_arguments(reply, "s", dpp->uri);
@@ -173,6 +409,12 @@ static void dpp_destroy_interface(void *user_data)
 
 static int dpp_init(void)
 {
+	nl80211 = l_genl_family_new(iwd_get_genl(), NL80211_GENL_NAME);
+	if (!nl80211) {
+		l_error("Failed to obtain nl80211");
+		return -EIO;
+	}
+
 	netdev_watch = netdev_watch_add(dpp_netdev_watch, NULL, NULL);
 
 	l_dbus_register_interface(dbus_get_bus(), IWD_DPP_INTERFACE,
-- 
2.31.1

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2021-12-15 17:58 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-12-15 17:58 [PATCH v3 06/11] dpp: send presence announcements on StartEnrollee James Prestwood

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox