core: use Interface Identifiers for IPv6 SLAAC addresses

Ethernet-like interfaces aren't the only type of interfaces that can
run IPv6 but the rdisc code only returns an address if the interface's
hardware address is 6 bytes.

Interface types like PPP (rfc5072) and IPoIB (rfc4391) have their own
specifications for constructing IPv6 addresses and we should honor
those.

So instead of expecting a MAC address, let each device subclass
generate an Interface Identifier and use that for rdisc instead.
This commit is contained in:
Dan Williams
2014-04-10 15:29:45 -05:00
parent 27f91d054c
commit e2270040c0
10 changed files with 203 additions and 47 deletions

View File

@@ -29,6 +29,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <linux/if.h> #include <linux/if.h>
#include <linux/if_infiniband.h>
#include "NetworkManagerUtils.h" #include "NetworkManagerUtils.h"
#include "nm-utils.h" #include "nm-utils.h"
@@ -1596,3 +1597,119 @@ nm_utils_is_specific_hostname (const char *name)
return FALSE; return FALSE;
} }
/******************************************************************/
/* Returns the "u" (universal/local) bit value for a Modified EUI-64 */
static gboolean
get_gre_eui64_u_bit (guint32 addr)
{
static const struct {
guint32 mask;
guint32 result;
} items[] = {
{ 0xff000000 }, { 0x7f000000 }, /* IPv4 loopback */
{ 0xf0000000 }, { 0xe0000000 }, /* IPv4 multicast */
{ 0xffffff00 }, { 0xe0000000 }, /* IPv4 local multicast */
{ 0xffffffff }, { INADDR_BROADCAST }, /* limited broadcast */
{ 0xff000000 }, { 0x00000000 }, /* zero net */
{ 0xff000000 }, { 0x0a000000 }, /* private 10 (RFC3330) */
{ 0xfff00000 }, { 0xac100000 }, /* private 172 */
{ 0xffff0000 }, { 0xc0a80000 }, /* private 192 */
{ 0xffff0000 }, { 0xa9fe0000 }, /* IPv4 link-local */
{ 0xffffff00 }, { 0xc0586300 }, /* anycast 6-to-4 */
{ 0xffffff00 }, { 0xc0000200 }, /* test 192 */
{ 0xfffe0000 }, { 0xc6120000 }, /* test 198 */
};
guint i;
for (i = 0; i < G_N_ELEMENTS (items); i++) {
if ((addr & htonl (items[i].mask)) == htonl (items[i].result))
return 0x00; /* "local" scope */
}
return 0x02; /* "universal" scope */
}
/**
* nm_utils_get_ipv6_interface_identifier:
* @link_type: the hardware link type
* @hwaddr: the hardware address of the interface
* @hwaddr_len: the length (in bytes) of @hwaddr
* @out_iid: on success, filled with the interface identifier; on failure
* zeroed out
*
* Constructs an interface identifier in "Modified EUI-64" format which is
* suitable for constructing IPv6 addresses. Note that the identifier is
* not obscured in any way (eg, RFC3041).
*
* Returns: %TRUE if the interface identifier could be constructed, %FALSE if
* if could not be constructed.
*/
gboolean
nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
const guint8 *hwaddr,
guint hwaddr_len,
NMUtilsIPv6IfaceId *out_iid)
{
guint32 addr;
g_return_val_if_fail (hwaddr != NULL, FALSE);
g_return_val_if_fail (hwaddr_len > 0, FALSE);
g_return_val_if_fail (out_iid != NULL, FALSE);
out_iid->id = 0;
switch (link_type) {
case NM_LINK_TYPE_INFINIBAND:
/* Use the port GUID per http://tools.ietf.org/html/rfc4391#section-8,
* making sure to set the 'u' bit to 1. The GUID is the lower 64 bits
* of the IPoIB interface's hardware address.
*/
g_return_val_if_fail (hwaddr_len == INFINIBAND_ALEN, FALSE);
memcpy (out_iid->id_u8, hwaddr + INFINIBAND_ALEN - 8, 8);
out_iid->id_u8[0] |= 0x02;
return TRUE;
case NM_LINK_TYPE_GRE:
case NM_LINK_TYPE_GRETAP:
/* Hardware address is the network-endian IPv4 address */
g_return_val_if_fail (hwaddr_len == 4, FALSE);
addr = * (guint32 *) hwaddr;
out_iid->id_u8[0] = get_gre_eui64_u_bit (addr);
out_iid->id_u8[1] = 0x00;
out_iid->id_u8[2] = 0x5E;
out_iid->id_u8[3] = 0xFE;
memcpy (out_iid->id_u8 + 4, &addr, 4);
return TRUE;
default:
if (hwaddr_len == ETH_ALEN) {
/* Translate 48-bit MAC address to a 64-bit Modified EUI-64. See
* http://tools.ietf.org/html/rfc4291#appendix-A
*/
out_iid->id_u8[0] = hwaddr[0] ^ 0x02;
out_iid->id_u8[1] = hwaddr[1];
out_iid->id_u8[2] = hwaddr[2];
out_iid->id_u8[3] = 0xff;
out_iid->id_u8[4] = 0xfe;
out_iid->id_u8[5] = hwaddr[3];
out_iid->id_u8[6] = hwaddr[4];
out_iid->id_u8[7] = hwaddr[5];
return TRUE;
}
break;
}
return FALSE;
}
void
nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr,
const NMUtilsIPv6IfaceId iid)
{
memcpy (addr->s6_addr + 8, &iid.id_u8, 8);
}
void
nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid,
const struct in6_addr *addr)
{
memcpy (iid, addr->s6_addr + 8, 8);
}

