Browse Source

Remove old launcher code, fix build error in idtool, add terminate command to control bus.

Adam Ierymenko 12 years ago
parent
commit
4875eb49f8
9 changed files with 37 additions and 529 deletions
  1. 0 126
      idtool.cpp
  2. 0 21
      launcher-fakebin.c
  3. 0 279
      launcher.c
  4. 0 71
      launcher.h
  5. 6 14
      main.cpp
  6. 17 13
      node/Node.cpp
  7. 6 4
      node/Node.hpp
  8. 6 0
      node/NodeConfig.cpp
  9. 2 1
      node/RuntimeEnvironment.hpp

+ 0 - 126
idtool.cpp

@@ -45,8 +45,6 @@ static void printHelp(char *pn)
 	std::cout << "\tgetpublic <identity.secret>" << std::endl;
 	std::cout << "\tsign <identity.secret> <file>" << std::endl;
 	std::cout << "\tverify <identity.secret/public> <file> <signature>" << std::endl;
-	std::cout << "\tencrypt <identity.secret> <identity.public (recipient)> [<file>] [<outfile>]" << std::endl;
-	std::cout << "\tdecrypt <identity.secret> <identity.public (sender)> [<file>] [<outfile>]" << std::endl;
 }
 
 static Identity getIdFromArg(char *arg)
@@ -165,130 +163,6 @@ int main(int argc,char **argv)
 			std::cerr << argv[3] << " signature check FAILED" << std::endl;
 			return -1;
 		}
