diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp
index b6ec2d3fb..df224c198 100644
--- a/node/IncomingPacket.cpp
+++ b/node/IncomingPacket.cpp
@@ -510,9 +510,13 @@ bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,const SharedPtr<
 					peer->received(_localAddress,_remoteAddress,hops(),packetId(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP);
 
 					const 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());
-					if (RR->node->shouldUsePathForZeroTierTraffic(_localAddress,atAddr))
-						RR->sw->rendezvous(withPeer,_localAddress,atAddr);
+					TRACE("RENDEZVOUS from %s says %s might be at %s, attempting to contact",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str());
+					if (RR->node->shouldUsePathForZeroTierTraffic(_localAddress,atAddr)) {
+						const uint64_t now = RR->node->now();
+						peer->sendHELLO(_localAddress,atAddr,now,2); // send low-TTL packet to 'open' local NAT(s)
+						if (!peer->pushDirectPaths(_localAddress,atAddr,now,true))
+							peer->sendHELLO(_localAddress,atAddr,now);
+					}
 				} else {
 					TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",peer->address().toString().c_str(),_remoteAddress.toString().c_str());
 				}
@@ -746,7 +750,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 									outp.append((uint32_t)totalSize);
 									outp.append((uint32_t)chunkIndex);
 									outp.compress();
-									RR->sw->send(outp,true,0);
+									RR->sw->send(outp,true);
 									chunkIndex += chunkLen;
 								}
 							}
@@ -1139,7 +1143,7 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt
 				nextHop[h].appendTo(outp);
 				nextHopBestPathAddress[h].serialize(outp); // appends 0 if null InetAddress
 			}
-			RR->sw->send(outp,true,0);
+			RR->sw->send(outp,true);
 		}
 
 		// If there are next hops, forward the test along through the graph
@@ -1154,7 +1158,7 @@ bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,const SharedPt
 				if (RR->identity.address() != nextHop[h]) { // next hops that loop back to the current hop are not valid
 					outp.newInitializationVector();
 					outp.setDestination(nextHop[h]);
-					RR->sw->send(outp,true,originatorCredentialNetworkId);
+					RR->sw->send(outp,true);
 				}
 			}
 		}
diff --git a/node/Membership.cpp b/node/Membership.cpp
index e12bce3ca..dbba7f0d3 100644
--- a/node/Membership.cpp
+++ b/node/Membership.cpp
@@ -69,7 +69,7 @@ bool Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint
 			outp.append((uint8_t)0x00);
 			outp.append(capsAndTags.data(),capsAndTags.size());
 			outp.compress();
-			RR->sw->send(outp,true,0);
+			RR->sw->send(outp,true);
 			_lastPushedCom = now;
 			return true;
 		}
diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp
index 9e583e345..aeee0a859 100644
--- a/node/Multicaster.cpp
+++ b/node/Multicaster.cpp
@@ -240,7 +240,7 @@ void Multicaster::send(
 					mg.mac().appendTo(outp);
 					outp.append((uint32_t)mg.adi());
 					outp.append((uint32_t)gatherLimit);
-					RR->sw->send(outp,true,0);
+					RR->sw->send(outp,true);
 				}
 				gatherLimit = 0;
 			}
