From bd114483b25dc55b5df43292e6878493e35a9bae Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 7 May 2021 10:13:26 +0200 Subject: [PATCH 1/6] core: add comments about assuming connections (cherry picked from commit bb37e308675c364740412851803159551342f29f) --- src/core/devices/nm-device.c | 10 ++++++++++ src/core/nm-manager.c | 36 +++++++++++++++++------------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 87e71ad328..b6bca443ee 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, diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index c1c8034a77..8fe9cdc8ad 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -2626,19 +2626,21 @@ 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. - */ + /* 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 generate a connection, but it failed for now. - * Give it a chance to match a connection from the state file. */ + /* The device can potentially assume connections, but at this + * time there are no addresses configured and we can't generate + * a connection. Allow the device to assume a connection indicated + * in the state file by UUID. */ only_by_uuid = TRUE; } else { + /* The device can't assume connections */ nm_device_assume_state_reset(device); _LOG2D(LOGD_DEVICE, device, @@ -2650,15 +2652,8 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat 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 +2687,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 +2762,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)", From a2f9e11942193ee35c4f18123305482c8de0f7b4 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jun 2021 16:31:18 +0200 Subject: [PATCH 2/6] manager: exit early in get_existing_connection() Later the function will become more complex. Add a check to exit early if the device can't assume connections. (cherry picked from commit b1644fa826bafe95dfe338e56541c59468ba763c) --- src/core/devices/nm-device.c | 2 +- src/core/devices/nm-device.h | 1 + src/core/nm-manager.c | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index b6bca443ee..7932df4b2f 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -7647,7 +7647,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-manager.c b/src/core/nm-manager.c index 8fe9cdc8ad..978cf09d0e 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); From 165daca280a29c8e7a85b848e66f7198b74a816f Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 7 May 2021 13:57:04 +0200 Subject: [PATCH 3/6] core: add 'keep-configuration' device configuration option Add a new 'keep-configuration' device option, set to 'yes' by default. When set to 'no', on startup NetworkManager ignores that the interface is pre-configured and doesn't try to keep its configuration. Instead, it activates one of the persistent connections. (cherry picked from commit df2fe157142f92847d4f14103775e3cf704cb3ca) --- man/NetworkManager.conf.xml | 38 +++++++++++++++++++++++ src/core/nm-config.c | 1 + src/core/nm-manager.c | 55 ++++++++++++++++++++------------- src/libnm-base/nm-config-base.h | 1 + 4 files changed, 74 insertions(+), 21 deletions(-) diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index 3ef5d3dc5a..819c051e20 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -1150,6 +1150,44 @@ 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. + + + wifi.scan-rand-mac-address diff --git a/src/core/nm-config.c b/src/core/nm-config.c index 94ec7f381b..981950ff70 100644 --- a/src/core/nm-config.c +++ b/src/core/nm-config.c @@ -878,6 +878,7 @@ 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_WIFI_BACKEND, NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS, NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK, diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index 978cf09d0e..d859c1eaae 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -2632,28 +2632,41 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat } } - /* 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 there are no addresses configured and we can't generate - * a connection. Allow the device to assume a connection indicated - * in the state file by UUID. */ - only_by_uuid = TRUE; - } else { - /* The device can't assume connections */ - 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); diff --git a/src/libnm-base/nm-config-base.h b/src/libnm-base/nm-config-base.h index 7a23875a43..9ef5d7f846 100644 --- a/src/libnm-base/nm-config-base.h +++ b/src/libnm-base/nm-config-base.h @@ -62,6 +62,7 @@ #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_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 \ From 1a0f7e1bd314e35e3c1b3b1434b8b3e92445941f Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 18 May 2021 10:31:08 +0200 Subject: [PATCH 4/6] initrd: add a 'origin' user tag to connections Introduce a user tag key to indicate where the connection comes from. It would also be possible to have this as a standard property (as 'connection.origin'), but since this information can be considered 'meta-data' I think the user setting is more appropriate. (cherry picked from commit 86f22ce8bacb663e41980ba2463c6ae51e513e89) --- src/libnm-core-intern/nm-core-internal.h | 5 +++++ src/nm-initrd-generator/nm-initrd-generator.c | 8 ++++++++ 2 files changed, 13 insertions(+) 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/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; From 7ca6e9d6870c5e6057483c0d043389182d83d184 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 3 Jun 2021 09:01:18 +0200 Subject: [PATCH 5/6] core: add nm_utils_connection_match_spec_list() Add function nm_utils_connection_match_spec_list() to check whether a connection matches a spec list. Also document the supported syntax in the man page. (cherry picked from commit 604c611cd0520f97a2052f4b2f4560d09de0e738) --- man/NetworkManager.conf.xml | 49 +++++++++++++++++ src/core/nm-core-utils.c | 106 ++++++++++++++++++++++++++++++++++++ src/core/nm-core-utils.h | 4 ++ 3 files changed, 159 insertions(+) diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index 819c051e20..2eba34dff2 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -1685,6 +1685,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/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, From 72f6edb01d595a62f08a54035f9cf208f69bdd9c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 3 Jun 2021 09:01:23 +0200 Subject: [PATCH 6/6] core: introduce device 'allowed-connections' property Configuration can have [device*] and [connection*] settings and both can include a 'match-device=' key, which is a list of device-specs. Introduce a new 'allowed-connections' key for [device*] sections, which specifies a list of connection-specs to indicate which connections can be activated on the device. With this, it becomes possible to have a device configuration like: [device-enp1s0] match-device=interface-name:enp1s0 allowed-connections=except:origin:nm-initrd-generator so that NM in the real root ignores connections created by the nm-initrd-generator, and starts activating a persistent connection. This requires also setting 'keep-configuration=no' to not generate an assumed connection. (cherry picked from commit bace14fe1f374db26e49e4e7d61d2fbfce4241cc) --- man/NetworkManager.conf.xml | 19 +++++++ src/core/devices/nm-device.c | 11 ++++ src/core/nm-config-data.c | 81 ++++++++++++++++++++++++---- src/core/nm-config-data.h | 4 ++ src/core/nm-config.c | 12 ++++- src/core/settings/nm-settings.c | 10 +++- src/libnm-base/nm-config-base.h | 1 + src/libnm-glib-aux/nm-shared-utils.h | 1 + 8 files changed, 127 insertions(+), 12 deletions(-) diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index 2eba34dff2..7eaa4cb2a1 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -1188,6 +1188,25 @@ managed=1 + + 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 diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 7932df4b2f..c83775c106 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -7505,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) { @@ -7581,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; } 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 981950ff70..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) { @@ -879,6 +887,7 @@ static const ConfigGroup config_groups[] = { 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, @@ -1060,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/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 9ef5d7f846..c413e867ea 100644 --- a/src/libnm-base/nm-config-base.h +++ b/src/libnm-base/nm-config-base.h @@ -63,6 +63,7 @@ #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-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,