Browse Source

Implement WHOIS

Adam Ierymenko 6 years ago
parent
commit
b27a38e55e
1 changed files with 193 additions and 66 deletions
  1. 193 66
      root/root.cpp

+ 193 - 66
root/root.cpp

@@ -19,6 +19,7 @@
 #include <string.h>
 #include <string.h>
 #include <fcntl.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <signal.h>
+#include <errno.h>
 #include <sys/stat.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/socket.h>
@@ -60,6 +61,12 @@
 using namespace ZeroTier;
 using namespace ZeroTier;
 using json = nlohmann::json;
 using json = nlohmann::json;
 
 
+#ifdef MSG_DONTWAIT
+#define SENDTO_FLAGS MSG_DONTWAIT
+#else
+#define SENDTO_FLAGS 0
+#endif
+
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 
 
@@ -88,20 +95,25 @@ struct RendezvousKey
 
 
 struct RootPeer
 struct RootPeer
 {
 {
+	ZT_ALWAYS_INLINE RootPeer() : lastReceive(0),lastSync(0),lastEcho(0),lastHello(0) {}
+	ZT_ALWAYS_INLINE ~RootPeer() { Utils::burn(key,sizeof(key)); }
+
 	Identity id;
 	Identity id;
 	uint8_t key[32];
 	uint8_t key[32];
 	InetAddress ip4,ip6;
 	InetAddress ip4,ip6;
 	int64_t lastReceive;
 	int64_t lastReceive;
 	int64_t lastSync;
 	int64_t lastSync;
+	int64_t lastEcho;
+	int64_t lastHello;
+	std::mutex lock;
 
 
 	AtomicCounter __refCount;
 	AtomicCounter __refCount;
-
-	ZT_ALWAYS_INLINE ~RootPeer() { Utils::burn(key,sizeof(key)); }
 };
 };
 
 
 static Identity self;
 static Identity self;
 static std::atomic_bool run;
 static std::atomic_bool run;
 static json config;
 static json config;
+static std::string statsRoot;
 
 
 static std::unordered_map< uint64_t,std::unordered_map< MulticastGroup,std::unordered_map< Address,int64_t,AddressHasher >,MulticastGroupHasher > > multicastSubscriptions;
 static std::unordered_map< uint64_t,std::unordered_map< MulticastGroup,std::unordered_map< Address,int64_t,AddressHasher >,MulticastGroupHasher > > multicastSubscriptions;
 static std::unordered_map< Identity,SharedPtr<RootPeer>,IdentityHasher > peersByIdentity;
 static std::unordered_map< Identity,SharedPtr<RootPeer>,IdentityHasher > peersByIdentity;
@@ -126,8 +138,6 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 	const Address dest(pkt.destination());
 	const Address dest(pkt.destination());
 	const int64_t now = OSUtils::now();
 	const int64_t now = OSUtils::now();
 
 
-	// See if this is destined for us and isn't a fragment / fragmented. (No packets
-	// understood by the root are fragments/fragmented.)
 	if ((!fragment)&&(!pkt.fragmented())&&(dest == self.address())) {
 	if ((!fragment)&&(!pkt.fragmented())&&(dest == self.address())) {
 		SharedPtr<RootPeer> peer;
 		SharedPtr<RootPeer> peer;
 
 
@@ -161,7 +171,6 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 							}
 							}
 							peer->id = id;
 							peer->id = id;
 							peer->lastReceive = now;
 							peer->lastReceive = now;
