diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index deab2a586..cd8d717e9 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -3213,7 +3214,6 @@ nm_utils_get_ipv6_interface_identifier (NMLinkType link_type, } return FALSE; } - void nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr, const NMUtilsIPv6IfaceId iid) @@ -3228,6 +3228,125 @@ nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid, memcpy (iid, addr->s6_addr + 8, 8); } +static gboolean +_set_stable_privacy (struct in6_addr *addr, + const char *ifname, + const char *uuid, + guint dad_counter, + gchar *secret_key, + gsize key_len, + GError **error) +{ + GChecksum *sum; + guint8 digest[32]; + guint32 tmp[2]; + gsize len = sizeof (digest); + + g_return_val_if_fail (key_len, FALSE); + + /* Documentation suggests that this can fail. + * Maybe in case of a missing algorithm in crypto library? */ + sum = g_checksum_new (G_CHECKSUM_SHA256); + if (!sum) { + g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Can't create a SHA256 hash"); + return FALSE; + } + + key_len = CLAMP (key_len, 0, G_MAXUINT32); + + g_checksum_update (sum, addr->s6_addr, 8); + g_checksum_update (sum, (const guchar *) ifname, strlen (ifname) + 1); + if (!uuid) + uuid = ""; + g_checksum_update (sum, (const guchar *) uuid, strlen (uuid) + 1); + tmp[0] = htonl (dad_counter); + tmp[1] = htonl (key_len); + g_checksum_update (sum, (const guchar *) tmp, sizeof (tmp)); + g_checksum_update (sum, (const guchar *) secret_key, key_len); + + g_checksum_get_digest (sum, digest, &len); + g_checksum_free (sum); + + g_return_val_if_fail (len == 32, FALSE); + + memcpy (addr->s6_addr + 8, &digest[0], 8); + + return TRUE; +} + +#define RFC7217_IDGEN_RETRIES 3 +/** + * nm_utils_ipv6_addr_set_stable_privacy: + * + * Extend the address prefix with an interface identifier using the + * RFC 7217 Stable Privacy mechanism. + * + * Returns: %TRUE on success, %FALSE if the address could not be generated. + */ +gboolean +nm_utils_ipv6_addr_set_stable_privacy (struct in6_addr *addr, + const char *ifname, + const char *uuid, + guint dad_counter, + GError **error) +{ + gchar *secret_key = NULL; + gsize key_len = 0; + gboolean success = FALSE; + + if (dad_counter >= RFC7217_IDGEN_RETRIES) { + g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Too many DAD collisions"); + return FALSE; + } + + /* Let's try to load a saved secret key first. */ + if (g_file_get_contents (NMSTATEDIR "/secret_key", &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 { + int urandom = open ("/dev/urandom", O_RDONLY); + mode_t key_mask; + + if (!urandom) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Can't open /dev/urandom: %s", strerror (errno)); + return FALSE; + } + + /* 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); + + key_mask = umask (0077); + if (read (urandom, secret_key, key_len) == key_len) { + if (!g_file_set_contents (NMSTATEDIR "/secret_key", secret_key, key_len, error)) { + g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key"); + key_len = 0; + } + } else { + g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Could not obtain a secret"); + key_len = 0; + } + umask (key_mask); + close (urandom); + } + + if (key_len) { + success = _set_stable_privacy (addr, ifname, uuid, dad_counter, + secret_key, key_len, error); + } + + g_free (secret_key); + return success; +} + /** * nm_utils_setpgid: * @unused: unused diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index 3da223555..2d29b5bb9 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -286,6 +286,12 @@ gboolean nm_utils_get_ipv6_interface_identifier (NMLinkType link_type, void nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr, const NMUtilsIPv6IfaceId iid); +gboolean nm_utils_ipv6_addr_set_stable_privacy (struct in6_addr *addr, + const char *ifname, + const char *uuid, + guint dad_counter, + GError **error); + void nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid, const struct in6_addr *addr); diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 4ce5eb462..4b6d2d45f 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "nm-default.h" #include "nm-device.h" @@ -325,6 +326,7 @@ typedef struct { NMIP6Config * ac_ip6_config; guint linklocal6_timeout_id; + guint8 linklocal6_dad_counter; GHashTable * ip6_saved_properties; @@ -4832,9 +4834,11 @@ check_and_add_ipv6ll_addr (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); int ip_ifindex = nm_device_get_ip_ifindex (self); - NMUtilsIPv6IfaceId iid; struct in6_addr lladdr; guint i, n; + NMConnection *connection; + NMSettingIP6Config *s_ip6 = NULL; + GError *error = NULL; if (priv->nm_ipv6ll == FALSE) return; @@ -4853,21 +4857,43 @@ check_and_add_ipv6ll_addr (NMDevice *self) } } - if (!nm_device_get_ip_iface_identifier (self, &iid)) { - _LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 may be broken"); - return; - } - memset (&lladdr, 0, sizeof (lladdr)); lladdr.s6_addr16[0] = htons (0xfe80); - nm_utils_ipv6_addr_set_interface_identfier (&lladdr, iid); - if (priv->linklocal6_timeout_id) { - /* We already started and attempt to add a LL address. For the EUI-64 - * mode we can't pick a new one, we'll just fail. */ - _LOGW (LOGD_IP6, "linklocal6: DAD failed for an EUI-64 address"); - linklocal6_failed (self); - return; + connection = nm_device_get_applied_connection (self); + if (connection) + s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection)); + + if (s_ip6 && nm_setting_ip6_config_get_addr_gen_mode (s_ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) { + if (!nm_utils_ipv6_addr_set_stable_privacy (&lladdr, + nm_device_get_iface (self), + nm_connection_get_uuid (connection), + priv->linklocal6_dad_counter++, + &error)) { + _LOGW (LOGD_IP6, "linklocal6: failed to generate an address: %s", error->message); + g_clear_error (&error); + linklocal6_failed (self); + return; + } + _LOGD (LOGD_IP6, "linklocal6: using IPv6 stable-privacy addressing"); + } else { + NMUtilsIPv6IfaceId iid; + + if (priv->linklocal6_timeout_id) { + /* We already started and attempt to add a LL address. For the EUI-64 + * mode we can't pick a new one, we'll just fail. */ + _LOGW (LOGD_IP6, "linklocal6: DAD failed for an EUI-64 address"); + linklocal6_failed (self); + return; + } + + if (!nm_device_get_ip_iface_identifier (self, &iid)) { + _LOGW (LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue"); + return; + } + _LOGD (LOGD_IP6, "linklocal6: using EUI-64 identifier to generate IPv6LL address"); + + nm_utils_ipv6_addr_set_interface_identfier (&lladdr, iid); } _LOGD (LOGD_IP6, "linklocal6: adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL)); @@ -5143,10 +5169,15 @@ addrconf6_start_with_link_ready (NMDevice *self) g_assert (priv->rdisc); if (nm_platform_link_get_ipv6_token (NM_PLATFORM_GET, priv->ifindex, &iid)) { - _LOGD (LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface); - } else if (!nm_device_get_ip_iface_identifier (self, &iid)) { - _LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 cannot continue"); - return FALSE; + _LOGD (LOGD_IP6, "addrconf6: IPv6 tokenized identifier present"); + nm_rdisc_set_iid (priv->rdisc, iid); + } else if (nm_device_get_ip_iface_identifier (self, &iid)) { + _LOGD (LOGD_IP6, "addrconf6: using the device EUI-64 identifier"); + nm_rdisc_set_iid (priv->rdisc, iid); + } else { + /* Don't abort the addrconf at this point -- if rdisc needs the iid + * it will notice this itself. */ + _LOGI (LOGD_IP6, "addrconf6: no interface identifier; IPv6 adddress creation may fail"); } /* Apply any manual configuration before starting RA */ @@ -5167,7 +5198,6 @@ addrconf6_start_with_link_ready (NMDevice *self) G_CALLBACK (rdisc_ra_timeout), self); - nm_rdisc_set_iid (priv->rdisc, iid); nm_rdisc_start (priv->rdisc); return TRUE; } @@ -5178,7 +5208,7 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr) NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMConnection *connection; NMActStageReturn ret; - const char *ip_iface = nm_device_get_ip_iface (self); + NMSettingIP6Config *s_ip6 = NULL; connection = nm_device_get_applied_connection (self); g_assert (connection); @@ -5189,9 +5219,15 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr) priv->ac_ip6_config = NULL; } - priv->rdisc = nm_lndp_rdisc_new (nm_device_get_ip_ifindex (self), ip_iface); + s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection)); + g_assert (s_ip6); + + priv->rdisc = nm_lndp_rdisc_new (nm_device_get_ip_ifindex (self), + nm_device_get_ip_iface (self), + nm_connection_get_uuid (connection), + nm_setting_ip6_config_get_addr_gen_mode (s_ip6)); if (!priv->rdisc) { - _LOGE (LOGD_IP6, "failed to start router discovery (%s)", ip_iface); + _LOGE (LOGD_IP6, "addrconf6: failed to start router discovery"); return FALSE; } @@ -8627,6 +8663,8 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type) priv->v4_commit_first_time = TRUE; priv->v6_commit_first_time = TRUE; + priv->linklocal6_dad_counter = 0; + nm_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), self); nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), self); @@ -8875,6 +8913,9 @@ nm_device_spawn_iface_helper (NMDevice *self) g_ptr_array_add (argv, hex_iid); } + g_ptr_array_add (argv, g_strdup ("--addr-gen-mode")); + g_ptr_array_add (argv, g_strdup_printf ("%d", nm_setting_ip6_config_get_addr_gen_mode (NM_SETTING_IP6_CONFIG (s_ip6)))); + configured = TRUE; } diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c index 93ea17e53..fdd714f91 100644 --- a/src/nm-iface-helper.c +++ b/src/nm-iface-helper.c @@ -44,6 +44,7 @@ extern unsigned int if_nametoindex (const char *__ifname); #include "nm-rdisc.h" #include "nm-lndp-rdisc.h" #include "nm-utils.h" +#include "nm-setting-ip6-config.h" #if !defined(NM_DIST_VERSION) # define NM_DIST_VERSION VERSION @@ -69,6 +70,7 @@ static struct { char *dhcp4_clientid; char *dhcp4_hostname; char *iid_str; + NMSettingIP6ConfigAddrGenMode addr_gen_mode; char *logging_backend; char *opt_log_level; char *opt_log_domains; @@ -292,6 +294,7 @@ do_early_setup (int *argc, char **argv[]) { "priority4", '\0', 0, G_OPTION_ARG_INT64, &priority64_v4, N_("Route priority for IPv4"), N_("0") }, { "priority6", '\0', 0, G_OPTION_ARG_INT64, &priority64_v6, N_("Route priority for IPv6"), N_("1024") }, { "iid", 'e', 0, G_OPTION_ARG_STRING, &global_opt.iid_str, N_("Hex-encoded Interface Identifier"), "" }, + { "addr-gen-mode", 'e', 0, G_OPTION_ARG_INT, &global_opt.addr_gen_mode, N_("IPv6 SLAAC address generation mode"), "eui64" }, { "logging-backend", '\0', 0, G_OPTION_ARG_STRING, &global_opt.logging_backend, N_("The logging backend configuration value. See logging.backend in NetworkManager.conf"), NULL }, /* Logging/debugging */ @@ -470,7 +473,7 @@ main (int argc, char *argv[]) if (global_opt.slaac) { nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, ifindex, TRUE); - rdisc = nm_lndp_rdisc_new (ifindex, global_opt.ifname); + rdisc = nm_lndp_rdisc_new (ifindex, global_opt.ifname, global_opt.uuid, global_opt.addr_gen_mode); g_assert (rdisc); if (iid) diff --git a/src/rdisc/nm-lndp-rdisc.c b/src/rdisc/nm-lndp-rdisc.c index fead72cba..f0f56abb4 100644 --- a/src/rdisc/nm-lndp-rdisc.c +++ b/src/rdisc/nm-lndp-rdisc.c @@ -297,7 +297,7 @@ ipv6_sysctl_get (const char *ifname, const char *property, gint32 defval) } NMRDisc * -nm_lndp_rdisc_new (int ifindex, const char *ifname) +nm_lndp_rdisc_new (int ifindex, const char *ifname, const char *uuid, NMSettingIP6ConfigAddrGenMode addr_gen_mode) { NMRDisc *rdisc; NMLNDPRDiscPrivate *priv; @@ -307,6 +307,8 @@ nm_lndp_rdisc_new (int ifindex, const char *ifname) rdisc->ifindex = ifindex; rdisc->ifname = g_strdup (ifname); + rdisc->uuid = g_strdup (uuid); + rdisc->addr_gen_mode = addr_gen_mode; rdisc->max_addresses = ipv6_sysctl_get (ifname, "max_addresses", NM_RDISC_MAX_ADDRESSES_DEFAULT); diff --git a/src/rdisc/nm-lndp-rdisc.h b/src/rdisc/nm-lndp-rdisc.h index 407e1f925..4172a3043 100644 --- a/src/rdisc/nm-lndp-rdisc.h +++ b/src/rdisc/nm-lndp-rdisc.h @@ -44,6 +44,6 @@ typedef struct { GType nm_lndp_rdisc_get_type (void); -NMRDisc *nm_lndp_rdisc_new (int ifindex, const char *ifname); +NMRDisc *nm_lndp_rdisc_new (int ifindex, const char *ifname, const char *uuid, NMSettingIP6ConfigAddrGenMode addr_gen_mode); #endif /* __NETWORKMANAGER_LNDP_RDISC_H__ */ diff --git a/src/rdisc/nm-rdisc.c b/src/rdisc/nm-rdisc.c index 66982f116..37cc9ca8c 100644 --- a/src/rdisc/nm-rdisc.c +++ b/src/rdisc/nm-rdisc.c @@ -30,6 +30,8 @@ #include "nm-default.h" #include "nm-utils.h" +#include + #define _NMLOG_PREFIX_NAME "rdisc" typedef struct { @@ -104,6 +106,23 @@ nm_rdisc_add_gateway (NMRDisc *rdisc, const NMRDiscGateway *new) static gboolean complete_address (NMRDisc *rdisc, NMRDiscAddress *addr) { + GError *error = NULL; + + if (rdisc->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) { + if (!nm_utils_ipv6_addr_set_stable_privacy (&addr->address, + rdisc->ifname, + rdisc->uuid, + addr->dad_counter++, + &error)) { + _LOGW ("complete-address: failed to generate an stable-privacy address: %s", + error->message); + g_clear_error (&error); + return FALSE; + } + _LOGD ("complete-address: using an stable-privacy address"); + return TRUE; + } + if (!rdisc->iid.id) { _LOGW ("complete-address: can't generate an EUI-64 address: no interface identifier"); return FALSE; @@ -268,7 +287,10 @@ nm_rdisc_add_dns_domain (NMRDisc *rdisc, const NMRDiscDNSDomain *new) * the old identifier are removed. The caller should ensure the addresses * will be reset by soliciting router advertisements. * - * Returns: %TRUE if the token was changed, %FALSE otherwise. + * In case the stable privacy addressing is used %FALSE is returned and + * addresses are left untouched. + * + * Returns: %TRUE if addresses need to be regenerated, %FALSE otherwise. **/ gboolean nm_rdisc_set_iid (NMRDisc *rdisc, const NMUtilsIPv6IfaceId iid) @@ -277,6 +299,10 @@ nm_rdisc_set_iid (NMRDisc *rdisc, const NMUtilsIPv6IfaceId iid) if (rdisc->iid.id != iid.id) { rdisc->iid = iid; + + if (rdisc->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) + return FALSE; + if (rdisc->addresses->len) { _LOGD ("IPv6 interface identifier changed, flushing addresses"); g_array_remove_range (rdisc->addresses, 0, rdisc->addresses->len); @@ -693,6 +719,7 @@ finalize (GObject *object) NMRDisc *rdisc = NM_RDISC (object); g_free (rdisc->ifname); + g_free (rdisc->uuid); g_array_unref (rdisc->gateways); g_array_unref (rdisc->addresses); g_array_unref (rdisc->routes); diff --git a/src/rdisc/nm-rdisc.h b/src/rdisc/nm-rdisc.h index 4802e240f..c150a7291 100644 --- a/src/rdisc/nm-rdisc.h +++ b/src/rdisc/nm-rdisc.h @@ -26,6 +26,7 @@ #include #include "nm-default.h" +#include "nm-setting-ip6-config.h" #include "NetworkManagerUtils.h" #define NM_TYPE_RDISC (nm_rdisc_get_type ()) @@ -115,6 +116,8 @@ typedef struct { int ifindex; char *ifname; + char *uuid; + NMSettingIP6ConfigAddrGenMode addr_gen_mode; NMUtilsIPv6IfaceId iid; gint32 max_addresses; gint32 rtr_solicitations; diff --git a/src/rdisc/tests/test-rdisc-linux.c b/src/rdisc/tests/test-rdisc-linux.c index 2d8cd080a..a5b68494d 100644 --- a/src/rdisc/tests/test-rdisc-linux.c +++ b/src/rdisc/tests/test-rdisc-linux.c @@ -61,7 +61,10 @@ main (int argc, char **argv) return EXIT_FAILURE; } - rdisc = nm_lndp_rdisc_new (ifindex, ifname); + rdisc = nm_lndp_rdisc_new (ifindex, + ifname, + "8ce666e8-d34d-4fb1-b858-f15a7al28086", + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64); if (!rdisc) { g_print ("Failed to create NMRDisc instance\n"); return EXIT_FAILURE; diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index c4dd716f3..219e0270b 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -24,7 +24,8 @@ noinst_PROGRAMS = \ test-route-manager-fake \ test-dcb \ test-resolvconf-capture \ - test-wired-defname + test-wired-defname \ + test-utils ####### ip4 config test ####### @@ -110,6 +111,22 @@ test_wired_defname_SOURCES = \ test_wired_defname_LDADD = \ $(top_builddir)/src/libNetworkManager.la +####### utils test ####### + +test_utils_SOURCES = \ + test-utils.c + +test_utils_DEPENDENCIES = \ + $(top_srcdir)/src/NetworkManagerUtils.c + +test_utils_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DPREFIX=\"/nonexistent\" \ + -DNMSTATEDIR=\"/nonsense\" + +test_utils_LDADD = \ + $(top_builddir)/src/libNetworkManager.la + ####### secret agent interface test ####### EXTRA_DIST = test-secret-agent.py @@ -126,7 +143,8 @@ TESTS = \ test-resolvconf-capture \ test-general \ test-general-with-expect \ - test-wired-defname + test-wired-defname \ + test-utils if ENABLE_TESTS diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c new file mode 100644 index 000000000..79a7e6803 --- /dev/null +++ b/src/tests/test-utils.c @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2015 Red Hat, Inc. + * + */ + +#include "config.h" + +#include +#include +#include + +#include "NetworkManagerUtils.c" + +#include "nm-test-utils.h" + +static void +test_stable_privacy (void) +{ + struct in6_addr addr1; + + inet_pton (AF_INET6, "1234::", &addr1); + _set_stable_privacy (&addr1, "eth666", "6b138152-9f3e-4b97-aaf7-e6e553f2a24e", 0, "key", 3, NULL); + nmtst_assert_ip6_address (&addr1, "1234::4ceb:14cd:3d54:793f"); + + /* We get an address without the UUID. */ + inet_pton (AF_INET6, "1::", &addr1); + _set_stable_privacy (&addr1, "eth666", NULL, 384, "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); + _set_stable_privacy (&addr1, "eth666", NULL, 384, "key", 3, NULL); + nmtst_assert_ip6_address (&addr1, "2::338e:8d:c11:8726"); +} + +/*******************************************/ + +NMTST_DEFINE (); + +int +main (int argc, char **argv) +{ + nmtst_init_with_logging (&argc, &argv, NULL, "ALL"); + + g_test_add_func ("/utils/stable_privacy", test_stable_privacy); + + return g_test_run (); +}