Browse Source

Merge branch 'dev' into systemtray

Grant Limberg 8 years ago
parent
commit
b4bacd50a1

File diff suppressed because it is too large
+ 785 - 694
controller/EmbeddedNetworkController.cpp


+ 44 - 6
controller/EmbeddedNetworkController.hpp

@@ -37,11 +37,15 @@
 
 #include "../osdep/OSUtils.hpp"
 #include "../osdep/Thread.hpp"
+#include "../osdep/BlockingQueue.hpp"
 
 #include "../ext/json/json.hpp"
 
 #include "JSONDB.hpp"
 
+// Number of background threads to start -- not actually started until needed
+#define ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT 2
+
 namespace ZeroTier {
 
 class Node;
@@ -52,13 +56,14 @@ public:
 	EmbeddedNetworkController(Node *node,const char *dbPath);
 	virtual ~EmbeddedNetworkController();
 
-	virtual NetworkController::ResultCode doNetworkConfigRequest(
+	virtual void init(const Identity &signingId,Sender *sender);
+
+	virtual void request(
+		uint64_t nwid,
 		const InetAddress &fromAddr,
-		const Identity &signingId,
+		uint64_t requestPacketId,
 		const Identity &identity,
-		uint64_t nwid,
-		const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData,
-		NetworkConfig &nc);
+		const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
 
 	unsigned int handleControlPlaneHttpGET(
 		const std::vector<std::string> &path,
@@ -82,8 +87,31 @@ public:
 		std::string &responseBody,
 		std::string &responseContentType);
 
+	void threadMain()
+		throw();
+
 private:
 	static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report);
+	void _request(
+		uint64_t nwid,
+		const InetAddress &fromAddr,
+		uint64_t requestPacketId,
+		const Identity &identity,
+		const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
+
+	struct _RQEntry
+	{
+		uint64_t nwid;
+		uint64_t requestPacketId;
+		InetAddress fromAddr;
+		Identity identity;
+		Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData;
+	};
+	BlockingQueue<_RQEntry *> _queue;
+
+	Thread _threads[ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT];
+	bool _threadsStarted;
+	Mutex _threads_m;
 
 	// Gathers a bunch of statistics about members of a network, IP assignments, etc. that we need in various places
 	// This does lock _networkMemberCache_m
@@ -96,9 +124,14 @@ private:
 		unsigned long activeMemberCount;
 		unsigned long totalMemberCount;
 		uint64_t mostRecentDeauthTime;
+		uint64_t nmiTimestamp; // time this NMI structure was computed
 	};
+	std::map<uint64_t,_NetworkMemberInfo> _nmiCache;
+	Mutex _nmiCache_m;
 	void _getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi);
 
+	void _pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member);
+
 	// These init objects with default and static/informational fields
 	inline void _initMember(nlohmann::json &member)
 	{
@@ -112,7 +145,8 @@ private:
 		if (!member.count("creationTime")) member["creationTime"] = OSUtils::now();
 		if (!member.count("noAutoAssignIps")) member["noAutoAssignIps"] = false;
 		if (!member.count("revision")) member["revision"] = 0ULL;
-		if (!member.count("enableBroadcast")) member["enableBroadcast"] = true;
+		if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
+		if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
 		member["objtype"] = "member";
 	}
 	inline void _initNetwork(nlohmann::json &network)
@@ -121,6 +155,7 @@ private:
 		if (!network.count("creationTime")) network["creationTime"] = OSUtils::now();
 		if (!network.count("name")) network["name"] = "";
 		if (!network.count("multicastLimit")) network["multicastLimit"] = (uint64_t)32;
+		if (!network.count("enableBroadcast")) network["enableBroadcast"] = true;
 		if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}};
 		if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}};
 		if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array();
@@ -154,6 +189,9 @@ private:
 	Node *const _node;
 	std::string _path;
 
