Browse Source

Wiring back up PUSH_DIRECT_PATHS and friends.

Adam Ierymenko 5 years ago
parent
commit
ee5a988f14

+ 2 - 5
go/native/GoGlue.cpp

@@ -28,14 +28,10 @@
 
 #ifndef __WINDOWS__
 #include <unistd.h>
-#include <fcntl.h>
-#include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
-#include <netinet/ip6.h>
-#include <netinet/tcp.h>
 #ifdef __BSD__
 #include <net/if.h>
 #endif
@@ -104,7 +100,7 @@ const char *ZT_PLATFORM_DEFAULT_HOMEPATH = defaultHomePath.c_str();
 
 /* These functions are implemented in Go in pkg/ztnode/node-callbacks.go */
 extern "C" int goPathCheckFunc(void *,uint64_t,int,const void *,int);
-extern "C" int goPathLookupFunc(void *,uint64_t,int,int *,uint8_t [16],int *);
+extern "C" int goPathLookupFunc(void *,uint64_t,int,const ZT_Identity *,int *,uint8_t [16],int *);
 extern "C" void goStateObjectPutFunc(void *,int,const uint64_t [2],const void *,int);
 extern "C" int goStateObjectGetFunc(void *,int,const uint64_t [2],void *,unsigned int);
 extern "C" void goVirtualNetworkConfigFunc(void *,ZT_GoTap *,uint64_t,int,const ZT_VirtualNetworkConfig *);
@@ -282,6 +278,7 @@ static int ZT_GoNode_PathLookupFunction(
 		reinterpret_cast<ZT_GoNode *>(uptr)->goUserPtr,
 		ztAddress,
 		desiredAddressFamily,
+		id,
 		&family,
 		ip,
 		&port

+ 13 - 6
go/pkg/zerotier/identity.go

@@ -46,13 +46,10 @@ type Identity struct {
 	privateKey []byte
 }
 
-// NewIdentity generates a new identity of the selected type
-func NewIdentity(identityType int) (*Identity, error) {
-	cid := C.ZT_Identity_new(C.enum_ZT_Identity_Type(identityType))
-	if uintptr(unsafe.Pointer(cid)) == 0 {
-		return nil, ErrInternal
+func newIdentityFromCIdentity(cid unsafe.Pointer) (*Identity, error) {
+	if uintptr(cid) == 0 {
+		return nil, ErrInvalidParameter
 	}
-	defer C.ZT_Identity_delete(cid)
 	var idStrBuf [4096]byte
 	idStr := C.ZT_Identity_toString(cid,(*C.char)(unsafe.Pointer(&idStrBuf[0])),4096,1)
 	if uintptr(unsafe.Pointer(idStr)) == 0 {
@@ -61,6 +58,16 @@ func NewIdentity(identityType int) (*Identity, error) {
 	return NewIdentityFromString(C.GoString(idStr))
 }
 
+// NewIdentity generates a new identity of the selected type
+func NewIdentity(identityType int) (*Identity, error) {
+	cid := C.ZT_Identity_new(C.enum_ZT_Identity_Type(identityType))
+	if uintptr(unsafe.Pointer(cid)) == 0 {
+		return nil, ErrInternal
+	}
+	defer C.ZT_Identity_delete(cid)
+	return newIdentityFromCIdentity(cid)
+}
+
 // NewIdentityFromString generates a new identity from its string representation.
 // The private key is imported as well if it is present.
 func NewIdentityFromString(s string) (*Identity, error) {

+ 8 - 4
go/pkg/zerotier/node.go

@@ -660,10 +660,10 @@ func (n *Node) pathCheck(ztAddress Address, af int, ip net.IP, port int) bool {
 	return true
 }
 
-func (n *Node) pathLookup(ztAddress Address) (net.IP, int) {
+func (n *Node) pathLookup(id *Identity) (net.IP, int) {
 	n.localConfigLock.RLock()
 	defer n.localConfigLock.RUnlock()
-	virt := n.localConfig.Virtual[ztAddress]
+	virt := n.localConfig.Virtual[id.address]
 	if len(virt.Try) > 0 {
 		idx := rand.Int() % len(virt.Try)
 		return virt.Try[idx].IP, virt.Try[idx].Port
@@ -763,7 +763,7 @@ func goPathCheckFunc(gn unsafe.Pointer, ztAddress C.uint64_t, af C.int, ip unsaf
 }
 
 //export goPathLookupFunc
-func goPathLookupFunc(gn unsafe.Pointer, ztAddress C.uint64_t, _ int, familyP, ipP, portP unsafe.Pointer) C.int {
+func goPathLookupFunc(gn unsafe.Pointer, ztAddress C.uint64_t, desiredFamily int, identity, familyP, ipP, portP unsafe.Pointer) C.int {
 	nodesByUserPtrLock.RLock()
 	node := nodesByUserPtr[uintptr(gn)]
 	nodesByUserPtrLock.RUnlock()
@@ -771,7 +771,11 @@ func goPathLookupFunc(gn unsafe.Pointer, ztAddress C.uint64_t, _ int, familyP, i
 		return 0
 	}
 
-	ip, port := node.pathLookup(Address(ztAddress))
+	id, err := newIdentityFromCIdentity(identity)
+	if err != nil {
+		return 0
+	}
+	ip, port := node.pathLookup(id)
 	if len(ip) > 0 && port > 0 && port <= 65535 {
 		ip4 := ip.To4()
 		if len(ip4) == 4 {

+ 8 - 0
go/pkg/zerotier/root.go

@@ -0,0 +1,8 @@
+package zerotier
+
+// Root is a root server with one or more permanent IPs.
+type Root struct {
+	Identity Identity
+	DNSName string
+	PhysicalAddresses []InetAddress
+}

+ 5 - 0
node/Constants.hpp

@@ -141,6 +141,11 @@
  */
 #define ZT_PEER_ACTIVITY_TIMEOUT ((ZT_PEER_PING_PERIOD * 2) + 5000)
 
+/**
+ * Maximum interval between sort/prioritize of paths for a peer
+ */
+#define ZT_PEER_PRIORITIZE_PATHS_INTERVAL 5000
+
 /**
  * Delay between requests for updated network autoconf information
  *

+ 11 - 10
node/IncomingPacket.cpp

@@ -348,6 +348,7 @@ ZT_ALWAYS_INLINE bool _doWHOIS(IncomingPacket &pkt,const RuntimeEnvironment *con
 ZT_ALWAYS_INLINE bool _doRENDEZVOUS(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr<Peer> &peer,const SharedPtr<Path> &path)
 {
 	if (RR->topology->isRoot(peer->identity())) {
+		uint16_t junk = (uint16_t)Utils::random();
 		const Address with(pkt.field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
 		const SharedPtr<Peer> rendezvousWith(RR->topology->get(with));
 		if (rendezvousWith) {
@@ -355,9 +356,9 @@ ZT_ALWAYS_INLINE bool _doRENDEZVOUS(IncomingPacket &pkt,const RuntimeEnvironment
 			const unsigned int addrlen = pkt[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN];
 			if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) {
 				InetAddress atAddr(pkt.field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port);
-				if (RR->node->shouldUsePathForZeroTierTraffic(tPtr,with,path->localSocket(),atAddr)) {
-					const uint64_t junk = Utils::random();
-					RR->node->putPacket(tPtr,path->localSocket(),atAddr,&junk,4,2); // send low-TTL junk packet to 'open' local NAT(s) and stateful firewalls
+				if (rendezvousWith->shouldTryPath(tPtr,RR->node->now(),peer,atAddr)) {
+					if (atAddr.isV4())
+						RR->node->putPacket(tPtr,path->localSocket(),atAddr,&junk,2,2); // IPv4 "firewall opener"
 					rendezvousWith->sendHELLO(tPtr,path->localSocket(),atAddr,RR->node->now());
 				}
 			}
@@ -669,6 +670,7 @@ ZT_ALWAYS_INLINE bool _doPUSH_DIRECT_PATHS(IncomingPacket &pkt,const RuntimeEnvi
 
 		unsigned int count = pkt.at<uint16_t>(ZT_PACKET_IDX_PAYLOAD);
 		unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 2;
+		uint16_t junk = (uint16_t)Utils::random();
 
 		while (count--) {
 			/* unsigned int flags = (*this)[ptr++]; */ ++ptr;
@@ -680,18 +682,17 @@ ZT_ALWAYS_INLINE bool _doPUSH_DIRECT_PATHS(IncomingPacket &pkt,const RuntimeEnvi
 			switch(addrType) {
 				case 4: {
 					const InetAddress a(pkt.field(ptr,4),4,pkt.at<uint16_t>(ptr + 4));
-					if ((!peer->hasActivePathTo(now,a)) && // not already known
-							(RR->node->shouldUsePathForZeroTierTraffic(tPtr,peer->address(),-1,a)) ) // should use path
-					{
-						if (++countPerScope[(int)a.ipScope()][0] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY)
+					if (peer->shouldTryPath(tPtr,now,peer,a)) {
+						if (++countPerScope[(int)a.ipScope()][0] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) {
+							RR->node->putPacket(tPtr,path->localSocket(),a,&junk,2,2); // IPv4 "firewall opener"
+							++junk;
 							peer->sendHELLO(tPtr,-1,a,now);
+						}
 					}
 				}	break;
 				case 6: {
 					const InetAddress a(pkt.field(ptr,16),16,pkt.at<uint16_t>(ptr + 16));
-					if ((!peer->hasActivePathTo(now,a)) && // not already known
-							(RR->node->shouldUsePathForZeroTierTraffic(tPtr,peer->address(),-1,a)) ) // should use path
-					{
+					if (peer->shouldTryPath(tPtr,now,peer,a)) {
 						if (++countPerScope[(int)a.ipScope()][1] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY)
 							peer->sendHELLO(tPtr,-1,a,now);
 					}

+ 2 - 1
node/Mutex.hpp

@@ -60,13 +60,14 @@ public:
 	ZT_ALWAYS_INLINE void lock() const { pthread_rwlock_wrlock(&((const_cast <RWMutex *> (this))->_mh)); }
 	ZT_ALWAYS_INLINE void rlock() const { pthread_rwlock_rdlock(&((const_cast <RWMutex *> (this))->_mh)); }
 	ZT_ALWAYS_INLINE void unlock() const { pthread_rwlock_unlock(&((const_cast <RWMutex *> (this))->_mh)); }
+	ZT_ALWAYS_INLINE void runlock() const { pthread_rwlock_unlock(&((const_cast <RWMutex *> (this))->_mh)); }
 
 	class RLock
 	{
 	public:
 		ZT_ALWAYS_INLINE RLock(RWMutex &m) : _m(&m) { m.rlock(); }
 		ZT_ALWAYS_INLINE RLock(const RWMutex &m) : _m(const_cast<RWMutex *>(&m)) { _m->rlock(); }
-		ZT_ALWAYS_INLINE ~RLock() { _m->unlock(); }
+		ZT_ALWAYS_INLINE ~RLock() { _m->runlock(); }
 	private:
 		RWMutex *const _m;
 	};

+ 2 - 2
node/Node.cpp

@@ -174,10 +174,10 @@ struct _processBackgroundTasks_ping_eachPeer
 	ZT_ALWAYS_INLINE bool operator()(const SharedPtr<Peer> &peer,const bool isRoot)
 	{
 		unsigned int v4SendCount = 0,v6SendCount = 0;
-		peer->ping(tPtr,now,v4SendCount,v6SendCount);
+		peer->ping(tPtr,now,v4SendCount,v6SendCount,isRoot);
 
 		if (isRoot) {
-			if ((now - peer->lastReceive()) <= ZT_PEER_PING_PERIOD)
+			if ((now - peer->lastReceive()) <= (ZT_PEER_PING_PERIOD + 5000))
 				online = true;
 
 			if (v4SendCount == 0) {

+ 7 - 0
node/Path.hpp

@@ -92,6 +92,13 @@ public:
 	 */
 	bool send(const RuntimeEnvironment *RR,void *tPtr,const void *data,unsigned int len,int64_t now);
 
+	/**
+	 * Explicitly update last sent time
+	 *
+	 * @param t Time of send
+	 */
+	ZT_ALWAYS_INLINE void sent(const uint64_t t) { _lastOut = t; }
+
 	/**
 	 * Called when a packet is received from this remote path, regardless of content
 	 *

+ 163 - 90
node/Peer.cpp

@@ -23,15 +23,25 @@
 
 namespace ZeroTier {
 
+struct _PathPriorityComparisonOperator
+{
+	ZT_ALWAYS_INLINE bool operator()(const SharedPtr<Path> &a,const SharedPtr<Path> &b) const
+	{
+		return ( ((a)&&(a->lastIn() > 0)) && ((!b)||(b->lastIn() <= 0)||(a->lastIn() < b->lastIn())) );
+	}
+};
+
 Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) :
 	RR(renv),
 	_lastReceive(0),
 	_lastWhoisRequestReceived(0),
 	_lastEchoRequestReceived(0),
 	_lastPushDirectPathsReceived(0),
+	_lastPushDirectPathsSent(0),
 	_lastTriedStaticPath(0),
+	_lastPrioritizedPaths(0),
 	_latency(0xffff),
-	_pathCount(0),
+	_alivePathCount(0),
 	_id(peerIdentity),
 	_vProto(0),
 	_vMajor(0),
@@ -54,85 +64,64 @@ void Peer::received(
 	const uint64_t networkId)
 {
 	const int64_t now = RR->node->now();
-
 	_lastReceive = 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]) {
-					if (_paths[i] == path) {
-						havePath = true;
-						break;
-					}
-				} else break;
+		_paths_l.rlock();
+		for(int i=0;i<(int)_alivePathCount; ++i) {
+			if (_paths[i] == path) {
+				_paths_l.runlock();
+				goto path_check_done;
 			}
 		}
+		_paths_l.runlock();
 
-		bool attemptToContact = false;
-		if ((!havePath)&&(RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localSocket(),path->address()))) {
-			Mutex::Lock _l(_paths_m);
-
-			// Paths are redundant if they duplicate an alive path to the same IP or
-			// with the same local socket and address family.
-			bool redundant = false;
-			unsigned int replacePath = ZT_MAX_PEER_NETWORK_PATHS;
-			for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-				if (_paths[i]) {
-					if ( (_paths[i]->alive(now)) && ( ((_paths[i]->localSocket() == path->localSocket())&&(_paths[i]->address().ss_family == path->address().ss_family)) || (_paths[i]->address().ipsEqual2(path->address())) ) ) {
-						redundant = true;
-						break;
-					}
-					// If the path is the same address and port, simply assume this is a replacement
-					if ( (_paths[i]->address().ipsEqual2(path->address()))) {
-						replacePath = i;
-						break;
-					}
-				} else break;
-			}
+		if (verb == Packet::VERB_OK) {
+			RWMutex::Lock l(_paths_l);
 
-			// If the path isn't a duplicate of the same localSocket AND we haven't already determined a replacePath,
-			// then find the worst path and replace it.
-			if (!redundant && replacePath == ZT_MAX_PEER_NETWORK_PATHS) {
-				int replacePathQuality = 0;
-				for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+			int64_t lastReceiveTimeMax = 0;
+			int lastReceiveTimeMaxAt = 0;
+			for(int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+				if ((_paths[i]->address().ss_family == path->address().ss_family) &&
+				    (_paths[i]->localSocket() == path->localSocket()) && // TODO: should be localInterface when multipath is integrated
+				    (_paths[i]->address().ipsEqual2(path->address()))) {
+					// If this is another path to the same place, swap it out as the
+					// one we just received from may replace an old one but don't
+					// learn it as a new path.
+					_paths[i] = path;
+					goto path_check_done;
+				} else {
 					if (_paths[i]) {
-						const int q = _paths[i]->quality(now);
-						if (q > replacePathQuality) {
-							replacePathQuality = q;
-							replacePath = i;
+						if (_paths[i]->lastIn() > lastReceiveTimeMax) {
+							lastReceiveTimeMax = _paths[i]->lastIn();
+							lastReceiveTimeMaxAt = i;
 						}
 					} else {
-						replacePath = i;
-						break;
+						lastReceiveTimeMax = 0x7fffffffffffffffLL;
+						lastReceiveTimeMaxAt = i;
 					}
 				}
 			}
 
-			if (replacePath != ZT_MAX_PEER_NETWORK_PATHS) {
-				if (verb == Packet::VERB_OK) {
-					RR->t->peerLearnedNewPath(tPtr,networkId,*this,path,packetId);
-					_paths[replacePath] = path;
-				} else {
-					attemptToContact = true;
-				}
+			_lastPrioritizedPaths = now;
+			_paths[lastReceiveTimeMaxAt] = path;
+			_prioritizePaths(now);
+			RR->t->peerLearnedNewPath(tPtr,networkId,*this,path,packetId);
+		} else {
+			if (RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localSocket(),path->address())) {
+				sendHELLO(tPtr,path->localSocket(),path->address(),now);
+				path->sent(now);
+				RR->t->peerConfirmingUnknownPath(tPtr,networkId,*this,path,packetId,verb);
 			}
 		}
+	}
 
-		if (attemptToContact) {
-			sendHELLO(tPtr,path->localSocket(),path->address(),now);
-			path->sent(now);
-			RR->t->peerConfirmingUnknownPath(tPtr,networkId,*this,path,packetId,verb);
-		}
+path_check_done:
+	const int64_t sinceLastPush = now - _lastPushDirectPathsSent;
+	if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL)) {
+		_lastPushDirectPathsReceived = now;
 	}
-	*/
 
-	// Periodically push direct paths to the peer, doing so more often if we do not
-	// currently have a direct path.
 	/*
 	const int64_t sinceLastPush = now - _lastDirectPathPushSent;
 	if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL)) {
@@ -189,10 +178,23 @@ void Peer::received(
 	*/
 }
 
-bool Peer::hasActivePathTo(int64_t now,const InetAddress &addr) const
+bool Peer::shouldTryPath(void *tPtr,int64_t now,const SharedPtr<Peer> &suggestedBy,const InetAddress &addr) const
 {
-	// TODO
-	return false;
+	int maxHaveScope = -1;
+	{
+		RWMutex::RLock l(_paths_l);
+		for (unsigned int i = 0; i < _alivePathCount; ++i) {
+			if (_paths[i]) {
+				if (_paths[i]->address().ipsEqual2(addr))
+					return false;
+
+				int s = (int)_paths[i]->address().ipScope();
+				if (s > maxHaveScope)
+					maxHaveScope = s;
+			}
+		}
+	}
+	return ( ((int)addr.ipScope() > maxHaveScope) && RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),-1,addr) );
 }
 
 void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atAddress,int64_t now)
@@ -217,54 +219,125 @@ void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atA
 	}
 }
 
-void Peer::ping(void *tPtr,int64_t now,unsigned int &v4SendCount,unsigned int &v6SendCount)
+void Peer::ping(void *tPtr,int64_t now,unsigned int &v4SendCount,unsigned int &v6SendCount,const bool pingAllAddressTypes)
 {
-	/*
-	Mutex::Lock _l(_paths_m);
+	RWMutex::RLock l(_paths_l);
 
-	unsigned int j = 0;
-	for(unsigned int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
-		if ((_paths[i])&&(_paths[i]->alive(now))) {
-			sendHELLO(tPtr,_paths[i]->localSocket(),_paths[i]->address(),now);
+	_lastPrioritizedPaths = now;
+	_prioritizePaths(now);
 
+	if (_alivePathCount > 0) {
+		for (unsigned int i = 0; i < _alivePathCount; ++i) {
+			sendHELLO(tPtr,_paths[i]->localSocket(),_paths[i]->address(),now);
 			_paths[i]->sent(now);
+
 			if (_paths[i]->address().isV4())
 				++v4SendCount;
 			else if (_paths[i]->address().isV6())
 				++v6SendCount;
 
-			if (i != j)
-				_paths[j] = _paths[i];
-			++j;
+			if (!pingAllAddressTypes)
+				break;
+		}
+	} else {
+		SharedPtr<Peer> r(RR->topology->root());
+		if (r) {
+			SharedPtr<Path> rp(r->path(now));
+			if (rp) {
+				sendHELLO(tPtr,rp->localSocket(),rp->address(),now);
+				rp->sent(now);
+			}
 		}
 	}
-	while(j < ZT_MAX_PEER_NETWORK_PATHS) {
-		_paths[j].zero();
-		++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]) {
-			if ((_paths[i]->address().ss_family == inetAddressFamily)&&(_paths[i]->ipScope() == scope)) {
-				sendHELLO(tPtr,_paths[i]->localSocket(),_paths[i]->address(),now);
-				_paths[i]->sent(now);
-			}
-		} else break;
+	RWMutex::RLock l(_paths_l);
+	for(unsigned int i=0; i < _alivePathCount; ++i) {
+		if ((_paths[i])&&((_paths[i]->address().ss_family == inetAddressFamily)&&(_paths[i]->address().ipScope() == scope))) {
+			sendHELLO(tPtr,_paths[i]->localSocket(),_paths[i]->address(),now);
+			_paths[i]->sent(now);
+		}
+	}
+}
+
+void Peer::updateLatency(const unsigned int l)
+{
+	if ((l > 0)&&(l < 0xffff)) {
+		unsigned int lat = _latency;
+		if (lat < 0xffff) {
+			_latency = (l + l + lat) / 3;
+		} else {
+			_latency = l;
+		}
+	}
+}
+
+bool Peer::sendDirect(void *tPtr,const void *data,const unsigned int len,const int64_t now)
+{
+	if ((now - _lastPrioritizedPaths) > ZT_PEER_PRIORITIZE_PATHS_INTERVAL) {
+		_lastPrioritizedPaths = now;
+		_paths_l.lock();
+		_prioritizePaths(now);
+		if (_alivePathCount == 0) {
+			_paths_l.unlock();
+			return false;
+		}
+		const bool r = _paths[0]->send(RR,tPtr,data,len,now);
+		_paths_l.unlock();
+		return r;
+	} else {
+		_paths_l.rlock();
+		if (_alivePathCount == 0) {
+			_paths_l.runlock();
+			return false;
+		}
+		const bool r = _paths[0]->send(RR,tPtr,data,len,now);
+		_paths_l.runlock();
+		return r;
+	}
+}
+
+SharedPtr<Path> Peer::path(const int64_t now)
+{
+	if ((now - _lastPrioritizedPaths) > ZT_PEER_PRIORITIZE_PATHS_INTERVAL) {
+		_lastPrioritizedPaths = now;
+		RWMutex::Lock l(_paths_l);
+		_prioritizePaths(now);
+		if (_alivePathCount == 0)
+			return SharedPtr<Path>();
+		return _paths[0];
+	} else {
+		RWMutex::RLock l(_paths_l);
+		if (_alivePathCount == 0)
+			return SharedPtr<Path>();
+		return _paths[0];
 	}
-	*/
 }
 
 void Peer::getAllPaths(std::vector< SharedPtr<Path> > &paths)
 {
 	RWMutex::RLock l(_paths_l);
 	paths.clear();
-	paths.assign(_paths,_paths + _pathCount);
+	paths.assign(_paths,_paths + _alivePathCount);
+}
+
+void Peer::_prioritizePaths(const int64_t now)
+{
+	// assumes _paths_l is locked for writing
+	std::sort(_paths,_paths + ZT_MAX_PEER_NETWORK_PATHS,_PathPriorityComparisonOperator());
+
+	for(int i=0;i<ZT_MAX_PEER_NETWORK_PATHS;++i) {
+		if ((!_paths[i]) || (!_paths[i]->alive(now))) {
+			_alivePathCount = i;
+
+			for(;i<ZT_MAX_PEER_NETWORK_PATHS;++i)
+				_paths[i].zero();
+
+			return;
+		}
+	}
 }
 
 } // namespace ZeroTier

+ 21 - 41
node/Peer.hpp

@@ -40,7 +40,7 @@ class Peer
 	friend class SharedPtr<Peer>;
 
 private:
-	inline Peer() {}
+	ZT_ALWAYS_INLINE Peer() {}
 
 public:
 	ZT_ALWAYS_INLINE ~Peer() { Utils::burn(_key,sizeof(_key)); }
@@ -92,13 +92,14 @@ public:
 		uint64_t networkId);
 
 	/**
-	 * Check whether we have an active path to this peer via the given address
+	 * Check whether a path to this peer should be tried if received via e.g. RENDEZVOUS OR PUSH_DIRECT_PATHS
 	 *
 	 * @param now Current time
+	 * @param suggestingPeer Peer suggesting path (may be this peer)
 	 * @param addr Remote address
 	 * @return True if we have an active path to this destination
 	 */
-	bool hasActivePathTo(int64_t now,const InetAddress &addr) const;
+	bool shouldTryPath(void *tPtr,int64_t now,const SharedPtr<Peer> &suggestedBy,const InetAddress &addr) const;
 
 	/**
 	 * Send a HELLO to this peer at a specified physical address
@@ -113,16 +114,15 @@ public:
 	void sendHELLO(void *tPtr,int64_t localSocket,const InetAddress &atAddress,int64_t now);
 
 	/**
-	 * Send pings to active paths
-	 *
-	 * This also cleans up some internal data structures. It's called periodically from Node.
+	 * Send ping to this peer
 	 *
 	 * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
 	 * @param now Current time
 	 * @param v4SendCount Number of IPv4 packets sent (result parameter)
 	 * @param v6SendCount Number of IPv6 packets sent (result parameter)
+	 * @param pingAllAddressTypes If true, try to keep a link up for each address type/family
 	 */
-	void ping(void *tPtr,int64_t now,unsigned int &v4SendCount,unsigned int &v6SendCount);
+	void ping(void *tPtr,int64_t now,unsigned int &v4SendCount,unsigned int &v6SendCount,bool pingAllAddressTypes);
 
 	/**
 	 * Reset paths within a given IP scope and address family
@@ -161,15 +161,7 @@ public:
 	 *
 	 * @param l New latency measurment (in milliseconds)
 	 */
-	ZT_ALWAYS_INLINE void updateLatency(const unsigned int l)
-	{
-		if ((l > 0)&&(l < 0xffff)) {
-			unsigned int lat = _latency;
-			if (lat < 0xffff)
-				_latency = (l + lat) / 2;
-			else _latency = l;
-		}
-	}
+	void updateLatency(const unsigned int l);
 
 	/**
 	 * @return 256-bit secret symmetric encryption key
@@ -255,28 +247,12 @@ public:
 	 * @param now Current time
 	 * @return True if packet appears to have been sent, false if no path or send failed
 	 */
-	ZT_ALWAYS_INLINE bool sendDirect(void *tPtr,const void *data,const unsigned int len,const int64_t now)
-	{
-		_paths_l.rlock();
-		if (_pathCount == 0) {
-			_paths_l.unlock();
-			return false;
-		}
-		const bool r = _paths[0]->send(RR,tPtr,data,len,now);
-		_paths_l.unlock();
-		return r;
-	}
+	bool sendDirect(void *tPtr,const void *data,unsigned int len,const int64_t now);
 
 	/**
 	 * @return Current best path
 	 */
-	ZT_ALWAYS_INLINE SharedPtr<Path> path()
-	{
-		RWMutex::RLock l(_paths_l);
-		if (_pathCount == 0)
-			return SharedPtr<Path>();
-		return _paths[0];
-	}
+	SharedPtr<Path> path(int64_t now);
 
 	/**
 	 * Get all paths
@@ -286,20 +262,24 @@ public:
 	void getAllPaths(std::vector< SharedPtr<Path> > &paths);
 
 private:
+	void _prioritizePaths(int64_t now);
+
 	uint8_t _key[ZT_PEER_SECRET_KEY_LENGTH];
 
 	const RuntimeEnvironment *RR;
 
-	int64_t _lastReceive;
-	int64_t _lastWhoisRequestReceived;
-	int64_t _lastEchoRequestReceived;
-	int64_t _lastPushDirectPathsReceived;
-	int64_t _lastTriedStaticPath;
-	unsigned int _latency;
+	volatile int64_t _lastReceive;
+	volatile int64_t _lastWhoisRequestReceived;
+	volatile int64_t _lastEchoRequestReceived;
+	volatile int64_t _lastPushDirectPathsReceived;
+	volatile int64_t _lastPushDirectPathsSent;
+	volatile int64_t _lastTriedStaticPath;
+	volatile int64_t _lastPrioritizedPaths;
+	volatile unsigned int _latency;
 
 	AtomicCounter __refCount;
 
-	unsigned int _pathCount;
+	unsigned int _alivePathCount;
 	SharedPtr<Path> _paths[ZT_MAX_PEER_NETWORK_PATHS];
 	RWMutex _paths_l;
 

+ 5 - 5
node/Switch.cpp

@@ -59,7 +59,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre
 						fragment.incrementHops();
 						SharedPtr<Peer> relayTo = RR->topology->get(destination);
 						if ((!relayTo)||(!relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now))) {
-							relayTo = RR->topology->findRelayTo(now,destination);
+							relayTo = RR->topology->root();
 							if (relayTo)
 								relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now);
 						}
@@ -127,7 +127,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre
 						packet.incrementHops();
 						SharedPtr<Peer> relayTo = RR->topology->get(destination);
 						if ((!relayTo)||(!relayTo->sendDirect(tPtr,packet.data(),packet.size(),now))) {
-							relayTo = RR->topology->findRelayTo(now,destination);
+							relayTo = RR->topology->root();
 							if ((relayTo)&&(relayTo->address() != source))
 								relayTo->sendDirect(tPtr,packet.data(),packet.size(),now);
 						}
@@ -585,7 +585,7 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt)
 
 	const SharedPtr<Peer> peer(RR->topology->get(destination));
 	if (peer) {
-		viaPath = peer->path();
+		viaPath = peer->path(now);
 		if (!viaPath) {
 			if (peer->rateGateTryStaticPath(now)) {
 				InetAddress tryAddr;
@@ -599,9 +599,9 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt)
 				}
 			}
 
-			const SharedPtr<Peer> relay(RR->topology->findRelayTo(now,destination));
+			const SharedPtr<Peer> relay(RR->topology->root());
 			if (relay) {
-				viaPath = relay->path();
+				viaPath = relay->path(now);
 				if (!viaPath)
 					return false;
 			}

+ 1 - 16
node/Topology.hpp

@@ -111,7 +111,7 @@ public:
 
 		_paths_l.rlock();
 		SharedPtr<Path> p(_paths[k]);
-		_paths_l.unlock();
+		_paths_l.runlock();
 		if (p)
 			return p;
 
@@ -204,21 +204,6 @@ public:
 		}
 	}
 
-	/**
-	 * Get the best relay to a given address, which may or may not be a root
-	 *
-	 * @param now Current time
-	 * @param toAddr Destination address
-	 * @return Best current relay or NULL if none
-	 */
-	ZT_ALWAYS_INLINE SharedPtr<Peer> findRelayTo(const int64_t now,const Address &toAddr)
-	{
-		RWMutex::RLock l(_peers_l);
-		if (_rootPeers.empty())
-			return SharedPtr<Peer>();
-		return _rootPeers[0];
-	}
-
 	/**
 	 * @param allPeers vector to fill with all current peers
 	 */