commit 3941336e68bb6fd16b2fe4c6c8dd955262a90286
Author: Damian Johnson <atagar(a)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 = {}