Browse Source

Merge branch 'adamierymenko-dev' into android-jni

Grant Limberg 10 years ago
parent
commit
c59c74ddda

+ 144 - 14
controller/SqliteNetworkController.cpp

@@ -167,8 +167,8 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 			||(sqlite3_prepare_v2(_db,"SELECT n.id FROM Member AS m,Node AS n WHERE m.networkId = ? AND n.id = m.nodeId ORDER BY n.id ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,n.identity,n.lastAt,n.lastSeen,n.firstSeen FROM Member AS m,Node AS n WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT ipNetwork,ipNetmaskBits,ipVersion FROM IpAssignmentPool WHERE networkId = ? ORDER BY ipNetwork ASC",-1,&_sGetIpAssignmentPools2,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"SELECT ruleId,nodeId,vlanId,vlanPcp,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"flags\",invFlags,\"action\" FROM Rule WHERE networkId = ? ORDER BY ruleId ASC",-1,&_sListRules,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"INSERT INTO Rule (networkId,ruleId,nodeId,vlanId,vlanPcP,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"action\") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",-1,&_sCreateRule,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"SELECT ruleNo,nodeId,vlanId,vlanPcp,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"flags\",invFlags,\"action\" FROM Rule WHERE networkId = ? ORDER BY ruleNo ASC",-1,&_sListRules,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"INSERT INTO Rule (networkId,ruleNo,nodeId,vlanId,vlanPcP,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"action\") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",-1,&_sCreateRule,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"INSERT INTO Network (id,name,creationTime,revision) VALUES (?,?,?,1)",-1,&_sCreateNetwork,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT revision FROM Network WHERE id = ?",-1,&_sGetNetworkRevision,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"UPDATE Network SET revision = ? WHERE id = ?",-1,&_sSetNetworkRevision,(const char **)0) != SQLITE_OK)
@@ -179,7 +179,10 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 			||(sqlite3_prepare_v2(_db,"DELETE FROM Rule WHERE networkId = ?",-1,&_sDeleteRulesForNetwork,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignmentPool (networkId,ipNetwork,ipNetmaskBits,ipVersion) VALUES (?,?,?,?)",-1,&_sCreateIpAssignmentPool,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ?; DELETE FROM IpAssignmentPool WHERE networkId = ?; DELETE FROM Member WHERE networkId = ?; DELETE FROM MulticastRate WHERE networkId = ?; DELETE FROM Relay WHERE networkId = ?; DELETE FROM Rule WHERE networkId = ?; DELETE FROM Network WHERE id = ?;",-1,&_sDeleteNetworkAndRelated,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"DELETE FROM Network WHERE id = ?",-1,&_sDeleteNetwork,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"SELECT ip,ipVersion,metric FROM Gateway WHERE networkId = ? ORDER BY metric ASC",-1,&_sGetGateways,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"DELETE FROM Gateway WHERE networkId = ?",-1,&_sDeleteGateways,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"INSERT INTO Gateway (networkId,ip,ipVersion,metric) VALUES (?,?,?,?)",-1,&_sCreateGateway,(const char **)0) != SQLITE_OK)
 		 ) {
 		//printf("!!! %s\n",sqlite3_errmsg(_db));
 		sqlite3_close(_db);
@@ -222,7 +225,10 @@ SqliteNetworkController::~SqliteNetworkController()
 		sqlite3_finalize(_sDeleteIpAssignmentPoolsForNetwork);
 		sqlite3_finalize(_sDeleteRulesForNetwork);
 		sqlite3_finalize(_sCreateIpAssignmentPool);
-		sqlite3_finalize(_sDeleteNetworkAndRelated);
+		sqlite3_finalize(_sDeleteNetwork);
+		sqlite3_finalize(_sGetGateways);
+		sqlite3_finalize(_sDeleteGateways);
+		sqlite3_finalize(_sCreateGateway);
 		sqlite3_close(_db);
 	}
 }
@@ -455,6 +461,52 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 				netconf[ZT_NETWORKCONFIG_DICT_KEY_RELAYS] = relays;
 		}
 
+		{
+			char tmp[128];
+			std::string gateways;
+			sqlite3_reset(_sGetGateways);
+			sqlite3_bind_text(_sGetGateways,1,network.id,16,SQLITE_STATIC);
+			while (sqlite3_step(_sGetGateways) == SQLITE_ROW) {
+				const unsigned char *ip = (const unsigned char *)sqlite3_column_blob(_sGetGateways,0);
+				switch(sqlite3_column_int(_sGetGateways,1)) { // ipVersion
+					case 4:
+						Utils::snprintf(tmp,sizeof(tmp),"%s%d.%d.%d.%d/%d",
+							(gateways.length() > 0) ? "," : "",
+							(int)ip[0],
+							(int)ip[1],
+							(int)ip[2],
+							(int)ip[3],
+							(int)sqlite3_column_int(_sGetGateways,2)); // metric
+						gateways.append(tmp);
+						break;
+					case 6:
+						Utils::snprintf(tmp,sizeof(tmp),"%s%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d",
+							(gateways.length() > 0) ? "," : "",
+							(int)ip[0],
+							(int)ip[1],
+							(int)ip[2],
+							(int)ip[3],
+							(int)ip[4],
+							(int)ip[5],
+							(int)ip[6],
+							(int)ip[7],
+							(int)ip[8],
+							(int)ip[9],
+							(int)ip[10],
+							(int)ip[11],
+							(int)ip[12],
+							(int)ip[13],
+							(int)ip[14],
+							(int)ip[15],
+							(int)sqlite3_column_int(_sGetGateways,2)); // metric
+						gateways.append(tmp);
+						break;
+				}
+			}
+			if (gateways.length())
+				netconf[ZT_NETWORKCONFIG_DICT_KEY_GATEWAYS] = gateways;
+		}
+
 		if ((network.v4AssignMode)&&(!strcmp(network.v4AssignMode,"zt"))) {
 			std::string v4s;
 
@@ -808,6 +860,31 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 										sqlite3_step(_sCreateRelay);
 									}
 								}
+							} else if (!strcmp(j->u.object.values[k].name,"gateways")) {
+								sqlite3_reset(_sDeleteGateways);
+								sqlite3_bind_text(_sDeleteGateways,1,nwids,16,SQLITE_STATIC);
+								sqlite3_step(_sDeleteGateways);
+								if (j->u.object.values[k].value->type == json_array) {
+									for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
+										json_value *gateway = j->u.object.values[k].value->u.array.values[kk];
+										if ((gateway)&&(gateway->type == json_string)) {
+											InetAddress gwip(gateway->u.string.ptr);
+											int ipVersion = 0;
+											if (gwip.ss_family == AF_INET)
+												ipVersion = 4;
+											else if (gwip.ss_family == AF_INET6)
+												ipVersion = 6;
+											if (ipVersion) {
+												sqlite3_reset(_sCreateGateway);
+												sqlite3_bind_text(_sCreateGateway,1,nwids,16,SQLITE_STATIC);
+												sqlite3_bind_blob(_sCreateGateway,2,gwip.rawIpData(),(gwip.ss_family == AF_INET6) ? 16 : 4,SQLITE_STATIC);
+												sqlite3_bind_int(_sCreateGateway,3,ipVersion);
+												sqlite3_bind_int(_sCreateGateway,4,(int)gwip.metric());
+												sqlite3_step(_sCreateGateway);
+											}
+										}
+									}
+								}
 							} else if (!strcmp(j->u.object.values[k].name,"ipAssignmentPools")) {
 								if (j->u.object.values[k].value->type == json_array) {
 									std::set<InetAddress> pools;
@@ -855,7 +932,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 										json_value *rj = j->u.object.values[k].value->u.array.values[kk];
 										if ((rj)&&(rj->type == json_object)) {
 											struct { // NULL pointers indicate missing or NULL -- wildcards
-												const json_int_t *ruleId;
+												const json_int_t *ruleNo;
 												const char *nodeId;
 												const json_int_t *vlanId;
 												const json_int_t *vlanPcp;
@@ -875,8 +952,8 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 											memset(&rule,0,sizeof(rule));
 
 											for(unsigned int rk=0;rk<rj->u.object.length;++rk) {
-												if ((!strcmp(rj->u.object.values[rk].name,"ruleId"))&&(rj->u.object.values[rk].value->type == json_integer))
-													rule.ruleId = &(rj->u.object.values[rk].value->u.integer);
+												if ((!strcmp(rj->u.object.values[rk].name,"ruleNo"))&&(rj->u.object.values[rk].value->type == json_integer))
+													rule.ruleNo = &(rj->u.object.values[rk].value->u.integer);
 												else if ((!strcmp(rj->u.object.values[rk].name,"nodeId"))&&(rj->u.object.values[rk].value->type == json_string))
 													rule.nodeId = rj->u.object.values[rk].value->u.string.ptr;
 												else if ((!strcmp(rj->u.object.values[rk].name,"vlanId"))&&(rj->u.object.values[rk].value->type == json_integer))
@@ -909,11 +986,11 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
 													rule.action = rj->u.object.values[rk].value->u.string.ptr;
 											}
 
-											if ((rule.ruleId)&&(rule.action)&&(rule.action[0])) {
+											if ((rule.ruleNo)&&(rule.action)&&(rule.action[0])) {
 												char mactmp1[16],mactmp2[16];
 												sqlite3_reset(_sCreateRule);
 												sqlite3_bind_text(_sCreateRule,1,nwids,16,SQLITE_STATIC);
-												sqlite3_bind_int64(_sCreateRule,2,*rule.ruleId);
+												sqlite3_bind_int64(_sCreateRule,2,*rule.ruleNo);
 
 												// Optional values: null by default
 												for(int i=3;i<=16;++i)
@@ -993,6 +1070,11 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
 			char nwids[24];
 			Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
 
+			sqlite3_reset(_sGetNetworkById);
+			sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC);
+			if (sqlite3_step(_sGetNetworkById) != SQLITE_ROW)
+				return 404;
+
 			if (path.size() >= 3) {
 
 				if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
@@ -1000,6 +1082,12 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
 					char addrs[24];
 					Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
 
+					sqlite3_reset(_sGetMember);
+					sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC);
+					sqlite3_bind_text(_sGetMember,2,addrs,10,SQLITE_STATIC);
+					if (sqlite3_step(_sGetMember) != SQLITE_ROW)
+						return 404;
+
 					sqlite3_reset(_sDeleteIpAllocations);
 					sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC);
 					sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC);
@@ -1016,10 +1104,9 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
 
 			} else {
 
-				sqlite3_reset(_sDeleteNetworkAndRelated);
-				for(int i=1;i<=7;++i)
-					sqlite3_bind_text(_sDeleteNetworkAndRelated,i,nwids,16,SQLITE_STATIC);
-				return ((sqlite3_step(_sDeleteNetworkAndRelated) == SQLITE_DONE) ? 200 : 500);
+				sqlite3_reset(_sDeleteNetwork);
+				sqlite3_bind_text(_sDeleteNetwork,1,nwids,16,SQLITE_STATIC);
+				return ((sqlite3_step(_sDeleteNetwork) == SQLITE_DONE) ? 200 : 500);
 
 			}
 		} // else 404
@@ -1202,6 +1289,49 @@ unsigned int SqliteNetworkController::_doCPGet(
 						responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1)));
 						responseBody.append("\"}");
 					}
+					responseBody.append("],\n\t\"gateways\": [");
+
+					sqlite3_reset(_sGetGateways);
+					sqlite3_bind_text(_sGetGateways,1,nwids,16,SQLITE_STATIC);
+					bool firstGateway = true;
+					while (sqlite3_step(_sGetGateways) == SQLITE_ROW) {
+						char tmp[128];
+						const unsigned char *ip = (const unsigned char *)sqlite3_column_blob(_sGetGateways,0);
+						switch(sqlite3_column_int(_sGetGateways,1)) { // ipVersion
+							case 4:
+								Utils::snprintf(tmp,sizeof(tmp),"%s%d.%d.%d.%d/%d\"",
+									(firstGateway) ? "\"" : ",\"",
+									(int)ip[0],
+									(int)ip[1],
+									(int)ip[2],
+									(int)ip[3],
+									(int)sqlite3_column_int(_sGetGateways,2)); // metric
+								break;
+							case 6:
+								Utils::snprintf(tmp,sizeof(tmp),"%s%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d\"",
+									(firstGateway) ? "\"" : ",\"",
+									(int)ip[0],
+									(int)ip[1],
+									(int)ip[2],
+									(int)ip[3],
+									(int)ip[4],
+									(int)ip[5],
+									(int)ip[6],
+									(int)ip[7],
+									(int)ip[8],
+									(int)ip[9],
+									(int)ip[10],
+									(int)ip[11],
+									(int)ip[12],
+									(int)ip[13],
+									(int)ip[14],
+									(int)ip[15],
+									(int)sqlite3_column_int(_sGetGateways,2)); // metric
+								break;
+						}
+						responseBody.append(tmp);
+						firstGateway = false;
+					}
 					responseBody.append("],\n\t\"ipAssignmentPools\": [");
 
 					sqlite3_reset(_sGetIpAssignmentPools2);
@@ -1223,7 +1353,7 @@ unsigned int SqliteNetworkController::_doCPGet(
 					bool firstRule = true;
 					while (sqlite3_step(_sListRules) == SQLITE_ROW) {
 						responseBody.append(firstRule ? "\n\t{\n" : ",{\n");
-						Utils::snprintf(json,sizeof(json),"\t\t\"ruleId\": %lld,\n",sqlite3_column_int64(_sListRules,0));
+						Utils::snprintf(json,sizeof(json),"\t\t\"ruleNo\": %lld,\n",sqlite3_column_int64(_sListRules,0));
 						responseBody.append(json);
 						if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) {
 							Utils::snprintf(json,sizeof(json),"\t\t\"nodeId\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1));

+ 4 - 1
controller/SqliteNetworkController.hpp

@@ -123,7 +123,10 @@ private:
 	sqlite3_stmt *_sDeleteRulesForNetwork;
 	sqlite3_stmt *_sCreateIpAssignmentPool;
 	sqlite3_stmt *_sDeleteMember;
-	sqlite3_stmt *_sDeleteNetworkAndRelated;
+	sqlite3_stmt *_sDeleteNetwork;
+	sqlite3_stmt *_sGetGateways;
+	sqlite3_stmt *_sDeleteGateways;
+	sqlite3_stmt *_sCreateGateway;
 
 	Mutex _lock;
 };

+ 47 - 44
controller/schema.sql

@@ -3,22 +3,50 @@ CREATE TABLE Config (
   v varchar(1024) NOT NULL
 );
 
+CREATE TABLE Network (
+  id char(16) PRIMARY KEY NOT NULL,
+  name varchar(128) NOT NULL,
+  private integer NOT NULL DEFAULT(1),
+  enableBroadcast integer NOT NULL DEFAULT(1),
+  allowPassiveBridging integer NOT NULL DEFAULT(0),
+  v4AssignMode varchar(8) NOT NULL DEFAULT('none'),
+  v6AssignMode varchar(8) NOT NULL DEFAULT('none'),
+  multicastLimit integer NOT NULL DEFAULT(32),
+  creationTime integer NOT NULL DEFAULT(0),
+  revision integer NOT NULL DEFAULT(1)
+);
+
+CREATE TABLE Node (
+  id char(10) PRIMARY KEY NOT NULL,
+  identity varchar(4096) NOT NULL,
+  lastAt varchar(64),
+  lastSeen integer NOT NULL DEFAULT(0),
+  firstSeen integer NOT NULL DEFAULT(0)
+);
+
+CREATE TABLE Gateway (
+  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
+  ip blob(16) NOT NULL,
+  ipVersion integer NOT NULL DEFAULT(4),
+  metric integer NOT NULL DEFAULT(0)
+);
+
+CREATE UNIQUE INDEX Gateway_networkId_ip ON Gateway (networkId, ip);
+
 CREATE TABLE IpAssignment (
-  networkId char(16) NOT NULL,
-  nodeId char(10) NOT NULL,
+  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
+  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,
   ip blob(16) NOT NULL,
   ipNetmaskBits integer NOT NULL DEFAULT(0),
   ipVersion integer NOT NULL DEFAULT(4)
 );
 
-CREATE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);
+CREATE UNIQUE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);
 
 CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId);
 