+	NetworkController::Sender *_sender;
+	Identity _signingId;
+
 	struct _CircuitTestEntry
 	{
 		ZT_CircuitTest *test;

+ 1 - 1
controller/JSONDB.hpp

@@ -79,7 +79,7 @@ public:
 	{
 		for(std::map<std::string,_E>::iterator i(_db.lower_bound(prefix));i!=_db.end();) {
 			if ((i->first.length() >= prefix.length())&&(!memcmp(i->first.data(),prefix.data(),prefix.length()))) {
-				if (!func(i->first,get(i->second.obj,maxSinceCheck))) {
+				if (!func(i->first,get(i->first,maxSinceCheck))) {
 					std::map<std::string,_E>::iterator i2(i); ++i2;
 					this->erase(i->first);
 					i = i2;

+ 0 - 10
include/ZeroTierOne.h

@@ -1018,16 +1018,6 @@ typedef struct
 	 */
 	uint64_t address;
 
-	/**
-	 * Time we last received a unicast frame from this peer
-	 */
-	uint64_t lastUnicastFrame;
-
-	/**
-	 * Time we last received a multicast rame from this peer
-	 */
-	uint64_t lastMulticastFrame;
-
 	/**
 	 * Remote major version or -1 if not known
 	 */

+ 0 - 18
java/jni/ZT_jniutils.cpp

@@ -475,8 +475,6 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer)
     jclass peerClass = NULL;
 
     jfieldID addressField = NULL;
-    jfieldID lastUnicastFrameField = NULL;
-    jfieldID lastMulticastFrameField = NULL;
     jfieldID versionMajorField = NULL;
     jfieldID versionMinorField = NULL;
     jfieldID versionRevField = NULL;
@@ -500,20 +498,6 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer)
         return NULL;
     }
 
-    lastUnicastFrameField = lookup.findField(peerClass, "lastUnicastFrame", "J");
-    if(env->ExceptionCheck() || lastUnicastFrameField == NULL)
-    {
-        LOGE("Error finding lastUnicastFrame field of Peer object");
-        return NULL;
-    }
-
-    lastMulticastFrameField = lookup.findField(peerClass, "lastMulticastFrame", "J");
-    if(env->ExceptionCheck() || lastMulticastFrameField == NULL)
-    {
-        LOGE("Error finding lastMulticastFrame field of Peer object");
-        return NULL;
-    }
-
     versionMajorField = lookup.findField(peerClass, "versionMajor", "I");
     if(env->ExceptionCheck() || versionMajorField == NULL)
     {
@@ -571,8 +555,6 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer)
     }
 
     env->SetLongField(peerObject, addressField, (jlong)peer.address);
-    env->SetLongField(peerObject, lastUnicastFrameField, (jlong)peer.lastUnicastFrame);
-    env->SetLongField(peerObject, lastMulticastFrameField, (jlong)peer.lastMulticastFrame);
     env->SetIntField(peerObject, versionMajorField, peer.versionMajor);
     env->SetIntField(peerObject, versionMinorField, peer.versionMinor);
     env->SetIntField(peerObject, versionRevField, peer.versionRev);

+ 0 - 16
java/src/com/zerotier/sdk/Peer.java

@@ -34,8 +34,6 @@ import java.util.ArrayList;
  */
 public final class Peer {
     private long address;
-    private long lastUnicastFrame;
-    private long lastMulticastFrame;
     private int versionMajor;
     private int versionMinor;
     private int versionRev;
@@ -52,20 +50,6 @@ public final class Peer {
         return address;
     }
 
-    /**
-     * Time we last received a unicast frame from this peer
-     */
-    public final long lastUnicastFrame() {
-        return lastUnicastFrame;
-    }
-
-    /**
-     * Time we last received a multicast rame from this peer
-     */
-    public final long lastMulticastFrame() {
-        return lastMulticastFrame;
-    }
-
     /**
      * Remote major version or -1 if not known
      */

+ 6 - 0
macui/ZeroTier One/Network.h

@@ -59,4 +59,10 @@ enum NetworkType {
 - (NSString*)statusString;
 - (NSString*)typeString;
 
+- (BOOL)hasSameNetworkId:(UInt64)networkId;
+
+- (BOOL)isEqualToNetwork:(Network*)network;
+- (BOOL)isEqual:(id)object;
+- (NSUInteger)hash;
+
 @end

+ 59 - 0
macui/ZeroTier One/Network.m

@@ -275,4 +275,63 @@ NSString *NetworkAllowDefaultKey = @"allowDefault";
     }
 }
 
+- (BOOL)hasSameNetworkId:(UInt64)networkId
+{
+    return self.nwid == networkId;
+}
+
+- (BOOL)isEqualToNetwork:(Network*)network
+{
+    return [self.assignedAddresses isEqualToArray:network.assignedAddresses] &&
+        self.bridge == network.bridge &&
+        self.broadcastEnabled == network.broadcastEnabled &&
+        self.dhcp == network.dhcp &&
+        [self.mac isEqualToString:network.mac] &&
+        self.mtu == network.mtu &&
+        self.netconfRevision == network.netconfRevision &&
+        [self.name isEqualToString:network.name] &&
+        self.nwid == network.nwid &&
+        [self.portDeviceName isEqualToString:network.portDeviceName] &&
+        self.status == network.status &&
+        self.type == network.type &&
+        self.allowManaged == network.allowManaged &&
+        self.allowGlobal == network.allowGlobal &&
+        self.allowDefault == network.allowDefault &&
+        self.connected == network.connected;
+}
+
+- (BOOL)isEqual:(id)object
+{
+    if (self == object) {
+        return YES;
+    }
+
+    if (![object isKindOfClass:[Network class]]) {
+        return NO;
+    }
+
+    return [self isEqualToNetwork:object];
+}
+
+- (NSUInteger)hash
+{
+    return [self.assignedAddresses hash] ^
+        self.bridge ^
+        self.broadcastEnabled ^
+        self.dhcp ^
+        [self.mac hash] ^
+        self.mtu ^
+        self.netconfRevision ^
+        [self.name hash] ^
+        self.nwid ^
+        [self.portDeviceName hash] ^
+        self.portError ^
+        self.status ^
+        self.type ^
+        self.allowManaged ^
+        self.allowGlobal ^
+        self.allowDefault ^
+        self.connected;
+}
+
 @end

+ 1 - 1
macui/ZeroTier One/ShowNetworksViewController.h

@@ -23,7 +23,7 @@
 
 @interface ShowNetworksViewController : NSViewController<NSTableViewDelegate, NSTableViewDataSource>
 
-@property (nonatomic) NSArray<Network*> *networkList;
+@property (nonatomic) NSMutableArray<Network*> *networkList;
 @property (nonatomic) NetworkMonitor *netMonitor;
 @property (nonatomic) BOOL visible;
 

+ 37 - 3
macui/ZeroTier One/ShowNetworksViewController.m

@@ -21,6 +21,17 @@
 #import "NetworkInfoCell.h"
 #import "Network.h"
 
+BOOL hasNetworkWithID(NSArray<Network*> *list, UInt64 nwid)
+{
+    for(Network *n in list) {
+        if(n.nwid == nwid) {
+            return YES;
+        }
+    }
+
+    return NO;
+}
+
 @interface ShowNetworksViewController ()
 
 @end
@@ -30,6 +41,8 @@
 - (void)viewDidLoad {
     [super viewDidLoad];
 
+    self.networkList = [NSMutableArray array];
+
     [self.tableView setDelegate:self];
     [self.tableView setDataSource:self];
     [self.tableView setBackgroundColor:[NSColor clearColor]];
@@ -50,9 +63,30 @@
 }
 
 - (void)setNetworks:(NSArray<Network *> *)list {
-    _networkList = list;
-    if(_visible) {
-        [_tableView reloadData];
+    for (Network *n in list) {
+        if ([_networkList containsObject:n]) {
+            // don't need to do anything here.  Already an identical object in the list
+            continue;
+        }
+        else {
+            // network not in the list based on equality.  Did an object change? or is it a new item?
+            if (hasNetworkWithID(_networkList, n.nwid)) {
+
+                for (int i = 0; i < [_networkList count]; ++i) {
+                    Network *n2 = [_networkList objectAtIndex:i];
+                    if (n.nwid == n2.nwid)
+                    {
+                        [_networkList replaceObjectAtIndex:i withObject:n];
+                        [_tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:i]
+                                              columnIndexes:[NSIndexSet indexSetWithIndex:0]];
+                    }
+                }
+            }
+            else {
+                [_networkList addObject:n];
+                [_tableView reloadData];
+            }
+        }
     }
 }
 

+ 8 - 7
macui/ZeroTier One/ShowNetworksViewController.xib

@@ -1,8 +1,9 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2555" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
     <dependencies>
         <deployment identifier="macosx"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11542"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
         <customObject id="-2" userLabel="File's Owner" customClass="ShowNetworksViewController" customModule="ZeroTier_One" customModuleProvider="target">
@@ -24,7 +25,7 @@
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="none" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="386" rowSizeStyle="automatic" viewBased="YES" id="w5O-vn-cYB">
-                                <rect key="frame" x="0.0" y="0.0" width="530" height="0.0"/>
+                                <rect key="frame" x="0.0" y="0.0" width="530" height="481"/>
                                 <autoresizingMask key="autoresizingMask"/>
                                 <size key="intercellSpacing" width="3" height="2"/>
                                 <color key="backgroundColor" red="1" green="1" blue="1" alpha="0.59999999999999998" colorSpace="calibratedRGB"/>
@@ -50,7 +51,7 @@
                                                 <subviews>
                                                     <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="EUT-1A-lgY">
                                                         <rect key="frame" x="480" y="364" width="44" height="19"/>
-                                                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="bf8-gi-tpp">
+                                                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Label" id="bf8-gi-tpp">
                                                             <font key="font" size="13" name="AndaleMono"/>
                                                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@@ -242,7 +243,7 @@
                                                     </textField>
                                                     <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="GEJ-6D-gWU">
                                                         <rect key="frame" x="102" y="86" width="424" height="19"/>
-                                                        <textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Multiline Label" id="A3M-ZZ-6h7">
+                                                        <textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="right" title="Multiline Label" id="A3M-ZZ-6h7">
                                                             <font key="font" size="13" name="AndaleMono"/>
                                                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@@ -250,7 +251,7 @@
                                                     </textField>
                                                     <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lO9-Jg-9f8">
                                                         <rect key="frame" x="1" y="364" width="44" height="19"/>
-                                                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="p7O-rs-RvR">
+                                                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Label" id="p7O-rs-RvR">
                                                             <font key="font" size="13" name="AndaleMono"/>
                                                             <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>

+ 4 - 0
make-linux.mk

@@ -84,6 +84,10 @@ ifeq ($(ZT_TRACE),1)
 	DEFS+=-DZT_TRACE
 endif
 
+ifeq ($(ZT_RULES_ENGINE_DEBUGGING),1)
+	DEFS+=-DZT_RULES_ENGINE_DEBUGGING
+endif
+
 ifeq ($(ZT_DEBUG),1)
 	DEFS+=-DZT_TRACE
 	override CFLAGS+=-Wall -g -O -pthread $(INCLUDES) $(DEFS)

+ 2 - 2
node/Constants.hpp

@@ -256,12 +256,12 @@
 /**
  * How frequently to send heartbeats over in-use paths
  */
-#define ZT_PATH_HEARTBEAT_PERIOD 10000
+#define ZT_PATH_HEARTBEAT_PERIOD 14000
 
 /**
  * Paths are considered inactive if they have not received traffic in this long
  */
-#define ZT_PATH_ALIVE_TIMEOUT 25000
+#define ZT_PATH_ALIVE_TIMEOUT 45000
 
 /**
  * Minimum time between attempts to check dead paths to see if they can be re-awakened

+ 2 - 82
node/IncomingPacket.cpp

@@ -865,92 +865,12 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 		const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID);
 		const unsigned int hopCount = hops();
 		const uint64_t requestPacketId = packetId();
-		bool trustEstablished = false;
 
 		if (RR->localNetworkController) {
 			const unsigned int metaDataLength = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN);
 			const char *metaDataBytes = (const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength);
 			const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData(metaDataBytes,metaDataLength);
-
-			NetworkConfig *netconf = new NetworkConfig();
-			try {
-				switch(RR->localNetworkController->doNetworkConfigRequest((hopCount > 0) ? InetAddress() : _path->address(),RR->identity,peer->identity(),nwid,metaData,*netconf)) {
-
-					case NetworkController::NETCONF_QUERY_OK: {
-						trustEstablished = true;
-						Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *dconf = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
-						try {
-							if (netconf->toDictionary(*dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) {
-								uint64_t configUpdateId = RR->node->prng();
-								if (!configUpdateId) ++configUpdateId;
-								const unsigned int totalSize = dconf->sizeBytes();
-								unsigned int chunkIndex = 0;
-								while (chunkIndex < totalSize) {
-									const unsigned int chunkLen = std::min(totalSize - chunkIndex,(unsigned int)(ZT_UDP_DEFAULT_PAYLOAD_MTU - (ZT_PACKET_IDX_PAYLOAD + 256)));
-									Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
-									outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
-									outp.append(requestPacketId);
-
-									const unsigned int sigStart = outp.size();
-									outp.append(nwid);
-									outp.append((uint16_t)chunkLen);
-									outp.append((const void *)(dconf->data() + chunkIndex),chunkLen);
-
-									outp.append((uint8_t)0); // no flags
-									outp.append((uint64_t)configUpdateId);
-									outp.append((uint32_t)totalSize);
-									outp.append((uint32_t)chunkIndex);
-
-									C25519::Signature sig(RR->identity.sign(reinterpret_cast<const uint8_t *>(outp.data()) + sigStart,outp.size() - sigStart));
-									outp.append((uint8_t)1);
-									outp.append((uint16_t)ZT_C25519_SIGNATURE_LEN);
-									outp.append(sig.data,ZT_C25519_SIGNATURE_LEN);
-
-									outp.compress();
-									RR->sw->send(outp,true);
-									chunkIndex += chunkLen;
-								}
-							}
-							delete dconf;
-						} catch ( ... ) {
-							delete dconf;
-							throw;
-						}
-					}	break;
-
-					case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: {
-						Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
-						outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
-						outp.append(requestPacketId);
-						outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND);
-						outp.append(nwid);
-						outp.armor(peer->key(),true);
-						_path->send(RR,outp.data(),outp.size(),RR->node->now());
-					}	break;
-
-					case NetworkController::NETCONF_QUERY_ACCESS_DENIED: {
-						Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
-						outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
-						outp.append(requestPacketId);
-						outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_);
-						outp.append(nwid);
-						outp.armor(peer->key(),true);
-						_path->send(RR,outp.data(),outp.size(),RR->node->now());
-					} break;
-
-					case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR:
-						break;
-					case NetworkController::NETCONF_QUERY_IGNORE:
-						break;
-					default:
-						TRACE("NETWORK_CONFIG_REQUEST failed: invalid return value from NetworkController::doNetworkConfigRequest()");
-						break;
-				}
-				delete netconf;
-			} catch ( ... ) {
-				delete netconf;
-				throw;
-			}
+			RR->localNetworkController->request(nwid,(hopCount > 0) ? InetAddress() : _path->address(),requestPacketId,peer->identity(),metaData);
 		} else {
 			Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR);
 			outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
@@ -961,7 +881,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 			_path->send(RR,outp.data(),outp.size(),RR->node->now());
 		}
 
-		peer->received(_path,hopCount,requestPacketId,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,trustEstablished);
+		peer->received(_path,hopCount,requestPacketId,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,false);
 	} catch (std::exception &exc) {
 		fprintf(stderr,"WARNING: network config request failed with exception: %s" ZT_EOL_S,exc.what());
 		TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): %s",source().toString().c_str(),_path->address().toString().c_str(),exc.what());

+ 43 - 62
node/Network.cpp

@@ -599,7 +599,7 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) :
 		if (conf.length()) {
 			dconf->load(conf.c_str());
 			if (nconf->fromDictionary(*dconf)) {
-				this->_setConfiguration(*nconf,false);
+				this->setConfiguration(*nconf,false);
 				_lastConfigUpdate = 0; // we still want to re-request a new config from the network
 				gotConf = true;
 			}
@@ -1015,7 +1015,7 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
 	}
 
 	if (nc) {
-		this->_setConfiguration(*nc,true);
+		this->setConfiguration(*nc,true);
 		delete nc;
 		return configUpdateId;
 	} else {
@@ -1025,6 +1025,46 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
 	return 0;
 }
 
+int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk)
+{
+	// _lock is NOT locked when this is called
+	try {
+		if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id))
+			return 0;
+		if (_config == nconf)
+			return 1; // OK config, but duplicate of what we already have
+
+		ZT_VirtualNetworkConfig ctmp;
+		bool oldPortInitialized;
+		{
+			Mutex::Lock _l(_lock);
+			_config = nconf;
+			_lastConfigUpdate = RR->node->now();
+			_netconfFailure = NETCONF_FAILURE_NONE;
+			oldPortInitialized = _portInitialized;
+			_portInitialized = true;
+			_externalConfig(&ctmp);
+		}
+		_portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
+
+		if (saveToDisk) {
+			Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+			try {
+				char n[64];
+				Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
+				if (nconf.toDictionary(*d,false))
+					RR->node->dataStorePut(n,(const void *)d->data(),d->sizeBytes(),true);
+			} catch ( ... ) {}
+			delete d;
+		}
+
+		return 2; // OK and configuration has changed
+	} catch ( ... ) {
+		TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id);
+	}
+	return 0;
+}
+
 void Network::requestConfiguration()
 {
 	const Address ctrl(controller());
@@ -1046,26 +1086,7 @@ void Network::requestConfiguration()
 
 	if (ctrl == RR->identity.address()) {
 		if (RR->localNetworkController) {
-			NetworkConfig *nconf = new NetworkConfig();
-			try {
-				switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,*nconf)) {
-					case NetworkController::NETCONF_QUERY_OK:
-						this->_setConfiguration(*nconf,true);
-						break;
-					case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND:
-						this->setNotFound();
-						break;
-					case NetworkController::NETCONF_QUERY_ACCESS_DENIED:
-						this->setAccessDenied();
-						break;
-					default:
-						this->setNotFound();
-						break;
-				}
-			} catch ( ... ) {
-				this->setNotFound();
-			}
-			delete nconf;
+			RR->localNetworkController->request(_id,InetAddress(),0xffffffffffffffffULL,RR->identity,rmd);
 		} else {
 			this->setNotFound();
 		}
@@ -1257,46 +1278,6 @@ ZT_VirtualNetworkStatus Network::_status() const
 	}
 }
 
-int Network::_setConfiguration(const NetworkConfig &nconf,bool saveToDisk)
-{
-	// _lock is NOT locked when this is called
-	try {
-		if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id))
-			return 0;
-		if (_config == nconf)
-			return 1; // OK config, but duplicate of what we already have
-
-		ZT_VirtualNetworkConfig ctmp;
-		bool oldPortInitialized;
-		{
-			Mutex::Lock _l(_lock);
-			_config = nconf;
-			_lastConfigUpdate = RR->node->now();
-			_netconfFailure = NETCONF_FAILURE_NONE;
-			oldPortInitialized = _portInitialized;
-			_portInitialized = true;
-			_externalConfig(&ctmp);
-		}
-		_portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
-
-		if (saveToDisk) {
-			Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
-			try {
-				char n[64];
-				Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
-				if (nconf.toDictionary(*d,false))
-					RR->node->dataStorePut(n,(const void *)d->data(),d->sizeBytes(),true);
-			} catch ( ... ) {}
-			delete d;
-		}
-
-		return 2; // OK and configuration has changed
-	} catch ( ... ) {
-		TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id);
-	}
-	return 0;
-}
-
 void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const
 {
 	// assumes _lock is locked

+ 9 - 1
node/Network.hpp

@@ -187,6 +187,15 @@ public:
 	 */
 	uint64_t handleConfigChunk(const Packet &chunk,unsigned int ptr);
 
+	/**
+	 * Set network configuration
+	 *
+	 * @param nconf Network configuration
+	 * @param saveToDisk Save to disk? Used during loading, should usually be true otherwise.
+	 * @return 0 == bad, 1 == accepted but duplicate/unchanged, 2 == accepted and new
+	 */
+	int setConfiguration(const NetworkConfig &nconf,bool saveToDisk);
+
 	/**
 	 * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this
 	 */
@@ -328,7 +337,6 @@ public:
 	inline void **userPtr() throw() { return &_uPtr; }
 
 private:
-	int _setConfiguration(const NetworkConfig &nconf,bool saveToDisk);
 	ZT_VirtualNetworkStatus _status() const;
 	void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked
 	bool _gate(const SharedPtr<Peer> &peer);

+ 46 - 23
node/NetworkController.hpp

@@ -27,7 +27,6 @@
 
 namespace ZeroTier {
 
-class RuntimeEnvironment;
 class Identity;
 class Address;
 struct InetAddress;
@@ -38,45 +37,69 @@ struct InetAddress;
 class NetworkController
 {
 public:
+	enum ErrorCode
+	{
+		NC_ERROR_NONE = 0,
+		NC_ERROR_OBJECT_NOT_FOUND = 1,
+		NC_ERROR_ACCESS_DENIED = 2,
+		NC_ERROR_INTERNAL_SERVER_ERROR = 3
+	};
+
 	/**
-	 * Return value of doNetworkConfigRequest
+	 * Interface for sender used to send pushes and replies
 	 */
-	enum ResultCode
+	class Sender
 	{
-		NETCONF_QUERY_OK = 0,
-		NETCONF_QUERY_OBJECT_NOT_FOUND = 1,
-		NETCONF_QUERY_ACCESS_DENIED = 2,
-		NETCONF_QUERY_INTERNAL_SERVER_ERROR = 3,
-		NETCONF_QUERY_IGNORE = 4
+	public:
+		/**
+		 * Send a configuration to a remote peer
+		 *
+		 * @param nwid Network ID
+		 * @param requestPacketId Request packet ID to send OK(NETWORK_CONFIG_REQUEST) or 0 to send NETWORK_CONFIG (push)
+		 * @param destination Destination peer Address
+		 * @param nc Network configuration to send
+		 * @param sendLegacyFormatConfig If true, send an old-format network config
+		 */
+		virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig) = 0;
+
+		/**
+		 * Send a network configuration request error
+		 *
+		 * @param nwid Network ID
+		 * @param requestPacketId Request packet ID or 0 if none
+		 * @param destination Destination peer Address
+		 * @param errorCode Error code
+		 */
+		virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode) = 0;
 	};
 
 	NetworkController() {}
 	virtual ~NetworkController() {}
 
 	/**
-	 * Handle a network config request, sending replies if necessary
-	 *
-	 * This call is permitted to block, and may be called concurrently from more
-	 * than one thread. Implementations must use locks if needed.
+	 * Called when this is added to a Node to initialize and supply info
 	 *
-	 * On internal server errors, the 'error' field in result can be filled in
-	 * to indicate the error.
+	 * @param signingId Identity for signing of network configurations, certs, etc.
+	 * @param sender Sender implementation for sending replies or config pushes
+	 */
+	virtual void init(const Identity &signingId,Sender *sender) = 0;
+
+	/**
+	 * Handle a network configuration request
 	 *
-	 * @param fromAddr Originating wire address or null address if packet is not direct (or from self)
-	 * @param signingId Identity that should be used to sign results -- must include private key
-	 * @param identity Originating peer ZeroTier identity
 	 * @param nwid 64-bit network ID
+	 * @param fromAddr Originating wire address or null address if packet is not direct (or from self)
+	 * @param requestPacketId Packet ID of request packet or 0 if not initiated by remote request
+	 * @param identity ZeroTier identity of originating peer
 	 * @param metaData Meta-data bundled with request (if any)
-	 * @param nc NetworkConfig to fill with results
 	 * @return Returns NETCONF_QUERY_OK if result 'nc' is valid, or an error code on error
 	 */
-	virtual NetworkController::ResultCode doNetworkConfigRequest(
+	virtual void request(
+		uint64_t nwid,
 		const InetAddress &fromAddr,
-		const Identity &signingId,
+		uint64_t requestPacketId,
 		const Identity &identity,
-		uint64_t nwid,
-		const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData,
-		NetworkConfig &nc) = 0;
+		const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData) = 0;
 };
 
 } // namespace ZeroTier

