Browse Source

Implement extended armor throughout system but not turned on yet.

Adam Ierymenko 10 months ago
parent
commit
6b078ee935
9 changed files with 1308 additions and 1288 deletions
  1. 3 3
      node/Bond.cpp
  2. 0 11
      node/Credential.hpp
  3. 15 46
      node/IncomingPacket.cpp
  4. 1 1
      node/Multicaster.cpp
  5. 44 6
      node/Packet.cpp
  6. 22 0
      node/Packet.hpp
  7. 605 603
      node/Peer.cpp
  8. 617 617
      node/Peer.hpp
  9. 1 1
      node/Switch.cpp

+ 3 - 3
node/Bond.cpp

@@ -854,7 +854,7 @@ void Bond::sendPATH_NEGOTIATION_REQUEST(void* tPtr, int pathIdx)
 	outp.append<int16_t>(_localUtility);
 	if (_paths[pathIdx].p->address()) {
 		Metrics::pkt_path_negotiation_request_out++;
-		outp.armor(_peer->key(), false, _peer->aesKeysIfSupported());
+		outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
 		RR->node->putPacket(tPtr, _paths[pathIdx].p->localSocket(), _paths[pathIdx].p->address(), outp.data(), outp.size());
 		_overheadBytes += outp.size();
 	}
@@ -895,7 +895,7 @@ void Bond::sendQOS_MEASUREMENT(void* tPtr, int pathIdx, int64_t localSocket, con
 		// debug("sending QOS via link %s (len=%d)", pathToStr(_paths[pathIdx].p).c_str(), len);
 		outp.append(qosData, len);
 		if (atAddress) {
-			outp.armor(_peer->key(), false, _peer->aesKeysIfSupported());
+			outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
 			RR->node->putPacket(tPtr, localSocket, atAddress, outp.data(), outp.size());
 		}
 		else {
@@ -933,7 +933,7 @@ void Bond::processBackgroundBondTasks(void* tPtr, int64_t now)
 				if ((_monitorInterval > 0) && (((now - _paths[i].p->_lastIn) >= (_paths[i].alive ? _monitorInterval : _failoverInterval)))) {
 					if ((_peer->remoteVersionProtocol() >= 5) && (! ((_peer->remoteVersionMajor() == 1) && (_peer->remoteVersionMinor() == 1) && (_peer->remoteVersionRevision() == 0)))) {
 						Packet outp(_peer->address(), RR->identity.address(), Packet::VERB_ECHO);	// ECHO (this is our bond's heartbeat)
-						outp.armor(_peer->key(), true, _peer->aesKeysIfSupported());
+						outp.armor(_peer->key(), true, false, _peer->aesKeysIfSupported(), _peer->identity());
 						RR->node->expectReplyTo(outp.packetId());
 						RR->node->putPacket(tPtr, _paths[i].p->localSocket(), _paths[i].p->address(), outp.data(), outp.size());
 						_paths[i].p->_lastOut = now;

+ 0 - 11
node/Credential.hpp

@@ -14,17 +14,6 @@
 #ifndef ZT_CREDENTIAL_HPP
 #define ZT_CREDENTIAL_HPP
 
-#include <string>
-#include <memory>
-#include <stdexcept>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-
-#include "Constants.hpp"
-
 namespace ZeroTier {
 
 /**

+ 15 - 46
node/IncomingPacket.cpp

@@ -68,7 +68,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t f
 		const SharedPtr<Peer> peer(RR->topology->getPeer(tPtr,sourceAddress));
 		if (peer) {
 			if (!_authenticated) {
-				if (!dearmor(peer->key(), peer->aesKeys())) {
+				if (!dearmor(peer->key(), peer->aesKeys(), RR->identity)) {
 					RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,packetId(),sourceAddress,hops(),"invalid MAC");
 					peer->recordIncomingInvalidPacket(_path);
 					return true;
@@ -398,13 +398,13 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool
 
 				uint8_t key[ZT_SYMMETRIC_KEY_SIZE];
 				if (RR->identity.agree(id,key)) {
-					if (dearmor(key, peer->aesKeysIfSupported())) { // ensure packet is authentic, otherwise drop
+					if (dearmor(key, peer->aesKeysIfSupported(), RR->identity)) { // ensure packet is authentic, otherwise drop
 						RR->t->incomingPacketDroppedHELLO(tPtr,_path,pid,fromAddress,"address collision");
 						Packet outp(id.address(),RR->identity.address(),Packet::VERB_ERROR);
 						outp.append((uint8_t)Packet::VERB_HELLO);
 						outp.append((uint64_t)pid);
 						outp.append((uint8_t)Packet::ERROR_IDENTITY_COLLISION);
-						outp.armor(key,true,peer->aesKeysIfSupported());
+						outp.armor(key,true,false,peer->aesKeysIfSupported(),peer->identity());
 						Metrics::pkt_error_out++;
 						Metrics::pkt_error_identity_collision_out++;
 						_path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now());
@@ -419,7 +419,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool
 			} else {
 				// Identity is the same as the one we already have -- check packet integrity
 
-				if (!dearmor(peer->key(), peer->aesKeysIfSupported())) {
+				if (!dearmor(peer->key(), peer->aesKeysIfSupported(), RR->identity)) {
 					RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC");
 					return true;
 				}
@@ -444,7 +444,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool
 
 		// Check packet integrity and MAC (this is faster than locallyValidate() so do it first to filter out total crap)
 		SharedPtr<Peer> newPeer(new Peer(RR,RR->identity,id));
-		if (!dearmor(newPeer->key(), newPeer->aesKeysIfSupported())) {
+		if (!dearmor(newPeer->key(), newPeer->aesKeysIfSupported(), RR->identity)) {
 			RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC");
 			return true;
 		}
@@ -510,38 +510,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool
 	outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR);
 	outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR);
 	outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
-
-	if (protoVersion >= 5) {
-		_path->address().serialize(outp);
-	} else {
-		/* LEGACY COMPATIBILITY HACK:
-		 *
-		 * For a while now (since 1.0.3), ZeroTier has recognized changes in
-		 * its network environment empirically by examining its external network
-		 * address as reported by trusted peers. In versions prior to 1.1.0
-		 * (protocol version < 5), they did this by saving a snapshot of this
-		 * information (in SelfAwareness.hpp) keyed by reporting device ID and
-		 * address type.
-		 *
-		 * This causes problems when clustering is combined with symmetric NAT.
-		 * Symmetric NAT remaps ports, so different endpoints in a cluster will
-		 * report back different exterior addresses. Since the old code keys
-		 * this by device ID and not sending physical address and compares the
-		 * entire address including port, it constantly thinks its external
-		 * surface is changing and resets connections when talking to a cluster.
-		 *
-		 * In new code we key by sending physical address and device and we also
-		 * take the more conservative position of only interpreting changes in
-		 * IP address (neglecting port) as a change in network topology that
-		 * necessitates a reset. But we can make older clients work here by
-		 * nulling out the port field. Since this info is only used for empirical
-		 * detection of link changes, it doesn't break anything else.
-		 */
-		InetAddress tmpa(_path->address());
-		tmpa.setPort(0);
-		tmpa.serialize(outp);
-	}
-
+	_path->address().serialize(outp);
 	const unsigned int worldUpdateSizeAt = outp.size();
 	outp.addSize(2); // make room for 16-bit size field
 	if ((planetWorldId)&&(RR->topology->planetWorldTimestamp() > planetWorldTimestamp)&&(planetWorldId == RR->topology->planetWorldId())) {
@@ -562,7 +531,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool
 	}
 	outp.setAt<uint16_t>(worldUpdateSizeAt,(uint16_t)(outp.size() - (worldUpdateSizeAt + 2)));
 
-	outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+	outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),peer->identity());
 	peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now);
 	Metrics::pkt_ok_out++;
 	_path->send(RR,tPtr,outp.data(),outp.size(),now);
@@ -724,7 +693,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar
 
 	if (count > 0) {
 		Metrics::pkt_ok_out++;
-		outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+		outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),RR->identity);
 		_path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now());
 	}
 
@@ -952,7 +921,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const
 			outp.append((uint64_t)packetId());
 			outp.append((uint64_t)nwid);
 			const int64_t now = RR->node->now();
-			outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+			outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),peer->identity());
 			peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now);
 			Metrics::pkt_ok_out++;
 			_path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now());
@@ -981,7 +950,7 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share
 	if (size() > ZT_PACKET_IDX_PAYLOAD) {
 		outp.append(reinterpret_cast<const unsigned char *>(data()) + ZT_PACKET_IDX_PAYLOAD,size() - ZT_PACKET_IDX_PAYLOAD);
 	}