-							peer->lastSync = 0;
 							peersByIdentity.emplace(id,peer);
 							peersByIdentity.emplace(id,peer);
 							peersByVirtAddr[id.address()].emplace(peer);
 							peersByVirtAddr[id.address()].emplace(peer);
 						} else {
 						} else {
@@ -199,6 +208,8 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 		// If we found the peer, update IP and/or time and handle certain key packet types that the
 		// If we found the peer, update IP and/or time and handle certain key packet types that the
 		// root must concern itself with.
 		// root must concern itself with.
 		if (peer) {
 		if (peer) {
+			std::lock_guard<std::mutex> pl(peer->lock);
+
 			InetAddress *const peerIp = ip->isV4() ? &(peer->ip4) : &(peer->ip6);
 			InetAddress *const peerIp = ip->isV4() ? &(peer->ip4) : &(peer->ip6);
 			if (*peerIp != ip) {
 			if (*peerIp != ip) {
 				std::lock_guard<std::mutex> pbp_l(peersByPhysAddr_l);
 				std::lock_guard<std::mutex> pbp_l(peersByPhysAddr_l);
@@ -220,25 +231,71 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 			switch(pkt.verb()) {
 			switch(pkt.verb()) {
 				case Packet::VERB_HELLO:
 				case Packet::VERB_HELLO:
 					try {
 					try {
-						const uint64_t origId = pkt.packetId();
-						const uint64_t ts = pkt.template at<uint64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP);
-						pkt.reset(source,self.address(),Packet::VERB_OK);
-						pkt.append((uint8_t)Packet::VERB_HELLO);
-						pkt.append(origId);
-						pkt.append(ts);
-						pkt.append((uint8_t)ZT_PROTO_VERSION);
-						pkt.append((uint8_t)0);
-						pkt.append((uint8_t)0);
-						pkt.append((uint16_t)0);
-						ip->serialize(pkt);
-						pkt.armor(peer->key,true);
-						sendto(ip->isV4() ? v4s : v6s,pkt.data(),pkt.size(),0,(const struct sockaddr *)ip,(socklen_t)((ip->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)));
-						//printf("%s <- OK(HELLO)" ZT_EOL_S,ip->toString(ipstr));
+						if ((now - peer->lastHello) > 1000) {
+							peer->lastHello = now;
+							const uint64_t origId = pkt.packetId();
+							const uint64_t ts = pkt.template at<uint64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP);
+							pkt.reset(source,self.address(),Packet::VERB_OK);
+							pkt.append((uint8_t)Packet::VERB_HELLO);
+							pkt.append(origId);
+							pkt.append(ts);
+							pkt.append((uint8_t)ZT_PROTO_VERSION);
+							pkt.append((uint8_t)0);
+							pkt.append((uint8_t)0);
+							pkt.append((uint16_t)0);
+							ip->serialize(pkt);
+							pkt.armor(peer->key,true);
+							sendto(ip->isV4() ? v4s : v6s,pkt.data(),pkt.size(),SENDTO_FLAGS,(const struct sockaddr *)ip,(socklen_t)((ip->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)));
+						}
 					} catch ( ... ) {
 					} catch ( ... ) {
 						printf("* unexpected exception handling HELLO from %s" ZT_EOL_S,ip->toString(ipstr));
 						printf("* unexpected exception handling HELLO from %s" ZT_EOL_S,ip->toString(ipstr));
 					}
 					}
 					break;
 					break;
 
 
+				case Packet::VERB_ECHO:
+					try {
+						if ((now - peer->lastEcho) > 1000) {
+							peer->lastEcho = now;
+							Packet outp(source,self.address(),Packet::VERB_OK);
+							outp.append((uint8_t)Packet::VERB_ECHO);
+							outp.append(pkt.packetId());
+							outp.append(((const uint8_t *)pkt.data()) + ZT_PACKET_IDX_PAYLOAD,pkt.size() - ZT_PACKET_IDX_PAYLOAD);
+							outp.compress();
+							outp.armor(peer->key,true);
+							sendto(ip->isV4() ? v4s : v6s,outp.data(),outp.size(),SENDTO_FLAGS,(const struct sockaddr *)ip,(socklen_t)((ip->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)));
+						}
+					} catch ( ... ) {
+						printf("* unexpected exception handling ECHO from %s" ZT_EOL_S,ip->toString(ipstr));
+					}
+
+				case Packet::VERB_WHOIS:
+					try {
+						std::vector< SharedPtr<RootPeer> > results;
+						{
+							std::lock_guard<std::mutex> l(peersByVirtAddr_l);
+							for(unsigned int ptr=ZT_PACKET_IDX_PAYLOAD;(ptr+ZT_ADDRESS_LENGTH)<=pkt.size();ptr+=ZT_ADDRESS_LENGTH) {
+								auto peers = peersByVirtAddr.find(Address(pkt.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH));
+								if (peers != peersByVirtAddr.end()) {
+									for(auto p=peers->second.begin();p!=peers->second.end();++p)
+										results.push_back(*p);
+								}
+							}
+						}
+
+						if (!results.empty()) {
+							const uint64_t origId = pkt.packetId();
+							pkt.reset(source,self.address(),Packet::VERB_OK);
+							pkt.append((uint8_t)Packet::VERB_WHOIS);
+							pkt.append(origId);
+							for(auto p=results.begin();p!=results.end();++p)
+								(*p)->id.serialize(pkt,false);
+							pkt.armor(peer->key,true);
+							sendto(ip->isV4() ? v4s : v6s,pkt.data(),pkt.size(),SENDTO_FLAGS,(const struct sockaddr *)ip,(socklen_t)((ip->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)));
+						}
+					} catch ( ... ) {
+						printf("* unexpected exception handling ECHO from %s" ZT_EOL_S,ip->toString(ipstr));
+					}
+
 				case Packet::VERB_MULTICAST_LIKE:
 				case Packet::VERB_MULTICAST_LIKE:
 					try {
 					try {
 						std::lock_guard<std::mutex> l(multicastSubscriptions_l);
 						std::lock_guard<std::mutex> l(multicastSubscriptions_l);
@@ -289,7 +346,7 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 									if (l > 0) {
 									if (l > 0) {
 										pkt.setAt<uint16_t>(countAt,(uint16_t)l);
 										pkt.setAt<uint16_t>(countAt,(uint16_t)l);
 										pkt.armor(peer->key,true);
 										pkt.armor(peer->key,true);
-										sendto(ip->isV4() ? v4s : v6s,pkt.data(),pkt.size(),0,(const struct sockaddr *)ip,(socklen_t)(ip->isV4() ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)));
+										sendto(ip->isV4() ? v4s : v6s,pkt.data(),pkt.size(),SENDTO_FLAGS,(const struct sockaddr *)ip,(socklen_t)(ip->isV4() ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)));
 										//printf("%s %s gathered %u subscribers to %s/%.8lx on network %.16llx" ZT_EOL_S,ip->toString(ipstr),source.toString(astr),l,mg.mac().toString(tmpstr),(unsigned long)mg.adi(),(unsigned long long)nwid);
 										//printf("%s %s gathered %u subscribers to %s/%.8lx on network %.16llx" ZT_EOL_S,ip->toString(ipstr),source.toString(astr),l,mg.mac().toString(tmpstr),(unsigned long)mg.adi(),(unsigned long long)nwid);
 									}
 									}
 								}
 								}
@@ -312,7 +369,7 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 	// sending a RENDEZVOUS message.
 	// sending a RENDEZVOUS message.
 
 
 	bool introduce = false;
 	bool introduce = false;
-	{
+	if (!fragment) {
 		RendezvousKey rk(source,dest);
 		RendezvousKey rk(source,dest);
 		std::lock_guard<std::mutex> l(lastRendezvous_l);
 		std::lock_guard<std::mutex> l(lastRendezvous_l);
 		int64_t &lr = lastRendezvous[rk];
 		int64_t &lr = lastRendezvous[rk];
@@ -328,10 +385,10 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 		auto peers = peersByVirtAddr.find(dest);
 		auto peers = peersByVirtAddr.find(dest);
 		if (peers != peersByVirtAddr.end()) {
 		if (peers != peersByVirtAddr.end()) {
 			for(auto p=peers->second.begin();p!=peers->second.end();++p) {
 			for(auto p=peers->second.begin();p!=peers->second.end();++p) {
-				if ((*p)->ip6) {
-					toAddrs.push_back(std::pair< InetAddress *,SharedPtr<RootPeer> >(&((*p)->ip6),*p));
-				} else if ((*p)->ip4) {
+				if ((*p)->ip4) {
 					toAddrs.push_back(std::pair< InetAddress *,SharedPtr<RootPeer> >(&((*p)->ip4),*p));
 					toAddrs.push_back(std::pair< InetAddress *,SharedPtr<RootPeer> >(&((*p)->ip4),*p));
+				} else if ((*p)->ip6) {
+					toAddrs.push_back(std::pair< InetAddress *,SharedPtr<RootPeer> >(&((*p)->ip6),*p));
 				}
 				}
 			}
 			}
 		}
 		}
@@ -358,7 +415,7 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 						outp.append((uint8_t)16);
 						outp.append((uint8_t)16);
 						outp.append((const uint8_t *)b->second->ip6.rawIpData(),16);
 						outp.append((const uint8_t *)b->second->ip6.rawIpData(),16);
 						outp.armor((*a)->key,true);
 						outp.armor((*a)->key,true);
-						sendto(v6s,pkt.data(),pkt.size(),0,(const struct sockaddr *)&((*a)->ip6),(socklen_t)sizeof(struct sockaddr_in6));
+						sendto(v6s,pkt.data(),pkt.size(),SENDTO_FLAGS,(const struct sockaddr *)&((*a)->ip6),(socklen_t)sizeof(struct sockaddr_in6));
 
 
 						// Introduce destination to source (V6)
 						// Introduce destination to source (V6)
 						outp.reset(dest,self.address(),Packet::VERB_RENDEZVOUS);
 						outp.reset(dest,self.address(),Packet::VERB_RENDEZVOUS);
@@ -368,8 +425,9 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 						outp.append((uint8_t)16);
 						outp.append((uint8_t)16);
 						outp.append((const uint8_t *)ip->rawIpData(),16);
 						outp.append((const uint8_t *)ip->rawIpData(),16);
 						outp.armor(b->second->key,true);
 						outp.armor(b->second->key,true);
-						sendto(v6s,pkt.data(),pkt.size(),0,(const struct sockaddr *)&(b->second->ip6),(socklen_t)sizeof(struct sockaddr_in6));
-					} else if (((*a)->ip4)&&(b->second->ip4)) {
+						sendto(v6s,pkt.data(),pkt.size(),SENDTO_FLAGS,(const struct sockaddr *)&(b->second->ip6),(socklen_t)sizeof(struct sockaddr_in6));
+					}
+					if (((*a)->ip4)&&(b->second->ip4)) {
 						//printf("* introducing %s(%s) to %s(%s)" ZT_EOL_S,ip->toString(ipstr),source.toString(astr),b->second->ip4.toString(ipstr2),dest.toString(astr2));
 						//printf("* introducing %s(%s) to %s(%s)" ZT_EOL_S,ip->toString(ipstr),source.toString(astr),b->second->ip4.toString(ipstr2),dest.toString(astr2));
 
 
 						// Introduce source to destination (V4)
 						// Introduce source to destination (V4)
@@ -380,7 +438,7 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 						outp.append((uint8_t)4);
 						outp.append((uint8_t)4);
 						outp.append((const uint8_t *)b->second->ip4.rawIpData(),4);
 						outp.append((const uint8_t *)b->second->ip4.rawIpData(),4);
 						outp.armor((*a)->key,true);
 						outp.armor((*a)->key,true);
-						sendto(v4s,pkt.data(),pkt.size(),0,(const struct sockaddr *)&((*a)->ip4),(socklen_t)sizeof(struct sockaddr_in));
+						sendto(v4s,pkt.data(),pkt.size(),SENDTO_FLAGS,(const struct sockaddr *)&((*a)->ip4),(socklen_t)sizeof(struct sockaddr_in));
 
 
 						// Introduce destination to source (V4)
 						// Introduce destination to source (V4)
 						outp.reset(dest,self.address(),Packet::VERB_RENDEZVOUS);
 						outp.reset(dest,self.address(),Packet::VERB_RENDEZVOUS);
@@ -390,7 +448,7 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 						outp.append((uint8_t)4);
 						outp.append((uint8_t)4);
 						outp.append((const uint8_t *)ip->rawIpData(),4);
 						outp.append((const uint8_t *)ip->rawIpData(),4);
 						outp.armor(b->second->key,true);
 						outp.armor(b->second->key,true);
-						sendto(v4s,pkt.data(),pkt.size(),0,(const struct sockaddr *)&(b->second->ip4),(socklen_t)sizeof(struct sockaddr_in));
+						sendto(v4s,pkt.data(),pkt.size(),SENDTO_FLAGS,(const struct sockaddr *)&(b->second->ip4),(socklen_t)sizeof(struct sockaddr_in));
 					}
 					}
 				}
 				}
 			}
 			}
