Cleanup, new C++ netconf code is almost ready to test!

This commit is contained in:
Adam Ierymenko 2015-01-06 17:16:54 -08:00
parent a369c69091
commit 60fb28a90a
8 changed files with 370 additions and 63 deletions

View File

@ -1,7 +1,7 @@
CC=clang
CXX=clang++
INCLUDES=
INCLUDES=-I/usr/local/include
DEFS=
LIBS=

View File

@ -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;

View File

@ -369,14 +369,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
*/

View File

@ -35,6 +35,7 @@
#include <stdexcept>
#include "Constants.hpp"
#include "Utils.hpp"
// Three fields are added/updated by sign()
#define ZT_DICTIONARY_SIGNATURE "~!ed25519"
@ -107,6 +108,128 @@ public:
return e->second;
}
/**
* @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

View File

@ -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++);

View File

@ -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

View File

@ -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;

View File

@ -5,7 +5,8 @@
- : is used as the key namespace separator as per de-facto Redis standard.
- A top-level record may have a :~ child containing a hash. This is the root hash and contains any simple key=value properties of the record.
- Booleans: any value other than "1" or "true" is false.
- Timestamps are in milliseconds since the epoch and are stored as base-10 integers.
- Unless otherwise indicated *all integer values are in hexadecimal!*
- Timestamps are in milliseconds since the epoch
- IPv4 addresees: stored in standard dot notation e.g. 1.2.3.4
- IPv6 addresses: :'s are optional and addresses must be stored *without* shortening, e.g. with :0000: instead of ::. It must be possible to strip :'s from the address and get 128 bits of straight hex.
- Hexadecimal: all hex values must be lower case
@ -23,8 +24,6 @@ Network records are used by the network configuration master to issue configurat
### zt1:network:\<nwid\>:~
Each network has a network record indexed by its 64-bit network ID in lower-case hexadecimal. Unless otherwise indicated all integer values are in hexadecimal.
- !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.
- R owner :: id of user who owns this network (not used by netconf master, only for web UI and web API)
@ -34,7 +33,7 @@ Each network has a network record indexed by its 64-bit network ID in lower-case
- R infrastructure :: if true, network can't be deleted through API or web UI
- M private :: if true, network requires authentication
- R creationTime :: timestamp of network creation
- M etherTypes :: comma-delimited list of integers indicating Ethernet types permitted on network
- M etherTypes :: comma-delimited list of HEX integers indicating Ethernet types permitted on network
- M enableBroadcast :: if true, ff:ff:ff:ff:ff:ff is enabled network-wide
- M v4AssignMode :: 'none' (or null/empty/etc.), 'zt', 'dhcp'
- M v4AssignPool :: network/bits from which to assign IPs
@ -42,11 +41,14 @@ Each network has a network record indexed by its 64-bit network ID in lower-case
- M v6AssignPool :: network/bits from which to assign IPs
- M allowPassiveBridging :: if true, allow passive bridging
- M multicastLimit :: maximum number of recipients to receive a multicast on this network
- M multicastRates :: packed JSON containing multicast rates (see below)
- M multicastRates :: string-encoded dictionary containing multicast groups and rates (see below)
- 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 JSON document. 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.
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.
### zt1:network:\<nwid\>:member:\<address\>:~
@ -65,7 +67,8 @@ Each member of a network has a hash containing its configuration and authorizati
- R lastSeen :: time node was most recently seen
- 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 (updated on changes)
- R netconf :: most recent network configuration dictionary
- R netconfRevision :: revision of network when most recent netconf was generated
- 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)