diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml
index 3ef5d3dc5a..7eaa4cb2a1 100644
--- a/man/NetworkManager.conf.xml
+++ b/man/NetworkManager.conf.xml
@@ -1150,6 +1150,63 @@ managed=1
+
+ keep-configuration
+
+
+ On startup, NetworkManager tries to not interfere with
+ interfaces that are already configured. It does so by
+ generating a in-memory connection based on the interface
+ current configuration.
+
+
+ If this generated connection matches one of the existing
+ persistent connections, the persistent connection gets
+ activated. If there is no match, the generated
+ connection gets activated as "external", which means
+ that the connection is considered as active, but
+ NetworkManager doesn't actually touch the interface.
+
+
+ It is possible to disable this behavior by setting
+ keep-configuration to
+ no. In this way, on startup
+ NetworkManager always tries to activate the most
+ suitable persistent connection (the one with highest
+ autoconnect-priority or, in case of a tie, the one
+ activated most recently).
+
+
+ Note that when NetworkManager gets restarted, it stores
+ the previous state in
+ /run/NetworkManager; in particular
+ it saves the UUID of the connection that was previously
+ active so that it can be activated again after the
+ restart. Therefore,
+ keep-configuration does not have
+ any effect on service restart.
+
+
+
+
+ allowed-connections
+
+
+ A list of connections that can be activated on the
+ device. See for the
+ syntax to specify a connection. If this option is not
+ specified, all connections can be potentially activated
+ on the device, provided that the connection type and
+ other settings match.
+
+
+ A notable use case for this is to filter which
+ connections can be activated based on how they were
+ created; see the origin keyword in
+ .
+
+
+
wifi.scan-rand-mac-address
@@ -1647,6 +1704,55 @@ interface-name:vboxnet*,except:interface-name:vboxnet2
+
+
+ Connection List Format
+
+ Connections can be specified using the following format:
+
+
+
+
+ *
+ Matches every connection.
+
+
+ uuid:UUID
+ Match the connection by UUID, for example
+ "uuid:83037490-1d17-4986-a397-01f1db3a7fc2"
+
+
+ id=ID
+ Match the connection by name.
+
+
+ origin:ORIGIN
+ Match the connection by origin, stored in the
+ org.freedesktop.NetworkManager.origin tag of the user setting. For example, use
+ "except:origin:nm-initrd-generator" to forbid activation of connections created by the
+ initrd generator.
+
+
+ except:SPEC
+ Negative match of a connection. A negative match has higher priority then the positive
+ matches above.
+ If there is a list consisting only of negative matches, the behavior is the same as if there is also
+ match-all. That means, if none of all the negative matches is satisfied, the overall result is still a
+ positive match.
+
+
+ SPEC[,;]SPEC
+ Multiple specs can be concatenated with commas or semicolons. The order does not matter as
+ matches are either inclusive or negative (except:), with negative matches having higher
+ priority.
+ Backslash is supported to escape the separators ';' and ',', and to express special characters such as
+ newline ('\n'), tabulator ('\t'), whitespace ('\s') and backslash ('\\'). Whitespace is not a separator but
+ will be trimmed between two specs (unless escaped as '\s').
+
+
+
+
+
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 87e71ad328..c83775c106 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -7239,6 +7239,16 @@ _get_maybe_ipv6_disabled(NMDevice *self)
return (nm_platform_sysctl_get_int32(platform, NMP_SYSCTL_PATHID_ABSOLUTE(path), 0) == 0);
}
+/*
+ * nm_device_generate_connection:
+ *
+ * Generates a connection from an existing interface.
+ *
+ * If the device doesn't have an IP configuration and it's not a port or a
+ * controller, then no connection gets generated and the function returns
+ * %NULL. In such case, @maybe_later is set to %TRUE if a connection can be
+ * generated later when an IP address is assigned to the interface.
+ */
NMConnection *
nm_device_generate_connection(NMDevice *self,
NMDevice *master,
@@ -7495,6 +7505,8 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e
gs_free char * conn_iface = NULL;
NMDeviceClass * klass;
NMSettingMatch * s_match;
+ const GSList * specs;
+ gboolean has_match = FALSE;
klass = NM_DEVICE_GET_CLASS(self);
if (klass->connection_type_check_compatible) {
@@ -7571,6 +7583,15 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e
}
}
+ specs =
+ nm_config_data_get_device_allowed_connections_specs(NM_CONFIG_GET_DATA, self, &has_match);
+ if (has_match && !nm_utils_connection_match_spec_list(connection, specs, FALSE)) {
+ nm_utils_error_set_literal(error,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED,
+ "device configuration doesn't allow this connection");
+ return FALSE;
+ }
+
return TRUE;
}
@@ -7637,7 +7658,7 @@ nm_device_check_slave_connection_compatible(NMDevice *self, NMConnection *slave)
*
* Returns: %TRUE if the device is capable of assuming connections, %FALSE if not
*/
-static gboolean
+gboolean
nm_device_can_assume_connections(NMDevice *self)
{
return !!NM_DEVICE_GET_CLASS(self)->update_connection;
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index 53211c6f2f..f59b6fa845 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -527,6 +527,7 @@ nm_device_check_connection_compatible(NMDevice *device, NMConnection *connection
gboolean nm_device_check_slave_connection_compatible(NMDevice *device, NMConnection *connection);
+gboolean nm_device_can_assume_connections(NMDevice *self);
gboolean nm_device_unmanage_on_quit(NMDevice *self);
gboolean nm_device_spec_match_list(NMDevice *device, const GSList *specs);
diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c
index e127ea23bb..0cbff02771 100644
--- a/src/core/nm-config-data.c
+++ b/src/core/nm-config-data.c
@@ -26,6 +26,15 @@ typedef struct {
gboolean has;
GSList * spec;
} match_device;
+ union {
+ struct {
+ GSList * allowed_connections;
+ gboolean allowed_connections_has;
+ } device;
+ };
+ gboolean is_device;
+
+ /* List of key/value pairs in the section, sorted by key */
gsize lookup_len;
const NMUtilsNamedValue *lookup_idx;
} MatchSectionInfo;
@@ -1436,13 +1445,13 @@ _match_section_infos_lookup(const MatchSectionInfo *match_section_infos,
match = TRUE;
if (match) {
- *out_value = value;
+ NM_SET_OUT(out_value, value);
return match_section_infos;
}
}
out:
- *out_value = NULL;
+ NM_SET_OUT(out_value, NULL);
return NULL;
}
@@ -1538,6 +1547,37 @@ nm_config_data_get_device_config_int64(const NMConfigData *self,
return _nm_utils_ascii_str_to_int64(value, base, min, max, val_invalid);
}
+const GSList *
+nm_config_data_get_device_allowed_connections_specs(const NMConfigData *self,
+ NMDevice * device,
+ gboolean * has_match)
+{
+ const NMConfigDataPrivate *priv;
+ const MatchSectionInfo * connection_info;
+ const GSList * ret = NULL;
+
+ g_return_val_if_fail(self, NULL);
+
+ priv = NM_CONFIG_DATA_GET_PRIVATE(self);
+
+ connection_info = _match_section_infos_lookup(&priv->device_infos[0],
+ priv->keyfile,
+ NM_CONFIG_KEYFILE_KEY_DEVICE_ALLOWED_CONNECTIONS,
+ device,
+ NULL,
+ NULL,
+ NULL);
+
+ if (connection_info) {
+ nm_assert(connection_info->device.allowed_connections_has);
+ ret = connection_info->device.allowed_connections;
+ NM_SET_OUT(has_match, TRUE);
+ } else
+ NM_SET_OUT(has_match, FALSE);
+
+ return ret;
+}
+
const char *
nm_config_data_get_connection_default(const NMConfigData *self,
const char * property,
@@ -1610,7 +1650,10 @@ _match_section_info_get_str(const MatchSectionInfo *m, GKeyFile *keyfile, const
}
static void
-_match_section_info_init(MatchSectionInfo *connection_info, GKeyFile *keyfile, char *group)
+_match_section_info_init(MatchSectionInfo *connection_info,
+ GKeyFile * keyfile,
+ char * group,
+ gboolean is_device)
{
char ** keys = NULL;
gsize n_keys;
@@ -1629,6 +1672,14 @@ _match_section_info_init(MatchSectionInfo *connection_info, GKeyFile *keyfile, c
connection_info->stop_match =
nm_config_keyfile_get_boolean(keyfile, group, NM_CONFIG_KEYFILE_KEY_STOP_MATCH, FALSE);
+ if (is_device) {
+ connection_info->device.allowed_connections =
+ nm_config_get_match_spec(keyfile,
+ group,
+ NM_CONFIG_KEYFILE_KEY_DEVICE_ALLOWED_CONNECTIONS,
+ &connection_info->device.allowed_connections_has);
+ }
+
keys = g_key_file_get_keys(keyfile, group, &n_keys, NULL);
nm_utils_strv_sort(keys, n_keys);
@@ -1680,9 +1731,13 @@ _match_section_infos_free(MatchSectionInfo *match_section_infos)
if (!match_section_infos)
return;
+
for (m = match_section_infos; m->group_name; m++) {
g_free(m->group_name);
g_slist_free_full(m->match_device.spec, g_free);
+ if (m->is_device) {
+ g_slist_free_full(m->device.allowed_connections, g_free);
+ }
for (i = 0; i < m->lookup_len; i++) {
g_free(m->lookup_idx[i].name_mutable);
g_free(m->lookup_idx[i].value_str_mutable);
@@ -1693,12 +1748,16 @@ _match_section_infos_free(MatchSectionInfo *match_section_infos)
}
static MatchSectionInfo *
-_match_section_infos_construct(GKeyFile *keyfile, const char *prefix)
+_match_section_infos_construct(GKeyFile *keyfile, gboolean is_device)
{
char ** groups;
gsize i, j, ngroups;
char * connection_tag = NULL;
MatchSectionInfo *match_section_infos = NULL;
+ const char * prefix;
+
+ prefix =
+ is_device ? NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE : NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION;
/* get the list of existing [connection.\+]/[device.\+] sections.
*
@@ -1730,13 +1789,17 @@ _match_section_infos_construct(GKeyFile *keyfile, const char *prefix)
}
match_section_infos = g_new0(MatchSectionInfo, ngroups + 1 + (connection_tag ? 1 : 0));
+ match_section_infos->is_device = is_device;
for (i = 0; i < ngroups; i++) {
/* pass ownership of @group on... */
- _match_section_info_init(&match_section_infos[i], keyfile, groups[ngroups - i - 1]);
+ _match_section_info_init(&match_section_infos[i],
+ keyfile,
+ groups[ngroups - i - 1],
+ is_device);
}
if (connection_tag) {
/* pass ownership of @connection_tag on... */
- _match_section_info_init(&match_section_infos[i], keyfile, connection_tag);
+ _match_section_info_init(&match_section_infos[i], keyfile, connection_tag, is_device);
}
g_free(groups);
@@ -1950,10 +2013,8 @@ constructed(GObject *object)
priv->keyfile = _merge_keyfiles(priv->keyfile_user, priv->keyfile_intern);
- priv->connection_infos =
- _match_section_infos_construct(priv->keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
- priv->device_infos =
- _match_section_infos_construct(priv->keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
+ priv->connection_infos = _match_section_infos_construct(priv->keyfile, FALSE);
+ priv->device_infos = _match_section_infos_construct(priv->keyfile, TRUE);
priv->connectivity.enabled =
nm_config_keyfile_get_boolean(priv->keyfile,
diff --git a/src/core/nm-config-data.h b/src/core/nm-config-data.h
index 42d69a8b92..fa58d869c7 100644
--- a/src/core/nm-config-data.h
+++ b/src/core/nm-config-data.h
@@ -243,6 +243,10 @@ gint64 nm_config_data_get_device_config_int64(const NMConfigData *self,
gint64 val_no_match,
gint64 val_invalid);
+const GSList *nm_config_data_get_device_allowed_connections_specs(const NMConfigData *self,
+ NMDevice * device,
+ gboolean * has_match);
+
char ** nm_config_data_get_groups(const NMConfigData *self);
char ** nm_config_data_get_keys(const NMConfigData *self, const char *group);
gboolean nm_config_data_is_intern_atomic_group(const NMConfigData *self, const char *group);
diff --git a/src/core/nm-config.c b/src/core/nm-config.c
index 94ec7f381b..817368d1cd 100644
--- a/src/core/nm-config.c
+++ b/src/core/nm-config.c
@@ -796,6 +796,7 @@ static gboolean
_setting_is_device_spec(const char *group, const char *key)
{
#define _IS(group_v, key_v) (nm_streq(group, "" group_v "") && nm_streq(key, "" key_v ""))
+
return _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT)
|| _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER)
|| _IS(NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY)
@@ -806,6 +807,13 @@ _setting_is_device_spec(const char *group, const char *key)
&& nm_streq(key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE));
}
+static gboolean
+_setting_is_connection_spec(const char *group, const char *key)
+{
+ return NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE)
+ && nm_streq(key, NM_CONFIG_KEYFILE_KEY_DEVICE_ALLOWED_CONNECTIONS);
+}
+
static gboolean
_setting_is_string_list(const char *group, const char *key)
{
@@ -878,6 +886,8 @@ static const ConfigGroup config_groups[] = {
NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER,
NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
+ NM_CONFIG_KEYFILE_KEY_DEVICE_KEEP_CONFIGURATION,
+ NM_CONFIG_KEYFILE_KEY_DEVICE_ALLOWED_CONNECTIONS,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK,
@@ -1059,7 +1069,8 @@ read_config(GKeyFile * keyfile,
is_string_list = _setting_is_string_list(group, base_key);
- if (is_string_list || _setting_is_device_spec(group, base_key)) {
+ if (is_string_list || _setting_is_device_spec(group, base_key)
+ || _setting_is_connection_spec(group, base_key)) {
gs_unref_ptrarray GPtrArray *new = g_ptr_array_new_with_free_func(g_free);
char ** iter_val;
gs_strfreev char **old_val = NULL;
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 41d2256550..8fdc737960 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -1484,6 +1484,112 @@ nm_match_spec_device(const GSList *specs,
return _match_result(has_except, has_not_except, has_match, has_match_except);
}
+typedef struct {
+ const char *uuid;
+ const char *id;
+ const char *origin;
+} MatchConnectionData;
+
+static gboolean
+match_connection_eval(const char *spec_str, const MatchConnectionData *match_data)
+{
+ if (spec_str[0] == '*' && spec_str[1] == '\0')
+ return TRUE;
+
+ if (_MATCH_CHECK(spec_str, "id:"))
+ return nm_streq0(spec_str, match_data->id);
+
+ if (_MATCH_CHECK(spec_str, "uuid:"))
+ return nm_streq0(spec_str, match_data->uuid);
+
+ if (_MATCH_CHECK(spec_str, "origin:"))
+ return nm_streq0(spec_str, match_data->origin);
+
+ return FALSE;
+}
+
+static NMMatchSpecMatchType
+match_spec_connection(const GSList *specs, const char *id, const char *uuid, const char *origin)
+{
+ const GSList * iter;
+ gboolean has_match = FALSE;
+ gboolean has_match_except = FALSE;
+ gboolean has_except = FALSE;
+ gboolean has_not_except = FALSE;
+ const char * spec_str;
+ const MatchConnectionData match_data = {
+ .id = nm_str_not_empty(id),
+ .uuid = nm_str_not_empty(uuid),
+ .origin = nm_str_not_empty(origin),
+ };
+
+ if (!specs)
+ return NM_MATCH_SPEC_NO_MATCH;
+
+ for (iter = specs; iter; iter = iter->next) {
+ gboolean except;
+
+ spec_str = iter->data;
+
+ if (!spec_str || !*spec_str)
+ continue;
+
+ spec_str = match_except(spec_str, &except);
+
+ if (except)
+ has_except = TRUE;
+ else
+ has_not_except = TRUE;
+
+ if ((except && has_match_except) || (!except && has_match)) {
+ /* evaluating the match does not give new information. Skip it. */
+ continue;
+ }
+
+ if (!match_connection_eval(spec_str, &match_data))
+ continue;
+
+ if (except)
+ has_match_except = TRUE;
+ else
+ has_match = TRUE;
+ }
+
+ return _match_result(has_except, has_not_except, has_match, has_match_except);
+}
+
+int
+nm_utils_connection_match_spec_list(NMConnection *connection,
+ const GSList *specs,
+ int no_match_value)
+{
+ NMMatchSpecMatchType m;
+ NMSettingUser * s_user;
+ const char * origin = NULL;
+
+ if (!specs)
+ return no_match_value;
+
+ s_user = _nm_connection_get_setting(connection, NM_TYPE_SETTING_USER);
+ if (s_user)
+ origin = nm_setting_user_get_data(s_user, NM_USER_TAG_ORIGIN);
+
+ m = match_spec_connection(specs,
+ nm_connection_get_id(connection),
+ nm_connection_get_uuid(connection),
+ origin);
+ switch (m) {
+ case NM_MATCH_SPEC_MATCH:
+ return TRUE;
+ case NM_MATCH_SPEC_NEG_MATCH:
+ return FALSE;
+ case NM_MATCH_SPEC_NO_MATCH:
+ return no_match_value;
+ }
+ nm_assert_not_reached();
+ return no_match_value;
+}
+
static gboolean
match_config_eval(const char *str, const char *tag, guint cur_nm_version)
{
diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h
index bcb182b849..76c340d148 100644
--- a/src/core/nm-core-utils.h
+++ b/src/core/nm-core-utils.h
@@ -211,6 +211,10 @@ gboolean nm_utils_kernel_cmdline_match_check(const char *const *proc_cmdline,
guint num_patterns,
GError ** error);
+int nm_utils_connection_match_spec_list(NMConnection *connection,
+ const GSList *specs,
+ int no_match_value);
+
/*****************************************************************************/
gboolean nm_utils_connection_has_default_route(NMConnection *connection,
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index c1c8034a77..d859c1eaae 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -2595,6 +2595,12 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat
nm_device_capture_initial_config(device);
+ if (!nm_device_can_assume_connections(device)) {
+ nm_device_assume_state_reset(device);
+ _LOG2D(LOGD_DEVICE, device, "assume: device cannot assume connection");
+ return NULL;
+ }
+
if (ifindex) {
int master_ifindex = nm_platform_link_get_master(priv->platform, ifindex);
@@ -2626,39 +2632,47 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat
}
}
- /* The core of the API is nm_device_generate_connection() function and
- * update_connection() virtual method and the convenient connection_type
- * class attribute. Subclasses supporting the new API must have
- * update_connection() implemented, otherwise nm_device_generate_connection()
- * returns NULL.
- */
- connection = nm_device_generate_connection(device, master, &maybe_later, &gen_error);
- if (!connection) {
- if (maybe_later) {
- /* The device can generate a connection, but it failed for now.
- * Give it a chance to match a connection from the state file. */
- only_by_uuid = TRUE;
- } else {
- nm_device_assume_state_reset(device);
- _LOG2D(LOGD_DEVICE,
- device,
- "assume: cannot generate connection: %s",
- gen_error->message);
- return NULL;
+ if (nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA,
+ NM_CONFIG_KEYFILE_KEY_DEVICE_KEEP_CONFIGURATION,
+ device,
+ TRUE,
+ TRUE)) {
+ /* The core of the API is nm_device_generate_connection() function, based on
+ * update_connection() virtual method and the @connection_type_supported
+ * class attribute. Devices that support assuming existing connections must
+ * have update_connection() implemented, otherwise
+ * nm_device_generate_connection() returns NULL. */
+ connection = nm_device_generate_connection(device, master, &maybe_later, &gen_error);
+ if (!connection) {
+ if (maybe_later) {
+ /* The device can potentially assume connections, but at this
+ * time we can't generate a connection because no address is
+ * configured. Allow the device to assume a connection indicated
+ * in the state file by UUID. */
+ only_by_uuid = TRUE;
+ } else {
+ nm_device_assume_state_reset(device);
+ _LOG2D(LOGD_DEVICE,
+ device,
+ "assume: cannot generate connection: %s",
+ gen_error->message);
+ return NULL;
+ }
}
+ } else {
+ connection = NULL;
+ only_by_uuid = TRUE;
+ g_set_error(&gen_error,
+ NM_DEVICE_ERROR,
+ NM_DEVICE_ERROR_FAILED,
+ "device %s has 'keep-configuration=no'",
+ nm_device_get_iface(device));
}
nm_device_assume_state_get(device, &assume_state_guess_assume, &assume_state_connection_uuid);
- /* Now we need to compare the generated connection to each configured
- * connection. The comparison function is the heart of the connection
- * assumption implementation and it must compare the connections very
- * carefully to sort out various corner cases. Also, the comparison is
- * not entirely symmetric.
- *
- * When no configured connection matches the generated connection, we keep
- * the generated connection instead.
- */
+ /* If the device state file indicates a connection that was active before NM
+ * restarted, perform basic sanity checks on it. */
if (assume_state_connection_uuid
&& (connection_checked =
nm_settings_get_connection_by_uuid(priv->settings, assume_state_connection_uuid))
@@ -2692,8 +2706,9 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat
gs_free NMSettingsConnection **sett_conns = NULL;
guint len, i, j;
- /* the state file doesn't indicate a connection UUID to assume. Search the
- * persistent connections for a matching candidate. */
+ /* @assume_state_guess_assume=TRUE means this is the first start of NM
+ * and the state file contains no UUID. Search persistent connections
+ * for a matching candidate. */
sett_conns = nm_manager_get_activatable_connections(self, FALSE, FALSE, &len);
if (len > 0) {
for (i = 0, j = 0; i < len; i++) {
@@ -2766,6 +2781,8 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat
return matched;
}
+ /* When no configured connection matches the generated connection, we keep
+ * the generated connection instead. */
_LOG2D(LOGD_DEVICE,
device,
"assume: generated connection '%s' (%s)",
diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c
index a81f76b539..f9f98de7b1 100644
--- a/src/core/settings/nm-settings.c
+++ b/src/core/settings/nm-settings.c
@@ -498,6 +498,8 @@ _startup_complete_check_is_ready(NMSettings * self,
conn = nm_settings_connection_get_connection(sett_conn);
nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+ gs_free_error GError *error = NULL;
+
if (!nm_device_is_real(device))
continue;
@@ -508,7 +510,13 @@ _startup_complete_check_is_ready(NMSettings * self,
continue;
}
- if (!nm_device_check_connection_compatible(device, conn, NULL))
+ /* Check that device is compatible with the device. We are also happy
+ * with a device compatible but for which the connection is disallowed
+ * by NM configuration. */
+ if (!nm_device_check_connection_compatible(device, conn, &error)
+ && !g_error_matches(error,
+ NM_UTILS_ERROR,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED))
continue;
return TRUE;
diff --git a/src/libnm-base/nm-config-base.h b/src/libnm-base/nm-config-base.h
index 7a23875a43..c413e867ea 100644
--- a/src/libnm-base/nm-config-base.h
+++ b/src/libnm-base/nm-config-base.h
@@ -62,6 +62,8 @@
#define NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED "managed"
#define NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER "ignore-carrier"
#define NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS "sriov-num-vfs"
+#define NM_CONFIG_KEYFILE_KEY_DEVICE_KEEP_CONFIGURATION "keep-configuration"
+#define NM_CONFIG_KEYFILE_KEY_DEVICE_ALLOWED_CONNECTIONS "allowed-connections"
#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND "wifi.backend"
#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS "wifi.scan-rand-mac-address"
#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK \
diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h
index 2ef2520956..b1f084ed54 100644
--- a/src/libnm-core-intern/nm-core-internal.h
+++ b/src/libnm-core-intern/nm-core-internal.h
@@ -60,6 +60,7 @@
#include "nm-setting-team-port.h"
#include "nm-setting-team.h"
#include "nm-setting-tun.h"
+#include "nm-setting-user.h"
#include "nm-setting-veth.h"
#include "nm-setting-vlan.h"
#include "nm-setting-vpn.h"
@@ -79,6 +80,10 @@
#include "nm-vpn-editor-plugin.h"
#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#define NM_USER_TAG_ORIGIN "org.freedesktop.NetworkManager.origin"
+
+/*****************************************************************************/
+
/* NM_SETTING_COMPARE_FLAG_INFERRABLE: check whether a device-generated
* connection can be replaced by a already-defined connection. This flag only
* takes into account properties marked with the %NM_SETTING_PARAM_INFERRABLE
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index c0588eaf57..dcf37cd332 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -1258,6 +1258,7 @@ typedef enum {
NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED,
NM_UTILS_ERROR_SETTING_MISSING,
diff --git a/src/nm-initrd-generator/nm-initrd-generator.c b/src/nm-initrd-generator/nm-initrd-generator.c
index 490a4547d5..b78808b210 100644
--- a/src/nm-initrd-generator/nm-initrd-generator.c
+++ b/src/nm-initrd-generator/nm-initrd-generator.c
@@ -35,6 +35,14 @@ output_conn(gpointer key, gpointer value, gpointer user_data)
gs_free char * data = NULL;
gs_free_error GError *error = NULL;
gsize len;
+ NMSetting * setting;
+
+ setting = nm_setting_user_new();
+ nm_connection_add_setting(connection, setting);
+ nm_setting_user_set_data(NM_SETTING_USER(setting),
+ NM_USER_TAG_ORIGIN,
+ "nm-initrd-generator",
+ NULL);
if (!nm_connection_normalize(connection, NULL, NULL, &error))
goto err_out;