diff options
-rwxr-xr-x | .gitignore | 3 | ||||
-rw-r--r-- | netconf-service/README.txt | 16 | ||||
-rw-r--r-- | netconf-service/index.js | 263 | ||||
-rw-r--r-- | netconf-service/package.json | 14 |
4 files changed, 278 insertions, 18 deletions
@@ -6,8 +6,7 @@ .DS_Store .Apple* *.dSYM -/netconf-service/netconf-test -/netconf-service/netconf.service +/netconf-service/node_modules /ipch /windows/ZeroTierOne.sdf /windows/ZeroTierOne.v11.suo diff --git a/netconf-service/README.txt b/netconf-service/README.txt deleted file mode 100644 index 6ec7eeba..00000000 --- a/netconf-service/README.txt +++ /dev/null @@ -1,16 +0,0 @@ -This is the netconf service, which can be built and run by placing it in the -services.d subfolder of the ZeroTier One home directory. - -Users probably won't be interested in this. It's for running a "netconf -master," which handles certificate issuing, static IP assignment, and other -things for a network. The ZeroTier address of the netconf master forms the -first 40 bits of a hexadecimal network ID, permitting the master to be -located and queried. - -Masters currently don't support multi-homing, but they can easily be made -fault tolerant via fail-over. If the master node goes down, it can be -started elsewhere with the same ZT1 identity. (The underlying database would -also have to be fault tolerant.) - -For this to work it requires a MySQL backend with a properly structured -database. diff --git a/netconf-service/index.js b/netconf-service/index.js new file mode 100644 index 00000000..7ab2b35e --- /dev/null +++ b/netconf-service/index.js @@ -0,0 +1,263 @@ +// +// ZeroTier One - Global Peer to Peer Ethernet +// Copyright (C) 2011-2014 ZeroTier Networks LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// -- +// +// ZeroTier may be used and distributed under the terms of the GPLv3, which +// are available at: http://www.gnu.org/licenses/gpl-3.0.html +// +// If you would like to embed ZeroTier into a commercial application or +// redistribute it in a modified binary form, please contact ZeroTier Networks +// LLC. Start here: http://www.zerotier.com/ +// + +// Fields in netconf response dictionary +var ZT_NETWORKCONFIG_DICT_KEY_NETCONF_SERVICE_VERSION = "ncver"; +var ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES = "et"; +var ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID = "nwid"; +var ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP = "ts"; +var ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO = "id"; +var ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_PREFIX_BITS = "mpb"; +var ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_DEPTH = "md"; +var ZT_NETWORKCONFIG_DICT_KEY_ARP_CACHE_TTL = "cARP"; +var ZT_NETWORKCONFIG_DICT_KEY_NDP_CACHE_TTL = "cNDP"; +var ZT_NETWORKCONFIG_DICT_KEY_EMULATE_ARP = "eARP"; +var ZT_NETWORKCONFIG_DICT_KEY_EMULATE_NDP = "eNDP"; +var ZT_NETWORKCONFIG_DICT_KEY_IS_OPEN = "o"; +var ZT_NETWORKCONFIG_DICT_KEY_NAME = "name"; +var ZT_NETWORKCONFIG_DICT_KEY_DESC = "desc"; +var ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC = "v4s"; +var ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC = "v6s"; +var ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES = "mr"; +var ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP = "com"; + +// Path to zerotier-idtool +var ZEROTIER_IDTOOL = '/usr/local/bin/zerotier-idtool'; + +// Connect to redis, assuming database 0 and no auth (for now) +var redis = require('redis'); +var DB = redis.createClient(); +DB.on("error",function(err) { + console.error('Redis query error: '+err); +}); + +// Encoding-compatible with Dictionary.hpp in ZeroTier One C++ code base +function Dictionary(fromStr) +{ + var thiz = this; + + this.data = {}; + + this._esc = function(data) { + var es = ''; + for(var i=0;i<data.length;++i) { + var c = data.charAt(i); + switch(c) { + case '\0': es += '\\0'; break; + case '\r': es += '\\r'; break; + case '\n': es += '\\n'; break; + case '\\': es += '\\\\'; break; + case '=': es += '\\='; break; + default: es += c; break; + } + } + return es; + }; + this._unesc = function(s) { + if (typeof s !== 'string') + return ''; + var uns = ''; + var escapeState = false; + for(var i=0;i<s.length;++i) { + var c = s.charAt(i); + if (escapeState) { + escapeState = false; + switch(c) { + case '0': uns += '\0'; break; + case 'r': uns += '\r'; break; + case 'n': uns += '\n'; break; + default: uns += c; break; + } + } else{ + if ((c !== '\r')&&(c !== '\n')&&(c !== '\0')) { + if (c === '\\') + escapeState = true; + else uns += c; + } + } + } + return uns; + }; + + this.toString = function() { + var str = ''; + + for(var key in thiz.data) { + str += thiz._esc(key); + str += '='; + var value = thiz.data[key]; + if (value) + str += thiz._esc(value.toString()); + str += '\n'; + } + + return str; + }; + + this.fromString = function(str) { + thiz.data = {}; + if (typeof str !== 'string') + return thiz; + + var lines = str.split('\n'); + for(var l=0;l<lines.length;++l) { + var escapeState = false; + var eqAt = 0; + for(;eqAt<lines[l].length;++eqAt) { + var c = lines[l].charAt(eqAt); + if (escapeState) + escapeState = false; + else if (c === '\\') + escapeState = true; + else if (c === '=') + break; + } + + var k = thiz._unesc(lines[l].substr(0,eqAt)); + ++eqAt; + if ((k)&&(k.length > 0)) + thiz.data[k] = thiz._unesc((eqAt < lines[l].length) ? lines[l].substr(eqAt) : ''); + } + + return thiz; + }; + + if ((typeof fromStr === 'string')&&(fromStr.length > 0)) + thiz.fromString(fromStr); +}; + +/* Dictionary tester +var testDict1 = new Dictionary(); +var testDict2 = new Dictionary(); +testDict1.data['foo'] = '1'; +testDict1.data['bar'] = 'The quick brown fox\ncontained a carriage return.'; +testDict2.data['embeddedDictionary'] = testDict1.toString(); +testDict2.data['baz'] = 'eklrjelkrnlqkejrnlkqerne'; +console.log(testDict2.toString()); +console.log('After fromString(toString())...\n'); +console.log((new Dictionary(testDict2.toString())).toString()); +process.exit(0); +*/ + +// Variables initialized by netconf-init message +var netconfSigningIdentity = null; + +// +// Identity implementation using zerotier-idtool as subprocess to do actual crypto work +// + +function Identity(idstr) +{ + var thiz = this; + + this.str = ''; + this.fields = []; + + this.toString = function() { + return thiz.str; + }; + + this.fromString = function(str) { + thiz.str = ''; + thiz.fields = []; + if (!str) + return; + var fields = str.split(':'); + if ((fields.length < 3)||(fields[0].length !== 10)||(fields[1] !== '0')) + return; + thiz.fields = fields; + } + + this.isValid = function() { + if ((thiz.fields.length < 3)||(thiz.fields[0].length !== 10)||(thiz.fields[1] !== '0')) + return true; + return false; + } + + if (typeof idstr === 'string') + thiz.fromString(idstr); +}; + +// +// Message handler for messages over ZeroTier One service bus +// + +function handleMessage(dictStr) +{ + var message = new Dictionary(dictStr); + var response = new Dictionary(); + + if (!('type' in message.data)) + return; // no request type + + if (message.data['type'] === 'netconf-init') { + netconfSigningIdentity = new Identity(message.data['netconfId']); + if (!netconfSigningIdentity.isValid()) + netconfSigningIdentity = null; // empty strings and such are not valid + return; // no response expected + } else if (message.data['type'] === 'netconf-request') { + // Get required fields + var peerId = new Identity(message.data['peerId']); + var fromIpAndPort = message.data['from']; + var nwid = message.data['nwid']; + var requestId = message.data['requestId']; + if ((!peerId)||(!peerId.isValid())||(!fromIpAndPort)||(!nwid)||(nwid.length !== 16)||(!requestId)) + return; + + // Get optional fields + var meta = new Dictionary(message.data['meta']); + } else return; + + process.stdout.write(response.toString()+'\n'); +}; + +// +// Read stream of double-CR-terminated dictionaries from stdin until close/EOF +// + +var stdinReadBuffer = ''; +process.stdin.on('readable',function() { + var chunk = process.stdin.read(); + if (chunk) + stdinReadBuffer += chunk; + if ((stdinReadBuffer.length >= 2)&&(stdinReadBuffer.substr(stdinReadBuffer.length - 2) === '\n\n')) { + handleMessage(stdinReadBuffer); + stdinReadBuffer = ''; + } +}); +process.stdin.on('end',function() { + process.exit(0); +}); +process.stdin.on('close',function() { + process.exit(0); +}); +process.stdin.on('error',function() { + process.exit(0); +}); + +// Tell ZeroTier One that the service is running, solicit netconf-init +process.stdout.write('type=ready\n\n'); diff --git a/netconf-service/package.json b/netconf-service/package.json new file mode 100644 index 00000000..e5b13523 --- /dev/null +++ b/netconf-service/package.json @@ -0,0 +1,14 @@ +{ + "name": "zt1-netconf-service", + "version": "0.0.0", + "description": "Worker in charge of issuing network configuration from ZeroTier One netconf masters", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "ZeroTier Networks LLC", + "license": "GPL", + "dependencies": { + "redis": "~0.10.1" + } +} |