Bladeren bron

Some cleanup, revision to Endpoint string format, stub out HTTP API callbacks.

Adam Ierymenko 5 jaren geleden
bovenliggende
commit
40d48c969c
7 gewijzigde bestanden met toevoegingen van 243 en 128 verwijderingen
  1. 38 18
      core/Endpoint.cpp
  2. 71 80
      core/Identity.cpp
  3. 20 17
      core/MAC.hpp
  4. 35 0
      core/Node.cpp
  5. 11 0
      core/Node.hpp
  6. 13 4
      core/Utils.hpp
  7. 55 9
      core/zerotier.h

+ 38 - 18
core/Endpoint.cpp

@@ -30,14 +30,14 @@ char *Endpoint::toString(char s[ZT_ENDPOINT_STRING_SIZE_MAX]) const noexcept
 			break;
 		case ZT_ENDPOINT_TYPE_ZEROTIER:
 			s[0] = s_endpointTypeChars[ZT_ENDPOINT_TYPE_ZEROTIER];
-			s[1] = '=';
+			s[1] = '/';
 			zt().toString(s + 2);
 			break;
 		case ZT_ENDPOINT_TYPE_ETHERNET:
 		case ZT_ENDPOINT_TYPE_WIFI_DIRECT:
 		case ZT_ENDPOINT_TYPE_BLUETOOTH:
 			s[0] = s_endpointTypeChars[this->type];
-			s[1] = '=';
+			s[1] = '/';
 			eth().toString(s + 2);
 			break;
 		case ZT_ENDPOINT_TYPE_IP:
@@ -45,7 +45,7 @@ char *Endpoint::toString(char s[ZT_ENDPOINT_STRING_SIZE_MAX]) const noexcept
 		case ZT_ENDPOINT_TYPE_IP_TCP:
 		case ZT_ENDPOINT_TYPE_IP_HTTP:
 			s[0] = s_endpointTypeChars[this->type];
-			s[1] = '=';
+			s[1] = '/';
 			ip().toString(s + 2);
 			break;
 	}
@@ -59,13 +59,32 @@ bool Endpoint::fromString(const char *s) noexcept
 	if ((!s) || (!*s))
 		return true;
 