-	} else if (!strcmp(argv[1],"encrypt")) {
-		if (argc < 4) {
-			printHelp(argv[0]);
-			return -1;
-		}
-
-		Identity from = getIdFromArg(argv[2]);
-		if (!from) {
-			std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl;
-			return -1;
-		}
-		if (!from.hasPrivate()) {
-			std::cerr << argv[2] << " must contain a secret key" << std::endl;
-			return -1;
-		}
-
-		Identity to = getIdFromArg(argv[3]);
-		if (!to) {
-			std::cerr << "Identity argument invalid or file unreadable: " << argv[3] << std::endl;
-			return -1;
-		}
-
-		FILE *inf;
-		if (argc > 4) {
-			inf = fopen(argv[4],"r");
-			if (!inf) {
-				std::cerr << "Could not open input file " << argv[4] << std::endl;
-				return -1;
-			}
-		} else inf = stdin;
-		int inbuflen = 131072;
-		char *inbuf = (char *)malloc(inbuflen);
-		if (!inbuf) {
-			std::cerr << "Could not malloc()" << std::endl;
-			return -1;
-		}
-		int inlen = 0;
-		int n;
-		while ((n = (int)fread(inbuf + inlen,1,inbuflen - inlen,inf)) > 0) {
-			inlen += n;
-			if ((inbuflen - inlen) < 1024) {
-				inbuf = (char *)realloc(inbuf,inbuflen += 131072);
-				if (!inbuf) {
-					std::cerr << "Could not malloc()" << std::endl;
-					return -1;
-				}
-			}
-		}
-		if (inf != stdin)
-			fclose(inf);
-
-		std::string crypted(from.encrypt(to,inbuf,inlen));
-		if (!crypted.length()) {
-			std::cerr << "Failure encrypting data, check from/to identities" << std::endl;
-			return -1;
-		}
-
-		if (argc > 5)
-			Utils::writeFile(argv[5],crypted.data(),crypted.length());
-		else fwrite(crypted.data(),1,crypted.length(),stdout);
-
-		free(inbuf);
-	} else if (!strcmp(argv[1],"decrypt")) {
-		if (argc < 4) {
-			printHelp(argv[0]);
-			return -1;
-		}
-
-		Identity to = getIdFromArg(argv[2]);
-		if (!to) {
-			std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl;
-			return -1;
-		}
-
-		if (!to.hasPrivate()) {
-			std::cerr << argv[2] << " must contain a secret key" << std::endl;
-			return -1;
-		}
-
-		Identity from = getIdFromArg(argv[3]);
-		if (!from) {
-			std::cerr << "Identity argument invalid or file unreadable: " << argv[3] << std::endl;
-			return -1;
-		}
-
-		FILE *inf;
-		if (argc > 4) {
-			inf = fopen(argv[4],"r");
-			if (!inf) {
-				std::cerr << "Could not open input file " << argv[4] << std::endl;
-				return -1;
-			}
-		} else inf = stdin;
-		int inbuflen = 131072;
-		char *inbuf = (char *)malloc(inbuflen);
-		if (!inbuf) {
-			std::cerr << "Could not malloc()" << std::endl;
-			return -1;
-		}
-		int inlen = 0;
-		int n;
-		while ((n = (int)fread(inbuf + inlen,1,inbuflen - inlen,inf)) > 0) {
-			inlen += n;
-			if ((inbuflen - inlen) < 1024) {
-				inbuf = (char *)realloc(inbuf,inbuflen += 131072);
-				if (!inbuf) {
-					std::cerr << "Could not malloc()" << std::endl;
-					return -1;
-				}
-			}
-		}
-		if (inf != stdin)
-			fclose(inf);
-
-		std::string dec(to.decrypt(from,inbuf,inlen));
-		free(inbuf);
-		if (!dec.length()) {
-			std::cerr << "Failure decrypting data, check from/to identities" << std::endl;
-			return -1;
-		}
-
-		if (argc > 5)
-			Utils::writeFile(argv[5],dec.data(),dec.length());
-		else fwrite(dec.data(),1,dec.length(),stdout);
 	} else {
 		printHelp(argv[0]);
 		return -1;

+ 0 - 21
launcher-fakebin.c

@@ -1,21 +0,0 @@
-/* Fake zerotier-one binary to test launcher upgrade procedure */
-
-#include <stdio.h>
-#include <unistd.h>
-#include "launcher.h"
-
-const unsigned char EMBEDDED_VERSION_STAMP[20] = {
-	0x6d,0xfe,0xff,0x01,0x90,0xfa,0x89,0x57,0x88,0xa1,0xaa,0xdc,0xdd,0xde,0xb0,0x33,
-	ZEROTIER_FAKE_VERSION_MAJOR,
-	ZEROTIER_FAKE_VERSION_MINOR,
-	(unsigned char)(((unsigned int)ZEROTIER_FAKE_VERSION_REVISION) & 0xff), /* little-endian */
-	(unsigned char)((((unsigned int)ZEROTIER_FAKE_VERSION_REVISION) >> 8) & 0xff) 
-};
-
-int main(int argc,char **argv)
-{
-	fprintf(stderr,"Fake ZeroTier binary version %d.%d.%d\n",ZEROTIER_FAKE_VERSION_MAJOR,ZEROTIER_FAKE_VERSION_MINOR,ZEROTIER_FAKE_VERSION_REVISION);
-	sleep(5);
-	fprintf(stderr,"  (exiting)\n");
-	return ZEROTIER_EXEC_RETURN_VALUE_TERMINATED_FOR_UPGRADE;
-}

+ 0 - 279
launcher.c

@@ -1,279 +0,0 @@
-/*
- * 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/
- */
-
-/* Launcher for Linux/Unix/Mac */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <dirent.h>
-#include <unistd.h>
-#include <signal.h>
-#include <errno.h>
-#include <time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-
-#include "launcher.h"
-
-/* Must match first 16 bytes of EMBEDDED_VERSION_STAMP in Node.cpp */
-static const unsigned char EMBEDDED_VERSION_STAMP_KEY[16] = { 0x6d,0xfe,0xff,0x01,0x90,0xfa,0x89,0x57,0x88,0xa1,0xaa,0xdc,0xdd,0xde,0xb0,0x33 };
-
-const unsigned char EMBEDDED_LAUNCHER_VERSION_STAMP[20] = {
-	0x96,0xf0,0x00,0x08,0x18,0xff,0xc9,0xde,0xad,0xf0,0x0f,0xbe,0xef,0x30,0xce,0xa1, /* key */
-	ZT_LAUNCHER_VERSION_MAJOR,
-	ZT_LAUNCHER_VERSION_MINOR,
-	(unsigned char)(((unsigned int)ZT_LAUNCHER_VERSION_REVISION) & 0xff), /* little-endian */
-	(unsigned char)((((unsigned int)ZT_LAUNCHER_VERSION_REVISION) >> 8) & 0xff)
-};
-
-#define ZT_BINARY_NAME "zerotier-one"
-#define ZT_BINARY_UPDATE_PREFIX "zerotier-one_update."
-
-#define ZT_LAUNCHER_PIDFILE "zerotier-launcher.pid"
-#define ZT_ONE_PIDFILE "zerotier-one.pid"
-
-/* Load a file into newly malloc()'ed memory, len set to size */
-static unsigned char *loadFile(const char *path,unsigned long *len)
-{
-	unsigned char *fbuf = (unsigned char *)0;
-	FILE *f = fopen(path,"rb");
-	if (f) {
-		if (!fseek(f,0,SEEK_END)) {
-			long l = ftell(f);
-			if (l > 0) {
-				fseek(f,0,SEEK_SET);
-				fbuf = malloc(l);
-				if (fbuf) {
-					if (fread(fbuf,l,1,f) != 1) {
-						free(fbuf);
-						fbuf = (unsigned char *)0;
-					} else *len = (unsigned long)l;
-				}
-			}
-		}
-		fclose(f);
-	}
-	return fbuf;
-}
-
-/* Scans a ZeroTier binary and determines its version from its embedded version code */
-static int findVersion(const unsigned char *bin,unsigned long len,unsigned int *major,unsigned int *minor,unsigned int *revision)
-{
-	unsigned long i;
-
-	if (len > 20) {
-		for(i=0;i<(len - 20);++i) {
-			if ((bin[i] == EMBEDDED_VERSION_STAMP_KEY[0])&&(!memcmp(bin + i,EMBEDDED_VERSION_STAMP_KEY,16))) {
-				*major = bin[i + 16];
-				*minor = bin[i + 17];
-				*revision = ((unsigned int)bin[i + 18] & 0xff) | (((unsigned int)bin[i + 19] << 8) & 0xff00);
-				return 1;
-			}
-		}
-	}
-
-	return 0;
-}
-
-/* Scan for updates and, if found, replace the main binary if possible */
-static int doUpdateBinaryIfNewer()
-{
-	long pfxLen = strlen(ZT_BINARY_UPDATE_PREFIX);
-	struct dirent dbuf,*d;
-	int needUpdate;
-	unsigned int major = 0,minor = 0,revision = 0;
-	unsigned int existingMajor = 0,existingMinor = 0,existingRevision = 0;
-	unsigned long binLen;
-	unsigned char *bin;
-	char oldname[1024];
-	DIR *dir;
-
-	binLen = 0;
-	bin = loadFile(ZT_BINARY_NAME,&binLen);
-	if (!((bin)&&(binLen)&&(findVersion(bin,binLen,&existingMajor,&existingMinor,&existingRevision)))) {
-		if (bin)
-			free(bin);
-		return 0;
-	}
-	free(bin);
-
-	dir = opendir(".");
-	if (!dir)
-		return 0;
-	while (!readdir_r(dir,&dbuf,&d)) {
-		if (!d) break;
-		if (!strncasecmp(d->d_name,ZT_BINARY_UPDATE_PREFIX,pfxLen)) {
-			binLen = 0;
-			unsigned char *bin = loadFile(d->d_name,&binLen);
-			if ((bin)&&(binLen)&&(findVersion(bin,binLen,&major,&minor,&revision))) {
-				needUpdate = 0;
-				if (major > existingMajor)
-					needUpdate = 1;
-				else if (major == existingMajor) {
-					if (minor > existingMinor)
-						needUpdate = 1;
-					else if (minor == existingMinor) {
-						if (revision > existingRevision)
-							needUpdate = 1;
-					}
-				}
-				free(bin);
-				if (needUpdate) {
-					/* fprintf(stderr,"zerotier-launcher: replacing %s with %s\n",ZT_BINARY_NAME,d->d_name); */
-					sprintf(oldname,"%s.OLD",ZT_BINARY_NAME);
-					if (!rename(ZT_BINARY_NAME,oldname)) {
-						/* fprintf(stderr,"zerotier-launcher: %s -> %s\n",ZT_BINARY_NAME,oldname); */
-						if (!rename(d->d_name,ZT_BINARY_NAME)) {
-							/* fprintf(stderr,"zerotier-launcher: %s -> %s\nzerotier-launcher: delete %s\n",d->d_name,ZT_BINARY_NAME,oldname); */
-							chmod(ZT_BINARY_NAME,0755);
-							unlink(oldname);
-							return 1;
-						}
-					}
-					break;
-				}
-			}
-			if (bin)
-				free(bin);
-		}
-	}
-	closedir(dir);
-
-	return 0;
-}
-
-static volatile long childPid = 0;
-
-static void sigRepeater(int sig)
-{
-	if (childPid > 0)
-		kill(childPid,sig);
-}
-
-int main(int argc,char **argv)
-{
-	const char *zerotierHome = ZT_DEFAULT_HOME;
-	FILE *pidf;
-	int status,exitCode;
-	unsigned long timeStart;
-	unsigned int numSubTwoSecondRuns;
-
-	/* Pass on certain signals transparently to the subprogram to do with as it will */
-	signal(SIGHUP,&sigRepeater);
-	signal(SIGPIPE,SIG_IGN);
-	signal(SIGUSR1,&sigRepeater);
-	signal(SIGUSR2,&sigRepeater);
-	signal(SIGALRM,SIG_IGN);
-	signal(SIGURG,SIG_IGN);
-	signal(SIGTERM,&sigRepeater);
-	signal(SIGQUIT,&sigRepeater);
-
-	if (argc == 2)
-		zerotierHome = argv[1];
-
-	if (chdir(zerotierHome)) {
-		fprintf(stderr,"%s: fatal error: could not chdir to %s\n",argv[0],zerotierHome);
-		return ZT_EXEC_RETURN_VALUE_UNRECOVERABLE_ERROR;
-	}
-
-	pidf = fopen(ZT_LAUNCHER_PIDFILE,"w");
-	if (pidf) {
-		fprintf(pidf,"%d",(int)getpid());
-		fclose(pidf);
-	}
-
-	numSubTwoSecondRuns = 0;
-	exitCode = ZT_EXEC_RETURN_VALUE_NORMAL_TERMINATION;
-
-restart_subprogram:
-	/* We actually do this on every loop, which is fine. It picks up any
-	 * newer versions that are waiting and swaps them out for the current
-	 * running binary. */
-	doUpdateBinaryIfNewer();
-
-	timeStart = time(0);
-	childPid = fork();
-	if (childPid < 0) {
-		fprintf(stderr,"%s: fatal error: could not fork(): %s\n",argv[0],strerror(errno));
-		return ZT_EXEC_RETURN_VALUE_UNRECOVERABLE_ERROR;
-	} else if (childPid) {
-		pidf = fopen(ZT_ONE_PIDFILE,"w");
-		if (pidf) {
-			fprintf(pidf,"%d",(int)childPid);
-			fclose(pidf);
-		}
-
-		status = ZT_EXEC_RETURN_VALUE_NO_BINARY;
-wait_for_subprogram_exit:
-		if ((long)waitpid(childPid,&status,0) >= 0) {
-			if (WIFEXITED(status)) {
-				unlink(ZT_ONE_PIDFILE);
-
-				if ((time(0) - timeStart) < 2) {
-					/* Terminate abnormally if we appear to be looping in a tight loop
-					 * to avoid fork bombing if one exits abnormally without an abnormal
-					 * exit code. */
-					if (++numSubTwoSecondRuns >= 16) {
-						fprintf(stderr,"%s: fatal error: program exiting immediately in infinite loop\n",argv[0]);
-						return ZT_EXEC_RETURN_VALUE_UNRECOVERABLE_ERROR;
-					}
-				}
-
-				switch(WEXITSTATUS(status)) {
-					case ZT_EXEC_RETURN_VALUE_NORMAL_TERMINATION:
-						exitCode = ZT_EXEC_RETURN_VALUE_NORMAL_TERMINATION;
-						goto exit_launcher;
-					case ZT_EXEC_RETURN_VALUE_NO_BINARY:
-						fprintf(stderr,"%s: fatal error: binary zerotier-one not found at %s\n",argv[0],zerotierHome);
-						exitCode = ZT_EXEC_RETURN_VALUE_UNRECOVERABLE_ERROR;
-						goto exit_launcher;
-					case ZT_EXEC_RETURN_VALUE_TERMINATED_FOR_UPGRADE:
-					case ZT_EXEC_RETURN_VALUE_PLEASE_RESTART:
-						goto restart_subprogram;
-					default:
-						exitCode = status;
-						goto exit_launcher;
-				}
-			}
-		} else if (errno != EINTR) {
-			fprintf(stderr,"%s: fatal error: waitpid() failed: %s\n",argv[0],strerror(errno));
-			exitCode = ZT_EXEC_RETURN_VALUE_UNRECOVERABLE_ERROR;
-			goto exit_launcher;
-		} else {
-			goto wait_for_subprogram_exit;
-		}
-	} else {
-		execl(ZT_BINARY_NAME,ZT_BINARY_NAME,zerotierHome,(char *)0);
-		exit(ZT_EXEC_RETURN_VALUE_NO_BINARY); /* only reached if execl succeeds */
-	}
-
-exit_launcher:
-	unlink(ZT_LAUNCHER_PIDFILE);
-	return exitCode;
-}

+ 0 - 71
launcher.h

@@ -1,71 +0,0 @@
-/*
- * 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_LAUNCHER_H
-#define _ZT_LAUNCHER_H
-
-#define ZT_LAUNCHER_VERSION_MAJOR 0
-#define ZT_LAUNCHER_VERSION_MINOR 0
-#define ZT_LAUNCHER_VERSION_REVISION 1
-
-/**
- * Normal termination
- * 
- * This causes the launcher too to exit normally.
- */
-#define ZT_EXEC_RETURN_VALUE_NORMAL_TERMINATION 0
-
-/**
- * Terminated for upgrade
- * 
- * This tells the launcher that an upgrade may be available, so a scan for
- * newer executables should be performed followed by a restart.
- */
-#define ZT_EXEC_RETURN_VALUE_TERMINATED_FOR_UPGRADE 1
-
-/**
- * Terminated but should be restarted
- * 
- * This simply tells the launcher to restart the executable. Possible
- * reasons include the need to change a config parameter that requires restart.
- */
-#define ZT_EXEC_RETURN_VALUE_PLEASE_RESTART 2
-
-/**
- * Unrecoverable error
- * 
- * This tells the launcher to exit after possibly sending an error report to
- * ZeroTier if the user has this option enabled.
- */
-#define ZT_EXEC_RETURN_VALUE_UNRECOVERABLE_ERROR 3
-
-/**
- * Used on Unix systems to return from forked sub-process if exec fails
- */
-#define ZT_EXEC_RETURN_VALUE_NO_BINARY 4
-
-#endif

+ 6 - 14
main.cpp

@@ -56,8 +56,6 @@
 #include "node/Utils.hpp"
 #include "node/Node.hpp"
 
-#include "launcher.h"
-
 using namespace ZeroTier;
 
 // ---------------------------------------------------------------------------
@@ -102,7 +100,7 @@ static void sighandlerQuit(int sig)
 {
 	Node *n = node;
 	if (n)
-		n->terminate();
+		n->terminate(Node::NODE_NORMAL_TERMINATION,"terminated by signal");
 	else exit(0);
 }
 #endif
@@ -117,7 +115,7 @@ static BOOL WINAPI _handlerRoutine(DWORD dwCtrlType)
 		case CTRL_SHUTDOWN_EVENT:
 			Node *n = node;
 			if (n)
-				n->terminate();
+				n->terminate(Node::NODE_NORMAL_TERMINATION,"terminated by signal");
 			return TRUE;
 	}
 	return FALSE;