-	outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+	outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),peer->identity());
 	peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now);
 	Metrics::pkt_ok_out++;
 	_path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now());
@@ -1177,7 +1146,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void
 		outp.append(requestPacketId);
 		outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION);
 		outp.append(nwid);
-		outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+		outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),peer->identity());
 		Metrics::pkt_error_out++;
 		Metrics::pkt_error_unsupported_op_out++;
 		_path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now());
@@ -1201,7 +1170,7 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c
 			outp.append((uint64_t)network->id());
 			outp.append((uint64_t)configUpdateId);
 			const int64_t now = RR->node->now();
-			outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+			outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),peer->identity());
 			peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now);
 			Metrics::pkt_ok_out++;
 			_path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now());
@@ -1244,7 +1213,7 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr
 		outp.append((uint32_t)mg.adi());
 		const unsigned int gatheredLocally = RR->mc->gather(peer->address(),nwid,mg,outp,gatherLimit);
 		if (gatheredLocally > 0) {
-			outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+			outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),peer->identity());
 			peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now);
 			Metrics::pkt_ok_out++;
 			_path->send(RR,tPtr,outp.data(),outp.size(),now);
@@ -1348,7 +1317,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr,
 			outp.append((unsigned char)0x02); // flag 0x02 = contains gather results
 			if (RR->mc->gather(peer->address(),nwid,to,outp,gatherLimit)) {
 				const int64_t now = RR->node->now();
-				outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+				outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),peer->identity());
 				peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now);
 				Metrics::pkt_ok_out++;
 				_path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now());
@@ -1490,7 +1459,7 @@ void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void
 	outp.append(packetId());
 	outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE);
 	outp.append(nwid);
-	outp.armor(peer->key(),true,peer->aesKeysIfSupported());
+	outp.armor(peer->key(),true,false,peer->aesKeysIfSupported(),peer->identity());
 	Metrics::pkt_error_out++;
 	Metrics::pkt_error_need_membership_cert_out++;
 	_path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now());

+ 1 - 1
node/Multicaster.cpp

@@ -198,7 +198,7 @@ void Multicaster::send(
 					if (!network->config().disableCompression()) {
 						outp.compress();
 					}
-					outp.armor(bestMulticastReplicator->key(),true,bestMulticastReplicator->aesKeysIfSupported());
+					outp.armor(bestMulticastReplicator->key(),true,false,bestMulticastReplicator->aesKeysIfSupported(),bestMulticastReplicator->identity());
 					Metrics::pkt_multicast_frame_out++;
 					bestMulticastReplicatorPath->send(RR,tPtr,outp.data(),outp.size(),now);
 					return;

+ 44 - 6
node/Packet.cpp

@@ -18,6 +18,7 @@
 #include <stdio.h>
 
 #include "Packet.hpp"
+#include "ECC.hpp"
 
 #if defined(ZT_USE_X64_ASM_SALSA2012) && defined(ZT_ARCH_X64)
 #include "../ext/x64-salsa2012-asm/salsa2012.h"
@@ -1009,8 +1010,7 @@ void Packet::armor(const void *key,bool encryptPayload,bool extendedArmor,const
 {
     uint8_t *const data = reinterpret_cast<uint8_t *>(unsafeData());
 
-    if (extendedArmor) {
-    }
+    this->setExtendedArmor(extendedArmor);
 
     if ((aesKeys) && (encryptPayload)) {
         setCipher(ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV);
@@ -1042,19 +1042,22 @@ void Packet::armor(const void *key,bool encryptPayload,bool extendedArmor,const
         if (ZT_HAS_FAST_CRYPTO()) {
             const unsigned int payloadLen = (encryptPayload) ? (size() - ZT_PACKET_IDX_VERB) : 0;
             uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8];
+            uint64_t mac[2];
+
             ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,payloadLen + 64,(data + ZT_PACKET_IDX_IV),mangledKey);
             Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast<const uint8_t *>(keyStream + 8),payloadLen);
-            uint64_t mac[2];
             Poly1305::compute(mac,data + ZT_PACKET_IDX_VERB,size() - ZT_PACKET_IDX_VERB,keyStream);
+
 #ifdef ZT_NO_TYPE_PUNNING
             memcpy(data + ZT_PACKET_IDX_MAC,mac,8);
 #else
             (*reinterpret_cast<uint64_t *>(data + ZT_PACKET_IDX_MAC)) = mac[0];
 #endif
         } else {
-            Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV);
-
             uint64_t macKey[4];
+            uint64_t mac[2];
+
+            Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV);
             s20.crypt12(ZERO_KEY,macKey,sizeof(macKey));
 
             uint8_t *const payload = data + ZT_PACKET_IDX_VERB;
@@ -1062,17 +1065,51 @@ void Packet::armor(const void *key,bool encryptPayload,bool extendedArmor,const
             if (encryptPayload) {
                 s20.crypt12(payload,payload,payloadLen);
             }
-            uint64_t mac[2];
 
             Poly1305::compute(mac,payload,payloadLen,macKey);
             memcpy(data + ZT_PACKET_IDX_MAC,mac,8);
         }
     }