-	const char *start = strchr(s, '=');
-	if (start++ != nullptr) {
-		// Parse a fully qualified type-address format Endpoint.
+	// Locate first slash, colon, and dot to help classify input.
+	const char *slash = nullptr, *colon = nullptr, *dot = nullptr;
+	for(const char *p=s;;++p) {
+		const char c = *p;
+		if (c != 0) {
+			switch (c) {
+				case '/':
+					slash = p;
+					break;
+				case ':':
+					colon = p;
+					break;
+				case '.':
+					dot = p;
+					break;
+			}
+		} else break;
+	}
+
+	if ((slash != nullptr) && (((colon == nullptr) && (dot == nullptr)) || (colon > slash) || (dot > slash))) {
+		// Detect a fully specified endpoint of the form type/ip/port or type/other,
+		// but don't detect ip/port as a fully specified endpoint.
 		char tmp[16];
 		for (unsigned int i=0;i<16;++i) {
-			char ss = s[i];
-			if (ss == '-') {
+			const char ss = s[i];
+			if (ss == '/') {
 				tmp[i] = 0;
 				break;
 			}
@@ -74,32 +93,33 @@ bool Endpoint::fromString(const char *s) noexcept
 		tmp[15] = 0;
 		this->type = (ZT_EndpointType)Utils::strToUInt(tmp);
 
-		Fingerprint tmpfp;
-		MAC tmpmac;
+		++slash;
 		switch (this->type) {
 			case ZT_ENDPOINT_TYPE_NIL:
 				break;
-			case ZT_ENDPOINT_TYPE_ZEROTIER:
-				if (!tmpfp.fromString(start))
+			case ZT_ENDPOINT_TYPE_ZEROTIER: {
+				Fingerprint tmpfp;
+				if (!tmpfp.fromString(slash))
 					return false;
 				this->value.fp = tmpfp;
-				break;
+			} break;
 			case ZT_ENDPOINT_TYPE_ETHERNET:
 			case ZT_ENDPOINT_TYPE_WIFI_DIRECT:
-			case ZT_ENDPOINT_TYPE_BLUETOOTH:
-				tmpmac.fromString(start);
+			case ZT_ENDPOINT_TYPE_BLUETOOTH: {
+				MAC tmpmac;
+				tmpmac.fromString(slash);
 				this->value.mac = tmpmac.toInt();
-				break;
+			} break;
 			case ZT_ENDPOINT_TYPE_IP:
 			case ZT_ENDPOINT_TYPE_IP_UDP:
 			case ZT_ENDPOINT_TYPE_IP_TCP:
 			case ZT_ENDPOINT_TYPE_IP_HTTP:
-				if (!asInetAddress(this->value.ss).fromString(start))
+				if (!asInetAddress(this->value.ss).fromString(slash))
 					return false;
 			default:
 				return false;
 		}
-	} else if ((strchr(s, ':')) || (strchr(s, '.'))) {
+	} else if (((colon != nullptr) || (dot != nullptr)) && (slash != nullptr)) {
 		// Parse raw IP/port strings as IP_UDP endpoints.
 		this->type = ZT_ENDPOINT_TYPE_IP_UDP;
 		if (!asInetAddress(this->value.ss).fromString(s))

+ 71 - 80
core/Identity.cpp

@@ -37,29 +37,29 @@ void identityV0ProofOfWorkFrankenhash(const void *const publicKey, unsigned int
 	// Initialize genmem[] using Salsa20 in a CBC-like configuration since
 	// ordinary Salsa20 is randomly seek-able. This is good for a cipher
 	// but is not what we want for sequential memory-hardness.
-	Utils::zero<ZT_V0_IDENTITY_GEN_MEMORY>(genmem);
-	Salsa20 s20(digest, (char *) digest + 32);
-	s20.crypt20((char *) genmem, (char *) genmem, 64);
-	for (unsigned long i = 64;i < ZT_V0_IDENTITY_GEN_MEMORY;i += 64) {
+	Utils::zero< ZT_V0_IDENTITY_GEN_MEMORY >(genmem);
+	Salsa20 s20(digest, (char *)digest + 32);
+	s20.crypt20((char *)genmem, (char *)genmem, 64);
+	for (unsigned long i = 64; i < ZT_V0_IDENTITY_GEN_MEMORY; i += 64) {
 		unsigned long k = i - 64;
-		*((uint64_t * )((char *) genmem + i)) = *((uint64_t * )((char *) genmem + k));
-		*((uint64_t * )((char *) genmem + i + 8)) = *((uint64_t * )((char *) genmem + k + 8));
-		*((uint64_t * )((char *) genmem + i + 16)) = *((uint64_t * )((char *) genmem + k + 16));
-		*((uint64_t * )((char *) genmem + i + 24)) = *((uint64_t * )((char *) genmem + k + 24));
-		*((uint64_t * )((char *) genmem + i + 32)) = *((uint64_t * )((char *) genmem + k + 32));
-		*((uint64_t * )((char *) genmem + i + 40)) = *((uint64_t * )((char *) genmem + k + 40));
-		*((uint64_t * )((char *) genmem + i + 48)) = *((uint64_t * )((char *) genmem + k + 48));
-		*((uint64_t * )((char *) genmem + i + 56)) = *((uint64_t * )((char *) genmem + k + 56));
-		s20.crypt20((char *) genmem + i, (char *) genmem + i, 64);
+		*((uint64_t *)((char *)genmem + i)) = *((uint64_t *)((char *)genmem + k));
+		*((uint64_t *)((char *)genmem + i + 8)) = *((uint64_t *)((char *)genmem + k + 8));
+		*((uint64_t *)((char *)genmem + i + 16)) = *((uint64_t *)((char *)genmem + k + 16));
+		*((uint64_t *)((char *)genmem + i + 24)) = *((uint64_t *)((char *)genmem + k + 24));
+		*((uint64_t *)((char *)genmem + i + 32)) = *((uint64_t *)((char *)genmem + k + 32));
+		*((uint64_t *)((char *)genmem + i + 40)) = *((uint64_t *)((char *)genmem + k + 40));
+		*((uint64_t *)((char *)genmem + i + 48)) = *((uint64_t *)((char *)genmem + k + 48));
+		*((uint64_t *)((char *)genmem + i + 56)) = *((uint64_t *)((char *)genmem + k + 56));
+		s20.crypt20((char *)genmem + i, (char *)genmem + i, 64);
 	}
 
 	// Render final digest using genmem as a lookup table
-	for (unsigned long i = 0;i < (ZT_V0_IDENTITY_GEN_MEMORY / sizeof(uint64_t));) {
-		unsigned long idx1 = (unsigned long) (Utils::ntoh(((uint64_t *) genmem)[i++]) % (64 / sizeof(uint64_t))); // NOLINT(hicpp-use-auto,modernize-use-auto)
-		unsigned long idx2 = (unsigned long) (Utils::ntoh(((uint64_t *) genmem)[i++]) % (ZT_V0_IDENTITY_GEN_MEMORY / sizeof(uint64_t))); // NOLINT(hicpp-use-auto,modernize-use-auto)
-		uint64_t tmp = ((uint64_t *) genmem)[idx2];
-		((uint64_t *) genmem)[idx2] = ((uint64_t *) digest)[idx1];
-		((uint64_t *) digest)[idx1] = tmp;
+	for (unsigned long i = 0; i < (ZT_V0_IDENTITY_GEN_MEMORY / sizeof(uint64_t));) {
+		unsigned long idx1 = (unsigned long)(Utils::ntoh(((uint64_t *)genmem)[i++]) % (64 / sizeof(uint64_t))); // NOLINT(hicpp-use-auto,modernize-use-auto)
+		unsigned long idx2 = (unsigned long)(Utils::ntoh(((uint64_t *)genmem)[i++]) % (ZT_V0_IDENTITY_GEN_MEMORY / sizeof(uint64_t))); // NOLINT(hicpp-use-auto,modernize-use-auto)
+		uint64_t tmp = ((uint64_t *)genmem)[idx2];
+		((uint64_t *)genmem)[idx2] = ((uint64_t *)digest)[idx1];
+		((uint64_t *)digest)[idx1] = tmp;
 		s20.crypt20(digest, digest, 64);
 	}
 }
@@ -86,7 +86,9 @@ struct p_CompareLittleEndian
 #if __BYTE_ORDER == __BIG_ENDIAN
 	ZT_INLINE bool operator()(const uint64_t a,const uint64_t b) const noexcept { return Utils::swapBytes(a) < Utils::swapBytes(b); }
 #else
-	ZT_INLINE bool operator()(const uint64_t a,const uint64_t b) const noexcept { return a < b; }
+	ZT_INLINE bool operator()(const uint64_t a, const uint64_t b) const noexcept
+	{ return a < b; }
+
 #endif
 };
 
@@ -100,7 +102,7 @@ bool identityV1ProofOfWorkCriteria(const void *in, const unsigned int len)
 	// executing all branches and then selecting the answer, which means this
 	// construction should require a GPU to do ~3X the work of a CPU per iteration.
 	SHA512(w, in, len);
-	for (unsigned int i = 8, j = 0;i < (ZT_IDENTITY_V1_POW_MEMORY_SIZE / 8);) {
+	for (unsigned int i = 8, j = 0; i < (ZT_IDENTITY_V1_POW_MEMORY_SIZE / 8);) {
 		uint64_t *const ww = w + i;
 		const uint64_t *const wp = w + j;
 		i += 8;
@@ -159,7 +161,8 @@ bool Identity::generate(const Type t)
 			delete[] genmem;
 			m_fp.address = address; // address comes from PoW hash for type 0 identities
 			m_computeHash();
-		} break;
+		}
+			break;
 
 		case P384: {
 			for (;;) {
@@ -185,7 +188,8 @@ bool Identity::generate(const Type t)
 					break;
 				}
 			}
-		} break;
+		}
+			break;
 
 		default:
 			return false;
@@ -201,7 +205,7 @@ bool Identity::locallyValidate() const noexcept
 			switch (m_type) {
 				case C25519: {
 					uint8_t digest[64];
-					char *const genmem = (char *) malloc(ZT_V0_IDENTITY_GEN_MEMORY);
+					char *const genmem = (char *)malloc(ZT_V0_IDENTITY_GEN_MEMORY);
 					if (!genmem)
 						return false;
 					identityV0ProofOfWorkFrankenhash(m_pub, ZT_C25519_COMBINED_PUBLIC_KEY_SIZE, digest, genmem);
@@ -225,14 +229,13 @@ void Identity::hashWithPrivate(uint8_t h[ZT_FINGERPRINT_HASH_SIZE]) const
 		switch (m_type) {
 			case C25519:
 				SHA384(h, m_pub, ZT_C25519_COMBINED_PUBLIC_KEY_SIZE, m_priv, ZT_C25519_COMBINED_PRIVATE_KEY_SIZE);
-				break;
+				return;
 			case P384:
 				SHA384(h, m_pub, sizeof(m_pub), m_priv, sizeof(m_priv));
-				break;
+				return;
 		}
-		return;
 	}
-	Utils::zero<48>(h);
+	Utils::zero< ZT_FINGERPRINT_HASH_SIZE >(h);
 }
 
 unsigned int Identity::sign(const void *data, unsigned int len, void *sig, unsigned int siglen) const
@@ -250,7 +253,7 @@ unsigned int Identity::sign(const void *data, unsigned int len, void *sig, unsig
 					static_assert(ZT_ECC384_SIGNATURE_HASH_SIZE == ZT_SHA384_DIGEST_SIZE, "weird!");
 					uint8_t h[ZT_ECC384_SIGNATURE_HASH_SIZE];
 					SHA384(h, data, len, m_pub, ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE);
-					ECC384ECDSASign(m_priv + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE, h, (uint8_t *) sig);
+					ECC384ECDSASign(m_priv + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE, h, (uint8_t *)sig);
 					return ZT_ECC384_SIGNATURE_SIZE;
 				}
 		}
@@ -267,7 +270,7 @@ bool Identity::verify(const void *data, unsigned int len, const void *sig, unsig
 			if (siglen == ZT_ECC384_SIGNATURE_SIZE) {
 				uint8_t h[ZT_ECC384_SIGNATURE_HASH_SIZE];
 				SHA384(h, data, len, m_pub, ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE);
-				return ECC384ECDSAVerify(m_pub + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE, h, (const uint8_t *) sig);
+				return ECC384ECDSAVerify(m_pub + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE, h, (const uint8_t *)sig);
 			}
 			break;
 	}
@@ -276,37 +279,25 @@ bool Identity::verify(const void *data, unsigned int len, const void *sig, unsig
 
 bool Identity::agree(const Identity &id, uint8_t key[ZT_SYMMETRIC_KEY_SIZE]) const
 {
-	uint8_t rawkey[128];
-	uint8_t h[64];
-	if (m_hasPrivate) {
-		if (m_type == C25519) {
-			if ((id.m_type == C25519) || (id.m_type == P384)) {
-				// If we are a C25519 key we can agree with another C25519 key or with only the
-				// C25519 portion of a type 1 P-384 key.
-				C25519::agree(m_priv, id.m_pub, rawkey);
-				SHA512(h, rawkey, ZT_C25519_ECDH_SHARED_SECRET_SIZE);
-				Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(key, h);
-				return true;
-			}
-		} else if (m_type == P384) {
-			if (id.m_type == P384) {
-				// For another P384 identity we execute DH agreement with BOTH keys and then
-				// hash the results together. For those (cough FIPS cough) who only consider
-				// P384 to be kosher, the C25519 secret can be considered a "salt"
-				// or something. For those who don't trust P384 this means the privacy of
-				// your traffic is also protected by C25519.
-				C25519::agree(m_priv, id.m_pub, rawkey);
-				ECC384ECDH(id.m_pub + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE, m_priv + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE, rawkey + ZT_C25519_ECDH_SHARED_SECRET_SIZE);
-				SHA384(h, rawkey, ZT_C25519_ECDH_SHARED_SECRET_SIZE + ZT_ECC384_SHARED_SECRET_SIZE);
-				Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(key, h);
-				return true;
-			} else if (id.m_type == C25519) {
-				// If the other identity is a C25519 identity we can agree using only that type.
-				C25519::agree(m_priv, id.m_pub, rawkey);
-				SHA512(h, rawkey, ZT_C25519_ECDH_SHARED_SECRET_SIZE);
-				Utils::copy<ZT_SYMMETRIC_KEY_SIZE>(key, h);
-				return true;
-			}
+	uint8_t rawkey[128], h[64];
+	if (likely(m_hasPrivate)) {
+		if ((m_type == C25519) || (id.m_type == C25519)) {
+			// If we are a C25519 key we can agree with another C25519 key or with only the
+			// C25519 portion of a type 1 P-384 key.
+			C25519::agree(m_priv, id.m_pub, rawkey);
+			SHA512(h, rawkey, ZT_C25519_ECDH_SHARED_SECRET_SIZE);
+			Utils::copy< ZT_SYMMETRIC_KEY_SIZE >(key, h);
+			return true;
+		} else if ((m_type == P384) && (id.m_type == P384)) {
+			// For another P384 identity we execute DH agreement with BOTH keys and then
+			// hash the results together. For those (cough FIPS cough) who only consider
+			// P384 to be kosher, the C25519 secret can be considered a "salt"
+			// or something. For those who don't trust P384 this means the privacy of
+			// your traffic is also protected by C25519.
+			C25519::agree(m_priv, id.m_pub, rawkey);
+			ECC384ECDH(id.m_pub + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE, m_priv + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE, rawkey + ZT_C25519_ECDH_SHARED_SECRET_SIZE);
+			SHA384(key, rawkey, ZT_C25519_ECDH_SHARED_SECRET_SIZE + ZT_ECC384_SHARED_SECRET_SIZE);
+			return true;
 		}
 	}
 	return false;
@@ -330,22 +321,22 @@ char *Identity::toString(bool includePrivate, char buf[ZT_IDENTITY_STRING_BUFFER
 				Utils::hex(m_priv, ZT_C25519_COMBINED_PRIVATE_KEY_SIZE, p);
 				p += ZT_C25519_COMBINED_PRIVATE_KEY_SIZE * 2;
 			}
-			*p = (char) 0;
+			*p = (char)0;
 			return buf;
 		}
 		case P384: {
 			*(p++) = '1';
 			*(p++) = ':';
-			int el = Utils::b32e(m_pub, sizeof(m_pub), p, (int) (ZT_IDENTITY_STRING_BUFFER_LENGTH - (uintptr_t) (p - buf)));
+			int el = Utils::b32e(m_pub, sizeof(m_pub), p, (int)(ZT_IDENTITY_STRING_BUFFER_LENGTH - (uintptr_t)(p - buf)));
 			if (el <= 0) return nullptr;
 			p += el;
 			if ((m_hasPrivate) && (includePrivate)) {
 				*(p++) = ':';
-				el = Utils::b32e(m_priv, sizeof(m_priv), p, (int) (ZT_IDENTITY_STRING_BUFFER_LENGTH - (uintptr_t) (p - buf)));
+				el = Utils::b32e(m_priv, sizeof(m_priv), p, (int)(ZT_IDENTITY_STRING_BUFFER_LENGTH - (uintptr_t)(p - buf)));
 				if (el <= 0) return nullptr;
 				p += el;
 			}
-			*p = (char) 0;
+			*p = (char)0;
 			return buf;
 		}
 	}
@@ -362,7 +353,7 @@ bool Identity::fromString(const char *str)
 
 	int fno = 0;
 	char *saveptr = nullptr;
-	for (char *f = Utils::stok(tmp, ":", &saveptr);((f) && (fno < 4));f = Utils::stok(nullptr, ":", &saveptr)) {
+	for (char *f = Utils::stok(tmp, ":", &saveptr); ((f) && (fno < 4)); f = Utils::stok(nullptr, ":", &saveptr)) {
 		switch (fno++) {
 
 			case 0:
@@ -437,11 +428,11 @@ int Identity::marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX], const bool inc
 	switch (m_type) {
 
 		case C25519:
-			data[ZT_ADDRESS_LENGTH] = (uint8_t) C25519;
-			Utils::copy<ZT_C25519_COMBINED_PUBLIC_KEY_SIZE>(data + ZT_ADDRESS_LENGTH + 1, m_pub);
+			data[ZT_ADDRESS_LENGTH] = (uint8_t)C25519;
+			Utils::copy< ZT_C25519_COMBINED_PUBLIC_KEY_SIZE >(data + ZT_ADDRESS_LENGTH + 1, m_pub);
 			if ((includePrivate) && (m_hasPrivate)) {
 				data[ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE] = ZT_C25519_COMBINED_PRIVATE_KEY_SIZE;
-				Utils::copy<ZT_C25519_COMBINED_PRIVATE_KEY_SIZE>(data + ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1, m_priv);
+				Utils::copy< ZT_C25519_COMBINED_PRIVATE_KEY_SIZE >(data + ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1, m_priv);
 				return ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1 + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE;
 			} else {
 				data[ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE] = 0;
@@ -449,11 +440,11 @@ int Identity::marshal(uint8_t data[ZT_IDENTITY_MARSHAL_SIZE_MAX], const bool inc
 			}
 
 		case P384:
-			data[ZT_ADDRESS_LENGTH] = (uint8_t) P384;
-			Utils::copy<ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE>(data + ZT_ADDRESS_LENGTH + 1, m_pub);
+			data[ZT_ADDRESS_LENGTH] = (uint8_t)P384;
+			Utils::copy< ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE >(data + ZT_ADDRESS_LENGTH + 1, m_pub);
 			if ((includePrivate) && (m_hasPrivate)) {
 				data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE] = ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE;
-				Utils::copy<ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE>(data + ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1, m_priv);
+				Utils::copy< ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE >(data + ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1, m_priv);
 				return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE;
 			} else {
 				data[ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE] = 0;
@@ -473,13 +464,13 @@ int Identity::unmarshal(const uint8_t *data, const int len) noexcept
 	m_fp.address = Address(data);
 
 	unsigned int privlen;
-	switch ((m_type = (Type) data[ZT_ADDRESS_LENGTH])) {
+	switch ((m_type = (Type)data[ZT_ADDRESS_LENGTH])) {
 
 		case C25519:
 			if (len < (ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1))
 				return -1;
 
-			Utils::copy<ZT_C25519_COMBINED_PUBLIC_KEY_SIZE>(m_pub, data + ZT_ADDRESS_LENGTH + 1);
+			Utils::copy< ZT_C25519_COMBINED_PUBLIC_KEY_SIZE >(m_pub, data + ZT_ADDRESS_LENGTH + 1);
 			m_computeHash();
 
 			privlen = data[ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE];
@@ -487,7 +478,7 @@ int Identity::unmarshal(const uint8_t *data, const int len) noexcept
 				if (len < (ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1 + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE))
 					return -1;
 				m_hasPrivate = true;
-				Utils::copy<ZT_C25519_COMBINED_PRIVATE_KEY_SIZE>(m_priv, data + ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1);
+				Utils::copy< ZT_C25519_COMBINED_PRIVATE_KEY_SIZE >(m_priv, data + ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1);
 				return ZT_ADDRESS_LENGTH + 1 + ZT_C25519_COMBINED_PUBLIC_KEY_SIZE + 1 + ZT_C25519_COMBINED_PRIVATE_KEY_SIZE;
 			} else if (privlen == 0) {
 				m_hasPrivate = false;
@@ -499,7 +490,7 @@ int Identity::unmarshal(const uint8_t *data, const int len) noexcept
 			if (len < (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1))
 				return -1;
 
-			Utils::copy<ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE>(m_pub, data + ZT_ADDRESS_LENGTH + 1);
+			Utils::copy< ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE >(m_pub, data + ZT_ADDRESS_LENGTH + 1);
 			m_computeHash(); // this sets the address for P384
 			if (Address(m_fp.hash) != m_fp.address) // this sanity check is possible with V1 identities
 				return -1;
@@ -509,7 +500,7 @@ int Identity::unmarshal(const uint8_t *data, const int len) noexcept
 				if (len < (ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE))
 					return -1;
 				m_hasPrivate = true;
-				Utils::copy<ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE>(&m_priv, data + ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1);
+				Utils::copy< ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE >(&m_priv, data + ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1);
 				return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE;
 			} else if (privlen == 0) {
 				m_hasPrivate = false;
@@ -547,7 +538,7 @@ ZT_Identity *ZT_Identity_new(enum ZT_IdentityType type)
 		return nullptr;
 	try {
 		ZeroTier::Identity *const id = new ZeroTier::Identity();
-		id->generate((ZeroTier::Identity::Type) type);
+		id->generate((ZeroTier::Identity::Type)type);
 		return reinterpret_cast<ZT_Identity *>(id);
 	} catch (...) {
 		return nullptr;
@@ -596,8 +587,8 @@ int ZT_Identity_verify(const ZT_Identity *id, const void *data, unsigned int len
 enum ZT_IdentityType ZT_Identity_type(const ZT_Identity *id)
 {
 	if (!id)
-		return (ZT_IdentityType) 0;
-	return (enum ZT_IdentityType) reinterpret_cast<const ZeroTier::Identity *>(id)->type();
+		return (ZT_IdentityType)0;
+	return (enum ZT_IdentityType)reinterpret_cast<const ZeroTier::Identity *>(id)->type();
 }
 
 char *ZT_Identity_toString(const ZT_Identity *id, char *buf, int capacity, int includePrivate)

+ 20 - 17
core/MAC.hpp

@@ -32,7 +32,7 @@ public:
 	{}
 
 	ZT_INLINE MAC(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_t d, const uint8_t e, const uint8_t f) noexcept:
-		m_mac((((uint64_t) a) << 40U) | (((uint64_t) b) << 32U) | (((uint64_t) c) << 24U) | (((uint64_t) d) << 16U) | (((uint64_t) e) << 8U) | ((uint64_t) f))
+		m_mac((((uint64_t)a) << 40U) | (((uint64_t)b) << 32U) | (((uint64_t)c) << 24U) | (((uint64_t)d) << 16U) | (((uint64_t)e) << 8U) | ((uint64_t)f))
 	{}
 
 	explicit ZT_INLINE MAC(const uint64_t m) noexcept:
@@ -63,7 +63,7 @@ public:
 	 */
 	ZT_INLINE void setTo(const uint8_t b[6]) noexcept
 	{
-		m_mac = ((uint64_t) b[0] << 40U) | ((uint64_t) b[1] << 32U) | ((uint64_t) b[2] << 24U) | ((uint64_t) b[3] << 16U) | ((uint64_t) b[4] << 8U) | (uint64_t) b[5];
+		m_mac = ((uint64_t)b[0] << 40U) | ((uint64_t)b[1] << 32U) | ((uint64_t)b[2] << 24U) | ((uint64_t)b[3] << 16U) | ((uint64_t)b[4] << 8U) | (uint64_t)b[5];
 	}
 
 	/**
@@ -72,12 +72,12 @@ public:
 	 */
 	ZT_INLINE void copyTo(uint8_t b[6]) const noexcept
 	{
-		b[0] = (uint8_t) (m_mac >> 40U);
-		b[1] = (uint8_t) (m_mac >> 32U);
-		b[2] = (uint8_t) (m_mac >> 24U);
-		b[3] = (uint8_t) (m_mac >> 16U);
-		b[4] = (uint8_t) (m_mac >> 8U);
-		b[5] = (uint8_t) m_mac;
+		b[0] = (uint8_t)(m_mac >> 40U);
+		b[1] = (uint8_t)(m_mac >> 32U);
+		b[2] = (uint8_t)(m_mac >> 24U);
+		b[3] = (uint8_t)(m_mac >> 16U);
+		b[4] = (uint8_t)(m_mac >> 8U);
+		b[5] = (uint8_t)m_mac;
 	}
 
 	/**
@@ -100,7 +100,7 @@ public:
 	 */
 	ZT_INLINE void fromAddress(const Address &ztaddr, uint64_t nwid) noexcept
 	{
-		uint64_t m = ((uint64_t) firstOctetForNetwork(nwid)) << 40U;
+		uint64_t m = ((uint64_t)firstOctetForNetwork(nwid)) << 40U;
 		m |= ztaddr.toInt(); // a is 40 bits
 		m ^= ((nwid >> 8U) & 0xffU) << 32U;
 		m ^= ((nwid >> 16U) & 0xffU) << 24U;
@@ -134,7 +134,7 @@ public:
 	 */
 	static ZT_INLINE unsigned char firstOctetForNetwork(uint64_t nwid) noexcept
 	{
-		const uint8_t a = ((uint8_t) (nwid & 0xfeU) | 0x02U); // locally administered, not multicast, from LSB of network ID
+		const uint8_t a = ((uint8_t)(nwid & 0xfeU) | 0x02U); // locally administered, not multicast, from LSB of network ID
 		return ((a == 0x52) ? 0x32 : a); // blacklist 0x52 since it's used by KVM, libvirt, and other popular virtualization engines... seems de-facto standard on Linux
 	}
 
@@ -143,7 +143,7 @@ public:
 	 * @return Byte at said position (address interpreted in big-endian order)
 	 */
 	ZT_INLINE uint8_t operator[](unsigned int i) const noexcept
-	{ return (uint8_t)(m_mac >> (unsigned int) (40 - (i * 8))); }
+	{ return (uint8_t)(m_mac >> (unsigned int)(40 - (i * 8))); }
 
 	/**
 	 * @return 6, which is the number of bytes in a MAC, for container compliance
@@ -152,7 +152,7 @@ public:
 	{ return 6; }
 
 	ZT_INLINE unsigned long hashCode() const noexcept
-	{ return (unsigned long) Utils::hash64(m_mac); }
+	{ return (unsigned long)Utils::hash64(m_mac); }
 
 	ZT_INLINE operator bool() const noexcept
 	{ return (m_mac != 0ULL); }
@@ -185,12 +185,15 @@ public:
 		buf[14] = ':';
 		buf[15] = Utils::HEXCHARS[(m_mac >> 4U) & 0xfU];
 		buf[16] = Utils::HEXCHARS[m_mac & 0xfU];
-		buf[17] = (char) 0;
+		buf[17] = (char)0;
 		return buf;
 	}
 
 	ZT_INLINE String toString() const
-	{ char tmp[18]; return String(toString(tmp)); }
+	{
+		char tmp[18];
+		return String(toString(tmp));
+	}
 
 	/**
 	 * Parse a MAC address in hex format with or without : separators and ignoring non-hex characters.
@@ -205,11 +208,11 @@ public:
 				uint64_t c;
 				const char hc = *s++;
 				if ((hc >= 48) && (hc <= 57))
-					c = (uint64_t) hc - 48;
+					c = (uint64_t)hc - 48;
 				else if ((hc >= 97) && (hc <= 102))
-					c = (uint64_t) hc - 87;
+					c = (uint64_t)hc - 87;
 				else if ((hc >= 65) && (hc <= 70))
-					c = (uint64_t) hc - 55;
+					c = (uint64_t)hc - 55;
 				else continue;
 				m_mac = (m_mac << 4U) | c;
 			}

+ 35 - 0
core/Node.cpp

@@ -214,6 +214,20 @@ ZT_ResultCode Node::processVirtualNetworkFrame(
 	}
 }
 
+ZT_ResultCode Node::processHTTPResponse(
+	void *tptr,
+	int64_t now,
+	void *requestId,
+	int responseCode,
+	const char **headerNames,
+	const char **headerValues,
+	const void *body,
+	unsigned int bodySize,
+	unsigned int flags)
+{
+	return ZT_RESULT_OK;
+}
+
 ZT_ResultCode Node::processBackgroundTasks(
 	void *tPtr,
 	int64_t now,
@@ -899,6 +913,27 @@ enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame(
 	}
 }
 
+enum ZT_ResultCode ZT_Node_processHTTPResponse(
+	ZT_Node *node,
+	void *tptr,
+	int64_t now,
+	void *requestId,
+	int responseCode,
+	const char **headerNames,
+	const char **headerValues,
+	const void *body,
+	unsigned int bodySize,
+	unsigned int flags)
+{
+	try {
+		return reinterpret_cast<ZeroTier::Node *>(node)->processHTTPResponse(tptr, now, requestId, responseCode, headerNames, headerValues, body, bodySize, flags);
+	} catch (std::bad_alloc &exc) {
+		return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY;
+	} catch (...) {
+		return ZT_RESULT_ERROR_INTERNAL;
+	}
+}
+
 enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node, void *tptr, int64_t now, volatile int64_t *nextBackgroundTaskDeadline)
 {
 	try {

+ 11 - 0
core/Node.hpp

@@ -71,6 +71,17 @@ public:
 		unsigned int frameLength,
 		volatile int64_t *nextBackgroundTaskDeadline);
 
+	ZT_ResultCode processHTTPResponse(
+		void *tptr,
+		int64_t now,
+		void *requestId,
+		int responseCode,
+		const char **headerNames,
+		const char **headerValues,
+		const void *body,
+		unsigned int bodySize,
+		unsigned int flags);
+
 	ZT_ResultCode processBackgroundTasks(
 		void *tPtr,
 		int64_t now,

+ 13 - 4
core/Utils.hpp

@@ -53,11 +53,9 @@ namespace Utils {
 #define ZT_ROL32(x, r) (((x) << (r)) | ((x) >> (32 - (r))))
 
 #ifdef ZT_ARCH_X64
-
 struct CPUIDRegisters
 {
 	CPUIDRegisters() noexcept;
-
 	bool rdrand;
 	bool aes;
 	bool avx;
@@ -68,7 +66,6 @@ struct CPUIDRegisters
 	bool sha;
 	bool fsrm;
 };
-
 extern const CPUIDRegisters CPUID;
 #endif
 
@@ -285,10 +282,22 @@ static ZT_INLINE uint32_t hash32(uint32_t x) noexcept
  */
 static ZT_INLINE bool allZero(const void *const b, unsigned int l) noexcept
 {
+	const uint8_t *p = reinterpret_cast<const uint8_t *>(b);
+
+#ifndef ZT_NO_UNALIGNED_ACCESS
+	while (l >= 8) {
+		if (*reinterpret_cast<const uint64_t *>(p) != 0)
+			return false;
+		p += 8;
+		l -= 8;
+	}
+#endif
+
 	for (unsigned int i = 0; i < l; ++i) {
-		if (reinterpret_cast<const uint8_t *>(b)[i] != 0)
+		if (reinterpret_cast<const uint8_t *>(p)[i] != 0)
 			return false;
 	}
+
 	return true;
 }
 

+ 55 - 9
core/zerotier.h

@@ -1459,15 +1459,6 @@ typedef int (*ZT_StateGetFunction)(
 /**
  * Function to send a ZeroTier packet out over the physical wire (L2/L3)
  *
- * Parameters:
- *  (1) Node
- *  (2) User pointer
- *  (3) Local socket or -1 for "all" or "any"
- *  (4) Remote address
- *  (5) Packet data
- *  (6) Packet length
- *  (7) Desired IP TTL or 0 to use default
- *
  * If there is only one local socket, the local socket can be ignored.
  * If the local socket is -1, the packet should be sent out from all
  * bound local sockets or a random bound local socket.
@@ -1490,6 +1481,29 @@ typedef int (*ZT_WirePacketSendFunction)(
 	unsigned int,                     /* Packet length */
 	unsigned int);                    /* TTL or 0 to use default */
 
+/**
+ * Function to initiate HTTP requests
+ *
+ * The supplied HTTP request identifier is an opaque pointer that must
+ * be returned via ZT_Node_processHttpResponse(). If this handler is
+ * implemented then ZT_Node_processHttpResponse() must be called for
+ * each call made by the core to this. This function itself does not
+ * return any error code; use processHttpResponse() for that. It may
+ * be called directly from inside the implementation of this.
+ */
+typedef void (*ZT_HTTPRequestFunction)(
+	ZT_Node *,                        /* Node */
+	void *,                           /* User ptr */
+	void *,                           /* Thread ptr */
+	void *,                           /* HTTP request identifier */
+	const char *,                     /* HTTP method (GET, HEAD, etc.) */
+	const char *,                     /* URL */
+	const char **,                    /* Header names, NULL terminated */
+	const char **,                    /* Header values, NULL terminated */
+	const void *,                     /* Request body or NULL if none */
+	unsigned int,                     /* Length of request body in bytes */
+	unsigned int);                    /* Flags */
+
 /**
  * Function to check whether a path should be used for ZeroTier traffic
  *
@@ -1568,6 +1582,11 @@ struct ZT_Node_Callbacks
 	 */
 	ZT_WirePacketSendFunction wirePacketSendFunction;
 
+	/**
+	 * RECOMMENDED: Function to initiate HTTP requests
+	 */
+	ZT_HTTPRequestFunction httpRequestFunction;
+
 	/**
 	 * REQUIRED: Function to inject frames into a virtual network's TAP
 	 */
@@ -1718,6 +1737,33 @@ ZT_SDK_API enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame(
 	int isZtBuffer,
 	volatile int64_t *nextBackgroundTaskDeadline);
 
+/**
+ * Process a response from HTTP requests initiated via API callback
+ *
+ * @param node Node instance
+ * @param tptr Thread pointer to pass to functions/callbacks resulting from this call
+ * @param now Current clock in milliseconds
+ * @param requestId Opaque pointer provided via the requesting callback
+ * @param responseCode HTTP response code (e.g. 200, 500)
+ * @param headerNames HTTP header names, terminated by a NULL pointer
+ * @param headerValues HTTP header values corresponding with each name
+ * @param body Response body or NULL if none
+ * @param bodySize Size of response body in bytes
+ * @param flags Response flags
+ * @return OK (0) or error code if a fatal error condition has occurred
+ */
+ZT_SDK_API enum ZT_ResultCode ZT_Node_processHTTPResponse(
+	ZT_Node *node,
+	void *tptr,
+	int64_t now,
+	void *requestId,
+	int responseCode,
+	const char **headerNames,
+	const char **headerValues,
+	const void *body,
+	unsigned int bodySize,
+	unsigned int flags);
+
 /**
  * Perform periodic background operations
  *