|
|
|
|
@ -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']); |
|
|
|
|
} |
|
|
|
|
|