+ 87 - 2
node/Node.cpp

@@ -405,8 +405,6 @@ ZT_PeerList *Node::peers() const
 	for(std::vector< std::pair< Address,SharedPtr<Peer> > >::iterator pi(peers.begin());pi!=peers.end();++pi) {
 		ZT_Peer *p = &(pl->peers[pl->peerCount++]);
 		p->address = pi->second->address().toInt();
-		p->lastUnicastFrame = pi->second->lastUnicastFrame();
-		p->lastMulticastFrame = pi->second->lastMulticastFrame();
 		if (pi->second->remoteVersionKnown()) {
 			p->versionMajor = pi->second->remoteVersionMajor();
 			p->versionMinor = pi->second->remoteVersionMinor();
@@ -492,6 +490,7 @@ void Node::clearLocalInterfaceAddresses()
 void Node::setNetconfMaster(void *networkControllerInstance)
 {
 	RR->localNetworkController = reinterpret_cast<NetworkController *>(networkControllerInstance);
+	RR->localNetworkController->init(RR->identity,this);
 }
 
 ZT_ResultCode Node::circuitTestBegin(ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *))
@@ -720,6 +719,92 @@ void Node::setTrustedPaths(const struct sockaddr_storage *networks,const uint64_
 	RR->topology->setTrustedPaths(reinterpret_cast<const InetAddress *>(networks),ids,count);
 }
 
+void Node::ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig)
+{
+	if (destination == RR->identity.address()) {
+		SharedPtr<Network> n(network(nwid));
+		if (!n) return;
+		n->setConfiguration(nc,true);
+	} else {
+		Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *dconf = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+		try {
+			if (nc.toDictionary(*dconf,sendLegacyFormatConfig)) {
+				uint64_t configUpdateId = prng();
+				if (!configUpdateId) ++configUpdateId;
+
+				const unsigned int totalSize = dconf->sizeBytes();
+				unsigned int chunkIndex = 0;
+				while (chunkIndex < totalSize) {
+					const unsigned int chunkLen = std::min(totalSize - chunkIndex,(unsigned int)(ZT_UDP_DEFAULT_PAYLOAD_MTU - (ZT_PACKET_IDX_PAYLOAD + 256)));
+					Packet outp(destination,RR->identity.address(),(requestPacketId) ? Packet::VERB_OK : Packet::VERB_NETWORK_CONFIG);
+					if (requestPacketId) {
+						outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
+						outp.append(requestPacketId);
+					}
+
+					const unsigned int sigStart = outp.size();
+					outp.append(nwid);
+					outp.append((uint16_t)chunkLen);
+					outp.append((const void *)(dconf->data() + chunkIndex),chunkLen);
+
+					outp.append((uint8_t)0); // no flags
+					outp.append((uint64_t)configUpdateId);
+					outp.append((uint32_t)totalSize);
+					outp.append((uint32_t)chunkIndex);
+
+					C25519::Signature sig(RR->identity.sign(reinterpret_cast<const uint8_t *>(outp.data()) + sigStart,outp.size() - sigStart));
+					outp.append((uint8_t)1);
+					outp.append((uint16_t)ZT_C25519_SIGNATURE_LEN);
+					outp.append(sig.data,ZT_C25519_SIGNATURE_LEN);
+
+					outp.compress();
+					RR->sw->send(outp,true);
+					chunkIndex += chunkLen;
+				}
+			}
+			delete dconf;
+		} catch ( ... ) {
+			delete dconf;
+			throw;
+		}
+	}
+}
+
+void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode)
+{
+	if (destination == RR->identity.address()) {
+		SharedPtr<Network> n(network(nwid));
+		if (!n) return;
+		switch(errorCode) {
+			case NetworkController::NC_ERROR_OBJECT_NOT_FOUND:
+			case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR:
+				n->setNotFound();
+				break;
+			case NetworkController::NC_ERROR_ACCESS_DENIED:
+				n->setAccessDenied();
+				break;
+
+			default: break;
+		}
+	} else if (requestPacketId) {
+		Packet outp(destination,RR->identity.address(),Packet::VERB_ERROR);
+		outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
+		outp.append(requestPacketId);
+		switch(errorCode) {
+			//case NetworkController::NC_ERROR_OBJECT_NOT_FOUND:
+			//case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR:
+			default:
+				outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND);
+				break;
+			case NetworkController::NC_ERROR_ACCESS_DENIED:
+				outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_);
+				break;
+		}
+		outp.append(nwid);
+		RR->sw->send(outp,true);
+	} // else we can't send an ERROR() in response to nothing, so discard
+}
+
 } // namespace ZeroTier
 
 /****************************************************************************/

