diff options
author | Adam Ierymenko <adam.ierymenko@gmail.com> | 2014-05-08 00:11:50 +0000 |
---|---|---|
committer | Adam Ierymenko <adam.ierymenko@gmail.com> | 2014-05-08 00:11:50 +0000 |
commit | 1e6475fad6309d2fe318f93736366edf17f5a1f2 (patch) | |
tree | b3ff3388cddfbdd065e02d85d5be6b86c4b81de8 /netconf-service/index.js | |
parent | d5f95b721de4eda1173fa91b91bb26a03202b46a (diff) | |
download | infinitytier-1e6475fad6309d2fe318f93736366edf17f5a1f2.tar.gz infinitytier-1e6475fad6309d2fe318f93736366edf17f5a1f2.zip |
Clean out unused netconf fields, rename a few, work on new netconf server.
Diffstat (limited to 'netconf-service/index.js')
-rw-r--r-- | netconf-service/index.js | 500 |
1 files changed, 284 insertions, 216 deletions
diff --git a/netconf-service/index.js b/netconf-service/index.js index d76b4c6a..db55487c 100644 --- a/netconf-service/index.js +++ b/netconf-service/index.js @@ -26,20 +26,15 @@ // // 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_PRIVATE = "p"; +var ZT_NETWORKCONFIG_DICT_KEY_NAME = "n"; +var ZT_NETWORKCONFIG_DICT_KEY_DESC = "d"; var ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC = "v4s"; var ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC = "v6s"; var ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES = "mr"; @@ -48,6 +43,9 @@ 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'; +// From Constants.hpp in node/ +var ZT_NETWORK_AUTOCONF_DELAY = 60000; + // Connect to redis, assuming database 0 and no auth (for now) var redis = require('redis'); var DB = redis.createClient(); @@ -58,6 +56,8 @@ DB.on("error",function(err) { // Global variables -- these are initialized on startup or netconf-init message var netconfSigningIdentity = null; // identity of netconf master, with private key portion +var spawn = require('child_process').spawn; + 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() : '')); } @@ -208,241 +208,309 @@ function Identity(idstr) thiz.fromString(idstr); }; +function generateCertificateOfMembership(nwid,peerAddress,callback) +{ + var comTimestamp = '0,' + Date.now().toString(16) + ',' + (ZT_NETWORK_AUTOCONF_DELAY * 4).toString(16); + var comNwid = '1,' + nwid + ',0'; + var comIssuedTo = '2,' + peerAddress + ',ffffffffffffffff'; + var cert = ''; + var idtool = spawn(ZEROTIER_IDTOOL,[ 'mkcom',netconfSigningIdentity,comTimestamp,comNwid,comIssuedTo ]); + idtool.stdout.on('data',function(data) { + if (typeof data === 'string') + cert += data; + }); + idtool.on('close',function(exitCode) { + return callback((cert.length > 0) ? cert : null,exitCode); + }); +}; + // // Message handler for messages over ZeroTier One service bus // -function handleMessage(dictStr) +function doNetconfInit(message) { - var message = new Dictionary(dictStr); + netconfSigningIdentity = new Identity(message.data['netconfId']); + if (!netconfSigningIdentity.hasPrivate()) { + netconfSigningIdentity = null; + console.error('got invalid netconf signing identity in netconf-init'); + } +} - if (!('type' in message.data)) { - console.error('ignored message without request type field'); +function doNetconfRequest(message) +{ + if ((!netconfSigningIdentity)||(!netconfSigningIdentity.hasPrivate())) { + console.error('got netconf-request before netconf-init, ignored'); return; } - if (message.data['type'] === 'netconf-init') { + // 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; - netconfSigningIdentity = new Identity(message.data['netconfId']); - if (!netconfSigningIdentity.hasPrivate()) { - netconfSigningIdentity = null; - console.error('got invalid netconf signing identity'); - } + var network = null; + var member = null; - } else if (message.data['type'] === 'netconf-request') { - if ((!netconfSigningIdentity)||(!netconfSigningIdentity.hasPrivate())) { - console.error('got netconf-request before netconf-init, ignored'); - return; - } + var authorized = false; - // 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; + var v4NeedAssign = false; + var v6NeedAssign = false; + var v4Assignments = []; + var v6Assignments = []; + var ipAssignments = []; // both v4 and v6 + + async.series([function(next) { - // 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; + // 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); - }); - },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]); + + if (obj) { + // Update existing member record with new last seen time, etc. + 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 record to network for newly seen peer + authorized = ztDbTrue(network['private']) ? false : true; // public networks authorize everyone by default + var now = Date.now().toString(); + member = { + 'id': peerId.address(), + 'nwid': nwid, + 'authorized': authorized ? '1' : '0', + 'identity': peerId.toString(), + 'firstSeen': now, + 'lastSeen': now, + 'lastAt': fromIpAndPort, + 'clientVersion': (message.data['clientVersion']) ? message.data['clientVersion'] : '?.?.?', + 'clientOs': (message.data['clientOs']) ? message.data['clientOs'] : '?' + }; + DB.hmset(memberKey,member,next); } + }); + },function(next) { + + // Figure out which IP address auto-assignments we need to look up or make + if (!authorized) 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); - } + + v4NeedAssign = (network['v4AssignMode'] === 'zt'); + v6NeedAssign = (network['v6AssignMode'] === 'zt'); + + var ipa = csvToArray(member['ipAssignments']); + for(var i=0;i<ipa.length;++i) { + if (ipa[i]) + ipAssignments.push(ipa[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)||(v4Assignments.length > 0)) + return next(null); + + var ipAssignmentAttempts = 0; + var v4pool = network['v4AssignPool']; // technically csv but only one netblock currently supported + var peerAddress = 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); + netmask &= 0xffffffff; } } - 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 ((network === 0)||(netmask === 0xffffffff)) + return next(null); + var invmask = netmask ^ 0xffffffff; + var abcd = 0; + + var assignment = null; + + var ipAssignmentsKey = 'zt1:network:'+nwid+':ipAssignments'; + var memberKey = 'zt1:network:'+nwid+':member:'+peerAddress+':~'; + + async.whilst( + function() { return ((v4Assignments.length === 0)&&(ipAssignmentAttempts < 1000)); }, + function(next2) { + ++ipAssignmentAttempts; + + // Generate or increment IP address source bits + if (abcd === 0) { + var a = parseInt(peerAddress.substr(2,2),16) & 0xff; + var b = parseInt(peerAddress.substr(4,2),16) & 0xff; + var c = parseInt(peerAddress.substr(6,2),16) & 0xff; + var d = parseInt(peerAddress.substr(8,2),16) & 0xff; + abcd = (a << 24) | (b << 16) | (c << 8) | d; + } else ++abcd; + if ((abcd & 0xff) === 0) + abcd |= 1; + abcd &= 0xffffffff; + + // 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); + + // Check :ipAssignments to see if this IP is already taken + DB.hget(ipAssignmentsKey,assignment,function(err,value) { + if (err) + return next2(err); + + // IP is already taken, try again via async.whilst() + if ((value)&&(value !== peerAddress)) + return next2(null); // if someone's already got this IP, keep looking + + v4Assignments.push(assignment); + ipAssignments.push(assignment); + + // Save assignment to :ipAssignments hash + DB.hset(ipAssignmentsKey,assignment,peerAddress,function(err) { 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); - }); + + // Save updated CSV list of assignments to member record + var ipacsv = ipAssignments.join(','); + member['ipAssignments'] = ipacsv; + DB.hset(memberKey,'ipAssignments',ipacsv,next2); }); - }, - next - ); + }); + }, + next + ); - },function(next) { // assign IPv6 if needed -- TODO - if ((!authorized)||(!v6NeedAssign)) - return next(null); + },function(next) { + // assign IPv6 if needed -- TODO + if ((!authorized)||(!v6NeedAssign)||(v6Assignments.length > 0)) 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(); + + return next(null); + + }],function(err) { + + if (err) { + console.log('error composing response for '+peerId.address()+': '+err); + return; + } + + var response = new Dictionary(); + response.data['peer'] = peerId.address(); + response.data['nwid'] = nwid; + response.data['type'] = 'netconf-response'; + response.data['requestId'] = requestId; + + if (authorized) { + var certificateOfMembership = null; + var privateNetwork = ztDbTrue(network['private']); + + async.series([function(next) { + + // Generate certificate of membership if necessary + if (privateNetwork) { + generateCertificateOfMembership(nwid,peerId.address(),function(cert,exitCode) { + if (cert) { + certificateOfMembership = cert; + return next(null); + } else return next(new Error('zerotier-idtool returned '+exitCode)); + }); + } else return next(null); + + }],function(err) { + + if (err) { + console.error('unable to generate certificate for peer '+peerId.address()+' on network '+nwid+': '+err); + response.data['error'] = 'ACCESS_DENIED'; // unable to generate certificate + } else { + var netconf = new Dictionary(); + 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(16); + 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_PRIVATE] = privateNetwork ? '1' : '0'; + netconf.data[ZT_NETWORKCONFIG_DICT_KEY_NAME] = network['name']; + if (network['desc']) + netconf.data[ZT_NETWORKCONFIG_DICT_KEY_DESC] = network['desc']; + if ((v4NeedAssign)&&(v4Assignments.length > 0)) + netconf.data[ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC] = v4Assignments.join(','); + if ((v6NeedAssign)&&(v6Assignments.length > 0)) + netconf.data[ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC] = v6Assignments.join(','); + if (certificateOfMembership !== null) + netconf.data[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = certificateOfMembership; + response.data['netconf'] = netconf.toString(); + } process.stdout.write(response.toString()+'\n'); - return; - } else { - } - }); + + }); + + } else { + + // Peer not authorized to join network + response.data['error'] = 'ACCESS_DENIED'; + process.stdout.write(response.toString()+'\n'); + + } + + }); +} + +function handleMessage(dictStr) +{ + var message = new Dictionary(dictStr); + if (!('type' in message.data)) { + console.error('ignored message without request type field'); + return; + } else if (message.data['type'] === 'netconf-init') { + doNetconfInit(message); + } else if (message.data['type'] === 'netconf-request') { + doNetconfRequest(message); } else { console.error('ignored unrecognized message type: '+message.data['type']); } |