View File

@@ -27,6 +27,7 @@
#include <net/ethernet.h> #include <net/ethernet.h>
#include "nm-connection.h" #include "nm-connection.h"
#include "nm-platform.h"
gboolean nm_ethernet_address_is_valid (const struct ether_addr *test_addr); gboolean nm_ethernet_address_is_valid (const struct ether_addr *test_addr);
@@ -131,4 +132,35 @@ const char *nm_utils_ip6_property_path (const char *ifname, const char *property
gboolean nm_utils_is_specific_hostname (const char *name); gboolean nm_utils_is_specific_hostname (const char *name);
/* IPv6 Interface Identifer helpers */
/**
* NMUtilsIPv6IfaceId:
* @id: convenience member for validity checking; never use directly
* @id_u8: the 64-bit Interface Identifier
*
* Holds a 64-bit IPv6 Interface Identifier. The IID is a sequence of bytes
* and should not normally be treated as a %guint64, but this is done for
* convenience of validity checking and initialization.
*/
typedef struct {
union {
guint64 id;
guint8 id_u8[8];
};
} NMUtilsIPv6IfaceId;
#define NM_UTILS_IPV6_IFACE_ID_INIT { .id = 0 };
gboolean nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
const guint8 *hwaddr,
guint len,
NMUtilsIPv6IfaceId *out_iid);
void nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr,
const NMUtilsIPv6IfaceId iid);
void nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid,
const struct in6_addr *addr);
#endif /* NETWORK_MANAGER_UTILS_H */ #endif /* NETWORK_MANAGER_UTILS_H */

View File

@@ -5,6 +5,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/src/ \ -I$(top_srcdir)/src/ \
-I$(top_srcdir)/src/config \ -I$(top_srcdir)/src/config \
-I$(top_srcdir)/src/devices \ -I$(top_srcdir)/src/devices \
-I${top_srcdir}/src/platform \
-DG_LOG_DOMAIN=\""NetworkManager"\" \ -DG_LOG_DOMAIN=\""NetworkManager"\" \
-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \
$(GLIB_CFLAGS) \ $(GLIB_CFLAGS) \

View File