@@ -410,8 +468,10 @@ static void handlePacket(const int v4s,const int v6s,const InetAddress *const ip
 	}
 	}
 
 
 	for(auto i=toAddrs.begin();i!=toAddrs.end();++i) {
 	for(auto i=toAddrs.begin();i!=toAddrs.end();++i) {
-		//printf("%s -> %s for %s -> %s" ZT_EOL_S,ip->toString(ipstr),i->first->toString(ipstr2),source.toString(astr),dest.toString(astr2));
-		sendto(i->first->isV4() ? v4s : v6s,pkt.data(),pkt.size(),0,(const struct sockaddr *)i->first,(socklen_t)(i->first->isV4() ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)));
+		//printf("%s -> %s for %s -> %s (%u bytes)" ZT_EOL_S,ip->toString(ipstr),i->first->toString(ipstr2),source.toString(astr),dest.toString(astr2),pkt.size());
+		if (sendto(i->first->isV4() ? v4s : v6s,pkt.data(),pkt.size(),SENDTO_FLAGS,(const struct sockaddr *)i->first,(socklen_t)(i->first->isV4() ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) <= 0) {
+			printf("* write error forwarding packet to %s: %s" ZT_EOL_S,i->first->toString(ipstr),strerror(errno));
+		}
 	}
 	}
 }
 }
 
 
