From 5e71e07f5940056f1fb5124dcdfeb71a313b9854 Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@gmail.com>
Date: Mon, 21 Oct 2013 14:12:00 -0400
Subject: [PATCH] Add persistent identity caching for use on supernodes.
 Activate by just making an iddb.d directory in the ZeroTier home folder. Also
 clean up some obsolete cruft from makefiles.

---
 Makefile.linux         |  3 +--
 Makefile.mac           |  2 +-
 node/Constants.hpp     | 18 ------------------
 node/Node.cpp          |  2 +-
 node/NodeConfig.cpp    |  2 ++
 node/PacketDecoder.cpp | 29 ++++++++++++++++++++++++-----
 node/Topology.cpp      | 32 +++++++++++++++++++++++++++++++-
 node/Topology.hpp      | 23 ++++++++++++++++++++++-
 8 files changed, 82 insertions(+), 29 deletions(-)

diff --git a/Makefile.linux b/Makefile.linux
index 7bacec735..8035a8811 100644
--- a/Makefile.linux
+++ b/Makefile.linux
@@ -2,8 +2,7 @@ CC=gcc
 CXX=g++
 
 INCLUDES=
-ARCH=$(shell uname -m)
-DEFS=-DZT_ARCH="$(ARCH)" -DZT_OSNAME="linux"
+DEFS=
 LIBS=
 
 # Uncomment for a release optimized build
diff --git a/Makefile.mac b/Makefile.mac
index e195f0886..335357984 100644
--- a/Makefile.mac
+++ b/Makefile.mac
@@ -2,7 +2,7 @@ CC=gcc
 CXX=g++
 
 INCLUDES=
-DEFS=-DZT_ARCH="x86_combined" -DZT_OSNAME="mac" -DZT_TRACE
+DEFS=
 LIBS=-lm
 
 # Uncomment for a release optimized universal binary build
diff --git a/node/Constants.hpp b/node/Constants.hpp
index 23dd5e480..4fe7fd348 100644
--- a/node/Constants.hpp
+++ b/node/Constants.hpp
@@ -99,24 +99,6 @@
 #ifndef __BYTE_ORDER
 error_no_byte_order_defined;
 #endif
