* [PATCH 0/2] network: add externally managed profiles
@ 2026-04-28 11:47 Matthias Kurz
2026-04-28 11:47 ` [PATCH 1/2] " Matthias Kurz
2026-04-28 11:47 ` [PATCH 2/2] doc: document " Matthias Kurz
0 siblings, 2 replies; 3+ messages in thread
From: Matthias Kurz @ 2026-04-28 11:47 UTC (permalink / raw)
To: iwd
Add [Settings].ExternallyManaged for network profiles whose contents are
owned by another network manager.
This is intended for setups where an external manager writes iwd profile
files so iwd can read the connection parameters, but the external manager
remains the source of truth for the file contents and secret storage
policy. In that case iwd should not rewrite the profile as a side effect
of normal network operation.
When ExternallyManaged=true is set, iwd still reads the profile and can use
it for connection setup, but skips profile writes during normal network
operation. Explicit profile removal through KnownNetwork.Forget remains
available.
This works for handcrafted profiles and for profiles mirrored by another
manager such as NetworkManager.
Matthias Kurz (2):
network: add externally managed profiles
doc: document externally managed profiles
src/iwd.network.rst | 8 ++++
src/knownnetworks.c | 8 ++++
src/knownnetworks.h | 2 +
src/network.c | 67 +++++++++++++++++++-------
src/storage.c | 17 ++++++-
unit/test-storage.c | 114 +++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 196 insertions(+), 20 deletions(-)
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 1/2] network: add externally managed profiles
2026-04-28 11:47 [PATCH 0/2] network: add externally managed profiles Matthias Kurz
@ 2026-04-28 11:47 ` Matthias Kurz
2026-04-28 11:47 ` [PATCH 2/2] doc: document " Matthias Kurz
1 sibling, 0 replies; 3+ messages in thread
From: Matthias Kurz @ 2026-04-28 11:47 UTC (permalink / raw)
To: iwd
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
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 2/2] doc: document externally managed profiles
2026-04-28 11:47 [PATCH 0/2] network: add externally managed profiles Matthias Kurz
2026-04-28 11:47 ` [PATCH 1/2] " Matthias Kurz
@ 2026-04-28 11:47 ` Matthias Kurz
1 sibling, 0 replies; 3+ messages in thread
From: Matthias Kurz @ 2026-04-28 11:47 UTC (permalink / raw)
To: iwd
Document [Settings].ExternallyManaged in iwd.network. The setting
marks profiles that IWD may read but must not update during normal
network operation, while explicit removal remains allowed.
---
src/iwd.network.rst | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/iwd.network.rst b/src/iwd.network.rst
index 892c6eb8..2b5c3f0e 100644
--- a/src/iwd.network.rst
+++ b/src/iwd.network.rst
@@ -174,6 +174,14 @@ The group ``[Settings]`` contains general settings.
Properly configured Access Points will typically update this setting
appropriately via Transition Disable indications. User customization
of this value is thus typically not required.
+ * - ExternallyManaged
+ - Values: true, **false**
+
+ If enabled, the profile contents are managed by an external
+ application. IWD will read the profile but will not update it as a
+ side effect of network operation, for example to persist connection
+ metadata or security material learned at runtime. Explicit profile
+ removal through the KnownNetwork Forget method is still allowed.
* - UseDefaultEccGroup
- Values: true, false
--
2.54.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-04-28 11:48 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-28 11:47 [PATCH 0/2] network: add externally managed profiles Matthias Kurz
2026-04-28 11:47 ` [PATCH 1/2] " Matthias Kurz
2026-04-28 11:47 ` [PATCH 2/2] doc: document " Matthias Kurz
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox