diff options
author | Thomas Haller <thaller@redhat.com> | 2017-01-09 17:29:53 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2017-01-09 17:29:53 +0100 |
commit | 4b9176eb7dc5b28579f557320f8be65f05d5c05e (patch) | |
tree | 6d4e3cf704186024fc3cf0bd875ac59cf616e278 | |
parent | ae5adc9e21c642a198868b519b2a278b0b108ab8 (diff) | |
parent | 6fa069fad1887a32d525ee6cce1f55d60ff3fc8d (diff) |
core: merge branch 'th/stable-id-bgo776904'
https://bugzilla.gnome.org/show_bug.cgi?id=776904
-rw-r--r-- | Makefile.examples | 2 | ||||
-rw-r--r-- | examples/nm-conf.d/30-anon.conf | 55 | ||||
-rw-r--r-- | libnm-core/nm-keyfile-reader.c | 5 | ||||
-rw-r--r-- | libnm-core/nm-keyfile-writer.c | 3 | ||||
-rw-r--r-- | libnm-core/nm-setting-connection.c | 31 | ||||
-rw-r--r-- | libnm-core/nm-setting-ip6-config.c | 4 | ||||
-rw-r--r-- | libnm-core/nm-setting-wired.c | 4 | ||||
-rw-r--r-- | libnm-core/nm-setting-wireless.c | 4 | ||||
-rw-r--r-- | man/NetworkManager.conf.xml | 3 | ||||
-rw-r--r-- | shared/nm-utils/nm-macros-internal.h | 19 | ||||
-rw-r--r-- | src/devices/nm-device.c | 92 | ||||
-rw-r--r-- | src/ndisc/nm-fake-ndisc.c | 2 | ||||
-rw-r--r-- | src/ndisc/nm-lndp-ndisc.c | 1 | ||||
-rw-r--r-- | src/ndisc/nm-ndisc.c | 3 | ||||
-rw-r--r-- | src/nm-core-utils.c | 237 | ||||
-rw-r--r-- | src/nm-core-utils.h | 23 | ||||
-rw-r--r-- | src/tests/test-general.c | 86 | ||||
-rw-r--r-- | src/tests/test-utils.c | 4 |
18 files changed, 525 insertions, 53 deletions
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 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; 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 @@ -601,6 +601,9 @@ ipv6.ip6-privacy=0 <term><varname>connection.lldp</varname></term> </varlistentry> <varlistentry> + <term><varname>connection.stable-id</varname></term> + </varlistentry> + <varlistentry> <term><varname>ethernet.cloned-mac-address</varname></term> <listitem><para>If left unspecified, it defaults to "preserve".</para></listitem> </varlistentry> 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) \ 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); + + /* 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; + } - 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); + 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-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..a50bbe4385 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 */ @@ -1175,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 f2d55652eb..219f309beb 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) @@ -3234,8 +3267,188 @@ 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 = <NULL> + * (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 (guint8 stable_type, +_set_stable_privacy (NMUtilsStableType stable_type, struct in6_addr *addr, const char *ifname, const char *network_id, @@ -3249,7 +3462,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? */ @@ -3263,6 +3477,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. * @@ -3272,13 +3491,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); @@ -3296,7 +3513,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, @@ -3328,9 +3545,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, @@ -3430,9 +3645,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); @@ -3441,6 +3653,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 66bcdc29e1..e3aa257f25 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 */ /** @@ -360,13 +362,28 @@ gboolean nm_utils_get_ipv6_interface_identifier (NMLinkType link_type, guint dev_id, NMUtilsIPv6IfaceId *out_iid); -typedef enum { /*< skip >*/ - NM_UTILS_STABLE_TYPE_UUID = 0, +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, + 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 (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-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 (); } 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); |