Wireless Daemon for Linux
 help / color / mirror / Atom feed
From: Matthias Kurz <m.kurz@irregular.at>
To: iwd@lists.linux.dev
Subject: [PATCH 1/2] network: add externally managed profiles
Date: Tue, 28 Apr 2026 13:47:56 +0200	[thread overview]
Message-ID: <20260428114809.75413-2-m.kurz@irregular.at> (raw)
In-Reply-To: <20260428114809.75413-1-m.kurz@irregular.at>

Add [Settings].ExternallyManaged for network profiles whose contents
are owned by another network manager. IWD still reads these profiles
for connection setup, but avoids updating them as a side effect of
network operation.

Skip connection timestamp/profile writes, security cache writes,
automatic profile encryption rewrites, and KnownNetwork AutoConnect
persistence for externally managed profiles. Explicit Forget remains
available through the existing removal path.

Add storage tests covering the existing encryption rewrite path and
the externally managed no-rewrite case.
---
 src/knownnetworks.c |   8 ++++
 src/knownnetworks.h |   2 +
 src/network.c       |  67 +++++++++++++++++++-------
 src/storage.c       |  17 ++++++-
 unit/test-storage.c | 114 +++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 188 insertions(+), 20 deletions(-)

diff --git a/src/knownnetworks.c b/src/knownnetworks.c
index 886f8145..471638e0 100644
--- a/src/knownnetworks.c
+++ b/src/knownnetworks.c
@@ -82,6 +82,11 @@ void __network_config_parse(const struct l_settings *settings,
 
 	config->always_random_addr = b;
 
+	if (!l_settings_get_bool(settings, NET_EXTERNALLY_MANAGED, &b))
+		b = false;
+
+	config->externally_managed = b;
+
 	value = l_settings_get_value(settings, NET_ADDRESS_OVERRIDE);
 	if (value) {
 		if (util_string_to_address(value, new_addr) &&
@@ -690,6 +695,9 @@ static struct l_dbus_message *known_network_property_set_autoconnect(
 	if (network->config.is_autoconnectable == autoconnect)
 		return l_dbus_message_new_method_return(message);
 
+	if (network->config.externally_managed)
+		return dbus_error_not_available(message);
+
 	settings = network->ops->open(network);
 	if (!settings)
 		return dbus_error_failed(message);
diff --git a/src/knownnetworks.h b/src/knownnetworks.h
index a117ded5..ddf72413 100644
--- a/src/knownnetworks.h
+++ b/src/knownnetworks.h
@@ -30,6 +30,7 @@
 #define NET_TRANSITION_DISABLE SETTINGS, "TransitionDisable"
 #define NET_TRANSITION_DISABLE_MODES SETTINGS, "DisabledTransitionModes"
 #define NET_USE_DEFAULT_ECC_GROUP SETTINGS, "UseDefaultEccGroup"
+#define NET_EXTERNALLY_MANAGED SETTINGS, "ExternallyManaged"
 
 enum security;
 struct scan_freq_set;
@@ -82,6 +83,7 @@ struct network_config {
 	bool have_transition_disable : 1;
 	uint8_t transition_disable;
 	enum known_network_ecc_group ecc_group;
+	bool externally_managed : 1;
 };
 
 struct network_info {
diff --git a/src/network.c b/src/network.c
index a5a2375a..e1115be2 100644
--- a/src/network.c
+++ b/src/network.c
@@ -109,6 +109,29 @@ static bool network_settings_load(struct network *network)
 	return network->settings != NULL;
 }
 
+static bool network_settings_are_externally_managed(
+					struct l_settings *settings)
+{
+	bool externally_managed;
+
+	if (!settings)
+		return false;
+
+	if (!l_settings_get_bool(settings, NET_EXTERNALLY_MANAGED,
+					&externally_managed))
+		return false;
+
+	return externally_managed;
+}
+
+static bool network_is_externally_managed(struct network *network)
+{
+	if (network->info)
+		return network->info->config.externally_managed;
+
+	return network_settings_are_externally_managed(network->settings);
+}
+
 static void network_reset_psk(struct network *network)
 {
 	if (network->psk)
@@ -180,25 +203,27 @@ void network_connected(struct network *network)
 	const char *ssid = network_get_ssid(network);
 	int err;
 
-	if (!network->info) {
-		/*
-		 * This is an open network seen for the first time:
-		 *
-		 * Write a settings file to keep track of the
-		 * last connected time.  This will also make iwd autoconnect
-		 * to this network in the future.
-		 */
-		if (!network->settings)
-			network->settings = l_settings_new();
+	if (!network_is_externally_managed(network)) {
+		if (!network->info) {
+			/*
+			 * This is an open network seen for the first time:
+			 *
+			 * Write a settings file to keep track of the
+			 * last connected time.  This will also make iwd
+			 * autoconnect to this network in the future.
+			 */
+			if (!network->settings)
+				network->settings = l_settings_new();
 
-		storage_network_sync(security, ssid, network->settings);
-	} else {
-		err = network_info_touch(network->info);
-		if (err < 0)
-			l_error("Error %i touching network config", err);
+			storage_network_sync(security, ssid, network->settings);
+		} else {
+			err = network_info_touch(network->info);
+			if (err < 0)
+				l_error("Error %i touching network config", err);
 
-		/* Syncs frequencies of already known network*/
-		known_network_frequency_sync(network->info);
+			/* Syncs frequencies of already known network*/
+			known_network_frequency_sync(network->info);
+		}
 	}
 
 	l_queue_foreach_remove(network->secrets,
@@ -758,6 +783,9 @@ void network_sync_settings(struct network *network)
 
 	network->sync_settings = false;
 
+	if (network_is_externally_managed(network))
+		return;
+
 	/*
 	 * Re-open the settings from Disk, in case they were updated
 	 * since we last opened them.
@@ -768,6 +796,11 @@ void network_sync_settings(struct network *network)
 		if (L_WARN_ON(!fs_settings))
 			return;
 
+		if (network_settings_are_externally_managed(fs_settings)) {
+			l_settings_free(fs_settings);
+			return;
+		}
+
 		network_settings_save(network, fs_settings);
 		info->ops->sync(info, fs_settings);
 		l_settings_free(fs_settings);
diff --git a/src/storage.c b/src/storage.c
index 2115a879..7158ce72 100644
--- a/src/storage.c
+++ b/src/storage.c
@@ -559,10 +559,22 @@ done:
 	return 0;
 }
 
+static bool storage_settings_are_externally_managed(
+					struct l_settings *settings)
+{
+	bool externally_managed;
+
+	if (!l_settings_get_bool(settings, "Settings", "ExternallyManaged",
+					&externally_managed))
+		return false;
+
+	return externally_managed;
+}
+
 /*
  * Decrypts a network profile (if needed). If profile encryption is enabled
  * and the profile is unencrypted it will be encrypted and written back to
- * the file system automatically.
+ * the file system automatically, unless [Settings].ExternallyManaged is true.
  *
  * 'name' is used for decryption and is used as part of the IV.  Callers
  * should provide a unique identifier here if available.  For example, the
@@ -581,6 +593,9 @@ bool storage_decrypt(struct l_settings *settings, const char *path,
 	if (!needs_encryption)
 		return true;
 
+	if (storage_settings_are_externally_managed(settings))
+		return true;
+
 	/* Profile never encrypted before. Encrypt and write to disk */
 	encrypted = __storage_encrypt(settings, name, &elen);
 	if (!encrypted) {
diff --git a/unit/test-storage.c b/unit/test-storage.c
index 9382efa6..92391bc1 100644
--- a/unit/test-storage.c
+++ b/unit/test-storage.c
@@ -25,6 +25,9 @@
 #endif
 
 #include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
 #include <ell/ell.h>
 
@@ -35,6 +38,49 @@ static bool aes_ctr_supported(const void *data)
 	return l_cipher_is_supported(L_CIPHER_AES_CTR);
 }
 
+static char *create_profile_file(const char *data)
+{
+	char path[] = "/tmp/iwd-storage-test-XXXXXX";
+	size_t len = strlen(data);
+	int fd;
+
+	fd = mkstemp(path);
+	assert(fd >= 0);
+
+	assert(write(fd, data, len) == (ssize_t) len);
+	assert(close(fd) == 0);
+
+	return l_strdup(path);
+}
+
+static char *read_profile_file(const char *path)
+{
+	char buffer[4096];
+	ssize_t len;
+
+	len = read_file(buffer, sizeof(buffer) - 1, "%s", path);
+	assert(len >= 0);
+
+	buffer[len] = '\0';
+
+	return l_strdup(buffer);
+}
+
+static struct l_settings *create_unencrypted_psk_settings(
+					bool externally_managed)
+{
+	struct l_settings *settings = l_settings_new();
+
+	if (externally_managed)
+		l_settings_set_bool(settings, "Settings",
+					"ExternallyManaged", true);
+
+	l_settings_set_string(settings, "Security", "Passphrase",
+				"test-password");
+
+	return settings;
+}
+
 static void test_short_encrypted_bytes(const void *data)
 {
 	struct l_settings *settings = l_settings_new();
@@ -54,13 +100,77 @@ static void test_short_encrypted_bytes(const void *data)
 	storage_exit();
 }
 
+static void test_decrypt_encrypts_profile(const void *data)
+{
+	static const char profile[] =
+		"[Security]\n"
+		"Passphrase=test-password\n";
+	struct l_settings *settings;
+	char *path;
+	char *contents;
+
+	storage_init((const uint8_t *)"abc123", 6);
+
+	settings = create_unencrypted_psk_settings(false);
+	path = create_profile_file(profile);
+
+	assert(storage_decrypt(settings, path, "mySSID"));
+
+	contents = read_profile_file(path);
+	assert(strstr(contents, "EncryptedSecurity="));
+	assert(!strstr(contents, "Passphrase=test-password"));
+
+	l_free(contents);
+	unlink(path);
+	l_free(path);
+	l_settings_free(settings);
+
+	storage_exit();
+}
+
+static void test_decrypt_externally_managed_profile(const void *data)
+{
+	static const char profile[] =
+		"[Settings]\n"
+		"ExternallyManaged=true\n"
+		"\n"
+		"[Security]\n"
+		"Passphrase=test-password\n";
+	struct l_settings *settings;
+	char *path;
+	char *contents;
+
+	storage_init((const uint8_t *)"abc123", 6);
+
+	settings = create_unencrypted_psk_settings(true);
+	path = create_profile_file(profile);
+
+	assert(storage_decrypt(settings, path, "mySSID"));
+
+	contents = read_profile_file(path);
+	assert(!strcmp(contents, profile));
+
+	l_free(contents);
+	unlink(path);
+	l_free(path);
+	l_settings_free(settings);
+
+	storage_exit();
+}
+
 int main(int argc, char *argv[])
 {
 	l_test_init(&argc, &argv);
 
 	l_test_add_func_precheck("/storage/profile encryption",
-						test_short_encrypted_bytes,
-						aes_ctr_supported, 0);
+							test_short_encrypted_bytes,
+							aes_ctr_supported, 0);
+	l_test_add_func_precheck("/storage/profile encryption write",
+							test_decrypt_encrypts_profile,
+							aes_ctr_supported, 0);
+	l_test_add_func_precheck("/storage/externally managed profile",
+					test_decrypt_externally_managed_profile,
+					aes_ctr_supported, 0);
 
 	return l_test_run();
 }
-- 
2.54.0


  reply	other threads:[~2026-04-28 11:48 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-28 11:47 [PATCH 0/2] network: add externally managed profiles Matthias Kurz
2026-04-28 11:47 ` Matthias Kurz [this message]
2026-04-28 11:47 ` [PATCH 2/2] doc: document " Matthias Kurz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260428114809.75413-2-m.kurz@irregular.at \
    --to=m.kurz@irregular.at \
    --cc=iwd@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox