[tor-commits] [arm/master] Adding an option for reconnecting to Tor

atagar at torproject.org atagar at torproject.org
Wed May 25 16:45:25 UTC 2011


commit 3941336e68bb6fd16b2fe4c6c8dd955262a90286
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed May 25 09:39:59 2011 -0700

    Adding an option for reconnecting to Tor
    
    If Tor is shut down then restarted this provides an option so the user can
    press 'r' to reconnect to the new instance. Use cases are...
    
    Success -> log and display message indicating success
    Password Required -> prompt for password
    Failure -> display message with a description of the problem
---
 src/cli/controller.py   |   16 +++++++++++-----
 src/cli/headerPanel.py  |   34 +++++++++++++++++++++++++++++++---
 src/starter.py          |   10 +++-------
 src/util/connections.py |   18 ++++++++++++++++++
 src/util/torTools.py    |   45 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 108 insertions(+), 15 deletions(-)

diff --git a/src/cli/controller.py b/src/cli/controller.py
index d9895e6..e94f7f6 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -18,7 +18,7 @@ import cli.graphing.connStats
 import cli.graphing.resourceStats
 import cli.connections.connPanel
 
-from util import connections, conf, enum, log, panel, sysTools, torConfig, torTools, uiTools
+from util import connections, conf, enum, log, panel, sysTools, torConfig, torTools
 
 ARM_CONTROLLER = None
 
@@ -346,7 +346,7 @@ def heartbeatCheck(isUnresponsive):
   
   conn = torTools.getConn()
   lastHeartbeat = conn.getHeartbeat()
-  if conn.isAlive() and "BW" in conn.getControllerEvents() and lastHeartbeat != 0:
+  if conn.isAlive() and "BW" in conn.getControllerEvents():
     if not isUnresponsive and (time.time() - lastHeartbeat) >= 10:
       isUnresponsive = True
       log.log(log.NOTICE, "Relay unresponsive (last heartbeat: %s)" % time.ctime(lastHeartbeat))
@@ -357,15 +357,21 @@ def heartbeatCheck(isUnresponsive):
   
   return isUnresponsive
 
-def connResetListener(_, eventType):
+def connResetListener(conn, eventType):
   """
-  Pauses connection resolution when tor's shut down, and resumes if started
-  again.
+  Pauses connection resolution when tor's shut down, and resumes with the new
+  pid if started again.
   """
   
   if connections.isResolverAlive("tor"):
     resolver = connections.getResolver("tor")
     resolver.setPaused(eventType == torTools.State.CLOSED)