@@ -448,8 +508,11 @@ static int bindSocket(struct sockaddr *bindAddr)
 		f = 0; setsockopt(s,IPPROTO_IPV6,IPV6_DONTFRAG,&f,sizeof(f));
 		f = 0; setsockopt(s,IPPROTO_IPV6,IPV6_DONTFRAG,&f,sizeof(f));
 #endif
 #endif
 	}
 	}
-	f = 1; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f));
+#ifdef SO_REUSEPORT
 	f = 1; setsockopt(s,SOL_SOCKET,SO_REUSEPORT,(void *)&f,sizeof(f));
 	f = 1; setsockopt(s,SOL_SOCKET,SO_REUSEPORT,(void *)&f,sizeof(f));
+#else
+	f = 1; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f));
+#endif
 	f = 1; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(void *)&f,sizeof(f));
 	f = 1; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(void *)&f,sizeof(f));
 #ifdef IP_DONTFRAG
 #ifdef IP_DONTFRAG
 	f = 0; setsockopt(s,IPPROTO_IP,IP_DONTFRAG,&f,sizeof(f));
 	f = 0; setsockopt(s,IPPROTO_IP,IP_DONTFRAG,&f,sizeof(f));
@@ -534,6 +597,15 @@ int main(int argc,char **argv)
 	} catch ( ... ) {
 	} catch ( ... ) {
 		port = ZT_DEFAULT_PORT;
 		port = ZT_DEFAULT_PORT;
 	}
 	}
