diff --git a/AUTHORS.md b/AUTHORS.md
index 4900aab54..b286e7ca3 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -39,3 +39,6 @@
    digital signature algorithm, and Poly1305 MAC algorithm, all by
    Daniel J. Bernstein (public domain)<br>
    http://cr.yp.to/
+
+ * MiniUPNPC by Thomas Bernard [BSD]
+   http://miniupnp.free.fr
diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp
index 700b3561a..50be5b344 100644
--- a/controller/SqliteNetworkController.cpp
+++ b/controller/SqliteNetworkController.cpp
@@ -64,6 +64,10 @@
 // API version reported via JSON control plane
 #define ZT_NETCONF_CONTROLLER_API_VERSION 1
 
+// Drop requests for a given peer and network ID that occur more frequently
+// than this (ms).
+#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000
+
 namespace ZeroTier {
 
 namespace {
@@ -142,6 +146,7 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 		// Prepare statement will fail if Config table doesn't exist, which means our DB
 		// needs to be initialized.
 		if (sqlite3_exec(_db,ZT_NETCONF_SCHEMA_SQL"INSERT INTO Config (k,v) VALUES ('schemaVersion',"ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR");",0,0,0) != SQLITE_OK) {
+			//printf("%s\n",sqlite3_errmsg(_db));
 			sqlite3_close(_db);
 			throw std::runtime_error("SqliteNetworkController cannot initialize database and/or insert schemaVersion into Config table");
 		}
@@ -199,16 +204,21 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 			||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK)
 
 			/* Gateway */
-			||(sqlite3_prepare_v2(_db,"SELECT ip,ipVersion,metric FROM Gateway WHERE networkId = ? ORDER BY metric ASC",-1,&_sGetGateways,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"SELECT \"ip\",ipVersion,metric FROM Gateway WHERE networkId = ? ORDER BY metric ASC",-1,&_sGetGateways,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"DELETE FROM Gateway WHERE networkId = ?",-1,&_sDeleteGateways,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"INSERT INTO Gateway (networkId,ip,ipVersion,metric) VALUES (?,?,?,?)",-1,&_sCreateGateway,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"INSERT INTO Gateway (networkId,\"ip\",ipVersion,metric) VALUES (?,?,?,?)",-1,&_sCreateGateway,(const char **)0) != SQLITE_OK)
+
+			/* Log */
+			||(sqlite3_prepare_v2(_db,"INSERT INTO \"Log\" (networkId,nodeId,\"ts\",\"authorized\",\"version\",fromAddr) VALUES (?,?,?,?,?,?)",-1,&_sPutLog,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"SELECT \"ts\",\"authorized\",\"version\",fromAddr FROM \"Log\" WHERE networkId = ? AND nodeId = ? AND \"ts\" >= ? ORDER BY \"ts\" ASC",-1,&_sGetMemberLog,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"SELECT \"ts\",\"authorized\",\"version\",fromAddr FROM \"Log\" WHERE networkId = ? AND nodeId = ? ORDER BY \"ts\" DESC LIMIT 10",-1,&_sGetRecentMemberLog,(const char **)0) != SQLITE_OK)
 
 			/* Config */
 			||(sqlite3_prepare_v2(_db,"SELECT \"v\" FROM \"Config\" WHERE \"k\" = ?",-1,&_sGetConfig,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO \"Config\" (\"k\",\"v\") VALUES (?,?)",-1,&_sSetConfig,(const char **)0) != SQLITE_OK)
 
 		 ) {
-		//printf("!!! %s\n",sqlite3_errmsg(_db));
+		//printf("%s\n",sqlite3_errmsg(_db));
 		sqlite3_close(_db);
 		throw std::runtime_error("SqliteNetworkController unable to initialize one or more prepared statements");
 	}
@@ -283,12 +293,21 @@ SqliteNetworkController::~SqliteNetworkController()
 		sqlite3_finalize(_sIncrementMemberRevisionCounter);
 		sqlite3_finalize(_sGetConfig);
 		sqlite3_finalize(_sSetConfig);
+		sqlite3_finalize(_sPutLog);
+		sqlite3_finalize(_sGetMemberLog);
+		sqlite3_finalize(_sGetRecentMemberLog);
 		sqlite3_close(_db);
 	}
 }
 
 NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary &metaData,uint64_t haveRevision,Dictionary &netconf)
 {
+	// Decode some stuff from metaData
+	const unsigned int clientMajorVersion = (unsigned int)metaData.getHexUInt(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0);
+	const unsigned int clientMinorVersion = (unsigned int)metaData.getHexUInt(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0);
+	const unsigned int clientRevision = (unsigned int)metaData.getHexUInt(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0);
+	const bool clientIs104 = (Utils::compareVersion(clientMajorVersion,clientMinorVersion,clientRevision,1,0,4) >= 0);
+
 	Mutex::Lock _l(_lock);
 
 	// Note: we can't reuse prepared statements that return const char * pointers without
@@ -303,6 +322,15 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 		return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
 	}
 
+	// Check rate limit
+
+	{
+		uint64_t &lrt = _lastRequestTime[std::pair<Address,uint64_t>(identity.address(),nwid)];
+		uint64_t lrt2 = lrt;
+		if (((lrt = OSUtils::now()) - lrt2) <= ZT_NETCONF_MIN_REQUEST_PERIOD)
+			return NetworkController::NETCONF_QUERY_IGNORE;
+	}
+
 	NetworkRecord network;
 	memset(&network,0,sizeof(network));
 	Utils::snprintf(network.id,sizeof(network.id),"%.16llx",(unsigned long long)nwid);
@@ -387,18 +415,35 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 		sqlite3_step(_sIncrementMemberRevisionCounter);
 	}
 
+	// Add log entry
+	{
+		char ver[16];
+		std::string fa;
+		if (fromAddr) {
+			fa = fromAddr.toString();
+			if (fa.length() > 64)
+				fa = fa.substr(0,64);
+		}
+		sqlite3_reset(_sPutLog);
+		sqlite3_bind_text(_sPutLog,1,network.id,16,SQLITE_STATIC);
+		sqlite3_bind_text(_sPutLog,2,member.nodeId,10,SQLITE_STATIC);
+		sqlite3_bind_int64(_sPutLog,3,(long long)OSUtils::now());
+		sqlite3_bind_int(_sPutLog,4,member.authorized ? 1 : 0);
+		if ((clientMajorVersion > 0)||(clientMinorVersion > 0)||(clientRevision > 0)) {
+			Utils::snprintf(ver,sizeof(ver),"%u.%u.%u",clientMajorVersion,clientMinorVersion,clientRevision);
+			sqlite3_bind_text(_sPutLog,5,ver,-1,SQLITE_STATIC);
+		} else sqlite3_bind_null(_sPutLog,5);
+		if (fa.length() > 0)
+			sqlite3_bind_text(_sPutLog,6,fa.c_str(),-1,SQLITE_STATIC);
+		else sqlite3_bind_null(_sPutLog,6);
+		sqlite3_step(_sPutLog);
+	}
+
 	// Check member authorization
 
 	if (!member.authorized)
 		return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
 
-	// If netconf is unchanged from client reported revision, just tell client they're up to date
-
-	// Temporarily disabled -- old version didn't do this, and we'll go ahead and
-	// test more thoroughly before enabling this optimization.
-	//if ((haveRevision > 0)&&(haveRevision == network.revision))
-	//	return NetworkController::NETCONF_QUERY_OK_BUT_NOT_NEWER;
-
 	// Create and sign netconf
 
 	netconf.clear();
@@ -555,7 +600,8 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 				if ((ipNetmaskBits <= 0)||(ipNetmaskBits > 32))
 					continue;
 
-				switch((IpAssignmentType)sqlite3_column_int(_sGetIpAssignmentsForNode,0)) {
+				const IpAssignmentType ipt = (IpAssignmentType)sqlite3_column_int(_sGetIpAssignmentsForNode,0);
+				switch(ipt) {
 					case ZT_IP_ASSIGNMENT_TYPE_ADDRESS:
 						haveStaticIpAssignment = true;
 						break;
@@ -566,13 +612,15 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 						continue;
 				}
 
-				// We send both routes and IP assignments -- client knows which is
-				// which by whether address ends in all zeroes after netmask.
-				char tmp[32];
-				Utils::snprintf(tmp,sizeof(tmp),"%d.%d.%d.%d/%d",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],ipNetmaskBits);
-				if (v4s.length())
-					v4s.push_back(',');
-				v4s.append(tmp);
+				// 1.0.4 or newer clients support network routes in addition to IPs.
+				// Older clients only support IP address / netmask entries.
+				if ((clientIs104)||(ipt == ZT_IP_ASSIGNMENT_TYPE_ADDRESS)) {
+					char tmp[32];
+					Utils::snprintf(tmp,sizeof(tmp),"%d.%d.%d.%d/%d",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],ipNetmaskBits);
+					if (v4s.length())
+						v4s.push_back(',');
+					v4s.append(tmp);
+				}
 			}
 
 			if (!haveStaticIpAssignment) {
@@ -648,7 +696,7 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 		// TODO: IPv6 auto-assign once it's supported in UI
 
 		if (network.isPrivate) {
-			CertificateOfMembership com(network.revision,ZT1_CERTIFICATE_OF_MEMBERSHIP_REVISION_MAX_DELTA,nwid,identity.address());
+			CertificateOfMembership com(OSUtils::now(),ZT_NETWORK_AUTOCONF_DELAY + (ZT_NETWORK_AUTOCONF_DELAY / 2),nwid,identity.address());
 			if (com.sign(signingId)) // basically can't fail unless our identity is invalid
 				netconf[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = com.toString();
 			else {
@@ -716,6 +764,8 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 					char addrs[24];
 					Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
 
+					int64_t addToNetworkRevision = 0;
+
 					int64_t memberRowId = 0;
 					sqlite3_reset(_sGetMember);
 					sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC);
@@ -739,6 +789,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 						sqlite3_reset(_sIncrementMemberRevisionCounter);
 						sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
 						sqlite3_step(_sIncrementMemberRevisionCounter);
+						addToNetworkRevision = 1;
 					}
 
 					json_value *j = json_parse(body.c_str(),body.length());
@@ -758,6 +809,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 										sqlite3_reset(_sIncrementMemberRevisionCounter);
 										sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
 										sqlite3_step(_sIncrementMemberRevisionCounter);
+										addToNetworkRevision = 1;
 									}
 								} else if (!strcmp(j->u.object.values[k].name,"activeBridge")) {
 									if (j->u.object.values[k].value->type == json_boolean) {
@@ -771,6 +823,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 										sqlite3_reset(_sIncrementMemberRevisionCounter);
 										sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
 										sqlite3_step(_sIncrementMemberRevisionCounter);
+										addToNetworkRevision = 1;
 									}
 								} else if (!strcmp(j->u.object.values[k].name,"ipAssignments")) {
 									if (j->u.object.values[k].value->type == json_array) {
@@ -814,6 +867,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 												}
 											}
 										}
+										addToNetworkRevision = 1;
 									}
 								}
 
@@ -822,6 +876,13 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 						json_value_free(j);
 					}
 
+					if ((addToNetworkRevision > 0)&&(revision > 0)) {
+						sqlite3_reset(_sSetNetworkRevision);
+						sqlite3_bind_int64(_sSetNetworkRevision,1,revision + addToNetworkRevision);
+						sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC);
+						sqlite3_step(_sSetNetworkRevision);
+					}
+
 					return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType);
 				} // else 404
 
@@ -1332,58 +1393,33 @@ unsigned int SqliteNetworkController::_doCPGet(
 								responseBody.push_back('"');
 							}
 
-							responseBody.append("]");
+							responseBody.append("],\n\t\"recentLog\": [");
 
-							/* It's possible to get the actual netconf dictionary by including these
-							 * three URL arguments. The member identity must be the string
-							 * serialized identity of this member, and the signing identity must be
-							 * the full secret identity of this network controller. The have revision
-							 * is optional but would designate the revision our hypothetical client
-							 * already has.
-							 *
-							 * This is primarily for testing and is not used in production. It makes
-							 * it easy to test the entire network controller via its JSON API.
-							 *
-							 * If these arguments are included, three more object fields are returned:
-							 * 'netconf', 'netconfResult', and 'netconfResultMessage'. These are all
-							 * string fields and contain the actual netconf dictionary, the query
-							 * result code, and any verbose message e.g. an error description. */
-							std::map<std::string,std::string>::const_iterator memids(urlArgs.find("memberIdentity"));
-							std::map<std::string,std::string>::const_iterator sigids(urlArgs.find("signingIdentity"));
-							std::map<std::string,std::string>::const_iterator hrs(urlArgs.find("haveRevision"));
-							if ((memids != urlArgs.end())&&(sigids != urlArgs.end())) {
-								Dictionary netconf;
-								Identity memid,sigid;
-								try {
-									if (memid.fromString(memids->second)&&sigid.fromString(sigids->second)&&sigid.hasPrivate()) {
-										uint64_t hr = 0;
-										if (hrs != urlArgs.end())
-											hr = Utils::strToU64(hrs->second.c_str());
-										const char *result = "";
-										switch(this->doNetworkConfigRequest(InetAddress(),sigid,memid,nwid,Dictionary(),hr,netconf)) {
-											case NetworkController::NETCONF_QUERY_OK: result = "OK"; break;
-											case NetworkController::NETCONF_QUERY_OK_BUT_NOT_NEWER: result = "OK_BUT_NOT_NEWER"; break;
-											case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: result = "OBJECT_NOT_FOUND"; break;
-											case NetworkController::NETCONF_QUERY_ACCESS_DENIED: result = "ACCESS_DENIED"; break;
-											case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR: result = "INTERNAL_SERVER_ERROR"; break;
-											default: result = "(unrecognized result code)"; break;
-										}
-										responseBody.append(",\n\t\"netconf\": \"");
-										responseBody.append(_jsonEscape(netconf.toString().c_str()));
-										responseBody.append("\",\n\t\"netconfResult\": \"");
-										responseBody.append(result);
-										responseBody.append("\",\n\t\"netconfResultMessage\": \"");
-										responseBody.append(_jsonEscape(netconf["error"].c_str()));
-										responseBody.append("\"");
-									} else {
-										responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"invalid member or signing identity\"");
-									}
-								} catch ( ... ) {
-									responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"unexpected exception\"");
-								}
+							sqlite3_reset(_sGetRecentMemberLog);
+							sqlite3_bind_text(_sGetRecentMemberLog,1,nwids,16,SQLITE_STATIC);
+							sqlite3_bind_text(_sGetRecentMemberLog,2,addrs,10,SQLITE_STATIC);
+							bool firstLog = true;
+							while (sqlite3_step(_sGetRecentMemberLog) == SQLITE_ROW) {
+								responseBody.append(firstLog ? "{" : ",{");
+								firstLog = false;
+								responseBody.append("\"ts\":");
+								responseBody.append(reinterpret_cast<const char *>(sqlite3_column_text(_sGetRecentMemberLog,0)));
+								responseBody.append((sqlite3_column_int(_sGetRecentMemberLog,1) == 0) ? ",\"authorized\":false,\"version\":" : ",\"authorized\":true,\"version\":");
+								const char *ver = reinterpret_cast<const char *>(sqlite3_column_text(_sGetRecentMemberLog,2));
+								if ((ver)&&(ver[0])) {
+									responseBody.push_back('"');
+									responseBody.append(_jsonEscape(ver));
+									responseBody.append("\",\"fromAddr\":");
+								} else responseBody.append("null,\"fromAddr\":");
+								const char *fa = reinterpret_cast<const char *>(sqlite3_column_text(_sGetRecentMemberLog,3));
+								if ((fa)&&(fa[0])) {
+									responseBody.push_back('"');
+									responseBody.append(_jsonEscape(fa));
+									responseBody.append("\"}");
+								} else responseBody.append("null}");
 							}
 
-							responseBody.append("\n}\n");
+							responseBody.append("]\n}\n");
 
 							responseContentType = "application/json";
 							return 200;
@@ -1394,8 +1430,11 @@ unsigned int SqliteNetworkController::_doCPGet(
 
 						sqlite3_reset(_sListNetworkMembers);
 						sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC);
+						responseBody.append("{");
+						bool firstMember = true;
 						while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) {
-							responseBody.append((responseBody.length() > 0) ? ",\"" : "{\"");
+							responseBody.append(firstMember ? "\"" : ",\"");
+							firstMember = false;
 							responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0));
 							responseBody.append("\":");
 							responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,1));
diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp
index 8b39f7d9c..adfe09916 100644
--- a/controller/SqliteNetworkController.hpp
+++ b/controller/SqliteNetworkController.hpp
@@ -97,6 +97,9 @@ private:
 
 	std::string _dbPath;
 	std::string _instanceId;
+
+	std::map< std::pair<Address,uint64_t>,uint64_t > _lastRequestTime;
+
 	sqlite3 *_db;
 
 	sqlite3_stmt *_sGetNetworkById;
@@ -141,6 +144,9 @@ private:
 	sqlite3_stmt *_sIncrementMemberRevisionCounter;
 	sqlite3_stmt *_sGetConfig;
 	sqlite3_stmt *_sSetConfig;
+	sqlite3_stmt *_sPutLog;
+	sqlite3_stmt *_sGetMemberLog;
+	sqlite3_stmt *_sGetRecentMemberLog;
 
 	Mutex _lock;
 };