-CREATE INDEX IpAssignment_networkId ON IpAssignment (networkId);
-
 CREATE TABLE IpAssignmentPool (
-  networkId char(16) NOT NULL,
+  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
   ipNetwork blob(16) NOT NULL,
   ipNetmaskBits integer NOT NULL,
   ipVersion integer NOT NULL DEFAULT(4)
@@ -27,20 +55,17 @@ CREATE TABLE IpAssignmentPool (
 CREATE INDEX IpAssignmentPool_networkId ON IpAssignmentPool (networkId);
 
 CREATE TABLE Member (
-  networkId char(16) NOT NULL,
-  nodeId char(10) NOT NULL,
+  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
+  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,
   authorized integer NOT NULL DEFAULT(0),
-  activeBridge integer NOT NULL DEFAULT(0)
+  activeBridge integer NOT NULL DEFAULT(0),
+  PRIMARY KEY (networkId, nodeId)
 );
 
-CREATE INDEX Member_networkId ON Member (networkId);
-
 CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);
 
-CREATE UNIQUE INDEX Member_networkId_nodeId ON Member (networkId, nodeId);
-
 CREATE TABLE MulticastRate (
-  networkId char(16) NOT NULL,
+  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
   mgMac char(12) NOT NULL,
   mgAdi integer NOT NULL DEFAULT(0),
   preload integer NOT NULL,
@@ -50,41 +75,19 @@ CREATE TABLE MulticastRate (
 
 CREATE INDEX MulticastRate_networkId ON MulticastRate (networkId);
 
-CREATE TABLE Network (
-  id char(16) PRIMARY KEY NOT NULL,
-  name varchar(128) NOT NULL,
-  private integer NOT NULL DEFAULT(1),
-  enableBroadcast integer NOT NULL DEFAULT(1),
-  allowPassiveBridging integer NOT NULL DEFAULT(0),
-  v4AssignMode varchar(8) NOT NULL DEFAULT('none'),
-  v6AssignMode varchar(8) NOT NULL DEFAULT('none'),
-  multicastLimit integer NOT NULL DEFAULT(32),
-  creationTime integer NOT NULL DEFAULT(0),
-  revision integer NOT NULL DEFAULT(1)
-);
-
 CREATE TABLE Relay (
-  networkId char(16) NOT NULL,
-  nodeId char(10) NOT NULL,
-  phyAddress varchar(64) NOT NULL
+  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
+  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,
+  phyAddress varchar(64) NOT NULL,
+  PRIMARY KEY (networkId, nodeId)
 );
 
 CREATE INDEX Relay_networkId ON Relay (networkId);
 
-CREATE UNIQUE INDEX Relay_networkId_nodeId ON Relay (networkId, nodeId);
-
-CREATE TABLE Node (
-  id char(10) PRIMARY KEY NOT NULL,
-  identity varchar(4096) NOT NULL,
-  lastAt varchar(64),
-  lastSeen integer NOT NULL DEFAULT(0),
-  firstSeen integer NOT NULL DEFAULT(0)
-);
-
 CREATE TABLE Rule (
-  networkId char(16) NOT NULL,
-  ruleId integer NOT NULL,
-  nodeId char(10),
+  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
+  ruleNo integer NOT NULL,
+  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,
   vlanId integer,
   vlanPcp integer,
   etherType integer,
@@ -101,4 +104,4 @@ CREATE TABLE Rule (
   "action" varchar(4096) NOT NULL DEFAULT('accept')
 );
 
-CREATE INDEX Rule_networkId ON Rule (networkId);
+CREATE UNIQUE INDEX Rule_networkId_ruleNo ON Rule (networkId, ruleNo);

+ 47 - 44
controller/schema.sql.c

@@ -4,22 +4,50 @@
 "  v varchar(1024) NOT NULL\n"\
 ");\n"\
 "\n"\
+"CREATE TABLE Network (\n"\
+"  id char(16) PRIMARY KEY NOT NULL,\n"\
+"  name varchar(128) NOT NULL,\n"\
+"  private integer NOT NULL DEFAULT(1),\n"\
+"  enableBroadcast integer NOT NULL DEFAULT(1),\n"\
+"  allowPassiveBridging integer NOT NULL DEFAULT(0),\n"\
+"  v4AssignMode varchar(8) NOT NULL DEFAULT('none'),\n"\
+"  v6AssignMode varchar(8) NOT NULL DEFAULT('none'),\n"\
+"  multicastLimit integer NOT NULL DEFAULT(32),\n"\
+"  creationTime integer NOT NULL DEFAULT(0),\n"\
+"  revision integer NOT NULL DEFAULT(1)\n"\
+");\n"\
+"\n"\
+"CREATE TABLE Node (\n"\
+"  id char(10) PRIMARY KEY NOT NULL,\n"\
+"  identity varchar(4096) NOT NULL,\n"\
+"  lastAt varchar(64),\n"\
+"  lastSeen integer NOT NULL DEFAULT(0),\n"\
+"  firstSeen integer NOT NULL DEFAULT(0)\n"\
+");\n"\
+"\n"\
+"CREATE TABLE Gateway (\n"\
+"  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
+"  ip blob(16) NOT NULL,\n"\
+"  ipVersion integer NOT NULL DEFAULT(4),\n"\
+"  metric integer NOT NULL DEFAULT(0)\n"\
+");\n"\
+"\n"\
+"CREATE UNIQUE INDEX Gateway_networkId_ip ON Gateway (networkId, ip);\n"\
+"\n"\
 "CREATE TABLE IpAssignment (\n"\
-"  networkId char(16) NOT NULL,\n"\
-"  nodeId char(10) NOT NULL,\n"\
+"  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
+"  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"\
 "  ip blob(16) NOT NULL,\n"\
 "  ipNetmaskBits integer NOT NULL DEFAULT(0),\n"\
 "  ipVersion integer NOT NULL DEFAULT(4)\n"\
 ");\n"\
 "\n"\
-"CREATE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);\n"\
+"CREATE UNIQUE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);\n"\
 "\n"\
 "CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId);\n"\
 "\n"\
-"CREATE INDEX IpAssignment_networkId ON IpAssignment (networkId);\n"\
-"\n"\
 "CREATE TABLE IpAssignmentPool (\n"\
-"  networkId char(16) NOT NULL,\n"\
+"  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
 "  ipNetwork blob(16) NOT NULL,\n"\
 "  ipNetmaskBits integer NOT NULL,\n"\
 "  ipVersion integer NOT NULL DEFAULT(4)\n"\
@@ -28,20 +56,17 @@
 "CREATE INDEX IpAssignmentPool_networkId ON IpAssignmentPool (networkId);\n"\
 "\n"\
 "CREATE TABLE Member (\n"\
-"  networkId char(16) NOT NULL,\n"\
-"  nodeId char(10) NOT NULL,\n"\
+"  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
+"  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"\
 "  authorized integer NOT NULL DEFAULT(0),\n"\
-"  activeBridge integer NOT NULL DEFAULT(0)\n"\
+"  activeBridge integer NOT NULL DEFAULT(0),\n"\
+"  PRIMARY KEY (networkId, nodeId)\n"\
 ");\n"\
 "\n"\
-"CREATE INDEX Member_networkId ON Member (networkId);\n"\
-"\n"\
 "CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);\n"\
 "\n"\
-"CREATE UNIQUE INDEX Member_networkId_nodeId ON Member (networkId, nodeId);\n"\
-"\n"\
 "CREATE TABLE MulticastRate (\n"\
-"  networkId char(16) NOT NULL,\n"\
+"  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
 "  mgMac char(12) NOT NULL,\n"\
 "  mgAdi integer NOT NULL DEFAULT(0),\n"\
 "  preload integer NOT NULL,\n"\
@@ -51,41 +76,19 @@
 "\n"\
 "CREATE INDEX MulticastRate_networkId ON MulticastRate (networkId);\n"\
 "\n"\
-"CREATE TABLE Network (\n"\
-"  id char(16) PRIMARY KEY NOT NULL,\n"\
-"  name varchar(128) NOT NULL,\n"\
-"  private integer NOT NULL DEFAULT(1),\n"\
-"  enableBroadcast integer NOT NULL DEFAULT(1),\n"\
-"  allowPassiveBridging integer NOT NULL DEFAULT(0),\n"\
-"  v4AssignMode varchar(8) NOT NULL DEFAULT('none'),\n"\
-"  v6AssignMode varchar(8) NOT NULL DEFAULT('none'),\n"\
-"  multicastLimit integer NOT NULL DEFAULT(32),\n"\
-"  creationTime integer NOT NULL DEFAULT(0),\n"\
-"  revision integer NOT NULL DEFAULT(1)\n"\
-");\n"\
-"\n"\
 "CREATE TABLE Relay (\n"\
-"  networkId char(16) NOT NULL,\n"\
-"  nodeId char(10) NOT NULL,\n"\
-"  phyAddress varchar(64) NOT NULL\n"\
+"  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
+"  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"\
+"  phyAddress varchar(64) NOT NULL,\n"\
+"  PRIMARY KEY (networkId, nodeId)\n"\
 ");\n"\
 "\n"\
 "CREATE INDEX Relay_networkId ON Relay (networkId);\n"\
 "\n"\
-"CREATE UNIQUE INDEX Relay_networkId_nodeId ON Relay (networkId, nodeId);\n"\
-"\n"\
-"CREATE TABLE Node (\n"\
-"  id char(10) PRIMARY KEY NOT NULL,\n"\
-"  identity varchar(4096) NOT NULL,\n"\
-"  lastAt varchar(64),\n"\
-"  lastSeen integer NOT NULL DEFAULT(0),\n"\
-"  firstSeen integer NOT NULL DEFAULT(0)\n"\
-");\n"\
-"\n"\
 "CREATE TABLE Rule (\n"\
-"  networkId char(16) NOT NULL,\n"\
-"  ruleId integer NOT NULL,\n"\
-"  nodeId char(10),\n"\
+"  networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
+"  ruleNo integer NOT NULL,\n"\
+"  nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"\
 "  vlanId integer,\n"\
 "  vlanPcp integer,\n"\
 "  etherType integer,\n"\
@@ -102,5 +105,5 @@
 "  \"action\" varchar(4096) NOT NULL DEFAULT('accept')\n"\
 ");\n"\
 "\n"\
-"CREATE INDEX Rule_networkId ON Rule (networkId);\n"\
+"CREATE UNIQUE INDEX Rule_networkId_ruleNo ON Rule (networkId, ruleNo);\n"\
 ""

+ 4 - 1
ext/installfiles/linux/buildinstaller.sh

@@ -49,9 +49,12 @@ case "$system" in
 
 		echo "Assembling Linux installer for $machine and version $vmajor.$vminor.$revision"
 
-		mkdir -p 'build-installer/var/lib/zerotier-one'
+		mkdir -p 'build-installer/var/lib/zerotier-one/ui'
 		cp -fp 'ext/installfiles/linux/uninstall.sh' 'build-installer/var/lib/zerotier-one'
 		cp -fp 'zerotier-one' 'build-installer/var/lib/zerotier-one'
+		for f in ui/*.html ui/*.js ui/*.css ui/*.jsx ; do
+			cp -fp "$f" 'build-installer/var/lib/zerotier-one/ui'
+		done
 		mkdir -p 'build-installer/tmp'
 		cp -fp 'ext/installfiles/linux/init.d/zerotier-one' 'build-installer/tmp/init.d_zerotier-one'
 		cp -fp 'ext/installfiles/linux/systemd/zerotier-one.service' 'build-installer/tmp/systemd_zerotier-one.service'

+ 10 - 0
node/InetAddress.hpp

@@ -265,6 +265,16 @@ struct InetAddress : public sockaddr_storage
 	 */
 	inline unsigned int netmaskBits() const throw() { return port(); }
 
+	/**
+	 * Alias for port()
+	 *
+	 * This just aliases port() because for gateways we use this field to
+	 * store the gateway metric.
+	 *
+	 * @return Gateway metric
+	 */
+	inline unsigned int metric() const throw() { return port(); }
+
 	/**
 	 * Construct a full netmask as an InetAddress
 	 */

+ 9 - 0
node/NetworkConfig.cpp

@@ -163,6 +163,13 @@ void NetworkConfig::_fromDictionary(const Dictionary &d)
 	std::sort(_staticIps.begin(),_staticIps.end());
 	std::unique(_staticIps.begin(),_staticIps.end());
 
+	std::vector<std::string> gatewaysSplit(Utils::split(d.get(ZT_NETWORKCONFIG_DICT_KEY_GATEWAYS,"").c_str(),",","",""));
+	for(std::vector<std::string>::const_iterator gwstr(gatewaysSplit.begin());gwstr!=gatewaysSplit.end();++gwstr) {
+		InetAddress gw(*gwstr);
+		if ((std::find(_gateways.begin(),_gateways.end(),gw) == _gateways.end())&&((gw.ss_family == AF_INET)||(gw.ss_family == AF_INET6)))
+			_gateways.push_back(gw);
+	}
+
 	std::vector<std::string> activeBridgesSplit(Utils::split(d.get(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES,"").c_str(),",","",""));
 	for(std::vector<std::string>::const_iterator a(activeBridgesSplit.begin());a!=activeBridgesSplit.end();++a) {
 		if (a->length() == ZT_ADDRESS_LENGTH_HEX) { // ignore empty or garbage fields
@@ -211,7 +218,9 @@ bool NetworkConfig::operator==(const NetworkConfig &nc) const
 	if (_name != nc._name) return false;
 	if (_description != nc._description) return false;
 	if (_staticIps != nc._staticIps) return false;
+	if (_gateways != nc._gateways) return false;
 	if (_activeBridges != nc._activeBridges) return false;
+	if (_relays != nc._relays) return false;
 	if (_multicastRates.size() == nc._multicastRates.size()) {
 		// uclibc++ doesn't seem to implement map<> != map<> correctly, so do
 		// it ourselves. Note that this depends on the maps being sorted.

+ 39 - 0
node/NetworkConfig.hpp

@@ -49,24 +49,61 @@ namespace ZeroTier {
 
 // These dictionary keys are short so they don't take up much room in
 // netconf response packets.
+
+// integer(hex)[,integer(hex),...]
 #define ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES "et"
+
+// network ID
 #define ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID "nwid"
+
+// integer(hex)
 #define ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP "ts"
+
+// integer(hex)
 #define ZT_NETWORKCONFIG_DICT_KEY_REVISION "r"
+
+// address of member
 #define ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO "id"
+
+// integer(hex)
 #define ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT "ml"
+
+// dictionary of one or more of: MAC/ADI=preload,maxbalance,accrual
 #define ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES "mr"
+
+// 0/1
 #define ZT_NETWORKCONFIG_DICT_KEY_PRIVATE "p"
+
+// text
 #define ZT_NETWORKCONFIG_DICT_KEY_NAME "n"
+
+// text
 #define ZT_NETWORKCONFIG_DICT_KEY_DESC "d"
+
+// IP/bits[,IP/bits,...]
 #define ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC "v4s"
+
+// IP/bits[,IP/bits,...]
 #define ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC "v6s"
+
+// serialized CertificateOfMembership
 #define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP "com"
+
+// 0/1
 #define ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST "eb"
+
+// 0/1
 #define ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING "pb"
+
+// node[,node,...]
 #define ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES "ab"
+
+// node;IP/port[,node;IP/port]
 #define ZT_NETWORKCONFIG_DICT_KEY_RELAYS "rl"
 
+// IP/metric[,IP/metric,...]
+#define ZT_NETWORKCONFIG_DICT_KEY_GATEWAYS "gw"
+
 /**
  * Network configuration received from network controller nodes
  *
@@ -146,6 +183,7 @@ public:
 	inline const std::string &name() const throw() { return _name; }
 	inline const std::string &description() const throw() { return _description; }
 	inline const std::vector<InetAddress> &staticIps() const throw() { return _staticIps; }
+	inline const std::vector<InetAddress> &gateways() const throw() { return _gateways; }
 	inline const std::vector<Address> &activeBridges() const throw() { return _activeBridges; }
 	inline const std::vector< std::pair<Address,InetAddress> > &relays() const throw() { return _relays; }
 	inline const CertificateOfMembership &com() const throw() { return _com; }
@@ -188,6 +226,7 @@ private:
 	std::string _name;
 	std::string _description;
 	std::vector<InetAddress> _staticIps;
+	std::vector<InetAddress> _gateways;
 	std::vector<Address> _activeBridges;
 	std::vector< std::pair<Address,InetAddress> > _relays;
 	std::map<MulticastGroup,MulticastRate> _multicastRates;

+ 1 - 0
node/Packet.cpp

@@ -51,6 +51,7 @@ const char *Packet::verbString(Verb v)
 		case VERB_MULTICAST_GATHER: return "MULTICAST_GATHER";
 		case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME";
 		case VERB_SET_EPHEMERAL_KEY: return "SET_EPHEMERAL_KEY";
+		case VERB_CMA: return "CMA";
 	}
 	return "(unknown)";
 }

+ 29 - 3
node/Packet.hpp

@@ -513,8 +513,8 @@ public:
 		 * Destination address types and formats (not all of these are used now):
 		 *   0 - None -- no destination address data present
 		 *   1 - Ethernet address -- format: <[6] Ethernet MAC>
-		 *   4 - 6-byte IPv4 address -- format: <[4] IP>, <[2] port>
-		 *   6 - 18-byte IPv6 address -- format: <[16] IP>, <[2] port>
+		 *   4 - 6-byte IPv4 UDP address/port -- format: <[4] IP>, <[2] port>
+		 *   6 - 18-byte IPv6 UDP address/port -- format: <[16] IP>, <[2] port>
 		 *
 		 * OK payload:
 		 *   <[8] timestamp (echoed from original HELLO)>
@@ -770,6 +770,9 @@ public:
 		VERB_MULTICAST_FRAME = 14,
 
 		/* Ephemeral (PFS) key push:
+		 *   <[2] flags (unused and reserved, must be 0)>
+		 *   <[2] length of padding / extra field section>
+		 *   <[...] padding / extra field section>
 		 *   <[8] 64-bit PFS key set ID sender holds for recipient (0==none)>
 		 *   <[8] 64-bit PFS key set ID of this key set>
 		 *   [... begin PFS key record ...]
@@ -791,6 +794,12 @@ public:
 		 * the first record with common symmetric cipher, public key type,
 		 * and relevant flags must be used.
 		 *
+		 * The padding section may be filled with an arbitrary amount of random
+		 * or empty payload. This may be used as a countermeasure to prevent PFS
+		 * key pushes from being recognized by packet size vs. other packets in
+		 * the stream. This also provides potential space for additional fields
+		 * that might be indicated in the future by flags.
+		 *
 		 * Flags (all unspecified flags must be zero):
 		 *   0x01 - FIPS mode, only use record if FIPS compliant crypto in use
 		 *
@@ -814,7 +823,24 @@ public:
 		 *   <[8] PFS key set ID of received key set>
 		 *   <[1] index in record list of chosen key record>
 		 */
-		VERB_SET_EPHEMERAL_KEY = 15
+		VERB_SET_EPHEMERAL_KEY = 15,
+
+		/* "Call me at" -- push of potential endpoints for direct communication:
+		 *   <[1] flags>
+		 *   <[2] number of addresses>
+		 *   <[...] address types and addresses>
+		 *
+		 * Address types and addresses are of the same format as the destination
+		 * address type and address in HELLO.
+		 *
+		 * The receiver may, upon receiving a CMA push, attempt to establish a
+		 * direct link to one or more of the indicated addresses. Senders should
+		 * only send CMA pushes to peers that they have some relationship
+		 * with such as a shared network membership or a mutual trust.
+		 *
+		 * OK/ERROR are not generated.
+		 */
+		VERB_CMA = 16
 	};
 
 	/**

+ 173 - 175
osdep/WindowsEthernetTap.cpp

@@ -92,9 +92,6 @@ static const WindowsEthernetTapEnv WINENV;
 // Only create or delete devices one at a time
 static Mutex _systemTapInitLock;
 
-// Incrementing this causes everyone currently open to close and reopen
-static volatile int _systemTapResetStatus = 0;
-
 } // anonymous namespace
 
 WindowsEthernetTap::WindowsEthernetTap(
@@ -268,12 +265,6 @@ WindowsEthernetTap::WindowsEthernetTap(
 				}
 			} else break; // no more keys or error occurred
 		}
-
-		// When we create a new tap device from scratch, existing taps for
-		// some reason go into 'unplugged' state. This can be fixed by
-		// closing and re-opening them. Incrementing this causes all
-		// existing tap threads to do this.
-		++_systemTapResetStatus;
 	}
 
 	if (_netCfgInstanceId.length() > 0) {
@@ -299,7 +290,6 @@ WindowsEthernetTap::WindowsEthernetTap(
 		throw std::runtime_error("unable to find or create tap adapter");
 	}
 
-	// Convert device GUID junk... blech... is there an easier way to do this?
 	{
 		char nobraces[128];
 		const char *nbtmp1 = _netCfgInstanceId.c_str();
@@ -573,191 +563,199 @@ void WindowsEthernetTap::scanMulticastGroups(std::vector<MulticastGroup> &added,
 void WindowsEthernetTap::threadMain()
 	throw()
 {
-	char tapPath[256];
-	OVERLAPPED tapOvlRead,tapOvlWrite;
+	char tapReadBuf[ZT_IF_MTU + 32];
+	char tapPath[128];
 	HANDLE wait4[3];
-	char *tapReadBuf = (char *)0;
-
-	/* No idea why I did this. I did it a long time ago and there was only a
-	 * a snarky comment. But I'd never do crap like this without a reason, so
-	 * I am leaving it alone with a more descriptive snarky comment. */
-	while (!tapReadBuf) {
-		tapReadBuf = (char *)::malloc(ZT_IF_MTU + 32);
-		if (!tapReadBuf)
-			Sleep(1000);
-	}
+	OVERLAPPED tapOvlRead,tapOvlWrite;
 
 	Utils::snprintf(tapPath,sizeof(tapPath),"\\\\.\\Global\\%s.tap",_netCfgInstanceId.c_str());
-	int prevTapResetStatus = _systemTapResetStatus;
-	bool throwOneAway = true; // Restart once on startup, because Windows.
-	bool powerCycle = true; // If true, "power cycle" the device, because Windows.
-	while (_run) {
-		if (powerCycle) {
-			_disableTapDevice();
-			Sleep(500);
+
+	try {
+		while (_run) {
 			_enableTapDevice();
 			Sleep(500);
-		}
 
-		_tap = CreateFileA(tapPath,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED,NULL);
-		if (_tap == INVALID_HANDLE_VALUE) {
-			fprintf(stderr,"Error opening %s -- retrying.\r\n",tapPath);
-			powerCycle = true;
-			continue;
-		}
+			_tap = CreateFileA(tapPath,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED,NULL);
+			if (_tap == INVALID_HANDLE_VALUE) {
+				_disableTapDevice();
+				_enableTapDevice();
+				Sleep(1000);
+				continue;
+			}
 
-		{
-			uint32_t tmpi = 1;
-			DWORD bytesReturned = 0;
-			DeviceIoControl(_tap,TAP_WIN_IOCTL_SET_MEDIA_STATUS,&tmpi,sizeof(tmpi),&tmpi,sizeof(tmpi),&bytesReturned,NULL);
-		}
+			{
+				uint32_t tmpi = 1;
+				DWORD bytesReturned = 0;
+				DeviceIoControl(_tap,TAP_WIN_IOCTL_SET_MEDIA_STATUS,&tmpi,sizeof(tmpi),&tmpi,sizeof(tmpi),&bytesReturned,NULL);
+			}
 
-		{
 #ifdef ZT_WINDOWS_CREATE_FAKE_DEFAULT_ROUTE
-			/* This inserts a fake default route and a fake ARP entry, forcing
-			 * Windows to detect this as a "real" network and apply proper
-			 * firewall rules.
-			 *
-			 * This hack is completely stupid, but Windows made me do it
-			 * by being broken and insane.
-			 *
-			 * Background: Windows tries to detect its network location by
-			 * matching it to the ARP address of the default route. Networks
-			 * without default routes are "unidentified networks" and cannot
-			 * have their firewall classification changed by the user (easily).
-			 *
-			 * Yes, you read that right.
-			 *
-			 * The common workaround is to set *NdisDeviceType to 1, which
-			 * totally disables all Windows firewall functionality. This is
-			 * the answer you'll find on most forums for things like OpenVPN.
-			 *
-			 * Yes, you read that right.
-			 *
-			 * The default route workaround is also known, but for this to
-			 * work there must be a known default IP that resolves to a known
-			 * ARP address. This works for an OpenVPN tunnel, but not here
-			 * because this isn't a tunnel. It's a mesh. There is no "other
-			 * end," or any other known always on IP.
-			 *
-			 * So let's make a fake one and shove it in there along with its
-			 * fake static ARP entry. Also makes it instant-on and static.
-			 *
-			 * We'll have to see what DHCP does with this. In the future we
-			 * probably will not want to do this on DHCP-enabled networks, so
-			 * when we enable DHCP we will go in and yank this wacko hacko from
-			 * the routing table before doing so.
-			 *
-			 * Like Jesse Pinkman would say: "YEEEEAAH BITCH!" */
-			const uint32_t fakeIp = htonl(0x19fffffe); // 25.255.255.254 -- unrouted IPv4 block
-			for(int i=0;i<8;++i) {
-				MIB_IPNET_ROW2 ipnr;
-				memset(&ipnr,0,sizeof(ipnr));
-				ipnr.Address.si_family = AF_INET;
-				ipnr.Address.Ipv4.sin_addr.s_addr = fakeIp;
-				ipnr.InterfaceLuid.Value = _deviceLuid.Value;
-				ipnr.PhysicalAddress[0] = _mac[0] ^ 0x10; // just make something up that's consistent and not part of this net
-				ipnr.PhysicalAddress[1] = 0x00;
-				ipnr.PhysicalAddress[2] = (UCHAR)((_deviceGuid.Data1 >> 24) & 0xff);
-				ipnr.PhysicalAddress[3] = (UCHAR)((_deviceGuid.Data1 >> 16) & 0xff);
-				ipnr.PhysicalAddress[4] = (UCHAR)((_deviceGuid.Data1 >> 8) & 0xff);
-				ipnr.PhysicalAddress[5] = (UCHAR)(_deviceGuid.Data1 & 0xff);
-				ipnr.PhysicalAddressLength = 6;
-				ipnr.State = NlnsPermanent;
-				ipnr.IsRouter = 1;
-				ipnr.IsUnreachable = 0;
-				ipnr.ReachabilityTime.LastReachable = 0x0fffffff;
-				ipnr.ReachabilityTime.LastUnreachable = 1;
-				DWORD result = CreateIpNetEntry2(&ipnr);
-				if (result != NO_ERROR)
-					Sleep(500);
-				else break;
-			}
-			for(int i=0;i<8;++i) {
-				MIB_IPFORWARD_ROW2 nr;
-				memset(&nr,0,sizeof(nr));
-				InitializeIpForwardEntry(&nr);
-				nr.InterfaceLuid.Value = _deviceLuid.Value;
-				nr.DestinationPrefix.Prefix.si_family = AF_INET; // rest is left as 0.0.0.0/0
-				nr.NextHop.si_family = AF_INET;
-				nr.NextHop.Ipv4.sin_addr.s_addr = fakeIp;
-				nr.Metric = 9999; // do not use as real default route
-				nr.Protocol = MIB_IPPROTO_NETMGMT;
-				DWORD result = CreateIpForwardEntry2(&nr);
-				if (result != NO_ERROR)
-					Sleep(500);
-				else break;
+			{
+				/* This inserts a fake default route and a fake ARP entry, forcing
+				 * Windows to detect this as a "real" network and apply proper
+				 * firewall rules.
+				 *
+				 * This hack is completely stupid, but Windows made me do it
+				 * by being broken and insane.
+				 *
+				 * Background: Windows tries to detect its network location by
+				 * matching it to the ARP address of the default route. Networks
+				 * without default routes are "unidentified networks" and cannot
+				 * have their firewall classification changed by the user (easily).
+				 *
+				 * Yes, you read that right.
+				 *
+				 * The common workaround is to set *NdisDeviceType to 1, which
+				 * totally disables all Windows firewall functionality. This is
+				 * the answer you'll find on most forums for things like OpenVPN.
+				 *
+				 * Yes, you read that right.
+				 *
+				 * The default route workaround is also known, but for this to
+				 * work there must be a known default IP that resolves to a known
+				 * ARP address. This works for an OpenVPN tunnel, but not here
+				 * because this isn't a tunnel. It's a mesh. There is no "other
+				 * end," or any other known always on IP.
+				 *
+				 * So let's make a fake one and shove it in there along with its
+				 * fake static ARP entry. Also makes it instant-on and static.
+				 *
+				 * We'll have to see what DHCP does with this. In the future we
+				 * probably will not want to do this on DHCP-enabled networks, so
+				 * when we enable DHCP we will go in and yank this wacko hacko from
+				 * the routing table before doing so.
+				 *
+				 * Like Jesse Pinkman would say: "YEEEEAAH BITCH!" */
+				const uint32_t fakeIp = htonl(0x19fffffe); // 25.255.255.254 -- unrouted IPv4 block
+				for(int i=0;i<8;++i) {
+					MIB_IPNET_ROW2 ipnr;
+					memset(&ipnr,0,sizeof(ipnr));
+					ipnr.Address.si_family = AF_INET;
+					ipnr.Address.Ipv4.sin_addr.s_addr = fakeIp;
+					ipnr.InterfaceLuid.Value = _deviceLuid.Value;
+					ipnr.PhysicalAddress[0] = _mac[0] ^ 0x10; // just make something up that's consistent and not part of this net
+					ipnr.PhysicalAddress[1] = 0x00;
+					ipnr.PhysicalAddress[2] = (UCHAR)((_deviceGuid.Data1 >> 24) & 0xff);
+					ipnr.PhysicalAddress[3] = (UCHAR)((_deviceGuid.Data1 >> 16) & 0xff);
+					ipnr.PhysicalAddress[4] = (UCHAR)((_deviceGuid.Data1 >> 8) & 0xff);
+					ipnr.PhysicalAddress[5] = (UCHAR)(_deviceGuid.Data1 & 0xff);
+					ipnr.PhysicalAddressLength = 6;
+					ipnr.State = NlnsPermanent;
+					ipnr.IsRouter = 1;
+					ipnr.IsUnreachable = 0;
+					ipnr.ReachabilityTime.LastReachable = 0x0fffffff;
+					ipnr.ReachabilityTime.LastUnreachable = 1;
+					DWORD result = CreateIpNetEntry2(&ipnr);
+					if (result != NO_ERROR)
+						Sleep(500);
+					else break;
+				}
+				for(int i=0;i<8;++i) {
+					MIB_IPFORWARD_ROW2 nr;
+					memset(&nr,0,sizeof(nr));
+					InitializeIpForwardEntry(&nr);
+					nr.InterfaceLuid.Value = _deviceLuid.Value;
+					nr.DestinationPrefix.Prefix.si_family = AF_INET; // rest is left as 0.0.0.0/0
+					nr.NextHop.si_family = AF_INET;
+					nr.NextHop.Ipv4.sin_addr.s_addr = fakeIp;
+					nr.Metric = 9999; // do not use as real default route
+					nr.Protocol = MIB_IPPROTO_NETMGMT;
+					DWORD result = CreateIpForwardEntry2(&nr);
+					if (result != NO_ERROR)
+						Sleep(500);
+					else break;
+				}
 			}
 #endif
-		}
-
-		memset(&tapOvlRead,0,sizeof(tapOvlRead));
-		tapOvlRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
-		memset(&tapOvlWrite,0,sizeof(tapOvlWrite));
-		tapOvlWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
-
-		wait4[0] = _injectSemaphore;
-		wait4[1] = tapOvlRead.hEvent;
-		wait4[2] = tapOvlWrite.hEvent; // only included if writeInProgress is true
 
-		ReadFile(_tap,tapReadBuf,sizeof(tapReadBuf),NULL,&tapOvlRead);
-		bool writeInProgress = false;
-		while (_run) {
-			if ((prevTapResetStatus != _systemTapResetStatus)||(throwOneAway)) {
-				powerCycle = throwOneAway;
-				throwOneAway = false;
-				prevTapResetStatus = _systemTapResetStatus;
-				break; // this will cause us to close and reopen the tap
-			}
-			DWORD r = WaitForMultipleObjectsEx(writeInProgress ? 3 : 2,wait4,FALSE,2500,TRUE);
-			if (!_run) break; // will also break outer while(_run)
-
-			if ((r == WAIT_TIMEOUT)||(r == WAIT_FAILED))
-				continue;
-
-			if (HasOverlappedIoCompleted(&tapOvlRead)) {
-				DWORD bytesRead = 0;
-				if (GetOverlappedResult(_tap,&tapOvlRead,&bytesRead,FALSE)) {
-					if ((bytesRead > 14)&&(_enabled)) {
-						MAC to(tapReadBuf,6);
-						MAC from(tapReadBuf + 6,6);
-						unsigned int etherType = ((((unsigned int)tapReadBuf[12]) & 0xff) << 8) | (((unsigned int)tapReadBuf[13]) & 0xff);
-						try {
-							// TODO: decode vlans
-							_handler(_arg,_nwid,from,to,etherType,0,tapReadBuf + 14,bytesRead - 14);
-						} catch ( ... ) {} // handlers should not throw
+			memset(&tapOvlRead,0,sizeof(tapOvlRead));
+			tapOvlRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
+			memset(&tapOvlWrite,0,sizeof(tapOvlWrite));
+			tapOvlWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
+
+			wait4[0] = _injectSemaphore;
+			wait4[1] = tapOvlRead.hEvent;
+			wait4[2] = tapOvlWrite.hEvent; // only included if writeInProgress is true
+
+			ReadFile(_tap,tapReadBuf,sizeof(tapReadBuf),NULL,&tapOvlRead);
+			bool writeInProgress = false;
+			ULONGLONG timeOfLastBorkCheck = GetTickCount64();
+			while (_run) {
+				DWORD waitResult = WaitForMultipleObjectsEx(writeInProgress ? 3 : 2,wait4,FALSE,2500,TRUE);
+				if (!_run) break; // will also break outer while(_run)
+
+				// Check for issues with adapter and close/reopen if any are detected. This
+				// check fixes a while boatload of Windows adapter 'coma' issues after
+				// sleep/wake and when adapters are added/removed. Basically if the tap
+				// device is borked, whack it.
+				{
+					ULONGLONG tc = GetTickCount64();
+					if ((tc - timeOfLastBorkCheck) >= 2500) {
+						timeOfLastBorkCheck = tc;
+						MIB_IF_TABLE2 *ift = NULL;
+						if ((GetIfTable2(&ift) == NO_ERROR)&&(ift)) {
+							bool isBorked = false;
+							for(ULONG r=0;r<ift->NumEntries;++r) {
+								if (ift->Table[r].InterfaceLuid.Value == _deviceLuid.Value) {
+									if ((ift->Table[r].InterfaceAndOperStatusFlags.NotMediaConnected)||(ift->Table[r].MediaConnectState == MediaConnectStateDisconnected))
+										isBorked = true;
+									break;
+								}
+							}
+							FreeMibTable(ift);
+							if (isBorked) {
+								// Close and reopen tap device if there's an issue (outer loop)
+								break;
+							}
+						}
 					}
 				}
-				ReadFile(_tap,tapReadBuf,ZT_IF_MTU + 32,NULL,&tapOvlRead);
-			}
 
-			if (writeInProgress) {
-				if (HasOverlappedIoCompleted(&tapOvlWrite)) {
-					writeInProgress = false;
-					_injectPending_m.lock();
-					_injectPending.pop();
-				} else continue; // still writing, so skip code below and wait
-			} else _injectPending_m.lock();
-
-			if (!_injectPending.empty()) {
-				WriteFile(_tap,_injectPending.front().first.data,_injectPending.front().second,NULL,&tapOvlWrite);
-				writeInProgress = true;
-			}
+				if ((waitResult == WAIT_TIMEOUT)||(waitResult == WAIT_FAILED))
+					continue;
+
+				if (HasOverlappedIoCompleted(&tapOvlRead)) {
+					DWORD bytesRead = 0;
+					if (GetOverlappedResult(_tap,&tapOvlRead,&bytesRead,FALSE)) {
+						if ((bytesRead > 14)&&(_enabled)) {
+							MAC to(tapReadBuf,6);
+							MAC from(tapReadBuf + 6,6);
+							unsigned int etherType = ((((unsigned int)tapReadBuf[12]) & 0xff) << 8) | (((unsigned int)tapReadBuf[13]) & 0xff);
+							try {
+								// TODO: decode vlans
+								_handler(_arg,_nwid,from,to,etherType,0,tapReadBuf + 14,bytesRead - 14);
+							} catch ( ... ) {} // handlers should not throw
+						}
+					}
+					ReadFile(_tap,tapReadBuf,ZT_IF_MTU + 32,NULL,&tapOvlRead);
+				}
 
-			_injectPending_m.unlock();
-		}
+				if (writeInProgress) {
+					if (HasOverlappedIoCompleted(&tapOvlWrite)) {
+						writeInProgress = false;
+						_injectPending_m.lock();
+						_injectPending.pop();
+					} else continue; // still writing, so skip code below and wait
+				} else _injectPending_m.lock();
+
+				if (!_injectPending.empty()) {
+					WriteFile(_tap,_injectPending.front().first.data,_injectPending.front().second,NULL,&tapOvlWrite);
+					writeInProgress = true;
+				}
 
-		CancelIo(_tap);
+				_injectPending_m.unlock();
+			}
 
-		CloseHandle(tapOvlRead.hEvent);
-		CloseHandle(tapOvlWrite.hEvent);
-		CloseHandle(_tap);
-		_tap = INVALID_HANDLE_VALUE;
+			CancelIo(_tap);
 
-		// We will restart and re-open the tap unless _run == false
-	}
+			CloseHandle(tapOvlRead.hEvent);
+			CloseHandle(tapOvlWrite.hEvent);
+			CloseHandle(_tap);
+			_tap = INVALID_HANDLE_VALUE;
 
-	::free(tapReadBuf);
+			// We will restart and re-open the tap unless _run == false
+		}
+	} catch ( ... ) {} // catch unexpected exceptions -- this should not happen but would prevent program crash or other weird issues since threads should not throw
 }
 
 void WindowsEthernetTap::destroyAllPersistentTapDevices(const char *pathToHelpers)

+ 2 - 2
osdep/WindowsEthernetTap.hpp

@@ -98,8 +98,8 @@ private:
 
 	GUID _deviceGuid;
 	NET_LUID _deviceLuid;
-	std::string _netCfgInstanceId; // NetCfgInstanceId, a GUID
-	std::string _deviceInstanceId; // DeviceInstanceID, another kind of "instance ID"
+	std::string _netCfgInstanceId;
+	std::string _deviceInstanceId;
 
 	std::vector<MulticastGroup> _multicastGroups;
 

+ 3 - 3
service/ControlPlane.cpp

@@ -454,7 +454,7 @@ unsigned int ControlPlane::handleRequest(
 			} else {
 #ifdef ZT_ENABLE_NETWORK_CONTROLLER
 				if (_controller)
-					_controller->handleControlPlaneHttpGET(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
+					scode = _controller->handleControlPlaneHttpGET(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
 				else scode = 404;
 #else
 				scode = 404;
@@ -490,7 +490,7 @@ unsigned int ControlPlane::handleRequest(
 			} else {
 #ifdef ZT_ENABLE_NETWORK_CONTROLLER
 				if (_controller)
-					_controller->handleControlPlaneHttpPOST(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
+					scode = _controller->handleControlPlaneHttpPOST(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
 				else scode = 404;
 #else
 				scode = 404;
@@ -525,7 +525,7 @@ unsigned int ControlPlane::handleRequest(
 			} else {
 #ifdef ZT_ENABLE_NETWORK_CONTROLLER
 				if (_controller)
-					_controller->handleControlPlaneHttpDELETE(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
+					scode = _controller->handleControlPlaneHttpDELETE(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
 				else scode = 404;
 #else
 				scode = 404;

+ 2 - 2
service/README.md

@@ -204,7 +204,7 @@ Relay objects define network-specific preferred relay nodes. Traffic to peers on
 
  * **Note**: at the moment, <u>only rules specifying allowed Ethernet types are used</u>. The database supports a richer rule set, but this is not implemented yet in the client. <u>Other types of rules will have no effect</u> (yet).
 
-Rules are matched in order of ruleId. If no rules match, the default action is 'drop'. To allow all traffic, create a single rule with all *null* fields and an action of 'accept'.
+Rules are matched in order of ruleNo. If no rules match, the default action is 'drop'. To allow all traffic, create a single rule with all *null* fields and an action of 'accept'.
 
 Rule object fields can be *null*, in which case they are omitted from the object. A null field indicates "no match on this criteria."
 
@@ -212,7 +212,7 @@ IP related fields apply only to Ethernet frames of type IPv4 or IPV6. Otherwise
 
 <table>
 <tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>
-<tr><td>ruleId</td><td>integer</td><td>User-defined rule ID and sort order</td></tr>
+<tr><td>ruleNo</td><td>integer</td><td>User-defined rule ID and sort order</td></tr>
 <tr><td>nodeId</td><td>string</td><td>10-digit hex ZeroTier address of node (a.k.a. "port on switch")</td></tr>
 <tr><td>vlanId</td><td>integer</td><td>Ethernet VLAN ID</td></tr>
 <tr><td>vlanPcp</td><td>integer</td><td>Ethernet VLAN priority code point (PCP) ID</td></tr>