diff --git a/node/Network.cpp b/node/Network.cpp
index b84756aa0..e098c1fd3 100644
--- a/node/Network.cpp
+++ b/node/Network.cpp
@@ -127,7 +127,7 @@ static int _doZtFilter(
 				outp.append((uint16_t)etherType);
 				outp.append(frameData,frameLen);
 				outp.compress();
-				RR->sw->send(outp,true,nwid);
+				RR->sw->send(outp,true);
 
 				if (rt == ZT_NETWORK_RULE_ACTION_REDIRECT) {
 					return -1; // match, drop packet (we redirected it)
@@ -678,7 +678,7 @@ void Network::requestConfiguration()
 	outp.append((const void *)rmd.data(),rmdSize);
 	outp.append((_config) ? (uint64_t)_config.revision : (uint64_t)0);
 	outp.compress();
-	RR->sw->send(outp,true,0);
+	RR->sw->send(outp,true);
 
 	// Expect replies with this in-re packet ID
 	_inboundConfigPacketId = outp.packetId();
@@ -894,7 +894,7 @@ void Network::_announceMulticastGroupsTo(const SharedPtr<Peer> &peer,const std::
 	for(std::vector<MulticastGroup>::const_iterator mg(allMulticastGroups.begin());mg!=allMulticastGroups.end();++mg) {
 		if ((outp.size() + 24) >= ZT_PROTO_MAX_PACKET_LENGTH) {
 			outp.compress();
-			RR->sw->send(outp,true,0);
+			RR->sw->send(outp,true);
 			outp.reset(peer->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE);
 		}
 
@@ -906,7 +906,7 @@ void Network::_announceMulticastGroupsTo(const SharedPtr<Peer> &peer,const std::
 
 	if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) {
 		outp.compress();
-		RR->sw->send(outp,true,0);
+		RR->sw->send(outp,true);
 	}
 }
 
diff --git a/node/Node.cpp b/node/Node.cpp
index f04559dba..4da79347e 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -237,7 +237,7 @@ public:
 				// way whatsoever. This will e.g. find network preferred relays that lack
 				// stable endpoints by using root servers.
 				Packet outp(p->address(),RR->identity.address(),Packet::VERB_NOP);
-				RR->sw->send(outp,true,0);
+				RR->sw->send(outp,true);
 			}
 
 			lastReceiveFromUpstream = std::max(p->lastReceive(),lastReceiveFromUpstream);
@@ -520,7 +520,7 @@ ZT_ResultCode Node::circuitTestBegin(ZT_CircuitTest *test,void (*reportCallback)
 			for(unsigned int a=0;a<test->hops[0].breadth;++a) {
 				outp.newInitializationVector();
 				outp.setDestination(Address(test->hops[0].addresses[a]));
-				RR->sw->send(outp,true,0);
+				RR->sw->send(outp,true);
 			}
 		} catch ( ... ) {
 			return ZT_RESULT_FATAL_ERROR_INTERNAL; // probably indicates FIFO too big for packet
diff --git a/node/OutboundMulticast.cpp b/node/OutboundMulticast.cpp
index a5856164a..c99529279 100644
--- a/node/OutboundMulticast.cpp
+++ b/node/OutboundMulticast.cpp
@@ -90,7 +90,7 @@ void OutboundMulticast::sendOnly(const RuntimeEnvironment *RR,const Address &toA
 		//TRACE(">>MC %.16llx -> %s",(unsigned long long)this,toAddr.toString().c_str());
 		_packet.newInitializationVector();
 		_packet.setDestination(toAddr);
-		RR->sw->send(_packet,true,_nwid);
+		RR->sw->send(_packet,true);
 	}
 }
 
diff --git a/node/Peer.cpp b/node/Peer.cpp
index 89dce570a..77e1d0b57 100644
--- a/node/Peer.cpp
+++ b/node/Peer.cpp
@@ -241,7 +241,7 @@ bool Peer::doPingAndKeepalive(uint64_t now,int inetAddressFamily)
 	return false;
 }
 
-bool Peer::pushDirectPaths(const InetAddress &localAddr,const InetAddress &toAddress,uint64_t now,bool force,bool includePrivatePaths)
+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
@@ -258,10 +258,8 @@ bool Peer::pushDirectPaths(const InetAddress &localAddr,const InetAddress &toAdd
 	std::vector<InetAddress> pathsToPush;
 
 	std::vector<InetAddress> dps(RR->node->directPaths());
-	for(std::vector<InetAddress>::const_iterator i(dps.begin());i!=dps.end();++i) {
-		if ((includePrivatePaths)||(i->ipScope() == InetAddress::IP_SCOPE_GLOBAL))
-			pathsToPush.push_back(*i);
-	}
+	for(std::vector<InetAddress>::const_iterator i(dps.begin());i!=dps.end();++i)
+		pathsToPush.push_back(*i);
 
 	std::vector<InetAddress> sym(RR->sa->getSymmetricNatPredictions());
 	for(unsigned long i=0,added=0;i<sym.size();++i) {
diff --git a/node/Peer.hpp b/node/Peer.hpp
index d8c44ebe4..200c5ac4c 100644
--- a/node/Peer.hpp
+++ b/node/Peer.hpp
@@ -199,10 +199,9 @@ public:
 	 * @param toAddress Remote address to send push to (usually from path)
 	 * @param now Current time
 	 * @param force If true, push regardless of rate limit
-	 * @param includePrivatePaths If true, include local interface address paths (should only be done to peers with a trust relationship)
 	 * @return True if something was actually sent
 	 */
-	bool pushDirectPaths(const InetAddress &localAddr,const InetAddress &toAddress,uint64_t now,bool force,bool includePrivatePaths);
+	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 8bed0c511..a4fae3d56 100644
--- a/node/SelfAwareness.cpp
+++ b/node/SelfAwareness.cpp
@@ -106,7 +106,7 @@ void SelfAwareness::iam(const Address &reporter,const InetAddress &receivedOnLoc
 		for(std::vector< SharedPtr<Peer> >::const_iterator p(rset.peersReset.begin());p!=rset.peersReset.end();++p) {
 			if ((*p)->activelyTransferringFrames(now)) {
 				Packet outp((*p)->address(),RR->identity.address(),Packet::VERB_NOP);
-				RR->sw->send(outp,true,0);
+				RR->sw->send(outp,true);
 			}
 		}
 	} else {
diff --git a/node/Switch.cpp b/node/Switch.cpp
index 167c7928f..37daff272 100644
--- a/node/Switch.cpp
+++ b/node/Switch.cpp
@@ -476,14 +476,14 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
 			outp.append((uint16_t)etherType);
 			outp.append(data,len);
 			outp.compress();
-			send(outp,true,network->id());
+			send(outp,true);
 		} else {
 			Packet outp(toZT,RR->identity.address(),Packet::VERB_FRAME);
 			outp.append(network->id());
 			outp.append((uint16_t)etherType);
 			outp.append(data,len);
 			outp.compress();
-			send(outp,true,network->id());
+			send(outp,true);
 		}
 
 		//TRACE("%.16llx: UNICAST: %s -> %s etherType==%s(%.4x) vlanId==%u len==%u fromBridged==%d includeCom==%d",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType),etherType,vlanId,len,(int)fromBridged,(int)includeCom);
@@ -536,23 +536,21 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
 			outp.append((uint16_t)etherType);
 			outp.append(data,len);
 			outp.compress();
-			send(outp,true,network->id());
+			send(outp,true);
 		}
 	}
 }
 
