From 0c951b6e56db47f2be4850343ae3908d0ea83099 Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@gmail.com>
Date: Wed, 10 Feb 2016 18:41:39 -0800
Subject: [PATCH] More tweaks to new symmetric NAT buster, and stop using old
 iterative method since this supersedes it.

---
 node/Peer.cpp          | 123 +++++++++++++++++++++++------------------
 node/Peer.hpp          |   6 +-
 node/SelfAwareness.cpp |  63 ++++++++++++++-------
 node/SelfAwareness.hpp |   7 +++
 node/Switch.cpp        |  49 ++++++++--------
 5 files changed, 148 insertions(+), 100 deletions(-)

diff --git a/node/Peer.cpp b/node/Peer.cpp
index c3157f036..b9d11292e 100644
--- a/node/Peer.cpp
+++ b/node/Peer.cpp
@@ -240,70 +240,83 @@ bool Peer::doPingAndKeepalive(uint64_t now,int inetAddressFamily)
 	return false;
 }
 
-void Peer::pushDirectPaths(Path *path,uint64_t now,bool force)
+bool Peer::pushDirectPaths(const InetAddress &localAddr,const InetAddress &toAddress,uint64_t now,bool force)
 {
 #ifdef ZT_ENABLE_CLUSTER
 	// Cluster mode disables normal PUSH_DIRECT_PATHS in favor of cluster-based peer redirection
 	if (RR->cluster)
-		return;
+		return false;
 #endif
 
-	if (((now - _lastDirectPathPushSent) >= ZT_DIRECT_PATH_PUSH_INTERVAL)||(force)) {
-		_lastDirectPathPushSent = now;
+	if (!force) {
+		if ((now - _lastDirectPathPushSent) < ZT_DIRECT_PATH_PUSH_INTERVAL)
+			return false;
+		else _lastDirectPathPushSent = now;
+	}
 
-		std::vector<InetAddress> dps(RR->node->directPaths());
-		if (dps.empty())
-			return;
-
-#ifdef ZT_TRACE
-		{
-			std::string ps;
-			for(std::vector<InetAddress>::const_iterator p(dps.begin());p!=dps.end();++p) {
-				if (ps.length() > 0)
-					ps.push_back(',');
-				ps.append(p->toString());
-			}
-			TRACE("pushing %u direct paths to %s: %s",(unsigned int)dps.size(),_id.address().toString().c_str(),ps.c_str());
-		}
-#endif
-
-		std::vector<InetAddress>::const_iterator p(dps.begin());
-		while (p != dps.end()) {
-			Packet outp(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS);
-			outp.addSize(2); // leave room for count
-
-			unsigned int count = 0;
-			while ((p != dps.end())&&((outp.size() + 24) < ZT_PROTO_MAX_PACKET_LENGTH)) {
-				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) {
-				outp.setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count);
-				outp.armor(_key,true);
-				path->send(RR,outp.data(),outp.size(),now);
-			}
+	std::vector<InetAddress> dps(RR->node->directPaths());
+	std::vector<InetAddress> sym(RR->sa->getSymmetricNatPredictions());
+	for(unsigned long i=0,added=0;i<sym.size();++i) {
+		InetAddress tmp(sym[(unsigned long)RR->node->prng() % sym.size()]);
+		if (std::find(dps.begin(),dps.end(),tmp) == dps.end()) {
+			dps.push_back(tmp);
+			if (++added >= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY)
+				break;
 		}
 	}
+	if (dps.empty())
+		return false;
+
+#ifdef ZT_TRACE
+	{
+		std::string ps;
+		for(std::vector<InetAddress>::const_iterator p(dps.begin());p!=dps.end();++p) {
+			if (ps.length() > 0)
+				ps.push_back(',');
+			ps.append(p->toString());
+		}
+		TRACE("pushing %u direct paths to %s: %s",(unsigned int)dps.size(),_id.address().toString().c_str(),ps.c_str());
+	}
+#endif
+
+	std::vector<InetAddress>::const_iterator p(dps.begin());
+	while (p != dps.end()) {
+		Packet outp(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS);
+		outp.addSize(2); // leave room for count
+
+		unsigned int count = 0;
+		while ((p != dps.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) {
+			outp.setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count);
+			outp.armor(_key,true);
+			RR->node->putPacket(localAddr,toAddress,outp.data(),outp.size(),0);
+		}
+	}
+
+	return true;
 }
 
 bool Peer::resetWithinScope(InetAddress::IpScope scope,uint64_t now)
diff --git a/node/Peer.hpp b/node/Peer.hpp
index 5796baf93..94c58ae89 100644
--- a/node/Peer.hpp
+++ b/node/Peer.hpp
@@ -170,11 +170,13 @@ public:
 	/**
 	 * Push direct paths back to self if we haven't done so in the configured timeout
 	 *
-	 * @param path Remote path to use to send the push
+	 * @param localAddr Local address
+	 * @param toAddress Remote address to send push to (usually from path)
 	 * @param now Current time
 	 * @param force If true, push regardless of rate limit
+	 * @return True if something was actually sent
 	 */
-	void pushDirectPaths(Path *path,uint64_t now,bool force);
+	bool pushDirectPaths(const InetAddress &localAddr,const InetAddress &toAddress,uint64_t now,bool force);
 
 	/**
 	 * @return All known direct paths to this peer (active or inactive)
diff --git a/node/SelfAwareness.cpp b/node/SelfAwareness.cpp
index db069046a..cf43a6441 100644
--- a/node/SelfAwareness.cpp
+++ b/node/SelfAwareness.cpp
@@ -20,6 +20,9 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <set>
+#include <vector>
+
 #include "Constants.hpp"
 #include "SelfAwareness.hpp"
 #include "RuntimeEnvironment.hpp"
@@ -68,30 +71,14 @@ void SelfAwareness::iam(const Address &reporter,const InetAddress &reporterPhysi
 {
 	const InetAddress::IpScope scope = myPhysicalAddress.ipScope();
 
-	// This would be weird, e.g. a public IP talking to 10.0.0.1, so just ignore it.
-	// If your network is this weird it's probably not reliable information.
-	if (scope != reporterPhysicalAddress.ipScope())
+	if ((scope != reporterPhysicalAddress.ipScope())||(scope == InetAddress::IP_SCOPE_NONE)||(scope == InetAddress::IP_SCOPE_LOOPBACK)||(scope == InetAddress::IP_SCOPE_MULTICAST))
 		return;
 
-	// Some scopes we ignore, and global scope IPs are only used for this
-	// mechanism if they come from someone we trust (e.g. a root).
-	switch(scope) {
-		case InetAddress::IP_SCOPE_NONE:
-		case InetAddress::IP_SCOPE_LOOPBACK:
-		case InetAddress::IP_SCOPE_MULTICAST:
-			return;
-		case InetAddress::IP_SCOPE_GLOBAL:
-			if (!trusted)
-				return;
-			break;
-		default:
-			break;
-	}
-
 	Mutex::Lock _l(_phy_m);
 	PhySurfaceEntry &entry = _phy[PhySurfaceKey(reporter,reporterPhysicalAddress,scope)];
 
-	if ( ((now - entry.ts) < ZT_SELFAWARENESS_ENTRY_TIMEOUT) && (!entry.mySurface.ipsEqual(myPhysicalAddress)) ) {
+	if ( (trusted) && ((now - entry.ts) < ZT_SELFAWARENESS_ENTRY_TIMEOUT) && (!entry.mySurface.ipsEqual(myPhysicalAddress)) ) {
+		// Changes to external surface reported by trusted peers causes path reset in this scope
 		entry.mySurface = myPhysicalAddress;
 		entry.ts = now;
 		TRACE("physical address %s for scope %u as seen from %s(%s) differs from %s, resetting paths in scope",myPhysicalAddress.toString().c_str(),(unsigned int)scope,reporter.toString().c_str(),reporterPhysicalAddress.toString().c_str(),entry.mySurface.toString().c_str());
@@ -123,6 +110,7 @@ void SelfAwareness::iam(const Address &reporter,const InetAddress &reporterPhysi
 			}
 		}
 	} else {
+		// Otherwise just update DB to use to determine external surface info
 		entry.mySurface = myPhysicalAddress;
 		entry.ts = now;
 	}
@@ -140,4 +128,41 @@ void SelfAwareness::clean(uint64_t now)
 	}
 }
 
+std::vector<InetAddress> SelfAwareness::getSymmetricNatPredictions()
+{
+	std::set<InetAddress> surfaces;
+
+	// Ideas based on: https://tools.ietf.org/html/draft-takeda-symmetric-nat-traversal-00
+
+	{
+		Mutex::Lock _l(_phy_m);
+		Hashtable< PhySurfaceKey,PhySurfaceEntry >::Iterator i(_phy);
+		PhySurfaceKey *k = (PhySurfaceKey *)0;
+		PhySurfaceEntry *e = (PhySurfaceEntry *)0;
+		while (i.next(k,e)) {
+			if ((e->mySurface.ss_family == AF_INET)&&(e->mySurface.ipScope() == InetAddress::IP_SCOPE_GLOBAL)) {
+				surfaces.insert(e->mySurface);
+			}
+		}
+	}
+
+	if (surfaces.size() > 1) {
+		// More than one global IPv4 surface means this is a symmetric NAT
+		std::vector<InetAddress> r;
+		for(std::set<InetAddress>::iterator i(surfaces.begin());i!=surfaces.end();++i) {
+			InetAddress nextPort(*i);
+			unsigned int p = nextPort.port();
+			if (p >= 65535)
+				p = 1025;
+			else ++p;
+			nextPort.setPort(p);
+			if (surfaces.count(nextPort) == 0)
+				r.push_back(nextPort);
+		}
+		return r;
+	}
+
+	return std::vector<InetAddress>();
+}
+
 } // namespace ZeroTier
diff --git a/node/SelfAwareness.hpp b/node/SelfAwareness.hpp
index 2534d986a..73950a53e 100644
--- a/node/SelfAwareness.hpp
+++ b/node/SelfAwareness.hpp
@@ -56,6 +56,13 @@ public:
 	 */
 	void clean(uint64_t now);
 
+	/**
+	 * If we appear to be behind a symmetric NAT, get predictions for possible external endpoints
+	 *
+	 * @return Symmetric NAT predictions or empty vector if none
+	 */
+	std::vector<InetAddress> getSymmetricNatPredictions();
+
 private:
 	struct PhySurfaceKey
 	{
diff --git a/node/Switch.cpp b/node/Switch.cpp
index 1ed1dfe61..4e20300df 100644
--- a/node/Switch.cpp
+++ b/node/Switch.cpp
@@ -478,31 +478,31 @@ unsigned long Switch::doTimerTasks(uint64_t now)
 		Mutex::Lock _l(_contactQueue_m);
 		for(std::list<ContactQueueEntry>::iterator qi(_contactQueue.begin());qi!=_contactQueue.end();) {
 			if (now >= qi->fireAtTime) {
-				if (qi->peer->hasActiveDirectPath(now)) {
-					// Cancel if connection has succeeded
+				if (!qi->peer->pushDirectPaths(qi->localAddr,qi->inaddr,now,true))
+					qi->peer->sendHELLO(qi->localAddr,qi->inaddr,now);
+				_contactQueue.erase(qi++);
+				continue;
+				/* Old symmetric NAT buster code, obsoleted by port prediction alg in SelfAwareness but left around for now in case we revert
+				if (qi->strategyIteration == 0) {
+					// First strategy: send packet directly to destination
+					qi->peer->sendHELLO(qi->localAddr,qi->inaddr,now);
+				} else if (qi->strategyIteration <= 3) {
+					// Strategies 1-3: try escalating ports for symmetric NATs that remap sequentially
+					InetAddress tmpaddr(qi->inaddr);
+					int p = (int)qi->inaddr.port() + qi->strategyIteration;
+					if (p > 65535)
+						p -= 64511;
+					tmpaddr.setPort((unsigned int)p);
+					qi->peer->sendHELLO(qi->localAddr,tmpaddr,now);
+				} else {
+					// All strategies tried, expire entry
 					_contactQueue.erase(qi++);
 					continue;
-				} else {
-					if (qi->strategyIteration == 0) {
-						// First strategy: send packet directly to destination
-						qi->peer->sendHELLO(qi->localAddr,qi->inaddr,now);
-					} else if (qi->strategyIteration <= 3) {
-						// Strategies 1-3: try escalating ports for symmetric NATs that remap sequentially
-						InetAddress tmpaddr(qi->inaddr);
-						int p = (int)qi->inaddr.port() + qi->strategyIteration;
-						if (p > 65535)
-							p -= 64511;
-						tmpaddr.setPort((unsigned int)p);
-						qi->peer->sendHELLO(qi->localAddr,tmpaddr,now);
-					} else {
-						// All strategies tried, expire entry
-						_contactQueue.erase(qi++);
-						continue;
-					}
-					++qi->strategyIteration;
-					qi->fireAtTime = now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY;
-					nextDelay = std::min(nextDelay,(unsigned long)ZT_NAT_T_TACTICAL_ESCALATION_DELAY);
 				}
+				++qi->strategyIteration;
+				qi->fireAtTime = now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY;
+				nextDelay = std::min(nextDelay,(unsigned long)ZT_NAT_T_TACTICAL_ESCALATION_DELAY);
+				*/
 			} else {
 				nextDelay = std::min(nextDelay,(unsigned long)(qi->fireAtTime - now));
 			}
@@ -813,12 +813,13 @@ bool Switch::_trySend(const Packet &packet,bool encrypt,uint64_t nwid)
 				relay = RR->topology->getBestRoot();
 
 			if (!(relay)||(!(viaPath = relay->getBestPath(now))))
-				return false; // no paths, no root servers?
+				return false; // no paths, no root servers?, no relays? :P~~~
 		}
 
 		if ((network)&&(relay)&&(network->isAllowed(peer))) {
 			// Push hints for direct connectivity to this peer if we are relaying
-			peer->pushDirectPaths(viaPath,now,false);
+			peer->pushDirectPaths(viaPath->localAddress(),viaPath->address(),now,false);
+			viaPath->sent(now);
 		}
 
 		Packet tmp(packet);