diff --git a/controller/schema.sql b/controller/schema.sql
index e85785b75..398d63ac3 100644
--- a/controller/schema.sql
+++ b/controller/schema.sql
@@ -65,6 +65,18 @@ CREATE TABLE Member (
 CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);
 CREATE INDEX Member_networkId_memberRevision ON Member(networkId, memberRevision);
 
+CREATE TABLE Log (
+  networkId char(16) NOT NULL,
+  nodeId char(10) NOT NULL,
+  ts integer NOT NULL,
+  authorized integer NOT NULL,
+  version varchar(16),
+  fromAddr varchar(64)
+);
+
+CREATE INDEX Log_networkId_nodeId ON Log(networkId, nodeId);
+CREATE INDEX Log_ts ON Log(ts);
+
 CREATE TABLE Relay (
   networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
   address char(10) NOT NULL,
diff --git a/controller/schema.sql.c b/controller/schema.sql.c
index efeb280ce..fa83f880a 100644
--- a/controller/schema.sql.c
+++ b/controller/schema.sql.c
@@ -66,6 +66,18 @@
 "CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);\n"\
 "CREATE INDEX Member_networkId_memberRevision ON Member(networkId, memberRevision);\n"\
 "\n"\
+"CREATE TABLE Log (\n"\
+"  networkId char(16) NOT NULL,\n"\
+"  nodeId char(10) NOT NULL,\n"\
+"  ts integer NOT NULL,\n"\
+"  authorized integer NOT NULL,\n"\
+"  version varchar(16),\n"\
+"  fromAddr varchar(64)\n"\
+");\n"\
+"\n"\
+"CREATE INDEX Log_networkId_nodeId ON Log(networkId, nodeId);\n"\
+"CREATE INDEX Log_ts ON Log(ts);\n"\
+"\n"\
 "CREATE TABLE Relay (\n"\
 "  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
 "  address char(10) NOT NULL,\n"\
diff --git a/ext/bin/miniupnpc/Changelog.txt b/ext/bin/miniupnpc/Changelog.txt
new file mode 100644
index 000000000..bb2abb7ee
--- /dev/null
+++ b/ext/bin/miniupnpc/Changelog.txt
@@ -0,0 +1,633 @@
+$Id: Changelog.txt,v 1.208 2015/07/15 12:18:59 nanard Exp $
+miniUPnP client Changelog.
+
+2015/07/15:
+  Check malloc/calloc
+
+2015/06/16:
+  update getDevicesFromMiniSSDPD() to process longer minissdpd
+    responses
+
+2015/05/22:
+  add searchalltypes param to upnpDiscoverDevices()
+  increments API_VERSION to 13
+
+2015/04/30:
+  upnpc: output version on the terminal
+
+2015/04/27:
+  _BSD_SOURCE is deprecated in favor of _DEFAULT_SOURCE
+  fix CMakeLists.txt COMPILE_DEFINITIONS
+  fix getDevicesFromMiniSSDPD() not setting scope_id
+  improve -r command of upnpc command line tool
+
+2014/11/17:
+  search all :
+    upnpDiscoverDevices() / upnpDiscoverAll() functions
+    listdevices executable
+  increment API_VERSION to 12
+  validate igd_desc_parse
+
+2014/11/13:
+  increment API_VERSION to 11
+
+2014/11/05:
+  simplified function GetUPNPUrls()
+
+2014/09/11:
+  use remoteHost arg of DeletePortMapping
+
+2014/09/06:
+  Fix python3 build
+
+2014/07/01:
+  Fix parsing of IGD2 root descriptions
+
+2014/06/10:
+  rename LIBSPEC to MINIUPNP_LIBSPEC
+
+2014/05/15:
+  Add support for IGD2 AddAnyPortMapping and DeletePortMappingRange
+
+2014/02/05:
+  handle EINPROGRESS after connect()
+
+2014/02/03:
+  minixml now handle XML comments
+
+VERSION 1.9 : released 2014/01/31
+
+2014/01/31:
+  added argument remoteHost to UPNP_GetSpecificPortMappingEntry()
+  increment API_VERSION to 10
+
+2013/12/09:
+  --help and -h arguments in upnpc.c
+
+2013/10/07:
+  fixed potential buffer overrun in miniwget.c
+  Modified UPNP_GetValidIGD() to check for ExternalIpAddress
+
+2013/08/01:
+  define MAXHOSTNAMELEN if not already done
+
+2013/06/06:
+  update upnpreplyparse to allow larger values (128 chars instead of 64)
+
+2013/05/14:
+  Update upnpreplyparse to take into account "empty" elements
+  validate upnpreplyparse.c code with "make check"
+
+2013/05/03:
+  Fix Solaris build thanks to Maciej Małecki
+
+2013/04/27:
+  Fix testminiwget.sh for BSD
+
+2013/03/23:
+  Fixed Makefile for *BSD
+
+2013/03/11:
+  Update Makefile to use JNAerator version 0.11
+
+2013/02/11:
+  Fix testminiwget.sh for use with dash
+  Use $(DESTDIR) in Makefile
+
+VERSION 1.8 : released 2013/02/06
+
+2012/10/16:
+  fix testminiwget with no IPv6 support
+
+2012/09/27:
+  Rename all include guards to not clash with C99
+  (7.1.3 Reserved identifiers).
+
+2012/08/30:
+  Added -e option to upnpc program (set description for port mappings)
+
+2012/08/29:
+  Python 3 support (thanks to Christopher Foo)
+
+2012/08/11:
+  Fix a memory link in UPNP_GetValidIGD()
+  Try to handle scope id in link local IPv6 URL under MS Windows
+
+2012/07/20:
+  Disable HAS_IP_MREQN on DragonFly BSD
+
+2012/06/28:
+  GetUPNPUrls() now inserts scope into link-local IPv6 addresses
+
+2012/06/23:
+  More error return checks in upnpc.c
+  #define MINIUPNPC_GET_SRC_ADDR enables receivedata() to get scope_id
+  parseURL() now parses IPv6 addresses scope
+  new parameter for miniwget() : IPv6 address scope
+  increment API_VERSION to 9
+
+2012/06/20:
+  fixed CMakeLists.txt
+
+2012/05/29
+  Improvements in testminiwget.sh
+
+VERSION 1.7 : released 2012/05/24
+
+2012/05/01:
+  Cleanup settings of CFLAGS in Makefile
+  Fix signed/unsigned integer comparaisons
+
+2012/04/20:
+  Allow to specify protocol with TCP or UDP for -A option
+
+2012/04/09:
+  Only try to fetch XML description once in UPNP_GetValidIGD()
+  Added -ansi flag to compilation, and fixed C++ comments to ANSI C comments.
+
+2012/04/05:
+  minor improvements to minihttptestserver.c
+
+2012/03/15:
+  upnperrors.c returns valid error string for unrecognized error codes
+
+2012/03/08:
+  make minihttptestserver listen on loopback interface instead of 0.0.0.0
+
+2012/01/25:
+  Maven installation thanks to Alexey Kuznetsov
+
+2012/01/21:
+  Replace WIN32 macro by _WIN32
+
+2012/01/19:
+  Fixes in java wrappers thanks to Alexey Kuznetsov :
+    https://github.com/axet/miniupnp/tree/fix-javatest/miniupnpc
+  Make and install .deb packages (python) thanks to Alexey Kuznetsov :
+    https://github.com/axet/miniupnp/tree/feature-debbuild/miniupnpc
+
+2012/01/07:
+  The multicast interface can now be specified by name with IPv4.
+
+2012/01/02:
+  Install man page
+
+2011/11/25:
+  added header to Port Mappings list in upnpc.c
+
+2011/10/09:
+  Makefile : make clean now removes jnaerator generated files.
+  MINIUPNPC_VERSION in miniupnpc.h (updated by make)
+
+2011/09/12:
+  added rootdescURL to UPNPUrls structure.
+
+VERSION 1.6 : released 2011/07/25
+
+2011/07/25:
+  Update doc for version 1.6 release
+
+2011/06/18:
+  Fix for windows in miniwget.c
+
+2011/06/04:
+  display remote host in port mapping listing
+
+2011/06/03:
+  Fix in make install : there were missing headers
+
+2011/05/26:
+  Fix the socket leak in miniwget thanks to Richard Marsh.
+  Permit to add leaseduration in -a command. Display lease duration.
+
+2011/05/15:
+  Try both LinkLocal and SiteLocal multicast address for SSDP in IPv6
+
+2011/05/09:
+  add a test in testminiwget.sh.
+  more error checking in miniwget.c
+
+2011/05/06:
+  Adding some tool to test and validate miniwget.c
+  simplified and debugged miniwget.c
+
+2011/04/11:
+  moving ReceiveData() to a receivedata.c file.
+  parsing presentation url
+  adding IGD v2 WANIPv6FirewallControl commands
+
+2011/04/10:
+  update of miniupnpcmodule.c
+  comments in miniwget.c, update in testminiwget
+  Adding errors codes from IGD v2
+  new functions in upnpc.c for IGD v2
+
+2011/04/09:
+  Support for litteral ip v6 address in miniwget
+
+2011/04/08:
+  Adding support for urn:schemas-upnp-org:service:WANIPv6FirewallControl:1
+  Updating APIVERSION
+  Supporting IPV6 in upnpDiscover()
+  Adding a -6 option to upnpc command line tool
+
+2011/03/18:
+  miniwget/parseURL() : return an error when url param is null.
+  fixing GetListOfPortMappings()
+
+2011/03/14:
+  upnpDiscover() now reporting an error code.
+  improvements in comments.
+
+2011/03/11:
+  adding miniupnpcstrings.h.cmake and CMakeLists.txt files.
+
+2011/02/15:
+  Implementation of GetListOfPortMappings()
+
+2011/02/07:
+  updates to minixml to support character data starting with spaces
+  minixml now support CDATA
+  upnpreplyparse treats <NewPortListing> specificaly
+  change in simpleUPnPcommand to return the buffer (simplification)
+
+2011/02/06:
+  Added leaseDuration argument to AddPortMapping()
+  Starting to implement GetListOfPortMappings()
+
+2011/01/11:
+  updating wingenminiupnpcstrings.c
+
+2011/01/04:
+  improving updateminiupnpcstrings.sh
+
+VERSION 1.5 : released 2011/01/01
+
+2010/12/21:
+  use NO_GETADDRINFO macro to disable the use of getaddrinfo/freeaddrinfo
+
+2010/12/11:
+  Improvements on getHTTPResponse() code.
+
+2010/12/09:
+  new code for miniwget that handle Chunked transfer encoding
+  using getHTTPResponse() in SOAP call code
+  Adding MANIFEST.in for 'python setup.py bdist_rpm'
+
+2010/11/25:
+  changes to minissdpc.c to compile under Win32.
+  see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=729
+
+2010/09/17:
+  Various improvement to Makefile from Michał Górny
+
+2010/08/05:
+  Adding the script "external-ip.sh" from Reuben Hawkins
+
+2010/06/09:
+  update to python module to match modification made on 2010/04/05
+  update to Java test code to match modification made on 2010/04/05
+  all UPNP_* function now return an error if the SOAP request failed
+  at HTTP level.
+
+2010/04/17:
+  Using GetBestRoute() under win32 in order to find the
+  right interface to use.
+
+2010/04/12:
+  Retrying with HTTP/1.1 if HTTP/1.0 failed. see
+  http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1703
+
+2010/04/07:
+  avoid returning duplicates in upnpDiscover()
+
+2010/04/05:
+  Create a connecthostport.h/.c with connecthostport() function
+  and use it in miniwget and miniupnpc.
+  Use getnameinfo() instead of inet_ntop or inet_ntoa
+  Work to make miniupnpc IPV6 compatible...
+  Add java test code.
+  Big changes in order to support device having both WANIPConnection
+  and WANPPPConnection.
+
+2010/04/04:
+  Use getaddrinfo() instead of gethostbyname() in miniwget.
+
+2010/01/06:
+  #define _DARWIN_C_SOURCE for Mac OS X
+
+2009/12/19:
+  Improve MinGW32 build
+
+2009/12/11:
+  adding a MSVC9 project to build the static library and executable
+
+2009/12/10:
+  Fixing some compilation stuff for Windows/MinGW
+
+2009/12/07:
+  adaptations in Makefile and updateminiupnpcstring.sh for AmigaOS
+  some fixes for Windows when using virtual ethernet adapters (it is the
+  case with VMWare installed).
+
+2009/12/04:
+  some fixes for AmigaOS compilation
+  Changed HTTP version to HTTP/1.0 for Soap too (to prevent chunked
+  transfer encoding)
+
+2009/12/03:
+  updating printIDG and testigddescparse.c for debug.
+  modifications to compile under AmigaOS
+  adding a testminiwget program
+  Changed miniwget to advertise itself as HTTP/1.0 to prevent chunked
+  transfer encoding
+
+2009/11/26:
+  fixing updateminiupnpcstrings.sh to take into account
+  which command that does not return an error code.
+
+VERSION 1.4 : released 2009/10/30
+
+2009/10/16:
+  using Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS in python module.
+
+2009/10/10:
+  Some fixes for compilation under Solaris
+  compilation fixes : http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1464
+
+2009/09/21:
+  fixing the code to ignore EINTR during connect() calls.
+
+2009/08/07:
+  Set socket timeout for connect()
+  Some cleanup in miniwget.c
+
+2009/08/04:
+  remove multiple redirections with -d in upnpc.c
+  Print textual error code in upnpc.c
+  Ignore EINTR during the connect() and poll() calls.
+
+2009/07/29:
+  fix in updateminiupnpcstrings.sh if OS name contains "/"
+  Sending a correct value for MX: field in SSDP request
+
+2009/07/20:
+  Change the Makefile to compile under Mac OS X
+  Fixed a stackoverflow in getDevicesFromMiniSSDPD()
+
+2009/07/09:
+  Compile under Haiku
+  generate miniupnpcstrings.h.in from miniupnpcstrings.h
+
+2009/06/04:
+  patching to compile under CygWin and cross compile for minGW
+
+VERSION 1.3 :
+
+2009/04/17:
+  updating python module
+  Use strtoull() when using C99
+
+2009/02/28:
+  Fixed miniwget.c for compiling under sun
+
+2008/12/18:
+  cleanup in Makefile (thanks to Paul de Weerd)
+  minissdpc.c : win32 compatibility
+  miniupnpc.c : changed xmlns prefix from 'm' to 'u'
+  Removed NDEBUG (using DEBUG)
+
+2008/10/14:
+  Added the ExternalHost argument to DeletePortMapping()
+
+2008/10/11:
+  Added the ExternalHost argument to AddPortMapping()
+  Put a correct User-Agent: header in HTTP requests.
+
+VERSION 1.2 :
+
+2008/10/07:
+  Update docs
+
+2008/09/25:
+  Integrated sameport patch from Dario Meloni : Added a "sameport"
+  argument to upnpDiscover().
+
+2008/07/18:
+  small modif to make Clang happy :)
+
+2008/07/17:
+  #define SOAPPREFIX "s" in miniupnpc.c in order to remove SOAP-ENV...
+
+2008/07/14:
+  include declspec.h in installation (to /usr/include/miniupnpc)
+
+VERSION 1.1 :
+
+2008/07/04:
+  standard options for install/ln instead of gnu-specific stuff.
+
+2008/07/03:
+  now builds a .dll and .lib with win32. (mingw32)
+
+2008/04/28:
+  make install now install the binary of the upnpc tool
+
+2008/04/27:
+  added testupnpigd.py
+  added error strings for miniupnpc "internal" errors
+  improved python module error/exception reporting.
+
+2008/04/23:
+  Completely rewrite igd_desc_parse.c in order to be compatible with
+  Linksys WAG200G
+  Added testigddescparse
+  updated python module
+
+VERSION 1.0 :
+
+2008/02/21:
+  put some #ifdef DEBUG around DisplayNameValueList()
+
+2008/02/18:
+  Improved error reporting in upnpcommands.c
+  UPNP_GetStatusInfo() returns LastConnectionError
+
+2008/02/16:
+  better error handling in minisoap.c
+  improving display of "valid IGD found" in upnpc.c
+
+2008/02/03:
+  Fixing UPNP_GetValidIGD()
+  improved make install :)
+
+2007/12/22:
+  Adding upnperrors.c/h to provide a strupnperror() function
+  used to translate UPnP error codes to string.
+
+2007/12/19:
+  Fixing getDevicesFromMiniSSDPD()
+  improved error reporting of UPnP functions
+
+2007/12/18:
+  It is now possible to specify a different location for MiniSSDPd socket.
+  working with MiniSSDPd is now more efficient.
+  python module improved.
+
+2007/12/16:
+  improving error reporting
+
+2007/12/13:
+  Try to improve compatibility by using HTTP/1.0 instead of 1.1 and
+  XML a bit different for SOAP.
+
+2007/11/25:
+  fixed select() call for linux
+
+2007/11/15:
+  Added -fPIC to CFLAG for better shared library code.
+
+2007/11/02:
+  Fixed a potential socket leak in miniwget2()
+
+2007/10/16:
+  added a parameter to upnpDiscover() in order to allow the use of another
+  interface than the default multicast interface.
+
+2007/10/12:
+  Fixed the creation of symbolic link in Makefile
+
+2007/10/08:
+  Added man page
+
+2007/10/02:
+  fixed memory bug in GetUPNPUrls()
+
+2007/10/01:
+  fixes in the Makefile
+  Added UPNP_GetIGDFromUrl() and adapted the sample program accordingly.
+  Added SONAME in the shared library to please debian :)
+  fixed MS Windows compilation (minissdpd is not available under MS Windows).
+
+2007/09/25:
+  small change to Makefile to be able to install in a different location
+  (default is /usr)
+
+2007/09/24:
+  now compiling both shared and static library
+
+2007/09/19:
+  Cosmetic changes on upnpc.c
+
+2007/09/02:
+  adapting to new miniSSDPd (release version ?)
+
+2007/08/31:
+  Usage of miniSSDPd to skip discovery process.
+
+2007/08/27:
+  fixed python module to allow compilation with Python older than Python 2.4
+
+2007/06/12:
+  Added a python module.
+
+2007/05/19:
+  Fixed compilation under MinGW
+
+2007/05/15:
+  fixed a memory leak in AddPortMapping()
+  Added testupnpreplyparse executable to check the parsing of
+  upnp soap messages
+  minixml now ignore namespace prefixes.
+
+2007/04/26:
+  upnpc now displays external ip address with -s or -l
+
+2007/04/11:
+  changed MINIUPNPC_URL_MAXSIZE to 128 to accomodate the "BT Voyager 210"
+
+2007/03/19:
+  cleanup in miniwget.c
+
+2007/03/01:
+  Small typo fix...
+
+2007/01/30:
+  Now parsing the HTTP header from SOAP responses in order to
+  get content-length value.
+
+2007/01/29:
+  Fixed the Soap Query to speedup the HTTP request.
+  added some Win32 DLL stuff...
+
+2007/01/27:
+  Fixed some WIN32 compatibility issues
+
+2006/12/14:
+  Added UPNPIGD_IsConnected() function in miniupnp.c/.h
+  Added UPNP_GetValidIGD() in miniupnp.c/.h
+  cleaned upnpc.c main(). now using UPNP_GetValidIGD()
+
+2006/12/07:
+  Version 1.0-RC1 released
+
+2006/12/03:
+  Minor changes to compile under SunOS/Solaris
+
+2006/11/30:
+  made a minixml parser validator program
+  updated minixml to handle attributes correctly
+
+2006/11/22:
+  Added a -r option to the upnpc sample thanks to Alexander Hubmann.
+
+2006/11/19:
+  Cleanup code to make it more ANSI C compliant
+
+2006/11/10:
+  detect and display local lan address.
+
+2006/11/04:
+  Packets and Bytes Sent/Received are now unsigned int.
+
+2006/11/01:
+  Bug fix thanks to Giuseppe D'Angelo
+
+2006/10/31:
+  C++ compatibility for .h files.
+  Added a way to get ip Address on the LAN used to reach the IGD.
+
+2006/10/25:
+  Added M-SEARCH to the services in the discovery process.
+
+2006/10/22:
+  updated the Makefile to use makedepend, added a "make install"
+  update Makefile
+
+2006/10/20:
+  fixing the description url parsing thanks to patch sent by
+  Wayne Dawe.
+  Fixed/translated some comments.
+  Implemented a better discover process, first looking
+  for IGD then for root devices (as some devices only reply to
+  M-SEARCH for root devices).
+
+2006/09/02:
+  added freeUPNPDevlist() function.
+
+2006/08/04:
+  More command line arguments checking
+
+2006/08/01:
+  Added the .bat file to compile under Win32 with minGW32
+
+2006/07/31:
+  Fixed the rootdesc parser (igd_desc_parse.c)
+
+2006/07/20:
+  parseMSEARCHReply() is now returning the ST: line as well
+  starting changes to detect several UPnP devices on the network
+
+2006/07/19:
+  using GetCommonLinkProperties to get down/upload bitrate
+
diff --git a/ext/bin/miniupnpc/LICENSE b/ext/bin/miniupnpc/LICENSE
new file mode 100644
index 000000000..cb5a06044
--- /dev/null
+++ b/ext/bin/miniupnpc/LICENSE
@@ -0,0 +1,27 @@
+MiniUPnPc
+Copyright (c) 2005-2015, Thomas BERNARD
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * The name of the author may not be used to endorse or promote products
+	  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/ext/bin/miniupnpc/README.md b/ext/bin/miniupnpc/README.md
new file mode 100644
index 000000000..c3d538ba9
--- /dev/null
+++ b/ext/bin/miniupnpc/README.md
@@ -0,0 +1,4 @@
+libminiupnpc binaries
+======
+
+This is a binary build of [libminiupnpc](http://miniupnp.free.fr) for certain architectures to faciliate easy building. Where possible the build flags were set for improved security by enabling options like stack protector (a.k.a. stack canary), ASLR support, etc.
diff --git a/ext/bin/miniupnpc/VERSION b/ext/bin/miniupnpc/VERSION
new file mode 100644
index 000000000..2e0e38c63
--- /dev/null
+++ b/ext/bin/miniupnpc/VERSION
@@ -0,0 +1 @@
+1.9
diff --git a/ext/bin/miniupnpc/include/miniupnpc/codelength.h b/ext/bin/miniupnpc/include/miniupnpc/codelength.h
new file mode 100644
index 000000000..f5f8e30f9
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/codelength.h
@@ -0,0 +1,54 @@
+/* $Id: codelength.h,v 1.5 2015/07/09 12:40:18 nanard Exp $ */
+/* Project : miniupnp
+ * Author : Thomas BERNARD
+ * copyright (c) 2005-2015 Thomas Bernard
+ * This software is subjet to the conditions detailed in the
+ * provided LICENCE file. */
+#ifndef CODELENGTH_H_INCLUDED
+#define CODELENGTH_H_INCLUDED
+
+/* Encode length by using 7bit per Byte :
+ * Most significant bit of each byte specifies that the
+ * following byte is part of the code */
+
+/* n : unsigned
+ * p : unsigned char *
+ */
+#define DECODELENGTH(n, p) n = 0; \
+                           do { n = (n << 7) | (*p & 0x7f); } \
+                           while((*(p++)&0x80) && (n<(1<<25)));
+
+/* n :    unsigned
+ * READ : function/macro to read one byte (unsigned char)
+ */
+#define DECODELENGTH_READ(n, READ) \
+	n = 0; \
+	do { \
+		unsigned char c; \
+		READ(c); \
+		n = (n << 7) | (c & 0x07f); \
+		if(!(c&0x80)) break; \
+	} while(n<(1<<25));
+
+/* n :       unsigned
+ * p :       unsigned char *
+ * p_limit : unsigned char *
+ */
+#define DECODELENGTH_CHECKLIMIT(n, p, p_limit) \
+	n = 0; \
+	do { \
+		if((p) >= (p_limit)) break; \
+		n = (n << 7) | (*(p) & 0x7f); \
+	} while((*((p)++)&0x80) && (n<(1<<25)));
+
+
+/* n : unsigned
+ * p : unsigned char *
+ */
+#define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \
+                         if(n>=2097152) *(p++) = (n >> 21) | 0x80; \
+                         if(n>=16384) *(p++) = (n >> 14) | 0x80; \
+                         if(n>=128) *(p++) = (n >> 7) | 0x80; \
+                         *(p++) = n & 0x7f;
+
+#endif /* CODELENGTH_H_INCLUDED */
diff --git a/ext/bin/miniupnpc/include/miniupnpc/connecthostport.h b/ext/bin/miniupnpc/include/miniupnpc/connecthostport.h
new file mode 100644
index 000000000..56941d6fa
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/connecthostport.h
@@ -0,0 +1,18 @@
+/* $Id: connecthostport.h,v 1.3 2012/09/27 15:42:10 nanard Exp $ */
+/* Project: miniupnp
+ * http://miniupnp.free.fr/
+ * Author: Thomas Bernard
+ * Copyright (c) 2010-2012 Thomas Bernard
+ * This software is subjects to the conditions detailed
+ * in the LICENCE file provided within this distribution */
+#ifndef CONNECTHOSTPORT_H_INCLUDED
+#define CONNECTHOSTPORT_H_INCLUDED
+
+/* connecthostport()
+ * return a socket connected (TCP) to the host and port
+ * or -1 in case of error */
+int connecthostport(const char * host, unsigned short port,
+                    unsigned int scope_id);
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/igd_desc_parse.h b/ext/bin/miniupnpc/include/miniupnpc/igd_desc_parse.h
new file mode 100644
index 000000000..0de546b69
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/igd_desc_parse.h
@@ -0,0 +1,49 @@
+/* $Id: igd_desc_parse.h,v 1.12 2014/11/17 17:19:13 nanard Exp $ */
+/* Project : miniupnp
+ * http://miniupnp.free.fr/
+ * Author : Thomas Bernard
+ * Copyright (c) 2005-2014 Thomas Bernard
+ * This software is subject to the conditions detailed in the
+ * LICENCE file provided in this distribution.
+ * */
+#ifndef IGD_DESC_PARSE_H_INCLUDED
+#define IGD_DESC_PARSE_H_INCLUDED
+
+/* Structure to store the result of the parsing of UPnP
+ * descriptions of Internet Gateway Devices */
+#define MINIUPNPC_URL_MAXSIZE (128)
+struct IGDdatas_service {
+	char controlurl[MINIUPNPC_URL_MAXSIZE];
+	char eventsuburl[MINIUPNPC_URL_MAXSIZE];
+	char scpdurl[MINIUPNPC_URL_MAXSIZE];
+	char servicetype[MINIUPNPC_URL_MAXSIZE];
+	/*char devicetype[MINIUPNPC_URL_MAXSIZE];*/
+};
+
+struct IGDdatas {
+	char cureltname[MINIUPNPC_URL_MAXSIZE];
+	char urlbase[MINIUPNPC_URL_MAXSIZE];
+	char presentationurl[MINIUPNPC_URL_MAXSIZE];
+	int level;
+	/*int state;*/
+	/* "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */
+	struct IGDdatas_service CIF;
+	/* "urn:schemas-upnp-org:service:WANIPConnection:1"
+	 * "urn:schemas-upnp-org:service:WANPPPConnection:1" */
+	struct IGDdatas_service first;
+	/* if both WANIPConnection and WANPPPConnection are present */
+	struct IGDdatas_service second;
+	/* "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1" */
+	struct IGDdatas_service IPv6FC;
+	/* tmp */
+	struct IGDdatas_service tmp;
+};
+
+void IGDstartelt(void *, const char *, int);
+void IGDendelt(void *, const char *, int);
+void IGDdata(void *, const char *, int);
+#ifdef DEBUG
+void printIGD(struct IGDdatas *);
+#endif /* DEBUG */
+
+#endif /* IGD_DESC_PARSE_H_INCLUDED */
diff --git a/ext/bin/miniupnpc/include/miniupnpc/minisoap.h b/ext/bin/miniupnpc/include/miniupnpc/minisoap.h
new file mode 100644
index 000000000..14c859d1e
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/minisoap.h
@@ -0,0 +1,15 @@
+/* $Id: minisoap.h,v 1.5 2012/09/27 15:42:10 nanard Exp $ */
+/* Project : miniupnp
+ * Author : Thomas Bernard
+ * Copyright (c) 2005 Thomas Bernard
+ * This software is subject to the conditions detailed in the
+ * LICENCE file provided in this distribution. */
+#ifndef MINISOAP_H_INCLUDED
+#define MINISOAP_H_INCLUDED
+
+/*int httpWrite(int, const char *, int, const char *);*/
+int soapPostSubmit(int, const char *, const char *, unsigned short,
+		   const char *, const char *, const char *);
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/minissdpc.h b/ext/bin/miniupnpc/include/miniupnpc/minissdpc.h
new file mode 100644
index 000000000..915b0026f
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/minissdpc.h
@@ -0,0 +1,15 @@
+/* $Id: minissdpc.h,v 1.2 2012/09/27 15:42:10 nanard Exp $ */
+/* Project: miniupnp
+ * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
+ * Author: Thomas Bernard
+ * Copyright (c) 2005-2007 Thomas Bernard
+ * This software is subjects to the conditions detailed
+ * in the LICENCE file provided within this distribution */
+#ifndef MINISSDPC_H_INCLUDED
+#define MINISSDPC_H_INCLUDED
+
+struct UPNPDev *
+getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath);
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/miniupnpc.h b/ext/bin/miniupnpc/include/miniupnpc/miniupnpc.h
new file mode 100644
index 000000000..0eeabc234
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/miniupnpc.h
@@ -0,0 +1,154 @@
+/* $Id: miniupnpc.h,v 1.42 2015/07/21 13:16:55 nanard Exp $ */
+/* Project: miniupnp
+ * http://miniupnp.free.fr/
+ * Author: Thomas Bernard
+ * Copyright (c) 2005-2015 Thomas Bernard
+ * This software is subjects to the conditions detailed
+ * in the LICENCE file provided within this distribution */
+#ifndef MINIUPNPC_H_INCLUDED
+#define MINIUPNPC_H_INCLUDED
+
+#include "miniupnpc_declspec.h"
+#include "igd_desc_parse.h"
+
+/* error codes : */
+#define UPNPDISCOVER_SUCCESS (0)
+#define UPNPDISCOVER_UNKNOWN_ERROR (-1)
+#define UPNPDISCOVER_SOCKET_ERROR (-101)
+#define UPNPDISCOVER_MEMORY_ERROR (-102)
+
+/* versions : */
+#define MINIUPNPC_VERSION	"1.9.20150721"
+#define MINIUPNPC_API_VERSION	13
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structures definitions : */
+struct UPNParg { const char * elt; const char * val; };
+
+char *
+simpleUPnPcommand(int, const char *, const char *,
+                  const char *, struct UPNParg *,
+                  int *);
+
+struct UPNPDev {
+	struct UPNPDev * pNext;
+	char * descURL;
+	char * st;
+	unsigned int scope_id;
+	char buffer[2];
+};
+
+/* upnpDiscover()
+ * discover UPnP devices on the network.
+ * The discovered devices are returned as a chained list.
+ * It is up to the caller to free the list with freeUPNPDevlist().
+ * delay (in millisecond) is the maximum time for waiting any device
+ * response.
+ * If available, device list will be obtained from MiniSSDPd.
+ * Default path for minissdpd socket will be used if minissdpdsock argument
+ * is NULL.
+ * If multicastif is not NULL, it will be used instead of the default
+ * multicast interface for sending SSDP discover packets.
+ * If sameport is not null, SSDP packets will be sent from the source port
+ * 1900 (same as destination port) otherwise system assign a source port.
+ * "searchalltypes" parameter is useful when searching several types,
+ * if 0, the discovery will stop with the first type returning results. */
+MINIUPNP_LIBSPEC struct UPNPDev *
+upnpDiscover(int delay, const char * multicastif,
+             const char * minissdpdsock, int sameport,
+             int ipv6,
+             int * error);
+
+MINIUPNP_LIBSPEC struct UPNPDev *
+upnpDiscoverAll(int delay, const char * multicastif,
+                const char * minissdpdsock, int sameport,
+                int ipv6,
+                int * error);
+
+MINIUPNP_LIBSPEC struct UPNPDev *
+upnpDiscoverDevice(const char * device, int delay, const char * multicastif,
+                const char * minissdpdsock, int sameport,
+                int ipv6,
+                int * error);
+
+MINIUPNP_LIBSPEC struct UPNPDev *
+upnpDiscoverDevices(const char * const deviceTypes[],
+                    int delay, const char * multicastif,
+                    const char * minissdpdsock, int sameport,
+                    int ipv6,
+                    int * error,
+                    int searchalltypes);
+
+/* freeUPNPDevlist()
+ * free list returned by upnpDiscover() */
+MINIUPNP_LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist);
+
+/* parserootdesc() :
+ * parse root XML description of a UPnP device and fill the IGDdatas
+ * structure. */
+MINIUPNP_LIBSPEC void parserootdesc(const char *, int, struct IGDdatas *);
+
+/* structure used to get fast access to urls
+ * controlURL: controlURL of the WANIPConnection
+ * ipcondescURL: url of the description of the WANIPConnection
+ * controlURL_CIF: controlURL of the WANCommonInterfaceConfig
+ * controlURL_6FC: controlURL of the WANIPv6FirewallControl
+ */
+struct UPNPUrls {
+	char * controlURL;
+	char * ipcondescURL;
+	char * controlURL_CIF;
+	char * controlURL_6FC;
+	char * rootdescURL;
+};
+
+/* UPNP_GetValidIGD() :
+ * return values :
+ *     0 = NO IGD found
+ *     1 = A valid connected IGD has been found
+ *     2 = A valid IGD has been found but it reported as
+ *         not connected
+ *     3 = an UPnP device has been found but was not recognized as an IGD
+ *
+ * In any non zero return case, the urls and data structures
+ * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to
+ * free allocated memory.
+ */
+MINIUPNP_LIBSPEC int
+UPNP_GetValidIGD(struct UPNPDev * devlist,
+                 struct UPNPUrls * urls,
+				 struct IGDdatas * data,
+				 char * lanaddr, int lanaddrlen);
+
+/* UPNP_GetIGDFromUrl()
+ * Used when skipping the discovery process.
+ * When succeding, urls, data, and lanaddr arguments are set.
+ * return value :
+ *   0 - Not ok
+ *   1 - OK */
+MINIUPNP_LIBSPEC int
+UPNP_GetIGDFromUrl(const char * rootdescurl,
+                   struct UPNPUrls * urls,
+                   struct IGDdatas * data,
+                   char * lanaddr, int lanaddrlen);
+
+MINIUPNP_LIBSPEC void
+GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *,
+            const char *, unsigned int);
+
+MINIUPNP_LIBSPEC void
+FreeUPNPUrls(struct UPNPUrls *);
+
+/* return 0 or 1 */
+MINIUPNP_LIBSPEC int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/miniupnpc_declspec.h b/ext/bin/miniupnpc/include/miniupnpc/miniupnpc_declspec.h
new file mode 100644
index 000000000..40adb922e
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/miniupnpc_declspec.h
@@ -0,0 +1,21 @@
+#ifndef MINIUPNPC_DECLSPEC_H_INCLUDED
+#define MINIUPNPC_DECLSPEC_H_INCLUDED
+
+#if defined(_WIN32) && !defined(MINIUPNP_STATICLIB)
+	/* for windows dll */
+	#ifdef MINIUPNP_EXPORTS
+		#define MINIUPNP_LIBSPEC __declspec(dllexport)
+	#else
+		#define MINIUPNP_LIBSPEC __declspec(dllimport)
+	#endif
+#else
+	#if defined(__GNUC__) && __GNUC__ >= 4
+		/* fix dynlib for OS X 10.9.2 and Apple LLVM version 5.0 */
+		#define MINIUPNP_LIBSPEC __attribute__ ((visibility ("default")))
+	#else
+		#define MINIUPNP_LIBSPEC
+	#endif
+#endif
+
+#endif /* MINIUPNPC_DECLSPEC_H_INCLUDED */
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/miniupnpcstrings.h b/ext/bin/miniupnpc/include/miniupnpc/miniupnpcstrings.h
new file mode 100644
index 000000000..80a1d757d
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/miniupnpcstrings.h
@@ -0,0 +1,23 @@
+/* $Id: miniupnpcstrings.h.in,v 1.6 2014/11/04 22:31:55 nanard Exp $ */
+/* Project: miniupnp
+ * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
+ * Author: Thomas Bernard
+ * Copyright (c) 2005-2014 Thomas Bernard
+ * This software is subjects to the conditions detailed
+ * in the LICENCE file provided within this distribution */
+#ifndef MINIUPNPCSTRINGS_H_INCLUDED
+#define MINIUPNPCSTRINGS_H_INCLUDED
+
+#define OS_STRING "Darwin/14.4.0"
+#define MINIUPNPC_VERSION_STRING "1.9"
+
+#if 0
+/* according to "UPnP Device Architecture 1.0" */
+#define UPNP_VERSION_STRING "UPnP/1.0"
+#else
+/* according to "UPnP Device Architecture 1.1" */
+#define UPNP_VERSION_STRING "UPnP/1.1"
+#endif
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/miniupnpctypes.h b/ext/bin/miniupnpc/include/miniupnpc/miniupnpctypes.h
new file mode 100644
index 000000000..591c32fb6
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/miniupnpctypes.h
@@ -0,0 +1,19 @@
+/* $Id: miniupnpctypes.h,v 1.2 2012/09/27 15:42:10 nanard Exp $ */
+/* Miniupnp project : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org
+ * Author : Thomas Bernard
+ * Copyright (c) 2011 Thomas Bernard
+ * This software is subject to the conditions detailed in the
+ * LICENCE file provided within this distribution */
+#ifndef MINIUPNPCTYPES_H_INCLUDED
+#define MINIUPNPCTYPES_H_INCLUDED
+
+#if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)
+#define UNSIGNED_INTEGER unsigned long long
+#define STRTOUI	strtoull
+#else
+#define UNSIGNED_INTEGER unsigned int
+#define STRTOUI	strtoul
+#endif
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/miniwget.h b/ext/bin/miniupnpc/include/miniupnpc/miniwget.h
new file mode 100644
index 000000000..d6db71a85
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/miniwget.h
@@ -0,0 +1,30 @@
+/* $Id: miniwget.h,v 1.10 2015/07/21 13:16:55 nanard Exp $ */
+/* Project : miniupnp
+ * Author : Thomas Bernard
+ * Copyright (c) 2005-2015 Thomas Bernard
+ * This software is subject to the conditions detailed in the
+ * LICENCE file provided in this distribution.
+ * */
+#ifndef MINIWGET_H_INCLUDED
+#define MINIWGET_H_INCLUDED
+
+#include "miniupnpc_declspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+MINIUPNP_LIBSPEC void * getHTTPResponse(int s, int * size);
+
+MINIUPNP_LIBSPEC void * miniwget(const char *, int *, unsigned int);
+
+MINIUPNP_LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int, unsigned int);
+
+int parseURL(const char *, char *, unsigned short *, char * *, unsigned int *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/minixml.h b/ext/bin/miniupnpc/include/miniupnpc/minixml.h
new file mode 100644
index 000000000..9f43aa48c
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/minixml.h
@@ -0,0 +1,37 @@
+/* $Id: minixml.h,v 1.7 2012/09/27 15:42:10 nanard Exp $ */
+/* minimal xml parser
+ *
+ * Project : miniupnp
+ * Website : http://miniupnp.free.fr/
+ * Author : Thomas Bernard
+ * Copyright (c) 2005 Thomas Bernard
+ * This software is subject to the conditions detailed in the
+ * LICENCE file provided in this distribution.
+ * */
+#ifndef MINIXML_H_INCLUDED
+#define MINIXML_H_INCLUDED
+#define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n'))
+
+/* if a callback function pointer is set to NULL,
+ * the function is not called */
+struct xmlparser {
+	const char *xmlstart;
+	const char *xmlend;
+	const char *xml;	/* pointer to current character */
+	int xmlsize;
+	void * data;
+	void (*starteltfunc) (void *, const char *, int);
+	void (*endeltfunc) (void *, const char *, int);
+	void (*datafunc) (void *, const char *, int);
+	void (*attfunc) (void *, const char *, int, const char *, int);
+};
+
+/* parsexml()
+ * the xmlparser structure must be initialized before the call
+ * the following structure members have to be initialized :
+ * xmlstart, xmlsize, data, *func
+ * xml is for internal usage, xmlend is computed automatically */
+void parsexml(struct xmlparser *);
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/portlistingparse.h b/ext/bin/miniupnpc/include/miniupnpc/portlistingparse.h
new file mode 100644
index 000000000..661ad1faa
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/portlistingparse.h
@@ -0,0 +1,65 @@
+/* $Id: portlistingparse.h,v 1.11 2015/07/21 13:16:55 nanard Exp $ */
+/* MiniUPnP project
+ * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
+ * (c) 2011-2015 Thomas Bernard
+ * This software is subject to the conditions detailed
+ * in the LICENCE file provided within the distribution */
+#ifndef PORTLISTINGPARSE_H_INCLUDED
+#define PORTLISTINGPARSE_H_INCLUDED
+
+#include "miniupnpc_declspec.h"
+/* for the definition of UNSIGNED_INTEGER */
+#include "miniupnpctypes.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* sample of PortMappingEntry :
+  <p:PortMappingEntry>
+    <p:NewRemoteHost>202.233.2.1</p:NewRemoteHost>
+    <p:NewExternalPort>2345</p:NewExternalPort>
+    <p:NewProtocol>TCP</p:NewProtocol>
+    <p:NewInternalPort>2345</p:NewInternalPort>
+    <p:NewInternalClient>192.168.1.137</p:NewInternalClient>
+    <p:NewEnabled>1</p:NewEnabled>
+    <p:NewDescription>dooom</p:NewDescription>
+    <p:NewLeaseTime>345</p:NewLeaseTime>
+  </p:PortMappingEntry>
+ */
+typedef enum { PortMappingEltNone,
+       PortMappingEntry, NewRemoteHost,
+       NewExternalPort, NewProtocol,
+       NewInternalPort, NewInternalClient,
+       NewEnabled, NewDescription,
+       NewLeaseTime } portMappingElt;
+
+struct PortMapping {
+	struct PortMapping * l_next;	/* list next element */
+	UNSIGNED_INTEGER leaseTime;
+	unsigned short externalPort;
+	unsigned short internalPort;
+	char remoteHost[64];
+	char internalClient[64];
+	char description[64];
+	char protocol[4];
+	unsigned char enabled;
+};
+
+struct PortMappingParserData {
+	struct PortMapping * l_head;	/* list head */
+	portMappingElt curelt;
+};
+
+MINIUPNP_LIBSPEC void
+ParsePortListing(const char * buffer, int bufsize,
+                 struct PortMappingParserData * pdata);
+
+MINIUPNP_LIBSPEC void
+FreePortListing(struct PortMappingParserData * pdata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/ext/bin/miniupnpc/include/miniupnpc/receivedata.h b/ext/bin/miniupnpc/include/miniupnpc/receivedata.h
new file mode 100644
index 000000000..0520a11d3
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/receivedata.h
@@ -0,0 +1,19 @@
+/* $Id: receivedata.h,v 1.4 2012/09/27 15:42:10 nanard Exp $ */
+/* Project: miniupnp
+ * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
+ * Author: Thomas Bernard
+ * Copyright (c) 2011-2012 Thomas Bernard
+ * This software is subjects to the conditions detailed
+ * in the LICENCE file provided within this distribution */
+#ifndef RECEIVEDATA_H_INCLUDED
+#define RECEIVEDATA_H_INCLUDED
+
+/* Reads data from the specified socket.
+ * Returns the number of bytes read if successful, zero if no bytes were
+ * read or if we timed out. Returns negative if there was an error. */
+int receivedata(int socket,
+                char * data, int length,
+                int timeout, unsigned int * scope_id);
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/upnpcommands.h b/ext/bin/miniupnpc/include/miniupnpc/upnpcommands.h
new file mode 100644
index 000000000..22eda5e39
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/upnpcommands.h
@@ -0,0 +1,348 @@
+/* $Id: upnpcommands.h,v 1.31 2015/07/21 13:16:55 nanard Exp $ */
+/* Miniupnp project : http://miniupnp.free.fr/
+ * Author : Thomas Bernard
+ * Copyright (c) 2005-2015 Thomas Bernard
+ * This software is subject to the conditions detailed in the
+ * LICENCE file provided within this distribution */
+#ifndef UPNPCOMMANDS_H_INCLUDED
+#define UPNPCOMMANDS_H_INCLUDED
+
+#include "upnpreplyparse.h"
+#include "portlistingparse.h"
+#include "miniupnpc_declspec.h"
+#include "miniupnpctypes.h"
+
+/* MiniUPnPc return codes : */
+#define UPNPCOMMAND_SUCCESS (0)
+#define UPNPCOMMAND_UNKNOWN_ERROR (-1)
+#define UPNPCOMMAND_INVALID_ARGS (-2)
+#define UPNPCOMMAND_HTTP_ERROR (-3)
+#define UPNPCOMMAND_INVALID_RESPONSE (-4)
+#define UPNPCOMMAND_MEM_ALLOC_ERROR (-5)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+MINIUPNP_LIBSPEC UNSIGNED_INTEGER
+UPNP_GetTotalBytesSent(const char * controlURL,
+					const char * servicetype);
+
+MINIUPNP_LIBSPEC UNSIGNED_INTEGER
+UPNP_GetTotalBytesReceived(const char * controlURL,
+						const char * servicetype);
+
+MINIUPNP_LIBSPEC UNSIGNED_INTEGER
+UPNP_GetTotalPacketsSent(const char * controlURL,
+					const char * servicetype);
+
+MINIUPNP_LIBSPEC UNSIGNED_INTEGER
+UPNP_GetTotalPacketsReceived(const char * controlURL,
+					const char * servicetype);
+
+/* UPNP_GetStatusInfo()
+ * status and lastconnerror are 64 byte buffers
+ * Return values :
+ * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR
+ * or a UPnP Error code */
+MINIUPNP_LIBSPEC int
+UPNP_GetStatusInfo(const char * controlURL,
+			       const char * servicetype,
+				   char * status,
+				   unsigned int * uptime,
+                   char * lastconnerror);
+
+/* UPNP_GetConnectionTypeInfo()
+ * argument connectionType is a 64 character buffer
+ * Return Values :
+ * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR
+ * or a UPnP Error code */
+MINIUPNP_LIBSPEC int
+UPNP_GetConnectionTypeInfo(const char * controlURL,
+                           const char * servicetype,
+						   char * connectionType);
+
+/* UPNP_GetExternalIPAddress() call the corresponding UPNP method.
+ * if the third arg is not null the value is copied to it.
+ * at least 16 bytes must be available
+ *
+ * Return values :
+ * 0 : SUCCESS
+ * NON ZERO : ERROR Either an UPnP error code or an unknown error.
+ *
+ * possible UPnP Errors :
+ * 402 Invalid Args - See UPnP Device Architecture section on Control.
+ * 501 Action Failed - See UPnP Device Architecture section on Control. */
+MINIUPNP_LIBSPEC int
+UPNP_GetExternalIPAddress(const char * controlURL,
+                          const char * servicetype,
+                          char * extIpAdd);
+
+/* UPNP_GetLinkLayerMaxBitRates()
+ * call WANCommonInterfaceConfig:1#GetCommonLinkProperties
+ *
+ * return values :
+ * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR
+ * or a UPnP Error Code. */
+MINIUPNP_LIBSPEC int
+UPNP_GetLinkLayerMaxBitRates(const char* controlURL,
+							const char* servicetype,
+							unsigned int * bitrateDown,
+							unsigned int * bitrateUp);
+
+/* UPNP_AddPortMapping()
+ * if desc is NULL, it will be defaulted to "libminiupnpc"
+ * remoteHost is usually NULL because IGD don't support it.
+ *
+ * Return values :
+ * 0 : SUCCESS
+ * NON ZERO : ERROR. Either an UPnP error code or an unknown error.
+ *
+ * List of possible UPnP errors for AddPortMapping :
+ * errorCode errorDescription (short) - Description (long)
+ * 402 Invalid Args - See UPnP Device Architecture section on Control.
+ * 501 Action Failed - See UPnP Device Architecture section on Control.
+ * 606 Action not authorized - The action requested REQUIRES authorization and
+ *                             the sender was not authorized.
+ * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be
+ *                                   wild-carded
+ * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded
+ * 718 ConflictInMappingEntry - The port mapping entry specified conflicts
+ *                     with a mapping assigned previously to another client
+ * 724 SamePortValuesRequired - Internal and External port values
+ *                              must be the same
+ * 725 OnlyPermanentLeasesSupported - The NAT implementation only supports
+ *                  permanent lease times on port mappings
+ * 726 RemoteHostOnlySupportsWildcard - RemoteHost must be a wildcard
+ *                             and cannot be a specific IP address or DNS name
+ * 727 ExternalPortOnlySupportsWildcard - ExternalPort must be a wildcard and
+ *                                        cannot be a specific port value
+ * 728 NoPortMapsAvailable - There are not enough free ports available to
+ *                           complete port mapping.
+ * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed
+ *                                   due to conflict with other mechanisms.
+ * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded
+ */
+MINIUPNP_LIBSPEC int
+UPNP_AddPortMapping(const char * controlURL, const char * servicetype,
+		    const char * extPort,
+		    const char * inPort,
+		    const char * inClient,
+		    const char * desc,
+		    const char * proto,
+		    const char * remoteHost,
+		    const char * leaseDuration);
+
+/* UPNP_AddAnyPortMapping()
+ * if desc is NULL, it will be defaulted to "libminiupnpc"
+ * remoteHost is usually NULL because IGD don't support it.
+ *
+ * Return values :
+ * 0 : SUCCESS
+ * NON ZERO : ERROR. Either an UPnP error code or an unknown error.
+ *
+ * List of possible UPnP errors for AddPortMapping :
+ * errorCode errorDescription (short) - Description (long)
+ * 402 Invalid Args - See UPnP Device Architecture section on Control.
+ * 501 Action Failed - See UPnP Device Architecture section on Control.
+ * 606 Action not authorized - The action requested REQUIRES authorization and
+ *                             the sender was not authorized.
+ * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be
+ *                                   wild-carded
+ * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded
+ * 728 NoPortMapsAvailable - There are not enough free ports available to
+ *                           complete port mapping.
+ * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed
+ *                                   due to conflict with other mechanisms.
+ * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded
+ */
+MINIUPNP_LIBSPEC int
+UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype,
+		       const char * extPort,
+		       const char * inPort,
+		       const char * inClient,
+		       const char * desc,
+		       const char * proto,
+		       const char * remoteHost,
+		       const char * leaseDuration,
+		       char * reservedPort);
+
+/* UPNP_DeletePortMapping()
+ * Use same argument values as what was used for AddPortMapping().
+ * remoteHost is usually NULL because IGD don't support it.
+ * Return Values :
+ * 0 : SUCCESS
+ * NON ZERO : error. Either an UPnP error code or an undefined error.
+ *
+ * List of possible UPnP errors for DeletePortMapping :
+ * 402 Invalid Args - See UPnP Device Architecture section on Control.
+ * 606 Action not authorized - The action requested REQUIRES authorization
+ *                             and the sender was not authorized.
+ * 714 NoSuchEntryInArray - The specified value does not exist in the array */
+MINIUPNP_LIBSPEC int
+UPNP_DeletePortMapping(const char * controlURL, const char * servicetype,
+		       const char * extPort, const char * proto,
+		       const char * remoteHost);
+
+/* UPNP_DeletePortRangeMapping()
+ * Use same argument values as what was used for AddPortMapping().
+ * remoteHost is usually NULL because IGD don't support it.
+ * Return Values :
+ * 0 : SUCCESS
+ * NON ZERO : error. Either an UPnP error code or an undefined error.
+ *
+ * List of possible UPnP errors for DeletePortMapping :
+ * 606 Action not authorized - The action requested REQUIRES authorization
+ *                             and the sender was not authorized.
+ * 730 PortMappingNotFound - This error message is returned if no port
+ *			     mapping is found in the specified range.
+ * 733 InconsistentParameters - NewStartPort and NewEndPort values are not consistent. */
+MINIUPNP_LIBSPEC int
+UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype,
+        		    const char * extPortStart, const char * extPortEnd,
+        		    const char * proto,
+        		    const char * manage);
+
+/* UPNP_GetPortMappingNumberOfEntries()
+ * not supported by all routers */
+MINIUPNP_LIBSPEC int
+UPNP_GetPortMappingNumberOfEntries(const char* controlURL,
+                                   const char* servicetype,
+                                   unsigned int * num);
+
+/* UPNP_GetSpecificPortMappingEntry()
+ *    retrieves an existing port mapping
+ * params :
+ *  in   extPort
+ *  in   proto
+ *  in   remoteHost
+ *  out  intClient (16 bytes)
+ *  out  intPort (6 bytes)
+ *  out  desc (80 bytes)
+ *  out  enabled (4 bytes)
+ *  out  leaseDuration (16 bytes)
+ *
+ * return value :
+ * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR
+ * or a UPnP Error Code.
+ *
+ * List of possible UPnP errors for _GetSpecificPortMappingEntry :
+ * 402 Invalid Args - See UPnP Device Architecture section on Control.
+ * 501 Action Failed - See UPnP Device Architecture section on Control.
+ * 606 Action not authorized - The action requested REQUIRES authorization
+ *                             and the sender was not authorized.
+ * 714 NoSuchEntryInArray - The specified value does not exist in the array.
+ */
+MINIUPNP_LIBSPEC int
+UPNP_GetSpecificPortMappingEntry(const char * controlURL,
+                                 const char * servicetype,
+                                 const char * extPort,
+                                 const char * proto,
+                                 const char * remoteHost,
+                                 char * intClient,
+                                 char * intPort,
+                                 char * desc,
+                                 char * enabled,
+                                 char * leaseDuration);
+
+/* UPNP_GetGenericPortMappingEntry()
+ * params :
+ *  in   index
+ *  out  extPort (6 bytes)
+ *  out  intClient (16 bytes)
+ *  out  intPort (6 bytes)
+ *  out  protocol (4 bytes)
+ *  out  desc (80 bytes)
+ *  out  enabled (4 bytes)
+ *  out  rHost (64 bytes)
+ *  out  duration (16 bytes)
+ *
+ * return value :
+ * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR
+ * or a UPnP Error Code.
+ *
+ * Possible UPNP Error codes :
+ * 402 Invalid Args - See UPnP Device Architecture section on Control.
+ * 606 Action not authorized - The action requested REQUIRES authorization
+ *                             and the sender was not authorized.
+ * 713 SpecifiedArrayIndexInvalid - The specified array index is out of bounds
+ */
+MINIUPNP_LIBSPEC int
+UPNP_GetGenericPortMappingEntry(const char * controlURL,
+                                const char * servicetype,
+								const char * index,
+								char * extPort,
+								char * intClient,
+								char * intPort,
+								char * protocol,
+								char * desc,
+								char * enabled,
+								char * rHost,
+								char * duration);
+
+/* UPNP_GetListOfPortMappings()      Available in IGD v2
+ *
+ *
+ * Possible UPNP Error codes :
+ * 606 Action not Authorized
+ * 730 PortMappingNotFound - no port mapping is found in the specified range.
+ * 733 InconsistantParameters - NewStartPort and NewEndPort values are not
+ *                              consistent.
+ */
+MINIUPNP_LIBSPEC int
+UPNP_GetListOfPortMappings(const char * controlURL,
+                           const char * servicetype,
+                           const char * startPort,
+                           const char * endPort,
+                           const char * protocol,
+                           const char * numberOfPorts,
+                           struct PortMappingParserData * data);
+
+/* IGD:2, functions for service WANIPv6FirewallControl:1 */
+MINIUPNP_LIBSPEC int
+UPNP_GetFirewallStatus(const char * controlURL,
+				const char * servicetype,
+				int * firewallEnabled,
+				int * inboundPinholeAllowed);
+
+MINIUPNP_LIBSPEC int
+UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype,
+                    const char * remoteHost,
+                    const char * remotePort,
+                    const char * intClient,
+                    const char * intPort,
+                    const char * proto,
+                    int * opTimeout);
+
+MINIUPNP_LIBSPEC int
+UPNP_AddPinhole(const char * controlURL, const char * servicetype,
+                    const char * remoteHost,
+                    const char * remotePort,
+                    const char * intClient,
+                    const char * intPort,
+                    const char * proto,
+                    const char * leaseTime,
+                    char * uniqueID);
+
+MINIUPNP_LIBSPEC int
+UPNP_UpdatePinhole(const char * controlURL, const char * servicetype,
+                    const char * uniqueID,
+                    const char * leaseTime);
+
+MINIUPNP_LIBSPEC int
+UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID);
+
+MINIUPNP_LIBSPEC int
+UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype,
+                                 const char * uniqueID, int * isWorking);
+
+MINIUPNP_LIBSPEC int
+UPNP_GetPinholePackets(const char * controlURL, const char * servicetype,
+                                 const char * uniqueID, int * packets);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/ext/bin/miniupnpc/include/miniupnpc/upnperrors.h b/ext/bin/miniupnpc/include/miniupnpc/upnperrors.h
new file mode 100644
index 000000000..3115aee5b
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/upnperrors.h
@@ -0,0 +1,26 @@
+/* $Id: upnperrors.h,v 1.6 2015/07/21 13:16:55 nanard Exp $ */
+/* (c) 2007-2015 Thomas Bernard
+ * All rights reserved.
+ * MiniUPnP Project.
+ * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
+ * This software is subjet to the conditions detailed in the
+ * provided LICENCE file. */
+#ifndef UPNPERRORS_H_INCLUDED
+#define UPNPERRORS_H_INCLUDED
+
+#include "miniupnpc_declspec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* strupnperror()
+ * Return a string description of the UPnP error code
+ * or NULL for undefinded errors */
+MINIUPNP_LIBSPEC const char * strupnperror(int err);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/ext/bin/miniupnpc/include/miniupnpc/upnpreplyparse.h b/ext/bin/miniupnpc/include/miniupnpc/upnpreplyparse.h
new file mode 100644
index 000000000..6badd15b2
--- /dev/null
+++ b/ext/bin/miniupnpc/include/miniupnpc/upnpreplyparse.h
@@ -0,0 +1,63 @@
+/* $Id: upnpreplyparse.h,v 1.19 2014/10/27 16:33:19 nanard Exp $ */
+/* MiniUPnP project
+ * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
+ * (c) 2006-2013 Thomas Bernard
+ * This software is subject to the conditions detailed
+ * in the LICENCE file provided within the distribution */
+
+#ifndef UPNPREPLYPARSE_H_INCLUDED
+#define UPNPREPLYPARSE_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct NameValue {
+	struct NameValue * l_next;
+	char name[64];
+	char value[128];
+};
+
+struct NameValueParserData {
+	struct NameValue * l_head;
+	char curelt[64];
+	char * portListing;
+	int portListingLength;
+	int topelt;
+	const char * cdata;
+	int cdatalen;
+};
+
+/* ParseNameValue() */
+void
+ParseNameValue(const char * buffer, int bufsize,
+               struct NameValueParserData * data);
+
+/* ClearNameValueList() */
+void
+ClearNameValueList(struct NameValueParserData * pdata);
+
+/* GetValueFromNameValueList() */
+char *
+GetValueFromNameValueList(struct NameValueParserData * pdata,
+                          const char * Name);
+
+#if 0
+/* GetValueFromNameValueListIgnoreNS() */
+char *
+GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata,
+                                  const char * Name);
+#endif
+
+/* DisplayNameValueList() */
+#ifdef DEBUG
+void
+DisplayNameValueList(char * buffer, int bufsize);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/ext/bin/miniupnpc/linux-arm32/libminiupnpc.a b/ext/bin/miniupnpc/linux-arm32/libminiupnpc.a
new file mode 100644
index 000000000..4983f6280
Binary files /dev/null and b/ext/bin/miniupnpc/linux-arm32/libminiupnpc.a differ
diff --git a/ext/bin/miniupnpc/linux-x64/libminiupnpc.a b/ext/bin/miniupnpc/linux-x64/libminiupnpc.a
new file mode 100644
index 000000000..270366e03
Binary files /dev/null and b/ext/bin/miniupnpc/linux-x64/libminiupnpc.a differ
diff --git a/ext/bin/miniupnpc/linux-x86/libminiupnpc.a b/ext/bin/miniupnpc/linux-x86/libminiupnpc.a
new file mode 100644
index 000000000..99cbef212
Binary files /dev/null and b/ext/bin/miniupnpc/linux-x86/libminiupnpc.a differ
diff --git a/ext/bin/miniupnpc/mac-x64/libminiupnpc.a b/ext/bin/miniupnpc/mac-x64/libminiupnpc.a
new file mode 100644
index 000000000..3c2e528d1
Binary files /dev/null and b/ext/bin/miniupnpc/mac-x64/libminiupnpc.a differ
diff --git a/ext/bin/miniupnpc/windows-x64/miniupnpc.lib b/ext/bin/miniupnpc/windows-x64/miniupnpc.lib
new file mode 100644
index 000000000..e05fefc88
Binary files /dev/null and b/ext/bin/miniupnpc/windows-x64/miniupnpc.lib differ
diff --git a/ext/bin/miniupnpc/windows-x86/miniupnpc.lib b/ext/bin/miniupnpc/windows-x86/miniupnpc.lib
new file mode 100644
index 000000000..a7fe41918
Binary files /dev/null and b/ext/bin/miniupnpc/windows-x86/miniupnpc.lib differ
diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip
index ff7ee2190..617556988 100644
--- a/ext/installfiles/windows/ZeroTier One.aip	
+++ b/ext/installfiles/windows/ZeroTier One.aip	
@@ -23,7 +23,7 @@
     <ROW Property="CTRLS" Value="2"/>
     <ROW Property="MSIFASTINSTALL" MultiBuildValue="DefaultBuild:2"/>
     <ROW Property="Manufacturer" Value="ZeroTier, Inc."/>
