Rest of software updater, ready to test...

This commit is contained in:
Adam Ierymenko 2013-12-10 15:30:53 -08:00
parent 612c17240a
commit bf0da9f2f7
10 changed files with 361 additions and 11 deletions

View File

@ -44,6 +44,7 @@
#else
#include <unistd.h>
#include <pwd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
@ -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;
termReason = node->reasonForTermination();
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;
const char *termReason = node->reasonForTermination();
fprintf(stderr,"%s: abnormal termination: %s\n",argv[0],(termReason) ? termReason : "(unknown reason)");
break;
} break;
default:
break;
}

View File

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

View File

@ -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())
{
}

View File

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

View File

@ -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 <char *>(curlPath.c_str());
curlArgs[1] = const_cast <char *>("-D");
curlArgs[2] = const_cast <char *>("-"); // 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);

View File

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

View File

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

187
node/SoftwareUpdater.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* --
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#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

110
node/SoftwareUpdater.hpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* --
*
* 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 <stdint.h>
#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

View File

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