diff options
author | Adam Ierymenko <adam.ierymenko@gmail.com> | 2014-05-07 02:45:15 +0000 |
---|---|---|
committer | Adam Ierymenko <adam.ierymenko@gmail.com> | 2014-05-07 02:45:15 +0000 |
commit | d5f95b721de4eda1173fa91b91bb26a03202b46a (patch) | |
tree | c2ff2840d15a55691cbab52dd0a95e2f60e1bcf0 /netconf-service/index.js | |
parent | ae2eeff5c6bf759eec17a4608318dd499e222b38 (diff) | |
download | infinitytier-d5f95b721de4eda1173fa91b91bb26a03202b46a.tar.gz infinitytier-d5f95b721de4eda1173fa91b91bb26a03202b46a.zip |
Yet more work on netconf master, add redis docs.
Diffstat (limited to 'netconf-service/index.js')
-rw-r--r-- | netconf-service/index.js | 259 |
1 files changed, 232 insertions, 27 deletions
diff --git a/netconf-service/index.js b/netconf-service/index.js index 5af2aa16..d76b4c6a 100644 --- a/netconf-service/index.js +++ b/netconf-service/index.js @@ -45,7 +45,7 @@ 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 +// 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) @@ -55,7 +55,17 @@ DB.on("error",function(err) { console.error('redis query error: '+err); }); -// Encoding-compatible with Dictionary.hpp in ZeroTier One C++ code base +// 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; @@ -150,22 +160,6 @@ function Dictionary(fromStr) 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 // @@ -181,6 +175,10 @@ function Identity(idstr) return thiz.str; }; + this.address = function() { + return ((thiz.fields.length > 0) ? thiz.fields[0] : '0000000000'); + }; + this.fromString = function(str) { thiz.str = ''; thiz.fields = []; @@ -202,7 +200,7 @@ function Identity(idstr) return false; }; - this.hasSecretKey = function() { + this.hasPrivate = function() { return ((thiz.isValid())&&(thiz.fields.length >= 4)); }; @@ -217,17 +215,26 @@ function Identity(idstr) function handleMessage(dictStr) { var message = new Dictionary(dictStr); - var response = new Dictionary(); - if (!('type' in message.data)) - return; // no request type + 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.isValid()) - netconfSigningIdentity = null; // empty strings and such are not valid - return; // no response expected + 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']; @@ -238,9 +245,207 @@ function handleMessage(dictStr) // Get optional fields var meta = new Dictionary(message.data['meta']); - } else return; + 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]); + } - process.stdout.write(response.toString()+'\n'); + 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']); + } }; // |