+ 6 - 2
node/Node.hpp

@@ -36,6 +36,7 @@
 #include "Network.hpp"
 #include "Path.hpp"
 #include "Salsa20.hpp"
+#include "NetworkController.hpp"
 
 #undef TRACE
 #ifdef ZT_TRACE
@@ -55,7 +56,7 @@ namespace ZeroTier {
  *
  * The pointer returned by ZT_Node_new() is an instance of this class.
  */
-class Node
+class Node : public NetworkController::Sender
 {
 public:
 	Node(
@@ -69,7 +70,7 @@ public:
 		ZT_PathCheckFunction pathCheckFunction,
 		ZT_EventCallback eventCallback);
 
-	~Node();
+	virtual ~Node();
 
 	// Public API Functions ----------------------------------------------------
 
@@ -282,6 +283,9 @@ public:
 		return false;
 	}
 
+	virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig);
+	virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode);
+
 private:
 	inline SharedPtr<Network> _network(uint64_t nwid) const
 	{

+ 11 - 6
node/Peer.cpp

@@ -42,8 +42,7 @@ static uint32_t _natKeepaliveBuf = 0;
 
 Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) :
 	_lastReceive(0),
-	_lastUnicastFrame(0),
-	_lastMulticastFrame(0),
+	_lastNontrivialReceive(0),
 	_lastDirectPathPushSent(0),
 	_lastDirectPathPushReceive(0),
 	_lastCredentialRequestSent(0),
@@ -128,10 +127,16 @@ void Peer::received(
 #endif
 
 	_lastReceive = now;
-	if ((verb == Packet::VERB_FRAME)||(verb == Packet::VERB_EXT_FRAME))
-		_lastUnicastFrame = now;
-	else if (verb == Packet::VERB_MULTICAST_FRAME)
-		_lastMulticastFrame = now;
+	switch (verb) {
+		case Packet::VERB_FRAME:
+		case Packet::VERB_EXT_FRAME:
+		case Packet::VERB_NETWORK_CONFIG_REQUEST:
+		case Packet::VERB_NETWORK_CONFIG:
+		case Packet::VERB_MULTICAST_FRAME:
+			_lastNontrivialReceive = now;
+			break;
+		default: break;
+	}
 
 	if (trustEstablished) {
 		_lastTrustEstablishedPacketReceived = now;

+ 2 - 18
node/Peer.hpp

@@ -226,25 +226,10 @@ public:
 	 */
 	inline bool isAlive(const uint64_t now) const { return ((now - _lastReceive) < ZT_PEER_ACTIVITY_TIMEOUT); }
 
-	/**
-	 * @return Time of most recent unicast frame received
-	 */
-	inline uint64_t lastUnicastFrame() const { return _lastUnicastFrame; }
-
-	/**
-	 * @return Time of most recent multicast frame received
-	 */
-	inline uint64_t lastMulticastFrame() const { return _lastMulticastFrame; }
-
-	/**
-	 * @return Time of most recent frame of any kind (unicast or multicast)
-	 */
-	inline uint64_t lastFrame() const { return std::max(_lastUnicastFrame,_lastMulticastFrame); }
-
 	/**
 	 * @return True if this peer has sent us real network traffic recently
 	 */
-	inline uint64_t isActive(uint64_t now) const { return ((now - lastFrame()) < ZT_PEER_ACTIVITY_TIMEOUT); }
+	inline uint64_t isActive(uint64_t now) const { return ((now - _lastNontrivialReceive) < ZT_PEER_ACTIVITY_TIMEOUT); }
 
 	/**
 	 * @return Latency in milliseconds or 0 if unknown
@@ -469,8 +454,7 @@ private:
 	uint8_t _key[ZT_PEER_SECRET_KEY_LENGTH];
 	uint8_t _remoteClusterOptimal6[16];
 	uint64_t _lastReceive; // direct or indirect
-	uint64_t _lastUnicastFrame;
-	uint64_t _lastMulticastFrame;
+	uint64_t _lastNontrivialReceive; // frames, things like netconf, etc.
 	uint64_t _lastDirectPathPushSent;
 	uint64_t _lastDirectPathPushReceive;
 	uint64_t _lastCredentialRequestSent;

+ 10 - 1
one.cpp

@@ -973,6 +973,7 @@ int main(int argc,char **argv)
 	std::string homeDir;
 	unsigned int port = ZT_DEFAULT_PORT;
 	bool skipRootCheck = false;
+	const char *allowManagementFrom = (const char *)0;
 
 	for(int i=1;i<argc;++i) {
 		if (argv[i][0] == '-') {
@@ -986,6 +987,14 @@ int main(int argc,char **argv)
 					}
 					break;
 
+				case 'M': // allow management from this IP/bits network
+					allowManagementFrom = argv[i] + 2;
+					if (!strlen(allowManagementFrom)) {
+						printHelp(argv[0],stdout);
+						return 1;
+					}
+					break;
+
 #ifdef __UNIX_LIKE__
 				case 'd': // Run in background as daemon
 					runAsDaemon = true;
@@ -1167,7 +1176,7 @@ int main(int argc,char **argv)
 	unsigned int returnValue = 0;
 
 	for(;;) {
-		zt1Service = OneService::newInstance(homeDir.c_str(),port);
+		zt1Service = OneService::newInstance(homeDir.c_str(),port,allowManagementFrom);
 		switch(zt1Service->run()) {
 			case OneService::ONE_STILL_RUNNING: // shouldn't happen, run() won't return until done
 			case OneService::ONE_NORMAL_TERMINATION:

+ 64 - 0
osdep/BlockingQueue.hpp

@@ -0,0 +1,64 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016  ZeroTier, Inc.  https://www.zerotier.com/
+ *
+ * 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/>.
+ */
+
+#ifndef ZT_BLOCKINGQUEUE_HPP
+#define ZT_BLOCKINGQUEUE_HPP
+
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+
+namespace ZeroTier {
+
+/**
+ * Simple C++11 thread-safe queue
+ *
+ * Do not use in node/ since we have not gone C++11 there yet.
+ */
+template <class T>
+class BlockingQueue
+{
+public:
+	BlockingQueue(void) {}
+
+	inline void post(T t)
+	{
+		std::lock_guard<std::mutex> lock(m);
+		q.push(t);
+		c.notify_one();
+	}
+
+	inline T get(void)
+	{
+		std::unique_lock<std::mutex> lock(m);
+		while(q.empty())
+			c.wait(lock);
+		T val = q.front();
+		q.pop();
+		return val;
+	}
+
+private:
+	std::queue<T> q;
+	mutable std::mutex m;
+	std::condition_variable c;
+};
+
+} // namespace ZeroTier
+
+#endif

+ 2 - 2
osdep/LinuxDropPrivileges.cpp

@@ -102,6 +102,8 @@ void dropPrivileges(std::string homeDir) {
         return;
     }
 
+    createOwnedHomedir(homeDir, targetUser);
+
     if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_NET_RAW, 0, 0) < 0) {
         // Kernel has no support for ambient capabilities.
         notDropping(homeDir);
@@ -113,8 +115,6 @@ void dropPrivileges(std::string homeDir) {
         return;
     }
 
-    createOwnedHomedir(homeDir, targetUser);
-
     if (setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID) | (1 << CAP_SETGID)) < 0) {
         fprintf(stderr, "ERROR: failed to set capabilities (not running as real root?)\n");
         exit(1);

+ 0 - 7
service/ControlPlane.cpp

@@ -222,8 +222,6 @@ static void _jsonAppend(unsigned int depth,std::string &buf,const ZT_Peer *peer)
 	Utils::snprintf(json,sizeof(json),
 		"%s{\n"
 		"%s\t\"address\": \"%.10llx\",\n"
-		"%s\t\"lastUnicastFrame\": %llu,\n"
-		"%s\t\"lastMulticastFrame\": %llu,\n"
 		"%s\t\"versionMajor\": %d,\n"
 		"%s\t\"versionMinor\": %d,\n"
 		"%s\t\"versionRev\": %d,\n"
@@ -234,8 +232,6 @@ static void _jsonAppend(unsigned int depth,std::string &buf,const ZT_Peer *peer)
 		"%s}",
 		prefix,
 		prefix,peer->address,
-		prefix,peer->lastUnicastFrame,
-		prefix,peer->lastMulticastFrame,
 		prefix,peer->versionMajor,
 		prefix,peer->versionMinor,
 		prefix,peer->versionRev,
@@ -274,9 +270,6 @@ unsigned int ControlPlane::handleRequest(
 	std::map<std::string,std::string> urlArgs;
 	Mutex::Lock _l(_lock);
 
-	if (!((fromAddress.ipsEqual(InetAddress::LO4))||(fromAddress.ipsEqual(InetAddress::LO6))))
-		return 403; // Forbidden: we only allow access from localhost right now
-
 	/* Note: this is kind of restricted in what it'll take. It does not support
 	 * URL encoding, and /'s in URL args will screw it up. But the only URL args
 	 * it really uses in ?jsonp=funcionName, and otherwise it just takes simple

+ 24 - 17
service/OneService.cpp

@@ -483,6 +483,7 @@ public:
 
 	const std::string _homePath;
 	BackgroundResolver _tcpFallbackResolver;
+	InetAddress _allowManagementFrom;
 	EmbeddedNetworkController *_controller;
 	Phy<OneServiceImpl *> _phy;
 	Node *_node;
@@ -570,7 +571,7 @@ public:
 
 	// end member variables ----------------------------------------------------
 
-	OneServiceImpl(const char *hp,unsigned int port) :
+	OneServiceImpl(const char *hp,unsigned int port,const char *allowManagementFrom) :
 		_homePath((hp) ? hp : ".")
 		,_tcpFallbackResolver(ZT_TCP_FALLBACK_RELAY)
 		,_controller((EmbeddedNetworkController *)0)
@@ -595,6 +596,9 @@ public:
 #endif
 		,_run(true)
 	{
+		if (allowManagementFrom)
+			_allowManagementFrom.fromString(allowManagementFrom);
+
 		_ports[0] = 0;
 		_ports[1] = 0;
 		_ports[2] = 0;
@@ -614,7 +618,7 @@ public:
 				struct sockaddr_in in4;
 				memset(&in4,0,sizeof(in4));
 				in4.sin_family = AF_INET;
-				in4.sin_addr.s_addr = Utils::hton((uint32_t)0x7f000001); // right now we just listen for TCP @127.0.0.1
+				in4.sin_addr.s_addr = Utils::hton((uint32_t)((allowManagementFrom) ? 0 : 0x7f000001)); // right now we just listen for TCP @127.0.0.1
 				in4.sin_port = Utils::hton((uint16_t)port);
 				_v4TcpControlSocket = _phy.tcpListen((const struct sockaddr *)&in4,this);
 
@@ -622,7 +626,8 @@ public:
 				memset((void *)&in6,0,sizeof(in6));
 				in6.sin6_family = AF_INET6;
 				in6.sin6_port = in4.sin_port;
-				in6.sin6_addr.s6_addr[15] = 1; // IPv6 localhost == ::1
+				if (!allowManagementFrom)
+					in6.sin6_addr.s6_addr[15] = 1; // IPv6 localhost == ::1
 				_v6TcpControlSocket = _phy.tcpListen((const struct sockaddr *)&in6,this);
 
 				// We must bind one of IPv4 or IPv6 -- support either failing to support hosts that
@@ -1259,12 +1264,10 @@ public:
 
 	inline void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from)
 	{
-		if ((!from)||(reinterpret_cast<const InetAddress *>(from)->ipScope() != InetAddress::IP_SCOPE_LOOPBACK)) {
-			// Non-Loopback: deny (for now)
+		if (!from) {
 			_phy.close(sockN,false);
 			return;
 		} else {
-			// Loopback == HTTP JSON API request
 			TcpConnection *tc = new TcpConnection();
 			_tcpConnections.insert(tc);
 			tc->type = TcpConnection::TCP_HTTP_INCOMING;
@@ -1701,16 +1704,20 @@ public:
 		std::string contentType("text/plain"); // default if not changed in handleRequest()
 		unsigned int scode = 404;
 
-		try {
-			if (_controlPlane)
-				scode = _controlPlane->handleRequest(tc->from,tc->parser.method,tc->url,tc->headers,tc->body,data,contentType);
-			else scode = 500;
-		} catch (std::exception &exc) {
-			fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: %s" ZT_EOL_S,exc.what());
-			scode = 500;
-		} catch ( ... ) {
-			fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: unknown exceptino" ZT_EOL_S);
-			scode = 500;
+		if ( ((!_allowManagementFrom)&&(tc->from.ipScope() == InetAddress::IP_SCOPE_LOOPBACK)) || (_allowManagementFrom.containsAddress(tc->from)) ) {
+			try {
+				if (_controlPlane)
+					scode = _controlPlane->handleRequest(tc->from,tc->parser.method,tc->url,tc->headers,tc->body,data,contentType);
+				else scode = 500;
+			} catch (std::exception &exc) {
+				fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: %s" ZT_EOL_S,exc.what());
+				scode = 500;
+			} catch ( ... ) {
+				fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: unknown exceptino" ZT_EOL_S);
+				scode = 500;
+			}
+		} else {
+			scode = 401;
 		}
 
 		const char *scodestr;
@@ -1975,7 +1982,7 @@ std::string OneService::autoUpdateUrl()
 	return std::string();
 }
 
-OneService *OneService::newInstance(const char *hp,unsigned int port) { return new OneServiceImpl(hp,port); }
+OneService *OneService::newInstance(const char *hp,unsigned int port,const char *allowManagementFrom) { return new OneServiceImpl(hp,port,allowManagementFrom); }
 OneService::~OneService() {}
 
 } // namespace ZeroTier

+ 3 - 1
service/OneService.hpp

@@ -98,10 +98,12 @@ public:
 	 *
 	 * @param hp Home path
 	 * @param port TCP and UDP port for packets and HTTP control (if 0, pick random port)
+	 * @param allowManagementFrom If non-NULL, allow control from supplied IP/netmask
 	 */
 	static OneService *newInstance(
 		const char *hp,
-		unsigned int port);
+		unsigned int port,
+		const char *allowManagementFrom = (const char *)0);
 
 	virtual ~OneService();
 

+ 16 - 3
service/README.md

@@ -25,6 +25,8 @@ A *jsonp* URL argument may be supplied to request JSONP encapsulation. A JSONP r
 <tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr>
 <tr><td>address</td><td>string</td><td>10-digit hexadecimal ZeroTier address of this node</td><td>no</td></tr>
 <tr><td>publicIdentity</td><td>string</td><td>Full public ZeroTier identity of this node</td><td>no</td></tr>
+<tr><td>worldId</td><td>integer</td><td>Fixed value representing the virtual data center of Earth.</td><td>no</td></tr>
+<tr><td>worldTimestamp</td><td>integer</td><td>Timestamp of the last root server topology change.</td><td>no</td></tr>
 <tr><td>online</td><td>boolean</td><td>Does this node appear to have upstream network access?</td><td>no</td></tr>
 <tr><td>tcpFallbackActive</td><td>boolean</td><td>Is TCP fallback mode active?</td><td>no</td></tr>
 <tr><td>versionMajor</td><td>integer</td><td>ZeroTier major version</td><td>no</td></tr>
@@ -77,9 +79,22 @@ Most network settings are not writable, as they are defined by the network contr
 <tr><td>broadcastEnabled</td><td>boolean</td><td>Is Ethernet broadcast (ff:ff:ff:ff:ff:ff) allowed?</td><td>no</td></tr>
 <tr><td>portError</td><td>integer</td><td>Error code (if any) returned by underlying OS "tap" driver</td><td>no</td></tr>
 <tr><td>netconfRevision</td><td>integer</td><td>Network configuration revision ID</td><td>no</td></tr>
-<tr><td>multicastSubscriptions</td><td>[string]</td><td>Multicast memberships as array of MAC/ADI tuples</td><td>no</td></tr>
 <tr><td>assignedAddresses</td><td>[string]</td><td>ZeroTier-managed IP address assignments as array of IP/netmask bits tuples</td><td>no</td></tr>
+<tr><td>routes</td><td>[route]</td><td>ZeroTier-managed route assignments for a network. See below for a description of the route object.</td><td>no</td></tr>
 <tr><td>portDeviceName</td><td>string</td><td>OS-specific network device name (if available)</td><td>no</td></tr>
+<tr><td>allowManaged</td><td>boolean</td><td>Whether ZeroTier-managed IP addresses are allowed.</td><td>yes</td></tr>
+<tr><td>allowGlobal</td><td>boolean</td><td>Whether globally-reachable IP addresses are allowed to be assigned.</td><td>yes</td></tr>
+<tr><td>allowDefault</td><td>boolean</td><td>Whether a default route is allowed to be assigned for the network (route all traffic via ZeroTier)</td><td>yes</td></tr> 
+</table>
+
+`route` objects
+
+<table>
+<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr>
+<tr><td>target</td><td>string</td><td>Target network / netmask bits, NULL, or 0.0.0.0/0 for default route</td><td>no</td></tr>
+<tr><td>via</td><td>string</td><td>Gateway IP address</td><td>no</td></tr>
+<tr><td>flags</td><td>integer</td><td>Route flags</td><td>no</td></tr>
+<tr><td>metric</td><td>integer</td><td>Route metric (not currently used)</td><td>no</td></tr>
 </table>
 
 #### /peer
@@ -99,8 +114,6 @@ Getting /peer returns an array of peer objects for all current peers. See below
 <table>
 <tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr>
 <tr><td>address</td><td>string</td><td>10-digit hex ZeroTier address</td><td>no</td></tr>
-<tr><td>lastUnicastFrame</td><td>integer</td><td>Time of last unicast frame in ms since epoch</td><td>no</td></tr>
-<tr><td>lastMulticastFrame</td><td>integer</td><td>Time of last multicast frame in ms since epoch</td><td>no</td></tr>
 <tr><td>versionMajor</td><td>integer</td><td>Major version of remote if known</td><td>no</td></tr>
 <tr><td>versionMinor</td><td>integer</td><td>Minor version of remote if known</td><td>no</td></tr>
 <tr><td>versionRev</td><td>integer</td><td>Revision of remote if known</td><td>no</td></tr>

+ 233 - 0
windows/WinUI/MainWindow.xaml.cs

@@ -0,0 +1,233 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Timers;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using System.Windows.Threading;
+
+namespace WinUI
+{
+    /// <summary>
+    /// Interaction logic for MainWindow.xaml
+    /// </summary>
+    public partial class MainWindow : Window
+    {
+        APIHandler handler;
+        Regex charRegex = new Regex("[0-9a-fxA-FX]");
+        Regex wholeStringRegex = new Regex("^[0-9a-fxA-FX]+$");
+
+        Timer timer = new Timer();
+
+        bool connected = false;
+
+        public MainWindow()
+        {
+            InitializeComponent();
+
+            if (InitAPIHandler())
+            {
+                networksPage.SetAPIHandler(handler);
+
+                updateStatus();
+                if (!connected)
+                {
+                    MessageBox.Show("Unable to connect to ZeroTier Service.");
+                }
+
+                updateNetworks();
+                //updatePeers();
+
+                DataObject.AddPastingHandler(joinNetworkID, OnPaste);
+
+                timer.Elapsed += new ElapsedEventHandler(OnUpdateTimer);
+                timer.Interval = 2000;
+                timer.Enabled = true;
+            }
+        }
+
+
+        private String readAuthToken(String path)
+        {
+            String authToken = "";
+
+            if (File.Exists(path))
+            {
+                try
+                {
+                    byte[] tmp = File.ReadAllBytes(path);
+                    authToken = System.Text.Encoding.UTF8.GetString(tmp).Trim();
+                }
+                catch
+                {
+                    MessageBox.Show("Unable to read ZeroTier One Auth Token from:\r\n" + path, "ZeroTier One");
+                }
+            }
+
+            return authToken;
+        }
+
+        private Int32 readPort(String path)
+        {
+            Int32 port = 9993;
+
+            try
+            {
+                byte[] tmp = File.ReadAllBytes(path);
+                port = Int32.Parse(System.Text.Encoding.ASCII.GetString(tmp).Trim());
+                if ((port <= 0) || (port > 65535))
+                    port = 9993;
+            }
+            catch
+            {
+            }
+
+            return port;
+        }
+
+        private bool InitAPIHandler()
+        {
+            String localZtDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\ZeroTier\\One";
+            String globalZtDir = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\ZeroTier\\One";
+
+            String authToken = "";
+            Int32 port = 9993;
+
+            if (!File.Exists(localZtDir + "\\authtoken.secret") || !File.Exists(localZtDir + "\\zerotier-one.port"))
+            {
+                // launch external process to copy file into place
+                String curPath = System.Reflection.Assembly.GetEntryAssembly().Location;
+                int index = curPath.LastIndexOf("\\");
+                curPath = curPath.Substring(0, index);
+                ProcessStartInfo startInfo = new ProcessStartInfo(curPath + "\\copyutil.exe", globalZtDir + " " + localZtDir);
+                startInfo.Verb = "runas";
+
+
+                var process = Process.Start(startInfo);
+                process.WaitForExit();
+            }
+
+            authToken = readAuthToken(localZtDir + "\\authtoken.secret");
+
+            if ((authToken == null) || (authToken.Length <= 0))
+            {
+                MessageBox.Show("Unable to read ZeroTier One authtoken", "ZeroTier One");
+                this.Close();
+                return false;
+            }
+
+            port = readPort(localZtDir + "\\zerotier-one.port");
+            handler = new APIHandler(port, authToken);
+            return true;
+        }
+
+        private void updateStatus()
+        {
+            var status = handler.GetStatus();
+
+            if (status != null)
+            {
+                connected = true;
+
+                networkId.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
+                {
+                    this.networkId.Text = status.Address;
+                }));
+                versionString.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
+                {
+                    this.versionString.Content = status.Version;
+                }));
+                onlineStatus.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
+                {
+                    this.onlineStatus.Content = (status.Online ? "ONLINE" : "OFFLINE");
+                }));
+            }
+            else
+            {
+                connected = false;
+
+                networkId.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
+                {
+                    this.networkId.Text = "";
+                }));
+                versionString.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
+                {
+                    this.versionString.Content = "0";
+                }));
+                onlineStatus.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
+                {
+                    this.onlineStatus.Content = "OFFLINE";
+                }));
+            }
+        }
+
+        private void updateNetworks()
+        {
+            var networks = handler.GetNetworks();
+
+            networksPage.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
+            {
+                networksPage.setNetworks(networks);
+            }));
+        }
+
+        private void updatePeers()
+        {
+            //var peers = handler.GetPeers();
+
+            //peersPage.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
+            //{
+            //    peersPage.SetPeers(peers);
+            //}));
+        }
+
+        private void OnUpdateTimer(object source, ElapsedEventArgs e)
+        {
+            updateStatus();
+            updateNetworks();
+            //updatePeers();
+        }
+
+        private void joinButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (joinNetworkID.Text.Length < 16)
+            {
+                MessageBox.Show("Invalid Network ID");
+            }
+            else
+            {
+                handler.JoinNetwork(joinNetworkID.Text);
+            }
+        }
+
+        private void OnNetworkEntered(object sender, TextCompositionEventArgs e)
+        {
+            e.Handled = !charRegex.IsMatch(e.Text);
+        }
+
+        private void OnPaste(object sender, DataObjectPastingEventArgs e)
+        {
+            var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
+            if (!isText) return;
+
+            var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string;
+
+            if (!wholeStringRegex.IsMatch(text))
+            {
+                e.CancelCommand();
+            }
+        }
+    }
+}

