aboutsummaryrefslogtreecommitdiff
path: root/sys/netinet6/in6_ifattach.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/netinet6/in6_ifattach.c')
-rw-r--r--sys/netinet6/in6_ifattach.c284
1 files changed, 237 insertions, 47 deletions
diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c
index f284f7fa5ffc..090ba610460b 100644
--- a/sys/netinet6/in6_ifattach.c
+++ b/sys/netinet6/in6_ifattach.c
@@ -33,6 +33,7 @@
#include <sys/param.h>
#include <sys/systm.h>
+#include <sys/counter.h>
#include <sys/malloc.h>
#include <sys/socket.h>
#include <sys/sockio.h>
@@ -70,6 +71,9 @@
#include <netinet6/mld6_var.h>
#include <netinet6/scope6_var.h>
+#include <crypto/sha2/sha256.h>
+#include <machine/atomic.h>
+
#ifdef IP6_AUTO_LINKLOCAL
VNET_DEFINE(int, ip6_auto_linklocal) = IP6_AUTO_LINKLOCAL;
#else
@@ -79,11 +83,12 @@ VNET_DEFINE(int, ip6_auto_linklocal) = 1; /* enabled by default */
VNET_DEFINE(struct callout, in6_tmpaddrtimer_ch);
#define V_in6_tmpaddrtimer_ch VNET(in6_tmpaddrtimer_ch)
+VNET_DEFINE(int, ip6_stableaddr_netifsource) = IP6_STABLEADDR_NETIFSRC_NAME; /* Use interface name by default */
+
VNET_DECLARE(struct inpcbinfo, ripcbinfo);
#define V_ripcbinfo VNET(ripcbinfo)
static int get_rand_ifid(struct ifnet *, struct in6_addr *);
-static int get_ifid(struct ifnet *, struct ifnet *, struct in6_addr *);
static int in6_ifattach_linklocal(struct ifnet *, struct ifnet *);
static int in6_ifattach_loopback(struct ifnet *);
static void in6_purgemaddrs(struct ifnet *);
@@ -99,6 +104,9 @@ static void in6_purgemaddrs(struct ifnet *);
#define IFID_LOCAL(in6) (!EUI64_LOCAL(in6))
#define IFID_UNIVERSAL(in6) (!EUI64_UNIVERSAL(in6))
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
/*
* Generate a last-resort interface identifier, when the machine has no
* IEEE802/EUI64 address sources.
@@ -148,22 +156,14 @@ get_rand_ifid(struct ifnet *ifp, struct in6_addr *in6)
}
-/*
- * Get interface identifier for the specified interface.
- * XXX assumes single sockaddr_dl (AF_LINK address) per an interface
- *
- * in6 - upper 64bits are preserved
+/**
+ * Get interface link level sockaddr
*/
-int
-in6_get_hw_ifid(struct ifnet *ifp, struct in6_addr *in6)
+static struct sockaddr_dl *
+get_interface_link_level(struct ifnet *ifp)
{
struct ifaddr *ifa;
struct sockaddr_dl *sdl;
- u_int8_t *addr;
- size_t addrlen;
- static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
- static u_int8_t allone[8] =
- { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
NET_EPOCH_ASSERT();
@@ -176,14 +176,30 @@ in6_get_hw_ifid(struct ifnet *ifp, struct in6_addr *in6)
if (sdl->sdl_alen == 0)
continue;
- goto found;
+ return sdl;
}
- return -1;
+ return NULL;
+}
+
+/*
+ * Get hwaddr from link interface
+ */
+static uint8_t *
+in6_get_interface_hwaddr(struct ifnet *ifp, size_t *len)
+{
+ struct sockaddr_dl *sdl;
+ u_int8_t *addr;
+ static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ static u_int8_t allone[8] =
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+ sdl = get_interface_link_level(ifp);
+ if (sdl == NULL)
+ return (NULL);
-found:
addr = LLADDR(sdl);
- addrlen = sdl->sdl_alen;
+ *len = sdl->sdl_alen;
/* get EUI64 */
switch (ifp->if_type) {
@@ -194,36 +210,21 @@ found:
case IFT_IEEE1394:
/* IEEE802/EUI64 cases - what others? */
/* IEEE1394 uses 16byte length address starting with EUI64 */
- if (addrlen > 8)
- addrlen = 8;
+ if (*len > 8)
+ *len = 8;
/* look at IEEE802/EUI64 only */
- if (addrlen != 8 && addrlen != 6)
- return -1;
+ if (*len != 8 && *len != 6)
+ return (NULL);
/*
* check for invalid MAC address - on bsdi, we see it a lot
* since wildboar configures all-zero MAC on pccard before
* card insertion.
*/
- if (bcmp(addr, allzero, addrlen) == 0)
- return -1;
- if (bcmp(addr, allone, addrlen) == 0)
- return -1;
-
- /* make EUI64 address */
- if (addrlen == 8)
- bcopy(addr, &in6->s6_addr[8], 8);
- else if (addrlen == 6) {
- in6->s6_addr[8] = addr[0];
- in6->s6_addr[9] = addr[1];
- in6->s6_addr[10] = addr[2];
- in6->s6_addr[11] = 0xff;
- in6->s6_addr[12] = 0xfe;
- in6->s6_addr[13] = addr[3];
- in6->s6_addr[14] = addr[4];
- in6->s6_addr[15] = addr[5];
- }
+ if (memcmp(addr, allzero, *len) == 0 || memcmp(addr, allone, *len) == 0)
+ return (NULL);
+
break;
case IFT_GIF:
@@ -234,16 +235,51 @@ found:
* identifier source (can be renumbered).
* we don't do this.
*/
- return -1;
+ return (NULL);
case IFT_INFINIBAND:
- if (addrlen != 20)
- return -1;
- bcopy(addr + 12, &in6->s6_addr[8], 8);
+ if (*len != 20)
+ return (NULL);
+ *len = 8;
+ addr += 12;
break;
default:
+ return (NULL);
+ }
+
+ return addr;
+}
+
+ /*
+ * Get interface identifier for the specified interface.
+ * XXX assumes single sockaddr_dl (AF_LINK address) per an interface
+ *
+ * in6 - upper 64bits are preserved
+ */
+int
+in6_get_hw_ifid(struct ifnet *ifp, struct in6_addr *in6)
+{
+ size_t hwaddr_len;
+ uint8_t *hwaddr;
+ static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ hwaddr = in6_get_interface_hwaddr(ifp, &hwaddr_len);
+ if (hwaddr == NULL || (hwaddr_len != 6 && hwaddr_len != 8))
return -1;
+
+ /* make EUI64 address */
+ if (hwaddr_len == 8)
+ memcpy(&in6->s6_addr[8], hwaddr, 8);
+ else if (hwaddr_len == 6) {
+ in6->s6_addr[8] = hwaddr[0];
+ in6->s6_addr[9] = hwaddr[1];
+ in6->s6_addr[10] = hwaddr[2];
+ in6->s6_addr[11] = 0xff;
+ in6->s6_addr[12] = 0xfe;
+ in6->s6_addr[13] = hwaddr[3];
+ in6->s6_addr[14] = hwaddr[4];
+ in6->s6_addr[15] = hwaddr[5];
}
/* sanity check: g bit must not indicate "group" */
@@ -265,21 +301,175 @@ found:
}
/*
+ * Validate generated interface id to make sure it does not fall in any reserved range:
+ *
+ * https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml
+ */
+static bool
+validate_ifid(uint8_t *iid)
+{
+ static uint8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ static uint8_t reserved_eth[5] = { 0x02, 0x00, 0x5E, 0xFF, 0xFE };
+ static uint8_t reserved_anycast[7] = { 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+ /* Subnet-Router Anycast (RFC 4291)*/
+ if (memcmp(iid, allzero, 8) == 0)
+ return (false);
+
+ /*
+ * Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block (RFC 4291)
+ * and
+ * Proxy Mobile IPv6 (RFC 6543)
+ */
+ if (memcmp(iid, reserved_eth, 5) == 0)
+ return (false);
+
+ /* Reserved Subnet Anycast Addresses (RFC 2526) */
+ if (memcmp(iid, reserved_anycast, 7) == 0 && iid[7] >= 0x80)
+ return (false);
+
+ return (true);
+}
+
+/*
+ * Get interface identifier for the specified interface, according to
+ * RFC 7217 Stable and Opaque IDs with SLAAC, using HMAC-SHA256 digest.
+ *
+ * in6 - upper 64bits are preserved
+ */
+bool
+in6_get_stableifid(struct ifnet *ifp, struct in6_addr *in6, int prefixlen)
+{
+ struct sockaddr_dl *sdl;
+ const uint8_t *netiface;
+ size_t netiface_len, hostuuid_len;
+ uint8_t hostuuid[HOSTUUIDLEN + 1], hmac_key[SHA256_BLOCK_LENGTH],
+ hk_ipad[SHA256_BLOCK_LENGTH], hk_opad[SHA256_BLOCK_LENGTH];
+ uint64_t dad_failures;
+ SHA256_CTX ctxt;
+
+ switch (V_ip6_stableaddr_netifsource) {
+ case IP6_STABLEADDR_NETIFSRC_ID:
+ sdl = get_interface_link_level(ifp);
+ if (sdl == NULL)
+ return (false);
+ netiface = (uint8_t *)&LLINDEX(sdl);
+ netiface_len = sizeof(u_short); /* real return type of LLINDEX */
+ break;
+
+ case IP6_STABLEADDR_NETIFSRC_MAC:
+ netiface = in6_get_interface_hwaddr(ifp, &netiface_len);
+ if (netiface == NULL)
+ return (false);
+ break;
+
+ case IP6_STABLEADDR_NETIFSRC_NAME:
+ default:
+ netiface = (const uint8_t *)if_name(ifp);
+ netiface_len = strlen(netiface);
+ break;
+ }
+
+ /* Use hostuuid as constant "secret" key */
+ getcredhostuuid(curthread->td_ucred, hostuuid, sizeof(hostuuid));
+ if (strncmp(hostuuid, DEFAULT_HOSTUUID, sizeof(hostuuid)) == 0) {
+ // If hostuuid is not set, use a random value
+ arc4rand(hostuuid, HOSTUUIDLEN, 0);
+ hostuuid[HOSTUUIDLEN] = '\0';
+ }
+ hostuuid_len = strlen(hostuuid);
+
+ dad_failures = atomic_load_int(&DAD_FAILURES(ifp));
+
+ /*
+ * RFC 7217 section 7
+ *
+ * default max retries
+ */
+ if (dad_failures > V_ip6_stableaddr_maxretries)
+ return (false);
+
+ /*
+ * Use hostuuid as basis for HMAC key
+ */
+ memset(hmac_key, 0, sizeof(hmac_key));
+ if (hostuuid_len <= SHA256_BLOCK_LENGTH) {
+ /* copy to hmac key variable, zero padded */
+ memcpy(hmac_key, hostuuid, hostuuid_len);
+ } else {
+ /* if longer than block length, use hash of the value, zero padded */
+ SHA256_Init(&ctxt);
+ SHA256_Update(&ctxt, hostuuid, hostuuid_len);
+ SHA256_Final(hmac_key, &ctxt);
+ }
+ /* XOR key with ipad and opad values */
+ for (uint16_t i = 0; i < sizeof(hmac_key); i++) {
+ hk_ipad[i] = hmac_key[i] ^ HMAC_IPAD;
+ hk_opad[i] = hmac_key[i] ^ HMAC_OPAD;
+ }
+
+ /*
+ * Generate interface id in a loop, adding an offset to be factored in the hash function.
+ * This is necessary, because if the generated interface id happens to be invalid we
+ * want to force the hash function to generate a different one, otherwise we would end up
+ * in an infinite loop trying the same invalid interface id over and over again.
+ *
+ * Using an uint8 counter for the offset, so limit iteration at UINT8_MAX. This is a safety
+ * measure, this will never iterate more than once or twice in practice.
+ */
+ for(uint8_t offset = 0; offset < UINT8_MAX; offset++) {
+ uint8_t digest[SHA256_DIGEST_LENGTH];
+
+ /* Calculate inner hash */
+ SHA256_Init(&ctxt);
+ SHA256_Update(&ctxt, hk_ipad, sizeof(hk_ipad));
+ SHA256_Update(&ctxt, in6->s6_addr, prefixlen / 8);
+ SHA256_Update(&ctxt, netiface, netiface_len);
+ SHA256_Update(&ctxt, (uint8_t *)&dad_failures, 8);
+ SHA256_Update(&ctxt, hostuuid, hostuuid_len);
+ SHA256_Update(&ctxt, &offset, 1);
+ SHA256_Final(digest, &ctxt);
+
+ /* Calculate outer hash */
+ SHA256_Init(&ctxt);
+ SHA256_Update(&ctxt, hk_opad, sizeof(hk_opad));
+ SHA256_Update(&ctxt, digest, sizeof(digest));
+ SHA256_Final(digest, &ctxt);
+
+ if (validate_ifid(digest)) {
+ /* assumes sizeof(digest) > sizeof(ifid) */
+ memcpy(&in6->s6_addr[8], digest, 8);
+
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+/*
* Get interface identifier for the specified interface. If it is not
* available on ifp0, borrow interface identifier from other information
* sources.
*
* altifp - secondary EUI64 source
*/
-static int
-get_ifid(struct ifnet *ifp0, struct ifnet *altifp,
+int
+in6_get_ifid(struct ifnet *ifp0, struct ifnet *altifp,
struct in6_addr *in6)
{
struct ifnet *ifp;
NET_EPOCH_ASSERT();
- /* first, try to get it from the interface itself */
+ /* first, try to get it from the interface itself, with stable algorithm, if configured */
+ if ((ND_IFINFO(ifp0)->flags & ND6_IFF_STABLEADDR) && in6_get_stableifid(ifp0, in6, 64) == 0) {
+ nd6log((LOG_DEBUG, "%s: got interface identifier from itself (stable private)\n",
+ if_name(ifp0)));
+ goto success;
+ }
+
+ /* then/otherwise try to get it from the interface itself */
if (in6_get_hw_ifid(ifp0, in6) == 0) {
nd6log((LOG_DEBUG, "%s: got interface identifier from itself\n",
if_name(ifp0)));
@@ -356,7 +546,7 @@ in6_ifattach_linklocal(struct ifnet *ifp, struct ifnet *altifp)
ifra.ifra_addr.sin6_addr.s6_addr32[3] = htonl(1);
} else {
NET_EPOCH_ENTER(et);
- error = get_ifid(ifp, altifp, &ifra.ifra_addr.sin6_addr);
+ error = in6_get_ifid(ifp, altifp, &ifra.ifra_addr.sin6_addr);
NET_EPOCH_EXIT(et);
if (error != 0) {
nd6log((LOG_ERR,