Browse Source

Merge branch 'adamierymenko-dev' into android-jni

Grant Limberg 10 years ago
parent
commit
1e8ead441c

+ 40 - 10
controller/SqliteNetworkController.cpp

@@ -108,6 +108,7 @@ struct NetworkRecord {
 	int multicastLimit;
 	uint64_t creationTime;
 	uint64_t revision;
+	uint64_t memberRevisionCounter;
 };
 
 } // anonymous namespace
@@ -149,12 +150,13 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 	if (
 
 			/* Network */
-			  (sqlite3_prepare_v2(_db,"SELECT name,private,enableBroadcast,allowPassiveBridging,v4AssignMode,v6AssignMode,multicastLimit,creationTime,revision FROM Network WHERE id = ?",-1,&_sGetNetworkById,(const char **)0) != SQLITE_OK)
+			  (sqlite3_prepare_v2(_db,"SELECT name,private,enableBroadcast,allowPassiveBridging,v4AssignMode,v6AssignMode,multicastLimit,creationTime,revision,memberRevisionCounter FROM Network WHERE id = ?",-1,&_sGetNetworkById,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT revision FROM Network WHERE id = ?",-1,&_sGetNetworkRevision,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"UPDATE Network SET revision = ? WHERE id = ?",-1,&_sSetNetworkRevision,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"INSERT INTO Network (id,name,creationTime,revision) VALUES (?,?,?,1)",-1,&_sCreateNetwork,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"DELETE FROM Network WHERE id = ?",-1,&_sDeleteNetwork,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT id FROM Network ORDER BY id ASC",-1,&_sListNetworks,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"UPDATE Network SET memberRevisionCounter = (memberRevisionCounter + 1) WHERE id = ?",-1,&_sIncrementMemberRevisionCounter,(const char **)0) != SQLITE_OK)
 
 			/* Node */
 			||(sqlite3_prepare_v2(_db,"SELECT identity FROM Node WHERE id = ?",-1,&_sGetNodeIdentity,(const char **)0) != SQLITE_OK)
@@ -188,12 +190,12 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 
 			/* Member */
 			||(sqlite3_prepare_v2(_db,"SELECT rowid,authorized,activeBridge FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sGetMember,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,n.identity FROM Member AS m JOIN Node AS n ON n.id = m.nodeId WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge) VALUES (?,?,?,0)",-1,&_sCreateMember,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,m.memberRevision,n.identity FROM Member AS m JOIN Node AS n ON n.id = m.nodeId WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge,memberRevision) VALUES (?,?,?,0,(SELECT memberRevisionCounter FROM Network WHERE id = ?))",-1,&_sCreateMember,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT nodeId FROM Member WHERE networkId = ? AND activeBridge > 0 AND authorized > 0",-1,&_sGetActiveBridges,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT m.nodeId FROM Member AS m WHERE m.networkId = ? ORDER BY m.nodeId ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"UPDATE Member SET authorized = ? WHERE rowid = ?",-1,&_sUpdateMemberAuthorized,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"UPDATE Member SET activeBridge = ? WHERE rowid = ?",-1,&_sUpdateMemberActiveBridge,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"UPDATE Member SET authorized = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberAuthorized,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"UPDATE Member SET activeBridge = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberActiveBridge,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK)
 
 			/* Gateway */
@@ -251,6 +253,7 @@ SqliteNetworkController::~SqliteNetworkController()
 		sqlite3_finalize(_sGetGateways);
 		sqlite3_finalize(_sDeleteGateways);
 		sqlite3_finalize(_sCreateGateway);
+		sqlite3_finalize(_sIncrementMemberRevisionCounter);
 		sqlite3_close(_db);
 	}
 }
@@ -316,6 +319,7 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 		network.multicastLimit = sqlite3_column_int(_sGetNetworkById,6);
 		network.creationTime = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7);
 		network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,8);
+		network.memberRevisionCounter = (uint64_t)sqlite3_column_int64(_sGetNetworkById,9);
 	} else return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
 
 	// Fetch Member record
@@ -340,11 +344,16 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 		sqlite3_bind_text(_sCreateMember,1,network.id,16,SQLITE_STATIC);
 		sqlite3_bind_text(_sCreateMember,2,member.nodeId,10,SQLITE_STATIC);
 		sqlite3_bind_int(_sCreateMember,3,(member.authorized ? 1 : 0));
+		sqlite3_bind_text(_sCreateMember,4,network.id,16,SQLITE_STATIC);
 		if (sqlite3_step(_sCreateMember) != SQLITE_DONE) {
 			netconf["error"] = "unable to create new member record";
 			return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
 		}
 		member.rowid = (int64_t)sqlite3_last_insert_rowid(_db);
+
+		sqlite3_reset(_sIncrementMemberRevisionCounter);
+		sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,network.id,16,SQLITE_STATIC);
+		sqlite3_step(_sIncrementMemberRevisionCounter);
 	}
 
 	// Check member authorization
@@ -683,9 +692,14 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 						sqlite3_bind_text(_sCreateMember,1,nwids,16,SQLITE_STATIC);
 						sqlite3_bind_text(_sCreateMember,2,addrs,10,SQLITE_STATIC);
 						sqlite3_bind_int(_sCreateMember,3,0);
+						sqlite3_bind_text(_sCreateMember,4,nwids,16,SQLITE_STATIC);
 						if (sqlite3_step(_sCreateMember) != SQLITE_DONE)
 							return 500;
 						memberRowId = (int64_t)sqlite3_last_insert_rowid(_db);
+
+						sqlite3_reset(_sIncrementMemberRevisionCounter);
+						sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
+						sqlite3_step(_sIncrementMemberRevisionCounter);
 					}
 
 					json_value *j = json_parse(body.c_str(),body.length());
