From c1abc45a28d457b5c3dadb124b2fce56f96b5114 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Sat, 4 Jan 2014 20:14:30 +0100 Subject: [PATCH] Added node.js tools for administration of cjdns --- contrib/nodejs/tools/cexec.js | 28 +++ contrib/nodejs/tools/dumptable.js | 38 ++++ contrib/nodejs/tools/getLinks.js | 241 ++++++++++++++++++++++++ contrib/nodejs/tools/lib/publicToIp6.js | 75 ++++++++ contrib/nodejs/tools/peerStats.js | 45 +++++ 5 files changed, 427 insertions(+) create mode 100755 contrib/nodejs/tools/cexec.js create mode 100755 contrib/nodejs/tools/dumptable.js create mode 100755 contrib/nodejs/tools/getLinks.js create mode 100644 contrib/nodejs/tools/lib/publicToIp6.js create mode 100755 contrib/nodejs/tools/peerStats.js diff --git a/contrib/nodejs/tools/cexec.js b/contrib/nodejs/tools/cexec.js new file mode 100755 index 000000000..ff7a1140c --- /dev/null +++ b/contrib/nodejs/tools/cexec.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node +/* vim: set expandtab ts=4 sw=4: */ +/* + * You may redistribute this program and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var Cjdns = require('../cjdnsadmin/cjdnsadmin'); + +Cjdns.connectWithAdminInfo(function (cjdns) { + var code = process.argv[process.argv.length-1].replace(/\).*$/, ',cb);'); + code = code.replace('(,cb);', '(cb);'); + var f = new Function('x', 'cb', 'x.' + code); + f(cjdns, function (err, ret) { + if (err) { throw err; } + console.log(JSON.stringify(ret, null, ' ')); + cjdns.disconnect(); + }); +}); diff --git a/contrib/nodejs/tools/dumptable.js b/contrib/nodejs/tools/dumptable.js new file mode 100755 index 000000000..3a64a6fc9 --- /dev/null +++ b/contrib/nodejs/tools/dumptable.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node +/* vim: set expandtab ts=4 sw=4: */ +/* + * You may redistribute this program and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +var Cjdns = require('../cjdnsadmin/cjdnsadmin'); + +Cjdns.connectWithAdminInfo(function (cjdns) { + + var again = function (i) { + cjdns.NodeStore_dumpTable(i, function (err, table) { + if (err) { throw err; } + var j; + for (j = 0; j < table.routingTable.length; j++) { + var r = table.routingTable[j]; + console.log(r['ip'] + ' ' + r['path'] + ' ' + r['link'] + ' ' + r['version']); + } + if (j) { + again(i+1); + } else { + console.log(i + ' nodes'); + cjdns.disconnect(); + } + }); + }; + again(0); + +}); diff --git a/contrib/nodejs/tools/getLinks.js b/contrib/nodejs/tools/getLinks.js new file mode 100755 index 000000000..2e0e6bd71 --- /dev/null +++ b/contrib/nodejs/tools/getLinks.js @@ -0,0 +1,241 @@ +#!/usr/bin/env node +/* vim: set expandtab ts=4 sw=4: */ +/* + * You may redistribute this program and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +var Cjdns = require('../cjdnsadmin/cjdnsadmin'); +var nThen = require('../cjdnsadmin/nthen'); +var PublicToIp6 = require('./lib/publicToIp6'); + +var NodeStore_getNode = function(cjdns, state, addr, callback) { + if (typeof(state['NodeStore_getNode' + addr]) !== 'undefined') { + callback(state['NodeStore_getNode' + addr]); + } else { + //console.log('NodeStore_getNode(' + addr + ');'); + cjdns.NodeStore_getNode(addr, function (err, ret) { + if (err) { throw err; } + state['NodeStore_getNode' + addr] = ret; + callback(ret); + }); + } +}; + +var NodeStore_getRouteLabel = function(cjdns, state, parentPath, childAddr, callback) { + var key = 'NodeStore_getRouteLabel' + parentPath + ', ' + childAddr; + if (typeof(state[key]) !== 'undefined') { + callback(state[key]); + } else { + //console.log('NodeStore_getRouteLabel(' + parentPath + ', ' + childAddr + ');'); + cjdns.NodeStore_getRouteLabel(parentPath, childAddr, function (err, ret) { + if (err) { throw err; } + state[key] = ret; + callback(ret); + }); + } +}; + +var NodeStore_getLink = function (cjdns, state, addr, num, callback) { + var key = 'NodeStore_getLink' + addr + ', ' + num; + if (typeof(state[key]) !== 'undefined') { + callback(state[key]); + } else { + //console.log('NodeStore_getLink(' + addr + ', ' + num + ');'); + cjdns.NodeStore_getLink(addr, num, function (err, ret) { + if (err) { throw err; } + state[key] = ret; + callback(ret); + }); + } +}; + +var getNode = function (cjdns, next, output, state, parentPath, ipsByReach, nodes, callback) { + + if (next.parent === next.child || nodes.indexOf(next.child) > -1) { process.nextTick(callback); return; } + nodes.push(next.child); + + var getNodeRet; + var path = undefined; + nThen(function (waitFor) { + + NodeStore_getNode(cjdns, state, next.child, waitFor(function (ret) { + //console.log('cjdns.NodeStore_getNode(' + next.child + '); --> ' + JSON.stringify(ret, null, ' ')); + getNodeRet = ret; + })); + + }).nThen(function (waitFor) { + + if (!parentPath) { + return; + } + + NodeStore_getRouteLabel(cjdns, state, parentPath, next.child, waitFor(function (ret) { + if (ret.error !== 'none') { + throw new Error('cjdns.NodeStore_getRouteLabel(' + parentPath + ', ' + next.child + + '); --> ' + JSON.stringify([ret, parents], null, ' ')); + } + if (ret.result !== 'ffff.ffff.ffff.ffff') { + path = ret.result; + } + })); + + }).nThen(function (waitFor) { + + //console.log(spaces + next.child + ' ' + next.cannonicalLabel + " -> " + path); + // if next.parent skips the bootstrap route + if (next.parent) { + var out = {}; + output.push(out); + output = out; + } + if (output.peers) { /* sanity check */ throw new Error(); } + output.addr = next.child; + output.cannonicalLabel = next.cannonicalLabel; + output.fullPath = path; + output.peers = []; + + if (!path) { return; } + + var links = []; + nThen(function (waitFor) { + + for (var i = 0; i < getNodeRet.result.linkCount; i++) { + NodeStore_getLink(cjdns, state, next.child, i, waitFor(function (ret) { + links.push(ret); + })); + } + + }).nThen(function (waitFor) { + + //console.log(JSON.stringify(links, null, ' ')); + links.sort(function (a,b) { + return (ipsByReach.indexOf(a.result.child) < ipsByReach.indexOf(b.result.child)) ? + -1 : 1; + }); + //console.log(JSON.stringify(links, null, ' ')); + //console.log(JSON.stringify(ipsByReach, null, ' ')); + for (var i = 0; i < links.length; i++) { + getNode(cjdns, links[i].result, output.peers, state, path, ipsByReach, nodes, waitFor()); + } + + }).nThen(waitFor()); + + }).nThen(function (waitFor) { + + callback(); + + }); +}; + +var dumpOldTable = function (cjdns, callback) { + var output = []; + var again = function (i) { + cjdns.NodeStore_dumpTable(i, function (err, table) { + if (err) { throw err; } + var j; + for (j = 0; j < table.routingTable.length; j++) { + var r = table.routingTable[j]; + output.push(r); + } + if (j) { + again(i+1); + } else { + callback(output); + } + }); + }; + again(0); +}; + +var ipsByReachDesc = function (cjdns, callback) { + dumpOldTable(cjdns, function (oldTable) { + + oldTable.sort(function (a, b) { + if (a.ip !== b.ip) { return (a.ip > b.ip) ? 1 : -1; } + if (a.link !== b.link) { return (Number(a.link) < Number(b.link)) ? 1 : -1; } + if (a.path !== b.path) { return (a.path > b.path) ? 1 : -1; } + throw new Error("dupe entry"); + }); + var bestReaches = []; + var last; + for (var i = 0; i < oldTable.length; i++) { + var r = oldTable[i]; + if (last !== r.ip) { + bestReaches.push({ip:r.ip, link:r.link}); + last = r.ip; + } + } + bestReaches.sort(function (a, b) { return (a.link > b.link) ? 1 : -1; }); + var out = []; + for (var i = 0; i < bestReaches.length; i++) { + out.push(bestReaches[i].ip); + } + callback(out); + //bestReaches.forEach(function (node) { console.log(node.ip + ' ' + node.link); }); + }); +}; + +var getTree = function (cjdns, callback) { + ipsByReachDesc(cjdns, function (ipsByReach) { + + cjdns.NodeStore_getNode(undefined, function (err, ret) { + if (err) { throw err; } + var myIp6 = PublicToIp6.convert(ret['result']['key']); + var output = {}; + var selfRoute = '0000.0000.0000.0001'; + var initialNode = { child: myIp6, cannonicalLabel: selfRoute }; + getNode(cjdns, initialNode, output, {}, selfRoute, ipsByReach, [], function () { + callback(output); + }); + }); + }); +}; + +var printTree = function (cjdns, tree, callback) { + var pt = function (tree, spaces, callback) { + var nt = nThen(function (waitFor) { + process.stdout.write(spaces + tree.addr + ' ' + tree.cannonicalLabel + ' --> ' + tree.fullPath); + if (tree.fullPath === '0000.0000.0000.0001') { return; } + cjdns.RouterModule_pingNode(tree.fullPath, waitFor(function (err, ret) { + if (err) { throw err; } + var resp = (ret.result !== 'pong') ? "[" + ret.error + "]" : (ret.ms + 'ms.'); + process.stdout.write(' rp:' + resp); + })); + }).nThen(function (waitFor) { + cjdns.SwitchPinger_ping(tree.fullPath, waitFor(function (err, ret) { + if (err) { throw err; } + var resp = (ret.result !== 'pong') ? ret.error : (ret.ms + 'ms.'); + process.stdout.write(' sp:' + resp + '\n'); + })); + }).nThen; + + tree.peers.forEach(function (peer) { + nt = nt(function (waitFor) { + pt(peer, ' ' + spaces, waitFor()); + }).nThen; + }); + + nt(callback); + }; + pt(tree, '', callback); +}; + +Cjdns.connectWithAdminInfo(function (cjdns) { + + getTree(cjdns, function (output) { + //console.log(JSON.stringify(output, null, ' ')); + printTree(cjdns, output, function() { + cjdns.disconnect(); + }); + }); + +}); diff --git a/contrib/nodejs/tools/lib/publicToIp6.js b/contrib/nodejs/tools/lib/publicToIp6.js new file mode 100644 index 000000000..5cdaac9f3 --- /dev/null +++ b/contrib/nodejs/tools/lib/publicToIp6.js @@ -0,0 +1,75 @@ +/* vim: set expandtab ts=4 sw=4: */ +/* + * You may redistribute this program and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var Crypto = require('crypto'); + +var numForAscii = [ + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,99,99,99,99,99,99, + 99,99,10,11,12,99,13,14,15,99,16,17,18,19,20,99, + 21,22,23,24,25,26,27,28,29,30,31,99,99,99,99,99, + 99,99,10,11,12,99,13,14,15,99,16,17,18,19,20,99, + 21,22,23,24,25,26,27,28,29,30,31,99,99,99,99,99, +]; + +// see util/Base32.h +var Base32_decode = function (input) { + var output = []; + var outputIndex = 0; + var inputIndex = 0; + var nextByte = 0; + var bits = 0; + + while (inputIndex < input.length) { + var o = input.charCodeAt(inputIndex); + if (o & 0x80) { throw new Error(); } + var b = numForAscii[o]; + inputIndex++; + if (b > 31) { throw new Error("bad character " + input[inputIndex] + " in " + input); } + + nextByte |= (b << bits); + bits += 5; + + if (bits >= 8) { + output[outputIndex] = nextByte & 0xff; + outputIndex++; + bits -= 8; + nextByte >>= 8; + } + } + + if (bits >= 5 || nextByte) { + throw new Error("bits is " + bits + " and nextByte is " + nextByte); + } + + return new Buffer(output); +}; + +var convert = module.exports.convert = function (pubKey) { + if (pubKey.substring(pubKey.length-2) !== ".k") { throw new Error("key does not end with .k"); } + keyBytes = Base32_decode(pubKey.substring(0, pubKey.length-2)); + var hashOneBuff = new Buffer(Crypto.createHash('sha512').update(keyBytes).digest('hex'), 'hex'); + var hashTwo = Crypto.createHash('sha512').update(hashOneBuff).digest('hex'); + var first16 = hashTwo.substring(0,32); + var out = []; + for (var i = 0; i < 8; i++) { + out.push(first16.substring(i*4, i*4+4)); + } + return out.join(':'); +}; + +//console.log(convert('rjndc8rvg194ddf2j5v679cfjcpmsmhv8p022q3lvpym21cqwyh0.k')); diff --git a/contrib/nodejs/tools/peerStats.js b/contrib/nodejs/tools/peerStats.js new file mode 100755 index 000000000..1b412ad91 --- /dev/null +++ b/contrib/nodejs/tools/peerStats.js @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/* vim: set expandtab ts=4 sw=4: */ +/* + * You may redistribute this program and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +var Cjdns = require('../cjdnsadmin/cjdnsadmin'); +var PublicToIp6 = require('./lib/publicToIp6'); + +Cjdns.connectWithAdminInfo(function (cjdns) { + + var again = function (i) { + cjdns.InterfaceController_peerStats(i, function (err, ret) { + if (err) { throw err; } + ret.peers.forEach(function (peer) { + p = (PublicToIp6.convert(peer['publicKey']) + '\t' + peer['switchLabel'] + + '\tin ' + peer['bytesIn'] + '\tout ' + peer['bytesOut'] + '\t' + + peer['state'] + + '\tdup ' + peer['duplicates'] + + ' los ' + peer['lostPackets'] + + ' oor ' + peer['receivedOutOfRange']); + if (typeof(peer.user) === 'string') { + p += ' "' + peer['user'] + '"'; + } + console.log(p); + }); + if (typeof(ret.more) !== 'undefined') { + again(i+1); + } else { + cjdns.disconnect(); + } + }); + }; + again(0); + +});