소스 검색

Updater code, work in progress...

Adam Ierymenko 11 년 전
부모
커밋
ae138566a9
8개의 변경된 파일337개의 추가작업 그리고 6개의 파일을 삭제
  1. 5 0
      node/Defaults.hpp
  2. 1 1
      node/Packet.hpp
  3. 10 3
      node/RuntimeEnvironment.hpp
  4. 122 0
      node/Updater.cpp
  5. 182 0
      node/Updater.hpp
  6. 10 0
      node/Utils.cpp
  7. 6 2
      node/Utils.hpp
  8. 1 0
      objects.mk

+ 5 - 0
node/Defaults.hpp

@@ -67,6 +67,11 @@ public:
 	 * Supernodes on the ZeroTier network
 	 */
 	const std::map< Identity,std::vector<InetAddress> > supernodes;
+
+	/**
+	 * Identities permitted to sign software updates
+	 */
+	const std::map< Address,Identity > updateAuthorities;
 };
 
 extern const Defaults ZT_DEFAULTS;

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

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

+ 122 - 0
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 <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 "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<std::string,bool> ud(Utils::listDirectory(updatesPath.c_str()));
+
+	Mutex::Lock _l(_lock);
+	_sharedUpdates.clear();
+	for(std::map<std::string,bool>::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<unsigned char,16> 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
+

+ 182 - 0
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 <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_UPDATER_HPP
+#define _ZT_UPDATER_HPP
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <map>
+#include <vector>
+#include <algorithm>
+#include <iterator>
+#include <stdexcept>
+#include <string>
+
+#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<bool>::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<len;++i)
+				data[at + i] = ((const char *)chunk)[i];
+			haveChunks[whichChunk] = true;
+			return true;
+		}
+
+		std::vector<char> data;
+		std::vector<bool> haveChunks;
+		std::vector<Address> 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<unsigned char,16>,_Shared > _sharedUpdates;
+	Mutex _lock;
+};
+
+} // namespace ZeroTier
+
+#endif

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

+ 6 - 2
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<std::string,bool> 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

+ 1 - 0
objects.mk

@@ -25,4 +25,5 @@ OBJS=\
 	node/SysEnv.o \
 	node/Topology.o \
 	node/UdpSocket.o \
+	node/Updater.o \
 	node/Utils.o