@@ -157,12 +155,12 @@ int main(int argc,char **argv)
 				case '?':
 				default:
 					printHelp(argv[0],stderr);
-					return ZT_EXEC_RETURN_VALUE_NORMAL_TERMINATION;
+					return 0;
 			}
 		} else {
 			if (homeDir) {
 				printHelp(argv[0],stderr);
-				return ZT_EXEC_RETURN_VALUE_NORMAL_TERMINATION;
+				return 0;
 			}
 			homeDir = argv[i];
 			break;
@@ -176,22 +174,16 @@ int main(int argc,char **argv)
 	mkdir(homeDir,0755); // will fail if it already exists
 #endif
 
-	int exitCode = ZT_EXEC_RETURN_VALUE_NORMAL_TERMINATION;
+	int exitCode = 0;
 
 	node = new Node(homeDir);
 	const char *termReason = (char *)0;
 	switch(node->run()) {
-		case Node::NODE_RESTART_FOR_RECONFIGURATION:
-			exitCode = ZT_EXEC_RETURN_VALUE_PLEASE_RESTART;
-			break;
 		case Node::NODE_UNRECOVERABLE_ERROR:
-			exitCode = ZT_EXEC_RETURN_VALUE_UNRECOVERABLE_ERROR;
+			exitCode = -1;
 			termReason = node->reasonForTermination();
 			fprintf(stderr,"%s: abnormal termination: %s\n",argv[0],(termReason) ? termReason : "(unknown reason)");
 			break;
-		case Node::NODE_NEW_VERSION_AVAILABLE:
-			exitCode = ZT_EXEC_RETURN_VALUE_TERMINATED_FOR_UPGRADE;
-			break;
 		default:
 			break;
 	}

+ 17 - 13
node/Node.cpp

@@ -183,20 +183,20 @@ struct _NodeImpl
 {
 	RuntimeEnvironment renv;
 	std::string reasonForTerminationStr;
-	Node::ReasonForTermination reasonForTermination;
+	volatile Node::ReasonForTermination reasonForTermination;
 	volatile bool started;
 	volatile bool running;
-	volatile bool terminateNow;
 
-	// run() calls this on all return paths
-	inline Node::ReasonForTermination terminateBecause(Node::ReasonForTermination r,const char *rstr)
+	inline Node::ReasonForTermination terminate()
 	{
 		RuntimeEnvironment *_r = &renv;
-		LOG("terminating: %s",rstr);
+		LOG("terminating: %s",reasonForTerminationStr.c_str());
 
 		renv.shutdownInProgress = true;
 		Thread::sleep(500);
 
+		running = false;
+
 #ifndef __WINDOWS__
 		delete renv.netconfService;
 #endif
@@ -209,11 +209,14 @@ struct _NodeImpl
 		delete renv.prng;
 		delete renv.log;
 
+		return reasonForTermination;
+	}
+
+	inline Node::ReasonForTermination terminateBecause(Node::ReasonForTermination r,const char *rstr)
+	{
 		reasonForTerminationStr = rstr;
 		reasonForTermination = r;
-		running = false;
-
-		return r;
+		return terminate();
 	}
 };
 
@@ -279,7 +282,6 @@ Node::Node(const char *hp)
 	impl->reasonForTermination = Node::NODE_RUNNING;
 	impl->started = false;
 	impl->running = false;
-	impl->terminateNow = false;
 }
 
 Node::~Node()
