diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index 888141c65c..ed604bf0f4 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "NetworkManagerUtils.h" #include "nm-utils.h" @@ -1596,3 +1597,119 @@ nm_utils_is_specific_hostname (const char *name) 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); +} + diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index f1cd62a263..6e80069f57 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -27,6 +27,7 @@ #include #include "nm-connection.h" +#include "nm-platform.h" 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); +/* 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 */ diff --git a/src/config/tests/Makefile.am b/src/config/tests/Makefile.am index dc0f308bab..31ce724395 100644 --- a/src/config/tests/Makefile.am +++ b/src/config/tests/Makefile.am @@ -5,6 +5,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/ \ -I$(top_srcdir)/src/config \ -I$(top_srcdir)/src/devices \ + -I${top_srcdir}/src/platform \ -DG_LOG_DOMAIN=\""NetworkManager"\" \ -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ $(GLIB_CFLAGS) \ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index f638f09b95..f9b65929f6 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -575,6 +575,38 @@ nm_device_set_ip_iface (NMDevice *self, const char *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 * nm_device_get_driver (NMDevice *self) { @@ -3647,15 +3679,14 @@ static void addrconf6_start_with_link_ready (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - const guint8 *hw_addr; - size_t hw_addr_len = 0; + NMUtilsIPv6IfaceId iid; g_assert (priv->rdisc); - /* FIXME: what if interface has no lladdr, like PPP? */ - hw_addr = nm_platform_link_get_address (nm_device_get_ip_ifindex (self), &hw_addr_len); - if (hw_addr_len) - nm_rdisc_set_lladdr (priv->rdisc, (const char *) hw_addr, hw_addr_len); + if (NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, &iid)) + nm_rdisc_set_iid (priv->rdisc, iid); + else + 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_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, G_CALLBACK (rdisc_config_changed), self); - nm_rdisc_start (priv->rdisc); } @@ -7611,6 +7641,7 @@ nm_device_class_init (NMDeviceClass *klass) klass->bring_up = bring_up; klass->take_down = take_down; klass->carrier_changed = carrier_changed; + klass->get_ip_iface_identifier = get_ip_iface_identifier; /* Properties */ g_object_class_install_property diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h index 871764531f..ed0c06a7be 100644 --- a/src/devices/nm-device.h +++ b/src/devices/nm-device.h @@ -30,6 +30,7 @@ #include "nm-types.h" #include "nm-connection.h" #include "nm-rfkill-manager.h" +#include "NetworkManagerUtils.h" /* Properties */ #define NM_DEVICE_UDI "udi" @@ -119,6 +120,8 @@ typedef struct { void (* update_permanent_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); gboolean (* is_available) (NMDevice *self); diff --git a/src/devices/wifi/tests/Makefile.am b/src/devices/wifi/tests/Makefile.am index 7cdd149650..4b9b9dc242 100644 --- a/src/devices/wifi/tests/Makefile.am +++ b/src/devices/wifi/tests/Makefile.am @@ -3,6 +3,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/libnm-util \ -I$(top_builddir)/libnm-util \ -I$(top_srcdir)/src/logging \ + -I${top_srcdir}/src/platform \ -I$(top_srcdir)/src \ -I$(top_srcdir)/src/devices/wifi \ -I$(top_builddir)/src \ diff --git a/src/rdisc/nm-lndp-rdisc.c b/src/rdisc/nm-lndp-rdisc.c index 0f0a00e86c..87934e65a8 100644 --- a/src/rdisc/nm-lndp-rdisc.c +++ b/src/rdisc/nm-lndp-rdisc.c @@ -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 receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) { NMRDisc *rdisc = (NMRDisc *) user_data; NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); NMRDiscConfigMap changed = 0; - size_t lladdrlen = 0; - const char *lladdr = NULL; struct ndp_msgra *msgra = ndp_msgra (msg); NMRDiscGateway gateway; guint32 now = nm_utils_get_monotonic_timestamp_s (); int offset; int hop_limit; - if (rdisc->lladdr) - lladdr = g_bytes_get_data (rdisc->lladdr, &lladdrlen); - /* Router discovery is subject to the following RFC documents: * * http://tools.ietf.org/html/rfc4861 @@ -542,7 +517,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) /* Address */ 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)); address.address = route.network; address.timestamp = now; @@ -551,7 +526,8 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) if (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)) changed |= NM_RDISC_CONFIG_ADDRESSES; diff --git a/src/rdisc/nm-rdisc.c b/src/rdisc/nm-rdisc.c index 1682924c2a..d3dc14a194 100644 --- a/src/rdisc/nm-rdisc.c +++ b/src/rdisc/nm-rdisc.c @@ -40,11 +40,11 @@ static guint signals[LAST_SIGNAL] = { 0 }; /******************************************************************/ 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_bytes_unref (rdisc->lladdr); - rdisc->lladdr = addr ? g_bytes_new (addr, addrlen) : NULL; + g_return_if_fail (NM_IS_RDISC (rdisc)); + + rdisc->iid = iid; } void @@ -152,7 +152,6 @@ nm_rdisc_init (NMRDisc *rdisc) rdisc->routes = g_array_new (FALSE, FALSE, sizeof (NMRDiscRoute)); rdisc->dns_servers = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSServer)); rdisc->dns_domains = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSDomain)); - rdisc->lladdr = NULL; rdisc->hop_limit = 64; } @@ -167,9 +166,6 @@ nm_rdisc_finalize (GObject *object) g_array_unref (rdisc->routes); g_array_unref (rdisc->dns_servers); g_array_unref (rdisc->dns_domains); - - if (rdisc->lladdr) - g_bytes_unref (rdisc->lladdr); } static void diff --git a/src/rdisc/nm-rdisc.h b/src/rdisc/nm-rdisc.h index fba06bc215..642e7c9670 100644 --- a/src/rdisc/nm-rdisc.h +++ b/src/rdisc/nm-rdisc.h @@ -26,6 +26,8 @@ #include #include +#include "NetworkManagerUtils.h" + #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_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_RDISC, NMRDiscClass)) @@ -110,7 +112,7 @@ typedef struct { int ifindex; char *ifname; - GBytes *lladdr; + NMUtilsIPv6IfaceId iid; gint32 max_addresses; gint32 rtr_solicitations; gint32 rtr_solicitation_interval; @@ -133,7 +135,7 @@ typedef struct { 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); #endif /* NM_RDISC_H */ diff --git a/src/rdisc/tests/rdisc.c b/src/rdisc/tests/rdisc.c index 1fdf5b67a5..26237093df 100644 --- a/src/rdisc/tests/rdisc.c +++ b/src/rdisc/tests/rdisc.c @@ -37,7 +37,6 @@ main (int argc, char **argv) NMRDisc *(*new) (int ifindex, const char *ifname); int ifindex = 1; const char *ifname; - char mac[6] = { 0x02, 0xaa, 0xbb, 0xcc, 0xdd, 0xee }; #if !GLIB_CHECK_VERSION (2, 35, 0) g_type_init (); @@ -69,8 +68,6 @@ main (int argc, char **argv) if (!rdisc) return EXIT_FAILURE; - nm_rdisc_set_lladdr (rdisc, mac, 6); - nm_rdisc_start (rdisc); g_main_loop_run (loop);