-void Switch::send(const Packet &packet,bool encrypt,uint64_t nwid)
+void Switch::send(const Packet &packet,bool encrypt)
 {
 	if (packet.destination() == RR->identity.address()) {
 		TRACE("BUG: caught attempt to send() to self, ignored");
 		return;
 	}
 
-	//TRACE(">> %s to %s (%u bytes, encrypt==%d, nwid==%.16llx)",Packet::verbString(packet.verb()),packet.destination().toString().c_str(),packet.size(),(int)encrypt,nwid);
-
-	if (!_trySend(packet,encrypt,nwid)) {
+	if (!_trySend(packet,encrypt)) {
 		Mutex::Lock _l(_txQueue_m);
-		_txQueue.push_back(TXQueueEntry(packet.destination(),RR->node->now(),packet,encrypt,nwid));
+		_txQueue.push_back(TXQueueEntry(packet.destination(),RR->node->now(),packet,encrypt));
 	}
 }
 
@@ -625,17 +623,6 @@ bool Switch::unite(const Address &p1,const Address &p2)
 	return true;
 }
 
-void Switch::rendezvous(const SharedPtr<Peer> &peer,const InetAddress &localAddr,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();
-	peer->sendHELLO(localAddr,atAddr,now,2); // first attempt: send low-TTL packet to 'open' local NAT
-	{
-		Mutex::Lock _l(_contactQueue_m);
-		_contactQueue.push_back(ContactQueueEntry(peer,now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY,localAddr,atAddr));
-	}
-}
-
 void Switch::requestWhois(const Address &addr)
 {
 	bool inserted = false;
@@ -676,7 +663,7 @@ void Switch::doAnythingWaitingForPeer(const SharedPtr<Peer> &peer)
 		Mutex::Lock _l(_txQueue_m);
 		for(std::list< TXQueueEntry >::iterator txi(_txQueue.begin());txi!=_txQueue.end();) {
 			if (txi->dest == peer->address()) {
-				if (_trySend(txi->packet,txi->encrypt,txi->nwid))
+				if (_trySend(txi->packet,txi->encrypt))
 					_txQueue.erase(txi++);
 				else ++txi;
 			} else ++txi;
@@ -688,42 +675,6 @@ unsigned long Switch::doTimerTasks(uint64_t now)
 {
 	unsigned long nextDelay = 0xffffffff; // ceiling delay, caller will cap to minimum
 
-	{	// Iterate through NAT traversal strategies for entries in contact queue
-		Mutex::Lock _l(_contactQueue_m);
-		for(std::list<ContactQueueEntry>::iterator qi(_contactQueue.begin());qi!=_contactQueue.end();) {
-			if (now >= qi->fireAtTime) {
-				if (!qi->peer->pushDirectPaths(qi->localAddr,qi->inaddr,now,true,false))
-					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;
-				}
-				++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));
-			}
-			++qi; // if qi was erased, loop will have continued before here
-		}
-	}
-
 	{	// Retry outstanding WHOIS requests
 		Mutex::Lock _l(_outstandingWhoisRequests_m);
 		Hashtable< Address,WhoisRequest >::Iterator i(_outstandingWhoisRequests);
@@ -751,7 +702,7 @@ unsigned long Switch::doTimerTasks(uint64_t now)
 	{	// Time out TX queue packets that never got WHOIS lookups or other info.
 		Mutex::Lock _l(_txQueue_m);
 		for(std::list< TXQueueEntry >::iterator txi(_txQueue.begin());txi!=_txQueue.end();) {
-			if (_trySend(txi->packet,txi->encrypt,txi->nwid))
+			if (_trySend(txi->packet,txi->encrypt))
 				_txQueue.erase(txi++);
 			else if ((now - txi->creationTime) > ZT_TRANSMIT_QUEUE_TIMEOUT) {
 				TRACE("TX %s -> %s timed out",txi->packet.source().toString().c_str(),txi->packet.destination().toString().c_str());
@@ -787,20 +738,13 @@ Address Switch::_sendWhoisRequest(const Address &addr,const Address *peersAlread
 	return Address();
 }
 
-bool Switch::_trySend(const Packet &packet,bool encrypt,uint64_t nwid)
+bool Switch::_trySend(const Packet &packet,bool encrypt)
 {
 	SharedPtr<Peer> peer(RR->topology->getPeer(packet.destination()));
 
 	if (peer) {
 		const uint64_t now = RR->node->now();
 
-		SharedPtr<Network> network;
-		if (nwid) {
-			network = RR->node->network(nwid);
-			if ((!network)||(!network->hasConfig()))
-				return false; // we probably just left this network, let its packets die
-		}
-
 		Path *viaPath = peer->getBestPath(now);
 		SharedPtr<Peer> relay;
 
@@ -811,7 +755,7 @@ bool Switch::_trySend(const Packet &packet,bool encrypt,uint64_t nwid)
 		}
 
 		if (relay) {
-			peer->pushDirectPaths(viaPath->localAddress(),viaPath->address(),now,false,( (network)&&(network->isAllowed(peer)) ));
+			peer->pushDirectPaths(viaPath->localAddress(),viaPath->address(),now,false);
 			viaPath->sent(now);
 		}
 
diff --git a/node/Switch.hpp b/node/Switch.hpp
index ce4f00a16..7c903ef9e 100644
--- a/node/Switch.hpp
+++ b/node/Switch.hpp
@@ -92,15 +92,10 @@ public:
 	 * Needless to say, the packet's source must be this node. Otherwise it
 	 * won't be encrypted right. (This is not used for relaying.)
 	 *
-	 * The network ID should only be specified for frames and other actual
-	 * network traffic. Other traffic such as controller requests and regular
-	 * protocol messages should specify zero.
-	 *
 	 * @param packet Packet to send
 	 * @param encrypt Encrypt packet payload? (always true except for HELLO)
-	 * @param nwid Related network ID or 0 if message is not in-network traffic
 	 */
-	void send(const Packet &packet,bool encrypt,uint64_t nwid);
+	void send(const Packet &packet,bool encrypt);
 
 	/**
 	 * Send RENDEZVOUS to two peers to permit them to directly connect
@@ -113,15 +108,6 @@ public:
 	 */
 	bool unite(const Address &p1,const Address &p2);
 
-	/**
-	 * Attempt NAT traversal to peer at a given physical address
-	 *
-	 * @param peer Peer to contact
-	 * @param localAddr Local interface address
-	 * @param atAddr Address of peer
-	 */
-	void rendezvous(const SharedPtr<Peer> &peer,const InetAddress &localAddr,const InetAddress &atAddr);
-
 	/**
 	 * Request WHOIS on a given address
 	 *
@@ -151,7 +137,7 @@ public:
 
 private:
 	Address _sendWhoisRequest(const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted);
-	bool _trySend(const Packet &packet,bool encrypt,uint64_t nwid);
+	bool _trySend(const Packet &packet,bool encrypt);
 
 	const RuntimeEnvironment *const RR;
 	uint64_t _lastBeaconResponse;
@@ -205,16 +191,14 @@ private:
 	struct TXQueueEntry
 	{
 		TXQueueEntry() {}
-		TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc,uint64_t nw) :
+		TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc) :
 			dest(d),
 			creationTime(ct),
-			nwid(nw),
 			packet(p),
 			encrypt(enc) {}
 
 		Address dest;
 		uint64_t creationTime;
-		uint64_t nwid;
 		Packet packet; // unencrypted/unMAC'd packet -- this is done at send time
 		bool encrypt;
 	};
@@ -241,26 +225,6 @@ private:
 	};
 	Hashtable< _LastUniteKey,uint64_t > _lastUniteAttempt; // key is always sorted in ascending order, for set-like behavior
 	Mutex _lastUniteAttempt_m;
-
-	// Active attempts to contact remote peers, including state of multi-phase NAT traversal
-	struct ContactQueueEntry
-	{
-		ContactQueueEntry() {}
-		ContactQueueEntry(const SharedPtr<Peer> &p,uint64_t ft,const InetAddress &laddr,const InetAddress &a) :
-			peer(p),
-			fireAtTime(ft),
-			inaddr(a),
-			localAddr(laddr),
-			strategyIteration(0) {}
-
-		SharedPtr<Peer> peer;
-		uint64_t fireAtTime;
-		InetAddress inaddr;
-		InetAddress localAddr;
-		unsigned int strategyIteration;
-	};
-	std::list<ContactQueueEntry> _contactQueue;
-	Mutex _contactQueue_m;
 };
 
 } // namespace ZeroTier