+ 1 - 1
windows/WinUI/NetworkInfoView.xaml

@@ -64,7 +64,7 @@
             <TextBlock x:Name="broadcastEnabled" FontFamily="Lucida Console" TextWrapping="Wrap" Text="ENABLED" HorizontalAlignment="Right"  Grid.Column="2" Grid.Row="6" Foreground="#FF000000"/>
             <TextBlock x:Name="bridgingEnabled" FontFamily="Lucida Console" TextWrapping="Wrap" Text="DISABLED" HorizontalAlignment="Right"  Grid.Column="2" Grid.Row="7" Background="#FFEEEEEE" Foreground="#FF000000"/>
             <TextBlock x:Name="deviceName" FontFamily="Lucida Console" TextWrapping="Wrap" HorizontalAlignment="Right"  Grid.Column="2" Grid.Row="8" Foreground="#FF000000"><Span><Run Text="ethernet_32771"/></Span></TextBlock>
-            <TextBlock x:Name="managedIps" TextWrapping="Wrap" FontFamily="Lucida Console" HorizontalAlignment="Right" TextAlignment="Right"  Grid.Column="2" Grid.Row="9" Foreground="#FF000000"><Span><Run Text="28.2.169.248/7 "/></Span><LineBreak/><Span><Run Text="fd80:56c2:e21c:0000:0199:9383:4a02:a9f8/88"/></Span></TextBlock>
+            <TextBox x:Name="managedIps" TextWrapping="Wrap" FontFamily="Lucida Console" HorizontalAlignment="Right" TextAlignment="Right"  Grid.Column="2" Grid.Row="9" Foreground="#FF000000" IsReadOnly="True" BorderThickness="0" Background="#FFEEEEEE" Text="28.2.169.248/7&#x0a;fd80:56c2:e21c:0000:0199:9383:4a02:a9f8/88"/>
             <CheckBox x:Name="allowGlobal" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="10" />
             <CheckBox x:Name="allowManaged" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="11" />
             <CheckBox x:Name="allowDefault" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="12" />

+ 39 - 17
windows/WinUI/NetworkInfoView.xaml.cs

@@ -20,7 +20,7 @@ namespace WinUI
     /// </summary>
     public partial class NetworkInfoView : UserControl
     {
-        private ZeroTierNetwork network;
+        public ZeroTierNetwork network;
 
         public NetworkInfoView(ZeroTierNetwork network)
         {
@@ -29,19 +29,41 @@ namespace WinUI
             this.network = network;
 
             UpdateNetworkData();
+
+            allowDefault.Checked += AllowDefault_CheckStateChanged;
+            allowDefault.Unchecked += AllowDefault_CheckStateChanged;
+            allowGlobal.Checked += AllowGlobal_CheckStateChanged;
+            allowGlobal.Unchecked += AllowGlobal_CheckStateChanged;
+            allowManaged.Checked += AllowManaged_CheckStateChanged;
+            allowManaged.Unchecked += AllowManaged_CheckStateChanged;
         }
 
         private void UpdateNetworkData()
         {
-            this.networkId.Text = network.NetworkId;
-            this.networkName.Text = network.NetworkName;
-            this.networkStatus.Text = network.NetworkStatus;
-            this.networkType.Text = network.NetworkType;
-            this.macAddress.Text = network.MacAddress;
-            this.mtu.Text = network.MTU.ToString();
+
+            if (this.networkId.Text != network.NetworkId)
+                this.networkId.Text = network.NetworkId;
+
+            if (this.networkName.Text != network.NetworkName)
+                this.networkName.Text = network.NetworkName;
+
+            if (this.networkStatus.Text != network.NetworkStatus)
+                this.networkStatus.Text = network.NetworkStatus;
+
+            if (this.networkType.Text != network.NetworkType)
+                this.networkType.Text = network.NetworkType;
+
+            if (this.macAddress.Text != network.MacAddress)
+                this.macAddress.Text = network.MacAddress;
+
+            if (this.mtu.Text != network.MTU.ToString())
+                this.mtu.Text = network.MTU.ToString();
+
             this.broadcastEnabled.Text = (network.BroadcastEnabled ? "ENABLED" : "DISABLED");
             this.bridgingEnabled.Text = (network.Bridge ? "ENABLED" : "DISABLED");
-            this.deviceName.Text = network.DeviceName;
+
+            if (this.deviceName.Text != network.DeviceName)
+                this.deviceName.Text = network.DeviceName;
 
             string iplist = "";
             for (int i = 0; i < network.AssignedAddresses.Length; ++i)
@@ -51,19 +73,12 @@ namespace WinUI
                     iplist += "\n";
             }
 
-            this.managedIps.Text = iplist;
+            if (this.managedIps.Text != iplist)
+                this.managedIps.Text = iplist;
 
             this.allowDefault.IsChecked = network.AllowDefault;
             this.allowGlobal.IsChecked = network.AllowGlobal;
             this.allowManaged.IsChecked = network.AllowManaged;
-
-            allowDefault.Checked += AllowDefault_CheckStateChanged;
-            allowDefault.Unchecked += AllowDefault_CheckStateChanged;
-            allowGlobal.Checked += AllowGlobal_CheckStateChanged;
-            allowGlobal.Unchecked += AllowGlobal_CheckStateChanged;
-            allowManaged.Checked += AllowManaged_CheckStateChanged;
-            allowManaged.Unchecked += AllowManaged_CheckStateChanged;
-
         }
 
         public bool HasNetwork(ZeroTierNetwork network)
@@ -74,6 +89,13 @@ namespace WinUI
             return false;
         }
 
+        public void SetNetworkInfo(ZeroTierNetwork network)
+        {
+            this.network = network;
+
+            UpdateNetworkData();
+        }
+
         private void leaveButton_Click(object sender, RoutedEventArgs e)
         {
             APIHandler.Instance.LeaveNetwork(network.NetworkId);

+ 3 - 1
windows/WinUI/NetworkListView.xaml

@@ -93,7 +93,9 @@
                     </Grid>
                 </ItemsPanelTemplate>
             </StatusBar.ItemsPanel>
-			<StatusBarItem Grid.Column="0" x:Name="networkId" Content="deadbeef00" Foreground="White" FontFamily="Lucida Console"/>
+            <StatusBarItem Grid.Column="0" x:Name="networkId_placeholder">
+                <TextBox x:Name="networkId" Text="deadbeef00" HorizontalAlignment="Left" Grid.Column="0" Foreground="White" FontFamily="Lucida Console" BorderThickness="0" IsReadOnly="true" Background="Transparent"/>
+            </StatusBarItem>
             <StatusBarItem Grid.Column="1" x:Name="onlineStatus" Content="ONLINE" Foreground="White" FontFamily="Lucida Console"/>
             <StatusBarItem Grid.Column="2" x:Name="versionString" Content="1.0.5" Foreground="White" FontFamily="Lucida Console"/>
 			<StatusBarItem Grid.Column="3" x:Name="blank" Content="" Height="43" Foreground="White"/>

+ 2 - 2
windows/WinUI/NetworkListView.xaml.cs

@@ -63,7 +63,7 @@ namespace WinUI
 
                 networkId.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                 {
-                    this.networkId.Content = status.Address;
+                    this.networkId.Text = status.Address;
                 }));
                 versionString.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                 {
@@ -80,7 +80,7 @@ namespace WinUI
 
                 networkId.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                 {
-                    this.networkId.Content = "";
+                    this.networkId.Text = "";
                 }));
                 versionString.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                 {

+ 60 - 5
windows/WinUI/NetworksPage.xaml.cs

@@ -27,18 +27,73 @@ namespace WinUI
 
         public void setNetworks(List<ZeroTierNetwork> networks)
         {
-            this.wrapPanel.Children.Clear();
             if (networks == null)
             {
+                this.wrapPanel.Children.Clear();
                 return;
             }
 
-            for (int i = 0; i < networks.Count; ++i)
+            foreach (ZeroTierNetwork network in networks)
             {
-                this.wrapPanel.Children.Add(
-                    new NetworkInfoView(
-                        networks.ElementAt<ZeroTierNetwork>(i)));
+                NetworkInfoView view = ChildWithNetwork(network);
+                if (view != null)
+                {
+                    view.SetNetworkInfo(network);
+                }
+                else
+                {
+                    wrapPanel.Children.Add(
+                        new NetworkInfoView(
+                            network));
+                }
             }
+
+            // remove networks we're no longer joined to.
+            List<ZeroTierNetwork> tmpList = GetNetworksFromChildren();
+            foreach (ZeroTierNetwork n in networks)
+            {
+                if (tmpList.Contains(n))
+                {
+                    tmpList.Remove(n);
+                }
+            }
+
+            foreach (ZeroTierNetwork n in tmpList)
+            {
+                NetworkInfoView view = ChildWithNetwork(n);
+                if (view != null)
+                {
+                    wrapPanel.Children.Remove(view);
+                }
+            }
+        }
+
+        private NetworkInfoView ChildWithNetwork(ZeroTierNetwork network)
+        {
+            List<NetworkInfoView> list = wrapPanel.Children.OfType<NetworkInfoView>().ToList();
+           
+            foreach (NetworkInfoView view in list)
+            {
+                if (view.HasNetwork(network))
+                {
+                    return view;
+                }
+            }
+
+            return null;
+        }
+
+        private List<ZeroTierNetwork> GetNetworksFromChildren()
+        {
+            List<ZeroTierNetwork> networks = new List<ZeroTierNetwork>(wrapPanel.Children.Count);
+
+            List<NetworkInfoView> list = wrapPanel.Children.OfType<NetworkInfoView>().ToList();
+            foreach (NetworkInfoView n in list)
+            {
+                networks.Add(n.network);
+            }
+
+            return networks;
         }
     }
 }

+ 2 - 1
windows/ZeroTierOne/ZeroTierOne.vcxproj

@@ -20,6 +20,7 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\controller\EmbeddedNetworkController.cpp" />
+    <ClCompile Include="..\..\controller\JSONDB.cpp" />
     <ClCompile Include="..\..\ext\http-parser\http_parser.c" />
     <ClCompile Include="..\..\ext\libnatpmp\getgateway.c" />
     <ClCompile Include="..\..\ext\libnatpmp\natpmp.c" />
@@ -269,7 +270,7 @@
       <SDLCheck>true</SDLCheck>
       <AdditionalIncludeDirectories>
       </AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_RULES_ENGINE_DEBUGGING;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <MultiProcessorCompilation>false</MultiProcessorCompilation>
       <DisableSpecificWarnings>4996</DisableSpecificWarnings>
     </ClCompile>

+ 3 - 0
windows/ZeroTierOne/ZeroTierOne.vcxproj.filters

@@ -261,6 +261,9 @@
     <ClCompile Include="..\..\controller\EmbeddedNetworkController.cpp">
       <Filter>Source Files\controller</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\controller\JSONDB.cpp">
+      <Filter>Source Files\controller</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="resource.h">

Some files were not shown because too many files changed in this diff