From 142009c6a5b7fd867b081e97395d7f5e751e2ca1 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sat, 7 Jan 2017 17:48:46 +0100 Subject: [PATCH 1/6] shared: add nm_str_realloc() --- shared/nm-utils/nm-macros-internal.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index eb00357765..55abcf133b 100644 --- a/shared/nm-utils/nm-macros-internal.h +++ b/shared/nm-utils/nm-macros-internal.h @@ -356,6 +356,25 @@ nm_strdup_not_empty (const char *str) return str && str[0] ? g_strdup (str) : NULL; } +static inline char * +nm_str_realloc (char *str) +{ + gs_free char *s = str; + + /* Returns a new clone of @str and frees @str. The point is that @str + * possibly points to a larger chunck of memory. We want to freshly allocate + * a buffer. + * + * We could use realloc(), but that might not do anything or leave + * @str in its memory pool for chunks of a different size (bad for + * fragmentation). + * + * This is only useful when we want to keep the buffer around for a long + * time and want to re-allocate a more optimal buffer. */ + + return g_strdup (s); +} + /*****************************************************************************/ #define NM_PRINT_FMT_QUOTED(cond, prefix, str, suffix, str_else) \ From dea3c49ce2567ee823c3fda92c7b882a4572cab9 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 18 Dec 2016 17:58:30 +0100 Subject: [PATCH 2/6] utils: add nm_utils_get_boot_id() util to read "/proc/sys/kernel/random/boot_id" --- src/nm-core-utils.c | 33 +++++++++++++++++++++++++++++++++ src/nm-core-utils.h | 2 ++ 2 files changed, 35 insertions(+) diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index f2d55652eb..b5fbfe34f8 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -3040,6 +3040,39 @@ out: return NULL; } +/*****************************************************************************/ + +const char * +nm_utils_get_boot_id (void) +{ + static const char *boot_id; + + if (G_UNLIKELY (!boot_id)) { + gs_free char *contents = NULL; + + nm_utils_file_get_contents (-1, "/proc/sys/kernel/random/boot_id", 0, + &contents, NULL, NULL); + if (contents) { + g_strstrip (contents); + if (contents[0]) { + /* clone @contents because we keep @boot_id until the program + * ends. + * nm_utils_file_get_contents() likely allocated a larger + * buffer chunk initially and (although using realloc to shrink + * the buffer) it might not be best to keep this memory + * around. */ + boot_id = g_strdup (contents); + } + } + if (!boot_id) + boot_id = nm_utils_uuid_generate (); + } + + return boot_id; +} + +/*****************************************************************************/ + /* Returns the "u" (universal/local) bit value for a Modified EUI-64 */ static gboolean get_gre_eui64_u_bit (guint32 addr) diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h index 66bcdc29e1..dc5f6a8f4d 100644 --- a/src/nm-core-utils.h +++ b/src/nm-core-utils.h @@ -322,6 +322,8 @@ gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_u guint8 *nm_utils_secret_key_read (gsize *out_key_len, GError **error); +const char *nm_utils_get_boot_id (void); + /* IPv6 Interface Identifer helpers */ /** From 46d53e11012c047e09d04f663c1c36e6c47dc298 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 6 Jan 2017 11:54:47 +0100 Subject: [PATCH 3/6] keyfile: fix interpreting missing wifi.mac-address-randomization as permanent address With commit 4f6c91d6962cc031f07e52bb31adde560ad70fac, we aimed to enable mac-address-randomization by default for Wi-Fi. That however is not possible by default because it breaks various scenarios. Also, later wifi.mac-address-randomization was deprecated in favor of wifi.cloned-mac-address setting. Both wifi.mac-address-randomization and wifi.cloned-mac-address support global default values, so it is wrong to read a missing mac-address-randomization as "NEVER" -- which due to normalization also results in cloned-mac-address=permanent. --- libnm-core/nm-keyfile-reader.c | 5 ----- libnm-core/nm-keyfile-writer.c | 3 --- 2 files changed, 8 deletions(-) diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c index 3fa4de7c01..c071264d32 100644 --- a/libnm-core/nm-keyfile-reader.c +++ b/libnm-core/nm-keyfile-reader.c @@ -1376,11 +1376,6 @@ static void set_default_for_missing_key (NMSetting *setting, const char *property) { /* Set a value different from the default value of the property's spec */ - - if (NM_IS_SETTING_WIRELESS (setting)) { - if (!strcmp (property, NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION)) - g_object_set (setting, property, (NMSettingMacRandomization) NM_SETTING_MAC_RANDOMIZATION_NEVER, NULL); - } } static void diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c index dfcdb3d4ed..3a7007d954 100644 --- a/libnm-core/nm-keyfile-writer.c +++ b/libnm-core/nm-keyfile-writer.c @@ -638,9 +638,6 @@ can_omit_default_value (NMSetting *setting, const char *property) } else if (NM_IS_SETTING_IP6_CONFIG (setting)) { if (!strcmp (property, NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE)) return FALSE; - } else if (NM_IS_SETTING_WIRELESS (setting)) { - if (!strcmp (property, NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION)) - return FALSE; } return TRUE; From 21ae09c1cc2d1f7fa61f352ae5d5c43b0b02e384 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 18 Dec 2016 14:03:38 +0100 Subject: [PATCH 4/6] core: add assertions for network_id/stable_type We require a network-id. Assert that it is set. Also, we encode the stable-id as uint8. Thus, add an assertion that we don't use more then 254 IDs. If we ever make use of stable-type 255, we must extend the encoding to allow for more values. The assertion is there to catch that. --- src/ndisc/nm-fake-ndisc.c | 2 ++ src/ndisc/nm-lndp-ndisc.c | 1 + src/ndisc/nm-ndisc.c | 1 + src/nm-core-utils.c | 24 ++++++++++++------------ src/nm-core-utils.h | 10 ++++++++-- src/tests/test-utils.c | 4 ++-- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/ndisc/nm-fake-ndisc.c b/src/ndisc/nm-fake-ndisc.c index f1ada6c0cd..7a9fb11079 100644 --- a/src/ndisc/nm-fake-ndisc.c +++ b/src/ndisc/nm-fake-ndisc.c @@ -374,6 +374,8 @@ nm_fake_ndisc_new (int ifindex, const char *ifname) NM_NDISC_IFINDEX, ifindex, NM_NDISC_IFNAME, ifname, NM_NDISC_NODE_TYPE, (int) NM_NDISC_NODE_TYPE_HOST, + NM_NDISC_STABLE_TYPE, (int) NM_UTILS_STABLE_TYPE_UUID, + NM_NDISC_NETWORK_ID, "fake", NULL); } diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c index 9e5cdaa059..3bc1590ea8 100644 --- a/src/ndisc/nm-lndp-ndisc.c +++ b/src/ndisc/nm-lndp-ndisc.c @@ -550,6 +550,7 @@ nm_lndp_ndisc_new (NMPlatform *platform, g_return_val_if_fail (NM_IS_PLATFORM (platform), NULL); g_return_val_if_fail (!error || !*error, NULL); + g_return_val_if_fail (network_id, NULL); if (!nm_platform_netns_push (platform, &netns)) return NULL; diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c index 775bb61139..00f3104966 100644 --- a/src/ndisc/nm-ndisc.c +++ b/src/ndisc/nm-ndisc.c @@ -1054,6 +1054,7 @@ set_property (GObject *object, guint prop_id, case PROP_NETWORK_ID: /* construct-only */ priv->network_id = g_value_dup_string (value); + g_return_if_fail (priv->network_id); break; case PROP_ADDR_GEN_MODE: /* construct-only */ diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index b5fbfe34f8..4654b950d6 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -3268,7 +3268,7 @@ nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid, char *buf) /*****************************************************************************/ static gboolean -_set_stable_privacy (guint8 stable_type, +_set_stable_privacy (NMUtilsStableType stable_type, struct in6_addr *addr, const char *ifname, const char *network_id, @@ -3282,7 +3282,8 @@ _set_stable_privacy (guint8 stable_type, guint32 tmp[2]; gsize len = sizeof (digest); - g_return_val_if_fail (key_len, FALSE); + nm_assert (key_len); + nm_assert (network_id); /* Documentation suggests that this can fail. * Maybe in case of a missing algorithm in crypto library? */ @@ -3296,6 +3297,11 @@ _set_stable_privacy (guint8 stable_type, key_len = MIN (key_len, G_MAXUINT32); if (stable_type != NM_UTILS_STABLE_TYPE_UUID) { + guint8 stable_type_uint8; + + nm_assert (stable_type < (NMUtilsStableType) 255); + stable_type_uint8 = (guint8) stable_type; + /* Preferably, we would always like to include the stable-type, * but for backward compatibility reasons, we cannot for UUID. * @@ -3305,13 +3311,11 @@ _set_stable_privacy (guint8 stable_type, * and the terminating '\0' of @network_id, it is unambigiously * possible to revert the process and deduce the @stable_type. */ - g_checksum_update (sum, &stable_type, sizeof (stable_type)); + g_checksum_update (sum, &stable_type_uint8, sizeof (stable_type_uint8)); } g_checksum_update (sum, addr->s6_addr, 8); g_checksum_update (sum, (const guchar *) ifname, strlen (ifname) + 1); - if (!network_id) - network_id = ""; g_checksum_update (sum, (const guchar *) network_id, strlen (network_id) + 1); tmp[0] = htonl (dad_counter); tmp[1] = htonl (key_len); @@ -3329,7 +3333,7 @@ _set_stable_privacy (guint8 stable_type, } gboolean -nm_utils_ipv6_addr_set_stable_privacy_impl (guint8 stable_type, +nm_utils_ipv6_addr_set_stable_privacy_impl (NMUtilsStableType stable_type, struct in6_addr *addr, const char *ifname, const char *network_id, @@ -3361,9 +3365,7 @@ nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type, gs_free guint8 *secret_key = NULL; gsize key_len = 0; - nm_assert (NM_IN_SET (stable_type, - NM_UTILS_STABLE_TYPE_UUID, - NM_UTILS_STABLE_TYPE_STABLE_ID)); + g_return_val_if_fail (network_id, FALSE); if (dad_counter >= RFC7217_IDGEN_RETRIES) { g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, @@ -3463,9 +3465,6 @@ _hw_addr_gen_stable_eth (NMUtilsStableType stable_type, guint8 stable_type_uint8; nm_assert (stable_id); - nm_assert (NM_IN_SET (stable_type, - NM_UTILS_STABLE_TYPE_UUID, - NM_UTILS_STABLE_TYPE_STABLE_ID)); nm_assert (secret_key); sum = g_checksum_new (G_CHECKSUM_SHA256); @@ -3474,6 +3473,7 @@ _hw_addr_gen_stable_eth (NMUtilsStableType stable_type, key_len = MIN (key_len, G_MAXUINT32); + nm_assert (stable_type < (NMUtilsStableType) 255); stable_type_uint8 = stable_type; g_checksum_update (sum, (const guchar *) &stable_type_uint8, sizeof (stable_type_uint8)); diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h index dc5f6a8f4d..25a59361fc 100644 --- a/src/nm-core-utils.h +++ b/src/nm-core-utils.h @@ -362,13 +362,19 @@ gboolean nm_utils_get_ipv6_interface_identifier (NMLinkType link_type, guint dev_id, NMUtilsIPv6IfaceId *out_iid); -typedef enum { /*< skip >*/ +typedef enum { + /* The stable type. Note that this value is encoded in the + * generated addresses, thus the numbers MUST not change. + * + * Also note, if we ever allocate ID 255, we must take care + * that nm_utils_ipv6_addr_set_stable_privacy() extends the + * uint8 encoding of this value. */ NM_UTILS_STABLE_TYPE_UUID = 0, NM_UTILS_STABLE_TYPE_STABLE_ID = 1, } NMUtilsStableType; -gboolean nm_utils_ipv6_addr_set_stable_privacy_impl (guint8 stable_type, +gboolean nm_utils_ipv6_addr_set_stable_privacy_impl (NMUtilsStableType stable_type, struct in6_addr *addr, const char *ifname, const char *network_id, diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c index cd05a36d9f..16eb3aeccf 100644 --- a/src/tests/test-utils.c +++ b/src/tests/test-utils.c @@ -37,12 +37,12 @@ test_stable_privacy (void) /* We get an address without the UUID. */ inet_pton (AF_INET6, "1::", &addr1); - nm_utils_ipv6_addr_set_stable_privacy_impl (NM_UTILS_STABLE_TYPE_UUID, &addr1, "eth666", NULL, 384, (guint8 *) "key", 3, NULL); + nm_utils_ipv6_addr_set_stable_privacy_impl (NM_UTILS_STABLE_TYPE_UUID, &addr1, "eth666", "", 384, (guint8 *) "key", 3, NULL); nmtst_assert_ip6_address (&addr1, "1::11aa:2530:9144:dafa"); /* We get a different address in a different network. */ inet_pton (AF_INET6, "2::", &addr1); - nm_utils_ipv6_addr_set_stable_privacy_impl (NM_UTILS_STABLE_TYPE_UUID, &addr1, "eth666", NULL, 384, (guint8 *) "key", 3, NULL); + nm_utils_ipv6_addr_set_stable_privacy_impl (NM_UTILS_STABLE_TYPE_UUID, &addr1, "eth666", "", 384, (guint8 *) "key", 3, NULL); nmtst_assert_ip6_address (&addr1, "2::338e:8d:c11:8726"); inet_pton (AF_INET6, "1234::", &addr1); From f0d40525dfb079820e5dce48360ef6a9a540064a Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 18 Dec 2016 13:54:26 +0100 Subject: [PATCH 5/6] device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns. --- libnm-core/nm-setting-connection.c | 31 ++++- libnm-core/nm-setting-ip6-config.c | 4 +- libnm-core/nm-setting-wired.c | 4 +- libnm-core/nm-setting-wireless.c | 4 +- man/NetworkManager.conf.xml | 3 + src/devices/nm-device.c | 92 ++++++++++++--- src/ndisc/nm-ndisc.c | 2 +- src/nm-core-utils.c | 180 +++++++++++++++++++++++++++++ src/nm-core-utils.h | 11 +- src/tests/test-general.c | 86 ++++++++++++++ 10 files changed, 388 insertions(+), 29 deletions(-) diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c index d7a0d2fc11..ac8bdf2d2d 100644 --- a/libnm-core/nm-setting-connection.c +++ b/libnm-core/nm-setting-connection.c @@ -1432,13 +1432,32 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class) /** * NMSettingConnection:stable-id: * - * This token to generate stable IDs for the connection. If unset, - * the UUID will be used instead. + * Token to generate stable IDs for the connection. * - * The stable-id is used instead of the connection UUID 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. + * 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. 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. + * Specifing 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. * * Since: 1.4 **/ diff --git a/libnm-core/nm-setting-ip6-config.c b/libnm-core/nm-setting-ip6-config.c index 4ef3333dc3..429f27c95c 100644 --- a/libnm-core/nm-setting-ip6-config.c +++ b/libnm-core/nm-setting-ip6-config.c @@ -717,8 +717,8 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class) * when the interface hardware is replaced. * * The value of "stable-privacy" enables use of cryptographically - * secure hash of a secret host-specific key along with the connection - * identification and the network address as specified by RFC7217. + * secure hash of a secret host-specific key along with the connection's + * stable-id and the network address as specified by RFC7217. * This makes it impossible to use the address track host's presence, * and makes the address stable when the network interface hardware is * replaced. diff --git a/libnm-core/nm-setting-wired.c b/libnm-core/nm-setting-wired.c index fcf7e95204..fcd8e8370e 100644 --- a/libnm-core/nm-setting-wired.c +++ b/libnm-core/nm-setting-wired.c @@ -1150,8 +1150,8 @@ nm_setting_wired_class_init (NMSettingWiredClass *setting_wired_class) * "preserve" means not to touch the MAC address on activation. * "permanent" means to use the permanent hardware address of the device. * "random" creates a random MAC address on each connect. - * "stable" creates a hashed MAC address based on connection.stable-id (or - * the connection's UUID) and a machine dependent key. + * "stable" creates a hashed MAC address based on connection.stable-id and a + * machine dependent key. * * If unspecified, the value can be overwritten via global defaults, see manual * of NetworkManager.conf. If still unspecified, it defaults to "preserve" diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c index d7a347f504..1d129f6634 100644 --- a/libnm-core/nm-setting-wireless.c +++ b/libnm-core/nm-setting-wireless.c @@ -1348,8 +1348,8 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_wireless_class) * "preserve" means not to touch the MAC address on activation. * "permanent" means to use the permanent hardware address of the device. * "random" creates a random MAC address on each connect. - * "stable" creates a hashed MAC address based on connection.stable-id (or - * the connection's UUID) and a machine dependent key. + * "stable" creates a hashed MAC address based on connection.stable-id and a + * machine dependent key. * * If unspecified, the value can be overwritten via global defaults, see manual * of NetworkManager.conf. If still unspecified, it defaults to "preserve" diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index ae14b05e34..acedc58ddf 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -600,6 +600,9 @@ ipv6.ip6-privacy=0 connection.lldp + + connection.stable-id + ethernet.cloned-mac-address If left unspecified, it defaults to "preserve". diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index b6b464caa9..256651cf66 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -261,6 +261,9 @@ typedef struct _NMDevicePrivate { bool firmware_missing:1; bool nm_plugin_missing:1; bool hw_addr_perm_fake:1; /* whether the permanent HW address could not be read and is a fake */ + + NMUtilsStableType current_stable_id_type:3; + GHashTable * available_connections; char * hw_addr; char * hw_addr_perm; @@ -310,6 +313,8 @@ typedef struct _NMDevicePrivate { guint32 dhcp_timeout; char * dhcp_anycast_address; + char * current_stable_id; + /* Proxy Configuration */ NMProxyConfig *proxy_config; NMPacrunnerManager *pacrunner_manager; @@ -663,25 +668,76 @@ _add_capabilities (NMDevice *self, NMDeviceCapabilities capabilities) /*****************************************************************************/ static const char * -_get_stable_id (NMConnection *connection, NMUtilsStableType *out_stable_type) +_get_stable_id (NMDevice *self, + NMConnection *connection, + NMUtilsStableType *out_stable_type) { - NMSettingConnection *s_con; - const char *stable_id; + NMDevicePrivate *priv; + nm_assert (NM_IS_DEVICE (self)); nm_assert (NM_IS_CONNECTION (connection)); nm_assert (out_stable_type); - s_con = nm_connection_get_setting_connection (connection); - g_return_val_if_fail (s_con, NULL); + priv = NM_DEVICE_GET_PRIVATE (self); - stable_id = nm_setting_connection_get_stable_id (s_con); - if (!stable_id) { - *out_stable_type = NM_UTILS_STABLE_TYPE_UUID; - return nm_connection_get_uuid (connection); + /* we cache the generated stable ID for the time of an activation. + * + * The reason is, that we don't want the stable-id to change as long + * as the device is active. + * + * Especially with ${RANDOM} stable-id we want to generate *one* configuration + * for each activation. */ + if (G_UNLIKELY (!priv->current_stable_id)) { + gs_free char *default_id = NULL; + gs_free char *generated = NULL; + NMUtilsStableType stable_type; + NMSettingConnection *s_con; + const char *stable_id; + const char *uuid; + + s_con = nm_connection_get_setting_connection (connection); + + stable_id = nm_setting_connection_get_stable_id (s_con); + + if (!stable_id) { + default_id = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA, + "connection.stable-id", + self); + stable_id = default_id; + } + + uuid = nm_connection_get_uuid (connection); + + stable_type = nm_utils_stable_id_parse (stable_id, + uuid, + NULL, + &generated); + + /* current_stable_id_type is a bitfield! */ + nm_assert (stable_type <= (NMUtilsStableType) 0x2); + nm_assert (stable_type + (NMUtilsStableType) 1 > (NMUtilsStableType) 0); + priv->current_stable_id_type = stable_type; + + if (stable_type == NM_UTILS_STABLE_TYPE_UUID) + priv->current_stable_id = g_strdup (uuid); + else if (stable_type == NM_UTILS_STABLE_TYPE_STABLE_ID) + priv->current_stable_id = g_strdup (stable_id); + else if (stable_type == NM_UTILS_STABLE_TYPE_GENERATED) + priv->current_stable_id = nm_str_realloc (nm_utils_stable_id_generated_complete (generated)); + else { + nm_assert (stable_type == NM_UTILS_STABLE_TYPE_RANDOM); + priv->current_stable_id = nm_str_realloc (nm_utils_stable_id_random ()); + } + _LOGT (LOGD_DEVICE, + "stable-id: type=%d, \"%s\"" + "%s%s%s", + (int) priv->current_stable_id_type, + priv->current_stable_id, + NM_PRINT_FMT_QUOTED (stable_type == NM_UTILS_STABLE_TYPE_GENERATED, " from \"", generated, "\"", "")); } - *out_stable_type = NM_UTILS_STABLE_TYPE_STABLE_ID; - return stable_id; + *out_stable_type = priv->current_stable_id_type; + return priv->current_stable_id; } /*****************************************************************************/ @@ -6438,7 +6494,7 @@ check_and_add_ipv6ll_addr (NMDevice *self) NMUtilsStableType stable_type; const char *stable_id; - stable_id = _get_stable_id (connection, &stable_type); + stable_id = _get_stable_id (self, connection, &stable_type); if ( !stable_id || !nm_utils_ipv6_addr_set_stable_privacy (stable_type, &lladdr, @@ -6843,7 +6899,7 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr) s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection)); g_assert (s_ip6); - stable_id = _get_stable_id (connection, &stable_type); + stable_id = _get_stable_id (self, connection, &stable_type); if (stable_id) { priv->ndisc = nm_lndp_ndisc_new (NM_PLATFORM_GET, nm_device_get_ip_ifindex (self), @@ -11375,7 +11431,7 @@ nm_device_spawn_iface_helper (NMDevice *self) g_ptr_array_add (argv, g_strdup ("--uuid")); g_ptr_array_add (argv, g_strdup (nm_connection_get_uuid (connection))); - stable_id = _get_stable_id (connection, &stable_type); + stable_id = _get_stable_id (self, connection, &stable_type); if (stable_id && stable_type != NM_UTILS_STABLE_TYPE_UUID) { g_ptr_array_add (argv, g_strdup ("--stable-id")); g_ptr_array_add (argv, g_strdup_printf ("%d %s", (int) stable_type, stable_id)); @@ -11670,6 +11726,11 @@ _set_state_full (NMDevice *self, if (state >= NM_DEVICE_STATE_DISCONNECTED && old_state < NM_DEVICE_STATE_DISCONNECTED) nm_device_recheck_available_connections (self); + if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_DEACTIVATING) { + if (nm_clear_g_free (&priv->current_stable_id)) + _LOGT (LOGD_DEVICE, "stable-id: clear"); + } + /* Handle the new state here; but anything that could trigger * another state change should be done below. */ @@ -12544,7 +12605,7 @@ nm_device_hw_addr_set_cloned (NMDevice *self, NMConnection *connection, gboolean return TRUE; } - stable_id = _get_stable_id (connection, &stable_type); + stable_id = _get_stable_id (self, connection, &stable_type); if (stable_id) { hw_addr_generated = nm_utils_hw_addr_gen_stable_eth (stable_type, stable_id, nm_device_get_ip_iface (self), @@ -12933,6 +12994,7 @@ finalize (GObject *object) g_free (priv->type_desc); g_free (priv->type_description); g_free (priv->dhcp_anycast_address); + g_free (priv->current_stable_id); g_hash_table_unref (priv->ip6_saved_properties); g_hash_table_unref (priv->available_connections); diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c index 00f3104966..a50bbe4385 100644 --- a/src/ndisc/nm-ndisc.c +++ b/src/ndisc/nm-ndisc.c @@ -1176,7 +1176,7 @@ nm_ndisc_class_init (NMNDiscClass *klass) G_PARAM_STATIC_STRINGS); obj_properties[PROP_STABLE_TYPE] = g_param_spec_int (NM_NDISC_STABLE_TYPE, "", "", - NM_UTILS_STABLE_TYPE_UUID, NM_UTILS_STABLE_TYPE_STABLE_ID, NM_UTILS_STABLE_TYPE_UUID, + NM_UTILS_STABLE_TYPE_UUID, NM_UTILS_STABLE_TYPE_RANDOM, NM_UTILS_STABLE_TYPE_UUID, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index 4654b950d6..219f309beb 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -3267,6 +3267,186 @@ nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid, char *buf) /*****************************************************************************/ +char * +nm_utils_stable_id_random (void) +{ + char buf[15]; + + if (nm_utils_read_urandom (buf, sizeof (buf)) < 0) + g_return_val_if_reached (nm_utils_uuid_generate ()); + return g_base64_encode ((guchar *) buf, sizeof (buf)); +} + +char * +nm_utils_stable_id_generated_complete (const char *stable_id_generated) +{ + guint8 buf[20]; + GChecksum *sum; + gsize buf_size; + char *base64; + + /* for NM_UTILS_STABLE_TYPE_GENERATED we genererate a possibly long string + * by doing text-substitutions in nm_utils_stable_id_parse(). + * + * Let's shorten the (possibly) long stable_id to something more compact. */ + + g_return_val_if_fail (stable_id_generated, NULL); + + sum = g_checksum_new (G_CHECKSUM_SHA1); + nm_assert (sum); + + g_checksum_update (sum, (guchar *) stable_id_generated, strlen (stable_id_generated)); + + buf_size = sizeof (buf); + g_checksum_get_digest (sum, buf, &buf_size); + nm_assert (buf_size == sizeof (buf)); + + g_checksum_free (sum); + + /* we don't care to use the sha1 sum in common hex representation. + * Use instead base64, it's 27 chars (stripping the padding) vs. + * 40. */ + + base64 = g_base64_encode ((guchar *) buf, sizeof (buf)); + nm_assert (strlen (base64) == 28); + nm_assert (base64[27] == '='); + + base64[27] = '\0'; + return base64; +} + +static void +_stable_id_append (GString *str, + const char *substitution) +{ + if (!substitution) + substitution = ""; + g_string_append_printf (str, "=%zu{%s}", strlen (substitution), substitution); +} + +NMUtilsStableType +nm_utils_stable_id_parse (const char *stable_id, + const char *uuid, + const char *bootid, + char **out_generated) +{ + gsize i, idx_start; + GString *str = NULL; + + g_return_val_if_fail (out_generated, NM_UTILS_STABLE_TYPE_RANDOM); + + if (!stable_id) { + out_generated = NULL; + return NM_UTILS_STABLE_TYPE_UUID; + } + + /* the stable-id allows for some dynamic by performing text-substitutions + * of ${...} patterns. + * + * At first, it looks a bit like bash parameter substitution. + * In contrast however, the process is unambigious so that the resulting + * effective id differs if: + * - the original, untranslated stable-id differs + * - or any of the subsitutions differs. + * + * The reason for that is, for example if you specify "${CONNECTION}" in the + * stable-id, then the resulting ID should be always(!) unique for this connection. + * There should be no way another connection could specify any stable-id that results + * in the same addresses to be generated (aside hash collisions). + * + * + * For example: say you have a connection with UUID + * "123e4567-e89b-12d3-a456-426655440000" which happens also to be + * the current boot-id. + * Then: + * (1) connection.stable-id = + * (2) connection.stable-id = "123e4567-e89b-12d3-a456-426655440000" + * (3) connection.stable-id = "${CONNECTION}" + * (3) connection.stable-id = "${BOOT}" + * will all generate different addresses, although in one way or the + * other, they all mangle the uuid "123e4567-e89b-12d3-a456-426655440000". + * + * For example, with stable-id="${FOO}${BAR}" the substitutions + * - FOO="ab", BAR="c" + * - FOO="a", BAR="bc" + * should give a different effective id. + * + * For example, with FOO="x" and BAR="x", the stable-ids + * - "${FOO}${BAR}" + * - "${BAR}${FOO}" + * should give a different effective id. + */ + + idx_start = 0; + for (i = 0; stable_id[i]; ) { + if (stable_id[i] != '$') { + i++; + continue; + } + +#define CHECK_PREFIX(prefix) \ + ({ \ + gboolean _match = FALSE; \ + \ + if (g_str_has_prefix (&stable_id[i], ""prefix"")) { \ + _match = TRUE; \ + if (!str) \ + str = g_string_sized_new (256); \ + i += NM_STRLEN (prefix); \ + g_string_append_len (str, &(stable_id)[idx_start], i - idx_start); \ + idx_start = i; \ + } \ + _match; \ + }) + if (CHECK_PREFIX ("${CONNECTION}")) + _stable_id_append (str, uuid); + else if (CHECK_PREFIX ("${BOOT}")) + _stable_id_append (str, bootid ?: nm_utils_get_boot_id ()); + 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 specifing "cloned-mac-address=random". + * It makes however sense for RFC 7217 Stable Privacy IPv6 addresses + * where this is effectively the only way to generate a different + * (random) host identifier for each connect. + * + * With RANDOM, the user can switch the lifetime of the + * generated cloned-mac-address and IPv6 host identifier + * by toggeling only the stable-id property of the connection. + * With RANDOM being the most short-lived, ~non-stable~ variant. + */ + if (str) + g_string_free (str, TRUE); + *out_generated = NULL; + return NM_UTILS_STABLE_TYPE_RANDOM; + } else { + /* The text following the '$' is not recognized as valid + * substitution pattern. Treat it verbatim. */ + i++; + + /* Note that using unrecognized substitution patterns might + * yield different results with future versions. Avoid that, + * by not using '$' (except for actual substitutions) or escape + * it as "$$" (which is guaranteed to be treated verbatim + * in future). */ + if (stable_id[i] == '$') + i++; + } + } +#undef CHECK_PREFIX + + if (!str) { + *out_generated = NULL; + return NM_UTILS_STABLE_TYPE_STABLE_ID; + } + + if (idx_start < i) + g_string_append_len (str, &stable_id[idx_start], i - idx_start); + *out_generated = g_string_free (str, FALSE); + return NM_UTILS_STABLE_TYPE_GENERATED; +} + +/*****************************************************************************/ + static gboolean _set_stable_privacy (NMUtilsStableType stable_type, struct in6_addr *addr, diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h index 25a59361fc..e3aa257f25 100644 --- a/src/nm-core-utils.h +++ b/src/nm-core-utils.h @@ -369,10 +369,19 @@ typedef enum { * Also note, if we ever allocate ID 255, we must take care * that nm_utils_ipv6_addr_set_stable_privacy() extends the * uint8 encoding of this value. */ - NM_UTILS_STABLE_TYPE_UUID = 0, + NM_UTILS_STABLE_TYPE_UUID = 0, NM_UTILS_STABLE_TYPE_STABLE_ID = 1, + NM_UTILS_STABLE_TYPE_GENERATED = 2, + NM_UTILS_STABLE_TYPE_RANDOM = 3, } NMUtilsStableType; +NMUtilsStableType nm_utils_stable_id_parse (const char *stable_id, + const char *uuid, + const char *bootid, + char **out_generated); + +char *nm_utils_stable_id_random (void); +char *nm_utils_stable_id_generated_complete (const char *msg); gboolean nm_utils_ipv6_addr_set_stable_privacy_impl (NMUtilsStableType stable_type, struct in6_addr *addr, diff --git a/src/tests/test-general.c b/src/tests/test-general.c index a7337ba7b0..ed2f4e5119 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -1481,6 +1481,89 @@ test_reverse_dns_ip6 (void) /*****************************************************************************/ +static void +do_test_stable_id_parse (const char *stable_id, + NMUtilsStableType expected_stable_type, + const char *expected_generated) +{ + gs_free char *generated = NULL; + NMUtilsStableType stable_type; + + if (expected_stable_type == NM_UTILS_STABLE_TYPE_GENERATED) + g_assert (expected_generated); + else + g_assert (!expected_generated); + + if (expected_stable_type == NM_UTILS_STABLE_TYPE_UUID) + g_assert (!stable_id); + else + g_assert (stable_id); + + stable_type = nm_utils_stable_id_parse (stable_id, "_CONNECTION", "_BOOT", &generated); + + g_assert_cmpint (expected_stable_type, ==, stable_type); + + if (stable_type == NM_UTILS_STABLE_TYPE_GENERATED) { + g_assert_cmpstr (expected_generated, ==, generated); + g_assert (generated); + } else + g_assert (!generated); +} + +static void +test_stable_id_parse (void) +{ +#define _parse_stable_id(stable_id) do_test_stable_id_parse (""stable_id"", NM_UTILS_STABLE_TYPE_STABLE_ID, NULL) +#define _parse_generated(stable_id, expected_generated) do_test_stable_id_parse (""stable_id"", NM_UTILS_STABLE_TYPE_GENERATED, ""expected_generated"") +#define _parse_random(stable_id) do_test_stable_id_parse (""stable_id"", NM_UTILS_STABLE_TYPE_RANDOM, NULL) + do_test_stable_id_parse (NULL, NM_UTILS_STABLE_TYPE_UUID, NULL); + _parse_stable_id (""); + _parse_stable_id ("a"); + _parse_stable_id ("a$"); + _parse_stable_id ("a$x"); + _parse_stable_id (" ${a$x"); + _parse_stable_id ("${"); + _parse_stable_id ("${="); + _parse_stable_id ("${a"); + _parse_stable_id ("${a$x"); + _parse_stable_id ("a$$"); + _parse_stable_id ("a$$x"); + _parse_stable_id ("a$${CONNECTION}"); + _parse_stable_id ("a$${CONNECTION}x"); + _parse_generated ("${CONNECTION}", "${CONNECTION}=11{_CONNECTION}"); + _parse_generated ("${${CONNECTION}", "${${CONNECTION}=11{_CONNECTION}"); + _parse_generated ("${CONNECTION}x", "${CONNECTION}=11{_CONNECTION}x"); + _parse_generated ("x${CONNECTION}", "x${CONNECTION}=11{_CONNECTION}"); + _parse_generated ("${BOOT}x", "${BOOT}=5{_BOOT}x"); + _parse_generated ("x${BOOT}", "x${BOOT}=5{_BOOT}"); + _parse_generated ("x${BOOT}${CONNECTION}", "x${BOOT}=5{_BOOT}${CONNECTION}=11{_CONNECTION}"); + _parse_generated ("xX${BOOT}yY${CONNECTION}zZ", "xX${BOOT}=5{_BOOT}yY${CONNECTION}=11{_CONNECTION}zZ"); + _parse_random ("${RANDOM}"); + _parse_random (" ${RANDOM}"); + _parse_random ("${BOOT}${RANDOM}"); +} + +/*****************************************************************************/ + +static void +test_stable_id_generated_complete (void) +{ +#define ASSERT(str, expected) \ + G_STMT_START { \ + gs_free char *_s = NULL; \ + \ + _s = nm_utils_stable_id_generated_complete ((str)); \ + g_assert_cmpstr ((expected), ==, _s); \ + } G_STMT_END + + ASSERT ("", "2jmj7l5rSw0yVb/vlWAYkK/YBwk"); + ASSERT ("a", "hvfkN/qlp/zhXR3cuerq6jd2Z7g"); + ASSERT ("password", "W6ph5Mm5Pz8GgiULbPgzG37mj9g"); +#undef ASSERT +} + +/*****************************************************************************/ + NMTST_DEFINE (); int @@ -1518,6 +1601,9 @@ main (int argc, char **argv) g_test_add_func ("/general/reverse_dns/ip4", test_reverse_dns_ip4); g_test_add_func ("/general/reverse_dns/ip6", test_reverse_dns_ip6); + g_test_add_func ("/general/stable-id/parse", test_stable_id_parse); + g_test_add_func ("/general/stable-id/generated-complete", test_stable_id_generated_complete); + return g_test_run (); } From 6fa069fad1887a32d525ee6cce1f55d60ff3fc8d Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 6 Jan 2017 12:27:58 +0100 Subject: [PATCH 6/6] example: add example configuration snippet '30-anon.conf' --- Makefile.examples | 2 ++ examples/nm-conf.d/30-anon.conf | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 examples/nm-conf.d/30-anon.conf diff --git a/Makefile.examples b/Makefile.examples index fcad852286..61c2b1df6e 100644 --- a/Makefile.examples +++ b/Makefile.examples @@ -137,6 +137,8 @@ EXTRA_DIST += \ examples/lua/lgi/change-vpn-username.lua \ examples/lua/lgi/deactivate-all.lua \ \ + examples/nm-conf.d/30-anon.conf \ + \ examples/python/dbus/nm-state.py \ examples/python/dbus/add-connection.py \ examples/python/dbus/add-connection-compat.py \ diff --git a/examples/nm-conf.d/30-anon.conf b/examples/nm-conf.d/30-anon.conf new file mode 100644 index 0000000000..28a9ae701a --- /dev/null +++ b/examples/nm-conf.d/30-anon.conf @@ -0,0 +1,55 @@ +# Example configuration snippet for NetworkManager to +# overwrite some default value for more privacy. +# Put it for example to /etc/NetworkManager/conf.d/30-anon.conf +# +# See man NetworkManager.conf(5) for how default values +# work. See man nm-settings(5) for the connection properties. +# +# +# This enables privacy setting by default. The defaults +# apply only to settings that do not explicitly configure +# a per-connection override. +# That means, if the connection profile has +# +# $ nmcli connection show "$CON_NAME" | +# grep '^\(connection.stable-id\|ipv6.addr-gen-mode\|ipv6.ip6-privacy\|802-11-wireless.cloned-mac-address\|802-11-wireless.mac-address-randomization\|802-3-ethernet.cloned-mac-address\)' +# connection.stable-id: -- +# 802-3-ethernet.cloned-mac-address: -- +# 802-11-wireless.cloned-mac-address: -- +# 802-11-wireless.mac-address-randomization:default +# ipv6.ip6-privacy: -1 (unknown) +# ipv6.addr-gen-mode: stable-privacy +# +# then the default values are inherited and thus both the MAC +# address and the IPv6 host identifier are randomized. +# Also, ipv6 private addresses (RFC4941) are used in +# addition. +# +# +# For some profiles it can make sense to reuse the same stable-id +# (and thus MAC address and IPv6 host identifier) for the duration +# of the current boot, but still exclusive to the connection profile. +# Thus, explicitly set the stable-id like: +# +# $ nmcli connection modify "$CON_NAME" connection.stable-id '${CONNECTION}/${BOOT}' +# +# ... or keep it stable accross reboots, still distinct per profile: +# +# $ nmcli connection modify "$CON_NAME" connection.stable-id '${CONNECTION}' +# +# ... or use the same stable IDs for a bunch of profiles +# +# $ nmcli connection modify "$CON_NAME" connection.stable-id 'my-home-wifi yada yada' +# +# ... or use the same IDs for a bunch of profiles, but only for the current boot +# +# $ nmcli connection modify "$CON_NAME" connection.stable-id 'my-home-wifi yada yada/${BOOT}' + +[device-anon] +wifi.scan-rand-mac-address=yes + +[connection-anon] +connection.stable-id=${RANDOM} +ethernet.cloned-mac-address=stable +wifi.cloned-mac-address=stable +ipv6.ip6-privacy=2