@@ -575,6 +575,38 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface)
g_free (old_ip_iface); g_free (old_ip_iface);
} }
static gboolean
get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
{
NMLinkType link_type;
const guint8 *hwaddr = NULL;
size_t hwaddr_len = 0;
int ifindex;
gboolean success;
/* If we get here, we *must* have a kernel netdev, which implies an ifindex */
ifindex = nm_device_get_ip_ifindex (self);
g_assert (ifindex);
link_type = nm_platform_link_get_type (ifindex);
g_return_val_if_fail (link_type > NM_LINK_TYPE_UNKNOWN, 0);
hwaddr = nm_platform_link_get_address (ifindex, &hwaddr_len);
if (!hwaddr_len)
return FALSE;
success = nm_utils_get_ipv6_interface_identifier (link_type,
hwaddr,
hwaddr_len,
out_iid);
if (!success) {
nm_log_warn (LOGD_HW, "(%s): failed to generate interface identifier "
"for link type %u hwaddr_len %zu",
nm_device_get_ip_iface (self), link_type, hwaddr_len);
}
return success;
}
const char * const char *
nm_device_get_driver (NMDevice *self) nm_device_get_driver (NMDevice *self)
{ {
@@ -3647,15 +3679,14 @@ static void
addrconf6_start_with_link_ready (NMDevice *self) addrconf6_start_with_link_ready (NMDevice *self)
{ {
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
const guint8 *hw_addr; NMUtilsIPv6IfaceId iid;
size_t hw_addr_len = 0;
g_assert (priv->rdisc); g_assert (priv->rdisc);
/* FIXME: what if interface has no lladdr, like PPP? */ if (NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, &iid))
hw_addr = nm_platform_link_get_address (nm_device_get_ip_ifindex (self), &hw_addr_len); nm_rdisc_set_iid (priv->rdisc, iid);
if (hw_addr_len) else
nm_rdisc_set_lladdr (priv->rdisc, (const char *) hw_addr, hw_addr_len); nm_log_warn (LOGD_IP6, "(%s): failed to get interface identifier", nm_device_get_ip_iface (self));
nm_device_ipv6_sysctl_set (self, "accept_ra", "1"); nm_device_ipv6_sysctl_set (self, "accept_ra", "1");
nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0"); nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
@@ -3664,7 +3695,6 @@ addrconf6_start_with_link_ready (NMDevice *self)
priv->rdisc_config_changed_sigid = g_signal_connect (priv->rdisc, NM_RDISC_CONFIG_CHANGED, priv->rdisc_config_changed_sigid = g_signal_connect (priv->rdisc, NM_RDISC_CONFIG_CHANGED,
G_CALLBACK (rdisc_config_changed), self); G_CALLBACK (rdisc_config_changed), self);
nm_rdisc_start (priv->rdisc); nm_rdisc_start (priv->rdisc);
} }
@@ -7611,6 +7641,7 @@ nm_device_class_init (NMDeviceClass *klass)
klass->bring_up = bring_up; klass->bring_up = bring_up;
klass->take_down = take_down; klass->take_down = take_down;
klass->carrier_changed = carrier_changed; klass->carrier_changed = carrier_changed;
klass->get_ip_iface_identifier = get_ip_iface_identifier;
/* Properties */ /* Properties */
g_object_class_install_property g_object_class_install_property

View File

@@ -30,6 +30,7 @@
#include "nm-types.h" #include "nm-types.h"
#include "nm-connection.h" #include "nm-connection.h"
#include "nm-rfkill-manager.h" #include "nm-rfkill-manager.h"
#include "NetworkManagerUtils.h"
/* Properties */ /* Properties */
#define NM_DEVICE_UDI "udi" #define NM_DEVICE_UDI "udi"
@@ -119,6 +120,8 @@ typedef struct {
void (* update_permanent_hw_address) (NMDevice *self); void (* update_permanent_hw_address) (NMDevice *self);
void (* update_initial_hw_address) (NMDevice *self); void (* update_initial_hw_address) (NMDevice *self);
gboolean (* get_ip_iface_identifier) (NMDevice *self, NMUtilsIPv6IfaceId *out_iid);
guint32 (* get_generic_capabilities) (NMDevice *self); guint32 (* get_generic_capabilities) (NMDevice *self);
gboolean (* is_available) (NMDevice *self); gboolean (* is_available) (NMDevice *self);

View File

@@ -3,6 +3,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/libnm-util \ -I$(top_srcdir)/libnm-util \
-I$(top_builddir)/libnm-util \ -I$(top_builddir)/libnm-util \
-I$(top_srcdir)/src/logging \ -I$(top_srcdir)/src/logging \
-I${top_srcdir}/src/platform \
-I$(top_srcdir)/src \ -I$(top_srcdir)/src \
-I$(top_srcdir)/src/devices/wifi \ -I$(top_srcdir)/src/devices/wifi \
-I$(top_builddir)/src \ -I$(top_builddir)/src \

View File

@@ -433,43 +433,18 @@ translate_preference (enum ndp_route_preference preference)
} }
} }
static void
fill_address_from_mac (struct in6_addr *address, const char *mac)
{
unsigned char *identifier = address->s6_addr + 8;
if (!mac)
return;
/* Translate 48-bit MAC address to a 64-bit modified interface identifier
* and write it to the second half of the IPv6 address.
*
* See http://tools.ietf.org/html/rfc3513#page-21
*/
memcpy (identifier, mac, 3);
identifier[0] ^= 0x02;
identifier[3] = 0xff;
identifier[4] = 0xfe;
memcpy (identifier + 5, mac + 3, 3);
}
static int static int
receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
{ {
NMRDisc *rdisc = (NMRDisc *) user_data; NMRDisc *rdisc = (NMRDisc *) user_data;
NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
NMRDiscConfigMap changed = 0; NMRDiscConfigMap changed = 0;
size_t lladdrlen = 0;
const char *lladdr = NULL;
struct ndp_msgra *msgra = ndp_msgra (msg); struct ndp_msgra *msgra = ndp_msgra (msg);
NMRDiscGateway gateway; NMRDiscGateway gateway;
guint32 now = nm_utils_get_monotonic_timestamp_s (); guint32 now = nm_utils_get_monotonic_timestamp_s ();
int offset; int offset;
int hop_limit; int hop_limit;
if (rdisc->lladdr)
lladdr = g_bytes_get_data (rdisc->lladdr, &lladdrlen);
/* Router discovery is subject to the following RFC documents: /* Router discovery is subject to the following RFC documents:
* *
* http://tools.ietf.org/html/rfc4861 * http://tools.ietf.org/html/rfc4861
@@ -542,7 +517,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
/* Address */ /* Address */
if (ndp_msg_opt_prefix_flag_auto_addr_conf (msg, offset)) { if (ndp_msg_opt_prefix_flag_auto_addr_conf (msg, offset)) {
if (route.plen == 64 && lladdrlen == 6) { if (route.plen == 64 && rdisc->iid.id) {
memset (&address, 0, sizeof (address)); memset (&address, 0, sizeof (address));
address.address = route.network; address.address = route.network;
address.timestamp = now; address.timestamp = now;
@@ -551,7 +526,8 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
if (address.preferred > address.lifetime) if (address.preferred > address.lifetime)
address.preferred = address.lifetime; address.preferred = address.lifetime;
fill_address_from_mac (&address.address, lladdr); /* Add the Interface Identifier to the lower 64 bits */
nm_utils_ipv6_addr_set_interface_identfier (&address.address, rdisc->iid);
if (add_address (rdisc, &address)) if (add_address (rdisc, &address))
changed |= NM_RDISC_CONFIG_ADDRESSES; changed |= NM_RDISC_CONFIG_ADDRESSES;

View File

@@ -40,11 +40,11 @@ static guint signals[LAST_SIGNAL] = { 0 };
/******************************************************************/ /******************************************************************/
void void
nm_rdisc_set_lladdr (NMRDisc *rdisc, const char *addr, size_t addrlen) nm_rdisc_set_iid (NMRDisc *rdisc, const NMUtilsIPv6IfaceId iid)
{ {
if (rdisc->lladdr) g_return_if_fail (NM_IS_RDISC (rdisc));
g_bytes_unref (rdisc->lladdr);
rdisc->lladdr = addr ? g_bytes_new (addr, addrlen) : NULL; rdisc->iid = iid;
} }
void void
@@ -152,7 +152,6 @@ nm_rdisc_init (NMRDisc *rdisc)
rdisc->routes = g_array_new (FALSE, FALSE, sizeof (NMRDiscRoute)); rdisc->routes = g_array_new (FALSE, FALSE, sizeof (NMRDiscRoute));
rdisc->dns_servers = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSServer)); rdisc->dns_servers = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSServer));
rdisc->dns_domains = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSDomain)); rdisc->dns_domains = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSDomain));
rdisc->lladdr = NULL;
rdisc->hop_limit = 64; rdisc->hop_limit = 64;
} }
@@ -167,9 +166,6 @@ nm_rdisc_finalize (GObject *object)
g_array_unref (rdisc->routes); g_array_unref (rdisc->routes);
g_array_unref (rdisc->dns_servers); g_array_unref (rdisc->dns_servers);
g_array_unref (rdisc->dns_domains); g_array_unref (rdisc->dns_domains);
if (rdisc->lladdr)
g_bytes_unref (rdisc->lladdr);
} }
static void static void