+    
+    if eventType == torTools.State.INIT:
+      torPid = conn.getMyPid()
+      
+      if torPid and torPid != resolver.getPid():
+        resolver.setPid(torPid)
 
 def startTorMonitor(startTime):
   """
diff --git a/src/cli/headerPanel.py b/src/cli/headerPanel.py
index 102ef64..decf975 100644
--- a/src/cli/headerPanel.py
+++ b/src/cli/headerPanel.py
@@ -19,6 +19,8 @@ import time
 import curses
 import threading
 
+import cli.popups
+
 from util import log, panel, sysTools, torTools, uiTools
 
 # minimum width for which panel attempts to double up contents (two columns to
@@ -33,7 +35,9 @@ FLAG_COLORS = {"Authority": "white",  "BadExit": "red",     "BadDirectory": "red
 VERSION_STATUS_COLORS = {"new": "blue", "new in series": "blue", "obsolete": "red", "recommended": "green",  
                          "old": "red",  "unrecommended": "red",  "unknown": "cyan"}
 
-DEFAULT_CONFIG = {"features.showFdUsage": False,
+DEFAULT_CONFIG = {"startup.interface.ipAddress": "127.0.0.1",
+                  "startup.interface.port": 9051,
+                  "features.showFdUsage": False,
                   "log.fdUsageSixtyPercent": log.NOTICE,
                   "log.fdUsageNinetyPercent": log.WARN}
 
@@ -107,6 +111,29 @@ class HeaderPanel(panel.Panel, threading.Thread):
     if self.vals["tor/orPort"]: return 4 if isWide else 6
     else: return 3 if isWide else 4
   
+  def handleKey(self, key):
+    isKeystrokeConsumed = True
+    
+    if key in (ord('r'), ord('R')) and not self._isTorConnected:
+      try:
+        ctlAddr, ctlPort = self._config["startup.interface.ipAddress"], self._config["startup.interface.port"]
+        tmpConn, authType, authValue = torTools.getConnectionComponents(ctlAddr, ctlPort)
+        
+        if authType == torTools.AUTH_TYPE.PASSWORD:
+          authValue = cli.popups.inputPrompt("Controller Password: ")
+          if not authValue: raise IOError() # cancel reconnection
+        
+        tmpConn.authenticate(authValue)
+        torTools.getConn().init(tmpConn)
+        log.log(log.NOTICE, "Reconnected to Tor's control port")
+        cli.popups.showMsg("Tor reconnected", 1)
+      except Exception, exc:
+        # displays notice for failed connection attempt
+        if exc.args: cli.popups.showMsg("Unable to reconnect (%s)" % exc, 3)
+    else: isKeystrokeConsumed = False
+    
+    return isKeystrokeConsumed
+  
   def draw(self, width, height):
     self.valsLock.acquire()
     isWide = width + 1 >= MIN_DUAL_COL_WIDTH
@@ -233,7 +260,8 @@ class HeaderPanel(panel.Panel, threading.Thread):
       else:
         statusTime = torTools.getConn().getStatus()[1]
         statusTimeLabel = time.strftime("%H:%M %m/%d/%Y", time.localtime(statusTime))
-        self.addfstr(2 if isWide else 4, 0, "<b><red>Tor Disconnected</red></b> (%s)" % statusTimeLabel)
+        msg = "<b><red>Tor Disconnected</red></b> (%s) - press r to reconnect" % statusTimeLabel
+        self.addfstr(2 if isWide else 4, 0, msg)
       
       # Undisplayed / Line 3 Right (exit policy)
       if isWide:
@@ -313,7 +341,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     self._cond.notifyAll()
     self._cond.release()
   
-  def resetListener(self, conn, eventType):
+  def resetListener(self, _, eventType):
     """
     Updates static parameters on tor reload (sighup) events.
     
diff --git a/src/starter.py b/src/starter.py
index 0454f9e..b41b18b 100644
--- a/src/starter.py
+++ b/src/starter.py
@@ -11,7 +11,6 @@ import sys
 import time
 import getopt
 import locale
-import socket
 import platform
 
 import version
@@ -327,7 +326,7 @@ if __name__ == '__main__':
   
   # sets up TorCtl connection, prompting for the passphrase if necessary and
   # sending problems to stdout if they arise
-  TorCtl.INCORRECT_PASSWORD_MSG = "Controller password found in '%s' was incorrect" % configPath
+  TorCtl.TorCtl.INCORRECT_PASSWORD_MSG = "Controller password found in '%s' was incorrect" % configPath
   authPassword = config.get("startup.controlPassword", CONFIG["startup.controlPassword"])
   conn = TorCtl.TorCtl.connect(controlAddr, controlPort, authPassword)
   if conn == None:
@@ -344,12 +343,9 @@ if __name__ == '__main__':
     # making this a much bigger hack).
     
     try:
-      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-      s.connect((controlAddr, controlPort))
-      tmpConn = TorCtl.TorCtl.Connection(s)
+      tmpConn, authType, cookiePath = util.torTools.getConnectionComponents(controlAddr, controlPort)
       