-    <ROW Property="ProductCode" Value="1033:{CEB9F664-D7D0-446A-BAB0-CDC553ADC137} " Type="16"/>
+    <ROW Property="ProductCode" Value="1033:{C25B45D0-A473-44C1-A251-B72ED4E9C9FC} " Type="16"/>
     <ROW Property="ProductLanguage" Value="1033"/>
     <ROW Property="ProductName" Value="ZeroTier One"/>
     <ROW Property="ProductVersion" Value="1.0.3" Type="32"/>
@@ -87,8 +87,8 @@
     <ROW File="react.min.js" Component_="index.html" FileName="REACTM~1.JS|react.min.js" Attributes="0" SourcePath="..\..\..\ui\react.min.js" SelfReg="false" NextFile="simpleajax.min.js"/>
     <ROW File="simpleajax.min.js" Component_="index.html" FileName="SIMPLE~1.JS|simpleajax.min.js" Attributes="0" SourcePath="..\..\..\ui\simpleajax.min.js" SelfReg="false" NextFile="zerotier.css"/>
     <ROW File="zerotier.css" Component_="index.html" FileName="zerotier.css" Attributes="0" SourcePath="..\..\..\ui\zerotier.css" SelfReg="false" NextFile="ztui.min.js"/>
-    <ROW File="zerotierone_x64.exe" Component_="zerotierone_x64.exe" FileName="ZEROTI~2.EXE|zerotier-one_x64.exe" Attributes="0" SourcePath="..\..\..\windows\Build\x64\Release\zerotier-one_x64.exe" SelfReg="false" NextFile="ZeroTierOne.exe" DigSign="true"/>
-    <ROW File="zerotierone_x86.exe" Component_="zerotierone_x86.exe" FileName="ZEROTI~1.EXE|zerotier-one_x86.exe" Attributes="0" SourcePath="..\..\..\windows\Build\Win32\Release\zerotier-one_x86.exe" SelfReg="false" NextFile="zerotierone_x64.exe" DigSign="true"/>
+    <ROW File="zerotierone_x64.exe" Component_="zerotierone_x64.exe" FileName="ZEROTI~2.EXE|zerotier-one_x64.exe" Version="65535.65535.65535.65535" Attributes="0" SourcePath="..\..\..\windows\Build\x64\Release\zerotier-one_x64.exe" SelfReg="false" NextFile="ZeroTierOne.exe" DigSign="true"/>
+    <ROW File="zerotierone_x86.exe" Component_="zerotierone_x86.exe" FileName="ZEROTI~1.EXE|zerotier-one_x86.exe" Version="65535.65535.65535.65535" Attributes="0" SourcePath="..\..\..\windows\Build\Win32\Release\zerotier-one_x86.exe" SelfReg="false" NextFile="zerotierone_x64.exe" DigSign="true"/>
     <ROW File="zttap300.cat_2" Component_="zttap300.cat" FileName="zttap300.cat" Attributes="0" SourcePath="..\..\bin\tap-windows-ndis6\x64\zttap300.cat" SelfReg="false" NextFile="zttap300.sys_2"/>
     <ROW File="zttap300.cat_3" Component_="zttap300.cat_1" FileName="zttap300.cat" Attributes="0" SourcePath="..\..\bin\tap-windows-ndis6\x86\zttap300.cat" SelfReg="false" NextFile="zttap300.sys_3"/>
     <ROW File="zttap300.inf" Component_="zttap300.cat" FileName="zttap300.inf" Attributes="0" SourcePath="..\..\bin\tap-windows-ndis6\x64\zttap300.inf" SelfReg="false" NextFile="zttap300.cat_3"/>
