diff options
-rw-r--r-- | controller/SqliteNetworkController.cpp | 50 | ||||
-rw-r--r-- | controller/SqliteNetworkController.hpp | 1 | ||||
-rw-r--r-- | controller/schema.sql | 5 | ||||
-rw-r--r-- | controller/schema.sql.c | 5 | ||||
-rw-r--r-- | include/ZeroTierOne.h | 5 | ||||
-rw-r--r-- | node/Dictionary.hpp | 17 | ||||
-rw-r--r-- | node/IncomingPacket.cpp | 24 | ||||
-rw-r--r-- | node/InetAddress.hpp | 19 | ||||
-rw-r--r-- | node/Node.cpp | 8 | ||||
-rw-r--r-- | node/Node.hpp | 2 | ||||
-rw-r--r-- | node/Packet.hpp | 63 | ||||
-rw-r--r-- | node/Path.hpp | 54 | ||||
-rw-r--r-- | node/Peer.cpp | 58 | ||||
-rw-r--r-- | node/Peer.hpp | 15 | ||||
-rw-r--r-- | node/RemotePath.hpp | 4 | ||||
-rw-r--r-- | node/Switch.cpp | 23 | ||||
-rw-r--r-- | node/Switch.hpp | 9 | ||||
-rw-r--r-- | selftest.cpp | 2 | ||||
-rw-r--r-- | service/OneService.cpp | 2 | ||||
-rw-r--r-- | service/README.md | 4 |
20 files changed, 237 insertions, 133 deletions
diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index bba8f43b..cc53d4d5 100644 --- a/controller/SqliteNetworkController.cpp +++ b/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); diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp index a999bf7c..90cddec0 100644 --- a/controller/SqliteNetworkController.hpp +++ b/controller/SqliteNetworkController.hpp @@ -137,6 +137,7 @@ private: sqlite3_stmt *_sGetGateways; sqlite3_stmt *_sDeleteGateways; sqlite3_stmt *_sCreateGateway; + sqlite3_stmt *_sIncrementMemberRevisionCounter; Mutex _lock; }; diff --git a/controller/schema.sql b/controller/schema.sql index 94e611b9..c3eec234 100644 --- a/controller/schema.sql +++ b/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, diff --git a/controller/schema.sql.c b/controller/schema.sql.c index 30b88850..78123db4 100644 --- a/controller/schema.sql.c +++ b/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"\ diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index 446bbc77..7ae524a8 100644 --- a/include/ZeroTierOne.h +++ b/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 diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp index 8628bc44..1e643788 100644 --- a/node/Dictionary.hpp +++ b/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); diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 6c3a0932..ae99352e 100644 --- a/node/IncomingPacket.cpp +++ b/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; diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp index 35fd4314..e3537ce0 100644 --- a/node/InetAddress.hpp +++ b/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)); } diff --git a/node/Node.cpp b/node/Node.cpp index 3df34aec..ebe0527e 100644 --- a/node/Node.cpp +++ b/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; } diff --git a/node/Node.hpp b/node/Node.hpp index 579d3a57..0e966aa6 100644 --- a/node/Node.hpp +++ b/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); diff --git a/node/Packet.hpp b/node/Packet.hpp index e84306c2..fa377964 100644 --- a/node/Packet.hpp +++ b/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(); diff --git a/node/Path.hpp b/node/Path.hpp index cd21444b..0e53772d 100644 --- a/node/Path.hpp +++ b/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 diff --git a/node/Peer.cpp b/node/Peer.cpp index 84aa8bef..73c20228 100644 --- a/node/Peer.cpp +++ b/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)); diff --git a/node/Peer.hpp b/node/Peer.hpp index f5118794..283e3f33 100644 --- a/node/Peer.hpp +++ b/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 diff --git a/node/RemotePath.hpp b/node/RemotePath.hpp index 5592c8e1..291943c9 100644 --- a/node/RemotePath.hpp +++ b/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; diff --git a/node/Switch.cpp b/node/Switch.cpp index 4fd5d769..cf4fe249 100644 --- a/node/Switch.cpp +++ b/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) { diff --git a/node/Switch.hpp b/node/Switch.hpp index 89c9a56b..e7f1523a 100644 --- a/node/Switch.hpp +++ b/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 diff --git a/selftest.cpp b/selftest.cpp index f84abcc0..cf20fdf3 100644 --- a/selftest.cpp +++ b/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; diff --git a/service/OneService.cpp b/service/OneService.cpp index bde59d56..312e0bf2 100644 --- a/service/OneService.cpp +++ b/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; diff --git a/service/README.md b/service/README.md index 873e577f..d2dc6e91 100644 --- a/service/README.md +++ b/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> |