-      if tmpConn.get_auth_type() == TorCtl.TorCtl.AUTH_TYPE.COOKIE:
-        cookiePath = tmpConn.get_auth_cookie_path()
+      if authType == util.torTools.AUTH_TYPE.COOKIE:
         torPid = util.torTools.getPid(controlPort)
         
         if torPid and cookiePath[0] != "/":
diff --git a/src/util/connections.py b/src/util/connections.py
index c090893..47aa8af 100644
--- a/src/util/connections.py
+++ b/src/util/connections.py
@@ -538,6 +538,24 @@ class ConnectionResolver(threading.Thread):
     
     return self._resolutionCounter
   
+  def getPid(self):
+    """
+    Provides the pid used to narrow down connection resolution. This is an
+    empty string if undefined.
+    """
+    
+    return self.processPid
+  
+  def setPid(self, processPid):
+    """
+    Sets the pid used to narrow down connection resultions.
+    
+    Arguments:
+      processPid - pid for the process we're fetching connections for
+    """
+    
+    self.processPid = processPid
+  
   def setPaused(self, isPause):
     """
     Allows or prevents further connection resolutions (this still makes use of
diff --git a/src/util/torTools.py b/src/util/torTools.py
index 12af772..d7ffa4d 100644
--- a/src/util/torTools.py
+++ b/src/util/torTools.py
@@ -25,6 +25,9 @@ from util import enum, log, procTools, sysTools, uiTools
 # CLOSED - control port closed
 State = enum.Enum("INIT", "CLOSED")
 
+# enums for authentication on the tor control port
+AUTH_TYPE = enum.Enum("NONE", "COOKIE", "PASSWORD")
+
 # Addresses of the default directory authorities for tor version 0.2.3.0-alpha
 # (this comes from the dirservers array in src/or/config.c).
 DIR_SERVERS = [("86.59.21.38", "80"),         # tor26
@@ -301,6 +304,45 @@ def parseVersion(versionStr):
   
   return result
 
+def getConnectionComponents(controlAddr="127.0.0.1", controlPort=9051):
+  """
+  Provides an uninitiated torctl connection for the control port. This returns
+  a tuple with the...
+  (torctl connection, authType, authValue)
+  
+  The authValue corresponds to the cookie path if using an authentication
+  cookie. Otherwise this is the empty string. This raises an IOError if unable
+  to connect.
+  
+  Arguments:
+    controlAddr - ip address belonging to the controller
+    controlPort - port belonging to the controller
+  """
+  
+  try:
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect((controlAddr, controlPort))
+    conn = TorCtl.Connection(s)
+    authType, authValue = conn.get_auth_type(), ""
+    
+    if authType == TorCtl.AUTH_TYPE.COOKIE:
+      authValue = conn.get_auth_cookie_path()
+    
+    # converts to our enum type
+    if authType == TorCtl.AUTH_TYPE.NONE:
+      authType = AUTH_TYPE.NONE
+    elif authType == TorCtl.AUTH_TYPE.COOKIE:
+      authType = AUTH_TYPE.COOKIE
+    elif authType == TorCtl.AUTH_TYPE.PASSWORD:
+      authType = AUTH_TYPE.PASSWORD
+    
+    return (conn, authType, authValue)
+  except socket.error, exc:
+    if "Connection refused" in exc.args:
+      raise IOError("Connection refused. Is the ControlPort enabled?")
+    else: raise IOError("Failed to establish socket: %s" % exc)
+  except Exception, exc: raise IOError(exc)
+
 def getConn():
   """
   Singleton constructor for a Controller. Be aware that this starts as being
@@ -385,6 +427,9 @@ class Controller(TorCtl.PostEventListener):
       self.conn.add_event_listener(self)
       for listener in self.eventListeners: self.conn.add_event_listener(listener)
       
+      # registers this as our first heartbeat
+      self._updateHeartbeat()
+      
       # reset caches for ip -> fingerprint lookups
       self._fingerprintMappings = None
       self._fingerprintLookupCache = {}



More information about the tor-commits mailing list