From bf0da9f2f778aeb3eebe200a8cdeecbc6e1f9253 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 10 Dec 2013 15:30:53 -0800 Subject: Rest of software updater, ready to test... --- main.cpp | 17 +++- node/Constants.hpp | 10 +++ node/Defaults.cpp | 7 +- node/Defaults.hpp | 5 ++ node/HttpClient.cpp | 12 ++- node/Node.hpp | 17 +++- node/RuntimeEnvironment.hpp | 6 +- node/SoftwareUpdater.cpp | 187 ++++++++++++++++++++++++++++++++++++++++++++ node/SoftwareUpdater.hpp | 110 ++++++++++++++++++++++++++ objects.mk | 1 + 10 files changed, 361 insertions(+), 11 deletions(-) create mode 100644 node/SoftwareUpdater.cpp create mode 100644 node/SoftwareUpdater.hpp diff --git a/main.cpp b/main.cpp index 872fd37c..37d82bc8 100644 --- a/main.cpp +++ b/main.cpp @@ -44,6 +44,7 @@ #else #include #include +#include #include #include #include @@ -473,13 +474,21 @@ int main(int argc,char **argv) try { node = new Node(homeDir,port,controlPort); - const char *termReason = (char *)0; switch(node->run()) { - case Node::NODE_UNRECOVERABLE_ERROR: + case Node::NODE_NODE_RESTART_FOR_UPGRADE: { +#ifdef __UNIX_LIKE__ + const char *upgPath = node->reasonForTermination(); + if (upgPath) + execl(upgPath,upgPath,"-s",(char *)0); // -s = (re)start after install/upgrade + exitCode = -1; + fprintf(stderr,"%s: abnormal termination: unable to execute update at %s",argv[0],(upgPath) ? upgPath : "(unknown path)"); +#endif + } break; + case Node::NODE_UNRECOVERABLE_ERROR: { exitCode = -1; - termReason = node->reasonForTermination(); + const char *termReason = node->reasonForTermination(); fprintf(stderr,"%s: abnormal termination: %s\n",argv[0],(termReason) ? termReason : "(unknown reason)"); - break; + } break; default: break; } diff --git a/node/Constants.hpp b/node/Constants.hpp index 3f121cf4..21c8a0ec 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -330,4 +330,14 @@ error_no_byte_order_defined; */ #define ZT_RENDEZVOUS_NAT_T_DELAY 500 +/** + * Minimum interval between attempts to do a software update + */ +#define ZT_UPDATE_MIN_INTERVAL 120000 + +/** + * Update HTTP timeout in seconds + */ +#define ZT_UPDATE_HTTP_TIMEOUT 30 + #endif diff --git a/node/Defaults.cpp b/node/Defaults.cpp index cfc901b5..566658fa 100644 --- a/node/Defaults.cpp +++ b/node/Defaults.cpp @@ -122,13 +122,18 @@ static inline std::map< Address,Identity > _mkUpdateAuth() return ua; } +static inline std::string _mkUpdateUrl() +{ +} + Defaults::Defaults() : #ifdef ZT_TRACE_MULTICAST multicastTraceWatcher(ZT_TRACE_MULTICAST), #endif defaultHomePath(_mkDefaultHomePath()), supernodes(_mkSupernodeMap()), - updateAuthorities(_mkUpdateAuth()) + updateAuthorities(_mkUpdateAuth()), + updateLatestNfoURL(_mkUpdateUrl()) { } diff --git a/node/Defaults.hpp b/node/Defaults.hpp index d546d01f..9d6d4bcf 100644 --- a/node/Defaults.hpp +++ b/node/Defaults.hpp @@ -78,6 +78,11 @@ public: * build will not auto-update. */ const std::map< Address,Identity > updateAuthorities; + + /** + * URL to latest .nfo for software updates + */ + const std::string updateLatestNfoURL; }; extern const Defaults ZT_DEFAULTS; diff --git a/node/HttpClient.cpp b/node/HttpClient.cpp index 1d1624db..15c01c44 100644 --- a/node/HttpClient.cpp +++ b/node/HttpClient.cpp @@ -112,6 +112,12 @@ public: return; } + if (!_url.length()) { + _handler(_arg,-1,_url,false,"cannot fetch empty URL"); + delete this; + return; + } + curlArgs[0] = const_cast (curlPath.c_str()); curlArgs[1] = const_cast ("-D"); curlArgs[2] = const_cast ("-"); // append headers before output @@ -171,9 +177,11 @@ public: if (FD_ISSET(curlStdout[0],&readfds)) { int n = (int)::read(curlStdout[0],buf,sizeof(buf)); - if (n > 0) + if (n > 0) { _body.append(buf,n); - else if (n < 0) + // Reset timeout when data is read... + timesOutAt = Utils::now() + ((unsigned long long)_timeout * 1000ULL); + } else if (n < 0) break; if (_body.length() > CURL_MAX_MESSAGE_LENGTH) { ::kill(pid,SIGKILL); diff --git a/node/Node.hpp b/node/Node.hpp index 9d02c008..2736713f 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -97,9 +97,24 @@ public: */ enum ReasonForTermination { + /** + * Node is currently in run() + */ NODE_RUNNING = 0, + + /** + * Node is shutting down for normal reasons, including a signal + */ NODE_NORMAL_TERMINATION = 1, - NODE_RESTART_FOR_RECONFIGURATION = 2, + + /** + * An upgrade is available. Its path is in reasonForTermination(). + */ + NODE_RESTART_FOR_UPGRADE = 2, + + /** + * A serious unrecoverable error has occurred. + */ NODE_UNRECOVERABLE_ERROR = 3 }; diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index 48797b14..05e10676 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -46,7 +46,7 @@ class CMWC4096; class Service; class Node; class Multicaster; -class Updater; +class SoftwareUpdater; /** * Holds global state for an instance of ZeroTier::Node @@ -73,7 +73,7 @@ public: topology((Topology *)0), sysEnv((SysEnv *)0), nc((NodeConfig *)0), - updater((Updater *)0) + updater((SoftwareUpdater *)0) #ifndef __WINDOWS__ ,netconfService((Service *)0) #endif @@ -110,7 +110,7 @@ public: SysEnv *sysEnv; NodeConfig *nc; Node *node; - Updater *updater; // null if auto-updates are disabled + SoftwareUpdater *updater; // null if software updates are not enabled #ifndef __WINDOWS__ Service *netconfService; // null if no netconf service running #endif diff --git a/node/SoftwareUpdater.cpp b/node/SoftwareUpdater.cpp new file mode 100644 index 00000000..4cb2f7e4 --- /dev/null +++ b/node/SoftwareUpdater.cpp @@ -0,0 +1,187 @@ +/* + * 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 +#include +#include + +#include "../version.h" + +#include "SoftwareUpdater.hpp" +#include "Dictionary.hpp" +#include "C25519.hpp" +#include "Identity.hpp" +#include "Logger.hpp" +#include "RuntimeEnvironment.hpp" +#include "Thread.hpp" +#include "Node.hpp" + +#ifdef __UNIX_LIKE__ +#include +#include +#include +#include +#endif + +namespace ZeroTier { + +SoftwareUpdater::SoftwareUpdater(const RuntimeEnvironment *renv) : + _r(renv), + _myVersion(packVersion(ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION)), + _lastUpdateAttempt(0), + _status(UPDATE_STATUS_IDLE), + _die(false), + _lock() +{ +} + +SoftwareUpdater::~SoftwareUpdater() +{ + _die = true; + for(;;) { + _lock.lock(); + bool ip = (_status != UPDATE_STATUS_IDLE); + _lock.unlock(); + if (ip) + Thread::sleep(500); + else break; + } +} + +void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,bool onDisk,const std::string &body) +{ + SoftwareUpdater *upd = (SoftwareUpdater *)arg; + const RuntimeEnvironment *_r = (const RuntimeEnvironment *)upd->_r; + Mutex::Lock _l(upd->_lock); + + if ((upd->_die)||(upd->_status != UPDATE_STATUS_GETTING_NFO)) { + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + if (code != 200) { + LOG("unable to check for software updates, response code %d (%s)",code,body.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + try { + Dictionary nfo(body); + const unsigned int vMajor = Utils::strToUInt(nfo.get("vMajor").c_str()); + const unsigned int vMinor = Utils::strToUInt(nfo.get("vMinor").c_str()); + const unsigned int vRevision = Utils::strToUInt(nfo.get("vRevision").c_str()); + const Address signedBy(nfo.get("signedBy")); + const std::string signature(Utils::unhex(nfo.get("ed25519"))); + const std::string &url = nfo.get("url"); + + if (signature.length() != ZT_C25519_SIGNATURE_LEN) { + LOG("software update aborted: .nfo file invalid: bad Ed25519 signature"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + if ((url.length() <= 7)||(url.substr(0,7) != "http://")) { + LOG("software update aborted: .nfo file invalid: update URL must begin with http://"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) { + LOG("software update aborted: .nfo file invalid: version on web site <= my version"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + if (!ZT_DEFAULTS.updateAuthorities.count(signedBy)) { + LOG("software update aborted: .nfo file specifies unknown signing authority"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + upd->_status = UPDATE_STATUS_GETTING_FILE; + upd->_signedBy = signedBy; + upd->_signature = signature; + + HttpClient::GET(url,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionBinary,arg); + } catch ( ... ) { + LOG("software update check failed: .nfo file invalid: fields missing or invalid dictionary format"); + upd->_status = UPDATE_STATUS_IDLE; + } +} + +void SoftwareUpdater::_cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,bool onDisk,const std::string &body) +{ + SoftwareUpdater *upd = (SoftwareUpdater *)arg; + const RuntimeEnvironment *_r = (const RuntimeEnvironment *)upd->_r; + Mutex::Lock _l(upd->_lock); + + std::map< Address,Identity >::const_iterator updateAuthority = ZT_DEFAULTS.updateAuthorities.find(upd->_signedBy); + if (updateAuthority == ZT_DEFAULTS.updateAuthorities.end()) { // sanity check, shouldn't happen + LOG("software update aborted: .nfo file specifies unknown signing authority"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + // The all-important authenticity check... :) + if (!updateAuthority->second.verify(body.data(),body.length(),upd->_signature.data(),upd->_signature.length())) { + LOG("software update aborted: update fetched from '%s' failed certificate check against signer %s",url.c_str(),updateAuthority->first.toString().c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + +#ifdef __UNIX_LIKE__ + size_t lastSlash = url.rfind('/'); + if (lastSlash == std::string::npos) { // sanity check, shouldn't happen + LOG("software update aborted: invalid URL"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + std::string updatesDir(_r->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); + std::string updatePath(updatesDir + ZT_PATH_SEPARATOR_S + url.substr(lastSlash + 1)); + mkdir(updatesDir.c_str(),0755); + + int fd = ::open(updatePath.c_str(),O_WRONLY|O_CREAT|O_TRUNC,0755); + if (fd <= 0) { + LOG("software update aborted: unable to open %s for writing",updatePath.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + if ((long)::write(fd,body.data(),body.length()) != (long)body.length()) { + LOG("software update aborted: unable to write to %s",updatePath.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + ::close(fd); + ::chmod(updatePath.c_str(),0755); + + _r->node->terminate(Node::NODE_RESTART_FOR_UPGRADE,updatePath.c_str()); +#endif + +#ifdef __WINDOWS__ + todo; +#endif +} + +} // namespace ZeroTier diff --git a/node/SoftwareUpdater.hpp b/node/SoftwareUpdater.hpp new file mode 100644 index 00000000..bfcdf395 --- /dev/null +++ b/node/SoftwareUpdater.hpp @@ -0,0 +1,110 @@ +/* + * 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_SOFTWAREUPDATER_HPP +#define ZT_SOFTWAREUPDATER_HPP + +#include + +#include "Constants.hpp" +#include "Mutex.hpp" +#include "Utils.hpp" +#include "HttpClient.hpp" +#include "Defaults.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Software updater + */ +class SoftwareUpdater +{ +public: + SoftwareUpdater(const RuntimeEnvironment *renv); + ~SoftwareUpdater(); + + /** + * Called on each version message from a peer + * + * If a peer has a newer version, that causes an update to be started. + * + * @param vmaj Peer's major version + * @param vmin Peer's minor version + * @param rev Peer's revision + */ + inline void sawRemoteVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev) + { + const uint64_t tmp = packVersion(vmaj,vmin,rev); + if (tmp > _myVersion) { + Mutex::Lock _l(_lock); + if ((_status == UPDATE_STATUS_IDLE)&&(!_die)&&(ZT_DEFAULTS.updateLatestNfoURL.length())) { + const uint64_t now = Utils::now(); + if ((now - _lastUpdateAttempt) >= ZT_UPDATE_MIN_INTERVAL) { + _lastUpdateAttempt = now; + _status = UPDATE_STATUS_GETTING_NFO; + HttpClient::GET(ZT_DEFAULTS.updateLatestNfoURL,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionInfo,this); + } + } + } + } + + /** + * Pack three-component version into a 64-bit integer + * + * @param vmaj Major version (0..65535) + * @param vmin Minor version (0..65535) + * @param rev Revision (0..65535) + */ + static inline uint64_t packVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev) + throw() + { + return ( ((uint64_t)(vmaj & 0xffff) << 32) | ((uint64_t)(vmin & 0xffff) << 16) | (uint64_t)(rev & 0xffff) ); + } + +private: + static void _cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,bool onDisk,const std::string &body); + static void _cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,bool onDisk,const std::string &body); + + const RuntimeEnvironment *_r; + const uint64_t _myVersion; + volatile uint64_t _lastUpdateAttempt; + volatile enum { + UPDATE_STATUS_IDLE, + UPDATE_STATUS_GETTING_NFO, + UPDATE_STATUS_GETTING_FILE + } _status; + volatile bool _die; + Address _signedBy; + std::string _signature; + Mutex _lock; +}; + +} // namespace ZeroTier + +#endif diff --git a/objects.mk b/objects.mk index 7eb97574..317e9e84 100644 --- a/objects.mk +++ b/objects.mk @@ -21,6 +21,7 @@ OBJS=\ node/Poly1305.o \ node/Salsa20.o \ node/Service.o \ + node/SoftwareUpdater.o \ node/SHA512.o \ node/Switch.o \ node/SysEnv.o \ -- cgit v1.2.3