From aaf26b9ddaa5c792324677af7569137f4c2c94a4 Mon Sep 17 00:00:00 2001 From: thefinn93 Date: Mon, 6 Feb 2012 17:55:06 -0800 Subject: [PATCH 01/12] Download node names from Mikey's page (http://[fc5d:baa5:61fc:6ffd:9554:67f0:e290:7535]/nodes/list.json) instead of ircerr's. --- cjdnsmap.py | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/cjdnsmap.py b/cjdnsmap.py index f6a8ee1..116e4c0 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -24,6 +24,7 @@ import httplib2 import sys import math +import json ################################################# # code from http://effbot.org/zone/bencode.htm @@ -165,8 +166,8 @@ def find_parent(self, routes): else: filename = 'map.png' -# retrieve the node names from the page maintained by ircerr -page = 'http://[fc38:4c2c:1a8f:3981:f2e7:c2b9:6870:6e84]/ipv6-cjdnet.data.txt' +# retrieve the node names from the page maintained by Mikey +page = 'http://[fc5d:baa5:61fc:6ffd:9554:67f0:e290:7535]/nodes/list.json' print('Downloading the list of node names from {0} ...'.format(page)) names = {} h = httplib2.Http(".cache") @@ -174,26 +175,13 @@ def find_parent(self, routes): existing_names = set() doubles = set() -nameip = [] -for l in content.split('\n'): - l = l.strip() - if not l or l.startswith('#'): - continue - d = l.split(' ') - if len(d) < 2: - continue # use the standard last two bytes - ip = d[0] - name = d[1] - nameip.append((name,ip)) - if name in existing_names: - doubles.add(name) - existing_names.add(name) - -for name,ip in nameip: - if not name in doubles: - names[ip]=name +nameip = json.loads(content)['nodes'] + +for node in nameip: + if not node['name'] in doubles: + names[node['ip']]=node['name'] else: - names[ip]=name + ' ' + ip.split(':')[-1] + names[node['ip']]=node['name'] + ' ' + ip.split(':')[-1] # retrieve the routing data from the admin interface # FIXME: read these from the commandline or even from the config @@ -209,7 +197,6 @@ def find_parent(self, routes): data += r if not r or r.endswith('....e\n'): break -s.shutdown(socket.SHUT_RDWR) s.close() data = data.strip() bencode = decode(data) From fbe6a598d0715ae1c99c21106a680b4af02ee02c Mon Sep 17 00:00:00 2001 From: thefinn93 Date: Mon, 6 Feb 2012 18:28:52 -0800 Subject: [PATCH 02/12] Readded code to properly close the socket. I accidently removed it. --- cjdnsmap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cjdnsmap.py b/cjdnsmap.py index 116e4c0..6f7c45f 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -197,6 +197,7 @@ def find_parent(self, routes): data += r if not r or r.endswith('....e\n'): break +s.shutdown(socket.SHUT_RDWR) s.close() data = data.strip() bencode = decode(data) From 17dc6c80ba1c52f03bac845655fbfa5c957b3885 Mon Sep 17 00:00:00 2001 From: thefinn93 Date: Mon, 6 Feb 2012 19:01:34 -0800 Subject: [PATCH 03/12] Corrected the link state modifier to 5366870.0 --- cjdnsmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cjdnsmap.py b/cjdnsmap.py index 6f7c45f..d5593f0 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -150,7 +150,7 @@ def __init__(self, ip, name, path, link): route = route.replace('y','0001') route = route.replace('x','0000') self.route = route[::-1].rstrip('0')[:-1] - self.quality = link / 536870.0 # LINK_STATE_MULTIPLIER + self.quality = link / 5366870.0 # LINK_STATE_MULTIPLIER def find_parent(self, routes): parents = [(len(other.route),other) for other in routes if self.route.startswith(other.route) and self != other] From db3b91991963eae895ff3a1483b0f2ad89786fd8 Mon Sep 17 00:00:00 2001 From: thefinn93 Date: Sun, 12 Feb 2012 16:26:30 -0800 Subject: [PATCH 04/12] Updated to work with newer cjdns admin interface --- cjdnsmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cjdnsmap.py b/cjdnsmap.py index d5593f0..d9d2529 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -195,7 +195,7 @@ def find_parent(self, routes): while True: r = s.recv(1024) data += r - if not r or r.endswith('....e\n'): + if not r or len(data) % 1024: break s.shutdown(socket.SHUT_RDWR) s.close() From 92c8e39d268c7d34553a298a1804f45c4db07be2 Mon Sep 17 00:00:00 2001 From: finn Date: Sat, 21 Apr 2012 16:48:34 -0700 Subject: [PATCH 05/12] Implimented the official cjdns python API and added the option to get IP, port and password from the command line --- bencode.py | 309 ++++++++++++++++++++++++++++++++++++++++++++++++++++ cjdns.py | 118 ++++++++++++++++++++ cjdnsmap.py | 135 ++++++++++++++++++----- 3 files changed, 534 insertions(+), 28 deletions(-) create mode 100644 bencode.py create mode 100644 cjdns.py diff --git a/bencode.py b/bencode.py new file mode 100644 index 0000000..da4d035 --- /dev/null +++ b/bencode.py @@ -0,0 +1,309 @@ +# Written by Petru Paler +# see LICENSE.txt for license information +# http://cvs.degreez.net/viewcvs.cgi/*checkout*/bittornado/LICENSE.txt?rev=1.2 +# "the MIT license" + +def decode_int(x, f): + f += 1 + newf = x.index('e', f) + try: + n = int(x[f:newf]) + except (OverflowError, ValueError): + n = long(x[f:newf]) + if x[f] == '-': + if x[f + 1] == '0': + raise ValueError + elif x[f] == '0' and newf != f+1: + raise ValueError + return (n, newf+1) + +def decode_string(x, f): + colon = x.index(':', f) + try: + n = int(x[f:colon]) + except (OverflowError, ValueError): + n = long(x[f:colon]) + if x[f] == '0' and colon != f+1: + raise ValueError + colon += 1 + return (x[colon:colon+n], colon+n) + +def decode_list(x, f): + r, f = [], f+1 + while x[f] != 'e': + v, f = decode_func[x[f]](x, f) + r.append(v) + return (r, f + 1) + +def decode_dict(x, f): + r, f = {}, f+1 + lastkey = None + while x[f] != 'e': + k, f = decode_string(x, f) + if lastkey >= k: + raise ValueError + lastkey = k + r[k], f = decode_func[x[f]](x, f) + return (r, f + 1) + +decode_func = {} +decode_func['l'] = decode_list +decode_func['d'] = decode_dict +decode_func['i'] = decode_int +decode_func['0'] = decode_string +decode_func['1'] = decode_string +decode_func['2'] = decode_string +decode_func['3'] = decode_string +decode_func['4'] = decode_string +decode_func['5'] = decode_string +decode_func['6'] = decode_string +decode_func['7'] = decode_string +decode_func['8'] = decode_string +decode_func['9'] = decode_string + +def bdecode(x): + try: + r, l = decode_func[x[0]](x, 0) + except (IndexError, KeyError): + raise ValueError + if l != len(x): + raise ValueError + return r + +def test_bdecode(): + try: + bdecode('0:0:') + assert 0 + except ValueError: + pass + try: + bdecode('ie') + assert 0 + except ValueError: + pass + try: + bdecode('i341foo382e') + assert 0 + except ValueError: + pass + assert bdecode('i4e') == 4L + assert bdecode('i0e') == 0L + assert bdecode('i123456789e') == 123456789L + assert bdecode('i-10e') == -10L + try: + bdecode('i-0e') + assert 0 + except ValueError: + pass + try: + bdecode('i123') + assert 0 + except ValueError: + pass + try: + bdecode('') + assert 0 + except ValueError: + pass + try: + bdecode('i6easd') + assert 0 + except ValueError: + pass + try: + bdecode('35208734823ljdahflajhdf') + assert 0 + except ValueError: + pass + try: + bdecode('2:abfdjslhfld') + assert 0 + except ValueError: + pass + assert bdecode('0:') == '' + assert bdecode('3:abc') == 'abc' + assert bdecode('10:1234567890') == '1234567890' + try: + bdecode('02:xy') + assert 0 + except ValueError: + pass + try: + bdecode('l') + assert 0 + except ValueError: + pass + assert bdecode('le') == [] + try: + bdecode('leanfdldjfh') + assert 0 + except ValueError: + pass + assert bdecode('l0:0:0:e') == ['', '', ''] + try: + bdecode('relwjhrlewjh') + assert 0 + except ValueError: + pass + assert bdecode('li1ei2ei3ee') == [1, 2, 3] + assert bdecode('l3:asd2:xye') == ['asd', 'xy'] + assert bdecode('ll5:Alice3:Bobeli2ei3eee') == [['Alice', 'Bob'], [2, 3]] + try: + bdecode('d') + assert 0 + except ValueError: + pass + try: + bdecode('defoobar') + assert 0 + except ValueError: + pass + assert bdecode('de') == {} + assert bdecode('d3:agei25e4:eyes4:bluee') == {'age': 25, 'eyes': 'blue'} + assert bdecode('d8:spam.mp3d6:author5:Alice6:lengthi100000eee') == {'spam.mp3': {'author': 'Alice', 'length': 100000}} + try: + bdecode('d3:fooe') + assert 0 + except ValueError: + pass + try: + bdecode('di1e0:e') + assert 0 + except ValueError: + pass + try: + bdecode('d1:b0:1:a0:e') + assert 0 + except ValueError: + pass + try: + bdecode('d1:a0:1:a0:e') + assert 0 + except ValueError: + pass + try: + bdecode('i03e') + assert 0 + except ValueError: + pass + try: + bdecode('l01:ae') + assert 0 + except ValueError: + pass + try: + bdecode('9999:x') + assert 0 + except ValueError: + pass + try: + bdecode('l0:') + assert 0 + except ValueError: + pass + try: + bdecode('d0:0:') + assert 0 + except ValueError: + pass + try: + bdecode('d0:') + assert 0 + except ValueError: + pass + try: + bdecode('00:') + assert 0 + except ValueError: + pass + try: + bdecode('l-3:e') + assert 0 + except ValueError: + pass + try: + bdecode('i-03e') + assert 0 + except ValueError: + pass + bdecode('d0:i3ee') + +from types import StringType, IntType, LongType, DictType, ListType, TupleType + +class Bencached(object): + __slots__ = ['bencoded'] + + def __init__(self, s): + self.bencoded = s + +def encode_bencached(x,r): + r.append(x.bencoded) + +def encode_int(x, r): + r.extend(('i', str(x), 'e')) + +def encode_string(x, r): + r.extend((str(len(x)), ':', x)) + +def encode_list(x, r): + r.append('l') + for i in x: + encode_func[type(i)](i, r) + r.append('e') + +def encode_dict(x,r): + r.append('d') + ilist = x.items() + ilist.sort() + for k, v in ilist: + r.extend((str(len(k)), ':', k)) + encode_func[type(v)](v, r) + r.append('e') + +encode_func = {} +encode_func[type(Bencached(0))] = encode_bencached +encode_func[IntType] = encode_int +encode_func[LongType] = encode_int +encode_func[StringType] = encode_string +encode_func[ListType] = encode_list +encode_func[TupleType] = encode_list +encode_func[DictType] = encode_dict + +try: + from types import BooleanType + encode_func[BooleanType] = encode_int +except ImportError: + pass + +def bencode(x): + r = [] + encode_func[type(x)](x, r) + return ''.join(r) + +def test_bencode(): + assert bencode(4) == 'i4e' + assert bencode(0) == 'i0e' + assert bencode(-10) == 'i-10e' + assert bencode(12345678901234567890L) == 'i12345678901234567890e' + assert bencode('') == '0:' + assert bencode('abc') == '3:abc' + assert bencode('1234567890') == '10:1234567890' + assert bencode([]) == 'le' + assert bencode([1, 2, 3]) == 'li1ei2ei3ee' + assert bencode([['Alice', 'Bob'], [2, 3]]) == 'll5:Alice3:Bobeli2ei3eee' + assert bencode({}) == 'de' + assert bencode({'age': 25, 'eyes': 'blue'}) == 'd3:agei25e4:eyes4:bluee' + assert bencode({'spam.mp3': {'author': 'Alice', 'length': 100000}}) == 'd8:spam.mp3d6:author5:Alice6:lengthi100000eee' + assert bencode(Bencached(bencode(3))) == 'i3e' + try: + bencode({1: 'foo'}) + except TypeError: + return + assert 0 + +try: + import psyco + psyco.bind(bdecode) + psyco.bind(bencode) +except ImportError: + pass diff --git a/cjdns.py b/cjdns.py new file mode 100644 index 0000000..6146031 --- /dev/null +++ b/cjdns.py @@ -0,0 +1,118 @@ +# 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 . +import sys; +import socket; +import hashlib; +from bencode import *; + +BUFFER_SIZE = 69632; + +def callfunc(cjdns, funcName, password, args): + sock = cjdns.socket; + sock.send('d1:q6:cookiee'); + data = sock.recv(BUFFER_SIZE); + benc = bdecode(data); + cookie = benc['cookie']; + req = { + 'q': 'auth', + 'aq': funcName, + 'hash': hashlib.sha256(password + cookie).hexdigest(), + 'cookie' : cookie, + 'args': args + }; + reqBenc = bencode(req); + req['hash'] = hashlib.sha256(reqBenc).hexdigest(); + reqBenc = bencode(req); + sock.send(reqBenc); + data = sock.recv(BUFFER_SIZE); + try: + return bdecode(data); + except ValueError: + print("Failed to parse:\n" + data); + print("Length: " + str(len(data))); + raise; + +def cjdns_connect(ipAddr, port, password): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); + sock.connect((ipAddr, port)); + + # Make sure it pongs. + sock.send('d1:q4:pinge'); + data = sock.recv(BUFFER_SIZE); + if (data != 'd1:q4:ponge'): + raise Exception("Looks like " + ipAddr + ":" + port + " is to a non-cjdns socket."); + + # Get the functions and make the object + sock.send('d1:q7:invalide'); + data = sock.recv(BUFFER_SIZE); + benc = bdecode(data); + argLists = {}; + cc = ("class Cjdns:\n" + + " def __init__(self, socket):\n" + + " self.socket = socket;\n" + + " def disconnect(self):\n" + + " self.socket.close();\n"); + for func in benc['availableFunctions']: + argList = []; + argLists[func] = argList; + funcDict = benc['availableFunctions'][func]; + cc += " def " + func + "(self"; + + # If the arg is required, put it first, + # otherwise put it after and use a default + # value of a type which will be ignored by the core. + for arg in funcDict: + if (funcDict[arg]['required'] == 1): + argList.append(arg); + cc += ", " + arg; + for arg in funcDict: + argDict = funcDict[arg]; + if (argDict['required'] == 0): + argList.append(arg); + cc += ", " + arg + "="; + if (argDict['type'] == 'Int'): + cc += "''"; + else: + cc += "0"; + + cc += ("):\n" + + " args = {"); + for arg in argList: + cc += "\"" + arg + "\": " + arg + ", "; + cc += ("};\n" + + " return callfunc(self, \"" + func + "\", \"" + password + "\", args);\n"); + exec(cc); + + cjdns = Cjdns(sock); + + # Check our password. + ret = callfunc(cjdns, "ping", password, {}); + if ('error' in ret): + raise Exception("Connect failed, incorrect admin password?\n" + ret); + + cjdns.functions = ""; + nl = ""; + for func in benc['availableFunctions']: + funcDict = benc['availableFunctions'][func]; + cjdns.functions += nl + func + "("; + nl = "\n"; + sep = ""; + for arg in argLists[func]: + cjdns.functions += sep; + sep = ", "; + argDict = funcDict[arg]; + if (argDict['required'] == 1): + cjdns.functions += "required "; + cjdns.functions += argDict['type'] + " " + arg; + cjdns.functions += ")"; + + return cjdns; diff --git a/cjdnsmap.py b/cjdnsmap.py index d9d2529..e14aabd 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -18,13 +18,45 @@ # - Color nodes depending on the number of connections # -import pydot import re import socket -import httplib2 import sys import math import json +try: + import httplib2 +except: + print "Requires httplib2, try: " + print "sudo easy_install httplib2" + sys.exit() +try: + import pydot +except: + print "Requires pydot, try:" + print "sudo easy_install pydot" + sys.exit() +try: + from cjdns import cjdns_connect +except: + print "Requires cjdns python module. It should've been included" + print "with this program. Please ensure that it's in the path. It" + print "also comes with cjdns, check contrib/python/ in the cjdns source" + sys.exit() + +######## SETTINGS ############### +cjdadmin_ip = "127.0.0.1" # +cjdadmin_port = 11234 # +cjdadmin_pass = "insecure_pass" # +################################# + +if len(sys.argv) == 4: + cjdadmin_ip = sys.argv[1] + cjdadmin_port = sys.argv[2] + cjdadmin_pass = sys.argv[3] +elif len(sys.argv) != 1: + print "Usage is:" + print sys.argv[0] + " " + print "Or just specify it at the top of the python file" ################################################# # code from http://effbot.org/zone/bencode.htm @@ -125,6 +157,7 @@ def hsv_to_color(h,s,v): ################################################### +cjdns = cjdns_connect(cjdadmin_ip, cjdadmin_port, cjdadmin_pass) class route: def __init__(self, ip, name, path, link): self.ip = ip @@ -167,6 +200,7 @@ def find_parent(self, routes): filename = 'map.png' # retrieve the node names from the page maintained by Mikey +""" page = 'http://[fc5d:baa5:61fc:6ffd:9554:67f0:e290:7535]/nodes/list.json' print('Downloading the list of node names from {0} ...'.format(page)) names = {} @@ -182,37 +216,82 @@ def find_parent(self, routes): names[node['ip']]=node['name'] else: names[node['ip']]=node['name'] + ' ' + ip.split(':')[-1] +""" +page = 'http://ircerr.bt-chat.com/cjdns/ipv6-cjdnet.data.txt' +print('Downloading the list of node names from {0} ...'.format(page)) +names = {} +h = httplib2.Http(".cache") +r, content = h.request(page, "GET") + +existing_names = set() +doubles = set() +nameip = [] +for l in content.split('\n'): + l = l.strip() + if not l or l.startswith('#'): + continue + d = l.split(' ') + if len(d) < 2: + continue # use the standard last two bytes + ip = d[0] + name = d[1] + nameip.append((name,ip)) + if name in existing_names: + doubles.add(name) + existing_names.add(name) + +for name,ip in nameip: + if not name in doubles: + names[ip]=name + else: + names[ip]=name + ' ' + ip.split(':')[-1] # retrieve the routing data from the admin interface -# FIXME: read these from the commandline or even from the config -HOST = 'localhost' -PORT = 11234 -print('Retrieving the routing table from the admin interface at {0} port {1}'.format(HOST,PORT)) -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.connect((HOST, PORT)) -s.send('d1:q19:NodeStore_dumpTable4:txid4:....e') -data = '' +#HOST = 'localhost' +#PORT = 11234 +#print('Retrieving the routing table from the admin interface at {0} port {1}'.format(HOST,PORT)) +#s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +#s.connect((HOST, PORT)) +#s.send('d1:q19:NodeStore_dumpTable4:txid4:....e') +#data = '' +#while True: +# r = s.recv(1024) +# data += r +# if not r or len(data) % 1024: +# break +#s.shutdown(socket.SHUT_RDWR) +#s.close() +#data = data.strip() +#bencode = decode(data) + +#routes = [] +#for r in bencode['routingTable']: +# ip = r['ip'] +# path = r['path'] +# link = r['link'] +# if ip in names: +# name = names[ip] +# else: +# name = ip.split(':')[-1] +# r = route(ip,name,path,link) +# routes.append(r) +routes = []; +i = 0; while True: - r = s.recv(1024) - data += r - if not r or len(data) % 1024: + table = cjdns.NodeStore_dumpTable(i) + for r in table['routingTable']: + name = r['ip'].split(':')[-1] + if r['ip'] in names: + name = names[r['ip']] + routes.append(route(r['ip'],name,r['path'],r['link'])) +# for entry in routes: +# if (entry['link'] != 0): +# addresses[entry['ip']] = entry['path'] +# #print entry['ip'] + '@' + entry['path'] + ' - ' + str(entry['link']) + if not 'more' in table: break -s.shutdown(socket.SHUT_RDWR) -s.close() -data = data.strip() -bencode = decode(data) + i += 1 -routes = [] -for r in bencode['routingTable']: - ip = r['ip'] - path = r['path'] - link = r['link'] - if ip in names: - name = names[ip] - else: - name = ip.split(':')[-1] - r = route(ip,name,path,link) - routes.append(r) # sort the routes on quality tmp = [(r.quality,r) for r in routes] From fb9e52e95415e86c390e664a930aa5f79cd6f23c Mon Sep 17 00:00:00 2001 From: Finn Date: Sat, 21 Apr 2012 16:52:42 -0700 Subject: [PATCH 06/12] Pull node names from nodeinfo.hype --- cjdnsmap.py | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/cjdnsmap.py b/cjdnsmap.py index e14aabd..c676ee2 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -200,7 +200,6 @@ def find_parent(self, routes): filename = 'map.png' # retrieve the node names from the page maintained by Mikey -""" page = 'http://[fc5d:baa5:61fc:6ffd:9554:67f0:e290:7535]/nodes/list.json' print('Downloading the list of node names from {0} ...'.format(page)) names = {} @@ -216,6 +215,7 @@ def find_parent(self, routes): names[node['ip']]=node['name'] else: names[node['ip']]=node['name'] + ' ' + ip.split(':')[-1] + """ page = 'http://ircerr.bt-chat.com/cjdns/ipv6-cjdnet.data.txt' print('Downloading the list of node names from {0} ...'.format(page)) @@ -245,36 +245,8 @@ def find_parent(self, routes): names[ip]=name else: names[ip]=name + ' ' + ip.split(':')[-1] +""" -# retrieve the routing data from the admin interface -#HOST = 'localhost' -#PORT = 11234 -#print('Retrieving the routing table from the admin interface at {0} port {1}'.format(HOST,PORT)) -#s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -#s.connect((HOST, PORT)) -#s.send('d1:q19:NodeStore_dumpTable4:txid4:....e') -#data = '' -#while True: -# r = s.recv(1024) -# data += r -# if not r or len(data) % 1024: -# break -#s.shutdown(socket.SHUT_RDWR) -#s.close() -#data = data.strip() -#bencode = decode(data) - -#routes = [] -#for r in bencode['routingTable']: -# ip = r['ip'] -# path = r['path'] -# link = r['link'] -# if ip in names: -# name = names[ip] -# else: -# name = ip.split(':')[-1] -# r = route(ip,name,path,link) -# routes.append(r) routes = []; i = 0; while True: @@ -284,10 +256,6 @@ def find_parent(self, routes): if r['ip'] in names: name = names[r['ip']] routes.append(route(r['ip'],name,r['path'],r['link'])) -# for entry in routes: -# if (entry['link'] != 0): -# addresses[entry['ip']] = entry['path'] -# #print entry['ip'] + '@' + entry['path'] + ' - ' + str(entry['link']) if not 'more' in table: break i += 1 From 935bd167535465175421e2a977d9e3f9039766cb Mon Sep 17 00:00:00 2001 From: Finn Date: Sat, 21 Apr 2012 17:10:32 -0700 Subject: [PATCH 07/12] Took a hit from [waangals](https://github.com/waaghals/cjdnsmap/commit/2aba51240368b7fe159065dca6299dc1e9c5cc78#cjdnsmap.py) and made it output svg (optionally) and not overlap. --- cjdnsmap.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cjdnsmap.py b/cjdnsmap.py index c676ee2..da55e1e 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -44,18 +44,20 @@ sys.exit() ######## SETTINGS ############### -cjdadmin_ip = "127.0.0.1" # +cjdadmin_ip = "127.0.0.1" # cjdadmin_port = 11234 # cjdadmin_pass = "insecure_pass" # +filename = 'map.svg' # Picks format based on filename. If it ends in .svg it's an svg, otherwise it's a png ################################# -if len(sys.argv) == 4: +if len(sys.argv) == 5: cjdadmin_ip = sys.argv[1] cjdadmin_port = sys.argv[2] cjdadmin_pass = sys.argv[3] + filename = sys.argv[4] elif len(sys.argv) != 1: print "Usage is:" - print sys.argv[0] + " " + print sys.argv[0] + " " print "Or just specify it at the top of the python file" ################################################# @@ -194,11 +196,6 @@ def find_parent(self, routes): return parent return None -if len(sys.argv) > 1: - filename = sys.argv[-1] -else: - filename = 'map.png' - # retrieve the node names from the page maintained by Mikey page = 'http://[fc5d:baa5:61fc:6ffd:9554:67f0:e290:7535]/nodes/list.json' print('Downloading the list of node names from {0} ...'.format(page)) @@ -364,7 +361,7 @@ def add_edges(active,color): add_edges(True,'black') add_edges(False,'grey') -graph = pydot.Dot(graph_type='graph', K='2', splines='true', dpi='50', maxiter='10000', ranksep='2', nodesep='1', epsilon='0.1', overlap='true') +graph = pydot.Dot(graph_type='graph', K='2', splines='true', dpi='50', maxiter='10000', ranksep='2', nodesep='1', epsilon='0.1', overlap='false') calculate_family_hues() for n in nodes.itervalues(): graph.add_node(n.Node()) @@ -383,5 +380,8 @@ def add_edges(active,color): graph.add_edge(edge) print('Generating the map...') -graph.write_png(filename, prog='fdp') # dot neato twopi fdp circo +if filename.split(".")[-1] == "svg": + graph.write_svg(filename, prog='fdp') +else: + graph.write_png(filename, prog='fdp') print('Map written to {0}'.format(filename)) From 747f27a5f55095958777ac14942cf96dde1868b6 Mon Sep 17 00:00:00 2001 From: Finn Date: Sat, 21 Apr 2012 17:23:47 -0700 Subject: [PATCH 08/12] Fixed port imput from the command line --- cjdnsmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cjdnsmap.py b/cjdnsmap.py index da55e1e..95e24f9 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -52,7 +52,7 @@ if len(sys.argv) == 5: cjdadmin_ip = sys.argv[1] - cjdadmin_port = sys.argv[2] + cjdadmin_port = int(sys.argv[2]) cjdadmin_pass = sys.argv[3] filename = sys.argv[4] elif len(sys.argv) != 1: From a7648d3a5bbb628f861fbb4e93468fede6c97324 Mon Sep 17 00:00:00 2001 From: Finn Date: Sat, 21 Apr 2012 19:31:58 -0700 Subject: [PATCH 09/12] Made cjdns.py give a sensical error message when the admin password is wrong --- cjdns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cjdns.py b/cjdns.py index 6146031..9963c5a 100644 --- a/cjdns.py +++ b/cjdns.py @@ -97,7 +97,7 @@ def cjdns_connect(ipAddr, port, password): # Check our password. ret = callfunc(cjdns, "ping", password, {}); if ('error' in ret): - raise Exception("Connect failed, incorrect admin password?\n" + ret); + raise Exception("Connect failed, incorrect admin password?\n"); cjdns.functions = ""; nl = ""; From 2a9ddac1b4ce65b24c4ae7bcf96d1251cb9c1dfe Mon Sep 17 00:00:00 2001 From: Finn Herzfled Date: Sun, 20 Jan 2013 22:00:23 -0800 Subject: [PATCH 10/12] Added several options, updated the cjdns.py library --- cjdns.py | 33 +++++++++++++++++++++++++++++++-- cjdnsmap.py | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/cjdns.py b/cjdns.py index 9963c5a..f84c470 100644 --- a/cjdns.py +++ b/cjdns.py @@ -9,15 +9,25 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + import sys; +import os; import socket; import hashlib; +import json; from bencode import *; BUFFER_SIZE = 69632; def callfunc(cjdns, funcName, password, args): sock = cjdns.socket; + + # empty the socket if there's anything on it. + try: + sock.recv(BUFFER_SIZE, socket.MSG_DONTWAIT); + except: + pass; + sock.send('d1:q6:cookiee'); data = sock.recv(BUFFER_SIZE); benc = bdecode(data); @@ -49,7 +59,7 @@ def cjdns_connect(ipAddr, port, password): sock.send('d1:q4:pinge'); data = sock.recv(BUFFER_SIZE); if (data != 'd1:q4:ponge'): - raise Exception("Looks like " + ipAddr + ":" + port + " is to a non-cjdns socket."); + raise Exception("Looks like " + ipAddr + ":" + str(port) + " is to a non-cjdns socket."); # Get the functions and make the object sock.send('d1:q7:invalide'); @@ -97,7 +107,7 @@ def cjdns_connect(ipAddr, port, password): # Check our password. ret = callfunc(cjdns, "ping", password, {}); if ('error' in ret): - raise Exception("Connect failed, incorrect admin password?\n"); + raise Exception("Connect failed, incorrect admin password?\n" + str(ret)) cjdns.functions = ""; nl = ""; @@ -116,3 +126,22 @@ def cjdns_connect(ipAddr, port, password): cjdns.functions += ")"; return cjdns; + + +def cjdns_connectWithAdminInfo(): + try: + adminInfo = open(os.getenv("HOME") + '/.cjdnsadmin', 'r'); + except IOError: + print('Please create a file named .cjdnsadmin in your home directory with'); + print('ip, port, and password of your cjdns engine in json.'); + print('for example:'); + print('{'); + print(' "addr": "127.0.0.1",'); + print(' "port": 11234,'); + print(' "password": "You tell me! (you\'ll find it in your cjdroute.conf)"'); + print('}'); + raise; + + data = json.load(adminInfo); + adminInfo.close(); + return cjdns_connect(data['addr'], data['port'], data['password']); diff --git a/cjdnsmap.py b/cjdnsmap.py index 95e24f9..220d7de 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -18,6 +18,15 @@ # - Color nodes depending on the number of connections # +######## SETTINGS ############### +credentialsFromConfig = True # Whether or not to get cjdns credentials from ~/.cjdnsadmin +nonames = False # Should we even bother trying to look up names? +cjdadmin_ip = "127.0.0.1" # +cjdadmin_port = 11234 # +cjdadmin_pass = "insecure_pass" # +filename = 'map.svg' # Picks format based on filename. If it ends in .svg it's an svg, otherwise it's a png +################################# + import re import socket import sys @@ -36,29 +45,26 @@ print "sudo easy_install pydot" sys.exit() try: - from cjdns import cjdns_connect + from cjdns import cjdns_connect, cjdns_connectWithAdminInfo except: print "Requires cjdns python module. It should've been included" print "with this program. Please ensure that it's in the path. It" print "also comes with cjdns, check contrib/python/ in the cjdns source" sys.exit() -######## SETTINGS ############### -cjdadmin_ip = "127.0.0.1" # -cjdadmin_port = 11234 # -cjdadmin_pass = "insecure_pass" # -filename = 'map.svg' # Picks format based on filename. If it ends in .svg it's an svg, otherwise it's a png -################################# if len(sys.argv) == 5: cjdadmin_ip = sys.argv[1] cjdadmin_port = int(sys.argv[2]) cjdadmin_pass = sys.argv[3] filename = sys.argv[4] +elif len(sys.argv) == 2: + filename = sys.argv[1] elif len(sys.argv) != 1: print "Usage is:" print sys.argv[0] + " " - print "Or just specify it at the top of the python file" + print "Or:" + print sys.argv[0] + " []" ################################################# # code from http://effbot.org/zone/bencode.htm @@ -158,8 +164,11 @@ def hsv_to_color(h,s,v): ################################################### - -cjdns = cjdns_connect(cjdadmin_ip, cjdadmin_port, cjdadmin_pass) +try: + cjdns = cjdns_connect(cjdadmin_ip, cjdadmin_port, cjdadmin_pass) +except: + cjdns = cjdns_connectWithAdminInfo() + class route: def __init__(self, ip, name, path, link): self.ip = ip @@ -201,11 +210,18 @@ def find_parent(self, routes): print('Downloading the list of node names from {0} ...'.format(page)) names = {} h = httplib2.Http(".cache") -r, content = h.request(page, "GET") +if not nonames: + try: + r, content = h.request(page, "GET") + nameip = json.loads(content)['nodes'] + except: + print "Connection to Mikey's nodelist failed, continuing without names" + nameip = {} +else: + nameip = {} existing_names = set() doubles = set() -nameip = json.loads(content)['nodes'] for node in nameip: if not node['name'] in doubles: From 4025e878c48d5ffe0a809f4ebed45cadcdefaef1 Mon Sep 17 00:00:00 2001 From: Finn Herzfled Date: Fri, 1 Feb 2013 20:07:21 -0800 Subject: [PATCH 11/12] Updated cjdns.py to work with the latest admin interface --- cjdns.py | 127 +++++++++++++++++++++++++++++++++++++++++----------- cjdnsmap.py | 1 + 2 files changed, 101 insertions(+), 27 deletions(-) diff --git a/cjdns.py b/cjdns.py index f84c470..9f559ea 100644 --- a/cjdns.py +++ b/cjdns.py @@ -15,45 +15,100 @@ import socket; import hashlib; import json; +import threading; +import time; +import Queue; +import random; +import string; from bencode import *; BUFFER_SIZE = 69632; +KEEPALIVE_INTERVAL_SECONDS = 2; + +def randStr(): + return ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10)); def callfunc(cjdns, funcName, password, args): + txid = randStr(); sock = cjdns.socket; - - # empty the socket if there's anything on it. - try: - sock.recv(BUFFER_SIZE, socket.MSG_DONTWAIT); - except: - pass; - - sock.send('d1:q6:cookiee'); - data = sock.recv(BUFFER_SIZE); - benc = bdecode(data); - cookie = benc['cookie']; + sock.send('d1:q6:cookie4:txid10:'+ txid +'e'); + msg = _getMessage(cjdns, txid); + cookie = msg['cookie']; + txid = randStr(); req = { 'q': 'auth', 'aq': funcName, 'hash': hashlib.sha256(password + cookie).hexdigest(), 'cookie' : cookie, - 'args': args + 'args': args, + 'txid': txid }; reqBenc = bencode(req); req['hash'] = hashlib.sha256(reqBenc).hexdigest(); reqBenc = bencode(req); sock.send(reqBenc); - data = sock.recv(BUFFER_SIZE); + return _getMessage(cjdns, txid); + + +def receiverThread(cjdns): + timeOfLastSend = time.time(); + timeOfLastRecv = time.time(); try: - return bdecode(data); - except ValueError: - print("Failed to parse:\n" + data); - print("Length: " + str(len(data))); - raise; + while True: + if (timeOfLastSend + KEEPALIVE_INTERVAL_SECONDS < time.time()): + if (timeOfLastRecv + 10 < time.time()): + raise Exception("ping timeout"); + cjdns.socket.send('d1:q18:Admin_asyncEnabled4:txid8:keepalive'); + timeOfLastSend = time.time(); + + try: + data = cjdns.socket.recv(BUFFER_SIZE); + except (socket.timeout): continue; + + try: + benc = bdecode(data); + except (KeyError, ValueError): + print("error decoding [" + data + "]"); + continue; + + if benc['txid'] == 'keepaliv': + if benc['asyncEnabled'] == 0: + raise Exception("lost session"); + timeOfLastRecv = time.time(); + else: + #print "putting to queue " + str(benc); + cjdns.queue.put(benc); + + except KeyboardError: + print("interrupted"); + import thread + thread.interrupt_main(); + + +def _getMessage(cjdns, txid): + while True: + if txid in cjdns.messages: + msg = cjdns.messages[txid]; + del cjdns.messages[txid]; + return msg; + else: + #print "getting from queue"; + try: + # apparently any timeout at all allows the thread to be + # stopped but none make it unstoppable with ctrl+c + next = cjdns.queue.get(timeout=100); + except Queue.Empty: continue; + if 'txid' in next: + cjdns.messages[next['txid']] = next; + #print "adding message [" + str(next) + "]"; + else: + print "message with no txid: " + str(next); + def cjdns_connect(ipAddr, port, password): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); sock.connect((ipAddr, port)); + sock.settimeout(2); # Make sure it pongs. sock.send('d1:q4:pinge'); @@ -62,19 +117,33 @@ def cjdns_connect(ipAddr, port, password): raise Exception("Looks like " + ipAddr + ":" + str(port) + " is to a non-cjdns socket."); # Get the functions and make the object - sock.send('d1:q7:invalide'); - data = sock.recv(BUFFER_SIZE); - benc = bdecode(data); + page = 0; + availableFunctions = {}; + while True: + sock.send('d1:q24:Admin_availableFunctions4:argsd4:pagei' + str(page) + 'eee'); + data = sock.recv(BUFFER_SIZE); + benc = bdecode(data); + for func in benc['availableFunctions']: + availableFunctions[func] = benc['availableFunctions'][func]; + if (not 'more' in benc): + break; + page = page+1; + argLists = {}; cc = ("class Cjdns:\n" + " def __init__(self, socket):\n" + " self.socket = socket;\n" + + " self.queue = Queue.Queue();\n" + + " self.messages = {};\n" + " def disconnect(self):\n" - + " self.socket.close();\n"); - for func in benc['availableFunctions']: + + " self.socket.close();\n" + + " def getMessage(self, txid):\n" + + " return _getMessage(self, txid);\n"); + + for func in availableFunctions: argList = []; argLists[func] = argList; - funcDict = benc['availableFunctions'][func]; + funcDict = availableFunctions[func]; cc += " def " + func + "(self"; # If the arg is required, put it first, @@ -104,6 +173,10 @@ def cjdns_connect(ipAddr, port, password): cjdns = Cjdns(sock); + kat = threading.Thread(target=receiverThread, args=[cjdns]); + kat.setDaemon(True); + kat.start(); + # Check our password. ret = callfunc(cjdns, "ping", password, {}); if ('error' in ret): @@ -111,8 +184,8 @@ def cjdns_connect(ipAddr, port, password): cjdns.functions = ""; nl = ""; - for func in benc['availableFunctions']: - funcDict = benc['availableFunctions'][func]; + for func in availableFunctions: + funcDict = availableFunctions[func]; cjdns.functions += nl + func + "("; nl = "\n"; sep = ""; diff --git a/cjdnsmap.py b/cjdnsmap.py index 220d7de..5d61d33 100755 --- a/cjdnsmap.py +++ b/cjdnsmap.py @@ -58,6 +58,7 @@ cjdadmin_port = int(sys.argv[2]) cjdadmin_pass = sys.argv[3] filename = sys.argv[4] + print "Using credentials from argv" elif len(sys.argv) == 2: filename = sys.argv[1] elif len(sys.argv) != 1: From 6cf907a656af9ae8540a17a00b386b3ccb855b8a Mon Sep 17 00:00:00 2001 From: Finn Date: Mon, 18 Mar 2013 22:00:54 -0700 Subject: [PATCH 12/12] Updated cjdns.py --- cjdns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cjdns.py b/cjdns.py index 9f559ea..48edc7e 100644 --- a/cjdns.py +++ b/cjdns.py @@ -79,7 +79,7 @@ def receiverThread(cjdns): #print "putting to queue " + str(benc); cjdns.queue.put(benc); - except KeyboardError: + except KeyboardInterrupt: print("interrupted"); import thread thread.interrupt_main();