* [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings
@ 2021-04-26 23:34 Andrew Zaborowski
2021-04-26 23:34 ` [PATCH 2/3] doc: Clarify settings in iwd.ap(5) Andrew Zaborowski
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Andrew Zaborowski @ 2021-04-26 23:34 UTC (permalink / raw)
To: iwd
[-- Attachment #1: Type: text/plain, Size: 32682 bytes --]
Change ap_start to load all of the AP configuration from a struct
l_settings, moving the 6 or so parameters from struct ap_config members
to the l_settings groups and keys. This extends the ap profile concept
used for the DHCP settings. ap_start callers create the l_settings
object and fill the values in it or read the settings in from a file.
Since ap_setup_dhcp and ap_load_profile_and_dhcp no longer do the
settings file loading, they needed to be refactored and some issues were
fixed in their logic, e.g. l_dhcp_server_set_ip_address() was never
called when the "IP pool" was used. Also the IP pool was previously only
used if the ap->config->profile was NULL and this didn't match what the
docs said:
"If [IPv4].Address is not provided and no IP address is set on the
interface prior to calling StartProfile the IP pool will be used."
---
src/ap.c | 547 ++++++++++++++++++++++++++++++++----------------------
src/ap.h | 19 +-
src/p2p.c | 53 ++++--
3 files changed, 359 insertions(+), 260 deletions(-)
diff --git a/src/ap.c b/src/ap.c
index 46821c04..f55341e8 100644
--- a/src/ap.c
+++ b/src/ap.c
@@ -61,7 +61,15 @@ struct ap_state {
const struct ap_ops *ops;
ap_stopped_func_t stopped_func;
void *user_data;
- struct ap_config *config;
+
+ char ssid[33];
+ char passphrase[64];
+ uint8_t psk[32];
+ uint8_t channel;
+ uint8_t *authorized_macs;
+ unsigned int authorized_macs_num;
+ char wsc_name[33];
+ struct wsc_primary_device_type wsc_primary_device_type;
unsigned int ciphers;
enum ie_rsn_cipher_suite group_cipher;
@@ -228,24 +236,6 @@ static const char *broadcast_from_ip(const char *ip)
return inet_ntoa(ia);
}
-void ap_config_free(struct ap_config *config)
-{
- if (unlikely(!config))
- return;
-
- l_free(config->ssid);
-
- explicit_bzero(config->passphrase, sizeof(config->passphrase));
- explicit_bzero(config->psk, sizeof(config->psk));
- l_free(config->authorized_macs);
- l_free(config->wsc_name);
-
- if (config->profile)
- l_free(config->profile);
-
- l_free(config);
-}
-
static void ap_stop_handshake(struct sta_state *sta)
{
if (sta->sm) {
@@ -301,6 +291,14 @@ static void ap_reset(struct ap_state *ap)
{
struct netdev *netdev = ap->netdev;
+ explicit_bzero(ap->passphrase, sizeof(ap->passphrase));
+ explicit_bzero(ap->psk, sizeof(ap->psk));
+
+ if (ap->authorized_macs_num) {
+ l_free(ap->authorized_macs);
+ ap->authorized_macs_num = 0;
+ }
+
if (ap->mlme_watch)
l_genl_family_unregister(ap->nl80211, ap->mlme_watch);
@@ -317,9 +315,6 @@ static void ap_reset(struct ap_state *ap)
if (ap->rates)
l_uintset_free(ap->rates);
- ap_config_free(ap->config);
- ap->config = NULL;
-
l_queue_destroy(ap->wsc_pbc_probes, l_free);
ap->started = false;
@@ -666,25 +661,23 @@ static size_t ap_write_wsc_ie(struct ap_state *ap,
wsc_pr.response_type = WSC_RESPONSE_TYPE_AP;
memcpy(wsc_pr.uuid_e, ap->wsc_uuid_r, sizeof(wsc_pr.uuid_e));
- wsc_pr.primary_device_type =
- ap->config->wsc_primary_device_type;
+ wsc_pr.primary_device_type = ap->wsc_primary_device_type;
- if (ap->config->wsc_name)
- l_strlcpy(wsc_pr.device_name, ap->config->wsc_name,
+ if (ap->wsc_name[0] != '\0')
+ l_strlcpy(wsc_pr.device_name, ap->wsc_name,
sizeof(wsc_pr.device_name));
wsc_pr.config_methods =
WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
- if (ap->config->authorized_macs_num) {
- size_t len;
+ if (ap->authorized_macs_num) {
+ size_t len = ap->authorized_macs_num * 6;
- len = ap->config->authorized_macs_num * 6;
if (len > sizeof(wsc_pr.authorized_macs))
len = sizeof(wsc_pr.authorized_macs);
memcpy(wsc_pr.authorized_macs,
- ap->config->authorized_macs, len);
+ ap->authorized_macs, len);
}
wsc_data = wsc_build_probe_response(&wsc_pr, &wsc_data_size);
@@ -701,15 +694,14 @@ static size_t ap_write_wsc_ie(struct ap_state *ap,
WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
}
- if (ap->config->authorized_macs_num) {
- size_t len;
+ if (ap->authorized_macs_num) {
+ size_t len = ap->authorized_macs_num * 6;
- len = ap->config->authorized_macs_num * 6;
if (len > sizeof(wsc_beacon.authorized_macs))
len = sizeof(wsc_beacon.authorized_macs);
memcpy(wsc_beacon.authorized_macs,
- ap->config->authorized_macs, len);
+ ap->authorized_macs, len);
}
wsc_data = wsc_build_beacon(&wsc_beacon, &wsc_data_size);
@@ -831,8 +823,7 @@ static size_t ap_build_beacon_pr_head(struct ap_state *ap,
/* SSID IE */
ie_tlv_builder_next(&builder, IE_TYPE_SSID);
- ie_tlv_builder_set_data(&builder, ap->config->ssid,
- strlen(ap->config->ssid));
+ ie_tlv_builder_set_data(&builder, ap->ssid, strlen(ap->ssid));
/* Supported Rates IE */
ie_tlv_builder_next(&builder, IE_TYPE_SUPPORTED_RATES);
@@ -857,7 +848,7 @@ static size_t ap_build_beacon_pr_head(struct ap_state *ap,
/* DSSS Parameter Set IE for DSSS, HR, ERP and HT PHY rates */
ie_tlv_builder_next(&builder, IE_TYPE_DSSS_PARAMETER_SET);
- ie_tlv_builder_set_data(&builder, &ap->config->channel, 1);
+ ie_tlv_builder_set_data(&builder, &ap->channel, 1);
ie_tlv_builder_finalize(&builder, &len);
return 36 + len;
@@ -938,8 +929,7 @@ static uint32_t ap_send_mgmt_frame(struct ap_state *ap,
frame_xchg_cb_t callback,
void *user_data)
{
- uint32_t ch_freq = scan_channel_to_freq(ap->config->channel,
- SCAN_BAND_2_4_GHZ);
+ uint32_t ch_freq = scan_channel_to_freq(ap->channel, SCAN_BAND_2_4_GHZ);
uint64_t wdev_id = netdev_get_wdev_id(ap->netdev);
struct iovec iov[2];
@@ -958,8 +948,7 @@ static void ap_start_handshake(struct sta_state *sta, bool use_eapol_start,
struct ie_rsn_info rsn;
uint8_t bss_rsne[64];
- handshake_state_set_ssid(sta->hs, (void *) ap->config->ssid,
- strlen(ap->config->ssid));
+ handshake_state_set_ssid(sta->hs, (void *) ap->ssid, strlen(ap->ssid));
handshake_state_set_authenticator_address(sta->hs, own_addr);
handshake_state_set_supplicant_address(sta->hs, sta->addr);
@@ -1026,7 +1015,7 @@ static void ap_start_rsna(struct sta_state *sta, const uint8_t *gtk_rsc)
handshake_state_set_authenticator(sta->hs, true);
handshake_state_set_event_func(sta->hs, ap_handshake_event, sta);
handshake_state_set_supplicant_ie(sta->hs, sta->assoc_rsne);
- handshake_state_set_pmk(sta->hs, sta->ap->config->psk, 32);
+ handshake_state_set_pmk(sta->hs, sta->ap->psk, 32);
ap_start_handshake(sta, false, gtk_rsc);
}
@@ -1144,16 +1133,15 @@ static void ap_start_eap_wsc(struct sta_state *sta)
WSC_RF_BAND_2_4_GHZ);
l_settings_set_uint(sta->wsc_settings, "WSC", "ConfigurationMethods",
WSC_CONFIGURATION_METHOD_PUSH_BUTTON);
- l_settings_set_string(sta->wsc_settings, "WSC", "WPA2-SSID",
- ap->config->ssid);
+ l_settings_set_string(sta->wsc_settings, "WSC", "WPA2-SSID", ap->ssid);
- if (ap->config->passphrase[0])
+ if (ap->passphrase[0])
l_settings_set_string(sta->wsc_settings,
"WSC", "WPA2-Passphrase",
- ap->config->passphrase);
+ ap->passphrase);
else
l_settings_set_bytes(sta->wsc_settings,
- "WSC", "WPA2-PSK", ap->config->psk, 32);
+ "WSC", "WPA2-PSK", ap->psk, 32);
sta->hs = netdev_handshake_state_new(ap->netdev);
handshake_state_set_authenticator(sta->hs, true);
@@ -1611,8 +1599,8 @@ static void ap_assoc_reassoc(struct sta_state *sta, bool reassoc,
}
if (!rates || !ssid || (!wsc_data && !rsn) ||
- ssid_len != strlen(ap->config->ssid) ||
- memcmp(ssid, ap->config->ssid, ssid_len)) {
+ ssid_len != strlen(ap->ssid) ||
+ memcmp(ssid, ap->ssid, ssid_len)) {
err = MMPDU_REASON_CODE_INVALID_IE;
goto bad_frame;
}
@@ -1922,11 +1910,11 @@ static void ap_probe_req_cb(const struct mmpdu_header *hdr, const void *body,
if (!ssid || ssid_len == 0) /* Wildcard SSID */
match = true;
- else if (ssid && ssid_len == strlen(ap->config->ssid) && /* One SSID */
- !memcmp(ssid, ap->config->ssid, ssid_len))
+ else if (ssid && ssid_len == strlen(ap->ssid) && /* One SSID */
+ !memcmp(ssid, ap->ssid, ssid_len))
match = true;
else if (ssid && ssid_len == 7 && !memcmp(ssid, "DIRECT-", 7) &&
- !memcmp(ssid, ap->config->ssid, 7)) /* P2P wildcard */
+ !memcmp(ssid, ap->ssid, 7)) /* P2P wildcard */
match = true;
else if (ssid_list) { /* SSID List */
ie_tlv_iter_init(&iter, ssid_list, ssid_list_len);
@@ -1938,16 +1926,15 @@ static void ap_probe_req_cb(const struct mmpdu_header *hdr, const void *body,
ssid = (const char *) ie_tlv_iter_get_data(&iter);
ssid_len = ie_tlv_iter_get_length(&iter);
- if (ssid_len == strlen(ap->config->ssid) &&
- !memcmp(ssid, ap->config->ssid,
- ssid_len)) {
+ if (ssid_len == strlen(ap->ssid) &&
+ !memcmp(ssid, ap->ssid, ssid_len)) {
match = true;
break;
}
}
}
- if (dsss_channel != 0 && dsss_channel != ap->config->channel)
+ if (dsss_channel != 0 && dsss_channel != ap->channel)
match = false;
if (!match)
@@ -2053,15 +2040,15 @@ static void ap_auth_cb(const struct mmpdu_header *hdr, const void *body,
memcmp(hdr->address_3, bssid, 6))
return;
- if (ap->config->authorized_macs_num) {
+ if (ap->authorized_macs_num) {
unsigned int i;
- for (i = 0; i < ap->config->authorized_macs_num; i++)
- if (!memcmp(from, ap->config->authorized_macs + i * 6,
+ for (i = 0; i < ap->authorized_macs_num; i++)
+ if (!memcmp(from, ap->authorized_macs + i * 6,
6))
break;
- if (i == ap->config->authorized_macs_num) {
+ if (i == ap->authorized_macs_num) {
ap_auth_reply(ap, from, MMPDU_REASON_CODE_UNSPECIFIED);
return;
}
@@ -2210,8 +2197,7 @@ static struct l_genl_msg *ap_build_cmd_start_ap(struct ap_state *ap)
uint32_t nl_akm = CRYPTO_AKM_PSK;
uint32_t wpa_version = NL80211_WPA_VERSION_2;
uint32_t auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM;
- uint32_t ch_freq = scan_channel_to_freq(ap->config->channel,
- SCAN_BAND_2_4_GHZ);
+ uint32_t ch_freq = scan_channel_to_freq(ap->channel, SCAN_BAND_2_4_GHZ);
uint32_t ch_width = NL80211_CHAN_WIDTH_20;
unsigned int i;
@@ -2233,7 +2219,7 @@ static struct l_genl_msg *ap_build_cmd_start_ap(struct ap_state *ap)
return NULL;
cmd = l_genl_msg_new_sized(NL80211_CMD_START_AP, 256 + head_len +
- tail_len + strlen(ap->config->ssid));
+ tail_len + strlen(ap->ssid));
/* SET_BEACON attrs */
l_genl_msg_append_attr(cmd, NL80211_ATTR_BEACON_HEAD, head_len, head);
@@ -2247,8 +2233,8 @@ static struct l_genl_msg *ap_build_cmd_start_ap(struct ap_state *ap)
&ap->beacon_interval);
l_genl_msg_append_attr(cmd, NL80211_ATTR_DTIM_PERIOD, 4, &dtim_period);
l_genl_msg_append_attr(cmd, NL80211_ATTR_IFINDEX, 4, &ifindex);
- l_genl_msg_append_attr(cmd, NL80211_ATTR_SSID, strlen(ap->config->ssid),
- ap->config->ssid);
+ l_genl_msg_append_attr(cmd, NL80211_ATTR_SSID, strlen(ap->ssid),
+ ap->ssid);
l_genl_msg_append_attr(cmd, NL80211_ATTR_HIDDEN_SSID, 4,
&hidden_ssid);
l_genl_msg_append_attr(cmd, NL80211_ATTR_CIPHER_SUITES_PAIRWISE,
@@ -2493,7 +2479,8 @@ static void ap_mlme_notify(struct l_genl_msg *msg, void *user_data)
}
}
-static bool dhcp_load_settings(struct ap_state *ap, struct l_settings *settings)
+static bool dhcp_load_settings(struct ap_state *ap,
+ const struct l_settings *settings)
{
struct l_dhcp_server *server = ap->server;
struct in_addr ia;
@@ -2568,77 +2555,41 @@ error:
* 3. Address in IP pool.
*
* Returns: 0 if an IP was successfully selected and needs to be set
- * -EALREADY if an IP was already set on the interface
+ * -EALREADY if an IP was already set on the interface or
+ * IP configuration was not enabled,
* -EEXIST if the IP pool ran out of IP's
* -EINVAL if there was an error.
*/
-static int ap_setup_dhcp(struct ap_state *ap, struct l_settings *settings)
+static int ap_setup_dhcp(struct ap_state *ap, const struct l_settings *settings)
{
uint32_t ifindex = netdev_get_ifindex(ap->netdev);
struct in_addr ia;
uint32_t address = 0;
+ L_AUTO_FREE_VAR(char *, addr) = NULL;
int ret = -EINVAL;
- ap->server = l_dhcp_server_new(ifindex);
- if (!ap->server) {
- l_error("Failed to create DHCP server on %u", ifindex);
- return -EINVAL;;
- }
-
- if (getenv("IWD_DHCP_DEBUG"))
- l_dhcp_server_set_debug(ap->server, do_debug,
- "[DHCPv4 SERV] ", NULL);
-
/* get the current address if there is one */
if (l_net_get_address(ifindex, &ia) && ia.s_addr != 0)
address = ia.s_addr;
- if (ap->config->profile) {
- char *addr;
-
- addr = l_settings_get_string(settings, "IPv4", "Address");
- if (addr) {
- if (inet_pton(AF_INET, addr, &ia) < 0)
- goto free_addr;
-
- /* Is a matching address already set on interface? */
- if (ia.s_addr == address)
- ret = -EALREADY;
- else
- ret = 0;
- } else if (address) {
- /* No address in config, but interface has one set */
- addr = l_strdup(inet_ntoa(ia));
- ret = -EALREADY;
- } else
- goto free_addr;
-
- /* Set the remaining DHCP options in config file */
- if (!dhcp_load_settings(ap, settings)) {
- ret = -EINVAL;
- goto free_addr;
- }
-
- if (!l_dhcp_server_set_ip_address(ap->server, addr)) {
- ret = -EINVAL;
- goto free_addr;
- }
-
- ap->own_ip = l_strdup(addr);
-
-free_addr:
- l_free(addr);
+ addr = l_settings_get_string(settings, "IPv4", "Address");
+ if (addr) {
+ if (inet_pton(AF_INET, addr, &ia) < 0)
+ return -EINVAL;
- return ret;
+ /* Is a matching address already set on interface? */
+ if (ia.s_addr == address)
+ ret = -EALREADY;
+ else
+ ret = 0;
} else if (address) {
- /* No config file and address is already set */
- ap->own_ip = l_strdup(inet_ntoa(ia));
-
- return -EALREADY;
+ /* No address in config, but interface has one set */
+ addr = l_strdup(inet_ntoa(ia));
+ ret = -EALREADY;
} else if (pool.used) {
/* No config file, no address set. Use IP pool */
- ap->own_ip = ip_pool_get();
- if (!ap->own_ip) {
+ addr = ip_pool_get();
+ if (!addr) {
l_error("No more IP's in pool, cannot start AP on %u",
ifindex);
return -EEXIST;
@@ -2646,52 +2597,41 @@ free_addr:
ap->use_ip_pool = true;
ap->ip_prefix = pool.prefix;
+ ret = 0;
+ } else
+ return -EALREADY;
- return 0;
+ ap->server = l_dhcp_server_new(ifindex);
+ if (!ap->server) {
+ l_error("Failed to create DHCP server on %u", ifindex);
+ return -EINVAL;
}
- return -EINVAL;
+ if (getenv("IWD_DHCP_DEBUG"))
+ l_dhcp_server_set_debug(ap->server, do_debug,
+ "[DHCPv4 SERV] ", NULL);
+
+ /* Set the remaining DHCP options in config file */
+ if (!dhcp_load_settings(ap, settings))
+ return -EINVAL;
+
+ if (!l_dhcp_server_set_ip_address(ap->server, addr))
+ return -EINVAL;
+
+ ap->own_ip = l_steal_ptr(addr);
+ return ret;
}
-static int ap_load_profile_and_dhcp(struct ap_state *ap, bool *wait_dhcp)
+static int ap_load_dhcp(struct ap_state *ap, const struct l_settings *config,
+ bool *wait_dhcp)
{
uint32_t ifindex = netdev_get_ifindex(ap->netdev);
- char *passphrase;
- struct l_settings *settings = NULL;
int err = -EINVAL;
- /* No profile or DHCP settings */
- if (!ap->config->profile && !pool.used)
+ if (!l_settings_has_group(config, "IPv4"))
return 0;
- if (ap->config->profile) {
- settings = l_settings_new();
-
- if (!l_settings_load_from_file(settings, ap->config->profile))
- goto cleanup;
-
- passphrase = l_settings_get_string(settings, "Security",
- "Passphrase");
- if (passphrase) {
- if (strlen(passphrase) > 63) {
- l_error("[Security].Passphrase must not exceed "
- "63 characters");
- l_free(passphrase);
- goto cleanup;
- }
-
- strcpy(ap->config->passphrase, passphrase);
- l_free(passphrase);
- }
-
- if (!l_settings_has_group(settings, "IPv4")) {
- *wait_dhcp = false;
- err = 0;
- goto cleanup;
- }
- }
-
- err = ap_setup_dhcp(ap, settings);
+ err = ap_setup_dhcp(ap, config);
if (err == 0) {
/* Address change required */
ap->rtnl_add_cmd = l_rtnl_ifaddr4_add(rtnl, ifindex,
@@ -2701,8 +2641,7 @@ static int ap_load_profile_and_dhcp(struct ap_state *ap, bool *wait_dhcp)
if (!ap->rtnl_add_cmd) {
l_error("Failed to add IPv4 address");
- err = -EIO;
- goto cleanup;
+ return -EIO;
}
ap->cleanup_ip = true;
@@ -2715,24 +2654,201 @@ static int ap_load_profile_and_dhcp(struct ap_state *ap, bool *wait_dhcp)
err = 0;
}
-cleanup:
- l_settings_free(settings);
return err;
}
+static bool ap_load_psk(struct ap_state *ap, const struct l_settings *config)
+{
+ L_AUTO_FREE_VAR(char *, passphrase) =
+ l_settings_get_string(config, "Security", "Passphrase");
+ int err;
+
+ if (passphrase) {
+ if (strlen(passphrase) > 63) {
+ l_error("AP [Security].Passphrase must not exceed "
+ "63 characters");
+ return false;
+ }
+
+ strcpy(ap->passphrase, passphrase);
+ }
+
+ if (l_settings_has_key(config, "Security", "PreSharedKey")) {
+ size_t psk_len;
+ L_AUTO_FREE_VAR(uint8_t *, psk) = l_settings_get_bytes(config,
+ "Security",
+ "PreSharedKey",
+ &psk_len);
+
+ if (!psk || psk_len != 32) {
+ l_error("AP [Security].PreSharedKey must be a 32-byte "
+ "hexstring");
+ return false;
+ }
+
+ memcpy(ap->psk, psk, 32);
+ return true;
+ }
+
+ if (!passphrase) {
+ l_error("AP requires at least one of [Security].PreSharedKey, "
+ "[Security].Passphrase to be present");
+ return false;
+ }
+
+ err = crypto_psk_from_passphrase(passphrase, (uint8_t *) ap->ssid,
+ strlen(ap->ssid), ap->psk);
+ if (err < 0) {
+ l_error("AP couldn't generate the PSK from given "
+ "[Security].Passphrase value: %s (%i)",
+ strerror(-err), -err);
+ return false;
+ }
+
+ return true;
+}
+
+static int ap_load_config(struct ap_state *ap, const struct l_settings *config,
+ bool *out_wait_dhcp, bool *out_cck_rates)
+{
+ size_t len;
+ L_AUTO_FREE_VAR(char *, strval) = NULL;
+ int err;
+
+ strval = l_settings_get_string(config, "General", "SSID");
+ if (L_WARN_ON(!strval))
+ return -ENOMSG;
+
+ len = strlen(strval);
+ if (len < 1 || len > 32) {
+ l_error("AP SSID length outside the [1, 32] range");
+ return -EINVAL;
+ }
+
+ if (L_WARN_ON(!l_utf8_validate(strval, len, NULL)))
+ return -EINVAL;
+
+ strcpy(ap->ssid, strval);
+ l_free(l_steal_ptr(strval));
+
+ if (!ap_load_psk(ap, config))
+ return -EINVAL;
+
+ /*
+ * This loads DHCP settings either from the l_settings object or uses
+ * the defaults. wait_on_address will be set true if an address change
+ * is required.
+ */
+ err = ap_load_dhcp(ap, config, out_wait_dhcp);
+ if (err)
+ return err;
+
+ if (l_settings_has_key(config, "General", "Channel")) {
+ unsigned int uintval;
+
+ if (!l_settings_get_uint(config, "General", "Channel",
+ &uintval) ||
+ !scan_channel_to_freq(uintval,
+ SCAN_BAND_2_4_GHZ)) {
+ l_error("AP Channel value unsupported");
+ return -EINVAL;
+ }
+
+ ap->channel = uintval;
+ } else
+ /* TODO: Start a Get Survey to decide the channel */
+ ap->channel = 6;
+
+ strval = l_settings_get_string(config, "WSC", "DeviceName");
+ if (strval) {
+ len = strlen(strval);
+
+ if (len > 32) {
+ l_error("AP WSC name length outside the [1, 32] range");
+ return -EINVAL;
+ }
+
+ if (!l_utf8_validate(strval, len, NULL)) {
+ l_error("AP WSC name doesn't validate as UTF-8");
+ return -EINVAL;
+ }
+
+ strcpy(ap->wsc_name, strval);
+ l_free(l_steal_ptr(strval));
+ } else
+ memcpy(ap->wsc_name, ap->ssid, 33);
+
+ strval = l_settings_get_string(config, "WSC", "PrimaryDeviceType");
+ if (strval) {
+ bool ok = wsc_device_type_from_setting_str(strval,
+ &ap->wsc_primary_device_type);
+
+ if (!ok) {
+ l_error("AP [WSC].PrimaryDeviceType format unknown");
+ return -EINVAL;
+ }
+ } else {
+ /* Make ourselves a WFA standard PC by default */
+ ap->wsc_primary_device_type.category = 1;
+ memcpy(ap->wsc_primary_device_type.oui, wsc_wfa_oui, 3);
+ ap->wsc_primary_device_type.oui_type = 0x04;
+ ap->wsc_primary_device_type.subcategory = 1;
+ }
+
+ if (l_settings_get_value(config, "WSC", "AuthorizedMACs")) {
+ char **strvval;
+ unsigned int i;
+
+ strvval = l_settings_get_string_list(config, "WSC",
+ "AuthorizedMACs", ',');
+ if (!strvval) {
+ l_error("AP Authorized MACs list format wrong");
+ return -EINVAL;
+ }
+
+ ap->authorized_macs_num = l_strv_length(strvval);
+ ap->authorized_macs = l_malloc(ap->authorized_macs_num * 6);
+
+ for (i = 0; strvval[i]; i++)
+ if (!util_string_to_address(strvval[i],
+ ap->authorized_macs + i * 6)) {
+ l_error("Bad authorized MAC format: %s",
+ strvval[i]);
+ l_strfreev(strvval);
+ return -EINVAL;
+ }
+
+ l_strfreev(strvval);
+ }
+
+ if (l_settings_get_value(config, "General", "NoCCKRates")) {
+ bool boolval;
+
+ if (!l_settings_get_bool(config, "General", "NoCCKRates",
+ &boolval)) {
+ l_error("AP [General].NoCCKRates not a valid "
+ "boolean");
+ return -EINVAL;
+ }
+
+ *out_cck_rates = !boolval;
+ } else
+ *out_cck_rates = true;
+
+ return 0;
+}
+
/*
* Start a simple independent WPA2 AP on given netdev.
*
* @ops.handle_event is required and must react to AP_EVENT_START_FAILED
* and AP_EVENT_STOPPING by forgetting the ap_state struct, which is
* going to be freed automatically.
- * In the @config struct the .ssid field is required and one of
- * .passphrase and .psk must be filled in. All other fields are optional.
- * If @ap_start succeeds, the returned ap_state takes ownership of
- * @config and the caller shouldn't free it or any of the memory pointed
- * to by its members (which also can't be static).
+ * In the @config struct the [General].SSID key is required and one of
+ * [Security].Passphrase and [Security].PreSharedKey must be filled in.
+ * All other fields are optional.
*/
-struct ap_state *ap_start(struct netdev *netdev, struct ap_config *config,
+struct ap_state *ap_start(struct netdev *netdev, struct l_settings *config,
const struct ap_ops *ops, int *err_out,
void *user_data)
{
@@ -2742,57 +2858,37 @@ struct ap_state *ap_start(struct netdev *netdev, struct ap_config *config,
uint64_t wdev_id = netdev_get_wdev_id(netdev);
int err = -EINVAL;
bool wait_on_address = false;
+ bool cck_rates = true;
if (err_out)
*err_out = err;
- if (L_WARN_ON(!config->ssid))
- return NULL;
-
- if (L_WARN_ON(!config->profile && !config->passphrase[0] &&
- l_memeqzero(config->psk, sizeof(config->psk))))
+ if (L_WARN_ON(!config))
return NULL;
ap = l_new(struct ap_state, 1);
ap->nl80211 = l_genl_family_new(iwd_get_genl(), NL80211_GENL_NAME);
- ap->config = config;
ap->netdev = netdev;
ap->ops = ops;
ap->user_data = user_data;
- /*
- * This both loads a profile if required and loads DHCP settings either
- * by the profile itself or the IP pool (or does nothing in the case
- * of a profile-less configuration). wait_on_address will be set true
- * if an address change is required.
- */
- err = ap_load_profile_and_dhcp(ap, &wait_on_address);
- if (err < 0)
+ err = ap_load_config(ap, config, &wait_on_address, &cck_rates);
+ if (err)
goto error;
- if (!config->channel)
- /* TODO: Start a Get Survey to decide the channel */
- config->channel = 6;
-
- if (!config->wsc_name)
- config->wsc_name = l_strdup(config->ssid);
-
- if (!config->wsc_primary_device_type.category) {
- /* Make ourselves a WFA standard PC by default */
- config->wsc_primary_device_type.category = 1;
- memcpy(config->wsc_primary_device_type.oui, wsc_wfa_oui, 3);
- config->wsc_primary_device_type.oui_type = 0x04;
- config->wsc_primary_device_type.subcategory = 1;
- }
+ err = -EINVAL;
/* TODO: Add all ciphers supported by wiphy */
ap->ciphers = wiphy_select_cipher(wiphy, 0xffff);
ap->group_cipher = wiphy_select_cipher(wiphy, 0xffff);
ap->beacon_interval = 100;
+
+ wsc_uuid_from_addr(netdev_get_address(netdev), ap->wsc_uuid_r);
+
ap->rates = l_uintset_new(200);
/* TODO: Pick from actual supported rates */
- if (config->no_cck_rates) {
+ if (!cck_rates) {
l_uintset_put(ap->rates, 12); /* 6 Mbps*/
l_uintset_put(ap->rates, 18); /* 9 Mbps*/
l_uintset_put(ap->rates, 24); /* 12 Mbps*/
@@ -2807,15 +2903,6 @@ struct ap_state *ap_start(struct netdev *netdev, struct ap_config *config,
l_uintset_put(ap->rates, 22); /* 11 Mbps*/
}
- wsc_uuid_from_addr(netdev_get_address(netdev), ap->wsc_uuid_r);
-
- if (config->passphrase[0] &&
- crypto_psk_from_passphrase(config->passphrase,
- (uint8_t *) config->ssid,
- strlen(config->ssid),
- config->psk) < 0)
- goto error;
-
if (!frame_watch_add(wdev_id, 0, 0x0000 |
(MPDU_MANAGEMENT_SUBTYPE_ASSOCIATION_REQUEST << 4),
NULL, 0, ap_assoc_req_cb, ap, NULL))
@@ -2878,7 +2965,6 @@ error:
if (err_out)
*err_out = err;
- ap->config = NULL;
ap_reset(ap);
l_genl_family_free(ap->nl80211);
l_free(ap);
@@ -3128,7 +3214,7 @@ static struct l_dbus_message *ap_dbus_start(struct l_dbus *dbus,
{
struct ap_if_data *ap_if = user_data;
const char *ssid, *wpa2_passphrase;
- struct ap_config *config;
+ struct l_settings *config;
int err;
if (ap_if->ap && ap_if->ap->started)
@@ -3141,16 +3227,17 @@ static struct l_dbus_message *ap_dbus_start(struct l_dbus *dbus,
&ssid, &wpa2_passphrase))
return dbus_error_invalid_args(message);
- config = l_new(struct ap_config, 1);
- config->ssid = l_strdup(ssid);
- l_strlcpy(config->passphrase, wpa2_passphrase,
- sizeof(config->passphrase));
+ config = l_settings_new();
+ l_settings_set_string(config, "General", "SSID", ssid);
+ l_settings_set_string(config, "Security", "Passphrase",
+ wpa2_passphrase);
+ l_settings_add_group(config, "IPv4");
ap_if->ap = ap_start(ap_if->netdev, config, &ap_dbus_ops, &err, ap_if);
- if (!ap_if->ap) {
- ap_config_free(config);
+ l_settings_free(config);
+
+ if (!ap_if->ap)
return dbus_error_from_errno(err, message);
- }
ap_if->pending = l_dbus_message_ref(message);
return NULL;
@@ -3200,7 +3287,8 @@ static struct l_dbus_message *ap_dbus_start_profile(struct l_dbus *dbus,
{
struct ap_if_data *ap_if = user_data;
const char *ssid;
- struct ap_config *config;
+ struct l_settings *config;
+ char *config_path;
int err;
if (ap_if->ap && ap_if->ap->started)
@@ -3212,19 +3300,32 @@ static struct l_dbus_message *ap_dbus_start_profile(struct l_dbus *dbus,
if (!l_dbus_message_get_arguments(message, "s", &ssid))
return dbus_error_invalid_args(message);
- config = l_new(struct ap_config, 1);
- config->ssid = l_strdup(ssid);
- /* This tells ap_start to pull settings from a profile on disk */
- config->profile = storage_get_path("ap/%s.ap", ssid);
+ config = l_settings_new();
+ config_path = storage_get_path("ap/%s.ap", ssid);
+ err = l_settings_load_from_file(config, config_path) ? 0 : -EIO;
+ l_free(config_path);
+
+ if (err)
+ goto error;
+
+ /*
+ * Since [General].SSID is not an allowed setting for a profile on
+ * disk, we're free to potentially overwrite it with the SSID that
+ * the DBus user asked for.
+ */
+ l_settings_set_string(config, "General", "SSID", ssid);
ap_if->ap = ap_start(ap_if->netdev, config, &ap_dbus_ops, &err, ap_if);
- if (!ap_if->ap) {
- ap_config_free(config);
- return dbus_error_from_errno(err, message);
- }
+ l_settings_free(config);
+
+ if (!ap_if->ap)
+ goto error;
ap_if->pending = l_dbus_message_ref(message);
return NULL;
+
+error:
+ return dbus_error_from_errno(err, message);
}
static bool ap_dbus_property_get_started(struct l_dbus *dbus,
@@ -3247,11 +3348,11 @@ static bool ap_dbus_property_get_name(struct l_dbus *dbus,
{
struct ap_if_data *ap_if = user_data;
- if (!ap_if->ap || !ap_if->ap->config || !ap_if->ap->started)
+ if (!ap_if->ap || !ap_if->ap->started)
return false;
l_dbus_message_builder_append_basic(builder, 's',
- ap_if->ap->config->ssid);
+ ap_if->ap->ssid);
return true;
}
diff --git a/src/ap.h b/src/ap.h
index 729a6648..b0100f34 100644
--- a/src/ap.h
+++ b/src/ap.h
@@ -58,21 +58,6 @@ struct ap_event_registration_success_data {
typedef void (*ap_stopped_func_t)(void *user_data);
-struct ap_config {
- char *ssid;
- char passphrase[64];
- uint8_t psk[32];
- uint8_t channel;
- uint8_t *authorized_macs;
- unsigned int authorized_macs_num;
- char *wsc_name;
- struct wsc_primary_device_type wsc_primary_device_type;
-
- char *profile;
-
- bool no_cck_rates : 1;
-};
-
struct ap_ops {
void (*handle_event)(enum ap_event_type type, const void *event_data,
void *user_data);
@@ -100,9 +85,7 @@ struct ap_ops {
uint8_t *out_buf, void *user_data);
};
-void ap_config_free(struct ap_config *config);
-
-struct ap_state *ap_start(struct netdev *netdev, struct ap_config *config,
+struct ap_state *ap_start(struct netdev *netdev, struct l_settings *config,
const struct ap_ops *ops, int *err,
void *user_data);
void ap_shutdown(struct ap_state *ap, ap_stopped_func_t stopped_func,
diff --git a/src/p2p.c b/src/p2p.c
index 89d8c4ac..580eb53f 100644
--- a/src/p2p.c
+++ b/src/p2p.c
@@ -1187,13 +1187,32 @@ static const struct ap_ops p2p_go_ops = {
static void p2p_group_start(struct p2p_device *dev)
{
- struct ap_config *config = l_new(struct ap_config, 1);
-
- config->ssid = l_strdup(dev->go_group_id.ssid);
- config->channel = dev->listen_channel;
- config->wsc_name = l_strdup(dev->device_info.device_name);
- config->wsc_primary_device_type = dev->device_info.primary_device_type;
- config->no_cck_rates = true;
+ struct l_settings *config = l_settings_new();
+ uint8_t psk[32];
+ char *macs[2] = {};
+ const struct wsc_primary_device_type *pdt =
+ &dev->device_info.primary_device_type;
+ uint64_t pdt_uint =
+ ((uint64_t) pdt->category << 48) |
+ ((uint64_t) pdt->oui[0] << 40) |
+ ((uint64_t) pdt->oui[1] << 32) |
+ ((uint64_t) pdt->oui[2] << 24) |
+ ((uint64_t) pdt->oui_type << 16) |
+ pdt->subcategory;
+
+ l_settings_set_string(config, "General", "SSID", dev->go_group_id.ssid);
+ l_settings_set_uint(config, "General", "Channel", dev->listen_channel);
+ l_settings_set_bool(config, "General", "NoCCKRates", true);
+ l_settings_set_string(config, "WSC", "DeviceName",
+ dev->device_info.device_name);
+ l_settings_set_uint64(config, "WSC", "PrimaryDeviceType", pdt_uint);
+ /*
+ * Section 3.1.4.4: "It shall only allow association by the
+ * P2P Device that it is currently in Group Formation with."
+ */
+ macs[0] = (char *) util_address_to_string(
+ dev->conn_peer_interface_addr);
+ l_settings_set_string_list(config, "WSC", "AuthorizedMACs", macs, ',');
/*
* Section 3.2.1: "The Credentials for a P2P Group issued to a
@@ -1207,29 +1226,25 @@ static void p2p_group_start(struct p2p_device *dev)
* it's a little costlier to generate for the same cryptographic
* strength as the PSK.
*/
- if (!l_getrandom(config->psk, 32)) {
+ if (!l_getrandom(psk, 32)) {
l_error("l_getrandom() failed");
- ap_config_free(config);
+ l_settings_free(config);
p2p_connect_failed(dev);
return;
}
- /*
- * Section 3.1.4.4: "It shall only allow association by the
- * P2P Device that it is currently in Group Formation with."
- */
- config->authorized_macs = l_memdup(dev->conn_peer_interface_addr, 6);
- config->authorized_macs_num = 1;
+ l_settings_set_bytes(config, "Security", "PreSharedKey", psk, 32);
+
+ l_settings_add_group(config, "IPv4");
dev->capability.group_caps |= P2P_GROUP_CAP_GO;
dev->capability.group_caps |= P2P_GROUP_CAP_GROUP_FORMATION;
dev->group = ap_start(dev->conn_netdev, config, &p2p_go_ops, NULL, dev);
- if (!dev->group) {
- ap_config_free(config);
+ l_settings_free(config);
+
+ if (!dev->group)
p2p_connect_failed(dev);
- return;
- }
}
static void p2p_netconfig_event_handler(enum netconfig_event event,
--
2.27.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/3] doc: Clarify settings in iwd.ap(5)
2021-04-26 23:34 [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings Andrew Zaborowski
@ 2021-04-26 23:34 ` Andrew Zaborowski
2021-04-26 23:34 ` [PATCH 3/3] doc: Update iwd.ap(5) man page Andrew Zaborowski
2021-04-28 16:26 ` [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings Denis Kenzior
2 siblings, 0 replies; 4+ messages in thread
From: Andrew Zaborowski @ 2021-04-26 23:34 UTC (permalink / raw)
To: iwd
[-- Attachment #1: Type: text/plain, Size: 4849 bytes --]
Change some of the wording, add some references and more specific syntax
information (time units, IP list separator character, etc.)
---
src/iwd.ap.rst | 61 ++++++++++++++++++++++++++++++--------------------
1 file changed, 37 insertions(+), 24 deletions(-)
diff --git a/src/iwd.ap.rst b/src/iwd.ap.rst
index 52312855..d4534120 100644
--- a/src/iwd.ap.rst
+++ b/src/iwd.ap.rst
@@ -3,7 +3,7 @@
============
--------------------------------------
-Configuration of IWD access point
+Configuration of IWD access points
--------------------------------------
:Author: James Prestwood <prestwoj@gmail.com>
@@ -25,19 +25,21 @@ Description of access point provisioning files.
DESCRIPTION
===========
-An access point provisioning files define the configuration of an IWD access
+An access point provisioning file defines the configuration of an IWD access
point. These files live in *$STATE_DIRECTORY*/ap (/var/lib/iwd/ap by default).
+They are read when the `net.connman.iwd.AccessPoint.StartProfile(ssid)` DBus
+method is used.
FILE FORMAT
===========
-See *iwd.network* for details on the file format.
+See *iwd.network* for details on the settings file syntax.
SETTINGS
========
The settings are split into several categories. Each category has a group
-associated with it and described in separate tables below.
+associated with it and is described in the corresponding table below.
Network Authentication Settings
-------------------------------
@@ -54,13 +56,23 @@ configuration.
* - Passphrase
- 8..63 character string
- Passphrase to be used with this access point.
+ WPA-PSK Passphrase to be used with this access point.
-DHCP Server Settings
---------------------
+IPv4 Network Configuration
+--------------------------
-The group ``[IPv4]`` contains settings for IWD's built in DHCP server. All
-settings are optional.
+The group ``[IPv4]`` contains settings for IWD's built-in DHCP server. All
+settings are optional. They're used if network configuration was enabled as
+described in ``iwd.config(5)``. Omitting the ``[IPv4]`` group disables
+network configuration for this access point so if an all-defaults DHCP setup
+is desired, the group header line must still be present:
+
+.. code-block::
+
+ # Enable network configuration
+ [IPv4]
+
+ [other groups follow]
.. list-table::
:header-rows: 0
@@ -68,41 +80,42 @@ settings are optional.
:widths: 20 80
* - Address
- - IP Address of AP
+ - Local IP address
- Optional address for the DHCP server/access point. If provided this
- address will be set on the AP interface and any other DHCP server options
- will be derived from this address, unless they are overriden inside the
- AP profile. If [IPv4].Address is not provided and no IP address is set
- on the interface prior to calling StartProfile the IP pool will be used.
+ Optional local address pool for the access point and the DHCP server.
+ If provided this addresss will be set on the AP interface and any other
+ DHCP server options will be derived from it, unless they are overridden
+ by other settings below. If *Address* is not provided and no IP
+ address is set on the interface prior to calling `StartProfile`, the IP
+ pool defined by the global ``[General].APRanges`` setting will be used.
* - Gateway
- IP Address of gateway
- IP address of gateway. This will inherit from [IPv4].Address if not
- provided.
+ IP address of the gateway to be advertised by DHCP. This will fall back
+ to the local IP address if not provided.
* - Netmask
- - Netmask of DHCP server
+ - Local netmask of the AP
- This will be generated from [IPv4].Address if not provided.
+ This will be generated from ``[IPv4].Address`` if not provided.
* - DNSList
- - List of DNS servers
+ - List of DNS servers as a comma-separated IP address list
A list of DNS servers which will be advertised by the DHCP server. If
not provided no DNS servers will be sent by the DHCP server.
* - LeaseTime
- - Time limit for DHCP leases
+ - Time limit for DHCP leases in seconds
Override the default lease time.
* - IPRange
- - Range of IPs to use for the DHCP server
+ - Range of IPs given as two addresses separated by a comma
- If not provided a default range will be chosen which is the DHCP server
- address + 1 to 254.
+ From and to addresses of the range assigned to clients through DHCP.
+ If not provided the range from local address + 1 to .254 will be used.
SEE ALSO
========
--
2.27.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 3/3] doc: Update iwd.ap(5) man page
2021-04-26 23:34 [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings Andrew Zaborowski
2021-04-26 23:34 ` [PATCH 2/3] doc: Clarify settings in iwd.ap(5) Andrew Zaborowski
@ 2021-04-26 23:34 ` Andrew Zaborowski
2021-04-28 16:26 ` [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings Denis Kenzior
2 siblings, 0 replies; 4+ messages in thread
From: Andrew Zaborowski @ 2021-04-26 23:34 UTC (permalink / raw)
To: iwd
[-- Attachment #1: Type: text/plain, Size: 3035 bytes --]
Try to document some of the settings moved from struct ap_config to the
settings object now passed to ap_start(), skip the strictly internal-use
ones.
---
src/iwd.ap.rst | 60 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 59 insertions(+), 1 deletion(-)
diff --git a/src/iwd.ap.rst b/src/iwd.ap.rst
index d4534120..51622509 100644
--- a/src/iwd.ap.rst
+++ b/src/iwd.ap.rst
@@ -41,6 +41,23 @@ SETTINGS
The settings are split into several categories. Each category has a group
associated with it and is described in the corresponding table below.
+General Settings
+----------------
+
+The group ``[General]`` contains general AP configuration.
+
+.. list-table::
+ :header-rows: 0
+ :stub-columns: 0
+ :widths: 20 80
+ :align: left
+
+ * - Channel
+ - Channel number
+
+ Optional channel number for the access point to operate on. Only the
+ 2.4GHz-band channels are currently allowed.
+
Network Authentication Settings
-------------------------------
@@ -56,7 +73,14 @@ configuration.
* - Passphrase
- 8..63 character string
- WPA-PSK Passphrase to be used with this access point.
+ WPA-PSK Passphrase to be used with this access point. At least one of
+ *Passphrase*, *PreSharedKey* must be present.
+
+ * - PreSharedKey
+ - 64-character hex-string
+
+ Processed passphrase for this network in the form of a hex-encoded
+ 32-byte pre-shared key. Either this or *Passphrase* must be present.
IPv4 Network Configuration
--------------------------
@@ -117,6 +141,40 @@ is desired, the group header line must still be present:
From and to addresses of the range assigned to clients through DHCP.
If not provided the range from local address + 1 to .254 will be used.
+Wi-Fi Simple Configuration
+--------------------------
+
+The group ``[WSC]`` fine-tunes some Wi-Fi Simple Configuration local parameters
+(formerly known as WPS, Wi-Fi Protected Setup.)
+
+.. list-table::
+ :header-rows: 0
+ :stub-columns: 0
+ :widths: 20 80
+ :align: left
+
+ * - DeviceName
+ - 1..32-character string
+
+ Optional Device Name string for the AP to advertise as. Defaults to
+ the SSID.
+
+ * - PrimaryDeviceType
+ - Subcategory string or a 64-bit integer
+
+ Optional Primary Device Type for the AP to advertise as. Defaults to
+ PC computer. Can be specified as a lower-case WSC v2.0.5 subcategory
+ string or a 64-bit integer encoding, from MSB to LSB: the 16-bit
+ category ID, the 24-bit OUI, the 8-bit OUI type and the 16-bit
+ subcategory ID.
+
+ * - AuthorizedMACs
+ - Comma-separated MAC address list
+
+ Optional list of Authorized MAC addresses for the WSC registrar to
+ check on association. Each address is specified in the
+ colon-hexadecimal notation. Defaults to no MAC-based checks.
+
SEE ALSO
========
--
2.27.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings
2021-04-26 23:34 [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings Andrew Zaborowski
2021-04-26 23:34 ` [PATCH 2/3] doc: Clarify settings in iwd.ap(5) Andrew Zaborowski
2021-04-26 23:34 ` [PATCH 3/3] doc: Update iwd.ap(5) man page Andrew Zaborowski
@ 2021-04-28 16:26 ` Denis Kenzior
2 siblings, 0 replies; 4+ messages in thread
From: Denis Kenzior @ 2021-04-28 16:26 UTC (permalink / raw)
To: iwd
[-- Attachment #1: Type: text/plain, Size: 1190 bytes --]
Hi Andrew,
On 4/26/21 6:34 PM, Andrew Zaborowski wrote:
> Change ap_start to load all of the AP configuration from a struct
> l_settings, moving the 6 or so parameters from struct ap_config members
> to the l_settings groups and keys. This extends the ap profile concept
> used for the DHCP settings. ap_start callers create the l_settings
> object and fill the values in it or read the settings in from a file.
>
> Since ap_setup_dhcp and ap_load_profile_and_dhcp no longer do the
> settings file loading, they needed to be refactored and some issues were
> fixed in their logic, e.g. l_dhcp_server_set_ip_address() was never
> called when the "IP pool" was used. Also the IP pool was previously only
> used if the ap->config->profile was NULL and this didn't match what the
> docs said:
> "If [IPv4].Address is not provided and no IP address is set on the
> interface prior to calling StartProfile the IP pool will be used."
> ---
> src/ap.c | 547 ++++++++++++++++++++++++++++++++----------------------
> src/ap.h | 19 +-
> src/p2p.c | 53 ++++--
> 3 files changed, 359 insertions(+), 260 deletions(-)
>
All applied, thanks.
Regards,
-Denis
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2021-04-28 16:26 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-26 23:34 [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings Andrew Zaborowski
2021-04-26 23:34 ` [PATCH 2/3] doc: Clarify settings in iwd.ap(5) Andrew Zaborowski
2021-04-26 23:34 ` [PATCH 3/3] doc: Update iwd.ap(5) man page Andrew Zaborowski
2021-04-28 16:26 ` [PATCH 1/3] ap: Drop struct ap_config in favor of l_settings Denis Kenzior
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.