From 7f4da08ff7af6f5d2a06001f70dae906a859c66e Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 1 Jun 2017 12:57:44 -0700 Subject: . --- attic/root-watcher/README.md | 8 + attic/root-watcher/config.json.example | 30 ++++ attic/root-watcher/package.json | 16 ++ attic/root-watcher/schema.sql | 20 +++ attic/root-watcher/zerotier-root-watcher.js | 235 ++++++++++++++++++++++++++++ root-watcher/README.md | 8 - root-watcher/config.json.example | 30 ---- root-watcher/package.json | 16 -- root-watcher/schema.sql | 20 --- root-watcher/zerotier-root-watcher.js | 235 ---------------------------- 10 files changed, 309 insertions(+), 309 deletions(-) create mode 100644 attic/root-watcher/README.md create mode 100644 attic/root-watcher/config.json.example create mode 100644 attic/root-watcher/package.json create mode 100644 attic/root-watcher/schema.sql create mode 100644 attic/root-watcher/zerotier-root-watcher.js delete mode 100644 root-watcher/README.md delete mode 100644 root-watcher/config.json.example delete mode 100644 root-watcher/package.json delete mode 100644 root-watcher/schema.sql delete mode 100644 root-watcher/zerotier-root-watcher.js diff --git a/attic/root-watcher/README.md b/attic/root-watcher/README.md new file mode 100644 index 00000000..ded6a63f --- /dev/null +++ b/attic/root-watcher/README.md @@ -0,0 +1,8 @@ +Root Server Watcher +====== + +This is a small daemon written in NodeJS that watches a set of root servers and records peer status information into a Postgres database. + +To use type `npm install` to install modules. Then edit `config.json.example` and rename to `config.json`. For each of your roots you will need to configure a way for this script to reach it. You will also need to use `schema.sql` to initialize a Postgres database to contain your logs and set it up in `config.json` as well. + +This doesn't (yet) include any software for reading the log database and doing anything useful with the information inside, though given that it's a simple SQL database it should not be hard to compose queries to show interesting statistics. diff --git a/attic/root-watcher/config.json.example b/attic/root-watcher/config.json.example new file mode 100644 index 00000000..0ad1bbe1 --- /dev/null +++ b/attic/root-watcher/config.json.example @@ -0,0 +1,30 @@ +{ + "interval": 30000, + "dbSaveInterval": 60000, + "peerTimeout": 600000, + "db": { + "database": "ztr", + "user": "postgres", + "password": "s00p3rs3kr3t", + "host": "127.0.0.1", + "port": 5432, + "max": 16, + "idleTimeoutMillis": 30000 + }, + "roots": { + "my-root-01": { + "id": 1, + "ip": "10.0.0.1", + "port": 9993, + "authToken": "foobarbaz", + "peers": "/peer" + }, + "my-root-02": { + "id": 2, + "ip": "10.0.0.2", + "port": 9993, + "authToken": "lalafoo", + "peers": "/peer" + } + } +} diff --git a/attic/root-watcher/package.json b/attic/root-watcher/package.json new file mode 100644 index 00000000..d6e86d78 --- /dev/null +++ b/attic/root-watcher/package.json @@ -0,0 +1,16 @@ +{ + "name": "zerotier-root-watcher", + "version": "1.0.0", + "description": "Simple background service to watch a cluster of roots and record peer info into a database", + "main": "zerotier-root-watcher.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "ZeroTier, Inc. ", + "license": "GPL-3.0", + "dependencies": { + "async": "^2.3.0", + "pg": "^6.1.5", + "zlib": "^1.0.5" + } +} diff --git a/attic/root-watcher/schema.sql b/attic/root-watcher/schema.sql new file mode 100644 index 00000000..ade0fa3e --- /dev/null +++ b/attic/root-watcher/schema.sql @@ -0,0 +1,20 @@ +/* Schema for ZeroTier root watcher log database */ + +CREATE TABLE "Peer" +( + "ztAddress" BIGINT NOT NULL, + "timestamp" BIGINT NOT NULL, + "versionMajor" INTEGER NOT NULL, + "versionMinor" INTEGER NOT NULL, + "versionRev" INTEGER NOT NULL, + "rootId" INTEGER NOT NULL, + "phyPort" INTEGER NOT NULL, + "phyLinkQuality" REAL NOT NULL, + "phyLastReceive" BIGINT NOT NULL, + "phyAddress" INET NOT NULL +); + +CREATE INDEX "Peer_ztAddress" ON "Peer" ("ztAddress"); +CREATE INDEX "Peer_timestamp" ON "Peer" ("timestamp"); +CREATE INDEX "Peer_rootId" ON "Peer" ("rootId"); +CREATE INDEX "Peer_phyAddress" ON "Peer" ("phyAddress"); diff --git a/attic/root-watcher/zerotier-root-watcher.js b/attic/root-watcher/zerotier-root-watcher.js new file mode 100644 index 00000000..d4607fc2 --- /dev/null +++ b/attic/root-watcher/zerotier-root-watcher.js @@ -0,0 +1,235 @@ +'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 0)) { + let bestPath = null; + for(let i=0;i 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); diff --git a/root-watcher/README.md b/root-watcher/README.md deleted file mode 100644 index ded6a63f..00000000 --- a/root-watcher/README.md +++ /dev/null @@ -1,8 +0,0 @@ -Root Server Watcher -====== - -This is a small daemon written in NodeJS that watches a set of root servers and records peer status information into a Postgres database. - -To use type `npm install` to install modules. Then edit `config.json.example` and rename to `config.json`. For each of your roots you will need to configure a way for this script to reach it. You will also need to use `schema.sql` to initialize a Postgres database to contain your logs and set it up in `config.json` as well. - -This doesn't (yet) include any software for reading the log database and doing anything useful with the information inside, though given that it's a simple SQL database it should not be hard to compose queries to show interesting statistics. diff --git a/root-watcher/config.json.example b/root-watcher/config.json.example deleted file mode 100644 index 0ad1bbe1..00000000 --- a/root-watcher/config.json.example +++ /dev/null @@ -1,30 +0,0 @@ -{ - "interval": 30000, - "dbSaveInterval": 60000, - "peerTimeout": 600000, - "db": { - "database": "ztr", - "user": "postgres", - "password": "s00p3rs3kr3t", - "host": "127.0.0.1", - "port": 5432, - "max": 16, - "idleTimeoutMillis": 30000 - }, - "roots": { - "my-root-01": { - "id": 1, - "ip": "10.0.0.1", - "port": 9993, - "authToken": "foobarbaz", - "peers": "/peer" - }, - "my-root-02": { - "id": 2, - "ip": "10.0.0.2", - "port": 9993, - "authToken": "lalafoo", - "peers": "/peer" - } - } -} diff --git a/root-watcher/package.json b/root-watcher/package.json deleted file mode 100644 index d6e86d78..00000000 --- a/root-watcher/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "zerotier-root-watcher", - "version": "1.0.0", - "description": "Simple background service to watch a cluster of roots and record peer info into a database", - "main": "zerotier-root-watcher.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "ZeroTier, Inc. ", - "license": "GPL-3.0", - "dependencies": { - "async": "^2.3.0", - "pg": "^6.1.5", - "zlib": "^1.0.5" - } -} diff --git a/root-watcher/schema.sql b/root-watcher/schema.sql deleted file mode 100644 index ade0fa3e..00000000 --- a/root-watcher/schema.sql +++ /dev/null @@ -1,20 +0,0 @@ -/* Schema for ZeroTier root watcher log database */ - -CREATE TABLE "Peer" -( - "ztAddress" BIGINT NOT NULL, - "timestamp" BIGINT NOT NULL, - "versionMajor" INTEGER NOT NULL, - "versionMinor" INTEGER NOT NULL, - "versionRev" INTEGER NOT NULL, - "rootId" INTEGER NOT NULL, - "phyPort" INTEGER NOT NULL, - "phyLinkQuality" REAL NOT NULL, - "phyLastReceive" BIGINT NOT NULL, - "phyAddress" INET NOT NULL -); - -CREATE INDEX "Peer_ztAddress" ON "Peer" ("ztAddress"); -CREATE INDEX "Peer_timestamp" ON "Peer" ("timestamp"); -CREATE INDEX "Peer_rootId" ON "Peer" ("rootId"); -CREATE INDEX "Peer_phyAddress" ON "Peer" ("phyAddress"); diff --git a/root-watcher/zerotier-root-watcher.js b/root-watcher/zerotier-root-watcher.js deleted file mode 100644 index d4607fc2..00000000 --- a/root-watcher/zerotier-root-watcher.js +++ /dev/null @@ -1,235 +0,0 @@ -'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 0)) { - let bestPath = null; - for(let i=0;i 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); -- cgit v1.2.3