-#ifndef ZT_OSNAME
-#ifdef __WINDOWS__
-#define ZT_OSNAME "windows"
-#else
-no ZT_OSNAME defined;
-#endif
-#endif
-#ifndef ZT_ARCH
-#ifdef __WINDOWS__
-#ifdef _WIN64
-#define ZT_ARCH "x64"
-#else
-#define ZT_ARCH "x86"
-#endif
-#else
-error_no_ZT_ARCH_defined;
-#endif
-#endif
 
 /**
  * Length of a ZeroTier address in bytes
diff --git a/node/Node.cpp b/node/Node.cpp
index 8b2815e20..593e63bc3 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -408,7 +408,7 @@ Node::ReasonForTermination Node::run()
 		_r->mc = new Multicaster();
 		_r->sw = new Switch(_r);
 		_r->demarc = new Demarc(_r);
-		_r->topology = new Topology(_r);
+		_r->topology = new Topology(_r,Utils::fileExists((_r->homePath + ZT_PATH_SEPARATOR_S + "iddb.d").c_str()));
 		_r->sysEnv = new SysEnv(_r);
 		try {
 			_r->nc = new NodeConfig(_r,configAuthToken.c_str(),impl->controlPort);
diff --git a/node/NodeConfig.cpp b/node/NodeConfig.cpp
index 85aca6d2b..027f65ce3 100644
--- a/node/NodeConfig.cpp
+++ b/node/NodeConfig.cpp
@@ -353,7 +353,9 @@ bool NodeConfig::decodeControlMessagePacket(const void *key,const void *data,uns
 void NodeConfig::_CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len)
 {
 	NodeConfig *nc = (NodeConfig *)arg;
+#ifdef ZT_TRACE
 	const RuntimeEnvironment *_r = nc->_r;
+#endif
 
 	try {
 		unsigned long convId = 0;
diff --git a/node/PacketDecoder.cpp b/node/PacketDecoder.cpp
index 2efa7cf9c..f65e41675 100644
--- a/node/PacketDecoder.cpp
+++ b/node/PacketDecoder.cpp
@@ -189,16 +189,16 @@ bool PacketDecoder::_doHELLO(const RuntimeEnvironment *_r)
 			return true;
 		}
 
+		// Do we already have this peer?
 		SharedPtr<Peer> peer(_r->topology->getPeer(id.address()));
 		if (peer) {
+			// Check to make sure this isn't a colliding identity (different key,
+			// but same address). The odds are spectacularly low but it could happen.
+			// Could also be a sign of someone doing something nasty.
 			if (peer->identity() != id) {
-				// Sorry, someone beat you to that address. What are the odds?
-				// Well actually they're around two in 2^40. You should play
-				// the lottery.
 				unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
 				if (_r->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
 					TRACE("rejected HELLO from %s(%s): address already claimed",source().toString().c_str(),_remoteAddress.toString().c_str());
-
 					Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR);
 					outp.append((unsigned char)Packet::VERB_HELLO);
 					outp.append(packetId());
@@ -209,7 +209,26 @@ bool PacketDecoder::_doHELLO(const RuntimeEnvironment *_r)
 				return true;
 			} // else continue and send OK since we already know thee...
 		} else {
-			// Learn a new peer
+			// If we don't have a peer record on file, check the identity cache (if
+			// we have one) to see if we have a cached identity. Then check that for
+			// collision before adding a new peer.
+			Identity alreadyHaveCachedId(_r->topology->getIdentity(id.address()));
+			if ((alreadyHaveCachedId)&&(id != alreadyHaveCachedId)) {
+				unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
+				if (_r->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
+					TRACE("rejected HELLO from %s(%s): address already claimed",source().toString().c_str(),_remoteAddress.toString().c_str());
+					Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR);
+					outp.append((unsigned char)Packet::VERB_HELLO);
+					outp.append(packetId());
+					outp.append((unsigned char)Packet::ERROR_IDENTITY_COLLISION);
+					outp.armor(key,true);
+					_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
+				}
+				return true;
+			}
+
+			// Learn a new peer if it's new. This also adds it to the identity
+			// cache if that's enabled.
 			peer = _r->topology->addPeer(SharedPtr<Peer>(new Peer(_r->identity,id)));
 		}
 
diff --git a/node/Topology.cpp b/node/Topology.cpp
index 18432f25d..6efde33ed 100644
--- a/node/Topology.cpp
+++ b/node/Topology.cpp
@@ -35,10 +35,12 @@
 
 namespace ZeroTier {
 
-Topology::Topology(const RuntimeEnvironment *renv) :
+Topology::Topology(const RuntimeEnvironment *renv,bool enablePermanentIdCaching) :
 	_r(renv),
 	_amSupernode(false)
 {
+	if (enablePermanentIdCaching)
+		_idCacheBase = (_r->homePath + ZT_PATH_SEPARATOR_S + "iddb.d");
 	_loadPeers();
 }
 
@@ -83,6 +85,7 @@ SharedPtr<Peer> Topology::addPeer(const SharedPtr<Peer> &peer)
 	Mutex::Lock _l(_activePeers_m);
 	SharedPtr<Peer> p(_activePeers.insert(std::pair< Address,SharedPtr<Peer> >(peer->address(),peer)).first->second);
 	p->setLastUsed(now);
+	saveIdentity(p->identity());
 	return p;
 }
 
@@ -102,6 +105,32 @@ SharedPtr<Peer> Topology::getPeer(const Address &zta)
 	return SharedPtr<Peer>();
 }
 
+Identity Topology::getIdentity(const Address &zta)
+{
+	SharedPtr<Peer> p(getPeer(zta));
+	if (p)
+		return p->identity();
+	if (_idCacheBase.length()) {
+		std::string idcPath(_idCacheBase + ZT_PATH_SEPARATOR_S + zta.toString());
+		std::string ids;
+		if (Utils::readFile(idcPath.c_str(),ids)) {
+			try {
+				return Identity(ids);
+			} catch ( ... ) {} // ignore invalid IDs
+		}
+	}
+	return Identity();
+}
+
+void Topology::saveIdentity(const Identity &id)
+{
+	if ((id)&&(_idCacheBase.length())) {
+		std::string idcPath(_idCacheBase + ZT_PATH_SEPARATOR_S + id.address().toString());
+		if (!Utils::fileExists(idcPath.c_str()))
+			Utils::writeFile(idcPath.c_str(),id.toString(false));
+	}
+}
+
 SharedPtr<Peer> Topology::getBestSupernode(const Address *avoid,unsigned int avoidCount,bool strictAvoid) const
 {
 	SharedPtr<Peer> bestSupernode;
@@ -244,6 +273,7 @@ void Topology::_loadPeers()
 					SharedPtr<Peer> p(new Peer());
 					ptr += p->deserialize(buf,ptr);
 					_activePeers[p->address()] = p;
+					saveIdentity(p->identity());
 				}
 				if (ptr) {
 					memmove(buf.data(),buf.data() + ptr,buf.size() - ptr);
diff --git a/node/Topology.hpp b/node/Topology.hpp
index efb03dbe1..09dec86e0 100644
--- a/node/Topology.hpp
+++ b/node/Topology.hpp
@@ -53,7 +53,7 @@ class RuntimeEnvironment;
 class Topology
 {
 public:
-	Topology(const RuntimeEnvironment *renv);
+	Topology(const RuntimeEnvironment *renv,bool enablePermanentIdCaching);
 	~Topology();
 
 	/**
@@ -82,6 +82,25 @@ public:
 	 */
 	SharedPtr<Peer> getPeer(const Address &zta);
 
+	/**
+	 * Get an identity if cached or available in a peer record
+	 *
+	 * @param zta ZeroTier address
+	 * @return Identity or NULL-identity if not found
+	 */
+	Identity getIdentity(const Address &zta);
+
+	/**
+	 * Save identity in permanent store, or do nothing if disabled
+	 *
+	 * This is called automatically by addPeer(), so it should not need to be
+	 * called manually anywhere else. The private part of the identity, if
+	 * present, is NOT cached by this.
+	 *
+	 * @param id Identity to save
+	 */
+	void saveIdentity(const Identity &id);
+
 	/**
 	 * @return Current network supernodes
 	 */
@@ -274,6 +293,8 @@ private:
 	void _dumpPeers();
 	void _loadPeers();
 
+	std::string _idCacheBase; // empty if identity caching disabled
+
 	std::map< Address,SharedPtr<Peer> > _activePeers;
 	Mutex _activePeers_m;