+	try {
+		statsRoot = config["statsRoot"];
+		while ((statsRoot.length() > 0)&&(statsRoot[statsRoot.length()-1] == ZT_PATH_SEPARATOR))
+			statsRoot = statsRoot.substr(0,statsRoot.length()-1);
+		if (statsRoot.length() > 0)
+			OSUtils::mkdir(statsRoot);
+	} catch ( ... ) {
+		statsRoot = "";
+	}
 
 
 	unsigned int ncores = std::thread::hardware_concurrency();
 	unsigned int ncores = std::thread::hardware_concurrency();
 	if (ncores == 0) ncores = 1;
 	if (ncores == 0) ncores = 1;
@@ -567,7 +639,7 @@ int main(int argc,char **argv)
 		sockets.push_back(s6);
 		sockets.push_back(s6);
 		sockets.push_back(s4);
 		sockets.push_back(s4);
 
 
-		threads.push_back(std::thread([s4,s6]() {
+		threads.push_back(std::thread([s6,s4]() {
 			struct sockaddr_in6 in6;
 			struct sockaddr_in6 in6;
 			Packet pkt;
 			Packet pkt;
 			memset(&in6,0,sizeof(in6));
 			memset(&in6,0,sizeof(in6));
@@ -590,7 +662,7 @@ int main(int argc,char **argv)
 			}
 			}
 		}));
 		}));
 
 
