diff options
Diffstat (limited to 'node')
-rw-r--r-- | node/CertificateOfMembership.hpp | 28 | ||||
-rw-r--r-- | node/Constants.hpp | 8 | ||||
-rw-r--r-- | node/Dictionary.hpp | 123 | ||||
-rw-r--r-- | node/Network.cpp | 19 | ||||
-rw-r--r-- | node/NetworkConfigMaster.cpp | 222 | ||||
-rw-r--r-- | node/NetworkConfigMaster.hpp | 14 |
6 files changed, 359 insertions, 55 deletions
diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp index f76eb8ce..7a2b385b 100644 --- a/node/CertificateOfMembership.hpp +++ b/node/CertificateOfMembership.hpp @@ -92,12 +92,12 @@ public: enum ReservedId { /** - * Timestamp of certificate issue in milliseconds since epoch + * Revision number of certificate * - * maxDelta here defines certificate lifetime, and certs are lazily - * pushed to other peers on a net with a frequency of 1/2 this time. + * Certificates may differ in revision number by a designated max + * delta. Differences wider than this cause certificates not to agree. */ - COM_RESERVED_ID_TIMESTAMP = 0, + COM_RESERVED_ID_REVISION = 0, /** * Network ID for which certificate was issued @@ -123,14 +123,14 @@ public: /** * Create from required fields common to all networks * - * @param timestamp Timestamp of cert + * @param revision Revision number of certificate * @param timestampMaxDelta Maximum variation between timestamps on this net * @param nwid Network ID * @param issuedTo Certificate recipient */ - CertificateOfMembership(uint64_t timestamp,uint64_t timestampMaxDelta,uint64_t nwid,const Address &issuedTo) + CertificateOfMembership(uint64_t revision,uint64_t revisionMaxDelta,uint64_t nwid,const Address &issuedTo) { - _qualifiers.push_back(_Qualifier(COM_RESERVED_ID_TIMESTAMP,timestamp,timestampMaxDelta)); + _qualifiers.push_back(_Qualifier(COM_RESERVED_ID_REVISION,revision,revisionMaxDelta)); _qualifiers.push_back(_Qualifier(COM_RESERVED_ID_NETWORK_ID,nwid,0)); _qualifiers.push_back(_Qualifier(COM_RESERVED_ID_ISSUED_TO,issuedTo.toInt(),0xffffffffffffffffULL)); memset(_signature.data,0,_signature.size()); @@ -182,7 +182,7 @@ public: { if (_qualifiers.size() < 3) return false; - if (_qualifiers[0].id != COM_RESERVED_ID_TIMESTAMP) + if (_qualifiers[0].id != COM_RESERVED_ID_REVISION) return false; if (_qualifiers[1].id != COM_RESERVED_ID_NETWORK_ID) return false; @@ -192,26 +192,26 @@ public: } /** - * @return Maximum delta for mandatory timestamp field or 0 if field missing + * @return Maximum delta for mandatory revision field or 0 if field missing */ - inline uint64_t timestampMaxDelta() const + inline uint64_t revisionMaxDelta() const throw() { for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { - if (q->id == COM_RESERVED_ID_TIMESTAMP) + if (q->id == COM_RESERVED_ID_REVISION) return q->maxDelta; } return 0ULL; } /** - * @return Timestamp for this cert in ms since epoch (according to netconf's clock) + * @return Revision number for this cert */ - inline uint64_t timestamp() const + inline uint64_t revision() const throw() { for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) { - if (q->id == COM_RESERVED_ID_TIMESTAMP) + if (q->id == COM_RESERVED_ID_REVISION) return q->value; } return 0ULL; diff --git a/node/Constants.hpp b/node/Constants.hpp index 557c8208..af1322ab 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -370,14 +370,6 @@ #define ZT_ANTIRECURSION_HISTORY_SIZE 16 /** - * TTL for certificates of membership on private networks - * - * This is the max delta for the timestamp field of a COM, so it's a window - * plus or minus the certificate's timestamp. In milliseconds. - */ -#define ZT_NETWORK_CERTIFICATE_TTL_WINDOW (ZT_NETWORK_AUTOCONF_DELAY * 4) - -/** * How often to broadcast beacons over physical local LANs */ #define ZT_BEACON_INTERVAL 30000 diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp index c0bc13a2..a104ea1a 100644 --- a/node/Dictionary.hpp +++ b/node/Dictionary.hpp @@ -35,6 +35,7 @@ #include <stdexcept> #include "Constants.hpp" +#include "Utils.hpp" // Three fields are added/updated by sign() #define ZT_DICTIONARY_SIGNATURE "~!ed25519" @@ -108,6 +109,128 @@ public: } /** + * @param key Key to get + * @param dfl Default boolean result if key not found or empty (default: false) + * @return Boolean value of key + */ + inline bool getBoolean(const std::string &key,bool dfl = false) const + { + const_iterator e(find(key)); + if (e == end()) + return dfl; + if (e->second.length() < 1) + return dfl; + switch(e->second[0]) { + case '1': + case 't': + case 'T': + case 'y': + case 'Y': + return true; + } + return false; + } + + /** + * @param key Key to get + * @param dfl Default value if not present (default: 0) + * @return Value converted to unsigned 64-bit int or 0 if not found + */ + inline uint64_t getUInt(const std::string &key,uint64_t dfl = 0) const + { + const_iterator e(find(key)); + if (e == end()) + return dfl; + return Utils::strToU64(e->second.c_str()); + } + + /** + * @param key Key to get + * @param dfl Default value if not present (default: 0) + * @return Value converted to unsigned 64-bit int or 0 if not found + */ + inline uint64_t getHexUInt(const std::string &key,uint64_t dfl = 0) const + { + const_iterator e(find(key)); + if (e == end()) + return dfl; + return Utils::hexStrToU64(e->second.c_str()); + } + + /** + * @param key Key to get + * @param dfl Default value if not present (default: 0) + * @return Value converted to signed 64-bit int or 0 if not found + */ + inline int64_t getInt(const std::string &key,int64_t dfl = 0) const + { + const_iterator e(find(key)); + if (e == end()) + return dfl; + return Utils::strTo64(e->second.c_str()); + } + + /** + * @param key Key to set + * @param value String value + */ + inline void set(const std::string &key,const char *value) + { + (*this)[key] = value; + } + + /** + * @param key Key to set + * @param value String value + */ + inline void set(const std::string &key,const std::string &value) + { + (*this)[key] = value; + } + + /** + * @param key Key to set + * @param value Boolean value + */ + inline void set(const std::string &key,bool value) + { + (*this)[key] = ((value) ? "1" : "0"); + } + + /** + * @param key Key to set + * @param value Integer value + */ + inline void set(const std::string &key,uint64_t value) + { + char tmp[24]; + Utils::snprintf(tmp,sizeof(tmp),"%llu",(unsigned long long)value); + (*this)[key] = tmp; + } + + /** + * @param key Key to set + * @param value Integer value + */ + inline void set(const std::string &key,int64_t value) + { + char tmp[24]; + Utils::snprintf(tmp,sizeof(tmp),"%lld",(long long)value); + (*this)[key] = tmp; + } + + /** + * @param key Key to set + * @param value Integer value + */ + inline void setHex(const std::string &key,uint64_t value) + { + char tmp[24]; + Utils::snprintf(tmp,sizeof(tmp),"%llx",(unsigned long long)value); + (*this)[key] = tmp; + } + + /** * @param key Key to check * @return True if dictionary contains key */ diff --git a/node/Network.cpp b/node/Network.cpp index 26d8684f..df741026 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -352,7 +352,7 @@ void Network::addMembershipCertificate(const CertificateOfMembership &cert,bool } // If we made it past authentication, update cert - if (cert.timestamp() >= old.timestamp()) + if (cert.revision() != old.revision()) old = cert; } @@ -360,17 +360,10 @@ bool Network::peerNeedsOurMembershipCertificate(const Address &to,uint64_t now) { Mutex::Lock _l(_lock); if ((_config)&&(!_config->isPublic())&&(_config->com())) { - uint64_t pushInterval = _config->com().timestampMaxDelta() / 2; - if (pushInterval) { - // Give a 1s margin around +/- 1/2 max delta to account for network latency - if (pushInterval > 1000) - pushInterval -= 1000; - - uint64_t &lastPushed = _lastPushedMembershipCertificate[to]; - if ((now - lastPushed) > pushInterval) { - lastPushed = now; - return true; - } + uint64_t &lastPushed = _lastPushedMembershipCertificate[to]; + if ((now - lastPushed) > (ZT_NETWORK_AUTOCONF_DELAY / 2)) { + lastPushed = now; + return true; } } return false; @@ -421,7 +414,7 @@ void Network::clean() // Clean entries from the last pushed tracking map if they're so old as // to be no longer relevant. - uint64_t forgetIfBefore = now - (_config->com().timestampMaxDelta() * 3ULL); + uint64_t forgetIfBefore = now - (ZT_PEER_ACTIVITY_TIMEOUT * 16); // arbitrary reasonable cutoff for(std::map<Address,uint64_t>::iterator lp(_lastPushedMembershipCertificate.begin());lp!=_lastPushedMembershipCertificate.end();) { if (lp->second < forgetIfBefore) _lastPushedMembershipCertificate.erase(lp++); diff --git a/node/NetworkConfigMaster.cpp b/node/NetworkConfigMaster.cpp index 3357b150..3311dc65 100644 --- a/node/NetworkConfigMaster.cpp +++ b/node/NetworkConfigMaster.cpp @@ -26,6 +26,7 @@ */ #include "Constants.hpp" +#include "NetworkConfigMaster.hpp" #ifdef ZT_ENABLE_NETCONF_MASTER @@ -37,7 +38,6 @@ #include <sys/time.h> #include <sys/types.h> -#include "NetworkConfigMaster.hpp" #include "RuntimeEnvironment.hpp" #include "Switch.hpp" #include "Packet.hpp" @@ -45,6 +45,9 @@ #include "Utils.hpp" #include "Node.hpp" #include "Logger.hpp" +#include "Topology.hpp" +#include "Peer.hpp" +#include "CertificateOfMembership.hpp" // Redis timeout in seconds #define ZT_NETCONF_REDIS_TIMEOUT 10 @@ -74,13 +77,93 @@ NetworkConfigMaster::~NetworkConfigMaster() redisFree(_rc); } -void NetworkConfigMaster::doNetworkConfigRequest( - uint64_t packetId, - const Address &from, - uint64_t nwid, - const Dictionary &metaData, - uint64_t haveTimestamp) +void NetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uint64_t packetId,const Address &member,uint64_t nwid,const Dictionary &metaData,uint64_t haveTimestamp) { + char memberKey[256],nwids[24],addrs[16],nwKey[256]; + Dictionary memberRecord; + std::string revision,tmps2; + + Mutex::Lock _l(_lock); + + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)member.toInt()); + Utils::snprintf(memberKey,sizeof(memberKey),"zt1:network:%s:member:%s:~",nwids,addrs); + Utils::snprintf(nwKey,sizeof(nwKey),"zt1:network:%s:~",nwids); + + TRACE("netconf: request from %s for %s (if newer than %llu)",addrs,nwids,(unsigned long long)haveTimestamp); + + if (!_hget(nwKey,"id",tmps2)) { + LOG("netconf: Redis error retrieving %s/id",nwKey); + return; + } + if (tmps2 != nwids) { + TRACE("netconf: network %s not found",nwids); + Packet outp(member,RR->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(packetId); + outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND); + outp.append(nwid); + RR->sw->send(outp,true); + return; + } + + if (!_hget(nwKey,"revision",revision)) { + LOG("netconf: Redis error retrieving %s/revision",nwKey); + return; + } + if (!revision.length()) + revision = "0"; + + if (!_hgetall(memberKey,memberRecord)) { + LOG("netconf: Redis error retrieving %s",memberKey); + return; + } + + if ((memberRecord.size() == 0)||(memberRecord.get("id","") != addrs)||(memberRecord.get("nwid","") != nwids)) { + if (!_initNewMember(nwid,member,metaData,memberRecord)) + return; + } + + if (memberRecord.getBoolean("authorized")) { + uint64_t ts = memberRecord.getHexUInt("netconfTimestamp",0); + std::string netconf(memberRecord.get("netconf","")); + + Dictionary upd; + upd.setHex("netconfClientTimestamp",haveTimestamp); + if (fromAddr) + upd.set("lastAt",fromAddr.toString()); + upd.setHex("lastSeen",Utils::now()); + _hmset(memberKey,upd); + + if (((ts == 0)||(netconf.length() == 0))||(memberRecord.get("netconfRevision","") != revision)) { + if (!_generateNetconf(nwid,member,metaData,netconf,ts)) + return; + } + + if (ts > haveTimestamp) { + TRACE("netconf: sending %u bytes of netconf data to %s",netconf.length(),addrs); + Packet outp(member,RR->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(packetId); + outp.append(nwid); + outp.append((uint16_t)netconf.length()); + outp.append(netconf.data(),netconf.length()); + outp.compress(); + if (outp.size() > ZT_PROTO_MAX_PACKET_LENGTH) { // sanity check -- this would be weird + TRACE("netconf: compressed packet exceeds ZT_PROTO_MAX_PACKET_LENGTH!"); + return; + } + RR->sw->send(outp,true); + } + } else { + TRACE("netconf: access denied for %s on %s",addrs,nwids); + Packet outp(member,RR->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(packetId); + outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_); + outp.append(nwid); + RR->sw->send(outp,true); + } } bool NetworkConfigMaster::_reconnect() @@ -92,7 +175,7 @@ bool NetworkConfigMaster::_reconnect() tv.tv_sec = ZT_NETCONF_REDIS_TIMEOUT; tv.tv_usec = 0; - _rc = redisConnectWithTimeout(_redisHost.c_str(),_redisPort,&tv); + _rc = redisConnectWithTimeout(_redisHost.c_str(),_redisPort,tv); if (!_rc) return false; if (_rc->err) { @@ -100,14 +183,14 @@ bool NetworkConfigMaster::_reconnect() _rc = (redisContext *)0; return false; } - redisSetTimeout(_rc,&tv); // necessary??? + redisSetTimeout(_rc,tv); // necessary??? // TODO: support AUTH and SELECT !!! return true; } -bool NetworkConfigMaster::_hgetall(const char *key,std::map<std::string,std::string> &hdata) +bool NetworkConfigMaster::_hgetall(const char *key,Dictionary &hdata) { if (!_rc) { if (!_reconnect()) @@ -125,11 +208,11 @@ bool NetworkConfigMaster::_hgetall(const char *key,std::map<std::string,std::str if (reply->type == REDIS_REPLY_ARRAY) { for(long i=0;i<reply->elements;) { try { - const char *k = reply->elements[i]->str; + const char *k = reply->element[i]->str; if (++i >= reply->elements) break; - if ((k)&&(reply->elements[i]->str)) - hdata[k] = reply->elements[i]->str; + if ((k)&&(reply->element[i]->str)) + hdata[k] = reply->element[i]->str; ++i; } catch ( ... ) { break; // memory safety @@ -142,9 +225,9 @@ bool NetworkConfigMaster::_hgetall(const char *key,std::map<std::string,std::str return true; } -bool NetworkConfigMaster::_hmset(const char *key,const std::map<std::string,std::string> &hdata) +bool NetworkConfigMaster::_hmset(const char *key,const Dictionary &hdata) { - const const char *hargv[1024]; + const char *hargv[1024]; if (!hdata.size()) return true; @@ -157,7 +240,7 @@ bool NetworkConfigMaster::_hmset(const char *key,const std::map<std::string,std: hargv[0] = "HMSET"; hargv[1] = key; int hargc = 2; - for(std::map<std::string,std::string>::const_iterator i(hdata.begin());i!=hdata.end();++i) { + for(Dictionary::const_iterator i(hdata.begin());i!=hdata.end();++i) { if (hargc >= 1024) break; hargv[hargc++] = i->first.c_str(); @@ -228,6 +311,113 @@ bool NetworkConfigMaster::_hset(const char *key,const char *hashKey,const char * return true; } +bool NetworkConfigMaster::_initNewMember(uint64_t nwid,const Address &member,const Dictionary &metaData,Dictionary &memberRecord) +{ + char memberKey[256],nwids[24],addrs[16],nwKey[256]; + Dictionary networkRecord; + + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)member.toInt()); + Utils::snprintf(memberKey,sizeof(memberKey),"zt1:network:%s:member:%s:~",nwids,addrs); + Utils::snprintf(nwKey,sizeof(nwKey),"zt1:network:%s:~",nwids); + + if (!_hgetall(nwKey,networkRecord)) { + LOG("netconf: Redis error retrieving %s",nwKey); + return false; + } + if (networkRecord.get("id","") != nwids) { + TRACE("netconf: network %s not found (initNewMember)",nwids); + return false; + } + + memberRecord.clear(); + memberRecord["id"] = addrs; + memberRecord["nwid"] = nwids; + memberRecord["authorized"] = (networkRecord.getBoolean("private",true) ? "0" : "1"); // auto-authorize on public networks + memberRecord.setHex("firstSeen",Utils::now()); + { + SharedPtr<Peer> peer(RR->topology->getPeer(member)); + if (peer) + memberRecord["identity"] = peer->identity().toString(false); + } + + if (!_hmset(memberKey,memberRecord)) { + LOG("netconf: Redis error storing %s for new member %s",memberKey,addrs); + return false; + } + + return true; +} + +bool NetworkConfigMaster::_generateNetconf(uint64_t nwid,const Address &member,const Dictionary &metaData,std::string &netconf,uint64_t &ts) +{ + char memberKey[256],nwids[24],addrs[16],tss[24],nwKey[256]; + Dictionary networkRecord,memberRecord,nc; + + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)member.toInt()); + Utils::snprintf(memberKey,sizeof(memberKey),"zt1:network:%s:member:%s:~",nwids,addrs); + Utils::snprintf(nwKey,sizeof(nwKey),"zt1:network:%s:~",nwids); + + if (!_hgetall(nwKey,networkRecord)) { + LOG("netconf: Redis error retrieving %s",nwKey); + return false; + } + if (networkRecord.get("id","") != nwids) { + TRACE("netconf: network %s not found (generateNetconf)",nwids); + return false; + } + + if (!_hgetall(memberKey,memberRecord)) { + LOG("netconf: Redis error retrieving %s",memberKey); + return false; + } + + uint64_t revision = networkRecord.getHexUInt("revision",0); + bool isPrivate = networkRecord.getBoolean("private",true); + ts = Utils::now(); + Utils::snprintf(tss,sizeof(tss),"%llx",ts); + + nc[ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP] = tss; + nc[ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID] = nwids; + nc[ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO] = addrs; + nc[ZT_NETWORKCONFIG_DICT_KEY_PRIVATE] = isPrivate ? "1" : "0"; + nc[ZT_NETWORKCONFIG_DICT_KEY_NAME] = networkRecord.get("name",nwids); + nc[ZT_NETWORKCONFIG_DICT_KEY_DESC] = networkRecord.get("desc",""); + nc[ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST] = networkRecord.getBoolean("enableBroadcast",true) ? "1" : "0"; + nc[ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING] = networkRecord.getBoolean("allowPassiveBridging",false) ? "1" : "0"; + nc[ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES] = networkRecord.get("etherTypes",""); + nc[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES] = networkRecord.get("multicastRates",""); + + uint64_t ml = networkRecord.getHexUInt("multicastLimit",0); + if (ml > 0) + nc.setHex(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,ml); + + std::string activeBridgeList; + if (activeBridgeList.length() > 0) + nc[ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES] = activeBridgeList; + + std::string v4s,v6s; + if (v4s.length()) + nc[ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC] = v4s; + if (v6s.length()) + nc[ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC] = v6s; + + if (isPrivate) { + CertificateOfMembership com(revision,2,nwid,member); + if (com.sign(RR->identity)) + nc[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = com.toString(); + } + + netconf = nc.toString(); + + _hset(memberKey,"netconf",netconf.c_str()); + _hset(memberKey,"netconfTimestamp",tss); + _hset(memberKey,"netconfRevision",networkRecord.get("revision","0").c_str()); + + return true; +} + } // namespace ZeroTier #endif // ZT_ENABLE_NETCONF_MASTER diff --git a/node/NetworkConfigMaster.hpp b/node/NetworkConfigMaster.hpp index 636b197e..29aada27 100644 --- a/node/NetworkConfigMaster.hpp +++ b/node/NetworkConfigMaster.hpp @@ -41,6 +41,7 @@ #include "Address.hpp" #include "Dictionary.hpp" #include "Mutex.hpp" +#include "InetAddress.hpp" #include <hiredis/hiredis.h> @@ -82,15 +83,17 @@ public: * This is a blocking call, so rate is limited by Redis. It will fail * and log its failure if the Redis server is not available or times out. * + * @param fromAddr Originating IP address * @param packetId 64-bit packet ID - * @param from Originating peer ZeroTier address + * @param member Originating peer ZeroTier address * @param nwid 64-bit network ID * @param metaData Meta-data bundled with request (empty if none) * @param haveTimestamp Timestamp requesting peer has or 0 if none or not included */ void doNetworkConfigRequest( + const InetAddress &fromAddr, uint64_t packetId, - const Address &from, + const Address &member, uint64_t nwid, const Dictionary &metaData, uint64_t haveTimestamp); @@ -98,11 +101,14 @@ public: private: // These assume _lock is locked bool _reconnect(); - bool _hgetall(const char *key,std::map<std::string,std::string> &hdata); - bool _hmset(const char *key,const std::map<std::string,std::string> &hdata); + bool _hgetall(const char *key,Dictionary &hdata); + bool _hmset(const char *key,const Dictionary &hdata); bool _hget(const char *key,const char *hashKey,std::string &value); bool _hset(const char *key,const char *hashKey,const char *value); + bool _initNewMember(uint64_t nwid,const Address &member,const Dictionary &metaData,Dictionary &memberRecord); + bool _generateNetconf(uint64_t nwid,const Address &member,const Dictionary &metaData,std::string &netconf,uint64_t &ts); + Mutex _lock; std::string _redisHost; |