From ae138566a94ed407c71dbc7a155c810b2389cc4b Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 1 Nov 2013 12:38:38 -0400 Subject: [PATCH] Updater code, work in progress... --- node/Defaults.hpp | 5 + node/Packet.hpp | 2 +- node/RuntimeEnvironment.hpp | 13 ++- node/Updater.cpp | 122 ++++++++++++++++++++++++ node/Updater.hpp | 182 ++++++++++++++++++++++++++++++++++++ node/Utils.cpp | 10 ++ node/Utils.hpp | 8 +- objects.mk | 1 + 8 files changed, 337 insertions(+), 6 deletions(-) create mode 100644 node/Updater.cpp create mode 100644 node/Updater.hpp diff --git a/node/Defaults.hpp b/node/Defaults.hpp index 5f8701948..b0eb40e59 100644 --- a/node/Defaults.hpp +++ b/node/Defaults.hpp @@ -67,6 +67,11 @@ public: * Supernodes on the ZeroTier network */ const std::map< Identity,std::vector > supernodes; + + /** + * Identities permitted to sign software updates + */ + const std::map< Address,Identity > updateAuthorities; }; extern const Defaults ZT_DEFAULTS; diff --git a/node/Packet.hpp b/node/Packet.hpp index 56920f6f4..05c6f3a48 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -617,7 +617,7 @@ public: */ VERB_NETWORK_CONFIG_REFRESH = 12, - /* Request information about a shared file: + /* Request information about a shared file (for software updates): * <[1] flags, currently unused and must be 0> * <[2] 16-bit length of filename> * <[...] name of file being requested> diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index 3d73ca567..4baaab6b0 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -46,6 +46,7 @@ class CMWC4096; class Service; class Node; class Multicaster; +class Updater; /** * Holds global state for an instance of ZeroTier::Node @@ -71,24 +72,29 @@ public: demarc((Demarc *)0), topology((Topology *)0), sysEnv((SysEnv *)0), - nc((NodeConfig *)0) + nc((NodeConfig *)0), + updater((Updater *)0) #ifndef __WINDOWS__ ,netconfService((Service *)0) #endif { } + // home of saved state, identity, etc. std::string homePath; - // signal() to prematurely interrupt main loop wait + // signal() to prematurely interrupt main loop wait to cause loop to run + // again and detect some kind of change, exit, etc. Condition mainLoopWaitCondition; Identity identity; + // hacky... want to get rid of this flag... volatile bool shutdownInProgress; // Order matters a bit here. These are constructed in this order - // and then deleted in the opposite order on Node exit. + // and then deleted in the opposite order on Node exit. The order ensures + // that things that are needed are there before they're needed. Logger *log; // may be null CMWC4096 *prng; @@ -99,6 +105,7 @@ public: SysEnv *sysEnv; NodeConfig *nc; Node *node; + Updater *updater; // may be null if updates are disabled #ifndef __WINDOWS__ Service *netconfService; // may be null #endif diff --git a/node/Updater.cpp b/node/Updater.cpp new file mode 100644 index 000000000..6a6d9d9c3 --- /dev/null +++ b/node/Updater.cpp @@ -0,0 +1,122 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "Updater.hpp" +#include "RuntimeEnvironment.hpp" +#include "Logger.hpp" +#include "Defaults.hpp" + +namespace ZeroTier { + +Updater::Updater(const RuntimeEnvironment *renv) : + _r(renv), + _download((_Download *)0) +{ + refreshShared(); +} + +Updater::~Updater() +{ + Mutex::Lock _l(_lock); + delete _download; +} + +void Updater::refreshShared() +{ + std::string updatesPath(_r->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); + std::map ud(Utils::listDirectory(updatesPath.c_str())); + + Mutex::Lock _l(_lock); + _sharedUpdates.clear(); + for(std::map::iterator u(ud.begin());u!=ud.end();++u) { + if (u->second) + continue; // skip directories + if ((u->first.length() >= 4)&&(!strcasecmp(u->first.substr(u->first.length() - 4).c_str(),".nfo"))) + continue; // skip .nfo companion files + + std::string fullPath(updatesPath + ZT_PATH_SEPARATOR_S + u->first); + std::string nfoPath(fullPath + ".nfo"); + + std::string buf; + if (Utils::readFile(nfoPath.c_str(),buf)) { + Dictionary nfo(buf); + + _Shared shared; + shared.filename = fullPath; + + std::string sha512(Utils::unhex(nfo.get("sha512",std::string()))); + if (sha512.length() < sizeof(shared.sha512)) { + TRACE("skipped shareable update due to missing fields in companion .nfo: %s",fullPath.c_str()); + continue; + } + memcpy(shared.sha512,sha512.data(),sizeof(shared.sha512)); + + std::string sig(Utils::unhex(nfo.get("sha512sig_ed25519",std::string()))); + if (sig.length() < shared.sig.size()) { + TRACE("skipped shareable update due to missing fields in companion .nfo: %s",fullPath.c_str()); + continue; + } + memcpy(shared.sig.data,sig.data(),shared.sig.size()); + + // Check signature to guard against updates.d being used as a data + // exfiltration mechanism. We will only share properly signed updates, + // nothing else. + Address signedBy(nfo.get("signedBy",std::string())); + std::map< Address,Identity >::const_iterator authority(ZT_DEFAULTS.updateAuthorities.find(signedBy)); + if ((authority == ZT_DEFAULTS.updateAuthorities.end())||(!authority->second.verify(shared.sha512,64,shared.sig))) { + TRACE("skipped shareable update: not signed by valid authority or signature invalid: %s",fullPath.c_str()); + continue; + } + shared.signedBy = signedBy; + + int64_t fs = Utils::getFileSize(fullPath.c_str()); + if (fs <= 0) { + TRACE("skipped shareable update due to unreadable, invalid, or 0-byte file: %s",fullPath.c_str()); + continue; + } + shared.size = (unsigned long)fs; + + Array first16Bytes; + memcpy(first16Bytes.data,sha512.data(),16); + _sharedUpdates[first16Bytes] = shared; + } else { + TRACE("skipped shareable update due to missing companion .nfo: %s",fullPath.c_str()); + continue; + } + } +} + +void Updater::getUpdateIfThisIsNewer(unsigned int vMajor,unsigned int vMinor,unsigned int revision) +{ +} + +void Updater::retryIfNeeded() +{ +} + +} // namespace ZeroTier + diff --git a/node/Updater.hpp b/node/Updater.hpp new file mode 100644 index 000000000..66d45ee48 --- /dev/null +++ b/node/Updater.hpp @@ -0,0 +1,182 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_UPDATER_HPP +#define _ZT_UPDATER_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Packet.hpp" +#include "Mutex.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Array.hpp" +#include "Dictionary.hpp" + +// Chunk size-- this can be changed, picked to always fit in one packet each. +#define ZT_UPDATER_CHUNK_SIZE 1350 + +// Sanity check value for constraining max size since right now we buffer +// in RAM. +#define ZT_UPDATER_MAX_SUPPORTED_SIZE (1024 * 1024 * 16) + +// Retry timeout in ms. +#define ZT_UPDATER_RETRY_TIMEOUT 30000 + +// After this long, look for a new set of peers that have the download shared. +#define ZT_UPDATER_REPOLL_TIMEOUT 60000 + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Software update downloader and executer + * + * FYI: downloads occur via the protocol rather than out of band via http so + * that ZeroTier One can be run in secure jailed environments where it is the + * only protocol permitted over the "real" Internet. This is required for a + * number of potentially popular use cases. + * + * The protocol is a simple chunk-pulling "trivial FTP" like thing that should + * be suitable for core engine software updates. Software updates themselves + * are platform-specific executables that ZeroTier One then exits and runs. + * + * Updaters are cached one-deep and can be replicated peer to peer in addition + * to coming from supernodes. This makes it just a little bit BitTorrent-like + * and helps things scale, and is also ready for further protocol + * decentralization that may occur in the future. + */ +class Updater +{ +public: + Updater(const RuntimeEnvironment *renv); + ~Updater(); + + /** + * Rescan home path for shareable updates + * + * This happens automatically on construction. + */ + void refreshShared(); + + /** + * Attempt to find an update if this version is newer than ours + * + * This is called whenever a peer notifies us of its version. It does nothing + * if that version is not newer, otherwise it looks around for an update. + * + * @param vMajor Major version + * @param vMinor Minor version + * @param revision Revision + */ + void getUpdateIfThisIsNewer(unsigned int vMajor,unsigned int vMinor,unsigned int revision); + + /** + * Called periodically from main loop + */ + void retryIfNeeded(); + +private: + struct _Download + { + _Download(const void *s512,const std::string &fn,unsigned long len,unsigned int vMajor,unsigned int vMinor,unsigned int rev) + { + data.resize(len); + haveChunks.resize((len / ZT_UPDATER_CHUNK_SIZE) + 1,false); + filename = fn; + memcpy(sha512,s512,64); + lastChunkSize = len % ZT_UPDATER_CHUNK_SIZE; + versionMajor = vMajor; + versionMinor = vMinor; + revision = rev; + } + + long nextChunk() const + { + std::vector::const_iterator ptr(std::find(haveChunks.begin(),haveChunks.end(),false)); + if (ptr != haveChunks.end()) + return std::distance(haveChunks.begin(),ptr); + else return -1; + } + + bool gotChunk(unsigned long at,const void *chunk,unsigned long len) + { + unsigned long whichChunk = at / ZT_UPDATER_CHUNK_SIZE; + if (at != (ZT_UPDATER_CHUNK_SIZE * whichChunk)) + return false; // not at chunk boundary + if (whichChunk >= haveChunks.size()) + return false; // overflow + if ((whichChunk == (haveChunks.size() - 1))&&(len != lastChunkSize)) + return false; // last chunk, size wrong + else if (len != ZT_UPDATER_CHUNK_SIZE) + return false; // chunk size wrong + for(unsigned long i=0;i data; + std::vector haveChunks; + std::vector
peersThatHave; + std::string filename; + unsigned char sha512[64]; + Address currentlyReceivingFrom; + uint64_t lastChunkReceivedAt; + unsigned long lastChunkSize; + unsigned int versionMajor,versionMinor,revision; + }; + + struct _Shared + { + std::string filename; + unsigned char sha512[64]; + C25519::Signature sig; + Address signedBy; + unsigned long size; + }; + + const RuntimeEnvironment *_r; + _Download *_download; + std::map< Array,_Shared > _sharedUpdates; + Mutex _lock; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Utils.cpp b/node/Utils.cpp index c565d8c47..66bd27dd7 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -265,6 +265,16 @@ uint64_t Utils::getLastModified(const char *path) return (((uint64_t)s.st_mtime) * 1000ULL); } +static int64_t getFileSize(const char *path) +{ + struct stat s; + if (stat(path,&s)) + return -1; + if (S_ISREG(s.st_mode)) + return s.st_size; + return -1; +} + std::string Utils::toRfc1123(uint64_t t64) { struct tm t; diff --git a/node/Utils.hpp b/node/Utils.hpp index 6a56ba9db..208ff7552 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -118,8 +118,6 @@ public: * List a directory's contents * * @param path Path to list - * @param files Set to fill with files - * @param directories Set to fill with directories * @return Map of entries and whether or not they are also directories (empty on failure) */ static std::map listDirectory(const char *path); @@ -195,6 +193,12 @@ public: return (getLastModified(path) != 0); } + /** + * @param path Path to file + * @return File size or -1 if nonexistent or other failure + */ + static int64_t getFileSize(const char *path); + /** * @param t64 Time in ms since epoch * @return RFC1123 date string diff --git a/objects.mk b/objects.mk index 547033f93..f38f3e902 100644 --- a/objects.mk +++ b/objects.mk @@ -25,4 +25,5 @@ OBJS=\ node/SysEnv.o \ node/Topology.o \ node/UdpSocket.o \ + node/Updater.o \ node/Utils.o