From fca00ad10533620e66931ba5fee4012bd649eaa5 Mon Sep 17 00:00:00 2001 From: hayden7913 Date: Sat, 7 Dec 2019 14:34:20 -0800 Subject: [PATCH 1/4] refactor getMonitorUpdates to calculate updates from most recent mongo logs instead of memcache --- src/app.js | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/app.js b/src/app.js index 96f0348..f6a4745 100644 --- a/src/app.js +++ b/src/app.js @@ -56,11 +56,44 @@ function MonitorApp ({ } async function getMonitorUpdates() { - return await Promise.all(exitnodeIPs.map(async ip => { - let d = await getCacheData(`alive-${ip}`); - d = d ? d : { error: util.noCheckInMessage(ip) }; - d.ip = ip; - return d; + // Report a count of unique nodes and gateways connected to exitnodes + // If an exitnode has not checked in in the last two minutes, report an error message + const getExitnodeStats = function (exitnodeIP) { + return db.collection('routeLog') + .aggregate([ + { + // Get route logs posted in the last two minutes + $match: { + exitnodeIP, + timestamp: { $gt: new Date(new Date() - 2 * 60 * 1000) }, + }, + }, + { $sort: { timestamp: -1 } }, + // Select only the most recent log + { $limit: 1 } , + { $unwind: '$routes' }, + { + $group: { + _id: '$exitnodeIP', + nodeIPs: { $addToSet: '$routes.nodeIP' }, + gatewayIPs: { $addToSet: '$routes.gatewayIP' } + } + }, + { + $project: { + ip: exitnodeIP, + numberOfRoutes: { $size: '$nodeIPs' }, + numberOfGateways: { $size: '$gatewayIPs' }, + _id: false, + } + } + ]) + .toArray(); + }; + + return Promise.all(exitnodeIPs.map(async ip => { + const [stats] = await getExitnodeStats(ip); + return stats || { error: util.noCheckInMessage(ip) }; })); } From fa664aa93641c0d2edfabe1fcf8f65bbd3ac0dfc Mon Sep 17 00:00:00 2001 From: hayden7913 Date: Sat, 7 Dec 2019 16:28:26 -0800 Subject: [PATCH 2/4] remove monitor.sh and all related code --- README.md | 2 +- monitor.sh | 14 -------------- spec/httpSpec.js | 28 ++-------------------------- src/app.js | 17 ----------------- tools/simulate-activity.js | 34 ---------------------------------- 5 files changed, 3 insertions(+), 92 deletions(-) delete mode 100644 monitor.sh diff --git a/README.md b/README.md index c5fda2f..d7aa458 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ To help monitor network health. -Exit nodes periodically execute [monitor.sh](./monitor.sh) and [post-routing-table.sh](./post-routing-table.sh), hitting two API endpoints at https://peoplesopen.herokuapp.com/api/v0/. This relays information about the number of active routes, active gateways, and the full contents of the exit node's routing table. If an exit node hasn't checked in in more than 2 minutes, it is assumed to be down. +Exit nodes periodically execute [post-routing-table.sh](./post-routing-table.sh), hitting an API endpoint at https://peoplesopen.herokuapp.com/api/v0/nodes. This relays information about the number of active routes, active gateways, and the full contents of the exit node's routing table. If an exit node hasn't checked in in more than 2 minutes, it is assumed to be down. Uses memcache/memjs, and mongo db. Deployed to heroku. diff --git a/monitor.sh b/monitor.sh deleted file mode 100644 index c8869da..0000000 --- a/monitor.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# Exitnodes run this script periodically to publish -# statistics about connected nodes to the monitor server. - -# NOTE: This was useful before we started using the -# post-routing-table.sh script. It's probably redundant now. - -ROUTES=$(ip route | grep via | grep -v default | awk '{print $1 "\t" $3 }' | sort | uniq | wc -l) -GATEWAYS=$(ip route | grep via | grep -v default | awk '{print $3 }' | sort | uniq | wc -l) - -URL="https://monitor.peoplesopen.net/api/v0/monitor" - -curl -H "Content-Type: application/json" -X POST -d "{ \"numberOfRoutes\": $ROUTES, \"numberOfGateways\": $GATEWAYS }" $URL > /dev/null diff --git a/spec/httpSpec.js b/spec/httpSpec.js index c578b93..32f928b 100644 --- a/spec/httpSpec.js +++ b/spec/httpSpec.js @@ -1,7 +1,6 @@ const supertest = require('supertest'); -const MonitorApp = require('../src/app').MonitorApp +const MonitorApp = require('../src/app').MonitorApp; const getDBConnection = require('../src/db'); -const exitnodeIPs = require('../src/exitnodeIPs'); describe('HTTP tests', function() { @@ -31,27 +30,4 @@ describe('HTTP tests', function() { .expect(404, done); }); }); - - describe('POST /api/v0/monitor', function () { - it('error on non-exit node', function (done) { - supertest(MonitorApp({db})) - .post('/api/v0/monitor') - .accept('Content-Type', 'application/json') - .expect({ "error": "You aren\'t an exit node."}) - .expect(403, done); - }); - }); - - describe('POST /api/v0/monitor', function () { - it('error on malformed exit node', function (done) { - let app = MonitorApp({db}); - supertest(app) - .post('/api/v0/monitor') - .set('x-forwarded-for', exitnodeIPs[0]) - .accept('Content-Type', 'application/json') - .expect({ error: 'Bad request' }) - .expect(400, done); - }); - }); - -}); \ No newline at end of file +}); diff --git a/src/app.js b/src/app.js index f6a4745..ed080a1 100644 --- a/src/app.js +++ b/src/app.js @@ -152,23 +152,6 @@ function MonitorApp ({ res.json(await getMonitorUpdates()); })); - app.post('/api/v0/monitor', ipAuthMiddleware(exitnodeIPs), function(req, res) { - let ip = util.getRequestIP(req); - const key = `alive-${ip}`; - let handleErr = function(err) { - if (err) { - return res.status(502).json({ error: 'Could not set key, because of [' + err + '].' }); - } - return res.json({ message: 'Set attached values', result: processed }); - }; - const processed = util.processUpdate(req); - if (processed.error) { - return res.status(400).json(processed); - } else { - mjs.set(key, JSON.stringify(processed), {expires: 120}, handleErr); - } - }); - app.get('/api/v0/nodes', asyncMiddleware(async function(req, res) { let data = await getRoutingTableUpdates(); res.json(data); diff --git a/tools/simulate-activity.js b/tools/simulate-activity.js index 972b0da..19e123a 100644 --- a/tools/simulate-activity.js +++ b/tools/simulate-activity.js @@ -17,8 +17,6 @@ if (require.main === module) { async function main() { await Promise.all([ - simulateMonitorRequest({"numberOfGateways": 15, "numberOfRoutes": 21}, exitnodeIPs[0]), - simulateMonitorRequest({"numberOfGateways": 15, "numberOfRoutes": 29}, exitnodeIPs[1]), simulateRoutingTableRequest('134.0.0.0/26,134.0.0.1|132.0.0.0/26,132.0.0.1|131.0.0.0/26,131.0.0.1|129.0.0.0/26,129.0.0.1|127.0.0.0/26,127.0.0.1|128.0.0.0/26,128.0.0.1|127.0.0.10/26,127.0.0.1|', exitnodeIPs[0]), simulateRoutingTableRequest('134.0.0.0/26,134.0.0.1|132.0.0.0/26,132.0.0.1|131.0.0.0/26,131.0.0.1|125.0.0.0/26,124.0.0.1|121.0.0.0/26,121.0.0.1|123.0.0.0/26,123.0.0.1', exitnodeIPs[1]), simulateRoutingTableRequest('', exitnodeIPs[2]) // simulate an empty POST @@ -27,8 +25,6 @@ async function main() { setTimeout(async () => { // omit one routing table request to simulate a delay between posts await Promise.all([ - simulateMonitorRequest({"numberOfGateways": 15, "numberOfRoutes": 21}, exitnodeIPs[0]), - simulateMonitorRequest({"numberOfGateways": 15, "numberOfRoutes": 29}, exitnodeIPs[1]), simulateRoutingTableRequest('134.0.0.0/26,134.0.0.1|132.0.0.0/26,132.0.0.1|131.0.0.0/26,131.0.0.1|129.0.0.0/26,129.0.0.1|127.0.0.0/26,127.0.0.1|128.0.0.0/26,128.0.0.1|127.0.0.10/26,127.0.0.1|', exitnodeIPs[0]), simulateRoutingTableRequest('', exitnodeIPs[2]) // simulate an empty POST ]) @@ -37,36 +33,6 @@ async function main() { }) } -async function simulateMonitorRequest(data, exitnodeIP) { - const response = await monitorRequest(data, exitnodeIP) - switch (response.statusCode) { - case 200: - console.info(`Successful /monitor request`) - break; - default: - throw new Error(`Unexpected statusCode from simulated monitor request: ${response.statusCode}`) - } -} - -async function monitorRequest(data, exitnodeIP) { - return new Promise((resolve, reject) => { - var options = { - url: `http://localhost:${process.env.PORT}/api/v0/monitor`, - // the server authenticates the request by looking at - // its source ip or x-forwarded-for header - headers: { - 'x-forwarded-for': exitnodeIP, - 'content-type': 'application/json' - }, - body: (typeof data === 'object') ? JSON.stringify(data) : data - }; - request.post(options, (error, response) => { - if (error) return reject(error) - return resolve(response) - }) - }) -} - async function simulateRoutingTableRequest(body, exitnodeIP) { const response = await routingTableRequest(body, exitnodeIP) switch (response.statusCode) { From 348d8abbd3540c47e3e2bbadb4dfa88ededbaf3d Mon Sep 17 00:00:00 2001 From: hayden7913 Date: Tue, 24 Dec 2019 15:52:04 -0800 Subject: [PATCH 3/4] replace aggregation with regualr mongo query in getMonitorUpdates; rename getMonitorUpdates -> countActiveRoutesByExitnodes refactor getMonitorUpdates; rename to countActiveRoutesByExitnodes --- src/app.js | 55 +++++++++++++++++++----------------------------------- 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/src/app.js b/src/app.js index ed080a1..7ae42f4 100644 --- a/src/app.js +++ b/src/app.js @@ -55,45 +55,28 @@ function MonitorApp ({ return JSON.parse(value); } - async function getMonitorUpdates() { + function countActiveRoutesByExitnode() { // Report a count of unique nodes and gateways connected to exitnodes // If an exitnode has not checked in in the last two minutes, report an error message - const getExitnodeStats = function (exitnodeIP) { - return db.collection('routeLog') - .aggregate([ - { - // Get route logs posted in the last two minutes - $match: { - exitnodeIP, - timestamp: { $gt: new Date(new Date() - 2 * 60 * 1000) }, - }, - }, - { $sort: { timestamp: -1 } }, - // Select only the most recent log - { $limit: 1 } , - { $unwind: '$routes' }, - { - $group: { - _id: '$exitnodeIP', - nodeIPs: { $addToSet: '$routes.nodeIP' }, - gatewayIPs: { $addToSet: '$routes.gatewayIP' } - } - }, - { - $project: { - ip: exitnodeIP, - numberOfRoutes: { $size: '$nodeIPs' }, - numberOfGateways: { $size: '$gatewayIPs' }, - _id: false, - } - } - ]) + return Promise.all(exitnodeIPs.map(async ip => { + const twoMinutesAgo = new Date(new Date() - 2 * 60 * 1000); + const [mostRecentLog] = await db.collection('routeLog') + .find({ + exitnodeIP: ip, + timestamp: { $gt: twoMinutesAgo } + }) + .sort({ timestamp: -1 }) + .limit(1) .toArray(); - }; - return Promise.all(exitnodeIPs.map(async ip => { - const [stats] = await getExitnodeStats(ip); - return stats || { error: util.noCheckInMessage(ip) }; + if (!mostRecentLog) + return { error: util.noCheckInMessage(ip) }; + + return { + exitnodeIP: ip, + numberOfRoutes: _.unique(mostRecentLog.routes.map(r => r.nodeIP)).length, + numberOfGateways: _.unique(mostRecentLog.routes.map(r => r.gatewayIP)).length + }; })); } @@ -230,7 +213,7 @@ function MonitorApp ({ mjs.set(key, JSON.stringify(newRoutes), {}, handleErr); })); - + app.get('/api/v0/numNodesTimeseries', asyncMiddleware(async function(req, res) { let now = new Date(); let yesterday = new Date(now - 1000 * 60 * 60 * 24); From 78d8a095bf1e8e2d943dbd7e21e7ee71f68624a9 Mon Sep 17 00:00:00 2001 From: hayden7913 Date: Sat, 28 Dec 2019 13:49:26 -0800 Subject: [PATCH 4/4] rename updates -> routeCounts --- src/app.js | 8 ++++---- views/index.jade | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app.js b/src/app.js index 7ae42f4..cd7759b 100644 --- a/src/app.js +++ b/src/app.js @@ -103,8 +103,8 @@ function MonitorApp ({ // Home Page app.get('/', asyncMiddleware(async function(req, res, next) { - let updates = await getMonitorUpdates(); - let exitnodes = await getRoutingTableUpdates(); + const routeCounts = await countActiveRoutesByExitnode(); + const exitnodes = await getRoutingTableUpdates(); exitnodes.forEach(exitnode => { if (exitnode.routingTable) { @@ -121,7 +121,7 @@ function MonitorApp ({ }); res.render('index', { - updates: updates, + routeCounts, nodes: exitnodes, timeAgo: util.timeAgo }); @@ -132,7 +132,7 @@ function MonitorApp ({ // app.get('/api/v0/monitor', asyncMiddleware(async function(req, res, next) { - res.json(await getMonitorUpdates()); + res.json(await countActiveRoutesByExitnode()); })); app.get('/api/v0/nodes', asyncMiddleware(async function(req, res) { diff --git a/views/index.jade b/views/index.jade index 1cb5661..5d4b679 100644 --- a/views/index.jade +++ b/views/index.jade @@ -6,11 +6,11 @@ block content p Please help make this page better at a(href='https://github.com/sudomesh/monitor') https://github.com/sudomesh/monitor. - each update in updates - if update.error - p.monitor-warning #{update.error} + each count in routeCounts + if count.error + p.monitor-warning #{count.error} else - p #{update.ip} is connecting [#{update.numberOfRoutes}] nodes via [#{update.numberOfGateways}] gateways. + p #{count.exitnodeIP} is connecting [#{count.numberOfRoutes}] nodes via [#{count.numberOfGateways}] gateways. h3="node history" p A plot of the number of nodes connected to our exitnodes in the last 24 hours.