summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Ierymenko <adam.ierymenko@gmail.com>2013-12-10 15:30:53 -0800
committerAdam Ierymenko <adam.ierymenko@gmail.com>2013-12-10 15:30:53 -0800
commitbf0da9f2f778aeb3eebe200a8cdeecbc6e1f9253 (patch)
tree92e9c18362d7dd5a89371a9039128d9347791030
parent612c17240af65243a1fa5d8cc17d3ebdb38a9bee (diff)
downloadinfinitytier-bf0da9f2f778aeb3eebe200a8cdeecbc6e1f9253.tar.gz
infinitytier-bf0da9f2f778aeb3eebe200a8cdeecbc6e1f9253.zip
Rest of software updater, ready to test...
-rw-r--r--main.cpp17
-rw-r--r--node/Constants.hpp10
-rw-r--r--node/Defaults.cpp7
-rw-r--r--node/Defaults.hpp5
-rw-r--r--node/HttpClient.cpp12
-rw-r--r--node/Node.hpp17
-rw-r--r--node/RuntimeEnvironment.hpp6
-rw-r--r--node/SoftwareUpdater.cpp187
-rw-r--r--node/SoftwareUpdater.hpp110
-rw-r--r--objects.mk1
10 files changed, 361 insertions, 11 deletions
diff --git a/main.cpp b/main.cpp
index 872fd37c..37d82bc8 100644
--- a/main.cpp
+++ b/main.cpp
@@ -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;
+ 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 <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);
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 <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
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 <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
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 \