+
+    if (extendedArmor) {
+        ECC::Pair ephemeralKeyPair = ECC::generate();
+        uint8_t ephemeralSymmetric[32];
+        ECC::agree(ephemeralKeyPair, identity.publicKey(), ephemeralSymmetric, 32);
+
+        AES cipher(ephemeralSymmetric);
+        AES::CTR aesCtr(cipher);
+        aesCtr.init(data, 0, data + ZT_PACKET_IDX_EXTENDED_ARMOR_START);
+        aesCtr.crypt(data + ZT_PACKET_IDX_EXTENDED_ARMOR_START, size() - ZT_PACKET_IDX_EXTENDED_ARMOR_START);
+
+        this->append(ephemeralKeyPair.pub.data, ZT_ECC_EPHEMERAL_PUBLIC_KEY_LEN);
+    }
 }
 
 bool Packet::dearmor(const void *key,const AES aesKeys[2],const Identity &identity)
 {
     uint8_t *const data = reinterpret_cast<uint8_t *>(unsafeData());
+
+    if (extendedArmor()) {
+        if (size() < ZT_ECC_EPHEMERAL_PUBLIC_KEY_LEN) {
+            return false;
+        }
+        uint8_t ephemeralSymmetric[32];
+        ECC::Public ephemeralKey;
+        memcpy(ephemeralKey.data, data + (size() - ZT_ECC_EPHEMERAL_PUBLIC_KEY_LEN), ZT_ECC_EPHEMERAL_PUBLIC_KEY_LEN);
+        ECC::agree(identity.privateKeyPair(), ephemeralKey, ephemeralSymmetric, 32);
+
+        AES cipher(ephemeralSymmetric);
+        AES::CTR aesCtr(cipher);
+        aesCtr.init(data, 0, data + ZT_PACKET_IDX_EXTENDED_ARMOR_START);
+        aesCtr.crypt(data + ZT_PACKET_IDX_EXTENDED_ARMOR_START, size() - ZT_PACKET_IDX_EXTENDED_ARMOR_START);
+
+        this->setSize(size() - ZT_ECC_EPHEMERAL_PUBLIC_KEY_LEN);
+
+        /* Note: both the MAC and the data were encrypted with the ephemeral key. We don't need
+         * a separate MAC for the ephemeral encryption because the MAC check below is obviously
+         * going to fail if the ephemeral key was incorrect. */
+    }
+
     const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB;
     unsigned char *const payload = data + ZT_PACKET_IDX_VERB;
     const unsigned int cs = cipher();
@@ -1080,6 +1117,7 @@ bool Packet::dearmor(const void *key,const AES aesKeys[2],const Identity &identi
     if (cs == ZT_PROTO_CIPHER_SUITE__AES_GMAC_SIV) {
         if (aesKeys) {
             uint64_t tag[2];
+
 #ifdef ZT_NO_UNALIGNED_ACCESS
             Utils::copy<8>(tag, data);
             Utils::copy<8>(tag + 1, data + ZT_PACKET_IDX_MAC);

+ 22 - 0
node/Packet.hpp

@@ -1182,6 +1182,25 @@ public:
         }
     }
 
+    /**
+     * @return True if packet is encrypted with an extra ephemeral key
+     */
+    inline bool extendedArmor() const { return (((unsigned char)(*this)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_EXTENDED_ARMOR) != 0); }
+
+    /**
+     * Set this packet's extended armor flag
+     *
+     * @param f Extended armor flag value
+     */
+    inline void setExtendedArmor(bool f)
+    {
+        if (f) {
+            (*this)[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_EXTENDED_ARMOR;
+        } else {
+            (*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_EXTENDED_ARMOR);
+        }
+    }
+
     /**
      * @return True if compressed (result only valid if unencrypted)
      */
@@ -1287,6 +1306,8 @@ public:
      *
      * @param key 32-byte key
      * @param encryptPayload If true, encrypt packet payload, else just MAC
+     * @param extendedArmor Use an ephemeral key to encrypt payload (for encrypted HELLO)
+     * @param identity Identity of packet recipient/destination
      * @param aesKeys If non-NULL these are the two keys for AES-GMAC-SIV
      */
     void armor(const void *key,bool encryptPayload,bool extendedArmor,const AES aesKeys[2],const Identity &identity);
@@ -1300,6 +1321,7 @@ public:
      *
      * @param key 32-byte key
      * @param aesKeys If non-NULL these are the two keys for AES-GMAC-SIV
+     * @param identity Receiver's identity (must include secret)
      * @return False if packet is invalid or failed MAC authenticity check
      */
     bool dearmor(const void *key,const AES aesKeys[2],const Identity &identity);

+ 605 - 603
node/Peer.cpp

@@ -14,6 +14,7 @@
 #include "../version.h"
 #include "Constants.hpp"
 #include "Peer.hpp"
+#include "Identity.hpp"
 #include "Switch.hpp"
 #include "Network.hpp"
 #include "SelfAwareness.hpp"
@@ -29,674 +30,675 @@ namespace ZeroTier {
 static unsigned char s_freeRandomByteCounter = 0;
 
 Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity)
-	: RR(renv)
-	, _lastReceive(0)
-	, _lastNontrivialReceive(0)
-	, _lastTriedMemorizedPath(0)
-	, _lastDirectPathPushSent(0)
-	, _lastDirectPathPushReceive(0)
-	, _lastCredentialRequestSent(0)
-	, _lastWhoisRequestReceived(0)
-	, _lastCredentialsReceived(0)
-	, _lastTrustEstablishedPacketReceived(0)
-	, _lastSentFullHello(0)
-	, _lastEchoCheck(0)
-	, _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter)
-	, _vProto(0)
-	, _vMajor(0)
-	, _vMinor(0)
-	, _vRevision(0)
-	, _id(peerIdentity)
-	, _directPathPushCutoffCount(0)
-	, _echoRequestCutoffCount(0)
-	, _localMultipathSupported(false)
-	, _lastComputedAggregateMeanLatency(0)
+    : RR(renv)
+    , _lastReceive(0)
+    , _lastNontrivialReceive(0)
+    , _lastTriedMemorizedPath(0)
+    , _lastDirectPathPushSent(0)
+    , _lastDirectPathPushReceive(0)
+    , _lastCredentialRequestSent(0)
+    , _lastWhoisRequestReceived(0)
+    , _lastCredentialsReceived(0)
+    , _lastTrustEstablishedPacketReceived(0)
+    , _lastSentFullHello(0)
+    , _lastEchoCheck(0)
+    , _freeRandomByte((unsigned char)((uintptr_t)this >> 4) ^ ++s_freeRandomByteCounter)
+    , _vProto(0)
+    , _vMajor(0)
+    , _vMinor(0)
+    , _vRevision(0)
+    , _id(peerIdentity)
+    , _directPathPushCutoffCount(0)
+    , _echoRequestCutoffCount(0)
+    , _localMultipathSupported(false)
+    , _lastComputedAggregateMeanLatency(0)
 #ifndef ZT_NO_PEER_METRICS
-	, _peer_latency{Metrics::peer_latency.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}}, std::vector<uint64_t>{1,3,6,10,30,60,100,300,600,1000})}
-	, _alive_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","alive"}})}
-	, _dead_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","dead"}})}
-	, _incoming_packet{Metrics::peer_packets.Add({{"direction", "rx"},{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}
-	, _outgoing_packet{Metrics::peer_packets.Add({{"direction", "tx"},{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}
-	, _packet_errors{Metrics::peer_packet_errors.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}
+    , _peer_latency{Metrics::peer_latency.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}}, std::vector<uint64_t>{1,3,6,10,30,60,100,300,600,1000})}
+    , _alive_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","alive"}})}
+    , _dead_path_count{Metrics::peer_path_count.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())},{"status","dead"}})}
+    , _incoming_packet{Metrics::peer_packets.Add({{"direction", "rx"},{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}
+    , _outgoing_packet{Metrics::peer_packets.Add({{"direction", "tx"},{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}
+    , _packet_errors{Metrics::peer_packet_errors.Add({{"node_id", OSUtils::nodeIDStr(peerIdentity.address().toInt())}})}
 #endif
 {
-	if (!myIdentity.agree(peerIdentity,_key)) {
-		throw ZT_EXCEPTION_INVALID_ARGUMENT;
-	}
-
-	uint8_t ktmp[ZT_SYMMETRIC_KEY_SIZE];
-	KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K0,0,0,ktmp);
-	_aesKeys[0].init(ktmp);
-	KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K1,0,0,ktmp);
-	_aesKeys[1].init(ktmp);
-	Utils::burn(ktmp,ZT_SYMMETRIC_KEY_SIZE);
+    if (!myIdentity.agree(peerIdentity,_key)) {
+        throw ZT_EXCEPTION_INVALID_ARGUMENT;
+    }
+
+    uint8_t ktmp[ZT_SYMMETRIC_KEY_SIZE];
+    KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K0,0,0,ktmp);
+    _aesKeys[0].init(ktmp);
+    KBKDFHMACSHA384(_key,ZT_KBKDF_LABEL_AES_GMAC_SIV_K1,0,0,ktmp);
+    _aesKeys[1].init(ktmp);
+    Utils::burn(ktmp,ZT_SYMMETRIC_KEY_SIZE);
 }
 
 void Peer::received(
-	void *tPtr,
-	const SharedPtr<Path> &path,
-	const unsigned int hops,
-	const uint64_t packetId,
-	const unsigned int payloadLength,
-	const Packet::Verb verb,
-	const uint64_t inRePacketId,
-	const Packet::Verb inReVerb,
-	const bool trustEstablished,
-	const uint64_t networkId,
-	const int32_t flowId)
+    void *tPtr,
+    const SharedPtr<Path> &path,
+    const unsigned int hops,
+    const uint64_t packetId,
+    const unsigned int payloadLength,
+    const Packet::Verb verb,
+    const uint64_t inRePacketId,
+    const Packet::Verb inReVerb,
+    const bool trustEstablished,
+    const uint64_t networkId,
+    const int32_t flowId)
 {
-	const int64_t now = RR->node->now();
-
-	_lastReceive = now;
-	switch (verb) {
-		case Packet::VERB_FRAME:
-		case Packet::VERB_EXT_FRAME:
-		case Packet::VERB_NETWORK_CONFIG_REQUEST:
-		case Packet::VERB_NETWORK_CONFIG:
-		case Packet::VERB_MULTICAST_FRAME:
-			_lastNontrivialReceive = now;
-			break;
-		default:
-			break;
-	}
+    const int64_t now = RR->node->now();
+
+    _lastReceive = now;
+    switch (verb) {
+        case Packet::VERB_FRAME:
+        case Packet::VERB_EXT_FRAME:
+        case Packet::VERB_NETWORK_CONFIG_REQUEST:
+        case Packet::VERB_NETWORK_CONFIG:
+        case Packet::VERB_MULTICAST_FRAME:
+            _lastNontrivialReceive = now;
+            break;
+        default:
+            break;
+    }
 #ifndef ZT_NO_PEER_METRICS
-	_incoming_packet++;
+    _incoming_packet++;
 #endif
-	recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now);
-
-	if (trustEstablished) {
-		_lastTrustEstablishedPacketReceived = now;
-		path->trustedPacketReceived(now);
-	}
-
-	if (hops == 0) {
-		// If this is a direct packet (no hops), update existing paths or learn new ones
-		bool havePath = false;
-		{
-			Mutex::Lock _l(_paths_m);
-			for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-				if (_paths[i].p) {
-					if (_paths[i].p == path) {
-						_paths[i].lr = now;
-						havePath = true;
-						break;
-					}
-					// If same address on same interface then don't learn unless existing path isn't alive (prevents learning loop)
-					if (_paths[i].p->address().ipsEqual(path->address()) && _paths[i].p->localSocket() == path->localSocket()) {
-						if (_paths[i].p->alive(now) && !_bond) {
-							havePath = true;
-							break;
-						}
-					}
-				} else {
-					break;
-				}
-			}
-		}
-
-		if ( (!havePath) && RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localSocket(),path->address()) ) {
-			if (verb == Packet::VERB_OK) {
-				Mutex::Lock _l(_paths_m);
-				unsigned int oldestPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
-				unsigned int oldestPathAge = 0;
-				unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS;
-
-				for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-					if (_paths[i].p) {
-						// Keep track of oldest path as a last resort option
-						unsigned int currAge = _paths[i].p->age(now);
-						if (currAge > oldestPathAge) {
-							oldestPathAge = currAge;
-							oldestPathIdx = i;
-						}
-						if (_paths[i].p->address().ipsEqual(path->address())) {
-							if (_paths[i].p->localSocket() == path->localSocket()) {
-								if (!_paths[i].p->alive(now)) {
-									replacePath = i;
-									break;
-								}
-							}
-						}
-					} else {
-						replacePath = i;
-						break;
-					}
-				}
-
-				// If we didn't find a good candidate then resort to replacing oldest path
-				replacePath = (replacePath == ZT_MAX_PEER_NETWORK_PATHS) ? oldestPathIdx : replacePath;
-				if (replacePath != ZT_MAX_PEER_NETWORK_PATHS) {
-					RR->t->peerLearnedNewPath(tPtr, networkId, *this, path, packetId);
-					_paths[replacePath].lr = now;
-					_paths[replacePath].p = path;
-					_paths[replacePath].priority = 1;
-					Mutex::Lock _l(_bond_m);
-					if(_bond) {
-						_bond->nominatePathToBond(_paths[replacePath].p, now);
-					}
-				}
-			} else {
-				Mutex::Lock ltl(_lastTriedPath_m);
-
-				bool triedTooRecently = false;
-				for(std::list< std::pair< Path *, int64_t > >::iterator i(_lastTriedPath.begin());i!=_lastTriedPath.end();) {
-					if ((now - i->second) > 1000) {
-						_lastTriedPath.erase(i++);
-					} else if (i->first == path.ptr()) {
-						++i;
-						triedTooRecently = true;
-					} else {
-						++i;
-					}
-				}
-
-				if (!triedTooRecently) {
-					_lastTriedPath.push_back(std::pair< Path *, int64_t >(path.ptr(), now));
-					attemptToContactAt(tPtr,path->localSocket(),path->address(),now,true);
-					path->sent(now);
-					RR->t->peerConfirmingUnknownPath(tPtr,networkId,*this,path,packetId,verb);
-				}
-			}
-		}
-	}
-
-	// If we have a trust relationship periodically push a message enumerating
-	// all known external addresses for ourselves. If we already have a path this
-	// is done less frequently.
-	if (this->trustEstablished(now)) {
-		const int64_t sinceLastPush = now - _lastDirectPathPushSent;
-		bool lowBandwidth = RR->node->lowBandwidthModeEnabled();
-		int timerScale = lowBandwidth ? 16 : 1;
-		if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH * timerScale : ZT_DIRECT_PATH_PUSH_INTERVAL)) {
-			_lastDirectPathPushSent = now;
-			std::vector<InetAddress> pathsToPush(RR->node->directPaths());
-			std::vector<InetAddress> ma = RR->sa->whoami();
-			pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end());
-			if (!pathsToPush.empty()) {
-				std::vector<InetAddress>::const_iterator p(pathsToPush.begin());
-				while (p != pathsToPush.end()) {
-					Packet *const outp = new Packet(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS);
-					outp->addSize(2); // leave room for count
-					unsigned int count = 0;
-					while ((p != pathsToPush.end())&&((outp->size() + 24) < 1200)) {
-						uint8_t addressType = 4;
-						switch(p->ss_family) {
-							case AF_INET:
-								break;
-							case AF_INET6:
-								addressType = 6;
-								break;
-							default: // we currently only push IP addresses
-								++p;
-								continue;
-						}
-
-						outp->append((uint8_t)0); // no flags
-						outp->append((uint16_t)0); // no extensions
-						outp->append(addressType);
-						outp->append((uint8_t)((addressType == 4) ? 6 : 18));
-						outp->append(p->rawIpData(),((addressType == 4) ? 4 : 16));
-						outp->append((uint16_t)p->port());
-
-						++count;
-						++p;
-					}
-					if (count) {
-						Metrics::pkt_push_direct_paths_out++;
-						outp->setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count);
-						outp->compress();
-						outp->armor(_key,true,aesKeysIfSupported());
-						Metrics::pkt_push_direct_paths_out++;
-						path->send(RR,tPtr,outp->data(),outp->size(),now);
-					}
-					delete outp;
-				}
-			}
-		}
-	}
+    recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now);
+
+    if (trustEstablished) {
+        _lastTrustEstablishedPacketReceived = now;
+        path->trustedPacketReceived(now);
+    }
+
+    if (hops == 0) {
+        // If this is a direct packet (no hops), update existing paths or learn new ones
+        bool havePath = false;
+        {
+            Mutex::Lock _l(_paths_m);
+            for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+                if (_paths[i].p) {
+                    if (_paths[i].p == path) {
+                        _paths[i].lr = now;
+                        havePath = true;
+                        break;
+                    }
+                    // If same address on same interface then don't learn unless existing path isn't alive (prevents learning loop)
+                    if (_paths[i].p->address().ipsEqual(path->address()) && _paths[i].p->localSocket() == path->localSocket()) {
+                        if (_paths[i].p->alive(now) && !_bond) {
+                            havePath = true;
+                            break;
+                        }
+                    }
+                } else {
+                    break;
+                }
+            }
+        }
+
+        if ( (!havePath) && RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localSocket(),path->address()) ) {
+            if (verb == Packet::VERB_OK) {
+                Mutex::Lock _l(_paths_m);
+                unsigned int oldestPathIdx = ZT_MAX_PEER_NETWORK_PATHS;
+                unsigned int oldestPathAge = 0;
+                unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS;
+
+                for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+                    if (_paths[i].p) {
+                        // Keep track of oldest path as a last resort option
+                        unsigned int currAge = _paths[i].p->age(now);
+                        if (currAge > oldestPathAge) {
+                            oldestPathAge = currAge;
+                            oldestPathIdx = i;
+                        }
+                        if (_paths[i].p->address().ipsEqual(path->address())) {
+                            if (_paths[i].p->localSocket() == path->localSocket()) {
+                                if (!_paths[i].p->alive(now)) {
+                                    replacePath = i;
+                                    break;
+                                }
+                            }
+                        }
+                    } else {
+                        replacePath = i;
+                        break;
+                    }
+                }
+
+                // If we didn't find a good candidate then resort to replacing oldest path
+                replacePath = (replacePath == ZT_MAX_PEER_NETWORK_PATHS) ? oldestPathIdx : replacePath;
+                if (replacePath != ZT_MAX_PEER_NETWORK_PATHS) {
+                    RR->t->peerLearnedNewPath(tPtr, networkId, *this, path, packetId);
+                    _paths[replacePath].lr = now;
+                    _paths[replacePath].p = path;
+                    _paths[replacePath].priority = 1;
+                    Mutex::Lock _l(_bond_m);
+                    if(_bond) {
+                        _bond->nominatePathToBond(_paths[replacePath].p, now);
+                    }
+                }
+            } else {
+                Mutex::Lock ltl(_lastTriedPath_m);
+
+                bool triedTooRecently = false;
+                for(std::list< std::pair< Path *, int64_t > >::iterator i(_lastTriedPath.begin());i!=_lastTriedPath.end();) {
+                    if ((now - i->second) > 1000) {
+                        _lastTriedPath.erase(i++);
+                    } else if (i->first == path.ptr()) {
+                        ++i;
+                        triedTooRecently = true;
+                    } else {
+                        ++i;
+                    }
+                }
+
+                if (!triedTooRecently) {
+                    _lastTriedPath.push_back(std::pair< Path *, int64_t >(path.ptr(), now));
+                    attemptToContactAt(tPtr,path->localSocket(),path->address(),now,true);
+                    path->sent(now);
+                    RR->t->peerConfirmingUnknownPath(tPtr,networkId,*this,path,packetId,verb);
+                }
+            }
+        }
+    }
+
+    // If we have a trust relationship periodically push a message enumerating
+    // all known external addresses for ourselves. If we already have a path this
+    // is done less frequently.
+    if (this->trustEstablished(now)) {
+        const int64_t sinceLastPush = now - _lastDirectPathPushSent;
+        bool lowBandwidth = RR->node->lowBandwidthModeEnabled();
+        int timerScale = lowBandwidth ? 16 : 1;
+        if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH * timerScale : ZT_DIRECT_PATH_PUSH_INTERVAL)) {
+            _lastDirectPathPushSent = now;
+            std::vector<InetAddress> pathsToPush(RR->node->directPaths());
+            std::vector<InetAddress> ma = RR->sa->whoami();
+            pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end());
+            if (!pathsToPush.empty()) {
+                std::vector<InetAddress>::const_iterator p(pathsToPush.begin());
+                while (p != pathsToPush.end()) {
+                    Packet *const outp = new Packet(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS);
+                    outp->addSize(2); // leave room for count
+                    unsigned int count = 0;
+                    while ((p != pathsToPush.end())&&((outp->size() + 24) < 1200)) {
+                        uint8_t addressType = 4;
+                        switch(p->ss_family) {
+                            case AF_INET:
+                                break;
+                            case AF_INET6:
+                                addressType = 6;
+                                break;
+                            default: // we currently only push IP addresses
+                                ++p;
+                                continue;
+                        }
+
+                        outp->append((uint8_t)0); // no flags
+                        outp->append((uint16_t)0); // no extensions
+                        outp->append(addressType);
+                        outp->append((uint8_t)((addressType == 4) ? 6 : 18));
+                        outp->append(p->rawIpData(),((addressType == 4) ? 4 : 16));
+                        outp->append((uint16_t)p->port());
+
+                        ++count;
+                        ++p;
+                    }
+                    if (count) {
+                        Metrics::pkt_push_direct_paths_out++;
+                        outp->setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count);
+                        outp->compress();
+                        outp->armor(_key,true,false,aesKeysIfSupported(),_id);
+                        Metrics::pkt_push_direct_paths_out++;
+                        path->send(RR,tPtr,outp->data(),outp->size(),now);
+                    }
+                    delete outp;
+                }
+            }
+        }
+    }
 }
 
 SharedPtr<Path> Peer::getAppropriatePath(int64_t now, bool includeExpired, int32_t flowId)
 {
-	Mutex::Lock _l(_paths_m);
-	Mutex::Lock _lb(_bond_m);
-	if(_bond && _bond->isReady()) {
-		return _bond->getAppropriatePath(now, flowId);
-	}
-	unsigned int bestPath = ZT_MAX_PEER_NETWORK_PATHS;
-	/**
-	 * Send traffic across the highest quality path only. This algorithm will still
-	 * use the old path quality metric from protocol version 9.
-	 */
-	long bestPathQuality = 2147483647;
-	for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-		if (_paths[i].p) {
-			if ((includeExpired)||((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION)) {
-				const long q = _paths[i].p->quality(now) / _paths[i].priority;
-				if (q <= bestPathQuality) {
-					bestPathQuality = q;
-					bestPath = i;
-				}
-			}
-		} else {
-			break;
-		}
-	}
-	if (bestPath != ZT_MAX_PEER_NETWORK_PATHS) {
-		return _paths[bestPath].p;
-	}
-	return SharedPtr<Path>();
+    Mutex::Lock _l(_paths_m);
+    Mutex::Lock _lb(_bond_m);
+    if(_bond && _bond->isReady()) {
+        return _bond->getAppropriatePath(now, flowId);
+    }
+    unsigned int bestPath = ZT_MAX_PEER_NETWORK_PATHS;
+    /**
+     * Send traffic across the highest quality path only. This algorithm will still
+     * use the old path quality metric from protocol version 9.
+     */
+    long bestPathQuality = 2147483647;
+    for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+        if (_paths[i].p) {
+            if ((includeExpired)||((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION)) {
+                const long q = _paths[i].p->quality(now) / _paths[i].priority;
+                if (q <= bestPathQuality) {
+                    bestPathQuality = q;
+                    bestPath = i;
+                }
+            }
+        } else {
+            break;
+        }
+    }
+    if (bestPath != ZT_MAX_PEER_NETWORK_PATHS) {
+        return _paths[bestPath].p;
+    }
+    return SharedPtr<Path>();
 }
 
 void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr<Peer> &other) const
 {
-	unsigned int myBestV4ByScope[ZT_INETADDRESS_MAX_SCOPE+1];
-	unsigned int myBestV6ByScope[ZT_INETADDRESS_MAX_SCOPE+1];
-	long myBestV4QualityByScope[ZT_INETADDRESS_MAX_SCOPE+1];
-	long myBestV6QualityByScope[ZT_INETADDRESS_MAX_SCOPE+1];
-	unsigned int theirBestV4ByScope[ZT_INETADDRESS_MAX_SCOPE+1];
-	unsigned int theirBestV6ByScope[ZT_INETADDRESS_MAX_SCOPE+1];
-	long theirBestV4QualityByScope[ZT_INETADDRESS_MAX_SCOPE+1];
-	long theirBestV6QualityByScope[ZT_INETADDRESS_MAX_SCOPE+1];
-	for(int i=0;i<=ZT_INETADDRESS_MAX_SCOPE;++i) {
-		myBestV4ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
-		myBestV6ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
-		myBestV4QualityByScope[i] = 2147483647;
-		myBestV6QualityByScope[i] = 2147483647;
-		theirBestV4ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
-		theirBestV6ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
-		theirBestV4QualityByScope[i] = 2147483647;
-		theirBestV6QualityByScope[i] = 2147483647;
-	}
-
-	Mutex::Lock _l1(_paths_m);
-
-	for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-		if (_paths[i].p) {
-			const long q = _paths[i].p->quality(now) / _paths[i].priority;
-			const unsigned int s = (unsigned int)_paths[i].p->ipScope();
-			switch(_paths[i].p->address().ss_family) {
-				case AF_INET:
-					if (q <= myBestV4QualityByScope[s]) {
-						myBestV4QualityByScope[s] = q;
-						myBestV4ByScope[s] = i;
-					}
-					break;
-				case AF_INET6:
-					if (q <= myBestV6QualityByScope[s]) {
-						myBestV6QualityByScope[s] = q;
-						myBestV6ByScope[s] = i;
-					}
-					break;
-			}
-		} else {
-			break;
-		}
-	}
-
-	Mutex::Lock _l2(other->_paths_m);
-
-	for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-		if (other->_paths[i].p) {
-			const long q = other->_paths[i].p->quality(now) / other->_paths[i].priority;
-			const unsigned int s = (unsigned int)other->_paths[i].p->ipScope();
-			switch(other->_paths[i].p->address().ss_family) {
-				case AF_INET:
-					if (q <= theirBestV4QualityByScope[s]) {
-						theirBestV4QualityByScope[s] = q;
-						theirBestV4ByScope[s] = i;
-					}
-					break;
-				case AF_INET6:
-					if (q <= theirBestV6QualityByScope[s]) {
-						theirBestV6QualityByScope[s] = q;
-						theirBestV6ByScope[s] = i;
-					}
-					break;
-			}
-		} else {
-			break;
-		}
-	}
-
-	unsigned int mine = ZT_MAX_PEER_NETWORK_PATHS;
-	unsigned int theirs = ZT_MAX_PEER_NETWORK_PATHS;
-
-	for(int s=ZT_INETADDRESS_MAX_SCOPE;s>=0;--s) {
-		if ((myBestV6ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)&&(theirBestV6ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)) {
-			mine = myBestV6ByScope[s];
-			theirs = theirBestV6ByScope[s];
-			break;
-		}
-		if ((myBestV4ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)&&(theirBestV4ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)) {
-			mine = myBestV4ByScope[s];
-			theirs = theirBestV4ByScope[s];
-			break;
-		}
-	}
-
-	if (mine != ZT_MAX_PEER_NETWORK_PATHS) {
-		unsigned int alt = (unsigned int)RR->node->prng() & 1; // randomize which hint we send first for black magickal NAT-t reasons
-		const unsigned int completed = alt + 2;
-		while (alt != completed) {
-			if ((alt & 1) == 0) {
-				Packet outp(_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS);
-				outp.append((uint8_t)0);
-				other->_id.address().appendTo(outp);
-				outp.append((uint16_t)other->_paths[theirs].p->address().port());
-				if (other->_paths[theirs].p->address().ss_family == AF_INET6) {
-					outp.append((uint8_t)16);
-					outp.append(other->_paths[theirs].p->address().rawIpData(),16);
-				} else {
-					outp.append((uint8_t)4);
-					outp.append(other->_paths[theirs].p->address().rawIpData(),4);
-				}
-				outp.armor(_key,true,aesKeysIfSupported());
-				Metrics::pkt_rendezvous_out++;
-				_paths[mine].p->send(RR,tPtr,outp.data(),outp.size(),now);
-			} else {
-				Packet outp(other->_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS);
-				outp.append((uint8_t)0);
-				_id.address().appendTo(outp);
-				outp.append((uint16_t)_paths[mine].p->address().port());
-				if (_paths[mine].p->address().ss_family == AF_INET6) {
-					outp.append((uint8_t)16);
-					outp.append(_paths[mine].p->address().rawIpData(),16);
-				} else {
-					outp.append((uint8_t)4);
-					outp.append(_paths[mine].p->address().rawIpData(),4);
-				}
-				outp.armor(other->_key,true,other->aesKeysIfSupported());
-				Metrics::pkt_rendezvous_out++;
-				other->_paths[theirs].p->send(RR,tPtr,outp.data(),outp.size(),now);
-			}
-			++alt;
-		}
-	}
+    unsigned int myBestV4ByScope[ZT_INETADDRESS_MAX_SCOPE+1];
+    unsigned int myBestV6ByScope[ZT_INETADDRESS_MAX_SCOPE+1];
+    long myBestV4QualityByScope[ZT_INETADDRESS_MAX_SCOPE+1];
+    long myBestV6QualityByScope[ZT_INETADDRESS_MAX_SCOPE+1];
+    unsigned int theirBestV4ByScope[ZT_INETADDRESS_MAX_SCOPE+1];
+    unsigned int theirBestV6ByScope[ZT_INETADDRESS_MAX_SCOPE+1];
+    long theirBestV4QualityByScope[ZT_INETADDRESS_MAX_SCOPE+1];
+    long theirBestV6QualityByScope[ZT_INETADDRESS_MAX_SCOPE+1];
+    for(int i=0;i<=ZT_INETADDRESS_MAX_SCOPE;++i) {
+        myBestV4ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
+        myBestV6ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
+        myBestV4QualityByScope[i] = 2147483647;
+        myBestV6QualityByScope[i] = 2147483647;
+        theirBestV4ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
+        theirBestV6ByScope[i] = ZT_MAX_PEER_NETWORK_PATHS;
+        theirBestV4QualityByScope[i] = 2147483647;
+        theirBestV6QualityByScope[i] = 2147483647;
+    }
+
+    Mutex::Lock _l1(_paths_m);
+
+    for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+        if (_paths[i].p) {
+            const long q = _paths[i].p->quality(now) / _paths[i].priority;
+            const unsigned int s = (unsigned int)_paths[i].p->ipScope();
+            switch(_paths[i].p->address().ss_family) {
+                case AF_INET:
+                    if (q <= myBestV4QualityByScope[s]) {
+                        myBestV4QualityByScope[s] = q;
+                        myBestV4ByScope[s] = i;
+                    }
+                    break;
+                case AF_INET6:
+                    if (q <= myBestV6QualityByScope[s]) {
+                        myBestV6QualityByScope[s] = q;
+                        myBestV6ByScope[s] = i;
+                    }
+                    break;
+            }
+        } else {
+            break;
+        }
+    }
+
+    Mutex::Lock _l2(other->_paths_m);
+
+    for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+        if (other->_paths[i].p) {
+            const long q = other->_paths[i].p->quality(now) / other->_paths[i].priority;
+            const unsigned int s = (unsigned int)other->_paths[i].p->ipScope();
+            switch(other->_paths[i].p->address().ss_family) {
+                case AF_INET:
+                    if (q <= theirBestV4QualityByScope[s]) {
+                        theirBestV4QualityByScope[s] = q;
+                        theirBestV4ByScope[s] = i;
+                    }
+                    break;
+                case AF_INET6:
+                    if (q <= theirBestV6QualityByScope[s]) {
+                        theirBestV6QualityByScope[s] = q;
+                        theirBestV6ByScope[s] = i;
+                    }
+                    break;
+            }
+        } else {
+            break;
+        }
+    }
+
+    unsigned int mine = ZT_MAX_PEER_NETWORK_PATHS;
+    unsigned int theirs = ZT_MAX_PEER_NETWORK_PATHS;
+
+    for(int s=ZT_INETADDRESS_MAX_SCOPE;s>=0;--s) {
+        if ((myBestV6ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)&&(theirBestV6ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)) {
+            mine = myBestV6ByScope[s];
+            theirs = theirBestV6ByScope[s];
+            break;
+        }
+        if ((myBestV4ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)&&(theirBestV4ByScope[s] != ZT_MAX_PEER_NETWORK_PATHS)) {
+            mine = myBestV4ByScope[s];
+            theirs = theirBestV4ByScope[s];
+            break;
+        }
+    }
+
+    if (mine != ZT_MAX_PEER_NETWORK_PATHS) {
+        unsigned int alt = (unsigned int)RR->node->prng() & 1; // randomize which hint we send first for black magickal NAT-t reasons
+        const unsigned int completed = alt + 2;
+        while (alt != completed) {
+            if ((alt & 1) == 0) {
+                Packet outp(_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS);
+                outp.append((uint8_t)0);
+                other->_id.address().appendTo(outp);
+                outp.append((uint16_t)other->_paths[theirs].p->address().port());
+                if (other->_paths[theirs].p->address().ss_family == AF_INET6) {
+                    outp.append((uint8_t)16);
+                    outp.append(other->_paths[theirs].p->address().rawIpData(),16);
+                } else {
+                    outp.append((uint8_t)4);
+                    outp.append(other->_paths[theirs].p->address().rawIpData(),4);
+                }
+                outp.armor(_key,true,false,aesKeysIfSupported(),_id);
+                Metrics::pkt_rendezvous_out++;
+                _paths[mine].p->send(RR,tPtr,outp.data(),outp.size(),now);
+            } else {
+                Packet outp(other->_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS);
+                outp.append((uint8_t)0);
+                _id.address().appendTo(outp);
+                outp.append((uint16_t)_paths[mine].p->address().port());
+                if (_paths[mine].p->address().ss_family == AF_INET6) {
+                    outp.append((uint8_t)16);
+                    outp.append(_paths[mine].p->address().rawIpData(),16);
+                } else {
+                    outp.append((uint8_t)4);
+                    outp.append(_paths[mine].p->address().rawIpData(),4);
+                }
+                outp.armor(other->_key,true,false,other->aesKeysIfSupported(),other->identity());
+                Metrics::pkt_rendezvous_out++;
+                other->_paths[theirs].p->send(RR,tPtr,outp.data(),outp.size(),now);
+            }
+            ++alt;
+        }
+    }
 }
 
 void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atAddress,int64_t now)
 {
-	Packet outp(_id.address(),RR->identity.address(),Packet::VERB_HELLO);
-
-	outp.append((unsigned char)ZT_PROTO_VERSION);
-	outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR);
-	outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR);
-	outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
-	outp.append(now);
-	RR->identity.serialize(outp,false);
-	atAddress.serialize(outp);
-
-	outp.append((uint64_t)RR->topology->planetWorldId());
-	outp.append((uint64_t)RR->topology->planetWorldTimestamp());
-
-	const unsigned int startCryptedPortionAt = outp.size();
-
-	std::vector<World> moons(RR->topology->moons());
-	std::vector<uint64_t> moonsWanted(RR->topology->moonsWanted());
-	outp.append((uint16_t)(moons.size() + moonsWanted.size()));
-	for(std::vector<World>::const_iterator m(moons.begin());m!=moons.end();++m) {
-		outp.append((uint8_t)m->type());
-		outp.append((uint64_t)m->id());
-		outp.append((uint64_t)m->timestamp());
-	}
-	for(std::vector<uint64_t>::const_iterator m(moonsWanted.begin());m!=moonsWanted.end();++m) {
-		outp.append((uint8_t)World::TYPE_MOON);
-		outp.append(*m);
-		outp.append((uint64_t)0);
-	}
-
-	outp.cryptField(_key,startCryptedPortionAt,outp.size() - startCryptedPortionAt);
-
-	Metrics::pkt_hello_out++;
-
-	if (atAddress) {
-		outp.armor(_key,false,nullptr); // false == don't encrypt full payload, but add MAC
-		RR->node->expectReplyTo(outp.packetId());
-		RR->node->putPacket(tPtr,RR->node->lowBandwidthModeEnabled() ? localSocket : -1,atAddress,outp.data(),outp.size());
-	} else {
-		RR->node->expectReplyTo(outp.packetId());
-		RR->sw->send(tPtr,outp,false); // false == don't encrypt full payload, but add MAC
-	}
+    Packet outp(_id.address(),RR->identity.address(),Packet::VERB_HELLO);
+
+    outp.append((unsigned char)ZT_PROTO_VERSION);
+    outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR);
+    outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR);
+    outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION);
+    outp.append(now);
+    RR->identity.serialize(outp,false);
+    atAddress.serialize(outp);
+
+    outp.append((uint64_t)RR->topology->planetWorldId());
+    outp.append((uint64_t)RR->topology->planetWorldTimestamp());
+
+    const unsigned int startCryptedPortionAt = outp.size();
+
+    std::vector<World> moons(RR->topology->moons());
+    std::vector<uint64_t> moonsWanted(RR->topology->moonsWanted());
+    outp.append((uint16_t)(moons.size() + moonsWanted.size()));
+    for(std::vector<World>::const_iterator m(moons.begin());m!=moons.end();++m) {
+        outp.append((uint8_t)m->type());
+        outp.append((uint64_t)m->id());
+        outp.append((uint64_t)m->timestamp());
+    }
+    for(std::vector<uint64_t>::const_iterator m(moonsWanted.begin());m!=moonsWanted.end();++m) {
+        outp.append((uint8_t)World::TYPE_MOON);
+        outp.append(*m);
+        outp.append((uint64_t)0);
+    }
+
+    outp.cryptField(_key,startCryptedPortionAt,outp.size() - startCryptedPortionAt);
+
+    Metrics::pkt_hello_out++;
+
+    if (atAddress) {
+        // TODO: this is where extended armor should be invoked
+        outp.armor(_key,false,false,nullptr,_id);
+        RR->node->expectReplyTo(outp.packetId());
+        RR->node->putPacket(tPtr,RR->node->lowBandwidthModeEnabled() ? localSocket : -1,atAddress,outp.data(),outp.size());
+    } else {
+        RR->node->expectReplyTo(outp.packetId());
+        RR->sw->send(tPtr,outp,true);
+    }
 }
 
 void Peer::attemptToContactAt(void *tPtr,const int64_t localSocket,const InetAddress &atAddress,int64_t now,bool sendFullHello)
 {
-	if ( (!sendFullHello) && (_vProto >= 5) && (!((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0))) ) {
-		Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO);
-		outp.armor(_key,true,aesKeysIfSupported());
-		Metrics::pkt_echo_out++;
-		RR->node->expectReplyTo(outp.packetId());
-		RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size());
-	} else {
-		sendHELLO(tPtr,localSocket,atAddress,now);
-	}
+    if ( (!sendFullHello) && (_vProto >= 5) && (!((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0))) ) {
+        Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO);
+        outp.armor(_key,true,false,aesKeysIfSupported(),_id);
+        Metrics::pkt_echo_out++;
+        RR->node->expectReplyTo(outp.packetId());
+        RR->node->putPacket(tPtr,localSocket,atAddress,outp.data(),outp.size());
+    } else {
+        sendHELLO(tPtr,localSocket,atAddress,now);
+    }
 }
 
 void Peer::tryMemorizedPath(void *tPtr,int64_t now)
 {
-	if ((now - _lastTriedMemorizedPath) >= ZT_TRY_MEMORIZED_PATH_INTERVAL) {
-		_lastTriedMemorizedPath = now;
-		InetAddress mp;
-		if (RR->node->externalPathLookup(tPtr,_id.address(),-1,mp)) {
-			attemptToContactAt(tPtr,-1,mp,now,true);
-		}
-	}
+    if ((now - _lastTriedMemorizedPath) >= ZT_TRY_MEMORIZED_PATH_INTERVAL) {
+        _lastTriedMemorizedPath = now;
+        InetAddress mp;
+        if (RR->node->externalPathLookup(tPtr,_id.address(),-1,mp)) {
+            attemptToContactAt(tPtr,-1,mp,now,true);
+        }
+    }
 }
 
 void Peer::performMultipathStateCheck(void *tPtr, int64_t now)
 {
-	Mutex::Lock _l(_bond_m);
-	/**
-	 * Check for conditions required for multipath bonding and create a bond
-	 * if allowed.
-	 */
-	int numAlivePaths = 0;
-	bool atLeastOneNonExpired = false;
-	for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-		if (_paths[i].p) {
-			if(_paths[i].p->alive(now)) {
-				numAlivePaths++;
-			}
-			if ((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) {
-				atLeastOneNonExpired = true;
-			}
-		}
-	}
-	if (_bond) {
-		if (numAlivePaths == 0 && !atLeastOneNonExpired) {
-			_bond = SharedPtr<Bond>();
-			RR->bc->destroyBond(_id.address().toInt());
-		}
-		return;
-	}
-	_localMultipathSupported = ((numAlivePaths >= 1) && (RR->bc->inUse()) && (ZT_PROTO_VERSION > 9));
-	if (_localMultipathSupported && !_bond) {
-		if (RR->bc) {
-			_bond = RR->bc->createBond(RR, this);
-			/**
-			 * Allow new bond to retroactively learn all paths known to this peer
-			 */
-			if (_bond) {
-				for (unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-					if (_paths[i].p) {
-						_bond->nominatePathToBond(_paths[i].p, now);
-					}
-				}
-			}
-		}
-	}
+    Mutex::Lock _l(_bond_m);
+    /**
+     * Check for conditions required for multipath bonding and create a bond
+     * if allowed.
+     */
+    int numAlivePaths = 0;
+    bool atLeastOneNonExpired = false;
+    for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+        if (_paths[i].p) {
+            if(_paths[i].p->alive(now)) {
+                numAlivePaths++;
+            }
+            if ((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) {
+                atLeastOneNonExpired = true;
+            }
+        }
+    }
+    if (_bond) {
+        if (numAlivePaths == 0 && !atLeastOneNonExpired) {
+            _bond = SharedPtr<Bond>();
+            RR->bc->destroyBond(_id.address().toInt());
+        }
+        return;
+    }
+    _localMultipathSupported = ((numAlivePaths >= 1) && (RR->bc->inUse()) && (ZT_PROTO_VERSION > 9));
+    if (_localMultipathSupported && !_bond) {
+        if (RR->bc) {
+            _bond = RR->bc->createBond(RR, this);
+            /**
+             * Allow new bond to retroactively learn all paths known to this peer
+             */
+            if (_bond) {
+                for (unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+                    if (_paths[i].p) {
+                        _bond->nominatePathToBond(_paths[i].p, now);
+                    }
+                }
+            }
+        }
+    }
 }
 
 unsigned int Peer::doPingAndKeepalive(void *tPtr,int64_t now)
 {
-	unsigned int sent = 0;
-	{
-		Mutex::Lock _l(_paths_m);
-
-		performMultipathStateCheck(tPtr, now);
-
-		const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD);
-		if (sendFullHello) {
-			_lastSentFullHello = now;
-		}
-
-		// Right now we only keep pinging links that have the maximum priority. The
-		// priority is used to track cluster redirections, meaning that when a cluster
-		// redirects us its redirect target links override all other links and we
-		// let those old links expire.
-		long maxPriority = 0;
-		for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-			if (_paths[i].p) {
-				maxPriority = std::max(_paths[i].priority,maxPriority);
-			} else {
-				break;
-			}
-		}
-
-		bool deletionOccurred = false;
-		for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-			if (_paths[i].p) {
-				// Clean expired and reduced priority paths
-				if ( ((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) && (_paths[i].priority == maxPriority) ) {
-					if ((sendFullHello)||(_paths[i].p->needsHeartbeat(now))) {
-						attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,sendFullHello);
-						_paths[i].p->sent(now);
-						sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2;
-					}
-				} else {
-					_paths[i] = _PeerPath();
-					deletionOccurred = true;
-				}
-			}
-			if (!_paths[i].p || deletionOccurred) {
-				for(unsigned int j=i;j<ZT_MAX_PEER_NETWORK_PATHS;++j) {
-					if (_paths[j].p && i != j) {
-						_paths[i] = _paths[j];
-						_paths[j] = _PeerPath();
-						break;
-					}
-				}
-				deletionOccurred = false;
-			}
-		}
+    unsigned int sent = 0;
+    {
+        Mutex::Lock _l(_paths_m);
+
+        performMultipathStateCheck(tPtr, now);
+
+        const bool sendFullHello = ((now - _lastSentFullHello) >= ZT_PEER_PING_PERIOD);
+        if (sendFullHello) {
+            _lastSentFullHello = now;
+        }
+
+        // Right now we only keep pinging links that have the maximum priority. The
+        // priority is used to track cluster redirections, meaning that when a cluster
+        // redirects us its redirect target links override all other links and we
+        // let those old links expire.
+        long maxPriority = 0;
+        for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+            if (_paths[i].p) {
+                maxPriority = std::max(_paths[i].priority,maxPriority);
+            } else {
+                break;
+            }
+        }
+
+        bool deletionOccurred = false;
+        for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+            if (_paths[i].p) {
+                // Clean expired and reduced priority paths
+                if ( ((now - _paths[i].lr) < ZT_PEER_PATH_EXPIRATION) && (_paths[i].priority == maxPriority) ) {
+                    if ((sendFullHello)||(_paths[i].p->needsHeartbeat(now))) {
+                        attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,sendFullHello);
+                        _paths[i].p->sent(now);
+                        sent |= (_paths[i].p->address().ss_family == AF_INET) ? 0x1 : 0x2;
+                    }
+                } else {
+                    _paths[i] = _PeerPath();
+                    deletionOccurred = true;
+                }
+            }
+            if (!_paths[i].p || deletionOccurred) {
+                for(unsigned int j=i;j<ZT_MAX_PEER_NETWORK_PATHS;++j) {
+                    if (_paths[j].p && i != j) {
+                        _paths[i] = _paths[j];
+                        _paths[j] = _PeerPath();
+                        break;
+                    }
+                }
+                deletionOccurred = false;
+            }
+        }
 #ifndef ZT_NO_PEER_METRICS