View File

@@ -26,6 +26,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <netinet/in.h> #include <netinet/in.h>
#include "NetworkManagerUtils.h"
#define NM_TYPE_RDISC (nm_rdisc_get_type ()) #define NM_TYPE_RDISC (nm_rdisc_get_type ())
#define NM_RDISC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_RDISC, NMRDisc)) #define NM_RDISC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_RDISC, NMRDisc))
#define NM_RDISC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_RDISC, NMRDiscClass)) #define NM_RDISC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_RDISC, NMRDiscClass))
@@ -110,7 +112,7 @@ typedef struct {
int ifindex; int ifindex;
char *ifname; char *ifname;
GBytes *lladdr; NMUtilsIPv6IfaceId iid;
gint32 max_addresses; gint32 max_addresses;
gint32 rtr_solicitations; gint32 rtr_solicitations;
gint32 rtr_solicitation_interval; gint32 rtr_solicitation_interval;
@@ -133,7 +135,7 @@ typedef struct {
GType nm_rdisc_get_type (void); GType nm_rdisc_get_type (void);
void nm_rdisc_set_lladdr (NMRDisc *rdisc, const char *addr, size_t addrlen); void nm_rdisc_set_iid (NMRDisc *rdisc, const NMUtilsIPv6IfaceId iid);
void nm_rdisc_start (NMRDisc *rdisc); void nm_rdisc_start (NMRDisc *rdisc);
#endif /* NM_RDISC_H */ #endif /* NM_RDISC_H */

View File

@@ -37,7 +37,6 @@ main (int argc, char **argv)
NMRDisc *(*new) (int ifindex, const char *ifname); NMRDisc *(*new) (int ifindex, const char *ifname);
int ifindex = 1; int ifindex = 1;
const char *ifname; const char *ifname;
char mac[6] = { 0x02, 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
#if !GLIB_CHECK_VERSION (2, 35, 0) #if !GLIB_CHECK_VERSION (2, 35, 0)
g_type_init (); g_type_init ();
@@ -69,8 +68,6 @@ main (int argc, char **argv)
if (!rdisc) if (!rdisc)
return EXIT_FAILURE; return EXIT_FAILURE;
nm_rdisc_set_lladdr (rdisc, mac, 6);
nm_rdisc_start (rdisc); nm_rdisc_start (rdisc);
g_main_loop_run (loop); g_main_loop_run (loop);