@@ -697,17 +711,27 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 									if (j->u.object.values[k].value->type == json_boolean) {
 										sqlite3_reset(_sUpdateMemberAuthorized);
 										sqlite3_bind_int(_sUpdateMemberAuthorized,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
-										sqlite3_bind_int64(_sUpdateMemberAuthorized,2,memberRowId);
+										sqlite3_bind_text(_sUpdateMemberAuthorized,2,nwids,16,SQLITE_STATIC);
+										sqlite3_bind_int64(_sUpdateMemberAuthorized,3,memberRowId);
 										if (sqlite3_step(_sUpdateMemberAuthorized) != SQLITE_DONE)
 											return 500;
+
+										sqlite3_reset(_sIncrementMemberRevisionCounter);
+										sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
+										sqlite3_step(_sIncrementMemberRevisionCounter);
 									}
 								} else if (!strcmp(j->u.object.values[k].name,"activeBridge")) {
 									if (j->u.object.values[k].value->type == json_boolean) {
 										sqlite3_reset(_sUpdateMemberActiveBridge);
 										sqlite3_bind_int(_sUpdateMemberActiveBridge,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
-										sqlite3_bind_int64(_sUpdateMemberActiveBridge,2,memberRowId);
+										sqlite3_bind_text(_sUpdateMemberActiveBridge,2,nwids,16,SQLITE_STATIC);
+										sqlite3_bind_int64(_sUpdateMemberActiveBridge,3,memberRowId);
 										if (sqlite3_step(_sUpdateMemberActiveBridge) != SQLITE_DONE)
 											return 500;
+
+										sqlite3_reset(_sIncrementMemberRevisionCounter);
+										sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
+										sqlite3_step(_sIncrementMemberRevisionCounter);
 									}
 								} else if (!strcmp(j->u.object.values[k].name,"ipAssignments")) {
 									if (j->u.object.values[k].value->type == json_array) {
@@ -778,6 +802,8 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 						uint64_t nwidOriginalPostfix = nwidPostfix;
 						do {
 							uint64_t tryNwid = nwidPrefix | nwidPostfix;
+							if (!nwidPostfix)
+								tryNwid |= 1;
 							Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid);
 
 							sqlite3_reset(_sGetNetworkRevision);
@@ -799,7 +825,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 
 					sqlite3_reset(_sCreateNetwork);
 					sqlite3_bind_text(_sCreateNetwork,1,nwids,16,SQLITE_STATIC);
-					sqlite3_bind_text(_sCreateNetwork,2,nwids,16,SQLITE_STATIC); // default name, will be changed below if a name is specified in JSON
+					sqlite3_bind_text(_sCreateNetwork,2,"",0,SQLITE_STATIC);
 					sqlite3_bind_int64(_sCreateNetwork,3,(long long)OSUtils::now());
 					if (sqlite3_step(_sCreateNetwork) != SQLITE_DONE)
 						return 500;
@@ -1230,13 +1256,15 @@ unsigned int SqliteNetworkController::_doCPGet(
 							"\t\"address\": \"%s\",\n"
 							"\t\"authorized\": %s,\n"
 							"\t\"activeBridge\": %s,\n"
+							"\t\"memberRevision\": %llu,\n"
 							"\t\"identity\": \"%s\",\n"
 							"\t\"ipAssignments\": [",
 							nwids,
 							addrs,
 							(sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false",
 							(sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false",
-							_jsonEscape((const char *)sqlite3_column_text(_sGetMember2,2)).c_str());
+							(unsigned long long)sqlite3_column_int64(_sGetMember2,2),
+							_jsonEscape((const char *)sqlite3_column_text(_sGetMember2,3)).c_str());
 						responseBody = json;
 
 						sqlite3_reset(_sGetIpAssignmentsForNode2);
@@ -1333,6 +1361,7 @@ unsigned int SqliteNetworkController::_doCPGet(
 						"\t\"multicastLimit\": %d,\n"
 						"\t\"creationTime\": %llu,\n"
 						"\t\"revision\": %llu,\n"
+						"\t\"memberRevisionCounter\": %llu,\n"
 						"\t\"members\": [",
 						nwids,
 						_jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(),
@@ -1343,7 +1372,8 @@ unsigned int SqliteNetworkController::_doCPGet(
 						_jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,5)).c_str(),
 						sqlite3_column_int(_sGetNetworkById,6),
 						(unsigned long long)sqlite3_column_int64(_sGetNetworkById,7),
-						(unsigned long long)sqlite3_column_int64(_sGetNetworkById,8));
+						(unsigned long long)sqlite3_column_int64(_sGetNetworkById,8),
+						(unsigned long long)sqlite3_column_int64(_sGetNetworkById,9));
 					responseBody = json;
 
 					sqlite3_reset(_sListNetworkMembers);

+ 1 - 0
controller/SqliteNetworkController.hpp

@@ -137,6 +137,7 @@ private:
 	sqlite3_stmt *_sGetGateways;
 	sqlite3_stmt *_sDeleteGateways;
 	sqlite3_stmt *_sCreateGateway;
+	sqlite3_stmt *_sIncrementMemberRevisionCounter;
 
 	Mutex _lock;
 };

+ 4 - 1
controller/schema.sql

@@ -13,7 +13,8 @@ CREATE TABLE Network (
   v6AssignMode varchar(8) NOT NULL DEFAULT('none'),
   multicastLimit integer NOT NULL DEFAULT(32),
   creationTime integer NOT NULL DEFAULT(0),
-  revision integer NOT NULL DEFAULT(1)
+  revision integer NOT NULL DEFAULT(1),
+  memberRevisionCounter integer NOT NULL DEFAULT(1)
 );
 
 CREATE TABLE Node (
@@ -57,10 +58,12 @@ CREATE TABLE Member (
   nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,
   authorized integer NOT NULL DEFAULT(0),
   activeBridge integer NOT NULL DEFAULT(0),
+  memberRevision integer NOT NULL DEFAULT(0),
   PRIMARY KEY (networkId, nodeId)
 );
 
 CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);
+CREATE INDEX Member_networkId_memberRevision ON Member(networkId, memberRevision);
 
 CREATE TABLE Relay (
   networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,

+ 4 - 1
controller/schema.sql.c

@@ -14,7 +14,8 @@
 "  v6AssignMode varchar(8) NOT NULL DEFAULT('none'),\n"\
 "  multicastLimit integer NOT NULL DEFAULT(32),\n"\
 "  creationTime integer NOT NULL DEFAULT(0),\n"\
-"  revision integer NOT NULL DEFAULT(1)\n"\
+"  revision integer NOT NULL DEFAULT(1),\n"\
+"  memberRevisionCounter integer NOT NULL DEFAULT(1)\n"\
 ");\n"\
 "\n"\
 "CREATE TABLE Node (\n"\
@@ -58,10 +59,12 @@
 "  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"\
 "  authorized integer NOT NULL DEFAULT(0),\n"\
 "  activeBridge integer NOT NULL DEFAULT(0),\n"\
+"  memberRevision integer NOT NULL DEFAULT(0),\n"\
 "  PRIMARY KEY (networkId, nodeId)\n"\
 ");\n"\
 "\n"\
 "CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);\n"\
+"CREATE INDEX Member_networkId_memberRevision ON Member(networkId, memberRevision);\n"\
 "\n"\
 "CREATE TABLE Relay (\n"\
 "  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\

+ 2 - 3
include/ZeroTierOne.h

@@ -73,7 +73,7 @@ extern "C" {
  *
  * If this does change, also change it in tap.h in the tuntaposx code under
  * mac-tap.
- * 
+ *
  * Overhead for a normal frame split into two packets:
  *
  * 1414 = 1444 (typical UDP MTU) - 28 (packet header) - 2 (ethertype)
@@ -987,10 +987,9 @@ void ZT1_Node_freeQueryResult(ZT1_Node *node,void *qr);
  * @param addr Local interface address
  * @param metric Local interface metric
  * @param trust How much do you trust the local network under this interface?
- * @param reliable If nonzero, this interface doesn't link to anything behind a NAT or stateful firewall
  * @return Boolean: non-zero if address was accepted and added
  */
-int ZT1_Node_addLocalInterfaceAddress(ZT1_Node *node,const struct sockaddr_storage *addr,int metric,ZT1_LocalInterfaceAddressTrust trust,int reliable);
+int ZT1_Node_addLocalInterfaceAddress(ZT1_Node *node,const struct sockaddr_storage *addr,int metric,ZT1_LocalInterfaceAddressTrust trust);
 
 /**
  * Clear local interface addresses

+ 16 - 1
node/Dictionary.hpp

@@ -33,7 +33,7 @@
 #include <string>
 #include <map>
 #include <stdexcept>
- 
+
 #include "Constants.hpp"
 #include "Utils.hpp"
 
@@ -305,6 +305,21 @@ public:
 	 */
 	bool verify(const Identity &id) const;
 
+  inline bool operator==(const Dictionary &d) const
+  {
+    // std::map::operator== is broken on uclibc++
+    if (size() != d.size())
+      return false;
+    const_iterator a(begin());
+    const_iterator b(d.begin());
+    while (a != end()) {
+      if (*(a++) != *(b++))
+        return false;
+    }
+    return true;
+  }
+  inline bool operator!=(const Dictionary &d) const { return (!(*this == d)); }
+
 private:
 	void _mkSigBuf(std::string &buf) const;
 	static void _appendEsc(const char *data,unsigned int len,std::string &to);

+ 13 - 11
node/IncomingPacket.cpp

@@ -185,18 +185,17 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR)
 
 	try {
 		const unsigned int protoVersion = (*this)[ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION];
-		if (protoVersion < ZT_PROTO_VERSION_MIN) {
-			TRACE("dropped HELLO from %s(%s): protocol version too old",id.address().toString().c_str(),_remoteAddress.toString().c_str());
-			return true;
-		}
-
 		const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION];
 		const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION];
 		const unsigned int vRevision = at<uint16_t>(ZT_PROTO_VERB_HELLO_IDX_REVISION);
 		const uint64_t timestamp = at<uint64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP);