@@ -377,6 +379,7 @@ Node::ReasonForTermination Node::run()
 			// One is running.
 			return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,(std::string("another instance of ZeroTier One appears to be running, or local control UDP port cannot be bound: ") + exc.what()).c_str());
 		}
+		_r->node = this;
 
 		// TODO: make configurable
 		bool boundPort = false;
@@ -424,7 +427,7 @@ Node::ReasonForTermination Node::run()
 
 		LOG("%s starting version %s",_r->identity.address().toString().c_str(),versionString());
 
-		while (!impl->terminateNow) {
+		while (impl->reasonForTermination == NODE_RUNNING) {
 			uint64_t now = Utils::now();
 			bool resynchronize = false;
 
@@ -562,7 +565,7 @@ Node::ReasonForTermination Node::run()
 		return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"unexpected exception during outer main I/O loop");
 	}
 
-	return impl->terminateBecause(Node::NODE_NORMAL_TERMINATION,"normal termination");
+	return impl->terminate();
 }
 
 const char *Node::reasonForTermination() const
@@ -573,10 +576,11 @@ const char *Node::reasonForTermination() const
 	return ((_NodeImpl *)_impl)->reasonForTerminationStr.c_str();
 }
 
-void Node::terminate()
+void Node::terminate(ReasonForTermination reason,const char *reasonText)
 	throw()
 {
-	((_NodeImpl *)_impl)->terminateNow = true;
+	((_NodeImpl *)_impl)->reasonForTermination = reason;
+	((_NodeImpl *)_impl)->reasonForTerminationStr = ((reasonText) ? reasonText : "");
 	((_NodeImpl *)_impl)->renv.mainLoopWaitCondition.signal();
 }
 

