123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- //
- // 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 binary, invoked to enerate certificates of membership
- 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);
- });
- // Global variables -- these are initialized on startup or netconf-init message
- var netconfSigningIdentity = null; // identity of netconf master, with private key portion
- function ztDbTrue(v) { return ((v === '1')||(v === 'true')||(v > 0)); }
- function csvToArray(csv) { return (((typeof csv === 'string')&&(csv.length > 0)) ? csv.split(',') : []); }
- function arrayToCsv(a) { return ((Array.isArray(a)) ? ((a.length > 0) ? a.join(',') : '') : (((a !== null)&&(typeof a !== 'undefined')) ? a.toString() : '')); }
- //
- // ZeroTier One Dictionary -- encoding-compatible with Dictionary in 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);
- };
- //
- // 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.address = function() {
- return ((thiz.fields.length > 0) ? thiz.fields[0] : '0000000000');
- };
- this.fromString = function(str) {
- thiz.str = '';
- thiz.fields = [];
- if (typeof str !== 'string')
- return;
- for(var i=0;i<str.length;++i) {
- if ("0123456789abcdef:ABCDEF".indexOf(str.charAt(i)) < 0)
- return; // invalid character in identity
- }
- 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;
- };
- this.hasPrivate = function() {
- return ((thiz.isValid())&&(thiz.fields.length >= 4));
- };
- if (typeof idstr === 'string')
- thiz.fromString(idstr);
- };
- //
- // Message handler for messages over ZeroTier One service bus
- //
- function handleMessage(dictStr)
- {
- var message = new Dictionary(dictStr);
- if (!('type' in message.data)) {
- console.error('ignored message without request type field');
- return;
- }
- if (message.data['type'] === 'netconf-init') {
- netconfSigningIdentity = new Identity(message.data['netconfId']);
- if (!netconfSigningIdentity.hasPrivate()) {
- netconfSigningIdentity = null;
- console.error('got invalid netconf signing identity');
- }
- } else if (message.data['type'] === 'netconf-request') {
- if ((!netconfSigningIdentity)||(!netconfSigningIdentity.hasPrivate())) {
- console.error('got netconf-request before netconf-init, ignored');
- return;
- }
- // 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']);
- var clientVersion = message.data['clientVersion'];
- var clientOs = message.data['clientOs'];
- var network = null;
- var member = null;
- var authorized = false;
- var v4NeedAssign = false;
- var v6NeedAssign = false;
- var v4Assignments = [];
- var v6Assignments = [];
- async.series([function(next) { // network lookup
- DB.hgetall('zt1:network:'+nwid+':~',function(err,obj) {
- network = obj;
- return next(err);
- });
- },function(next) { // member record lookup, unless public network
- if ((!network)||(!('nwid' in network)||(network['nwid'] !== nwid))
- return next(null);
- var memberKey = 'zt1:network:'+nwid+':member:'+peerId.address()+':~';
- DB.hgetall(memberKey,function(err,obj) {
- if (err)
- return next(err);
- else if (obj) {
- // Update member object
- member = obj;
- authorized = (ztDbTrue(network['private']) || ztDbTrue(member['authorized']));
- DB.hmset(memberKey,{
- 'lastSeen': Date.now(),
- 'lastAt': fromIpAndPort,
- 'clientVersion': (clientVersion) ? clientVersion : '?.?.?',
- 'clientOs': (clientOs) ? clientOs : '?'
- },next);
- } else {
- // Add member object for new and unauthorized member
- authorized = false;
- member = {
- 'id': peerId.address(),
- 'nwid': nwid,
- 'authorized': 0,
- 'identity': peerId.toString(),
- 'firstSeen': Date.now(),
- 'lastSeen': Date.now(),
- 'lastAt': fromIpAndPort,
- 'clientVersion': (clientVersion) ? clientVersion : '?.?.?',
- 'clientOs': (clientOs) ? clientOs : '?'
- };
- DB.hmset(memberKey,member,next);
- }
- });
- },function(next) { // IP address auto-assignment, if needed
- if (!authorized)
- return next(null);
- v4NeedAssign = (network['v4AssignMode'] === 'zt');
- v6NeedAssign = (network['v6AssignMode'] === 'zt');
- var ipa = csvToArray(member['ipAssignments']);
- for(var i=0;i<ipa.length;++i) {
- if ((ipa[i].indexOf('.') > 0)&&(v4NeedAssign))
- v4Assignments.push(ipa[i]);
- else if ((ipa[i].indexOf(':') > 0)&&(v6NeedAssign))
- v6Assignments.push(ipa[i]);
- }
- return next(null);
- },function(next) { // assign IPv4 if needed
- if ((!authorized)||(!v4NeedAssign))
- return next(null);
- var ipAssignmentAttempts = 0; // for sanity-checking
- var v4pool = network['v4AssignPool'];
- var ztaddr = peerId.address();
- var network = 0;
- var netmask = 0;
- var netmaskBits = 0;
- if (v4pool) {
- var v4poolSplit = v4Pool.split('/');
- if (v4poolSplit.length === 2) {
- var networkSplit = v4poolSplit[0].split('.');
- if (networkSplit.length === 4) {
- network |= (parseInt(networkSplit[0],10) << 24) & 0xff000000;
- network |= (parseInt(networkSplit[1],10) << 16) & 0x00ff0000;
- network |= (parseInt(networkSplit[2],10) << 8) & 0x0000ff00;
- network |= parseInt(networkSplit[3],10) & 0x000000ff;
- netmaskBits = parseInt(v4poolSplit[1],10);
- if (netmaskBits > 32)
- netmaskBits = 32; // sanity check
- for(var i=0;i<netmaskBits;++i)
- netmask |= (0x80000000 >> i);
- }
- }
- }
- var invmask = netmask ^ 0xffffffff;
- var abcd = 0;
- var assignment = null;
- var ipAssignmentsKey = 'zt1:network:'+nwid+':ipAssignments';
- var memberKey = 'zt1:network:'+nwid+':member:'+ztaddr+':~';
- async.whilst(
- function() { return ((v4NeedAssign)&&(v4Assignments.length === 0)&&(network !== 0)&&(netmask !== 0xffffffff)&&(ipAssignmentAttempts < 1000)); },
- function(next2) {
- ++ipAssignmentAttempts;
- // Generate or increment IP address
- if (abcd === 0) {
- var a = parseInt(ztaddr.substr(2,2),16) & 0xff;
- var b = parseInt(ztaddr.substr(4,2),16) & 0xff;
- var c = parseInt(ztaddr.substr(6,2),16) & 0xff;
- var d = parseInt(ztaddr.substr(8,2),16) & 0xff;
- abcd = (a << 24) | (b << 16) | (c << 8) | d;
- } else ++abcd;
- if ((abcd & 0xff) === 0)
- abcd |= 1;
- // Derive an IP to test and generate assignment ip/bits string
- var ip = (abcd & invmask) | (network & netmask);
- assignment = ((ip >> 24) & 0xff).toString(10) + '.' + ((ip >> 16) & 0xff).toString(10) + '.' + ((ip >> 8) & 0xff).toString(10) + '.' + (ip & 0xff).toString(10) + '/' + netmaskBits.toString(10);
- DB.hget(ipAssignmentsKey,assignment,function(err,value) {
- if (err)
- return next2(err);
- if ((value)&&(value !== ztaddr))
- return next2(null); // if someone's already got this IP, keep looking
- v4Assignments.push(assignment);
- // Save assignment to :ipAssignments hash
- DB.hset(ipAssignmentsKey,assignment,ztaddr,function(err) {
- if (err)
- return next2(err);
- // Save updated CSV list of assignments to member record
- var ipAssignments = member['ipAssignments'];
- if (!ipAssignments)
- ipAssignments = '';
- if (ipAssignments.length > 0)
- ipAssignments += ',';
- ipAssignments += assignment;
- member['ipAssignments'] = ipAssignments;
- DB.hset(memberKey,'ipAssignments',ipAssignments,next2);
- });
- });
- },
- next
- );
- },function(next) { // assign IPv6 if needed -- TODO
- if ((!authorized)||(!v6NeedAssign))
- return next(null);
- return next(null);
- }],function(err) {
- if (err) {
- console.log('error composing response for '+peerId.address()+': '+err);
- return;
- } else if (authorized) {
- // TODO: COM!!!
- var certificateOfMembership = null;
- var netconf = new Dictionary();
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_NETCONF_SERVICE_VERSION] = '0.0.0';
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES] = network['etherTypes'];
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID] = nwid;
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP] = Date.now().toString();
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO] = peerId.address();
- //netconf.data[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_PREFIX_BITS] = 0;
- //netconf.data[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_DEPTH] = 0;
- //netconf.data[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES] = '';
- //netconf.data[ZT_NETWORKCONFIG_DICT_KEY_ARP_CACHE_TTL] = 0;
- //netconf.data[ZT_NETWORKCONFIG_DICT_KEY_NDP_CACHE_TTL] = 0;
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_EMULATE_ARP] = '0';
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_EMULATE_NDP] = '0';
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_IS_OPEN] = ztDbTrue(network['private']) ? '0' : '1';
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_NAME] = network['name'];
- if (network['desc'])
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_DESC] = network['desc'];
- if (v4NeedAssign)
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC] = (v4Assignments.length > 0) ? v4Assignments.join(',') : '';
- if (v6NeedAssign)
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC] = (v6Assignments.length > 0) ? v6Assignments.join(',') : '';
- if (certificateOfMembership !== null)
- netconf.data[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = certificateOfMembership;
- var response = new Dictionary();
- response.data['peer'] = peerId.address();
- response.data['nwid'] = nwid;
- response.data['type'] = 'netconf-response';
- response.data['requestId'] = requestId;
- response.data['netconf'] = netconf.toString();
- process.stdout.write(response.toString()+'\n');
- return;
- } else {
- }
- });
- } else {
- console.error('ignored unrecognized message type: '+message.data['type']);
- }
- };
- //
- // 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');
|