TorNet.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. #!/usr/bin/python
  2. #
  3. # Copyright 2011 Nick Mathewson, Michael Stone
  4. #
  5. # You may do anything with this work that copyright law would normally
  6. # restrict, so long as you retain the above notice(s) and this license
  7. # in all redistributed copies and derived works. There is no warranty.
  8. from __future__ import with_statement
  9. import os
  10. import templating
  11. import signal
  12. import subprocess
  13. import sys
  14. import re
  15. import errno
  16. import time
  17. def mkdir_p(d):
  18. try:
  19. os.makedirs(d)
  20. except OSError, e:
  21. if e.errno == errno.EEXIST:
  22. return
  23. raise
  24. class Node:
  25. ########
  26. # Users are expected to call these:
  27. def __init__(self, parent=None, **kwargs):
  28. self._parent = parent
  29. self._fields = self._createEnviron(parent, kwargs)
  30. def getN(self, N):
  31. return [ Node(self) for i in xrange(N) ]
  32. def specialize(self, **kwargs):
  33. return Node(parent=self, **kwargs)
  34. #######
  35. # Users are NOT expected to call these:
  36. def _getTorrcFname(self):
  37. return templating.Template("${torrc_fname}").format(self._fields)
  38. def _createTorrcFile(self, checkOnly=False):
  39. template = self._getTorrcTemplate()
  40. env = self._fields
  41. fn_out = self._getTorrcFname()
  42. output = template.format(env)
  43. if checkOnly:
  44. return
  45. with open(fn_out, 'w') as f:
  46. f.write(output)
  47. def _getTorrcTemplate(self):
  48. env = self._fields
  49. template_path = env['torrc_template_path']
  50. t = "$${include:$torrc}"
  51. return templating.Template(t, includePath=template_path)
  52. def _getFreeVars(self):
  53. template = self._getTorrcTemplate()
  54. env = self._fields
  55. return template.freevars(env)
  56. def _createEnviron(self, parent, argdict):
  57. if parent:
  58. parentfields = parent._fields
  59. else:
  60. parentfields = self._getDefaultFields()
  61. return TorEnviron(parentfields, **argdict)
  62. def _getDefaultFields(self):
  63. return _BASE_FIELDS
  64. def _checkConfig(self, net):
  65. self._createTorrcFile(checkOnly=True)
  66. def _preConfig(self, net):
  67. self._makeDataDir()
  68. if self._fields['authority']:
  69. self._genAuthorityKey()
  70. if self._fields['relay']:
  71. self._genRouterKey()
  72. def _config(self, net):
  73. self._createTorrcFile()
  74. #self._createScripts()
  75. def _postConfig(self, net):
  76. #self.net.addNode(self)
  77. pass
  78. def _setnodenum(self, num):
  79. self._fields['nodenum'] = num
  80. def _makeDataDir(self):
  81. env = self._fields
  82. datadir = env['dir']
  83. mkdir_p(os.path.join(datadir, 'keys'))
  84. def _genAuthorityKey(self):
  85. env = self._fields
  86. datadir = env['dir']
  87. tor_gencert = env['tor_gencert']
  88. lifetime = env['auth_cert_lifetime']
  89. idfile = os.path.join(datadir,'keys',"authority_identity_key")
  90. skfile = os.path.join(datadir,'keys',"authority_signing_key")
  91. certfile = os.path.join(datadir,'keys',"authority_certificate")
  92. addr = "%s:%s" % (env['ip'], env['dirport'])
  93. passphrase = env['auth_passphrase']
  94. if all(os.path.exists(f) for f in [idfile, skfile, certfile]):
  95. return
  96. cmdline = [
  97. tor_gencert,
  98. '--create-identity-key',
  99. '--passphrase-fd', '0',
  100. '-i', idfile,
  101. '-s', skfile,
  102. '-c', certfile,
  103. '-m', str(lifetime),
  104. '-a', addr]
  105. print "Creating identity key %s for %s with %s"%(idfile,env['nick']," ".join(cmdline))
  106. p = subprocess.Popen(cmdline, stdin=subprocess.PIPE)
  107. p.communicate(passphrase+"\n")
  108. assert p.returncode == 0 #XXXX BAD!
  109. def _genRouterKey(self):
  110. env = self._fields
  111. datadir = env['dir']
  112. tor = env['tor']
  113. idfile = os.path.join(datadir,'keys',"identity_key")
  114. cmdline = [
  115. tor,
  116. "--quiet",
  117. "--list-fingerprint",
  118. "--orport", "1",
  119. "--dirserver",
  120. "xyzzy 127.0.0.1:1 ffffffffffffffffffffffffffffffffffffffff",
  121. "--datadirectory", datadir ]
  122. p = subprocess.Popen(cmdline, stdout=subprocess.PIPE)
  123. stdout, stderr = p.communicate()
  124. fingerprint = "".join(stdout.split()[1:])
  125. assert re.match(r'^[A-F0-9]{40}$', fingerprint)
  126. env['fingerprint'] = fingerprint
  127. def _getDirServerLine(self):
  128. env = self._fields
  129. if not env['authority']:
  130. return ""
  131. datadir = env['dir']
  132. certfile = os.path.join(datadir,'keys',"authority_certificate")
  133. v3id = None
  134. with open(certfile, 'r') as f:
  135. for line in f:
  136. if line.startswith("fingerprint"):
  137. v3id = line.split()[1].strip()
  138. break
  139. assert v3id is not None
  140. return "DirServer %s v3ident=%s orport=%s %s %s:%s %s\n" %(
  141. env['nick'], v3id, env['orport'], env['dirserver_flags'],
  142. env['ip'], env['dirport'], env['fingerprint'])
  143. ##### Controlling a node. This should probably get split into its
  144. # own class. XXXX
  145. def getPid(self):
  146. env = self._fields
  147. pidfile = os.path.join(env['dir'], 'pid')
  148. if not os.path.exists(pidfile):
  149. return None
  150. with open(pidfile, 'r') as f:
  151. return int(f.read())
  152. def isRunning(self, pid=None):
  153. env = self._fields
  154. if pid is None:
  155. pid = self.getPid()
  156. if pid is None:
  157. return False
  158. try:
  159. os.kill(pid, 0) # "kill 0" == "are you there?"
  160. except OSError, e:
  161. if e.errno == errno.ESRCH:
  162. return False
  163. raise
  164. # okay, so the process exists. Say "True" for now.
  165. # XXXX check if this is really tor!
  166. return True
  167. def check(self, listRunning=True, listNonRunning=False):
  168. env = self._fields
  169. pid = self.getPid()
  170. running = self.isRunning(pid)
  171. name = env['nick']
  172. dir = env['dir']
  173. if running:
  174. if listRunning:
  175. print "%s is running with PID %s"%(name,pid)
  176. return True
  177. elif os.path.exists(os.path.join(dir, "core.%s"%pid)):
  178. if listNonRunning:
  179. print "%s seems to have crashed, and left core file core.%s"%(
  180. nick,pid)
  181. return False
  182. else:
  183. if listNonRunning:
  184. print "%s is stopped"%nick
  185. return False
  186. def hup(self):
  187. pid = self.getPid()
  188. running = self.isRunning()
  189. nick = self._fields['nick']
  190. if self.isRunning():
  191. print "Sending sighup to %s"%nick
  192. os.kill(pid, signal.SIGHUP)
  193. return True
  194. else:
  195. print "%s is not running"%nick
  196. return False
  197. def start(self):
  198. if self.isRunning():
  199. print "%s is already running"
  200. return
  201. torrc = self._getTorrcFname()
  202. cmdline = [
  203. self._fields['tor'],
  204. "--quiet",
  205. "-f", torrc,
  206. ]
  207. p = subprocess.Popen(cmdline)
  208. # XXXX this requires that RunAsDaemon is set.
  209. p.wait()
  210. if p.returncode != 0:
  211. print "Couldn't launch %s (%s): %s"%(self._fields['nick'],
  212. " ".join(cmdline),
  213. p.returncode)
  214. return False
  215. return True
  216. def stop(self, sig=signal.SIGINT):
  217. env = self._fields
  218. pid = self.getPid()
  219. if not self.isRunning(pid):
  220. print "%s is not running"%env['nick']
  221. return
  222. os.kill(pid, sig)
  223. DEFAULTS = {
  224. 'authority' : False,
  225. 'relay' : False,
  226. 'connlimit' : 60,
  227. 'net_base_dir' : 'net',
  228. 'tor' : 'tor',
  229. 'auth_cert_lifetime' : 12,
  230. 'ip' : '127.0.0.1',
  231. 'dirserver_flags' : 'no-v2',
  232. 'privnet_dir' : '.',
  233. 'torrc_fname' : '${dir}/torrc',
  234. 'orport_base' : 6000,
  235. 'dirport_base' : 7000,
  236. 'controlport_base' : 8000,
  237. 'socksport_base' : 9000,
  238. 'dirservers' : "Dirserver bleargh bad torrc file!",
  239. 'core' : True,
  240. }
  241. class TorEnviron(templating.Environ):
  242. def __init__(self,parent=None,**kwargs):
  243. templating.Environ.__init__(self, parent=parent, **kwargs)
  244. def _get_orport(self, me):
  245. return me['orport_base']+me['nodenum']
  246. def _get_controlport(self, me):
  247. return me['controlport_base']+me['nodenum']
  248. def _get_socksport(self, me):
  249. return me['socksport_base']+me['nodenum']
  250. def _get_dirport(self, me):
  251. return me['dirport_base']+me['nodenum']
  252. def _get_dir(self, me):
  253. return os.path.abspath(os.path.join(me['net_base_dir'],
  254. "nodes",
  255. "%03d%s"%(me['nodenum'], me['tag'])))
  256. def _get_nick(self, me):
  257. return "test%03d%s"%(me['nodenum'], me['tag'])
  258. def _get_tor_gencert(self, me):
  259. return me['tor']+"-gencert"
  260. def _get_auth_passphrase(self, me):
  261. return self['nick'] # OMG TEH SECURE!
  262. def _get_torrc_template_path(self, me):
  263. return [ os.path.join(me['privnet_dir'], 'torrc_templates') ]
  264. class Network:
  265. def __init__(self,defaultEnviron):
  266. self._nodes = []
  267. self._dfltEnv = defaultEnviron
  268. self._nextnodenum = 0
  269. def _addNode(self, n):
  270. n._setnodenum(self._nextnodenum)
  271. self._nextnodenum += 1
  272. self._nodes.append(n)
  273. def _checkConfig(self):
  274. for n in self._nodes:
  275. n._checkConfig(self)
  276. def configure(self):
  277. network = self
  278. dirserverlines = []
  279. self._checkConfig()
  280. # XXX don't change node names or types or count if anything is
  281. # XXX running!
  282. for n in self._nodes:
  283. n._preConfig(network)
  284. dirserverlines.append(n._getDirServerLine())
  285. self._dfltEnv['dirservers'] = "".join(dirserverlines)
  286. for n in self._nodes:
  287. n._config(network)
  288. for n in self._nodes:
  289. n._postConfig(network)
  290. def status(self):
  291. statuses = [n.check() for n in self._nodes]
  292. n_ok = len([x for x in statuses if x])
  293. print "%d/%d nodes are running"%(n_ok,len(self._nodes))
  294. def restart(self):
  295. self.stop()
  296. self.start()
  297. def start(self):
  298. print "Starting nodes"
  299. return all([n.start() for n in self._nodes])
  300. def hup(self):
  301. print "Sending SIGHUP to nodes"
  302. return all([n.hup() for n in self._nodes])
  303. def stop(self):
  304. for sig, desc in [(signal.SIGINT, "SIGINT"),
  305. (signal.SIGINT, "another SIGINT"),
  306. (signal.SIGKILL, "SIGKILL")]:
  307. print "Sending %s to nodes"%desc
  308. for n in self._nodes:
  309. if n.isRunning():
  310. n.stop(sig=sig)
  311. print "Waiting for nodes to finish."
  312. for n in xrange(15):
  313. time.sleep(1)
  314. if all(not n.isRunning() for n in self._nodes):
  315. return
  316. sys.stdout.write(".")
  317. sys.stdout.flush()
  318. for n in self._nodes:
  319. n.check(listNonRunning=False)
  320. def ConfigureNodes(nodelist):
  321. network = _THE_NETWORK
  322. for n in nodelist:
  323. network._addNode(n)
  324. def runConfigFile(verb, f):
  325. global _BASE_FIELDS
  326. global _THE_NETWORK
  327. _BASE_FIELDS = TorEnviron(templating.Environ(**DEFAULTS))
  328. _THE_NETWORK = Network(_BASE_FIELDS)
  329. _GLOBALS = dict(_BASE_FIELDS= _BASE_FIELDS,
  330. Node=Node,
  331. ConfigureNodes=ConfigureNodes,
  332. _THE_NETWORK=_THE_NETWORK)
  333. exec f in _GLOBALS
  334. network = _GLOBALS['_THE_NETWORK']
  335. if not hasattr(network, verb):
  336. print "I don't know how to %s. Known commands are: %s" % (
  337. verb, " ".join(x for x in dir(network) if not x.startswith("_")))
  338. return
  339. getattr(network,verb)()
  340. if __name__ == '__main__':
  341. f = open(sys.argv[2])
  342. runConfigFile(sys.argv[1], f)