+ 6 - 4
node/Node.hpp

@@ -86,8 +86,7 @@ public:
 		NODE_RUNNING = 0,
 		NODE_NORMAL_TERMINATION = 1,
 		NODE_RESTART_FOR_RECONFIGURATION = 2,
-		NODE_UNRECOVERABLE_ERROR = 3,
-		NODE_NEW_VERSION_AVAILABLE = 4
+		NODE_UNRECOVERABLE_ERROR = 3
 	};
 
 	/**
@@ -124,13 +123,16 @@ public:
 		throw();
 
 	/**
-	 * Cause run() to return with NODE_NORMAL_TERMINATION
+	 * Cause run() to return
 	 *
 	 * This can be called from a signal handler or another thread to signal a
 	 * running node to shut down. Shutdown may take a few seconds, so run()
 	 * may not return instantly. Multiple calls are ignored.
+	 *
+	 * @param reason Reason for termination
+	 * @param reasonText Text to be returned by reasonForTermination()
 	 */
-	void terminate()
+	void terminate(ReasonForTermination reason,const char *reasonText)
 		throw();
 
 	/**

+ 6 - 0
node/NodeConfig.cpp

@@ -55,6 +55,7 @@
 #include "Peer.hpp"
 #include "Salsa20.hpp"
 #include "HMAC.hpp"
+#include "Node.hpp"
 
 #ifdef __WINDOWS__
 #define strtoull _strtoui64
@@ -170,6 +171,7 @@ std::vector<std::string> NodeConfig::execute(const char *command)
 		_P("200 help listnetworks");
 		_P("200 help join <network ID>");
 		_P("200 help leave <network ID>");
+		_P("200 help terminate [<reason>]");
 	} else if (cmd[0] == "listpeers") {
 		_P("200 listpeers <ztaddr> <ipv4> <ipv6> <latency> <version>");
 		_r->topology->eachPeer(_DumpPeerStatistics(r));
@@ -231,6 +233,10 @@ std::vector<std::string> NodeConfig::execute(const char *command)
 		} else {
 			_P("400 leave requires a network ID (>0) in hexadecimal format");
 		}
+	} else if (cmd[0] == "terminate") {
+		if (cmd.size() > 1)
+			_r->node->terminate(Node::NODE_NORMAL_TERMINATION,cmd[1].c_str());
+		else _r->node->terminate(Node::NODE_NORMAL_TERMINATION,(const char *)0);
 	} else {
 		_P("404 %s No such command. Use 'help' for help.",cmd[0].c_str());
 	}

+ 2 - 1
node/RuntimeEnvironment.hpp

@@ -44,6 +44,7 @@ class SysEnv;
 class Multicaster;
 class CMWC4096;
 class Service;
+class Node;
 
 /**
  * Holds global state for an instance of ZeroTier::Node
@@ -96,7 +97,7 @@ public:
 	Topology *topology;
 	SysEnv *sysEnv;
 	NodeConfig *nc;
-
+	Node *node;
 #ifndef __WINDOWS__
 	Service *netconfService; // may be null
 #endif