From de1cb4dd99fc25c6fdad98639557fcd723977460 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 6 Mar 2011 23:29:27 -0500 Subject: Document some of TorNet.py --- lib/chutney/TorNet.py | 129 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/lib/chutney/TorNet.py b/lib/chutney/TorNet.py index 46925ba..210fa94 100644 --- a/lib/chutney/TorNet.py +++ b/lib/chutney/TorNet.py @@ -8,6 +8,7 @@ from __future__ import with_statement +# Get verbose tracebacks, so we can diagnose better. import cgitb cgitb.enable(format="plain") @@ -21,16 +22,55 @@ import time import chutney.Templating - -def mkdir_p(d): +def mkdir_p(d, mode=0777): + """Create directory 'd' and all of its parents as needed. Unlike + os.makedirs, does not give an error if d already exists. + """ try: - os.makedirs(d) + os.makedirs(d,mode=mode) except OSError, e: if e.errno == errno.EEXIST: return raise class Node(object): + """A Node represents a Tor node or a set of Tor nodes. It's created + in a network configuration file. + + This class is responsible for holding the user's selected node + configuration, and figuring out how the node needs to be + configured and launched. + """ + # XXXXX Split this class up; its various methods are too ungainly, + # and if we let them start talking to each other too intimately, + # we'll never crowbar them apart. One possible design: it should + # turn into a factory that can return a NodeLauncher and a + # NodeConfigurator depending on options. + + ## Fields: + # _parent + # _env + + ## Environment members used: + # torrc -- which torrc file to use + # torrc_template_path -- path to search for torrc files and include files + # authority -- bool -- are we an authority? + # relay -- bool -- are we a relay + # nodenum -- int -- set by chutney -- which unique node index is this? + # dir -- path -- set by chutney -- data directory for this tor + # tor_gencert -- path to tor_gencert binary + # tor -- path to tor binary + # auth_cert_lifetime -- lifetime of authority certs, in months. + # ip -- IP to listen on (used only if authority) + # orport, dirport -- (used only if authority) + # fingerprint -- used only if authority + # dirserver_flags -- used only if authority + # nick -- nickname of this router + + ## Environment members set + # fingerprint -- hex router key fingerprint + # nodenum -- int -- set by chutney -- which unique node index is this? + ######## # Users are expected to call these: def __init__(self, parent=None, **kwargs): @@ -50,27 +90,39 @@ class Node(object): ####### # Users are NOT expected to call these: def _getTorrcFname(self): + """Return the name of the file where we'll be writing torrc""" return self.expand("${torrc_fname}") def _createTorrcFile(self, checkOnly=False): + """Write the torrc file for this node. If checkOnly, just make sure + that the formatting is indeed possible. + """ fn_out = self._getTorrcFname() torrc_template = self._getTorrcTemplate() output = torrc_template.format(self._env) if checkOnly: + # XXXX Is it time-cosuming to format? If so, cache here. return with open(fn_out, 'w') as f: f.write(output) def _getTorrcTemplate(self): + """Return the template used to write the torrc for this node.""" template_path = self._env['torrc_template_path'] return chutney.Templating.Template("$${include:$torrc}", includePath=template_path) def _getFreeVars(self): + """Return a set of the free variables in the torrc template for this + node. + """ template = self._getTorrcTemplate() return template.freevars(self._env) def _createEnviron(self, parent, argdict): + """Return an Environ that delegates to the parent node's Environ (if + there is a parent node), or to the default environment. + """ if parent: parentenv = parent._env else: @@ -78,12 +130,20 @@ class Node(object): return TorEnviron(parentenv, **argdict) def _getDefaultEnviron(self): + """Return the default environment. Any variables that we can't find + set for any particular node, we look for here. + """ return _BASE_ENVIRON def _checkConfig(self, net): + """Try to format our torrc; raise an exception if we can't. + """ self._createTorrcFile(checkOnly=True) def _preConfig(self, net): + """Called on all nodes before any nodes configure: generates keys as + needed. + """ self._makeDataDir() if self._env['authority']: self._genAuthorityKey() @@ -91,21 +151,30 @@ class Node(object): self._genRouterKey() def _config(self, net): + """Called to configure a node: creates a torrc file for it.""" self._createTorrcFile() #self._createScripts() def _postConfig(self, net): + """Called on each nodes after all nodes configure.""" #self.net.addNode(self) pass def _setnodenum(self, num): + """Assign a value to the 'nodenum' element of this node. Each node + in a network gets its own nodenum. + """ self._env['nodenum'] = num def _makeDataDir(self): + """Create the data directory (with keys subdirectory) for this node. + """ datadir = self._env['dir'] mkdir_p(os.path.join(datadir, 'keys')) def _genAuthorityKey(self): + """Generate an authority identity and signing key for this authority, + if they do not already exist.""" datadir = self._env['dir'] tor_gencert = self._env['tor_gencert'] lifetime = self._env['auth_cert_lifetime'] @@ -132,6 +201,9 @@ class Node(object): assert p.returncode == 0 #XXXX BAD! def _genRouterKey(self): + """Generate an identity key for this router, unless we already have, + and set up the 'fingerprint' entry in the Environ. + """ datadir = self._env['dir'] tor = self._env['tor'] idfile = os.path.join(datadir,'keys',"identity_key") @@ -150,6 +222,8 @@ class Node(object): self._env['fingerprint'] = fingerprint def _getDirServerLine(self): + """Return a DirServer line for this Node. That'll be "" if this is + not an authority.""" if not self._env['authority']: return "" @@ -174,6 +248,10 @@ class Node(object): # own class. XXXX def getPid(self): + """Assuming that this node has its pidfile in ${dir}/pid, return + the pid of the running process, or None if there is no pid in the + file. + """ pidfile = os.path.join(self._env['dir'], 'pid') if not os.path.exists(pidfile): return None @@ -182,6 +260,10 @@ class Node(object): return int(f.read()) def isRunning(self, pid=None): + """Return true iff this node is running. (If 'pid' is provided, we + assume that the pid provided is the one of this node. Otherwise + we call getPid(). + """ if pid is None: pid = self.getPid() if pid is None: @@ -199,6 +281,13 @@ class Node(object): return True def check(self, listRunning=True, listNonRunning=False): + """See if this node is running, stopped, or crashed. If it's running + and listRunning is set, print a short statement. If it's + stopped and listNonRunning is set, then print a short statement. + If it's crashed, print a statement. Return True if the + node is running, false otherwise. + """ + # XXX Split this into "check" and "print" parts. pid = self.getPid() running = self.isRunning(pid) nick = self._env['nick'] @@ -218,6 +307,7 @@ class Node(object): return False def hup(self): + """Send a SIGHUP to this node, if it's running.""" pid = self.getPid() running = self.isRunning() nick = self._env['nick'] @@ -230,9 +320,12 @@ class Node(object): return False def start(self): + """Try to start this node; return True if we succeeded or it was + already running, False if we failed.""" + if self.isRunning(): print "%s is already running"%self._env['nick'] - return + return True torrc = self._getTorrcFname() cmdline = [ self._env['tor'], @@ -250,6 +343,7 @@ class Node(object): return True def stop(self, sig=signal.SIGINT): + """Try to stop this node by sending it the signal 'sig'.""" pid = self.getPid() if not self.isRunning(pid): print "%s is not running"%self._env['nick'] @@ -266,7 +360,7 @@ DEFAULTS = { 'auth_cert_lifetime' : 12, 'ip' : '127.0.0.1', 'dirserver_flags' : 'no-v2', - 'privnet_dir' : '.', + 'chutney_dir' : '.', 'torrc_fname' : '${dir}/torrc', 'orport_base' : 6000, 'dirport_base' : 7000, @@ -277,6 +371,27 @@ DEFAULTS = { } class TorEnviron(chutney.Templating.Environ): + """Subclass of chutney.Templating.Environ to implement commonly-used + substitutions. + + Environment fields provided: + + orport, controlport, socksport, dirport: + dir: + nick: + tor_gencert: + auth_passphrase: + torrc_template_path: + + Environment fields used: + nodenum + tag + orport_base, controlport_base, socksport_base, dirport_base + chutney_dir + tor + + XXXX document the above. Or document all fields in one place? + """ def __init__(self,parent=None,**kwargs): chutney.Templating.Environ.__init__(self, parent=parent, **kwargs) @@ -307,10 +422,12 @@ class TorEnviron(chutney.Templating.Environ): return self['nick'] # OMG TEH SECURE! def _get_torrc_template_path(self, my): - return [ os.path.join(my['privnet_dir'], 'torrc_templates') ] + return [ os.path.join(my['chutney_dir'], 'torrc_templates') ] class Network(object): + """A network of Tor nodes, plus functions to manipulate them + """ def __init__(self,defaultEnviron): self._nodes = [] self._dfltEnv = defaultEnviron -- cgit v1.2.3