浏览代码

Rest of software updater, ready to test...

Adam Ierymenko 11 年之前
父节点
当前提交
bf0da9f2f7
共有 10 个文件被更改,包括 361 次插入11 次删除
  1. 13 4
      main.cpp
  2. 10 0
      node/Constants.hpp
  3. 6 1
      node/Defaults.cpp
  4. 5 0
      node/Defaults.hpp
  5. 10 2
      node/HttpClient.cpp
  6. 16 1
      node/Node.hpp
  7. 3 3
      node/RuntimeEnvironment.hpp
  8. 187 0
      node/SoftwareUpdater.cpp
  9. 110 0
      node/SoftwareUpdater.hpp
  10. 1 0
      objects.mk

+ 13 - 4
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;
 		}

+ 10 - 0
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

+ 6 - 1
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())
 {
 }
 

+ 5 - 0
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;

+ 10 - 2
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);

+ 16 - 1
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
 	};
 

+ 3 - 3
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

+ 187 - 0
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

+ 110 - 0
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

+ 1 - 0
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 \