-
 		Identity id;
 		unsigned int destAddrPtr = id.deserialize(*this,ZT_PROTO_VERB_HELLO_IDX_IDENTITY) + ZT_PROTO_VERB_HELLO_IDX_IDENTITY;
+
+		if (protoVersion < ZT_PROTO_VERSION_MIN) {
+			TRACE("dropped HELLO from %s(%s): protocol version too old",id.address().toString().c_str(),_remoteAddress.toString().c_str());
+			return true;
+		}
 		if (source() != id.address()) {
 			TRACE("dropped HELLO from %s(%s): identity not for sending address",source().toString().c_str(),_remoteAddress.toString().c_str());
 			return true;
@@ -906,26 +905,29 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const Sha
 		unsigned int count = at<uint16_t>(ZT_PACKET_IDX_PAYLOAD);
 		unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 2;
 
-		while (count) { // if ptr overflows Buffer will throw
+		while (count--) { // if ptr overflows Buffer will throw
 			// TODO: properly handle blacklisting, support other features... see Packet.hpp.
 
 			unsigned int flags = (*this)[ptr++];
-			/*int metric = (*this)[ptr++];*/ ++ptr;
 			unsigned int extLen = at<uint16_t>(ptr); ptr += 2;
 			ptr += extLen; // unused right now
 			unsigned int addrType = (*this)[ptr++];
-
 			unsigned int addrLen = (*this)[ptr++];
+
 			switch(addrType) {
 				case 4: {
 					InetAddress a(field(ptr,4),4,at<uint16_t>(ptr + 4));
-					if ( ((flags & (0x01 | 0x02)) == 0) && (Path::isAddressValidForPath(a)) )
+					if ( ((flags & (0x01 | 0x02)) == 0) && (Path::isAddressValidForPath(a)) ) {
+						TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str());
 						peer->attemptToContactAt(RR,a,RR->node->now());
+					}
 				}	break;
 				case 6: {
 					InetAddress a(field(ptr,16),16,at<uint16_t>(ptr + 16));
-					if ( ((flags & (0x01 | 0x02)) == 0) && (Path::isAddressValidForPath(a)) )
+					if ( ((flags & (0x01 | 0x02)) == 0) && (Path::isAddressValidForPath(a)) ) {
+						TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str());
 						peer->attemptToContactAt(RR,a,RR->node->now());
+					}
 				}	break;
 			}
 			ptr += addrLen;

+ 11 - 8
node/InetAddress.hpp

@@ -63,17 +63,20 @@ struct InetAddress : public sockaddr_storage
 
 	/**
 	 * IP address scope
+	 *
+	 * Note that these values are in ascending order of path preference and
+	 * MUST remain that way or Path must be changed to reflect.
 	 */
 	enum IpScope
 	{
-		IP_SCOPE_NONE = 0,          // not an IP address -- also the number of classes, must be last entry
-		IP_SCOPE_LINK_LOCAL = 1,    // 169.254.x.x, IPv6 LL
-		IP_SCOPE_PRIVATE = 2,       // 10.x.x.x, etc.
-		IP_SCOPE_PSEUDOPRIVATE = 3, // 28.x.x.x, etc. -- unofficially unrouted IP blocks often "bogarted"
-		IP_SCOPE_SHARED = 4,        // 100.64.0.0/10, shared space for e.g. carrier-grade NAT
-		IP_SCOPE_GLOBAL = 5,        // globally routable IP address (all others)
-		IP_SCOPE_LOOPBACK = 6,      // 127.0.0.1
-		IP_SCOPE_MULTICAST = 7      // 224.0.0.0 and other multicast IPs
+		IP_SCOPE_NONE = 0,          // NULL or not an IP address
+		IP_SCOPE_MULTICAST = 1,     // 224.0.0.0 and other V4/V6 multicast IPs
+		IP_SCOPE_LOOPBACK = 2,      // 127.0.0.1, ::1, etc.
+		IP_SCOPE_PSEUDOPRIVATE = 3, // 28.x.x.x, etc. -- unofficially unrouted IPv4 blocks often "bogarted"
+		IP_SCOPE_GLOBAL = 4,        // globally routable IP address (all others)
+		IP_SCOPE_LINK_LOCAL = 5,    // 169.254.x.x, IPv6 LL
+		IP_SCOPE_SHARED = 6,        // 100.64.0.0/10, shared space for e.g. carrier-grade NAT
+		IP_SCOPE_PRIVATE = 7        // 10.x.x.x, 192.168.x.x, etc.
 	};
 
 	InetAddress() throw() { memset(this,0,sizeof(InetAddress)); }

+ 4 - 4
node/Node.cpp

@@ -432,11 +432,11 @@ void Node::freeQueryResult(void *qr)
 		::free(qr);
 }
 
