ZeroTierOne/root-watcher/zerotier-root-watcher.js

236 lines
6.2 KiB
JavaScript

'use strict';
const pg = require('pg');
const zlib = require('zlib');
const http = require('http');
const fs = require('fs');
const async = require('async');
const config = JSON.parse(fs.readFileSync('./config.json'));
const roots = config.roots||{};
const db = new pg.Pool(config.db);
process.on('uncaughtException',function(err) {
console.error('ERROR: uncaught exception: '+err);
if (err.stack)
console.error(err.stack);
});
function httpRequest(host,port,authToken,method,path,args,callback)
{
var responseBody = [];
var postData = (args) ? JSON.stringify(args) : null;
var req = http.request({
host: host,
port: port,
path: path,
method: method,
headers: {
'X-ZT1-Auth': (authToken||''),
'Content-Length': (postData) ? postData.length : 0
}
},function(res) {
res.on('data',function(chunk) {
if ((chunk)&&(chunk.length > 0))
responseBody.push(chunk);
});
res.on('timeout',function() {
try {
if (typeof callback === 'function') {
var cb = callback;
callback = null;
cb(new Error('connection timed out'),null);
}
req.abort();
} catch (e) {}
});
res.on('error',function(e) {
try {
if (typeof callback === 'function') {
var cb = callback;
callback = null;
cb(new Error('connection timed out'),null);
}
req.abort();
} catch (e) {}
});
res.on('end',function() {
if (typeof callback === 'function') {
var cb = callback;
callback = null;
if (responseBody.length === 0) {
return cb(null,{});
} else {
responseBody = Buffer.concat(responseBody);
if (responseBody.length < 2) {
return cb(null,{});
}
if ((responseBody.readUInt8(0,true) === 0x1f)&&(responseBody.readUInt8(1,true) === 0x8b)) {
try {
responseBody = zlib.gunzipSync(responseBody);
} catch (e) {
return cb(e,null);
}
}
try {
return cb(null,JSON.parse(responseBody));
} catch (e) {
return cb(e,null);
}
}
}
});
}).on('error',function(e) {
try {
if (typeof callback === 'function') {
var cb = callback;
callback = null;
cb(e,null);
}
req.abort();
} catch (e) {}
}).on('timeout',function() {
try {
if (typeof callback === 'function') {
var cb = callback;
callback = null;
cb(new Error('connection timed out'),null);
}
req.abort();
} catch (e) {}
});
req.setTimeout(30000);
req.setNoDelay(true);
if (postData !== null)
req.end(postData);
else req.end();
};
var peerStatus = {};
function saveToDb()
{
db.connect(function(err,client,clientDone) {
if (err) {
console.log('WARNING: database error writing peers: '+err.toString());
clientDone();
return setTimeout(saveToDb,config.dbSaveInterval||60000);
}
client.query('BEGIN',function(err) {
if (err) {
console.log('WARNING: database error writing peers: '+err.toString());
clientDone();
return setTimeout(saveToDb,config.dbSaveInterval||60000);
}
let timeout = Date.now() - (config.peerTimeout||600000);
let wtotal = 0;
async.eachSeries(Object.keys(peerStatus),function(address,nextAddress) {
let s = peerStatus[address];
if (s[1] <= timeout) {
delete peerStatus[address];
return process.nextTick(nextAddress);
} else {
++wtotal;
client.query('INSERT INTO "Peer" ("ztAddress","timestamp","versionMajor","versionMinor","versionRev","rootId","phyPort","phyLinkQuality","phyLastReceive","phyAddress") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)',s,nextAddress);
}
},function(err) {
if (err)
console.log('WARNING database error writing peers: '+err.toString());
console.log(Date.now().toString()+' '+wtotal);
client.query('COMMIT',function(err,result) {
clientDone();
return setTimeout(saveToDb,config.dbSaveInterval||60000);
});
});
});
});
};
function doRootUpdate(name,id,ip,port,peersPath,authToken,interval)
{
httpRequest(ip,port,authToken,"GET",peersPath,null,function(err,res) {
if (err) {
console.log('WARNING: cannot reach '+name+peersPath+' (will try again in 1s): '+err.toString());
return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },1000);
}
if (!Array.isArray(res)) {
console.log('WARNING: cannot reach '+name+peersPath+' (will try again in 1s): response is not an array of peers');
return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },1000);
}
//console.log(name+': '+res.length+' peer entries.');
let now = Date.now();
let count = 0;
for(let pi=0;pi<res.length;++pi) {
let peer = res[pi];
let address = peer.address;
let ztAddress = parseInt(address,16)||0;
if (!ztAddress)
continue;
let paths = peer.paths;
if ((Array.isArray(paths))&&(paths.length > 0)) {
let bestPath = null;
for(let i=0;i<paths.length;++i) {
if (paths[i].active) {
let lr = paths[i].lastReceive;
if ((lr > 0)&&((!bestPath)||(bestPath.lastReceive < lr)))
bestPath = paths[i];
}
}
if (bestPath) {
let a = bestPath.address;
if (typeof a === 'string') {
let a2 = a.split('/');
if (a2.length === 2) {
let vmaj = peer.versionMajor;
if ((typeof vmaj === 'undefined')||(vmaj === null)) vmaj = -1;
let vmin = peer.versionMinor;
if ((typeof vmin === 'undefined')||(vmin === null)) vmin = -1;
let vrev = peer.versionRev;
if ((typeof vrev === 'undefined')||(vrev === null)) vrev = -1;
let lr = parseInt(bestPath.lastReceive)||0;
let s = peerStatus[address];
if ((!s)||(s[8] < lr)) {
peerStatus[address] = [
ztAddress,
now,
vmaj,
vmin,
vrev,
id,
parseInt(a2[1])||0,
parseFloat(bestPath.linkQuality)||1.0,
lr,
a2[0]
];
}
++count;
}
}
}
}
}
console.log(name+': '+count+' peers with active direct paths.');
return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },interval);
});
};
for(var r in roots) {
var rr = roots[r];
if (rr.peers)
doRootUpdate(r,rr.id,rr.ip,rr.port,rr.peers,rr.authToken||null,config.interval||60000);
}
return setTimeout(saveToDb,config.dbSaveInterval||60000);