diff options
author | Adam Ierymenko <adam.ierymenko@gmail.com> | 2017-04-04 16:47:14 -0700 |
---|---|---|
committer | Adam Ierymenko <adam.ierymenko@gmail.com> | 2017-04-04 16:47:14 -0700 |
commit | 6fabaae736de2daa1ba0c22701bbb11539e3fba7 (patch) | |
tree | 13d3e7e374887a8eccb2be96e46ac687eacb7da8 /root-watcher/zerotier-root-watcher.js | |
parent | 1b2cfd63e552c32cd3efecc6c586a947f638af9f (diff) | |
download | infinitytier-6fabaae736de2daa1ba0c22701bbb11539e3fba7.tar.gz infinitytier-6fabaae736de2daa1ba0c22701bbb11539e3fba7.zip |
Add simple root server watcher (for our own new dashboard, but also for user use).
Diffstat (limited to 'root-watcher/zerotier-root-watcher.js')
-rw-r--r-- | root-watcher/zerotier-root-watcher.js | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/root-watcher/zerotier-root-watcher.js b/root-watcher/zerotier-root-watcher.js index c6ce6be0..d4607fc2 100644 --- a/root-watcher/zerotier-root-watcher.js +++ b/root-watcher/zerotier-root-watcher.js @@ -2,4 +2,234 @@ 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); |