-		uint16_t alive_path_count_tmp = 0, dead_path_count_tmp = 0;
-		for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-			if (_paths[i].p) {
-				if (_paths[i].p->alive(now)) {
-					alive_path_count_tmp++;
-				}
-				else {
-					dead_path_count_tmp++;
-				}
-			}
-		}
-		_alive_path_count = alive_path_count_tmp;
-		_dead_path_count = dead_path_count_tmp;
+        uint16_t alive_path_count_tmp = 0, dead_path_count_tmp = 0;
+        for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+            if (_paths[i].p) {
+                if (_paths[i].p->alive(now)) {
+                    alive_path_count_tmp++;
+                }
+                else {
+                    dead_path_count_tmp++;
+                }
+            }
+        }
+        _alive_path_count = alive_path_count_tmp;
+        _dead_path_count = dead_path_count_tmp;
 #endif
-	}
+    }
 #ifndef ZT_NO_PEER_METRICS
-	_peer_latency.Observe(latency(now));
+    _peer_latency.Observe(latency(now));
 #endif
-	return sent;
+    return sent;
 }
 
 void Peer::clusterRedirect(void *tPtr,const SharedPtr<Path> &originatingPath,const InetAddress &remoteAddress,const int64_t now)
 {
-	SharedPtr<Path> np(RR->topology->getPath(originatingPath->localSocket(),remoteAddress));
-	RR->t->peerRedirected(tPtr,0,*this,np);
-
-	attemptToContactAt(tPtr,originatingPath->localSocket(),remoteAddress,now,true);
-
-	{
-		Mutex::Lock _l(_paths_m);
-
-		// New priority is higher than the priority of the originating path (if known)
-		long newPriority = 1;
-		for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-			if (_paths[i].p) {
-				if (_paths[i].p == originatingPath) {
-					newPriority = _paths[i].priority;
-					break;
-				}
-			} else {
-				break;
-			}
-		}
-		newPriority += 2;
-
-		// Erase any paths with lower priority than this one or that are duplicate
-		// IPs and add this path.
-		unsigned int j = 0;
-		for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-			if (_paths[i].p) {
-				if ((_paths[i].priority >= newPriority)&&(!_paths[i].p->address().ipsEqual2(remoteAddress))) {
-					if (i != j) {
-						_paths[j] = _paths[i];
-					}
-					++j;
-				}
-			}
-		}
-		if (j < ZT_MAX_PEER_NETWORK_PATHS) {
-			_paths[j].lr = now;
-			_paths[j].p = np;
-			_paths[j].priority = newPriority;
-			++j;
-			while (j < ZT_MAX_PEER_NETWORK_PATHS) {
-				_paths[j].lr = 0;
-				_paths[j].p.zero();
-				_paths[j].priority = 1;
-				++j;
-			}
-		}
-	}
+    SharedPtr<Path> np(RR->topology->getPath(originatingPath->localSocket(),remoteAddress));
+    RR->t->peerRedirected(tPtr,0,*this,np);
+
+    attemptToContactAt(tPtr,originatingPath->localSocket(),remoteAddress,now,true);
+
+    {
+        Mutex::Lock _l(_paths_m);
+
+        // New priority is higher than the priority of the originating path (if known)
+        long newPriority = 1;
+        for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+            if (_paths[i].p) {
+                if (_paths[i].p == originatingPath) {
+                    newPriority = _paths[i].priority;
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        newPriority += 2;
+
+        // Erase any paths with lower priority than this one or that are duplicate
+        // IPs and add this path.
+        unsigned int j = 0;
+        for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+            if (_paths[i].p) {
+                if ((_paths[i].priority >= newPriority)&&(!_paths[i].p->address().ipsEqual2(remoteAddress))) {
+                    if (i != j) {
+                        _paths[j] = _paths[i];
+                    }
+                    ++j;
+                }
+            }
+        }
+        if (j < ZT_MAX_PEER_NETWORK_PATHS) {
+            _paths[j].lr = now;
+            _paths[j].p = np;
+            _paths[j].priority = newPriority;
+            ++j;
+            while (j < ZT_MAX_PEER_NETWORK_PATHS) {
+                _paths[j].lr = 0;
+                _paths[j].p.zero();
+                _paths[j].priority = 1;
+                ++j;
+            }
+        }
+    }
 }
 
 void Peer::resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddressFamily,int64_t now)
 {
-	Mutex::Lock _l(_paths_m);
-	for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-		if (_paths[i].p) {
-			if ((_paths[i].p->address().ss_family == inetAddressFamily)&&(_paths[i].p->ipScope() == scope)) {
-				attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,false);
-				_paths[i].p->sent(now);
-				_paths[i].lr = 0; // path will not be used unless it speaks again
-			}
-		} else {
-			break;
-		}
-	}
+    Mutex::Lock _l(_paths_m);
+    for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+        if (_paths[i].p) {
+            if ((_paths[i].p->address().ss_family == inetAddressFamily)&&(_paths[i].p->ipScope() == scope)) {
+                attemptToContactAt(tPtr,_paths[i].p->localSocket(),_paths[i].p->address(),now,false);
+                _paths[i].p->sent(now);
+                _paths[i].lr = 0; // path will not be used unless it speaks again
+            }
+        } else {
+            break;
+        }
+    }
 }
 
 void Peer::recordOutgoingPacket(const SharedPtr<Path> &path, const uint64_t packetId,
-	uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now)
+    uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now)
 {
 #ifndef ZT_NO_PEER_METRICS
-	_outgoing_packet++;
+    _outgoing_packet++;
 #endif
-	if (_localMultipathSupported && _bond) {
-		_bond->recordOutgoingPacket(path, packetId, payloadLength, verb, flowId, now);
-	}
+    if (_localMultipathSupported && _bond) {
+        _bond->recordOutgoingPacket(path, packetId, payloadLength, verb, flowId, now);
+    }
 }
 
 void Peer::recordIncomingInvalidPacket(const SharedPtr<Path>& path)
 {
 #ifndef ZT_NO_PEER_METRICS
-	_packet_errors++;
+    _packet_errors++;
 #endif
-	if (_localMultipathSupported && _bond) {
-		_bond->recordIncomingInvalidPacket(path);
-	}
+    if (_localMultipathSupported && _bond) {
+        _bond->recordIncomingInvalidPacket(path);
+    }
 }
 
 void Peer::recordIncomingPacket(const SharedPtr<Path> &path, const uint64_t packetId,
-	uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now)
+    uint16_t payloadLength, const Packet::Verb verb, const int32_t flowId, int64_t now)
 {
-	if (_localMultipathSupported && _bond) {
-		_bond->recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now);
-	}
+    if (_localMultipathSupported && _bond) {
+        _bond->recordIncomingPacket(path, packetId, payloadLength, verb, flowId, now);
+    }
 }
 
 } // namespace ZeroTier

File diff suppressed because it is too large
+ 617 - 617
node/Peer.hpp


+ 1 - 1
node/Switch.cpp

@@ -1119,7 +1119,7 @@ void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr<Peer> peer,SharedPtr<Path
 		packet.setTrusted(trustedPathId);
 	} else {
 		if (!packet.isEncrypted()) {
-			packet.armor(peer->key(),encrypt,peer->aesKeysIfSupported());
+			packet.armor(peer->key(),encrypt,false,peer->aesKeysIfSupported(),peer->identity());
 		}
 		RR->node->expectReplyTo(packet.packetId());
 	}

Some files were not shown because too many files changed in this diff