core: add support for RFC7217 stable privacy addressing

RFC7217 introduces an alternative mechanism for creating addresses during
stateless IPv6 address configuration. It's supposed to create addresses whose
host part stays stable in a particular network but changes when the hosts
enters another network to mitigate possibility of tracking the host movement.

It can be used alongside RFC 4941 privacy extensions (temporary addresses)
and replaces the use of RFC 4862 interface identifiers.

The address creation mode is controlld by ip6.addr_gen_mode property
(ADDR_GEN_MODE in ifcfg-rh), with values of "stable-privacy" and "eui-64",
defaulting to "eui-64" if unspecified.

The host part of an address is computed by hashing a system-specific secret
salted with various stable values that identify the connection with a secure
hash algorithm:

  RID = F(Prefix, Net_Iface, Network_ID, DAD_Counter, secret_key)

For NetworkManager we use these parameters:

* F()
  SHA256 hash function.

* Prefix
  This is a network part of the /64 address

* Net_Iface
  We use the interface name (e.g. "eth0"). This ensures the address won't
  change with the change of interface hardware.

* Network_ID
  We use the connection UUID here. This ensures the salt is different for
  wireless networks with a different SSID as suggested by RFC7217.

* DAD_Counter
  A per-address counter that increases with each DAD failure.

* secret_key
  We store the secret key in /var/lib/NetworkManager/secret_key. If it's
  shorter than 128 bits then it's rejected. If the file is not present we
  initialize it by fetching 256 pseudo-random bits from /dev/urandom on
  first use.

Duplicate address detection uses IDGEN_RETRIES = 3 and does not utilize the
IDGEN_DELAY delay (despite it SHOULD). This is for ease of implementation
and may change in future. Neither parameter is currently configurable.
This commit is contained in:
Lubomir Rintel
2015-10-03 19:44:27 +02:00
parent f85728ecff
commit e603c86926
11 changed files with 314 additions and 29 deletions

View File

@@ -29,6 +29,7 @@
#include <resolv.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <linux/if.h>
#include <linux/if_infiniband.h>
@@ -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

View File

@@ -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);

View File

@@ -32,6 +32,7 @@
#include <arpa/inet.h>
#include <fcntl.h>
#include <netlink/route/addr.h>
#include <linux/if_addr.h>
#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,14 +4857,27 @@ 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);
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
@@ -4870,6 +4887,15 @@ check_and_add_ipv6ll_addr (NMDevice *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));
if (!nm_platform_ip6_address_add (NM_PLATFORM_GET,
ip_ifindex,
@@ -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;
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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__ */

View File

@@ -30,6 +30,8 @@
#include "nm-default.h"
#include "nm-utils.h"
#include <nm-setting-ip6-config.h>
#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);

View File

@@ -26,6 +26,7 @@
#include <netinet/in.h>
#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;

View File

@@ -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;

View File

@@ -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

63
src/tests/test-utils.c Normal file
View File

@@ -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 <string.h>
#include <errno.h>
#include <arpa/inet.h>
#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 ();
}