diff options
-rw-r--r-- | node/IncomingPacket.cpp | 27 | ||||
-rw-r--r-- | node/InetAddress.cpp | 2 | ||||
-rw-r--r-- | node/InetAddress.h | 0 | ||||
-rw-r--r-- | node/NetworkConfigMaster.cpp | 250 | ||||
-rw-r--r-- | node/NetworkConfigMaster.hpp | 3 | ||||
-rw-r--r-- | node/Node.cpp | 21 | ||||
-rw-r--r-- | node/Packet.hpp | 67 | ||||
-rw-r--r-- | node/RuntimeEnvironment.hpp | 3 | ||||
-rw-r--r-- | redis-schema.md | 42 |
9 files changed, 302 insertions, 113 deletions
diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 44511c13..ded83d67 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -40,6 +40,7 @@ #include "Peer.hpp" #include "NodeConfig.hpp" #include "SoftwareUpdater.hpp" +#include "NetworkConfigMaster.hpp" namespace ZeroTier { @@ -713,6 +714,24 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons { try { uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID); + unsigned int metaDataLength = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN); + Dictionary metaData((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength),metaDataLength); + uint64_t haveTimestamp = 0; + if ((ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT + metaDataLength + 8) <= size()) + haveTimestamp = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT + metaDataLength); + + if (RR->netconfMaster) { + RR->netconfMaster->doNetworkConfigRequest(_remoteAddress,packetId(),source(),nwid,metaData,haveTimestamp); + } else { + Packet outp(source(),RR->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(packetId()); + outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION); + outp.append(nwid); + outp.armor(peer->key(),true); + _fromSock->send(_remoteAddress,outp.data(),outp.size()); + } + peer->received(RR,_fromSock,_remoteAddress,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,Utils::now()); } catch (std::exception &exc) { TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what()); @@ -725,6 +744,14 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons bool IncomingPacket::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer) { try { + unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; + while ((ptr + 8) <= size()) { + uint64_t nwid = at<uint64_t>(ptr); + SharedPtr<Network> nw(RR->nc->network(nwid)); + if ((nw)&&(source() == nw->controller())) + nw->requestConfiguration(); + ptr += 8; + } peer->received(RR,_fromSock,_remoteAddress,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,Utils::now()); } catch (std::exception &exc) { TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what()); diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp index 18c7f031..a27182a0 100644 --- a/node/InetAddress.cpp +++ b/node/InetAddress.cpp @@ -54,7 +54,7 @@ void InetAddress::set(const std::string &ip,unsigned int port) } else { _sa.sin.sin_family = AF_INET; _sa.sin.sin_port = Utils::hton((uint16_t)port); - if (inet_pton(AF_INET,ip.c_str(),(void *)&(_sa.sin.sin_addr.s_addr)) <= 0) + if (inet_pton(AF_INET,ip.c_str(),(void *)&(_sa.sin.sin_addr.s_addr)) <= 0) _sa.saddr.sa_family = 0; } } diff --git a/node/InetAddress.h b/node/InetAddress.h deleted file mode 100644 index e69de29b..00000000 --- a/node/InetAddress.h +++ /dev/null diff --git a/node/NetworkConfigMaster.cpp b/node/NetworkConfigMaster.cpp index 3311dc65..cbf7252c 100644 --- a/node/NetworkConfigMaster.cpp +++ b/node/NetworkConfigMaster.cpp @@ -38,6 +38,9 @@ #include <sys/time.h> #include <sys/types.h> +#include <algorithm> +#include <utility> + #include "RuntimeEnvironment.hpp" #include "Switch.hpp" #include "Packet.hpp" @@ -79,7 +82,7 @@ NetworkConfigMaster::~NetworkConfigMaster() 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]; + char memberKey[128],nwids[24],addrs[16],nwKey[128],revKey[128]; Dictionary memberRecord; std::string revision,tmps2; @@ -89,6 +92,7 @@ void NetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uin 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); + Utils::snprintf(revKey,sizeof(revKey),"zt1:network:%s:revision",nwids); TRACE("netconf: request from %s for %s (if newer than %llu)",addrs,nwids,(unsigned long long)haveTimestamp); @@ -107,8 +111,8 @@ void NetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uin return; } - if (!_hget(nwKey,"revision",revision)) { - LOG("netconf: Redis error retrieving %s/revision",nwKey); + if (!_get(revKey,revision)) { + LOG("netconf: Redis error retrieving %s",revKey); return; } if (!revision.length()) @@ -311,6 +315,54 @@ bool NetworkConfigMaster::_hset(const char *key,const char *hashKey,const char * return true; } +bool NetworkConfigMaster::_get(const char *key,std::string &value) +{ + if (!_rc) { + if (!_reconnect()) + return false; + } + + redisReply *reply = (redisReply *)redisCommand(_rc,"GET %s",key); + if (!reply) { + if (_reconnect()) + return _get(key,value); + return false; + } + + if ((reply->type == REDIS_REPLY_STRING)&&(reply->str)) + value = reply->str; + else value = ""; + + freeReplyObject(reply); + + return true; +} + +bool NetworkConfigMaster::_smembers(const char *key,std::vector<std::string> &sdata) +{ + if (!_rc) { + if (!_reconnect()) + return false; + } + + redisReply *reply = (redisReply *)redisCommand(_rc,"SMEMBERS %s",key); + if (!reply) { + if (_reconnect()) + return _smembers(key,sdata); + return false; + } + + sdata.clear(); + if (reply->type == REDIS_REPLY_ARRAY) { + for(long i=0;i<reply->elements;++i) { + if (reply->element[i]->str) + sdata.push_back(reply->element[i]->str); + } + } + + 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]; @@ -351,13 +403,17 @@ bool NetworkConfigMaster::_initNewMember(uint64_t nwid,const Address &member,con 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]; + char memberKey[256],nwids[24],addrs[16],tss[24],nwKey[256],revKey[128],abKey[128],ipaKey[128]; Dictionary networkRecord,memberRecord,nc; + std::string revision; + Utils::snprintf(memberKey,sizeof(memberKey),"zt1:network:%s:member:%s:~",nwids,addrs); 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); + Utils::snprintf(revKey,sizeof(revKey),"zt1:network:%s:revision",nwids); + Utils::snprintf(abKey,sizeof(revKey),"zt1:network:%s:activeBridges",nwids); + Utils::snprintf(ipaKey,sizeof(revKey),"zt1:network:%s:ipAssignments",nwids); if (!_hgetall(nwKey,networkRecord)) { LOG("netconf: Redis error retrieving %s",nwKey); @@ -373,11 +429,18 @@ bool NetworkConfigMaster::_generateNetconf(uint64_t nwid,const Address &member,c return false; } - uint64_t revision = networkRecord.getHexUInt("revision",0); + if (!_get(revKey,revision)) { + LOG("netconf: Redis error retrieving %s",revKey); + return false; + } + if (!revision.length()) + revision = "0"; + bool isPrivate = networkRecord.getBoolean("private",true); ts = Utils::now(); Utils::snprintf(tss,sizeof(tss),"%llx",ts); + // Core configuration nc[ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP] = tss; nc[ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID] = nwids; nc[ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO] = addrs; @@ -387,33 +450,180 @@ bool NetworkConfigMaster::_generateNetconf(uint64_t nwid,const Address &member,c 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",""); + // Multicast options + 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; + // Active bridge configuration + { + std::string activeBridgeList; + std::vector<std::string> activeBridgeSet; + if (!_smembers(abKey,activeBridgeSet)) { + LOG("netconf: Redis error retrieving set %s",abKey); + return false; + } + std::sort(activeBridgeSet.begin(),activeBridgeSet.end()); + for(std::vector<std::string>::const_iterator i(activeBridgeSet.begin());i!=activeBridgeSet.end();++i) { + if (i->length() == 10) { + if (activeBridgeList.length() > 0) + activeBridgeList.push_back(','); + activeBridgeList.append(*i); + } + } + if (activeBridgeList.length() > 0) + nc[ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES] = activeBridgeList; + } + + // IP address assignment and auto-assign using the ZeroTier-internal mechanism (not DHCP, etc.) + { + std::string ipAssignments(memberRecord.get("ipAssignments","")); + + // Get sorted, separated lists of IPv4 and IPv6 IP address assignments already present + std::vector<InetAddress> ip4s,ip6s; + { + std::vector<std::string> ips(Utils::split(ipAssignments.c_str(),",","","")); + for(std::vector<std::string>::iterator i(ips.begin());i!=ips.end();++i) { + InetAddress a(*i); + if (a.isV4()) + ip4s.push_back(a); + else if (a.isV6()) + ip6s.push_back(a); + } + } + std::sort(ip4s.begin(),ip4s.end()); + std::unique(ip4s.begin(),ip4s.end()); + std::sort(ip6s.begin(),ip6s.end()); + std::unique(ip6s.begin(),ip6s.end()); + + // If IPv4 assignment mode is 'zt', send them to the client + if (networkRecord.get("v4AssignMode","") == "zt") { + // If we have no IPv4 addresses and we have an assignment pool, auto-assign + if (ip4s.empty()) { + InetAddress v4AssignPool(networkRecord.get("v4AssignPool","")); + uint32_t pnet = Utils::ntoh(*((const uint32_t *)v4AssignPool.rawIpData())); + unsigned int pbits = v4AssignPool.netmaskBits(); + + if ((v4AssignPool.isV4())&&(pbits > 0)&&(pbits < 32)&&(pnet != 0)) { + uint32_t pmask = 0xffffffff << (32 - pbits); // netmask over network part + uint32_t invmask = ~pmask; // netmask over "random" part + + // Begin exploring the IP space by generating an IP from the ZeroTier address + uint32_t first = (((uint32_t)(member.toInt() & 0xffffffffULL)) & invmask) | (pnet & pmask); + if ((first & 0xff) == 0) + first |= 1; + else if ((first & 0xff) == 0xff) + first &= 0xfe; + + // Start by trying this first IP + uint32_t abcd = first; + + InetAddress ip; + bool gotone = false; + unsigned long sanityCounter = 0; + do { + // Convert to IPv4 InetAddress + uint32_t abcdNetworkByteOrder = Utils::hton(abcd); + ip.set(&abcdNetworkByteOrder,4,pbits); + + // Is 'ip' already assigned to another node? + std::string assignment; + if (!_hget(ipaKey,ip.toString().c_str(),assignment)) { + LOG("netconf: Redis error checking IP allocation in %s",ipaKey); + return false; + } + if ((assignment.length() != 10)||(assignment == member.toString())) { + gotone = true; + break; // not taken! + } + + // If we made it here, the IP was taken so increment and mask and try again + ++abcd; + abcd &= invmask; + abcd |= (pnet & pmask); + if ((abcd & 0xff) == 0) + abcd |= 1; + else if ((abcd & 0xff) == 0xff) + abcd &= 0xfe; + + // Don't spend insane amounts of time here -- if we have to try this hard, the user + // needs to allocate a larger IP block. + if (++sanityCounter >= 65535) + break; + } while (abcd != first); // keep going until we loop back around to 'first' + + // If we got one, add to IP list and claim in database + if (gotone) { + ip4s.push_back(ip); + _hset(ipaKey,ip.toString().c_str(),member.toString().c_str()); + if (ipAssignments.length() > 0) + ipAssignments.push_back(','); + ipAssignments.append(ip.toString()); + _hset(memberKey,"ipAssignments",ipAssignments.c_str()); + } else { + LOG("netconf: failed to allocate IP in %s for %s in network %s, need a larger pool!",v4AssignPool.toString().c_str(),addrs,nwids); + } + } + } + + // Create comma-delimited list to send to client + std::string v4s; + for(std::vector<InetAddress>::iterator i(ip4s.begin());i!=ip4s.end();++i) { + if (v4s.length() > 0) + v4s.push_back(','); + v4s.append(i->toString()); + } + if (v4s.length()) + nc[ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC] = v4s; + } + + if (networkRecord.get("v6AssignMode","") == "zt") { + // TODO: IPv6 auto-assign ... not quite baked yet. :) - 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; + std::string v6s; + for(std::vector<InetAddress>::iterator i(ip6s.begin());i!=ip6s.end();++i) { + if (v6s.length() > 0) + v6s.push_back(','); + v6s.append(i->toString()); + } + if (v6s.length()) + nc[ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC] = v6s; + } + } + // If this is a private network, generate a signed certificate of membership if (isPrivate) { - CertificateOfMembership com(revision,2,nwid,member); - if (com.sign(RR->identity)) + CertificateOfMembership com(Utils::strToU64(revision.c_str()),1,nwid,member); + if (com.sign(RR->identity)) // basically can't fail unless our identity is invalid nc[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = com.toString(); + else { + LOG("netconf: failure signing certificate (identity problem?)"); + return false; + } } + // Sign netconf dictionary itself + if (!nc.sign(RR->identity)) { + LOG("netconf: failure signing dictionary (identity problem?)"); + return false; + } + + // Convert to string-serialized form into result paramter netconf = nc.toString(); - _hset(memberKey,"netconf",netconf.c_str()); - _hset(memberKey,"netconfTimestamp",tss); - _hset(memberKey,"netconfRevision",networkRecord.get("revision","0").c_str()); + // Record new netconf in database for re-use on subsequent repeat queries + { + Dictionary upd; + upd["netconf"] = netconf; + upd["netconfTimestamp"] = tss; + upd["netconfRevision"] = revision; + if (!_hmset(memberKey,upd)) { + LOG("netconf: Redis error writing to key %s",memberKey); + return false; + } + } return true; } diff --git a/node/NetworkConfigMaster.hpp b/node/NetworkConfigMaster.hpp index 29aada27..70391882 100644 --- a/node/NetworkConfigMaster.hpp +++ b/node/NetworkConfigMaster.hpp @@ -37,6 +37,7 @@ #include <stdint.h> #include <string> #include <map> +#include <vector> #include "Address.hpp" #include "Dictionary.hpp" @@ -105,6 +106,8 @@ private: 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 _get(const char *key,std::string &value); + bool _smembers(const char *key,std::vector<std::string> &sdata); 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); diff --git a/node/Node.cpp b/node/Node.cpp index f79d6402..291af590 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -76,6 +76,7 @@ #include "AntiRecursion.hpp" #include "RoutingTable.hpp" #include "HttpClient.hpp" +#include "NetworkConfigMaster.hpp" namespace ZeroTier { @@ -89,6 +90,7 @@ struct _NodeImpl volatile bool started; volatile bool running; volatile bool resynchronize; + volatile bool disableRootTopologyUpdates; std::string overrideRootTopology; @@ -100,15 +102,16 @@ struct _NodeImpl running = false; - delete renv.updater; renv.updater = (SoftwareUpdater *)0; - delete renv.nc; renv.nc = (NodeConfig *)0; // shut down all networks, close taps, etc. - delete renv.topology; renv.topology = (Topology *)0; // now we no longer need routing info - delete renv.mc; renv.mc = (Multicaster *)0; - delete renv.antiRec; renv.antiRec = (AntiRecursion *)0; - delete renv.sw; renv.sw = (Switch *)0; // order matters less from here down - delete renv.http; renv.http = (HttpClient *)0; - delete renv.prng; renv.prng = (CMWC4096 *)0; - delete renv.log; renv.log = (Logger *)0; // but stop logging last of all + delete renv.updater; renv.updater = (SoftwareUpdater *)0; + delete renv.nc; renv.nc = (NodeConfig *)0; // shut down all networks, close taps, etc. + delete renv.topology; renv.topology = (Topology *)0; // now we no longer need routing info + delete renv.mc; renv.mc = (Multicaster *)0; + delete renv.antiRec; renv.antiRec = (AntiRecursion *)0; + delete renv.sw; renv.sw = (Switch *)0; // order matters less from here down + delete renv.netconfMaster; renv.netconfMaster = (NetworkConfigMaster *)0; + delete renv.http; renv.http = (HttpClient *)0; + delete renv.prng; renv.prng = (CMWC4096 *)0; + delete renv.log; renv.log = (Logger *)0; // but stop logging last of all return reasonForTermination; } diff --git a/node/Packet.hpp b/node/Packet.hpp index d7f7fd60..4b82c2cc 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -603,72 +603,7 @@ public: */ VERB_EXT_FRAME = 7, - /* A multicast frame [old multicast protocol, deprecated]: - * <[2] 16-bit propagation depth or 0xffff for "do not forward"> - * <[320] propagation FIFO> - * <[1024] propagation bloom filter> - * [... begin signed portion ...] - * <[1] 8-bit flags, currently unused and must be 0> - * <[8] 64-bit network ID> - * <[2] 16-bit random propagation bloom filter nonce> - * <[1] number of significant bits in propagation restrict prefix> - * <[1] propagation restriction prefix (sig bits right to left)> - * <[5] ZeroTier address of node of origin> - * <[3] 24-bit multicast ID, together with origin forms GUID> - * <[6] source MAC address> - * <[6] destination multicast group MAC address> - * <[4] destination multicast group ADI field> - * <[2] 16-bit frame ethertype> - * <[2] 16-bit length of payload> - * <[...] ethernet frame payload> - * [... end of signed portion ...] - * <[2] 16-bit length of signature> - * <[...] signature (currently Ed25519/SHA-512, 96 bytes in length)> - * [<[...] network membership certificate (optional)>] - * - * Flags: - * 0x01 - Multicast frame includes network membership certificate - * for original sender for this network. - * - * When a multicast frame is received: - * - * (1) Check the signature of the signed portion of packet, discard on fail - * (2) Check for duplicate multicast, STOP if duplicate - * (3) Check rate limits, STOP if over limit - * (4) Inject into tap if member of network and packet passes other checks - * (5) Increment propagation depth, STOP if over limit - * (6) Pop topmost element off FIFO -- this is next hop - * (7) Push suggested next hops onto FIFO until full -- set corresponding - * bits in bloom filter - * (8) Send to next hop, or to a supernode if none - * - * When choosing next hops, exclude addresses corresponding to bits already - * set in the bloom filter and addresses outside the propagation restrict - * prefix. - * - * Active bridges on a network are always added as next hops for all - * multicast and broadcast traffic, as if they "like" all groups. - * - * Algorithm for setting bits in bloom filter: - * - * (1) Place the address in the least significant 40 bits of a 64-bit int. - * (2) Add the bloom filter nonce to this value. - * (3) XOR the least significant 13 bits of this value with the next most - * significant 13 bits and so on, 4 times. - * (4) This value ANDed with 0x1fff is the bit to set in the bloom filter. - * (5) Set this bit via: byte[bit >> 3] |= (0x80 >> (bit & 7)) - * - * To check bits in bloom filter perform the same computation but mask the - * bit instead of ORing it. - * - * Propagation occurs within a restrict prefix. The restrict prefix is - * applied to the least significant 16 bits of an address. The original - * sender of the multicast sets the restrict prefix and sends 2^N copies - * of the multicast frame, one for each address prefix. - * - * ERROR may be generated if a membership certificate is needed for a - * closed network. Payload will be network ID. - */ + /* DEPRECATED -- legacy support only, will go away completely soon */ VERB_P5_MULTICAST_FRAME = 8, /* Announce interest in multicast group(s): diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index a7b7a99b..ad241535 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -48,6 +48,7 @@ class AntiRecursion; class EthernetTapFactory; class RoutingTable; class HttpClient; +class NetworkConfigMaster; /** * Holds global state for an instance of ZeroTier::Node @@ -77,6 +78,7 @@ public: log((Logger *)0), prng((CMWC4096 *)0), http((HttpClient *)0), + netconfMaster((NetworkConfigMaster *)0), sw((Switch *)0), mc((Multicaster *)0), antiRec((AntiRecursion *)0), @@ -122,6 +124,7 @@ public: Logger *log; // null if logging is disabled CMWC4096 *prng; HttpClient *http; + NetworkConfigMaster *netconfMaster; Switch *sw; Multicaster *mc; AntiRecursion *antiRec; diff --git a/redis-schema.md b/redis-schema.md index baba07fc..10fdeb1e 100644 --- a/redis-schema.md +++ b/redis-schema.md @@ -22,7 +22,7 @@ Network records are used by the network configuration master to issue configurations and certificates to virtual network members. These are the record types you should be interested in if you want to run your own netconf node. -### zt1:network:\<nwid\>:~ +### [Hash] zt1:network:\<nwid\>:~ - !R id :: must be \<nwid\> - !M name :: network's globally unique short name, which can contain only characters valid in an e-mail address. It's the job of the code that populates this DB to ensure that this is globally unique. @@ -42,22 +42,38 @@ Network records are used by the network configuration master to issue configurat - M allowPassiveBridging :: if true, allow passive bridging - M multicastLimit :: maximum number of recipients to receive a multicast on this network - M multicastRates :: string-encoded dictionary containing multicast groups and rates (see below) +- M encryptionMode :: encryption mode -- 0=always (default), 1=non-local only, 2=disable - M subscriptions :: comma-delimited list of subscriptions for this network -- M revision :: network revision number - M ui :: arbitrary field that can be used by the UI to store stuff Multicast rates are encoded as a dictionary. Each key is a multicast group in "MAC/ADI" format (e.g. *ff:ff:ff:ff:ff:ff/0*), and each value is a comma-delimited tuple of hex integer values: preload, max balance, and rate of accrual in bytes per second. An entry for *0* (or *0/0* or *00:00:00:00:00:00/0*) indicates the default setting for all unspecified multicast groups. Setting a rate limit like *ffffffff,ffffffff,ffffffff* as default will effectively turn off rate limits. -Incrementing the network's revision number causes network configurations to be regenerated automatically on next query by a peer. It's important to note that certificates of membership for private networks permit revision numbers to vary by up to **2**. Thus, revision should be incremented once for changes that do not have authorization implications and twice when de-authorizing a member from a network. For better continuity this double-increment can happen with a time delay between each increment to give still-authorized peers more time to get an updated certificate. +The encryption field allows encryption and full cryptographic authentication to be turned off in some or all cases. This improves performance for fast links such as LANs at the expense of security. Possible values are: (0) -- always encrypt (the default), (1) -- encrypt only for public IP destinations (e.g. not 10.x.x.x), (2) -- disable encryption. When encryption is disabled your traffic is vulnerable to both snooping and man-in-the-middle attacks, so most users will want to stick with the default. For same-data-center SDN-like uses cases we recommend sticking with (1). Only use (2) if you really know what you're doing. -### zt1:network:\<nwid\>:member:\<address\>:~ +### [Decimal Integer] zt1:network:\<nwid\>:revision + +The revision number holds a decimal integer that can be incremented with the INCR Redis command. It should be changed whenever any network or network member setting changes that impacts the network configuration that is sent to users. + +For private networks, the revision is used as part of the network membership certificate. *Certificates agree if their revision numbers differ by no more than one.* This has important implications. Generally speaking, you should INCR the revision *once* for most changes but *twice* when you de-authorize a member. This double increment may be performed with a time delay to allow the surviving members time to grab up to date network configurations before de-authorized members fall off the horizon. + +### [Set] zt1:network:\<nwid\>:activeBridges + +This is a set of ZeroTier addresses of designated active bridges on this network. It mirrors the state of the activeBridge field in the member record. Code should automatically remove entries from this set when activeBridge is false and add them when activeBridge is true. + +### [Hash] zt1:network:\<nwid\>:ipAssignments + +This is a hash mapping IP/netmask bits fields to 10-digit ZeroTier addresses of network members. IPv4 fields contain dots, e.g. "10.2.3.4/24" or "29.1.1.1/7". IPv6 fields contain colons. Note that IPv6 IP abbreviations must *not* be used; use \:0000\: instead of \:\: for zero parts of addresses. This is to simplify parser code and canonicalize for rapid search. All hex digits must be lower-case. + +This is only used if the network's IPv4 and/or IPv6 auto-assign mode is 'zt' for ZeroTier assignment. The netconf-master will auto-populate by choosing unused IPs, and it can be edited via the API as well. + +### [Hash] zt1:network:\<nwid\>:member:\<address\>:~ Each member of a network has a hash containing its configuration and authorization information. - !R id :: must be \<address\> - !R nwid :: must be \<nwid\> - M authorized :: true if node is authorized and will be issued valid certificates and network configurations -- M activeBridge :: true if node is an active bridge +- M activeBridge :: true if node is an active bridge (must mirror state of acticeBridges set) - M name :: name of member (user-defined) - M notes :: annotation field (user-defined) - R authorizedBy :: user ID of user who authorized membership (unused by netconf master) @@ -68,7 +84,7 @@ Each member of a network has a hash containing its configuration and authorizati - R lastAt :: real Internet IP/port where node was most recently seen - R ipAssignments :: comma-delimited list of IP address assignments (see below) - R netconf :: most recent network configuration dictionary -- R netconfRevision :: revision of network when most recent netconf was generated +- R netconfRevision :: network revision when netconf was generated (decimal integer) - R netconfTimestamp :: timestamp from netconf dictionary - R netconfClientTimestamp :: timestamp client most recently reported - M ui :: string-serialized JSON blob for use by the user interface (unused by netconf-master) @@ -77,13 +93,7 @@ The netconf field contains the most recent network configuration dictionary for updated whenever network configuration or member authorization is changed. It is sent to clients if authorized is true and if netconf itself contains a valid string-serialized dictionary. -The ipAssignments field is re-generated whenever the zt1:network:\<nwid\>:ipAssignments hash is modified for this member. Both the API code and the netconf-master code must keep this in sync. This field is read-only for API users; the ipAssignments part of the API must be used to modify member IP address assignments. - -### zt1:network:\<nwid\>:ipAssignments - -This is a hash mapping IP/netmask bits fields to 10-digit ZeroTier addresses of network members. IPv4 fields contain dots, e.g. "10.2.3.4/24" or "29.1.1.1/7". IPv6 fields contain colons. Note that IPv6 IP abbreviations must *not* be used; use \:0000\: instead of \:\: for zero parts of addresses. This is to simplify parser code and canonicalize for rapid search. All hex digits must be lower-case. - -This is only used if the network's IPv4 and/or IPv6 auto-assign mode is 'zt' for ZeroTier assignment. The netconf-master will auto-populate by choosing unused IPs, and it can be edited via the API as well. +The ipAssignments field is re-generated whenever the zt1:network:\<nwid\>:ipAssignments hash is modified for this member. Both the API code and the netconf-master code must keep this in sync. # Users (ZeroTier Networks only) @@ -91,11 +101,9 @@ This record type is only of interest to ZeroTier Networks itself. It holds user Netconf masters do not use these records so you don't need to worry about this if you are trying to run your own. -### zt1:user:\<auth\>:\<authUserId\>:~ - -Note: users are referred to elsewhere in the database by their compound key \<auth\>:\<authUserId\> as stored here in the id field. +### [Hash] zt1:user:\<auth\>:\<authUserId\>:~ -- !R id :: must be auth:authUserId +- !R id :: must be auth:authUserId -- this is the full key for referencing a user - !R auth :: authentication type e.g. 'google' or 'local' - !R authUserId :: user ID under auth schema, like an e-mail address or a Google profile ID. - M email :: user's email address |