diff --git a/ext/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/MainMenu.xib b/ext/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/MainMenu.xib
index 61dafbcc9..dd67a86ae 100644
--- a/ext/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/MainMenu.xib
+++ b/ext/mac-ui-macgap1-wrapper/src/MacGap/en.lproj/MainMenu.xib
@@ -125,7 +125,7 @@
 								</object>
 								<object class="NSMenuItem" id="755159360">
 									<reference key="NSMenu" ref="110575045"/>
-									<string key="NSTitle">Hide MacGap</string>
+									<string key="NSTitle">Hide ZeroTier One</string>
 									<string key="NSKeyEquiv">h</string>
 									<int key="NSKeyEquivModMask">1048576</int>
 									<int key="NSMnemonicLoc">2147483647</int>
diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h
index 7ae524a85..8583aa3ad 100644
--- a/include/ZeroTierOne.h
+++ b/include/ZeroTierOne.h
@@ -105,17 +105,6 @@ extern "C" {
  */
 #define ZT1_MAX_PEER_NETWORK_PATHS 4
 
-/**
- * Maximum number of revisions over which a network COM can differ and still be in-horizon (agree)
- *
- * This is the default max delta for the revision field in COMs issued
- * by network controllers, and is defined here for documentation purposes.
- * When a network is changed so as to de-authorize a member, its revision
- * should be incremented by this number. Otherwise all other changes that
- * materially affect the network should result in increment by one.
- */
-#define ZT1_CERTIFICATE_OF_MEMBERSHIP_REVISION_MAX_DELTA 16
-
 /**
  * Feature flag: ZeroTier One was built to be thread-safe -- concurrent processXXX() calls are okay
  */
@@ -978,11 +967,15 @@ void ZT1_Node_freeQueryResult(ZT1_Node *node,void *qr);
  * Take care that these are never ZeroTier interface addresses, otherwise
  * strange things might happen or they simply won't work.
  *
+ * Addresses can also be added here if they are the result of a UPnP or
+ * NAT-PMP port mapping or other discovery or mapping means.
+ *
  * This returns a boolean indicating whether or not the address was
  * accepted. ZeroTier will only communicate over certain address types
  * and (for IP) address classes. Thus it's safe to just dump your OS's
  * entire remote IP list (excluding ZeroTier interface IPs) into here
- * and let ZeroTier determine which addresses it will use.
+ * and let ZeroTier determine which addresses it will use. It will
+ * reject bad, empty, and unusable addresses.
  *
  * @param addr Local interface address
  * @param metric Local interface metric
diff --git a/make-linux.mk b/make-linux.mk
index c56f35691..2ef9b9850 100644
--- a/make-linux.mk
+++ b/make-linux.mk
@@ -18,8 +18,16 @@
 #
 
 # Automagically pick clang or gcc, with preference for clang
-CC?=$(shell if [ -e /usr/bin/clang ]; then echo clang; else echo gcc; fi)
-CXX?=$(shell if [ -e /usr/bin/clang++ ]; then echo clang++; else echo g++; fi)
+# This is only done if we have not overridden these with an environment or CLI variable
+ifeq ($(origin CC),default)
+	CC=$(shell if [ -e /usr/bin/clang ]; then echo clang; else echo gcc; fi)
+endif
+ifeq ($(origin CXX),default)
+	CXX=$(shell if [ -e /usr/bin/clang++ ]; then echo clang++; else echo g++; fi)
+endif
+
+UNAME_M=$(shell uname -m)
+
 INCLUDES=
 DEFS=
 LDLIBS?=
@@ -30,6 +38,29 @@ OBJS+=osdep/LinuxEthernetTap.o
 # "make official" is a shortcut for this
 ifeq ($(ZT_OFFICIAL_RELEASE),1)
 	DEFS+=-DZT_OFFICIAL_RELEASE 
+	ZT_USE_MINIUPNPC=1
+endif
+
+ifeq ($(ZT_USE_MINIUPNPC),1)
+	DEFS+=-DZT_USE_MINIUPNPC
+ifeq ($(UNAME_M),armv6l)
+	MINIUPNPC_LIB=ext/bin/miniupnpc/linux-arm32/libminiupnpc.a
+endif
+ifeq ($(UNAME_M),armv7l)
+	MINIUPNPC_LIB=ext/bin/miniupnpc/linux-arm32/libminiupnpc.a
+endif
+ifeq ($(UNAME_M),x86_64)
+	MINIUPNPC_LIB=ext/bin/miniupnpc/linux-x64/libminiupnpc.a
+endif
+ifeq ($(UNAME_M),i386)
+	MINIUPNPC_LIB=ext/bin/miniupnpc/linux-x86/libminiupnpc.a
+endif
+ifeq ($(UNAME_M),i686)
+	MINIUPNPC_LIB=ext/bin/miniupnpc/linux-x86/libminiupnpc.a
+endif
+	MINIUPNPC_LIB?=-lminiupnpc
+	LDLIBS+=$(MINIUPNPC_LIB)
+	OBJS+=osdep/UPNPClient.o
 endif
 
 # Build with ZT_ENABLE_NETWORK_CONTROLLER=1 to build with the Sqlite network controller
diff --git a/make-mac.mk b/make-mac.mk
index 1bc842cee..c0b3f89d1 100644
--- a/make-mac.mk
+++ b/make-mac.mk
@@ -1,13 +1,20 @@
-CC?=clang
-CXX?=clang++
+ifeq ($(origin CC),default)
+	CC=$(shell if [ -e /usr/bin/clang ]; then echo clang; else echo gcc; fi)
+endif
+ifeq ($(origin CXX),default)
+	CXX=$(shell if [ -e /usr/bin/clang++ ]; then echo clang++; else echo g++; fi)
+endif
 
-INCLUDES=-I/usr/local/include
+INCLUDES=
 DEFS=
 LIBS=
 ARCH_FLAGS=-arch x86_64
 
 include objects.mk
-OBJS+=osdep/OSXEthernetTap.o 
+OBJS+=osdep/OSXEthernetTap.o
+
+# Comment out to disable building against shipped libminiupnpc binary for Mac
+ZT_USE_MINIUPNPC?=1
 
 # Disable codesign since open source users will not have ZeroTier's certs
 CODESIGN=echo
@@ -17,7 +24,8 @@ CODESIGN_INSTALLER_CERT=
 
 # For internal use only -- signs everything with ZeroTier's developer cert
 ifeq ($(ZT_OFFICIAL_RELEASE),1)
-	DEFS+=-DZT_OFFICIAL_RELEASE -DZT_AUTO_UPDATE 
+	DEFS+=-DZT_OFFICIAL_RELEASE -DZT_AUTO_UPDATE
+	ZT_USE_MINIUPNPC=1
 	CODESIGN=codesign
 	PRODUCTSIGN=productsign
 	CODESIGN_APP_CERT="Developer ID Application: ZeroTier Networks LLC (8ZD9JUCZ4V)"
@@ -25,19 +33,25 @@ ifeq ($(ZT_OFFICIAL_RELEASE),1)
 endif
 
 ifeq ($(ZT_AUTO_UPDATE),1)
-	DEFS+=-DZT_AUTO_UPDATE 
+	DEFS+=-DZT_AUTO_UPDATE
+endif
+
+ifeq ($(ZT_USE_MINIUPNPC),1)
+	DEFS+=-DZT_USE_MINIUPNPC
+	LIBS+=ext/bin/miniupnpc/mac-x64/libminiupnpc.a
+	OBJS+=osdep/UPNPClient.o
 endif
 
 # Build with ZT_ENABLE_NETWORK_CONTROLLER=1 to build with the Sqlite network controller
 ifeq ($(ZT_ENABLE_NETWORK_CONTROLLER),1)
-	DEFS+=-DZT_ENABLE_NETWORK_CONTROLLER 
+	DEFS+=-DZT_ENABLE_NETWORK_CONTROLLER
 	LIBS+=-L/usr/local/lib -lsqlite3
-	OBJS+=controller/SqliteNetworkController.o 
+	OBJS+=controller/SqliteNetworkController.o
 endif
 
 # Debug mode -- dump trace output, build binary with -g
 ifeq ($(ZT_DEBUG),1)
-	DEFS+=-DZT_TRACE 
+	DEFS+=-DZT_TRACE
 	CFLAGS+=-Wall -g -pthread $(INCLUDES) $(DEFS)
 	STRIP=echo
 	# The following line enables optimization for the crypto code, since
diff --git a/node/Constants.hpp b/node/Constants.hpp
index d15fef13e..b7aa98170 100644
--- a/node/Constants.hpp
+++ b/node/Constants.hpp
@@ -60,6 +60,13 @@
 #include <endian.h>
 #endif
 
+// Disable type punning on ARM architecture -- some ARM chips throw SIGBUS on unaligned access
+#if defined(__arm__) || defined(__ARMEL__)
+#ifndef ZT_NO_TYPE_PUNNING
+#define ZT_NO_TYPE_PUNNING
+#endif
+#endif
+
 #if defined(__FreeBSD__) || defined(__OpenBSD__)
 #ifndef __UNIX_LIKE__
 #define __UNIX_LIKE__
@@ -158,7 +165,7 @@
 
 /**
  * Maximum number of packet fragments we'll support
- * 
+ *
  * The actual spec allows 16, but this is the most we'll support right
  * now. Packets with more than this many fragments are dropped.
  */
@@ -216,7 +223,7 @@
 
 /**
  * Maximum number of ZT hops allowed (this is not IP hops/TTL)
- * 
+ *
  * The protocol allows up to 7, but we limit it to something smaller.
  */
 #define ZT_RELAY_MAX_HOPS 3
@@ -246,10 +253,10 @@
 /**
  * How frequently to send a zero-byte UDP keepalive packet
  *
- * There are NATs with timeouts as short as 30 seconds, so this turns out
+ * There are NATs with timeouts as short as 20 seconds, so this turns out
  * to be needed.
  */
-#define ZT_NAT_KEEPALIVE_DELAY 25000
+#define ZT_NAT_KEEPALIVE_DELAY 19000
 
 /**
  * Delay between scans of the topology active peer DB for peers that need ping
@@ -296,6 +303,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 ae99352e9..b1fda8ef8 100644
--- a/node/IncomingPacket.cpp
+++ b/node/IncomingPacket.cpp
@@ -392,28 +392,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
 					const unsigned int dictlen = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN);
 					const std::string dict((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,dictlen),dictlen);
 					if (dict.length()) {
-						if (nw->setConfiguration(Dictionary(dict)) == 2) { // 2 == accepted and actually new
-							/* If this configuration was indeed new, we do another
-							 * controller request with its revision. We do this in
-							 * order to (a) tell the network controller we got it (it
-							 * won't send a duplicate if ts == current), and (b)
-							 * get another one if the controller is changing rapidly
-							 * until we finally have the final version.
-							 *
-							 * Note that we don't do this for network controllers with
-							 * versions <= 1.0.3, since those regenerate a new controller
-							 * with a new revision every time. In that case this double
-							 * confirmation would create a race condition. */
-							const SharedPtr<NetworkConfig> nc(nw->config2());
-							if ((peer->atLeastVersion(1,0,3))&&(nc)&&(nc->revision() > 0)) {
-								Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_CONFIG_REQUEST);
-								outp.append((uint64_t)nw->id());
-								outp.append((uint16_t)0); // no meta-data
-								outp.append((uint64_t)nc->revision());
-								outp.armor(peer->key(),true);
-								RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
-							}
-						}
+						nw->setConfiguration(Dictionary(dict));
 						TRACE("got network configuration for network %.16llx from %s",(unsigned long long)nw->id(),source().toString().c_str());
 					}
 				}