-		threads.push_back(std::thread([s4,s6]() {
+		threads.push_back(std::thread([s6,s4]() {
 			struct sockaddr_in in4;
 			struct sockaddr_in in4;
 			Packet pkt;
 			Packet pkt;
 			memset(&in4,0,sizeof(in4));
 			memset(&in4,0,sizeof(in4));
@@ -616,12 +688,13 @@ int main(int argc,char **argv)
 
 
 	int64_t lastCleanedMulticastSubscriptions = 0;
 	int64_t lastCleanedMulticastSubscriptions = 0;
 	int64_t lastCleanedPeers = 0;
 	int64_t lastCleanedPeers = 0;
+	int64_t lastWroteStats = 0;
 	while (run) {
 	while (run) {
-		peersByIdentity_l.lock();
-		peersByPhysAddr_l.lock();
-		printf("*** have %lu peers at %lu physical endpoints" ZT_EOL_S,(unsigned long)peersByIdentity.size(),(unsigned long)peersByPhysAddr.size());
-		peersByPhysAddr_l.unlock();
-		peersByIdentity_l.unlock();
+		//peersByIdentity_l.lock();
+		//peersByPhysAddr_l.lock();
+		//printf("*** have %lu peers at %lu physical endpoints" ZT_EOL_S,(unsigned long)peersByIdentity.size(),(unsigned long)peersByPhysAddr.size());
+		//peersByPhysAddr_l.unlock();
+		//peersByIdentity_l.unlock();
 		sleep(1);
 		sleep(1);
 
 
 		const int64_t now = OSUtils::now();
 		const int64_t now = OSUtils::now();
@@ -650,38 +723,92 @@ int main(int argc,char **argv)
 		if ((now - lastCleanedPeers) > 120000) {
 		if ((now - lastCleanedPeers) > 120000) {
 			lastCleanedPeers = now;
 			lastCleanedPeers = now;
 
 
-			std::lock_guard<std::mutex> pbi_l(peersByIdentity_l);
-			for(auto p=peersByIdentity.begin();p!=peersByIdentity.end();) {
-				if ((now - p->second->lastReceive) > ZT_PEER_ACTIVITY_TIMEOUT) {
-					std::lock_guard<std::mutex> pbv_l(peersByVirtAddr_l);
-					std::lock_guard<std::mutex> pbp_l(peersByPhysAddr_l);
-
-					auto pbv = peersByVirtAddr.find(p->second->id.address());
-					if (pbv != peersByVirtAddr.end()) {
-						pbv->second.erase(p->second);
-						if (pbv->second.empty())
-							peersByVirtAddr.erase(pbv);
-					}
+			{
+				std::lock_guard<std::mutex> pbi_l(peersByIdentity_l);
+				for(auto p=peersByIdentity.begin();p!=peersByIdentity.end();) {
+					if ((now - p->second->lastReceive) > ZT_PEER_ACTIVITY_TIMEOUT) {
+						std::lock_guard<std::mutex> pbv_l(peersByVirtAddr_l);
+						std::lock_guard<std::mutex> pbp_l(peersByPhysAddr_l);
+
+						auto pbv = peersByVirtAddr.find(p->second->id.address());
+						if (pbv != peersByVirtAddr.end()) {
+							pbv->second.erase(p->second);
+							if (pbv->second.empty())
+								peersByVirtAddr.erase(pbv);
+						}
 
 
-					if (p->second->ip4) {
-						auto pbp = peersByPhysAddr.find(p->second->ip4);
-						if (pbp != peersByPhysAddr.end()) {
-							pbp->second.erase(p->second);
-							if (pbp->second.empty())
-								peersByPhysAddr.erase(pbp);
+						if (p->second->ip4) {
+							auto pbp = peersByPhysAddr.find(p->second->ip4);
+							if (pbp != peersByPhysAddr.end()) {
+								pbp->second.erase(p->second);
+								if (pbp->second.empty())
+									peersByPhysAddr.erase(pbp);
+							}
 						}
 						}
-					}
-					if (p->second->ip6) {
-						auto pbp = peersByPhysAddr.find(p->second->ip6);
-						if (pbp != peersByPhysAddr.end()) {
-							pbp->second.erase(p->second);
-							if (pbp->second.empty())
-								peersByPhysAddr.erase(pbp);
+						if (p->second->ip6) {
+							auto pbp = peersByPhysAddr.find(p->second->ip6);
+							if (pbp != peersByPhysAddr.end()) {
+								pbp->second.erase(p->second);
+								if (pbp->second.empty())
+									peersByPhysAddr.erase(pbp);
+							}
 						}
 						}
+
+						peersByIdentity.erase(p++);
+					} else ++p;
+				}
+			}
+
+			{
+				std::lock_guard<std::mutex> l(lastRendezvous_l);
+				for(auto lr=lastRendezvous.begin();lr!=lastRendezvous.end();) {
+					if ((now - lr->second) > ZT_PEER_ACTIVITY_TIMEOUT)
+						lastRendezvous.erase(lr++);
+					else ++lr;
+				}
+			}
+		}
+
+		if (((now - lastWroteStats) > 15000)&&(statsRoot.length() > 0)) {
+			lastWroteStats = now;
+
+			std::string peersFilePath(statsRoot);
+			peersFilePath.append("/peers.tmp");
+			FILE *pf = fopen(peersFilePath.c_str(),"wb");
+
+			if (pf) {
+				std::vector< SharedPtr<RootPeer> > sp;
+				{
+					std::lock_guard<std::mutex> pbi_l(peersByIdentity_l);
+					sp.reserve(peersByIdentity.size());
+					for(auto p=peersByIdentity.begin();p!=peersByIdentity.end();++p) {
+						sp.push_back(p->second);
 					}
 					}
+				}
+				std::sort(sp.begin(),sp.end(),[](const SharedPtr<RootPeer> &a,const SharedPtr<RootPeer> &b) { return (a->id < b->id); });
+
+				char ip4[128],ip6[128];
+				for(auto p=sp.begin();p!=sp.end();++p) {
+					if ((*p)->ip4) {
+						(*p)->ip4.toString(ip4);
+					} else {
+						ip4[0] = '-';
+						ip4[1] = 0;
+					}
+					if ((*p)->ip6) {
+						(*p)->ip6.toString(ip6);
+					} else {
+						ip6[0] = '-';
+						ip6[1] = 0;
+					}
+					fprintf(pf,"%.10llx %21s %45s %5.4f" ZT_EOL_S,(unsigned long long)(*p)->id.address().toInt(),ip4,ip6,fabs((double)(now - (*p)->lastReceive) / 1000.0));
+				}
+				fclose(pf);
 
 
-					peersByIdentity.erase(p++);
-				} else ++p;
+				std::string peersFilePath2(statsRoot);
+				peersFilePath2.append("/peers");
+				OSUtils::rm(peersFilePath2);
+				OSUtils::rename(peersFilePath.c_str(),peersFilePath2.c_str());
 			}
 			}
 		}
 		}
 	}
 	}