From 4d498b3765695f1b82a2448f0e8efe698b33667d Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 9 Aug 2016 13:14:38 -0700 Subject: [PATCH] Handling of multi-part chunked network configs on the inbound side. --- node/Identity.hpp | 5 +++++ node/IncomingPacket.cpp | 16 +++++++------- node/Network.cpp | 48 ++++++++++++++++++++++++++++++++++++++++- node/Network.hpp | 18 ++++++++++++++++ node/NetworkConfig.cpp | 12 ++++++++++- node/NetworkConfig.hpp | 6 ++++-- 6 files changed, 93 insertions(+), 12 deletions(-) diff --git a/node/Identity.hpp b/node/Identity.hpp index 4aa93b876..ef7f2d775 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -282,6 +282,11 @@ public: bool fromString(const char *str); inline bool fromString(const std::string &str) { return fromString(str.c_str()); } + /** + * @return C25519 public key + */ + inline const C25519::Public &publicKey() const { return _publicKey; } + /** * @return True if this identity contains something */ diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 147f54da1..e25cb0580 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -402,15 +402,15 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr &p case Packet::VERB_NETWORK_CONFIG_REQUEST: { const SharedPtr nw(RR->node->network(at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID))); if ((nw)&&(nw->controller() == peer->address())) { - const unsigned int nclen = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN); - if (nclen) { - Dictionary dconf((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,nclen),nclen); - NetworkConfig nconf; - if (nconf.fromDictionary(dconf)) { - nw->setConfiguration(nconf,true); - TRACE("got network configuration for network %.16llx from %s",(unsigned long long)nw->id(),source().toString().c_str()); - } + const unsigned int chunkLen = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN); + const void *chunkData = field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,chunkLen); + unsigned int chunkIndex = 0; + unsigned int totalSize = chunkLen; + if ((ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT + chunkLen) < size()) { + totalSize = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT + chunkLen); + chunkIndex = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT + chunkLen + 4); } + nw->handleInboundConfigChunk(inRePacketId,chunkData,chunkLen,chunkIndex,totalSize); } } break; diff --git a/node/Network.cpp b/node/Network.cpp index 0fbdf5ba4..b84756aa0 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -343,6 +343,7 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) : _id(nwid), _mac(renv->identity.address(),nwid), _portInitialized(false), + _inboundConfigPacketId(0), _lastConfigUpdate(0), _destroyed(false), _netconfFailure(NETCONF_FAILURE_NONE), @@ -364,7 +365,7 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) : std::string conf(RR->node->dataStoreGet(confn)); if (conf.length()) { dconf->load(conf.c_str()); - if (nconf->fromDictionary(*dconf)) { + if (nconf->fromDictionary(Identity(),*dconf)) { this->setConfiguration(*nconf,false); _lastConfigUpdate = 0; // we still want to re-request a new config from the network gotConf = true; @@ -589,6 +590,47 @@ int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk) return 0; } +void Network::handleInboundConfigChunk(const uint64_t inRePacketId,const void *data,unsigned int chunkSize,unsigned int chunkIndex,unsigned int totalSize) +{ + std::string newConfig; + if ((_inboundConfigPacketId == inRePacketId)&&(totalSize < ZT_NETWORKCONFIG_DICT_CAPACITY)&&((chunkIndex + chunkSize) < totalSize)) { + Mutex::Lock _l(_lock); + TRACE("got %u bytes at position %u of network config request %.16llx, total expected length %u",chunkSize,chunkIndex,inRePacketId,totalSize); + _inboundConfigChunks[chunkIndex].append((const char *)data,chunkSize); + unsigned int totalWeHave = 0; + for(std::map::iterator c(_inboundConfigChunks.begin());c!=_inboundConfigChunks.end();++c) + totalWeHave += (unsigned int)c->second.length(); + if (totalWeHave == totalSize) { + TRACE("have all chunks for network config request %.16llx, assembling...",inRePacketId); + for(std::map::iterator c(_inboundConfigChunks.begin());c!=_inboundConfigChunks.end();++c) + newConfig.append(c->second); + _inboundConfigPacketId = 0; + _inboundConfigChunks.clear(); + } else if (totalWeHave > totalSize) { + _inboundConfigPacketId = 0; + _inboundConfigChunks.clear(); + } + } + + if (newConfig.length() > 0) { + if (newConfig.length() < ZT_NETWORKCONFIG_DICT_CAPACITY) { + Dictionary *dict = new Dictionary(newConfig.c_str()); + try { + Identity controllerId(RR->topology->getIdentity(this->controller())); + if (controllerId) { + NetworkConfig nc; + if (nc.fromDictionary(controllerId,*dict)) + this->setConfiguration(nc,true); + } + delete dict; + } catch ( ... ) { + delete dict; + throw; + } + } + } +} + void Network::requestConfiguration() { if (_id == ZT_TEST_NETWORK_ID) // pseudo-network-ID, uses locally generated static config @@ -637,6 +679,10 @@ void Network::requestConfiguration() outp.append((_config) ? (uint64_t)_config.revision : (uint64_t)0); outp.compress(); RR->sw->send(outp,true,0); + + // Expect replies with this in-re packet ID + _inboundConfigPacketId = outp.packetId(); + _inboundConfigChunks.clear(); } void Network::clean() diff --git a/node/Network.hpp b/node/Network.hpp index 16f071631..d13918cff 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -214,6 +214,21 @@ public: */ int setConfiguration(const NetworkConfig &nconf,bool saveToDisk); + /** + * Handle an inbound network config chunk + * + * Only chunks whose inRePacketId matches the packet ID of the last request + * are handled. If this chunk completes the config, it is decoded and + * setConfiguration() is called. + * + * @param inRePacketId In-re packet ID from OK(NETWORK_CONFIG_REQUEST) + * @param data Chunk data + * @param chunkSize Size of data[] + * @param chunkIndex Index of chunk in full config + * @param totalSize Total size of network config + */ + void handleInboundConfigChunk(const uint64_t inRePacketId,const void *data,unsigned int chunkSize,unsigned int chunkIndex,unsigned int totalSize); + /** * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this */ @@ -411,6 +426,9 @@ private: Hashtable< MulticastGroup,uint64_t > _multicastGroupsBehindMe; // multicast groups that seem to be behind us and when we last saw them (if we are a bridge) Hashtable< MAC,Address > _remoteBridgeRoutes; // remote addresses where given MACs are reachable (for tracking devices behind remote bridges) + uint64_t _inboundConfigPacketId; + std::map _inboundConfigChunks; + NetworkConfig _config; volatile uint64_t _lastConfigUpdate; diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 07e9bd4f9..a4fddf403 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -178,8 +178,18 @@ bool NetworkConfig::toDictionary(Dictionary &d,b return true; } -bool NetworkConfig::fromDictionary(const Dictionary &d) +bool NetworkConfig::fromDictionary(const Identity &controllerId,Dictionary &d) { + if ((d.contains(ZT_NETWORKCONFIG_DICT_KEY_SIGNATURE))&&(controllerId)) { + // FIXME: right now signature are optional since network configs are only + // accepted directly from the controller and the protocol already guarantees + // the sender. In the future these might be made non-optional once old + // controllers that do not sign are gone and if we ever support peer caching + // of network configs. + if (!d.unwrapAndVerify(ZT_NETWORKCONFIG_DICT_KEY_SIGNATURE,controllerId.publicKey())) + return false; + } + Buffer *tmp = new Buffer(); try { diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index 3682c466b..18244ec90 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -38,6 +38,7 @@ #include "Capability.hpp" #include "Tag.hpp" #include "Dictionary.hpp" +#include "Identity.hpp" /** * Flag: allow passive bridging (experimental) @@ -239,10 +240,11 @@ public: /** * Read this network config from a dictionary * - * @param d Dictionary + * @param controllerId Controller identity for verification of any signature or NULL identity to skip + * @param d Dictionary (non-const since it might be modified during parse, should not be used after call) * @return True if dictionary was valid and network config successfully initialized */ - bool fromDictionary(const Dictionary &d); + bool fromDictionary(const Identity &controllerId,Dictionary &d); /** * @return True if passive bridging is allowed (experimental)