Yet more work on netconf master, add redis docs.

This commit is contained in:
Adam Ierymenko 2014-05-07 02:45:15 +00:00
parent ae2eeff5c6
commit d5f95b721d
3 changed files with 365 additions and 27 deletions

View file

@ -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'];
process.stdout.write(response.toString()+'\n');
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']);
}
};
//