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..48edc7e
--- /dev/null
+++ b/cjdns.py
@@ -0,0 +1,220 @@
+# 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 os;
+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;
+ 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,
+ 'txid': txid
+ };
+ reqBenc = bencode(req);
+ req['hash'] = hashlib.sha256(reqBenc).hexdigest();
+ reqBenc = bencode(req);
+ sock.send(reqBenc);
+ return _getMessage(cjdns, txid);
+
+
+def receiverThread(cjdns):
+ timeOfLastSend = time.time();
+ timeOfLastRecv = time.time();
+ try:
+ 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 KeyboardInterrupt:
+ 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_DGRAM);
+ sock.connect((ipAddr, port));
+ sock.settimeout(2);
+
+ # Make sure it pongs.
+ sock.send('d1:q4:pinge');
+ data = sock.recv(BUFFER_SIZE);
+ if (data != 'd1:q4:ponge'):
+ raise Exception("Looks like " + ipAddr + ":" + str(port) + " is to a non-cjdns socket.");
+
+ # Get the functions and make the object
+ 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"
+ + " def getMessage(self, txid):\n"
+ + " return _getMessage(self, txid);\n");
+
+ for func in availableFunctions:
+ argList = [];
+ argLists[func] = argList;
+ funcDict = 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);
+
+ kat = threading.Thread(target=receiverThread, args=[cjdns]);
+ kat.setDaemon(True);
+ kat.start();
+
+ # Check our password.
+ ret = callfunc(cjdns, "ping", password, {});
+ if ('error' in ret):
+ raise Exception("Connect failed, incorrect admin password?\n" + str(ret))
+
+ cjdns.functions = "";
+ nl = "";
+ for func in availableFunctions:
+ funcDict = 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;
+
+
+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 f6a8ee1..5d61d33 100755
--- a/cjdnsmap.py
+++ b/cjdnsmap.py
@@ -18,12 +18,54 @@
# - Color nodes depending on the number of connections
#
-import pydot
+######## 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 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, 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()
+
+
+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]
+ print "Using credentials from argv"
+elif len(sys.argv) == 2:
+ filename = sys.argv[1]
+elif len(sys.argv) != 1:
+ print "Usage is:"
+ print sys.argv[0] + " "
+ print "Or:"
+ print sys.argv[0] + " []"
#################################################
# code from http://effbot.org/zone/bencode.htm
@@ -123,7 +165,11 @@ def hsv_to_color(h,s,v):
###################################################
-
+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
@@ -149,7 +195,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]
@@ -160,13 +206,32 @@ def find_parent(self, routes):
return parent
return None
-if len(sys.argv) > 1:
- filename = sys.argv[-1]
+# 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")
+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:
- 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'
+ nameip = {}
+
+existing_names = set()
+doubles = set()
+
+for node in nameip:
+ if not node['name'] in doubles:
+ 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")
@@ -194,37 +259,21 @@ def find_parent(self, routes):
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 = ''
+routes = [];
+i = 0;
while True:
- r = s.recv(1024)
- data += r
- if not r or r.endswith('....e\n'):
+ 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']))
+ 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]
@@ -329,7 +378,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())
@@ -348,5 +397,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))