@@ -509,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());
 			}
@@ -692,6 +671,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 		if (RR->localNetworkController) {
 			Dictionary netconf;
 			switch(RR->localNetworkController->doNetworkConfigRequest((h > 0) ? InetAddress() : _remoteAddress,RR->identity,peer->identity(),nwid,metaData,haveRevision,netconf)) {
+
 				case NetworkController::NETCONF_QUERY_OK: {
 					const std::string netconfStr(netconf.toString());
 					if (netconfStr.length() > 0xffff) { // sanity check since field ix 16-bit
@@ -712,8 +692,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 						}
 					}
 				}	break;
-				case NetworkController::NETCONF_QUERY_OK_BUT_NOT_NEWER: // nothing to do -- netconf has not changed
-					break;
+
 				case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: {
 					Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
 					outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
@@ -723,6 +702,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 					outp.armor(peer->key(),true);
 					RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
 				}	break;
+
 				case NetworkController::NETCONF_QUERY_ACCESS_DENIED: {
 					Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
 					outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
@@ -732,12 +712,18 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 					outp.armor(peer->key(),true);
 					RR->node->putPacket(_remoteAddress,outp.data(),outp.size());
 				} break;
+
 				case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR:
 					TRACE("NETWORK_CONFIG_REQUEST failed: internal error: %s",netconf.get("error","(unknown)").c_str());
 					break;
+
+				case NetworkController::NETCONF_QUERY_IGNORE:
+					break;
+
 				default:
 					TRACE("NETWORK_CONFIG_REQUEST failed: invalid return value from NetworkController::doNetworkConfigRequest()");
 					break;
+
 			}
 		} else {
 			Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp
index 91bfbed6c..1942c4cd4 100644
--- a/node/InetAddress.cpp
+++ b/node/InetAddress.cpp
@@ -74,7 +74,7 @@ InetAddress::IpScope InetAddress::ipScope() const
 					if ((ip & 0xfff00000) == 0xac100000) return IP_SCOPE_PRIVATE;       // 172.16.0.0/12
 					break;
 				case 0xc0:
-					if ((ip & 0xffff0000) == 0xc9a80000) return IP_SCOPE_PRIVATE;				// 192.168.0.0/16
+					if ((ip & 0xffff0000) == 0xc0a80000) return IP_SCOPE_PRIVATE;				// 192.168.0.0/16
 					break;
 				case 0xff: return IP_SCOPE_NONE;                                      // 255.0.0.0/8 (broadcast, or unused/unusable)
 				default:
diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp
index 33424e4a1..489c170b4 100644
--- a/node/Multicaster.cpp
+++ b/node/Multicaster.cpp
@@ -211,9 +211,11 @@ void Multicaster::send(
 		unsigned int count = 0;
 
 		for(std::vector<Address>::const_iterator ast(alwaysSendTo.begin());ast!=alwaysSendTo.end();++ast) {
-			out.sendOnly(RR,*ast);
-			if (++count >= limit)
-				break;
+			if (*ast != RR->identity.address()) {
+				out.sendOnly(RR,*ast);
+				if (++count >= limit)
+					break;
+			}
 		}
 
 		unsigned long idx = 0;
@@ -264,9 +266,11 @@ void Multicaster::send(
 		unsigned int count = 0;
 
 		for(std::vector<Address>::const_iterator ast(alwaysSendTo.begin());ast!=alwaysSendTo.end();++ast) {
-			out.sendAndLog(RR,*ast);
-			if (++count >= limit)
-				break;
+			if (*ast != RR->identity.address()) {
+				out.sendAndLog(RR,*ast);
+				if (++count >= limit)
+					break;
+			}
 		}
 
 		unsigned long idx = 0;
diff --git a/node/Network.cpp b/node/Network.cpp
index adc8e1b8c..549219d74 100644
--- a/node/Network.cpp
+++ b/node/Network.cpp
@@ -38,6 +38,8 @@
 #include "Buffer.hpp"
 #include "NetworkController.hpp"
 
+#include "../version.h"
+
 namespace ZeroTier {
 
 const ZeroTier::MulticastGroup Network::BROADCAST(ZeroTier::MAC(0xffffffffffffULL),0);
@@ -255,9 +257,18 @@ void Network::requestConfiguration()
 	}
 
 	TRACE("requesting netconf for network %.16llx from controller %s",(unsigned long long)_id,controller().toString().c_str());
+
+	// TODO: in the future we will include things like join tokens here, etc.
+	Dictionary metaData;
+	metaData.setHex(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,ZEROTIER_ONE_VERSION_MAJOR);
+	metaData.setHex(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,ZEROTIER_ONE_VERSION_MINOR);
+	metaData.setHex(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,ZEROTIER_ONE_VERSION_REVISION);
+	std::string mds(metaData.toString());
+
 	Packet outp(controller(),RR->identity.address(),Packet::VERB_NETWORK_CONFIG_REQUEST);
 	outp.append((uint64_t)_id);
-	outp.append((uint16_t)0); // no meta-data
+	outp.append((uint16_t)mds.length());
+	outp.append((const void *)mds.data(),(unsigned int)mds.length());
 	{
 		Mutex::Lock _l(_lock);
 		if (_config)
diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp
index ba4d338bb..7898646cf 100644
--- a/node/NetworkConfig.cpp
+++ b/node/NetworkConfig.cpp
@@ -47,7 +47,6 @@ SharedPtr<NetworkConfig> NetworkConfig::createTestNetworkConfig(const Address &s
 	nc->_private = false;
 	nc->_enableBroadcast = true;
 	nc->_name = "ZT_TEST_NETWORK";
-	nc->_description = "Built-in dummy test network";
 
 	// Make up a V4 IP from 'self' in the 10.0.0.0/8 range -- no
 	// guarantee of uniqueness but collisions are unlikely.
@@ -111,7 +110,6 @@ void NetworkConfig::_fromDictionary(const Dictionary &d)
 	_name = d.get(ZT_NETWORKCONFIG_DICT_KEY_NAME);
 	if (_name.length() > ZT1_MAX_NETWORK_SHORT_NAME_LENGTH)
 		throw std::invalid_argument("network short name too long (max: 255 characters)");
-	_description = d.get(ZT_NETWORKCONFIG_DICT_KEY_DESC,std::string());
 
 	// In dictionary IPs are split into V4 and V6 addresses, but we don't really
 	// need that so merge them here.
@@ -132,26 +130,22 @@ void NetworkConfig::_fromDictionary(const Dictionary &d)
 			case AF_INET:
 				if ((!addr.netmaskBits())||(addr.netmaskBits() > 32))
 					continue;
-				else if (addr.isNetwork()) {
-					// TODO: add route to network -- this is a route without an IP assignment
-					continue;
-				}
 				break;
 			case AF_INET6:
 				if ((!addr.netmaskBits())||(addr.netmaskBits() > 128))
 					continue;
-				else if (addr.isNetwork()) {
-					// TODO: add route to network -- this is a route without an IP assignment
-					continue;
-				}
 				break;
 			default: // ignore unrecognized address types or junk/empty fields
 				continue;
 		}
-		_staticIps.push_back(addr);
+		if (addr.isNetwork())
+			_localRoutes.push_back(addr);
+		else _staticIps.push_back(addr);
 	}
-	if (_staticIps.size() > ZT1_MAX_ZT_ASSIGNED_ADDRESSES)
-		throw std::invalid_argument("too many ZT-assigned IP addresses or routes");
+	if (_localRoutes.size() > ZT1_MAX_ZT_ASSIGNED_ADDRESSES) throw std::invalid_argument("too many ZT-assigned routes");
+	if (_staticIps.size() > ZT1_MAX_ZT_ASSIGNED_ADDRESSES) throw std::invalid_argument("too many ZT-assigned IP addresses");
+	std::sort(_localRoutes.begin(),_localRoutes.end());
+	_localRoutes.erase(std::unique(_localRoutes.begin(),_localRoutes.end()),_localRoutes.end());
 	std::sort(_staticIps.begin(),_staticIps.end());
 	_staticIps.erase(std::unique(_staticIps.begin(),_staticIps.end()),_staticIps.end());
 
@@ -201,7 +195,7 @@ bool NetworkConfig::operator==(const NetworkConfig &nc) const
 	if (_private != nc._private) return false;
 	if (_enableBroadcast != nc._enableBroadcast) return false;
 	if (_name != nc._name) return false;
-	if (_description != nc._description) return false;
+	if (_localRoutes != nc._localRoutes) return false;
 	if (_staticIps != nc._staticIps) return false;
 	if (_gateways != nc._gateways) return false;
 	if (_activeBridges != nc._activeBridges) return false;
@@ -211,4 +205,3 @@ bool NetworkConfig::operator==(const NetworkConfig &nc) const
 }
 
 } // namespace ZeroTier
-
diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp
index 5c7cdd7c0..6111e65ba 100644
--- a/node/NetworkConfig.hpp
+++ b/node/NetworkConfig.hpp
@@ -47,59 +47,48 @@
 
 namespace ZeroTier {
 
+// Fields for meta-data sent with network config requests
+#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION "majv"
+#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION "minv"
+#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION "revv"
+
 // These dictionary keys are short so they don't take up much room in
 // netconf response packets.
 
 // integer(hex)[,integer(hex),...]
 #define ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES "et"
-
 // network ID
 #define ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID "nwid"
-
 // integer(hex)
 #define ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP "ts"
-
 // integer(hex)
 #define ZT_NETWORKCONFIG_DICT_KEY_REVISION "r"
-
 // address of member
 #define ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO "id"
-
 // integer(hex)
 #define ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT "ml"
-
 // 0/1
 #define ZT_NETWORKCONFIG_DICT_KEY_PRIVATE "p"
-
 // text
 #define ZT_NETWORKCONFIG_DICT_KEY_NAME "n"
-
 // text
 #define ZT_NETWORKCONFIG_DICT_KEY_DESC "d"
-
 // IP/bits[,IP/bits,...]
 // Note that IPs that end in all zeroes are routes with no assignment in them.
 #define ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC "v4s"
-
 // IP/bits[,IP/bits,...]
 // Note that IPs that end in all zeroes are routes with no assignment in them.
 #define ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC "v6s"
-
 // serialized CertificateOfMembership
 #define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP "com"
-
 // 0/1
 #define ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST "eb"
-
 // 0/1
 #define ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING "pb"
-
 // node[,node,...]
 #define ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES "ab"
-
 // node;IP/port[,node;IP/port]
 #define ZT_NETWORKCONFIG_DICT_KEY_RELAYS "rl"
-
 // IP/metric[,IP/metric,...]
 #define ZT_NETWORKCONFIG_DICT_KEY_GATEWAYS "gw"
 
@@ -158,7 +147,7 @@ public:
 	inline bool isPublic() const throw() { return (!_private); }
 	inline bool isPrivate() const throw() { return _private; }
 	inline const std::string &name() const throw() { return _name; }
-	inline const std::string &description() const throw() { return _description; }
+	inline const std::vector<InetAddress> &localRoutes() const throw() { return _localRoutes; }
 	inline const std::vector<InetAddress> &staticIps() const throw() { return _staticIps; }
 	inline const std::vector<InetAddress> &gateways() const throw() { return _gateways; }
 	inline const std::vector<Address> &activeBridges() const throw() { return _activeBridges; }
@@ -194,7 +183,7 @@ private:
 	bool _private;
 	bool _enableBroadcast;
 	std::string _name;
-	std::string _description;
+	std::vector<InetAddress> _localRoutes;
 	std::vector<InetAddress> _staticIps;
 	std::vector<InetAddress> _gateways;
 	std::vector<Address> _activeBridges;
@@ -207,4 +196,3 @@ private:
 } // namespace ZeroTier
 
 #endif
-
diff --git a/node/NetworkController.hpp b/node/NetworkController.hpp
index 265ee3d44..ee481a623 100644
--- a/node/NetworkController.hpp
+++ b/node/NetworkController.hpp
@@ -52,10 +52,10 @@ public:
 	enum ResultCode
 	{
 		NETCONF_QUERY_OK = 0,
-		NETCONF_QUERY_OK_BUT_NOT_NEWER = 1,
-		NETCONF_QUERY_OBJECT_NOT_FOUND = 2,
-		NETCONF_QUERY_ACCESS_DENIED = 3,
-		NETCONF_QUERY_INTERNAL_SERVER_ERROR = 4
+		NETCONF_QUERY_OBJECT_NOT_FOUND = 1,
+		NETCONF_QUERY_ACCESS_DENIED = 2,
+		NETCONF_QUERY_INTERNAL_SERVER_ERROR = 3,
+		NETCONF_QUERY_IGNORE = 4
 	};
 
 	NetworkController() {}
diff --git a/node/Node.cpp b/node/Node.cpp
index ebe0527e7..d40ceab9c 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -316,6 +316,7 @@ ZT1_ResultCode Node::leave(uint64_t nwid)
 	for(std::vector< std::pair< uint64_t,SharedPtr<Network> > >::const_iterator n(_networks.begin());n!=_networks.end();++n) {
 		if (n->first != nwid)
 			newn.push_back(*n);
+		else n->second->destroy();
 	}
 	_networks.swap(newn);
 	return ZT1_RESULT_OK;
diff --git a/node/Peer.cpp b/node/Peer.cpp
index 73c202282..3cf0ec4e5 100644
--- a/node/Peer.cpp
+++ b/node/Peer.cpp
@@ -33,6 +33,7 @@
 #include "Switch.hpp"
 #include "Network.hpp"
 #include "AntiRecursion.hpp"
+#include "SelfAwareness.hpp"
 
 #include <algorithm>
 
@@ -225,10 +226,11 @@ void Peer::doPingAndKeepalive(const RuntimeEnvironment *RR,uint64_t now)
 
 void Peer::pushDirectPaths(const RuntimeEnvironment *RR,RemotePath *path,uint64_t now,bool force)
 {
-	if ((((now - _lastDirectPathPush) >= ZT_DIRECT_PATH_PUSH_INTERVAL)||(force))) {
+	if (((now - _lastDirectPathPush) >= ZT_DIRECT_PATH_PUSH_INTERVAL)||(force)) {
 		_lastDirectPathPush = now;
 
 		std::vector<Path> dps(RR->node->directPaths());
+
 #ifdef ZT_TRACE
 		{
 			std::string ps;
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 4780fa5b5..d3b79d186 100644
--- a/node/SelfAwareness.hpp
+++ b/node/SelfAwareness.hpp
@@ -29,6 +29,7 @@
 #define ZT_SELFAWARENESS_HPP
 
 #include <map>
+#include <vector>
 
 #include "InetAddress.hpp"
 #include "Address.hpp"
@@ -65,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 cf4fe2492..989f497a0 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,11 @@ bool Switch::unite(const Address &p1,const Address &p2,bool force)
 	return true;
 }
 
-void Switch::contact(const SharedPtr<Peer> &peer,const InetAddress &atAddr)
+void Switch::rendezvous(const SharedPtr<Peer> &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);
-
-	// If we have not punched through after this timeout, open refreshing can of whupass
 	{
 		Mutex::Lock _l(_contactQueue_m);
 		_contactQueue.push_back(ContactQueueEntry(peer,now + ZT_NAT_T_TACTICAL_ESCALATION_DELAY,atAddr));
@@ -451,35 +448,26 @@ unsigned long Switch::doTimerTasks(uint64_t now)
 {
 	unsigned long nextDelay = 0xffffffff; // ceiling delay, caller will cap to minimum
 
-	{ // Aggressive NAT traversal time!
+	{	// 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->hasActiveDirectPath(now)) {
-					// We've successfully NAT-t'd, so cancel attempt
+				if ((!qi->peer->alive(now))||(qi->peer->hasActiveDirectPath(now))) {
+					// Cancel attempt if we've already connected or peer is no longer "alive"
 					_contactQueue.erase(qi++);
 					continue;
 				} else {
-					// Nope, nothing yet. Time to kill some kittens.
 					if (qi->strategyIteration == 0) {
-						// First strategy: send packet directly (we already tried this but try again)
+						// First strategy: send packet directly to destination
 						qi->peer->attemptToContactAt(RR,qi->inaddr,now);
-					} else if (qi->strategyIteration <= 9) {
-						// Strategies 1-9: try escalating ports
+					} else if (qi->strategyIteration <= 4) {
+						// Strategies 1-4: try escalating ports for symmetric NATs that remap sequentially
 						InetAddress tmpaddr(qi->inaddr);
 						int p = (int)qi->inaddr.port() + qi->strategyIteration;
 						if (p < 0xffff) {
 							tmpaddr.setPort((unsigned int)p);
 							qi->peer->attemptToContactAt(RR,tmpaddr,now);
-						} else qi->strategyIteration = 9;
-					} else if (qi->strategyIteration <= 18) {
-						// Strategies 10-18: try ports below
-						InetAddress tmpaddr(qi->inaddr);
-						int p = (int)qi->inaddr.port() - (qi->strategyIteration - 9);
-						if (p >= 1024) {
-							tmpaddr.setPort((unsigned int)p);
-							qi->peer->attemptToContactAt(RR,tmpaddr,now);
-						} else qi->strategyIteration = 18;
+						} else qi->strategyIteration = 5;
 					} else {
 						// All strategies tried, expire entry
 						_contactQueue.erase(qi++);
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> &peer,const InetAddress &atAddr);
+	void rendezvous(const SharedPtr<Peer> &peer,const InetAddress &atAddr);
 
 	/**
 	 * Request WHOIS on a given address
diff --git a/node/Topology.hpp b/node/Topology.hpp
index c878bcc6e..1c5cca007 100644
--- a/node/Topology.hpp
+++ b/node/Topology.hpp
@@ -86,7 +86,7 @@ public:
 
 	/**
 	 * Get a peer from its address
-	 * 
+	 *
 	 * @param zta ZeroTier address of peer
 	 * @return Peer or NULL if not found
 	 */
@@ -103,7 +103,7 @@ public:
 
 	/**
 	 * Get the current favorite root server
-	 * 
+	 *
 	 * @return Root server with lowest latency or NULL if none
 	 */
 	inline SharedPtr<Peer> getBestRoot()
@@ -113,11 +113,11 @@ public:
 
 	/**
 	 * Get the best root server, avoiding root servers listed in an array
-	 * 
+	 *
 	 * This will get the best root server (lowest latency, etc.) but will
 	 * try to avoid the listed root servers, only using them if no others
 	 * are available.
-	 * 
+	 *
 	 * @param avoid Nodes to avoid
 	 * @param avoidCount Number of nodes to avoid
 	 * @param strictAvoid If false, consider avoided root servers anyway if no non-avoid root servers are available
diff --git a/one.cpp b/one.cpp
index c4970ab83..d63459193 100644
--- a/one.cpp
+++ b/one.cpp
@@ -256,7 +256,23 @@ static int cli(int argc,char **argv)
 
 	requestHeaders["X-ZT1-Auth"] = authToken;
 
-	if ((command == "info")||(command == "status")) {
+	if ((command.length() > 0)&&(command[0] == '/')) {
+		unsigned int scode = Http::GET(
+			1024 * 1024 * 16,
+			60000,
+			(const struct sockaddr *)&addr,
+			command.c_str(),
+			requestHeaders,
+			responseHeaders,
+			responseBody);
+		if (scode == 200) {
+			printf("%s",cliFixJsonCRs(responseBody).c_str());
+			return 0;
+		} else {
+			printf("%u %s %s"ZT_EOL_S,scode,command.c_str(),responseBody.c_str());
+			return 1;
+		}
+	} else if ((command == "info")||(command == "status")) {
 		unsigned int scode = Http::GET(
 			1024 * 1024 * 16,
 			60000,
@@ -363,7 +379,7 @@ static int cli(int argc,char **argv)
 													else if ((!strcmp(jpath->u.object.values[kk].name,"active"))&&(jpath->u.object.values[kk].value->type == json_boolean))
 														active = (jpath->u.object.values[kk].value->u.boolean != 0);
 												}
-												if (paddr) {
+												if ((paddr)&&((active)||(fixed))) {
 													int64_t now = (int64_t)OSUtils::now();
 													if (lastSend > 0)
 														lastSend = now - lastSend;
diff --git a/osdep/Http.cpp b/osdep/Http.cpp
index cd3cf1370..d491b062a 100644
--- a/osdep/Http.cpp
+++ b/osdep/Http.cpp
@@ -232,7 +232,7 @@ unsigned int Http::_do(
 		handler.error = false;
 		handler.done = false;
 
-		Phy<HttpPhyHandler *> phy(&handler,true);
+		Phy<HttpPhyHandler *> phy(&handler,true,true);
 
 		bool instantConnect = false;
 		handler.phy = &phy;
diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp
index bfe9b68aa..5de35eba3 100644
--- a/osdep/OSUtils.hpp
+++ b/osdep/OSUtils.hpp
@@ -121,10 +121,10 @@ public:
 
 	/**
 	 * Set modes on a file to something secure
-	 * 
+	 *
 	 * This locks a file so that only the owner can access it. What it actually
 	 * does varies by platform.
-	 * 
+	 *
 	 * @param path Path to lock
 	 * @param isDir True if this is a directory
 	 */
@@ -252,4 +252,3 @@ private:
 } // namespace ZeroTier
 
 #endif
-
diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp
index ec01625ba..2ea68b9de 100644
--- a/osdep/Phy.hpp
+++ b/osdep/Phy.hpp
@@ -144,7 +144,7 @@ private:
 	fd_set _readfds;
 	fd_set _writefds;
 #if defined(_WIN32) || defined(_WIN64)
-	fd_set _exceptfds;	
+	fd_set _exceptfds;
 #endif
 	long _nfds;
 
@@ -152,13 +152,15 @@ private:
 	ZT_PHY_SOCKFD_TYPE _whackSendSocket;
 
 	bool _noDelay;
+	bool _noCheck;
 
 public:
 	/**
 	 * @param handler Pointer of type HANDLER_PTR_TYPE to handler
 	 * @param noDelay If true, disable TCP NAGLE algorithm on TCP sockets
+	 * @param noCheck If true, attempt to set UDP SO_NO_CHECK option to disable sending checksums
 	 */
-	Phy(HANDLER_PTR_TYPE handler,bool noDelay) :
+	Phy(HANDLER_PTR_TYPE handler,bool noDelay,bool noCheck) :
 		_handler(handler)
 	{
 		FD_ZERO(&_readfds);
@@ -202,6 +204,7 @@ public:
 		_whackReceiveSocket = pipes[0];
 		_whackSendSocket = pipes[1];
 		_noDelay = noDelay;
+		_noCheck = noCheck;
 	}
 
 	~Phy()
@@ -296,6 +299,11 @@ public:
 #endif
 #ifdef IP_MTU_DISCOVER
 			f = 0; setsockopt(s,IPPROTO_IP,IP_MTU_DISCOVER,&f,sizeof(f));
+#endif
+#ifdef SO_NO_CHECK
+			if (_noCheck) {
+				f = 1; setsockopt(s,SOL_SOCKET,SO_NO_CHECK,(void *)&f,sizeof(f));
+			}
 #endif
 		}
 #endif // Windows or not
@@ -773,7 +781,7 @@ public:
 		// Causes entry to be deleted from list in poll(), ignored elsewhere
 		sws.type = ZT_PHY_SOCKET_CLOSED;
 
-		if (sws.sock >= _nfds) {
+		if ((long)sws.sock >= (long)_nfds) {
 			long nfds = (long)_whackSendSocket;
 			if ((long)_whackReceiveSocket > nfds)
 				nfds = (long)_whackReceiveSocket;
diff --git a/osdep/UPNPClient.cpp b/osdep/UPNPClient.cpp
new file mode 100644
index 000000000..ceecb3a3a
--- /dev/null
+++ b/osdep/UPNPClient.cpp
@@ -0,0 +1,198 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2015  ZeroTier, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * ZeroTier may be used and distributed under the terms of the GPLv3, which
+ * are available at: http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * If you would like to embed ZeroTier into a commercial application or
+ * redistribute it in a modified binary form, please contact ZeroTier Networks
+ * LLC. Start here: http://www.zerotier.com/
+ */
+
+#ifdef ZT_USE_MINIUPNPC
+
+// Uncomment to dump debug messages
+//#define ZT_UPNP_TRACE 1
+
+// Uncomment to build a main() for ad-hoc testing
+//#define ZT_UPNP_TEST 1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../node/Utils.hpp"
+#include "UPNPClient.hpp"
+
+#ifdef __WINDOWS__
+#ifndef MINIUPNP_STATICLIB
+#define MINIUPNP_STATICLIB
+#endif
+#endif
+
+#include "../ext/bin/miniupnpc/include/miniupnpc/miniupnpc.h"
+#include "../ext/bin/miniupnpc/include/miniupnpc/upnpcommands.h"
+
+namespace ZeroTier {
+
+class UPNPClientImpl
+{
+public:
+	UPNPClientImpl(int localUdpPortToMap) :
+		run(true),
+		localPort(localUdpPortToMap)
+	{
+	}
+
+	void threadMain()
+		throw()
+	{
+		char lanaddr[4096];
+		char externalip[4096]; // no range checking? so make these buffers larger than any UDP packet a uPnP server could send us as a precaution :P
+		char inport[16];
+		char outport[16];
+		struct UPNPUrls urls;
+		struct IGDdatas data;
+
+#ifdef ZT_UPNP_TRACE
+		fprintf(stderr,"UPNPClient: started for UDP port %d"ZT_EOL_S,localPort);
+#endif
+
+		unsigned int tryPortStart = 0;
+		Utils::getSecureRandom(&tryPortStart,sizeof(tryPortStart));
+		tryPortStart = (tryPortStart % (65535 - 1025)) + 1025;
+
+		while (run) {
+			{
+				int upnpError = 0;
+				UPNPDev *devlist = upnpDiscover(2000,(const char *)0,(const char *)0,0,0,&upnpError);
+				if (devlist) {
+#ifdef ZT_UPNP_TRACE
+					{
+						UPNPDev *dev = devlist;
+						while (dev) {
+							fprintf(stderr,"UPNPClient: found device at URL '%s': %s"ZT_EOL_S,dev->descURL,dev->st);
+							dev = dev->pNext;
+						}
+					}
+#endif
+
+					memset(lanaddr,0,sizeof(lanaddr));
+					memset(externalip,0,sizeof(externalip));
+					memset(&urls,0,sizeof(urls));
+					memset(&data,0,sizeof(data));
+					Utils::snprintf(inport,sizeof(inport),"%d",localPort);
+
+					if ((UPNP_GetValidIGD(devlist,&urls,&data,lanaddr,sizeof(lanaddr)))&&(lanaddr[0])) {
+#ifdef ZT_UPNP_TRACE
+						fprintf(stderr,"UPNPClient: my LAN IP address: %s"ZT_EOL_S,lanaddr);
+#endif
+						if ((UPNP_GetExternalIPAddress(urls.controlURL,data.first.servicetype,externalip) == UPNPCOMMAND_SUCCESS)&&(externalip[0])) {
+#ifdef ZT_UPNP_TRACE
+							fprintf(stderr,"UPNPClient: my external IP address: %s"ZT_EOL_S,externalip);
+#endif
+
+							for(int tries=0;tries<64;++tries) {
+								int tryPort = (int)tryPortStart + tries;
+								if (tryPort >= 65535)
+									tryPort = (tryPort - 65535) + 1025;
+								Utils::snprintf(outport,sizeof(outport),"%u",tryPort);
+
+								int mapResult = 0;
+								if ((mapResult = UPNP_AddPortMapping(urls.controlURL,data.first.servicetype,outport,inport,lanaddr,"ZeroTier","UDP",(const char *)0,ZT_UPNP_LEASE_DURATION)) == UPNPCOMMAND_SUCCESS) {
+	#ifdef ZT_UPNP_TRACE
+									fprintf(stderr,"UPNPClient: reserved external port: %s"ZT_EOL_S,outport);
+	#endif
+									{
+										Mutex::Lock sl(surface_l);
+										surface.clear();
+										InetAddress tmp(externalip);
+										tmp.setPort(tryPort);
+										surface.push_back(tmp);
+									}
+									break;
+								} else {
+	#ifdef ZT_UPNP_TRACE
+									fprintf(stderr,"UPNPClient: UPNP_AddAnyPortMapping(%s) failed: %d"ZT_EOL_S,outport,mapResult);
+	#endif
+									Thread::sleep(1000);
+								}
+							}
+						} else {
+#ifdef ZT_UPNP_TRACE
+							fprintf(stderr,"UPNPClient: UPNP_GetExternalIPAddress failed"ZT_EOL_S);
+#endif
+						}
+					} else {
+#ifdef ZT_UPNP_TRACE
+						fprintf(stderr,"UPNPClient: UPNP_GetValidIGD failed"ZT_EOL_S);
+#endif
+					}
+
+					freeUPNPDevlist(devlist);
+				} else {
+#ifdef ZT_UPNP_TRACE
+					fprintf(stderr,"UPNPClient: upnpDiscover error code: %d"ZT_EOL_S,upnpError);
+#endif
+				}
+			}
+
+#ifdef ZT_UPNP_TRACE
+			fprintf(stderr,"UPNPClient: rescanning in %d ms"ZT_EOL_S,ZT_UPNP_CLIENT_REFRESH_DELAY);
+#endif
+			Thread::sleep(ZT_UPNP_CLIENT_REFRESH_DELAY);
+		}
+		delete this;
+	}
+
+	volatile bool run;
+	int localPort;
+	Mutex surface_l;
+	std::vector<InetAddress> surface;
+};
+
+UPNPClient::UPNPClient(int localUdpPortToMap)
+{
+	_impl = new UPNPClientImpl(localUdpPortToMap);
+	Thread::start(_impl);
+}
+
+UPNPClient::~UPNPClient()
+{
+	_impl->run = false;
+}
+
+std::vector<InetAddress> UPNPClient::get() const
+{
+	Mutex::Lock _l(_impl->surface_l);
+	return _impl->surface;
+}
+
+} // namespace ZeroTier
+
+#ifdef ZT_UPNP_TEST
+int main(int argc,char **argv)
+{
+	ZeroTier::UPNPClient *client = new ZeroTier::UPNPClient(12345);
+	ZeroTier::Thread::sleep(0xffffffff); // wait forever
+	return 0;
+}
+#endif
+
+#endif // ZT_USE_MINIUPNPC
diff --git a/osdep/UPNPClient.hpp b/osdep/UPNPClient.hpp
new file mode 100644
index 000000000..28b9979d3
--- /dev/null
+++ b/osdep/UPNPClient.hpp
@@ -0,0 +1,84 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2015  ZeroTier, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * ZeroTier may be used and distributed under the terms of the GPLv3, which
+ * are available at: http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * If you would like to embed ZeroTier into a commercial application or
+ * redistribute it in a modified binary form, please contact ZeroTier Networks
+ * LLC. Start here: http://www.zerotier.com/
+ */
+
+#ifndef ZT_UPNPCLIENT_HPP
+#define ZT_UPNPCLIENT_HPP
+
+#ifdef ZT_USE_MINIUPNPC
+
+#include <vector>
+
+#include "../node/Constants.hpp"
+#include "../node/InetAddress.hpp"
+#include "../node/Mutex.hpp"
+#include "Thread.hpp"
+
+/**
+ * How frequently should we refresh our UPNP/NAT-PnP/whatever state?
+ */
+#define ZT_UPNP_CLIENT_REFRESH_DELAY 600000
+
+/**
+ * UPNP lease duration in seconds (as string)
+ */
+#define ZT_UPNP_LEASE_DURATION "3600"
+
+namespace ZeroTier {
+
+class UPNPClientImpl;
+
+/**
+ * UPnP/NAT-PnP daemon thread
+ */
+class UPNPClient
+{
+	friend class UPNPClientImpl;
+
+public:
+	/**
+	 * Create and start UPNP client service
+	 *
+	 * @param localUdpPortToMap Port we want visible to the outside world
+	 */
+	UPNPClient(int localUdpPortToMap);
+
+	~UPNPClient();
+
+	/**
+	 * @return All current external mappings for our port
+	 */
+	std::vector<InetAddress> get() const;
+
+private:
+	UPNPClientImpl *_impl;
+};
+
+} // namespace ZeroTier
+
+#endif // ZT_USE_MINIUPNPC
+
+#endif
diff --git a/selftest.cpp b/selftest.cpp
index cf20fdf3d..714964cb9 100644
--- a/selftest.cpp
+++ b/selftest.cpp
@@ -696,7 +696,7 @@ static int testPhy()
 
 	std::cout << "[phy] Creating phy endpoint..." << std::endl;
 	TestPhyHandlers testPhyHandlers;
-	testPhyInstance = new Phy<TestPhyHandlers *>(&testPhyHandlers,false);
+	testPhyInstance = new Phy<TestPhyHandlers *>(&testPhyHandlers,false,true);
 
 	std::cout << "[phy] Binding UDP listen socket to 127.0.0.1/60002... ";
 	PhySocket *udpListenSock = testPhyInstance->udpBind((const struct sockaddr *)&bindaddr);
diff --git a/service/OneService.cpp b/service/OneService.cpp
index d582a8937..06e37a45f 100644
--- a/service/OneService.cpp
+++ b/service/OneService.cpp
@@ -54,6 +54,7 @@
 #include "../osdep/OSUtils.hpp"
 #include "../osdep/Http.hpp"
 #include "../osdep/BackgroundResolver.hpp"
+#include "../osdep/UPNPClient.hpp"
 
 #include "OneService.hpp"
 #include "ControlPlane.hpp"
@@ -404,7 +405,7 @@ public:
 #ifdef ZT_ENABLE_NETWORK_CONTROLLER
 		_controller((_homePath + ZT_PATH_SEPARATOR_S + ZT1_CONTROLLER_DB_PATH).c_str()),
 #endif
-		_phy(this,false),
+		_phy(this,false,true),
 		_overrideRootTopology((overrideRootTopology) ? overrideRootTopology : ""),
 		_node((Node *)0),
 		_controlPlane((ControlPlane *)0),
@@ -415,6 +416,9 @@ public:
 		_tcpFallbackTunnel((TcpConnection *)0),
 		_termReason(ONE_STILL_RUNNING),
 		_port(port),
+#ifdef ZT_USE_MINIUPNPC
+		_upnpClient((int)port),
+#endif
 		_run(true)
 	{
 		struct sockaddr_in in4;
@@ -511,7 +515,7 @@ public:
 			_lastRestart = clockShouldBe;
 			uint64_t lastTapMulticastGroupCheck = 0;
 			uint64_t lastTcpFallbackResolve = 0;
-			uint64_t lastLocalInterfaceAddressCheck = 0;
+			uint64_t lastLocalInterfaceAddressCheck = (OSUtils::now() - ZT1_LOCAL_INTERFACE_CHECK_INTERVAL) + 15000; // do this in 15s to give UPnP time to configure and other things time to settle
 #ifdef ZT_AUTO_UPDATE
 			uint64_t lastSoftwareUpdateCheck = 0;
 #endif // ZT_AUTO_UPDATE
@@ -576,9 +580,16 @@ public:
 							ztDevices.push_back(t->second->deviceName());
 					}
 
+					_node->clearLocalInterfaceAddresses();
+
+#ifdef ZT_USE_MINIUPNPC
+					std::vector<InetAddress> upnpAddresses(_upnpClient.get());
+					for(std::vector<InetAddress>::const_iterator ext(upnpAddresses.begin());ext!=upnpAddresses.end();++ext)
+						_node->addLocalInterfaceAddress(reinterpret_cast<const struct sockaddr_storage *>(&(*ext)),0,ZT1_LOCAL_INTERFACE_ADDRESS_TRUST_NORMAL);
+#endif
+
 					struct ifaddrs *ifatbl = (struct ifaddrs *)0;
 					if ((getifaddrs(&ifatbl) == 0)&&(ifatbl)) {
-						_node->clearLocalInterfaceAddresses();
 						struct ifaddrs *ifa = ifatbl;
 						while (ifa) {
 							if ((ifa->ifa_name)&&(ifa->ifa_addr)) {
@@ -1242,6 +1253,10 @@ private:
 
 	unsigned int _port;
 
+#ifdef ZT_USE_MINIUPNPC
+	UPNPClient _upnpClient;
+#endif
+
 	bool _run;
 	Mutex _run_m;
 };
diff --git a/service/OneService.hpp b/service/OneService.hpp
index 7964958c7..7a4f78275 100644
--- a/service/OneService.hpp
+++ b/service/OneService.hpp
@@ -106,8 +106,6 @@ public:
 	 * The terminate() method may be called from a signal handler or another
 	 * thread to terminate execution. Otherwise this will not return unless
 	 * another condition terminates execution such as a fatal error.
-	 *
-	 * @param 
 	 */
 	virtual ReasonForTermination run() = 0;
 
diff --git a/tcp-proxy/tcp-proxy.cpp b/tcp-proxy/tcp-proxy.cpp
index 6acf7b423..70c0281af 100644
--- a/tcp-proxy/tcp-proxy.cpp
+++ b/tcp-proxy/tcp-proxy.cpp
@@ -297,7 +297,7 @@ int main(int argc,char **argv)
 	srand(time((time_t *)0));
 
 	TcpProxyService svc;
-	Phy<TcpProxyService *> phy(&svc,false);
+	Phy<TcpProxyService *> phy(&svc,false,true);
 	svc.phy = &phy;
 	svc.udpPortCounter = 1023;
 
diff --git a/version.h b/version.h
index f7b253a7b..62f8fb697 100644
--- a/version.h
+++ b/version.h
@@ -41,6 +41,6 @@
 /**
  * Revision
  */
-#define ZEROTIER_ONE_VERSION_REVISION 3
+#define ZEROTIER_ONE_VERSION_REVISION 4
 
 #endif
diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj
index 554669b60..14bf7c3eb 100644
--- a/windows/ZeroTierOne/ZeroTierOne.vcxproj
+++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj
@@ -47,6 +47,7 @@
     <ClCompile Include="..\..\osdep\BackgroundResolver.cpp" />
     <ClCompile Include="..\..\osdep\Http.cpp" />
     <ClCompile Include="..\..\osdep\OSUtils.cpp" />
+    <ClCompile Include="..\..\osdep\UPNPClient.cpp" />
     <ClCompile Include="..\..\osdep\WindowsEthernetTap.cpp" />
     <ClCompile Include="..\..\selftest.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@@ -61,6 +62,22 @@
     <ClCompile Include="ZeroTierOneService.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\codelength.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\connecthostport.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\igd_desc_parse.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\minisoap.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\minissdpc.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniupnpc.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniupnpcstrings.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniupnpctypes.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniupnpc_declspec.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniwget.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\minixml.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\portlistingparse.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\receivedata.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\upnpcommands.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\upnperrors.h" />
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\upnpreplyparse.h" />
     <ClInclude Include="..\..\ext\http-parser\http_parser.h" />
     <ClInclude Include="..\..\ext\json-parser\json.h" />
     <ClInclude Include="..\..\ext\lz4\lz4.h" />
@@ -108,6 +125,7 @@
     <ClInclude Include="..\..\osdep\OSUtils.hpp" />
     <ClInclude Include="..\..\osdep\Phy.hpp" />
     <ClInclude Include="..\..\osdep\Thread.hpp" />
+    <ClInclude Include="..\..\osdep\UPNPClient.hpp" />
     <ClInclude Include="..\..\osdep\WindowsEthernetTap.hpp" />
     <ClInclude Include="..\..\service\ControlPlane.hpp" />
     <ClInclude Include="..\..\service\ControlPlaneSubsystem.hpp" />
@@ -193,12 +211,13 @@
       <WarningLevel>Level3</WarningLevel>
       <Optimization>Disabled</Optimization>
       <SDLCheck>true</SDLCheck>
-      <AdditionalIncludeDirectories>$(SolutionDir)\ext\bin\libcrypto\include</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>NOMINMAX;ZT_TRACE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>
+      </AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>NOMINMAX;ZT_TRACE;ZT_USE_MINIUPNPC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
-      <AdditionalDependencies>wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>$(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
     </Link>
   </ItemDefinitionGroup>
@@ -207,12 +226,13 @@
       <WarningLevel>Level3</WarningLevel>
       <Optimization>Disabled</Optimization>
       <SDLCheck>true</SDLCheck>
-      <AdditionalIncludeDirectories>$(SolutionDir)\ext\bin\libcrypto\include</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>NOMINMAX;ZT_TRACE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>
+      </AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>NOMINMAX;ZT_TRACE;ZT_USE_MINIUPNPC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
-      <AdditionalDependencies>wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>$(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
     </Link>
   </ItemDefinitionGroup>
@@ -223,8 +243,9 @@
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <SDLCheck>true</SDLCheck>
-      <AdditionalIncludeDirectories>$(SolutionDir)\ext\bin\libcrypto\include</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>ZT_OFFICIAL_RELEASE;ZT_AUTO_UPDATE;ZT_SALSA20_SSE;NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>
+      </AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>ZT_OFFICIAL_RELEASE;ZT_AUTO_UPDATE;ZT_SALSA20_SSE;ZT_USE_MINIUPNPC;NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
       <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
       <StringPooling>true</StringPooling>
@@ -236,7 +257,7 @@
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
-      <AdditionalDependencies>wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>$(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
     </Link>
   </ItemDefinitionGroup>
@@ -247,8 +268,9 @@
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <SDLCheck>true</SDLCheck>
-      <AdditionalIncludeDirectories>$(SolutionDir)\ext\bin\libcrypto\include</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>ZT_OFFICIAL_RELEASE;ZT_AUTO_UPDATE;ZT_SALSA20_SSE;NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>
+      </AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>ZT_OFFICIAL_RELEASE;ZT_AUTO_UPDATE;ZT_SALSA20_SSE;ZT_USE_MINIUPNPC;NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
       <EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
       <StringPooling>true</StringPooling>
@@ -260,7 +282,7 @@
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
-      <AdditionalDependencies>wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>$(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
     </Link>
   </ItemDefinitionGroup>
diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters
index f36b5dc0e..abaa8547d 100644
--- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters
+++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters
@@ -70,6 +70,15 @@
     <Filter Include="Header Files\windows\ZeroTierOne">
       <UniqueIdentifier>{bf604491-14c4-4a74-81a6-6105d07c5c7c}</UniqueIdentifier>
     </Filter>
+    <Filter Include="Header Files\ext\bin">
+      <UniqueIdentifier>{5939db69-ab17-47c6-97fb-185e2c678737}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="Header Files\ext\bin\miniupnpc">
+      <UniqueIdentifier>{3666f510-b6da-47cb-8039-56441f2dac3e}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="Header Files\ext\bin\miniupnpc\include">
+      <UniqueIdentifier>{1a47071e-e51b-4535-89ae-858946f03118}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\service\ControlPlane.cpp">
@@ -177,6 +186,9 @@
     <ClCompile Include="..\..\osdep\BackgroundResolver.cpp">
       <Filter>Source Files\osdep</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\osdep\UPNPClient.cpp">
+      <Filter>Source Files\osdep</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="resource.h">
@@ -347,6 +359,57 @@
     <ClInclude Include="..\..\node\RemotePath.hpp">
       <Filter>Header Files\node</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\osdep\UPNPClient.hpp">
+      <Filter>Header Files\osdep</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\codelength.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\connecthostport.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\igd_desc_parse.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\minisoap.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\minissdpc.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniupnpc.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniupnpc_declspec.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniupnpcstrings.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniupnpctypes.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\miniwget.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\minixml.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\portlistingparse.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\receivedata.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\upnpcommands.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\upnperrors.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\ext\bin\miniupnpc\include\miniupnpc\upnpreplyparse.h">
+      <Filter>Header Files\ext\bin\miniupnpc\include</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="ZeroTierOne.rc">