-int Node::addLocalInterfaceAddress(const struct sockaddr_storage *addr,int metric,ZT1_LocalInterfaceAddressTrust trust,int reliable)
+int Node::addLocalInterfaceAddress(const struct sockaddr_storage *addr,int metric,ZT1_LocalInterfaceAddressTrust trust)
 {
 	if (Path::isAddressValidForPath(*(reinterpret_cast<const InetAddress *>(addr)))) {
 		Mutex::Lock _l(_directPaths_m);
-		_directPaths.push_back(Path(*(reinterpret_cast<const InetAddress *>(addr)),metric,(Path::Trust)trust,reliable != 0));
+		_directPaths.push_back(Path(*(reinterpret_cast<const InetAddress *>(addr)),metric,(Path::Trust)trust));
 		std::sort(_directPaths.begin(),_directPaths.end());
 		_directPaths.erase(std::unique(_directPaths.begin(),_directPaths.end()),_directPaths.end());
 		return 1;
@@ -711,10 +711,10 @@ void ZT1_Node_setNetconfMaster(ZT1_Node *node,void *networkControllerInstance)
 	} catch ( ... ) {}
 }
 
-int ZT1_Node_addLocalInterfaceAddress(ZT1_Node *node,const struct sockaddr_storage *addr,int metric,ZT1_LocalInterfaceAddressTrust trust,int reliable)
+int ZT1_Node_addLocalInterfaceAddress(ZT1_Node *node,const struct sockaddr_storage *addr,int metric,ZT1_LocalInterfaceAddressTrust trust)
 {
 	try {
-		return reinterpret_cast<ZeroTier::Node *>(node)->addLocalInterfaceAddress(addr,metric,trust,reliable);
+		return reinterpret_cast<ZeroTier::Node *>(node)->addLocalInterfaceAddress(addr,metric,trust);
 	} catch ( ... ) {
 		return 0;
 	}

+ 1 - 1
node/Node.hpp

@@ -105,7 +105,7 @@ public:
 	ZT1_VirtualNetworkConfig *networkConfig(uint64_t nwid) const;
 	ZT1_VirtualNetworkList *networks() const;
 	void freeQueryResult(void *qr);
-	int addLocalInterfaceAddress(const struct sockaddr_storage *addr,int metric,ZT1_LocalInterfaceAddressTrust trust,int reliable);
+	int addLocalInterfaceAddress(const struct sockaddr_storage *addr,int metric,ZT1_LocalInterfaceAddressTrust trust);
 	void clearLocalInterfaceAddresses();
 	void setNetconfMaster(void *networkControllerInstance);
 

+ 28 - 35
node/Packet.hpp

@@ -70,7 +70,7 @@
 
 /**
  * Maximum hop count allowed by packet structure (3 bits, 0-7)
- * 
+ *
  * This is a protocol constant. It's the maximum allowed by the length
  * of the hop counter -- three bits. See node/Constants.hpp for the
  * pragmatic forwarding limit, which is typically lower.
@@ -352,7 +352,7 @@ namespace ZeroTier {
 
 /**
  * ZeroTier packet
- * 
+ *
  * Packet format:
  *   <[8] random initialization vector (doubles as 64-bit packet ID)>
  *   <[5] destination ZT address>
@@ -362,7 +362,7 @@ namespace ZeroTier {
  *   [... -- begin encryption envelope -- ...]
  *   <[1] encrypted flags (top 3 bits) and verb (last 5 bits)>
  *   [... verb-specific payload ...]
- * 
+ *
  * Packets smaller than 28 bytes are invalid and silently discarded.
  *
  * The flags/cipher/hops bit field is: FFCCCHHH where C is a 3-bit cipher
@@ -384,15 +384,15 @@ class Packet : public Buffer<ZT_PROTO_MAX_PACKET_LENGTH>
 public:
 	/**
 	 * A packet fragment
-	 * 
+	 *
 	 * Fragments are sent if a packet is larger than UDP MTU. The first fragment
 	 * is sent with its normal header with the fragmented flag set. Remaining
 	 * fragments are sent this way.
-	 * 
+	 *
 	 * The fragmented bit indicates that there is at least one fragment. Fragments
 	 * themselves contain the total, so the receiver must "learn" this from the
 	 * first fragment it receives.
-	 * 
+	 *
 	 * Fragments are sent with the following format:
 	 *   <[8] packet ID of packet whose fragment this belongs to>
 	 *   <[5] destination ZT address>
@@ -430,7 +430,7 @@ public:
 
 		/**
 		 * Initialize from a packet
-		 * 
+		 *
 		 * @param p Original assembled packet
 		 * @param fragStart Start of fragment (raw index in packet data)
 		 * @param fragLen Length of fragment in bytes
@@ -446,7 +446,7 @@ public:
 
 		/**
 		 * Initialize from a packet
-		 * 
+		 *
 		 * @param p Original assembled packet
 		 * @param fragStart Start of fragment (raw index in packet data)
 		 * @param fragLen Length of fragment in bytes
@@ -473,7 +473,7 @@ public:
 
 		/**
 		 * Get this fragment's destination
-		 * 
+		 *
 		 * @return Destination ZT address
 		 */
 		inline Address destination() const { return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
@@ -872,7 +872,6 @@ public:
 		 *
 		 * Path record format:
 		 *   <[1] flags>
-		 *   <[1] metric from 0 (highest priority) to 255 (lowest priority)>
 		 *   <[2] length of extended path characteristics or 0 for none>
 		 *   <[...] extended path characteristics>
 		 *   <[1] address type>
@@ -882,9 +881,8 @@ public:
 		 * Path record flags:
 		 *   0x01 - Forget this path if it is currently known
 		 *   0x02 - Blacklist this path, do not use
-		 *   0x04 - Reliable path (no NAT keepalives, etc. are necessary)
-		 *   0x08 - Disable encryption (trust: privacy)
-		 *   0x10 - Disable encryption and authentication (trust: ultimate)
+		 *   0x04 - Disable encryption (trust: privacy)
+		 *   0x08 - Disable encryption and authentication (trust: ultimate)
 		 *
 		 * Address types and addresses are of the same format as the destination
 		 * address type and address in HELLO.
@@ -901,15 +899,10 @@ public:
 		 * is set.
 		 *
 		 * Only a subset of this functionality is currently implemented: basic
-		 * path pushing and learning. Metrics, most flags, and OK responses are
-		 * not yet implemented as of 1.0.4.
-		 *
-		 * OK response payload:
-		 *   <[2] 16-bit number of active direct paths we already have>
-		 *   <[2] 16-bit number of paths in push that we don't already have>
-		 *   <[2] 16-bit number of new paths we are trying (or will try)>
+		 * path pushing and learning. Blacklisting and trust are not fully
+		 * implemented yet (encryption is still always used).
 		 *
-		 * ERROR is presently not sent.
+		 * OK and ERROR are not generated.
 		 */
 		VERB_PUSH_DIRECT_PATHS = 16
 	};
@@ -974,7 +967,7 @@ public:
 
 	/**
 	 * Construct a new empty packet with a unique random packet ID
-	 * 
+	 *
 	 * Flags and hops will be zero. Other fields and data region are undefined.
 	 * Use the header access methods (setDestination() and friends) to fill out
 	 * the header. Payload should be appended; initial size is header size.
@@ -1004,7 +997,7 @@ public:
 
 	/**
 	 * Construct a new empty packet with a unique random packet ID
-	 * 
+	 *
 	 * @param dest Destination ZT address
 	 * @param source Source ZT address
 	 * @param v Verb
@@ -1021,7 +1014,7 @@ public:
 
 	/**
 	 * Reset this packet structure for reuse in place
-	 * 
+	 *
 	 * @param dest Destination ZT address
 	 * @param source Source ZT address
 	 * @param v Verb
@@ -1047,28 +1040,28 @@ public:
 
 	/**
 	 * Set this packet's destination
-	 * 
+	 *
 	 * @param dest ZeroTier address of destination
 	 */
 	inline void setDestination(const Address &dest) { dest.copyTo(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
 
 	/**
 	 * Set this packet's source
-	 * 
+	 *
 	 * @param source ZeroTier address of source
 	 */
 	inline void setSource(const Address &source) { source.copyTo(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
 
 	/**
 	 * Get this packet's destination
-	 * 
+	 *
 	 * @return Destination ZT address
 	 */
 	inline Address destination() const { return Address(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
 
 	/**
 	 * Get this packet's source
-	 * 
+	 *
 	 * @return Source ZT address
 	 */
 	inline Address source() const { return Address(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
@@ -1138,17 +1131,17 @@ public:
 
 	/**
 	 * Get this packet's unique ID (the IV field interpreted as uint64_t)
-	 * 
+	 *
 	 * @return Packet ID
 	 */
 	inline uint64_t packetId() const { return at<uint64_t>(ZT_PACKET_IDX_IV); }
 
 	/**
 	 * Set packet verb
-	 * 
+	 *
 	 * This also has the side-effect of clearing any verb flags, such as
 	 * compressed, and so must only be done during packet composition.
-	 * 
+	 *
 	 * @param v New packet verb
 	 */
 	inline void setVerb(Verb v) { (*this)[ZT_PACKET_IDX_VERB] = (char)v; }
@@ -1186,22 +1179,22 @@ public:
 
 	/**
 	 * Attempt to compress payload if not already (must be unencrypted)
-	 * 
+	 *
 	 * This requires that the payload at least contain the verb byte already
 	 * set. The compressed flag in the verb is set if compression successfully
 	 * results in a size reduction. If no size reduction occurs, compression
 	 * is not done and the flag is left cleared.
-	 * 
+	 *
 	 * @return True if compression occurred
 	 */
 	bool compress();
 
 	/**
 	 * Attempt to decompress payload if it is compressed (must be unencrypted)
-	 * 
+	 *
 	 * If payload is compressed, it is decompressed and the compressed verb
 	 * flag is cleared. Otherwise nothing is done and true is returned.
-	 * 
+	 *
 	 * @return True if data is now decompressed and valid, false on error
 	 */
 	bool uncompress();

+ 40 - 14
node/Path.hpp

@@ -34,10 +34,31 @@
 
 namespace ZeroTier {
 
+/**
+ * Base class for paths
+ *
+ * The base Path class is an immutable value.
+ */
 class Path
 {
 public:
-	// Must be the same values as ZT1_LocalInterfaceAddressTrust in ZeroTierOne.h
+	/**
+	 * Path trust category
+	 *
+	 * Note that this is NOT peer trust and has nothing to do with root server
+	 * designations or other trust metrics. This indicates how much we trust
+	 * this path to be secure and/or private. A trust level of normal means
+	 * encrypt and authenticate all traffic. Privacy trust means we can send
+	 * traffic in the clear. Ultimate trust means we don't even need
+	 * authentication. Generally a private path would be a hard-wired local
+	 * LAN, while an ultimate trust path would be a physically isolated private
+	 * server backplane.
+	 *
+	 * Nearly all paths will be normal trust. The other levels are for high
+	 * performance local SDN use only.
+	 *
+	 * These values MUST match ZT1_LocalInterfaceAddressTrust in ZeroTierOne.h
+	 */
 	enum Trust
 	{
 		TRUST_NORMAL = 0,
@@ -47,17 +68,15 @@ public:
 
 	Path() :
 		_addr(),
-		_metric(0),
-		_trust(TRUST_NORMAL),
-		_reliable(false)
+		_ipScope(InetAddress::IP_SCOPE_NONE),
+		_trust(TRUST_NORMAL)
 	{
 	}
 
-	Path(const InetAddress &addr,int metric,Trust trust,bool reliable) :
+	Path(const InetAddress &addr,int metric,Trust trust) :
 		_addr(addr),
-		_metric(metric),
-		_trust(trust),
-		_reliable(reliable)
+		_ipScope(addr.ipScope()),
+		_trust(trust)
 	{
 	}
 
@@ -67,9 +86,14 @@ public:
 	inline const InetAddress &address() const throw() { return _addr; }
 
 	/**
-	 * @return Metric (higher == worse) or negative if path is blacklisted
+	 * @return IP scope -- faster shortcut for address().ipScope()
+	 */
+	inline InetAddress::IpScope ipScope() const throw() { return _ipScope; }
+
+	/**
+	 * @return Preference rank, higher == better
 	 */
-	inline int metric() const throw() { return _metric; }
+	inline int preferenceRank() const throw() { return (int)_ipScope; } // IP scopes are in ascending rank order in InetAddress.hpp
 
 	/**
 	 * @return Path trust level
@@ -79,7 +103,10 @@ public:
 	/**
 	 * @return True if path is considered reliable (no NAT keepalives etc. are needed)
 	 */
-	inline bool reliable() const throw() { return _reliable; }
+	inline bool reliable() const throw()
+	{
+		return ((_ipScope != InetAddress::IP_SCOPE_GLOBAL)&&(_ipScope != InetAddress::IP_SCOPE_PSEUDOPRIVATE));
+	}
 
 	/**
 	 * @return True if address is non-NULL
@@ -127,11 +154,10 @@ public:
 		return false;
 	}
 
-protected:
+private:
 	InetAddress _addr;
-	int _metric; // negative == blacklisted
+	InetAddress::IpScope _ipScope; // memoize this since it's a computed value checked often
 	Trust _trust;
-	bool _reliable;
 };
 
 } // namespace ZeroTier

+ 38 - 20
node/Peer.cpp

@@ -161,6 +161,21 @@ void Peer::received(
 		_lastMulticastFrame = now;
 }
 
+RemotePath *Peer::getBestPath(uint64_t now)
+{
+	RemotePath *bestPath = (RemotePath *)0;
+	uint64_t lrMax = 0;
+	int rank = 0;
+	for(unsigned int p=0,np=_numPaths;p<np;++p) {
+		if ( (_paths[p].active(now)) && ((_paths[p].lastReceived() >= lrMax)||(_paths[p].preferenceRank() >= rank)) ) {
+			lrMax = _paths[p].lastReceived();
+			rank = _paths[p].preferenceRank();
+			bestPath = &(_paths[p]);
+		}
+	}
+	return bestPath;
+}
+
 void Peer::attemptToContactAt(const RuntimeEnvironment *RR,const InetAddress &atAddress,uint64_t now)
 {
 	Packet outp(_id.address(),RR->identity.address(),Packet::VERB_HELLO);
@@ -200,7 +215,7 @@ void Peer::doPingAndKeepalive(const RuntimeEnvironment *RR,uint64_t now)
 			TRACE("PING %s(%s)",_id.address().toString().c_str(),bestPath->address().toString().c_str());
 			attemptToContactAt(RR,bestPath->address(),now);
 			bestPath->sent(now);
-		} else if ((now - bestPath->lastSend()) >= ZT_NAT_KEEPALIVE_DELAY) {
+		} else if (((now - bestPath->lastSend()) >= ZT_NAT_KEEPALIVE_DELAY)&&(!bestPath->reliable())) {
 			TRACE("NAT keepalive %s(%s)",_id.address().toString().c_str(),bestPath->address().toString().c_str());
 			RR->node->putPacket(bestPath->address(),"",0);
 			bestPath->sent(now);
@@ -214,7 +229,17 @@ void Peer::pushDirectPaths(const RuntimeEnvironment *RR,RemotePath *path,uint64_
 		_lastDirectPathPush = now;
 
 		std::vector<Path> dps(RR->node->directPaths());
-		TRACE("pushing %u direct paths (local interface addresses) to %s",(unsigned int)dps.size(),_id.address().toString().c_str());
+#ifdef ZT_TRACE
+		{
+			std::string ps;
+			for(std::vector<Path>::const_iterator p(dps.begin());p!=dps.end();++p) {
+				if (ps.length() > 0)
+					ps.push_back(',');
+				ps.append(p->address().toString());
+			}
+			TRACE("pushing %u direct paths (local interface addresses) to %s: %s",(unsigned int)dps.size(),_id.address().toString().c_str(),ps.c_str());
+		}
+#endif
 
 		std::vector<Path>::const_iterator p(dps.begin());
 		while (p != dps.end()) {
@@ -230,32 +255,25 @@ void Peer::pushDirectPaths(const RuntimeEnvironment *RR,RemotePath *path,uint64_
 					case AF_INET6:
 						addressType = 6;
 						break;
-					default:
+					default: // we currently only push IP addresses
 						++p;
 						continue;
 				}
 
 				uint8_t flags = 0;
-				if (p->metric() < 0)
-					flags |= (0x01 | 0x02); // forget and blacklist
-				else {
-					if (p->reliable())
-						flags |= 0x04; // no NAT keepalives and such
-					switch(p->trust()) {
-						default:
-							break;
-						case Path::TRUST_PRIVACY:
-							flags |= 0x08; // no encryption
-							break;
-						case Path::TRUST_ULTIMATE:
-							flags |= (0x08 | 0x10); // no encryption, no authentication (redundant but go ahead and set both)
-							break;
-					}
+				switch(p->trust()) {
+					default:
+						break;
+					case Path::TRUST_PRIVACY:
+						flags |= 0x04; // no encryption
+						break;
+					case Path::TRUST_ULTIMATE:
+						flags |= (0x04 | 0x08); // no encryption, no authentication (redundant but go ahead and set both)
+						break;
 				}
 
 				outp.append(flags);
-				outp.append((uint8_t)((p->metric() >= 0) ? ((p->metric() <= 255) ? p->metric() : 255) : 0));
-				outp.append((uint16_t)0);
+				outp.append((uint16_t)0); // no extensions
 				outp.append(addressType);
 				outp.append((uint8_t)((addressType == 4) ? 6 : 18));
 				outp.append(p->address().rawIpData(),((addressType == 4) ? 4 : 16));

+ 2 - 13
node/Peer.hpp

@@ -102,7 +102,7 @@ public:
 	 *
 	 * This is called by the decode pipe when a packet is proven to be authentic
 	 * and appears to be valid.
-	 * 
+	 *
 	 * @param RR Runtime environment
 	 * @param remoteAddr Internet address of sender
 	 * @param hops ZeroTier (not IP) hops
@@ -126,18 +126,7 @@ public:
 	 * @param now Current time
 	 * @return Best path or NULL if there are no active (or fixed) direct paths
 	 */
-	inline RemotePath *getBestPath(uint64_t now)
-	{
-		RemotePath *bestPath = (RemotePath *)0;
-		uint64_t lrMax = 0;
-		for(unsigned int p=0,np=_numPaths;p<np;++p) {
-			if ((_paths[p].active(now))&&(_paths[p].lastReceived() >= lrMax)) {
-				lrMax = _paths[p].lastReceived();
-				bestPath = &(_paths[p]);
-			}
-		}
-		return bestPath;
-	}
+	RemotePath *getBestPath(uint64_t now);
 
 	/**
 	 * Send via best path

+ 2 - 2
node/RemotePath.hpp

@@ -56,7 +56,7 @@ public:
 		_fixed(false) {}
 
 	RemotePath(const InetAddress &addr,bool fixed) :
-		Path(addr,0,TRUST_NORMAL,false),
+		Path(addr,0,TRUST_NORMAL),
 		_lastSend(0),
 		_lastReceived(0),
 		_fixed(fixed) {}
@@ -123,7 +123,7 @@ public:
 	 */
 	inline bool send(const RuntimeEnvironment *RR,const void *data,unsigned int len,uint64_t now)
 	{
-		if (RR->node->putPacket(_addr,data,len)) {
+		if (RR->node->putPacket(address(),data,len)) {
 			sent(now);
 			RR->antiRec->logOutgoingZT(data,len);
 			return true;

+ 21 - 2
node/Switch.cpp

@@ -65,7 +65,8 @@ static const char *etherTypeName(const unsigned int etherType)
 #endif // ZT_TRACE
 
 Switch::Switch(const RuntimeEnvironment *renv) :
-	RR(renv)
+	RR(renv),
+	_lastBeaconResponse(0)
 {
 }
 
@@ -76,7 +77,25 @@ Switch::~Switch()
 void Switch::onRemotePacket(const InetAddress &fromAddr,const void *data,unsigned int len)
 {
 	try {
-		if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) {
+		if (len == 13) {
+			/* LEGACY: before VERB_PUSH_DIRECT_PATHS, peers used broadcast
+			 * announcements on the LAN to solve the 'same network problem.' We
+			 * no longer send these, but we'll listen for them for a while to
+			 * locate peers with versions <1.0.4. */
+			Address beaconAddr(reinterpret_cast<const char *>(data) + 8,5);
+			if (beaconAddr == RR->identity.address())
+				return;
+			SharedPtr<Peer> peer(RR->topology->getPeer(beaconAddr));
+			if (peer) { // we'll only respond to beacons from known peers
+				const uint64_t now = RR->node->now();
+				if ((now - _lastBeaconResponse) >= 2500) { // limit rate of responses
+					_lastBeaconResponse = now;
+					Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP);
+					outp.armor(peer->key(),false);
+					RR->node->putPacket(fromAddr,outp.data(),outp.size());
+				}
+			}
+		} else if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) {
 			if (((const unsigned char *)data)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) {
 				_handleRemotePacketFragment(fromAddr,data,len);
 			} else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) {

+ 5 - 4
node/Switch.hpp

@@ -99,20 +99,20 @@ public:
 
 	/**
 	 * Send a packet to a ZeroTier address (destination in packet)
-	 * 
+	 *
 	 * The packet must be fully composed with source and destination but not
 	 * yet encrypted. If the destination peer is known the packet
 	 * is sent immediately. Otherwise it is queued and a WHOIS is dispatched.
 	 *
 	 * The packet may be compressed. Compression isn't done here.
-	 * 
+	 *
 	 * Needless to say, the packet's source must be this node. Otherwise it
 	 * won't be encrypted right. (This is not used for relaying.)
 	 *
 	 * The network ID should only be specified for frames and other actual
 	 * network traffic. Other traffic such as controller requests and regular
 	 * protocol messages should specify zero.
-	 * 
+	 *
 	 * @param packet Packet to send
 	 * @param encrypt Encrypt packet payload? (always true except for HELLO)
 	 * @param nwid Related network ID or 0 if message is not in-network traffic
@@ -168,7 +168,7 @@ public:
 
 	/**
 	 * Perform retries and other periodic timer tasks
-	 * 
+	 *
 	 * This can return a very long delay if there are no pending timer
 	 * tasks. The caller should cap this comparatively vs. other values.
 	 *
@@ -184,6 +184,7 @@ private:
 	bool _trySend(const Packet &packet,bool encrypt,uint64_t nwid);
 
 	const RuntimeEnvironment *const RR;
+	uint64_t _lastBeaconResponse;
 
 	// Outsanding WHOIS requests and how many retries they've undergone
 	struct WhoisRequest

+ 1 - 1
selftest.cpp

@@ -916,7 +916,7 @@ int main(int argc,char **argv)
 	r |= testCertificate();
 	r |= testPhy();
 	r |= testResolver();
-	r |= testHttp();
+	//r |= testHttp();
 
 	if (r)
 		std::cout << std::endl << "SOMETHING FAILED!" << std::endl;

+ 1 - 1
service/OneService.cpp

@@ -592,7 +592,7 @@ public:
 								if (!isZT) {
 									InetAddress ip(ifa->ifa_addr);
 									ip.setPort(_port);
-									_node->addLocalInterfaceAddress(reinterpret_cast<const struct sockaddr_storage *>(&ip),0,ZT1_LOCAL_INTERFACE_ADDRESS_TRUST_NORMAL,0);
+									_node->addLocalInterfaceAddress(reinterpret_cast<const struct sockaddr_storage *>(&ip),0,ZT1_LOCAL_INTERFACE_ADDRESS_TRUST_NORMAL);
 								}
 							}
 							ifa = ifa->ifa_next;

+ 3 - 1
service/README.md

@@ -172,6 +172,7 @@ To create a new network with a random last six digits safely and atomically, you
 <tr><td>multicastLimit</td><td>integer</td><td>Maximum number of multicast recipients per multicast/broadcast address</td><td>yes</td></tr>
 <tr><td>creationTime</td><td>integer</td><td>Time network was created in ms since epoch</td><td>no</td></tr>
 <tr><td>revision</td><td>integer</td><td>Network config revision number</td><td>no</td></tr>
+<tr><td>memberRevisionCounter</td><td>integer</td><td>Current value of network revision counter (incremented after every member add or revision)</td><td>no</td></tr>
 <tr><td>members</td><td>[string]</td><td>Array of ZeroTier addresses of network members</td><td>no</td></tr>
 <tr><td>relays</td><td>[object]</td><td>Array of network-specific relay nodes (see below)</td><td>yes</td></tr>
 <tr><td>ipLocalRoutes</td><td>[string]</td><td>Array of IP network/netmask entries corresponding to networks routed directly via this interface (e.g. 10.0.0.0/8 to route 10.0.0.0 via this interface)</td></tr>
@@ -200,7 +201,7 @@ IP assignment pools are only used if they are within a network specified in ipLo
 <table>
 <tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>
 <tr><td>ipRangeStart</td><td>string</td><td>Start of IP assignment range</td></tr>
-<tr><td>ipRangeEnd</td><td>integer</td><td>End of IP assignment range</td></tr>
+<tr><td>ipRangeEnd</td><td>string</td><td>End of IP assignment range</td></tr>
 </table>
 
 **Rule object format:**
@@ -247,4 +248,5 @@ IP related fields apply only to Ethernet frames of type IPv4 or IPV6. Otherwise
 <tr><td>activeBridge</td><td>boolean</td><td>This member is an active network bridge</td><td>yes</td></tr>
 <tr><td>identity</td><td>string</td><td>Full ZeroTier identity of member</td><td>no</td></tr>
 <tr><td>ipAssignments</td><td>[string]</td><td>Array of IP/bits IP assignments</td><td>yes</td></tr>
+<tr><td>memberRevision</td><td>integer</td><td>Member revision counter value from network at time of last revision or member creation</td><td>no</td></tr>
 </table>