From b31071463cafda54afbf6f01d37aa7451b217b12 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 28 Jul 2015 11:28:47 -0700 Subject: [PATCH] Try another NAT traversal improvement. --- node/Constants.hpp | 3 +++ node/IncomingPacket.cpp | 2 +- node/SelfAwareness.cpp | 15 +++++++++++++++ node/SelfAwareness.hpp | 5 +++++ node/Switch.cpp | 17 +++++++++++++---- node/Switch.hpp | 4 ++-- 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index c192381cd..31a4c3137 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -296,6 +296,9 @@ /** * Delay between initial direct NAT-t packet and more aggressive techniques + * + * This may also be a delay before sending the first packet if we determine + * that we should wait for the remote to initiate rendezvous first. */ #define ZT_NAT_T_TACTICAL_ESCALATION_DELAY 1000 diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 76c47933d..b1fda8ef8 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -488,7 +488,7 @@ bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,const SharedPtr< InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port); TRACE("RENDEZVOUS from %s says %s might be at %s, starting NAT-t",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str()); peer->received(RR,_remoteAddress,hops(),packetId(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP); - RR->sw->contact(withPeer,atAddr); + RR->sw->rendezvous(withPeer,atAddr); } else { TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",peer->address().toString().c_str(),_remoteAddress.toString().c_str()); } diff --git a/node/SelfAwareness.cpp b/node/SelfAwareness.cpp index 000157886..716cf7f34 100644 --- a/node/SelfAwareness.cpp +++ b/node/SelfAwareness.cpp @@ -147,4 +147,19 @@ void SelfAwareness::clean(uint64_t now) } } +bool SelfAwareness::areGlobalIPv4PortsRandomized() const +{ + int port = 0; + Mutex::Lock _l(_phy_m); + for(std::map< PhySurfaceKey,PhySurfaceEntry >::const_iterator p(_phy.begin());p!=_phy.end();++p) { + if ((p->first.scope == InetAddress::IP_SCOPE_GLOBAL)&&(p->second.mySurface.ss_family == AF_INET)) { + const int tmp = (int)p->second.mySurface.port(); + if ((port)&&(tmp != port)) + return true; + else port = tmp; + } + } + return false; +} + } // namespace ZeroTier diff --git a/node/SelfAwareness.hpp b/node/SelfAwareness.hpp index 4eede5921..d3b79d186 100644 --- a/node/SelfAwareness.hpp +++ b/node/SelfAwareness.hpp @@ -66,6 +66,11 @@ public: */ void clean(uint64_t now); + /** + * @return True if our external (global scope) IPv4 ports appear to be randomized by a NAT device + */ + bool areGlobalIPv4PortsRandomized() const; + private: struct PhySurfaceKey { diff --git a/node/Switch.cpp b/node/Switch.cpp index 18935ce5e..a580078ef 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -43,6 +43,7 @@ #include "Topology.hpp" #include "Peer.hpp" #include "AntiRecursion.hpp" +#include "SelfAwareness.hpp" #include "Packet.hpp" namespace ZeroTier { @@ -385,15 +386,23 @@ bool Switch::unite(const Address &p1,const Address &p2,bool force) return true; } -void Switch::contact(const SharedPtr &peer,const InetAddress &atAddr) +void Switch::rendezvous(const SharedPtr &peer,const InetAddress &atAddr) { TRACE("sending NAT-t message to %s(%s)",peer->address().toString().c_str(),atAddr.toString().c_str()); const uint64_t now = RR->node->now(); - // Attempt to contact directly - peer->attemptToContactAt(RR,atAddr,now); + /* Attempt direct contact now unless we are IPv4 and our external ports + * appear to be randomized by a NAT device. In that case, we should let + * the other side send a message first. Why? If the other side is also + * randomized and symmetric, we are probably going to fail. But if the + * other side is "port restricted" but otherwise sane, us sending a + * packet first may actually close the remote's outgoing port to us! + * This assists with NAT-t in cases where one side is symmetric and the + * other is full cone but port restricted. */ + if ((atAddr.ss_family != AF_INET)||(!RR->sa->areGlobalIPv4PortsRandomized())) + peer->attemptToContactAt(RR,atAddr,now); - // If we have not punched through after this timeout, open refreshing can of whupass + // After 1s, try again and perhaps try more NAT-t strategies { Mutex::Lock _l(_contactQueue_m); _contactQueue.push_back(ContactQueueEntry(peer,now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY,atAddr)); diff --git a/node/Switch.hpp b/node/Switch.hpp index e7f1523a9..ac85606e2 100644 --- a/node/Switch.hpp +++ b/node/Switch.hpp @@ -136,12 +136,12 @@ public: bool unite(const Address &p1,const Address &p2,bool force); /** - * Send NAT traversal messages to peer at the given candidate address + * Attempt NAT traversal to peer at a given physical address * * @param peer Peer to contact * @param atAddr Address of peer */ - void contact(const SharedPtr &peer,const InetAddress &atAddr); + void rendezvous(const SharedPtr &peer,const InetAddress &atAddr); /** * Request WHOIS on a given address