From 4e463df100c2963c77d64a7b85e3a335baf988d6 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 22 May 2018 20:03:44 +0200 Subject: [PATCH 1/6] libnm: allow nm_utils_hwaddr_valid() of length zero nm_utils_hwaddr_valid() is used for validating strings. It should not assert against calling it with an empty string "". That is just an invalid hwaddr. --- libnm-core/nm-utils.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index a6d6486fac..a7b896314f 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -3827,9 +3827,11 @@ nm_utils_hwaddr_valid (const char *asc, gssize length) if (!hwaddr_aton (asc, buf, length, &l)) return FALSE; return length == l; - } else if (length == -1) { + } else if (length == -1) return !!hwaddr_aton (asc, buf, sizeof (buf), &l); - } else + else if (length == 0) + return FALSE; + else g_return_val_if_reached (FALSE); } From 5f5f75ce0ec550d8f0b98ea5332643e20b1d8012 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 22 May 2018 20:08:09 +0200 Subject: [PATCH 2/6] libnm: expose _bin2str() helper function as internal API --- libnm-core/nm-core-internal.h | 1 + libnm-core/nm-utils.c | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index 663fbe70d2..cace423a7d 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -210,6 +210,7 @@ guint8 *_nm_utils_hwaddr_aton (const char *asc, gpointer buffer, gsize buffer_le const char *nm_utils_hwaddr_ntoa_buf (gconstpointer addr, gsize addr_len, gboolean upper_case, char *buf, gsize buf_len); char *_nm_utils_bin2str (gconstpointer addr, gsize length, gboolean upper_case); +void _nm_utils_bin2str_full (gconstpointer addr, gsize length, const char delimiter, gboolean upper_case, char *out); GSList * _nm_utils_hash_values_to_slist (GHashTable *hash); diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index a7b896314f..c5af2e33e1 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -3685,8 +3685,8 @@ nm_utils_hwaddr_aton (const char *asc, gpointer buffer, gsize length) return buffer; } -static void -_bin2str (gconstpointer addr, gsize length, const char delimiter, gboolean upper_case, char *out) +void +_nm_utils_bin2str_full (gconstpointer addr, gsize length, const char delimiter, gboolean upper_case, char *out) { const guint8 *in = addr; const char *LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef"; @@ -3736,7 +3736,7 @@ nm_utils_bin2hexstr (gconstpointer src, gsize len, int final_len) g_return_val_if_fail (final_len < 0 || (gsize) final_len < buflen, NULL); result = g_malloc (buflen); - _bin2str (src, len, '\0', FALSE, result); + _nm_utils_bin2str_full (src, len, '\0', FALSE, result); /* Cut converted key off at the correct length for this cipher type */ if (final_len >= 0 && (gsize) final_len < buflen) @@ -3763,7 +3763,7 @@ nm_utils_hwaddr_ntoa (gconstpointer addr, gsize length) g_return_val_if_fail (length > 0, g_strdup ("")); result = g_malloc (length * 3); - _bin2str (addr, length, ':', TRUE, result); + _nm_utils_bin2str_full (addr, length, ':', TRUE, result); return result; } @@ -3776,7 +3776,7 @@ nm_utils_hwaddr_ntoa_buf (gconstpointer addr, gsize addr_len, gboolean upper_cas if (buf_len < addr_len * 3) g_return_val_if_reached (NULL); - _bin2str (addr, addr_len, ':', upper_case, buf); + _nm_utils_bin2str_full (addr, addr_len, ':', upper_case, buf); return buf; } @@ -3799,7 +3799,7 @@ _nm_utils_bin2str (gconstpointer addr, gsize length, gboolean upper_case) g_return_val_if_fail (length > 0, g_strdup ("")); result = g_malloc (length * 3); - _bin2str (addr, length, ':', upper_case, result); + _nm_utils_bin2str_full (addr, length, ':', upper_case, result); return result; } From d0f1dc654e8e7fe76f1386f24240dd4ae8644a51 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 22 May 2018 16:08:50 +0200 Subject: [PATCH 3/6] core: ensure NUL terminated secret_key buffer The secret_key is binary random data, so one shouldn't ever use it as a NUL terminated string directly. Still, just ensure that the entire buffer is always NUL terminated. --- src/nm-core-utils.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index 4b266ed563..9697b9bc38 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -2820,7 +2820,7 @@ nm_utils_secret_key_read (gsize *out_key_len, GError **error) /* RFC7217 mandates the key SHOULD be at least 128 bits. * Let's use twice as much. */ key_len = 32; - secret_key = g_malloc (key_len); + secret_key = g_malloc (key_len + 1); if (!nm_utils_random_bytes (secret_key, key_len)) { g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, @@ -2829,6 +2829,10 @@ nm_utils_secret_key_read (gsize *out_key_len, GError **error) goto out; } + /* the secret-key is binary. Still, ensure that it's NULL terminated, just like + * g_file_set_contents() does. */ + secret_key[32] = '\0'; + key_mask = umask (0077); if (!g_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, error)) { g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key: "); From dbcb1d6d97c609d53dac4a86dc45d0e2595d8857 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 22 May 2018 16:25:06 +0200 Subject: [PATCH 4/6] core: let nm_utils_secret_key_read() handle failures internally and add nm_utils_secret_key_get() to cache the secret-key, to only obtain it once. nm_utils_secret_key_read() is not expected to fail. However, in case of an unexpected error, don't propagate the error to the caller, but instead handle it internally. That means, in case of error: - log a warning within nm_utils_secret_key_read() itself. - always return a generated secret-key. In case of error, the key won't be persisted (obviously). But the caller can ignore the error and just proceed with an in-memory key. Hence, also add nm_utils_secret_key_get() to cache the key. This way, we only try to actually generate/read the secret-key once. Since that might fail and return an in-memory key, we must for future invocations return the same key, without generating a new one. --- src/nm-core-utils.c | 144 ++++++++++++++++++++++++++++---------------- src/nm-core-utils.h | 3 +- 2 files changed, 95 insertions(+), 52 deletions(-) diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index 9697b9bc38..50df2b5c2e 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -2797,57 +2797,103 @@ nm_utils_file_get_contents (int dirfd, /*****************************************************************************/ -guint8 * -nm_utils_secret_key_read (gsize *out_key_len, GError **error) +static gboolean +_secret_key_read (guint8 **out_secret_key, + gsize *out_key_len) { - guint8 *secret_key = NULL; + guint8 *secret_key; + gboolean success = TRUE; gsize key_len; - - /* out_key_len is not optional, because without it you cannot safely - * access the returned memory. */ - *out_key_len = 0; + gs_free_error GError *error = NULL; /* Let's try to load a saved secret key first. */ - if (g_file_get_contents (NMSTATEDIR "/secret_key", (char **) &secret_key, &key_len, NULL)) { - if (key_len < 16) { - g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, - "Key is too short to be usable"); - key_len = 0; - } - } else { - mode_t key_mask; - - /* RFC7217 mandates the key SHOULD be at least 128 bits. - * Let's use twice as much. */ - key_len = 32; - secret_key = g_malloc (key_len + 1); - - if (!nm_utils_random_bytes (secret_key, key_len)) { - g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, - "Can't get random data to generate secret key"); - key_len = 0; + if (g_file_get_contents (NMSTATEDIR "/secret_key", (char **) &secret_key, &key_len, &error)) { + if (key_len >= 16) goto out; - } - /* the secret-key is binary. Still, ensure that it's NULL terminated, just like - * g_file_set_contents() does. */ - secret_key[32] = '\0'; - - key_mask = umask (0077); - if (!g_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, error)) { - g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key: "); - key_len = 0; + /* the secret key is borked. Log a warning, but proceed below to generate + * a new one. */ + nm_log_warn (LOGD_CORE, "secret-key: too short secret key in \"%s\" (generate new key)", NMSTATEDIR "/secret_key"); + nm_clear_g_free (&secret_key); + } else { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + nm_log_warn (LOGD_CORE, "secret-key: failure reading secret key in \"%s\": %s (generate new key)", + NMSTATEDIR "/secret_key", error->message); } - umask (key_mask); + g_clear_error (&error); + } + + /* RFC7217 mandates the key SHOULD be at least 128 bits. + * Let's use twice as much. */ + key_len = 32; + secret_key = g_malloc (key_len + 1); + + /* the secret-key is binary. Still, ensure that it's NULL terminated, just like + * g_file_set_contents() does. */ + secret_key[32] = '\0'; + + if (!nm_utils_random_bytes (secret_key, key_len)) { + nm_log_warn (LOGD_CORE, "secret-key: failure to generate good random data for secret-key (use non-persistent key)"); + success = FALSE; + goto out; + } + + if (!nm_utils_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, 0077, &error)) { + nm_log_warn (LOGD_CORE, "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)", + NMSTATEDIR "/secret_key", error->message); + success = FALSE; + goto out; } out: - if (key_len) { - *out_key_len = key_len; - return secret_key; + /* regardless of success or failue, we always return a secret-key. The + * caller may choose to ignore the error and proceed. */ + *out_key_len = key_len; + *out_secret_key = secret_key; + return success; +} + +typedef struct { + const guint8 *secret_key; + gsize key_len; + bool is_good:1; +} SecretKeyData; + +gboolean +nm_utils_secret_key_get (const guint8 **out_secret_key, + gsize *out_key_len) +{ + static volatile const SecretKeyData *secret_key_static; + const SecretKeyData *secret_key; + + secret_key = g_atomic_pointer_get (&secret_key_static); + if (G_UNLIKELY (!secret_key)) { + static gsize init_value = 0; + static SecretKeyData secret_key_data; + gboolean tmp_success; + gs_free guint8 *tmp_secret_key = NULL; + gsize tmp_key_len; + + tmp_success = _secret_key_read (&tmp_secret_key, &tmp_key_len); + if (g_once_init_enter (&init_value)) { + secret_key_data.secret_key = tmp_secret_key; + secret_key_data.key_len = tmp_key_len; + secret_key_data.is_good = tmp_success; + + if (g_atomic_pointer_compare_and_exchange (&secret_key_static, NULL, &secret_key_data)) { + g_steal_pointer (&tmp_secret_key); + secret_key = &secret_key_data; + } + + g_once_init_leave (&init_value, 1); + } + if (!secret_key) + secret_key = g_atomic_pointer_get (&secret_key_static); } - g_free (secret_key); - return NULL; + + *out_secret_key = secret_key->secret_key; + *out_key_len = secret_key->key_len; + return secret_key->is_good; } /*****************************************************************************/ @@ -3287,7 +3333,7 @@ _set_stable_privacy (NMUtilsStableType stable_type, const char *ifname, const char *network_id, guint32 dad_counter, - guint8 *secret_key, + const guint8 *secret_key, gsize key_len, GError **error) { @@ -3386,8 +3432,8 @@ nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type, guint32 dad_counter, GError **error) { - gs_free guint8 *secret_key = NULL; - gsize key_len = 0; + const guint8 *secret_key; + gsize key_len; g_return_val_if_fail (network_id, FALSE); @@ -3397,9 +3443,7 @@ nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type, return FALSE; } - secret_key = nm_utils_secret_key_read (&key_len, error); - if (!secret_key) - return FALSE; + nm_utils_secret_key_get (&secret_key, &key_len); return _set_stable_privacy (stable_type, addr, ifname, network_id, dad_counter, secret_key, key_len, error); @@ -3535,14 +3579,12 @@ nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type, const char *current_mac_address, const char *generate_mac_address_mask) { - gs_free guint8 *secret_key = NULL; - gsize key_len = 0; + const guint8 *secret_key; + gsize key_len; g_return_val_if_fail (stable_id, NULL); - secret_key = nm_utils_secret_key_read (&key_len, NULL); - if (!secret_key) - return NULL; + nm_utils_secret_key_get (&secret_key, &key_len); return _hw_addr_gen_stable_eth (stable_type, stable_id, diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h index a9a70038f9..ed66a7f0c8 100644 --- a/src/nm-core-utils.h +++ b/src/nm-core-utils.h @@ -283,7 +283,8 @@ gboolean nm_utils_file_set_contents (const gchar *filename, char *nm_utils_machine_id_read (void); gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_uuid); -guint8 *nm_utils_secret_key_read (gsize *out_key_len, GError **error); +gboolean nm_utils_secret_key_get (const guint8 **out_secret_key, + gsize *out_key_len); const char *nm_utils_get_boot_id (void); From d1a94a85b10bc22c39df30677285ff56b614d24b Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 23 May 2018 12:03:18 +0200 Subject: [PATCH 5/6] device: hash a per-host key for ipv4.dhcp-client-id=stable Otherwise, the generated client-id depends purely on the profile's stable-id. It means, the same profile (that is, either the same UUID or same stable-id) on different hosts will result in identical client-ids. That is clearly not desired. Hash a per-host secret-key as well. Note, that we don't hash the interface name. So, activating the profile on different interfaces, will still yield the same client-id. But also note, that commonly a profile is restricted to one device, via "connection.interface-name". Note that this is a change in behavior. However, "ipv4.dhcp-client-id=stable" was only added recently and not yet released. Fixes: 62a78639797244ef49f439ba2d8bd3332d31585b --- clients/common/settings-docs.h.in | 2 +- libnm-core/nm-setting-ip4-config.c | 2 +- src/devices/nm-device.c | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/clients/common/settings-docs.h.in b/clients/common/settings-docs.h.in index 1b94905b4d..2896158157 100644 --- a/clients/common/settings-docs.h.in +++ b/clients/common/settings-docs.h.in @@ -211,7 +211,7 @@ #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_TTL N_("The TTL to assign to tunneled packets. 0 is a special value meaning that packets inherit the TTL value.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_ADDRESSES N_("Array of IP addresses.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DAD_TIMEOUT N_("Timeout in milliseconds used to check for the presence of duplicate IP addresses on the network. If an address conflict is detected, the activation will fail. A zero value means that no duplicate address detection is performed, -1 means the default value (either configuration ipvx.dad-timeout override or zero). A value greater than zero is a timeout in milliseconds. The property is currently implemented only for IPv4.") -#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID N_("A string sent to the DHCP server to identify the local machine which the DHCP server may use to customize the DHCP lease and options. When the property is a hex string ('aa:bb:cc') it is interpreted as a binary client ID, in which case the first byte is assumed to be the 'type' field as per RFC 2132 section 9.14 and the remaining bytes may be an hardware address (e.g. '01:xx:xx:xx:xx:xx:xx' where 1 is the Ethernet ARP type and the rest is a MAC address). If the property is not a hex string it is considered as a non-hardware-address client ID and the 'type' field is set to 0. The special values \"mac\" and \"perm-mac\" are supported, which use the current or permanent MAC address of the device to generate a client identifier with type ethernet type (01). Currently, these options only work for ethernet type of links. The special value \"stable\" is supported to generate a type 0 client identifier based on the stable-id (see connection.stable-id). If unset, a globally configured default is used. If still unset, the client-id from the last lease is reused.") +#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID N_("A string sent to the DHCP server to identify the local machine which the DHCP server may use to customize the DHCP lease and options. When the property is a hex string ('aa:bb:cc') it is interpreted as a binary client ID, in which case the first byte is assumed to be the 'type' field as per RFC 2132 section 9.14 and the remaining bytes may be an hardware address (e.g. '01:xx:xx:xx:xx:xx:xx' where 1 is the Ethernet ARP type and the rest is a MAC address). If the property is not a hex string it is considered as a non-hardware-address client ID and the 'type' field is set to 0. The special values \"mac\" and \"perm-mac\" are supported, which use the current or permanent MAC address of the device to generate a client identifier with type ethernet type (01). Currently, these options only work for ethernet type of links. The special value \"stable\" is supported to generate a type 0 client identifier based on the stable-id (see connection.stable-id) and a per-host key. If unset, a globally configured default is used. If still unset, the client-id from the last lease is reused.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_FQDN N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified FQDN will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-hostname\" are mutually exclusive and cannot be set at the same time.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_HOSTNAME N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-fqdn\" are mutually exclusive and cannot be set at the same time.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer. If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.") diff --git a/libnm-core/nm-setting-ip4-config.c b/libnm-core/nm-setting-ip4-config.c index 6214656e0b..0c668d8c02 100644 --- a/libnm-core/nm-setting-ip4-config.c +++ b/libnm-core/nm-setting-ip4-config.c @@ -725,7 +725,7 @@ nm_setting_ip4_config_class_init (NMSettingIP4ConfigClass *ip4_class) * type of links. * * The special value "stable" is supported to generate a type 0 client identifier based - * on the stable-id (see connection.stable-id). + * on the stable-id (see connection.stable-id) and a per-host key. * * If unset, a globally configured default is used. If still unset, the * client-id from the last lease is reused. diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 3980758631..d26f23d0a0 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -7072,6 +7072,8 @@ dhcp4_get_client_id (NMDevice *self, NMConnection *connection) guint8 buf[20]; gsize buf_size; guint32 salted_header; + const guint8 *secret_key; + gsize secret_key_len; stable_id = _get_stable_id (self, connection, &stable_type); if (!stable_id) @@ -7079,10 +7081,13 @@ dhcp4_get_client_id (NMDevice *self, NMConnection *connection) salted_header = htonl (2011610591 + stable_type); + nm_utils_secret_key_get (&secret_key, &secret_key_len); + sum = g_checksum_new (G_CHECKSUM_SHA1); g_checksum_update (sum, (const guchar *) &salted_header, sizeof (salted_header)); - g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id)); + g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id) + 1); + g_checksum_update (sum, (const guchar *) secret_key, secret_key_len); buf_size = sizeof (buf); g_checksum_get_digest (sum, buf, &buf_size); From eb821ead150bcbf2e52d2ea17e2acabf2c1ecdb9 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 22 May 2018 18:35:43 +0200 Subject: [PATCH 6/6] all: add stable-id specifier "${DEVICE}" Add new stable-id specifier "${DEVICE}" to explicitly declare that the connection's identity differs per-device. Note that for settings like "ipv6.addr-gen-mode=stable" we already hash the interface's name. So, in combination with addr-gen-mode, using this specifier has no real use. But for example, we don't do that for "ipv4.dhcp-client-id=stable". Point being, in various context we possibly already include a per-device token into the generation algorithm. But that is not the case for all contexts and uses. Especially the DHCPv4 client identifier is supposed to differ between interfaces (according to RFC). We don't do that by default with "ipv4.dhcp-client-id=stable", but with "${DEVICE}" can can now be configured by the user. Note that the fact that the client-id is the same accross interfaces, is not a common problem, because profiles are usually restricted to one device via connection.interface-name. --- clients/common/settings-docs.h.in | 2 +- libnm-core/nm-setting-connection.c | 35 +++++++++++++++++------------- src/devices/nm-device.c | 3 ++- src/nm-core-utils.c | 5 ++++- src/nm-core-utils.h | 3 ++- src/tests/test-general.c | 2 +- 6 files changed, 30 insertions(+), 20 deletions(-) diff --git a/clients/common/settings-docs.h.in b/clients/common/settings-docs.h.in index 2896158157..06f96a4e1f 100644 --- a/clients/common/settings-docs.h.in +++ b/clients/common/settings-docs.h.in @@ -152,7 +152,7 @@ #define DESCRIBE_DOC_NM_SETTING_CONNECTION_READ_ONLY N_("FALSE if the connection can be modified using the provided settings service's D-Bus interface with the right privileges, or TRUE if the connection is read-only and cannot be modified.") #define DESCRIBE_DOC_NM_SETTING_CONNECTION_SECONDARIES N_("List of connection UUIDs that should be activated when the base connection itself is activated. Currently only VPN connections are supported.") #define DESCRIBE_DOC_NM_SETTING_CONNECTION_SLAVE_TYPE N_("Setting name of the device type of this slave's master connection (eg, \"bond\"), or NULL if this connection is not a slave.") -#define DESCRIBE_DOC_NM_SETTING_CONNECTION_STABLE_ID N_("Token to generate stable IDs for the connection. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable. Note that also the interface name of the activating connection and a per-host secret key is included into the address generation so that the same stable-id on different hosts/devices yields different addresses. If the value is unset, an ID unique for the connection is used. Specifying a stable-id allows multiple connections to generate the same addresses. Another use is to generate IDs at runtime via dynamic substitutions. The '$' character is treated special to perform dynamic substitutions at runtime. Currently supported are \"${CONNECTION}\", \"${BOOT}\", \"${RANDOM}\". These effectively create unique IDs per-connection, per-boot, or every time. Any unrecognized patterns following '$' are treated verbatim, however are reserved for future use. You are thus advised to avoid '$' or escape it as \"$$\". For example, set it to \"${CONNECTION}/${BOOT}\" to create a unique id for this connection that changes with every reboot. Note that two connections only use the same effective id if their stable-id is also identical before performing dynamic substitutions.") +#define DESCRIBE_DOC_NM_SETTING_CONNECTION_STABLE_ID N_("This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device's name is included, so that different interfaces yield different addresses. The '$' character is treated special to perform dynamic substitutions at runtime. Currently supported are \"${CONNECTION}\", \"${DEVICE}\", \"${BOOT}\", \"${RANDOM}\". These effectively create unique IDs per-connection, per-device, per-boot, or every time. Note that \"${DEVICE}\" corresponds the the interface name of the device. Any unrecognized patterns following '$' are treated verbatim, however are reserved for future use. You are thus advised to avoid '$' or escape it as \"$$\". For example, set it to \"${CONNECTION}-${BOOT}-${DEVICE}\" to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is similar to \"${CONNECTION}\" and uses a unique, fixed ID for the connection.") #define DESCRIBE_DOC_NM_SETTING_CONNECTION_TIMESTAMP N_("The time, in seconds since the Unix Epoch, that the connection was last _successfully_ fully activated. NetworkManager updates the connection timestamp periodically when the connection is active to ensure that an active connection has the latest timestamp. The property is only meant for reading (changes to this property will not be preserved).") #define DESCRIBE_DOC_NM_SETTING_CONNECTION_TYPE N_("Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, \"802-3-ethernet\" or \"802-11-wireless\" or \"bluetooth\", etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, \"vpn\" or \"bridge\", etc).") #define DESCRIBE_DOC_NM_SETTING_CONNECTION_UUID N_("A universally unique identifier for the connection, for example generated with libuuid. It should be assigned when the connection is created, and never changed as long as the connection still applies to the same network. For example, it should not be changed when the \"id\" property or NMSettingIP4Config changes, but might need to be re-created when the Wi-Fi SSID, mobile broadband network provider, or \"type\" property changes. The UUID must be in the format \"2815492f-7e56-435e-b2e9-246bd7cdc664\" (ie, contains only hexadecimal characters and \"-\").") diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c index 8e6cb87c6e..f8f1b017e6 100644 --- a/libnm-core/nm-setting-connection.c +++ b/libnm-core/nm-setting-connection.c @@ -1535,7 +1535,10 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class) /** * NMSettingConnection:stable-id: * - * Token to generate stable IDs for the connection. + * This represents the identity of the connection used for various purposes. + * It allows to configure multiple profiles to share the identity. Also, + * the stable-id can contain placeholders that are substituted dynamically and + * deterministically depending on the context. * * The stable-id is used for generating IPv6 stable private addresses * with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the @@ -1543,26 +1546,28 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class) * and wifi.cloned-mac-address=stable. It is also used as DHCP client * identifier with ipv4.dhcp-client-id=stable. * - * Note that also the interface name of the activating connection and a - * per-host secret key is included into the address generation so that the - * same stable-id on different hosts/devices yields different addresses. - * - * If the value is unset, an ID unique for the connection is used. - * Specifying a stable-id allows multiple connections to generate the - * same addresses. Another use is to generate IDs at runtime via - * dynamic substitutions. + * Note that depending on the context where it is used, other parameters are + * also seeded into the generation algorithm. For example, a per-host key + * is commonly also included, so that different systems end up generating + * different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device's + * name is included, so that different interfaces yield different addresses. * * The '$' character is treated special to perform dynamic substitutions - * at runtime. Currently supported are "${CONNECTION}", "${BOOT}", "${RANDOM}". - * These effectively create unique IDs per-connection, per-boot, or every time. + * at runtime. Currently supported are "${CONNECTION}", "${DEVICE}", + * "${BOOT}", "${RANDOM}". + * These effectively create unique IDs per-connection, per-device, per-boot, + * or every time. Note that "${DEVICE}" corresponds the the interface name of the + * device. * Any unrecognized patterns following '$' are treated verbatim, however * are reserved for future use. You are thus advised to avoid '$' or * escape it as "$$". - * For example, set it to "${CONNECTION}/${BOOT}" to create a unique id for - * this connection that changes with every reboot. + * For example, set it to "${CONNECTION}-${BOOT}-${DEVICE}" to create a unique id for + * this connection that changes with every reboot and differs depending on the + * interface where the profile activates. * - * Note that two connections only use the same effective id if - * their stable-id is also identical before performing dynamic substitutions. + * If the value is unset, a global connection default is consulted. If the + * value is still unset, the default is similar to "${CONNECTION}" and uses + * a unique, fixed ID for the connection. * * Since: 1.4 **/ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index d26f23d0a0..d133452b87 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -1177,8 +1177,9 @@ _get_stable_id (NMDevice *self, uuid = nm_connection_get_uuid (connection); stable_type = nm_utils_stable_id_parse (stable_id, - uuid, + nm_device_get_ip_iface (self), NULL, + uuid, &generated); /* current_stable_id_type is a bitfield! */ diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index 50df2b5c2e..b1a4cc25a2 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -3181,8 +3181,9 @@ _stable_id_append (GString *str, NMUtilsStableType nm_utils_stable_id_parse (const char *stable_id, - const char *uuid, + const char *deviceid, const char *bootid, + const char *uuid, char **out_generated) { gsize i, idx_start; @@ -3257,6 +3258,8 @@ nm_utils_stable_id_parse (const char *stable_id, _stable_id_append (str, uuid); else if (CHECK_PREFIX ("${BOOT}")) _stable_id_append (str, bootid ?: nm_utils_get_boot_id ()); + else if (CHECK_PREFIX ("${DEVICE}")) + _stable_id_append (str, deviceid); else if (g_str_has_prefix (&stable_id[i], "${RANDOM}")) { /* RANDOM makes not so much sense for cloned-mac-address * as the result is simmilar to specyifing "cloned-mac-address=random". diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h index ed66a7f0c8..7f406d214c 100644 --- a/src/nm-core-utils.h +++ b/src/nm-core-utils.h @@ -340,8 +340,9 @@ typedef enum { } NMUtilsStableType; NMUtilsStableType nm_utils_stable_id_parse (const char *stable_id, - const char *uuid, + const char *deviceid, const char *bootid, + const char *uuid, char **out_generated); char *nm_utils_stable_id_random (void); diff --git a/src/tests/test-general.c b/src/tests/test-general.c index 0d500c62e2..4db05ac847 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -1621,7 +1621,7 @@ do_test_stable_id_parse (const char *stable_id, else g_assert (stable_id); - stable_type = nm_utils_stable_id_parse (stable_id, "_CONNECTION", "_BOOT", &generated); + stable_type = nm_utils_stable_id_parse (stable_id, "_DEVICE", "_BOOT", "_CONNECTION", &generated); g_assert_cmpint (expected_stable_type, ==, stable_type);