[or-cvs] r23933: {arm} added: testing script for checking connection resolution per (in arm/trunk/src: . interface interface/graphing util)

Damian Johnson atagar1 at gmail.com
Mon Dec 13 17:56:27 UTC 2010


Author: atagar
Date: 2010-12-13 17:56:26 +0000 (Mon, 13 Dec 2010)
New Revision: 23933

Added:
   arm/trunk/src/test.py
Modified:
   arm/trunk/src/interface/connPanel.py
   arm/trunk/src/interface/controller.py
   arm/trunk/src/interface/graphing/bandwidthStats.py
   arm/trunk/src/interface/logPanel.py
   arm/trunk/src/uninstall
   arm/trunk/src/util/connections.py
   arm/trunk/src/util/sysTools.py
   arm/trunk/src/util/torConfig.py
   arm/trunk/src/util/torTools.py
Log:
added: testing script for checking connection resolution performance, connection dumps, and the glyph demo
change: reordered resolvers by order of performance
fix: bringing all linux connection resolvers into parity (established tcp connections only)
fix: altering lsof calls to work on FreeBSD (thanks to Fabian Keil)
fix: timing issue caused the first connection resolution to not have the pid
fix: when the pid was unavailable some resolvers failed to work
fix: commands with quoted pipes were being misparsed by the sysTools' call function
fix: including udp connection results (needed since exits proxy dns traffic)
fix: skipping internal -> external address translation when the external address is private (caught by Fabian Keil)
fix: labeling connections to our socks port as being client connections



Modified: arm/trunk/src/interface/connPanel.py
===================================================================
--- arm/trunk/src/interface/connPanel.py	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/interface/connPanel.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -156,6 +156,7 @@
     self.orPort = "0"
     self.dirPort = "0"
     self.controlPort = "0"
+    self.socksPort = "0"
     self.family = []                # fingerpints of family entries
     self.isBridge = False           # true if BridgeRelay is set
     self.exitPolicy = ""
@@ -192,6 +193,8 @@
         self.listenPort = listenAddr[listenAddr.find(":") + 1:]
       else: self.listenPort = self.orPort
       
+      self.socksPort = torTools.getConn().getOption("SocksPort", "0")
+      
       # entry is None if not set, otherwise of the format "$<fingerprint>,$<fingerprint>"
       familyEntry = self.conn.get_option("MyFamily")[0][1]
       if familyEntry: self.family = familyEntry.split(",")
@@ -216,6 +219,7 @@
       self.orPort = "0"
       self.dirPort = "0"
       self.controlPort = "0"
+      self.socksPort = "0"
       self.family = []
       self.isBridge = False
       self.exitPolicy = ""
@@ -325,6 +329,9 @@
           type = "inbound"
           connectionCountTmp[0] += 1
           if SCRUB_PRIVATE_DATA and fIp not in self.fingerprintMappings.keys(): isPrivate = isGuard or self.isBridge
+        elif lPort == self.socksPort:
+          type = "client"
+          connectionCountTmp[2] += 1
         elif lPort == self.controlPort:
           type = "control"
           connectionCountTmp[4] += 1
@@ -348,8 +355,13 @@
             connectionCountTmp[1] += 1
             if SCRUB_PRIVATE_DATA and fIp not in self.fingerprintMappings.keys(): isPrivate = isExitAllowed(fIp, fPort, self.exitPolicy, self.exitRejectPrivate)
         
-        # replace nat address with external version if available
-        if self.address and type != "control": lIp = self.address
+        # replace nat address with external version if available and the
+        # external address isn't a private IP
+        # TODO: range should restrict to the following address ranges:
+        #   10.*, 172.16.* - 172.31.*, 192.168.*
+        # being lazy right now - fix the 172.* range when rewriting
+        isPrivateIp = fIp.startswith("10.") or fIp.startswith("192.168.") or fIp.startswith("172.")
+        if self.address and type != "control" and not isPrivateIp: lIp = self.address
         
         try:
           countryCodeQuery = "ip-to-country/%s" % fIp

Modified: arm/trunk/src/interface/controller.py
===================================================================
--- arm/trunk/src/interface/controller.py	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/interface/controller.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -529,8 +529,11 @@
   
   # minor refinements for connection resolver
   if not isBlindMode:
-    resolver = connections.getResolver("tor")
-    if torPid: resolver.processPid = torPid # helps narrow connection results
+    if torPid:
+      # use the tor pid to help narrow connection results
+      resolver = connections.getResolver("tor", torPid)
+    else:
+      resolver = connections.getResolver("tor")
   
   # hack to display a better (arm specific) notice if all resolvers fail
   connections.RESOLVER_FINAL_FAILURE_MSG += " (connection related portions of the monitor won't function)"
@@ -1423,7 +1426,7 @@
         panels["conn"].sortConnections()
     elif page == 1 and (key == ord('u') or key == ord('U')):
       # provides menu to pick identification resolving utility
-      optionTypes = [None, connections.CMD_NETSTAT, connections.CMD_SS, connections.CMD_LSOF, connections.CMD_SOCKSTAT, connections.CMD_BSD_SOCKSTAT, connections.CMD_BSD_PROCSTAT]
+      optionTypes = [None, connections.CMD_NETSTAT, connections.CMD_SOCKSTAT, connections.CMD_LSOF, connections.CMD_SS, connections.CMD_BSD_SOCKSTAT, connections.CMD_BSD_PROCSTAT]
       options = ["auto"] + [connections.CMD_STR[util] for util in optionTypes[1:]]
       
       initialSelection = connections.getResolver("tor").overwriteResolver # enums correspond to indices

Modified: arm/trunk/src/interface/graphing/bandwidthStats.py
===================================================================
--- arm/trunk/src/interface/graphing/bandwidthStats.py	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/interface/graphing/bandwidthStats.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -99,7 +99,7 @@
       return False
     
     # attempt to open the state file
-    try: stateFile = open("%s%s/state" % (torTools.getPathPrefix(), dataDir), "r")
+    try: stateFile = open("%s%s/state" % (conn.getPathPrefix(), dataDir), "r")
     except IOError:
       msg = PREPOPULATE_FAILURE_MSG % "unable to read the state file"
       log.log(self._config["log.graph.bw.prepopulateFailure"], msg)

Modified: arm/trunk/src/interface/logPanel.py
===================================================================
--- arm/trunk/src/interface/logPanel.py	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/interface/logPanel.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -206,7 +206,7 @@
   if not loggingLocation: return []
   
   # includes the prefix for tor paths
-  loggingLocation = torTools.getPathPrefix() + loggingLocation
+  loggingLocation = torTools.getConn().getPathPrefix() + loggingLocation
   
   # if the runlevels argument is a superset of the log file then we can
   # limit the read contents to the addLimit

Added: arm/trunk/src/test.py
===================================================================
--- arm/trunk/src/test.py	                        (rev 0)
+++ arm/trunk/src/test.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+
+"""
+Handler for arm tests and demos.
+"""
+
+import time
+from util import connections, torTools, uiTools
+
+MENU = """Arm Test Options:
+  1. Resolver Performance Test
+  2. Resolver Dump
+  3. Glyph Demo
+  q. Quit
+
+Selection: """
+
+def printDivider():
+  print("\n" + "-" * 40 + "\n")
+
+conn = None
+while True:
+  userInput = raw_input(MENU)
+  
+  # initiate the TorCtl connection if the test needs it
+  if userInput in ("1", "2") and not conn:
+    conn = torTools.getConn()
+    conn.init()
+    
+    # prefetch pid so extra system calls don't effect the timing of tests
+    conn.getMyPid()
+  
+  if userInput == "q":
+    break # quit test script
+  elif userInput == "1":
+    systemResolvers = connections.getSystemResolvers()
+    printDivider()
+    
+    allConnectionResults = []
+    for resolver in systemResolvers:
+      startTime = time.time()
+      connectionResults = connections.getConnections(resolver, "tor", conn.getMyPid())
+      connectionResults.sort()
+      allConnectionResults.append(connectionResults)
+      
+      resolverLabel = "%-10s" % connections.CMD_STR[resolver]
+      countLabel = "%4i results" % len(connectionResults)
+      timeLabel = "%0.4f seconds" % (time.time() - startTime)
+      print "%s %s     %s" % (resolverLabel, countLabel, timeLabel)
+    
+    allResolversMatch = True
+    firstResult = allConnectionResults.pop()
+    while allConnectionResults:
+      if allConnectionResults.pop() != firstResult:
+        allResolversMatch = False
+        break
+    
+    if allResolversMatch:
+      print("\nThe results of all the connection resolvers match")
+    else:
+      print("\nWarning: Connection resolver results differ")
+    
+    printDivider()
+  elif userInput == "2":
+    # use the given resolver to fetch tor's connections
+    while True:
+      # provide the selection options
+      printDivider()
+      print("Select a resolver:")
+      for i in range(1, 7):
+        print("  %i. %s" % (i, connections.CMD_STR[i]))
+      print("  q. Go back to the main menu")
+      
+      userSelection = raw_input("\nSelection: ")
+      if userSelection == "q":
+        printDivider()
+        break
+      
+      if userSelection.isdigit() and int(userSelection) in range(1, 7):
+        try:
+          resolver = int(userSelection)
+          startTime = time.time()
+          
+          print(connections.getResolverCommand(resolver, "tor", conn.getMyPid()))
+          connectionResults = connections.getConnections(resolver, "tor", conn.getMyPid())
+          connectionResults.sort()
+          
+          # prints results
+          printDivider()
+          for lIp, lPort, fIp, fPort in connectionResults:
+            print("  %s:%s -> %s:%s" % (lIp, lPort, fIp, fPort))
+          
+          print("\n  Runtime: %0.4f seconds" % (time.time() - startTime))
+        except IOError, exc:
+          print exc
+      else:
+        print("'%s' isn't a valid selection\n" % userSelection)
+  elif userInput == "3":
+    uiTools.demoGlyphs()
+    
+    # Switching to a curses context and back repetedy seems to screw up the
+    # terminal. Just to be safe this ends the process after the demo.
+    break
+  else:
+    print("'%s' isn't a valid selection\n" % userInput)
+

Modified: arm/trunk/src/uninstall
===================================================================
--- arm/trunk/src/uninstall	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/uninstall	2010-12-13 17:56:26 UTC (rev 23933)
@@ -1,7 +1,7 @@
 #!/bin/sh
 files="/usr/bin/arm /usr/share/man/man1/arm.1.gz /usr/share/arm"
 
-for i in $files 
+for i in $files
 do
   if [ -f $i -o -d $i ]; then
     rm -rf $i

Modified: arm/trunk/src/util/connections.py
===================================================================
--- arm/trunk/src/util/connections.py	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/util/connections.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -2,16 +2,17 @@
 Fetches connection data (IP addresses and ports) associated with a given
 process. This sort of data can be retrieved via a variety of common *nix
 utilities:
-- netstat   netstat -npt | grep <pid>/<process>
-- ss        ss -p | grep "\"<process>\",<pid>"
-- lsof      lsof -nPi | grep "<process>\s*<pid>.*(ESTABLISHED)"
+- netstat   netstat -np | grep "ESTABLISHED <pid>/<process>"
 - sockstat  sockstat | egrep "<process>\s*<pid>.*ESTABLISHED"
+- lsof      lsof -nPi | egrep "^<process>\s*<pid>.*((UDP.*)|(\(ESTABLISHED\)))"
+- ss        ss -nptu | grep "ESTAB.*\"<process>\",<pid>"
 
-all queries dump its stderr (directing it to /dev/null).
+all queries dump its stderr (directing it to /dev/null). Results include UDP
+and established TCP connections.
 
 FreeBSD lacks support for the needed netstat flags and has a completely
-different program for 'ss'. However, there's a couple other options (thanks to
-Fabian Keil and Hans Schnehl):
+different program for 'ss'. However, lsof works and there's a couple other
+options that perform even better (thanks to Fabian Keil and Hans Schnehl):
 - sockstat    sockstat -4c | grep '<process> *<pid>'
 - procstat    procstat -f <pid> | grep TCP | grep -v 0.0.0.0:0
 """
@@ -24,7 +25,7 @@
 from util import log, sysTools
 
 # enums for connection resolution utilities
-CMD_NETSTAT, CMD_SS, CMD_LSOF, CMD_SOCKSTAT, CMD_BSD_SOCKSTAT, CMD_BSD_PROCSTAT = range(1, 7)
+CMD_NETSTAT, CMD_SOCKSTAT, CMD_LSOF, CMD_SS, CMD_BSD_SOCKSTAT, CMD_BSD_PROCSTAT = range(1, 7)
 CMD_STR = {CMD_NETSTAT: "netstat",
            CMD_SS: "ss",
            CMD_LSOF: "lsof",
@@ -39,23 +40,27 @@
 
 # formatted strings for the commands to be executed with the various resolvers
 # options are:
-# n = prevents dns lookups, p = include process, t = tcp only
+# n = prevents dns lookups, p = include process
 # output:
 # tcp  0  0  127.0.0.1:9051  127.0.0.1:53308  ESTABLISHED 9912/tor
 # *note: bsd uses a different variant ('-t' => '-p tcp', but worse an
 #   equivilant -p doesn't exist so this can't function)
-RUN_NETSTAT = "netstat -npt | grep %s/%s"
+RUN_NETSTAT = "netstat -np | grep \"ESTABLISHED %s/%s\""
 
-# n = numeric ports, p = include process
+# n = numeric ports, p = include process, t = tcp sockets, u = udp sockets
 # output:
 # ESTAB  0  0  127.0.0.1:9051  127.0.0.1:53308  users:(("tor",9912,20))
 # *note: under freebsd this command belongs to a spreadsheet program
-RUN_SS = "ss -np | grep \"\\\"%s\\\",%s\""
+RUN_SS = "ss -nptu | grep \"ESTAB.*\\\"%s\\\",%s\""
 
 # n = prevent dns lookups, P = show port numbers (not names), i = ip only
 # output:
-# tor  9912  atagar  20u  IPv4  33453  TCP 127.0.0.1:9051->127.0.0.1:53308
-RUN_LSOF = "lsof -nPi | grep \"%s\s*%s.*(ESTABLISHED)\""
+# tor  3873  atagar  45u  IPv4  40994  0t0  TCP 10.243.55.20:45724->194.154.227.109:9001 (ESTABLISHED)
+# 
+# oddly, using the -p flag via:
+# lsof      lsof -nPi -p <pid> | grep "^<process>.*(ESTABLISHED)"
+# is much slower (11-28% in tests I ran)
+RUN_LSOF = "lsof -nPi | egrep \"^%s\\s*%s.*((UDP.*)|(\\(ESTABLISHED\\)))\""
 
 # output:
 # atagar  tor  3475  tcp4  127.0.0.1:9051  127.0.0.1:38942  ESTABLISHED
@@ -79,6 +84,34 @@
 def loadConfig(config):
   config.update(CONFIG)
 
+def getResolverCommand(resolutionCmd, processName, processPid = ""):
+  """
+  Provides the command that would be processed for the given resolver type.
+  This raises a ValueError if either the resolutionCmd isn't recognized or a
+  pid was requited but not provided.
+  
+  Arguments:
+    resolutionCmd - command to use in resolving the address
+    processName   - name of the process for which connections are fetched
+    processPid    - process ID (this helps improve accuracy)
+  """
+  
+  if not processPid:
+    # the pid is required for procstat resolution
+    if resolutionCmd == CMD_BSD_PROCSTAT:
+      raise ValueError("procstat resolution requires a pid")
+    
+    # if the pid was undefined then match any in that field
+    processPid = "[0-9]*"
+  
+  if resolutionCmd == CMD_NETSTAT: return RUN_NETSTAT % (processPid, processName)
+  elif resolutionCmd == CMD_SS: return RUN_SS % (processName, processPid)
+  elif resolutionCmd == CMD_LSOF: return RUN_LSOF % (processName, processPid)
+  elif resolutionCmd == CMD_SOCKSTAT: return RUN_SOCKSTAT % (processName, processPid)
+  elif resolutionCmd == CMD_BSD_SOCKSTAT: return RUN_BSD_SOCKSTAT % (processName, processPid)
+  elif resolutionCmd == CMD_BSD_PROCSTAT: return RUN_BSD_PROCSTAT % processPid
+  else: raise ValueError("Unrecognized resolution type: %s" % resolutionCmd)
+
 def getConnections(resolutionCmd, processName, processPid = ""):
   """
   Retrieves a list of the current connections for a given process, providing a
@@ -96,15 +129,9 @@
     processPid    - process ID (this helps improve accuracy)
   """
   
-  if resolutionCmd == CMD_NETSTAT: cmd = RUN_NETSTAT % (processPid, processName)
-  elif resolutionCmd == CMD_SS: cmd = RUN_SS % (processName, processPid)
-  elif resolutionCmd == CMD_LSOF: cmd = RUN_LSOF % (processName, processPid)
-  elif resolutionCmd == CMD_SOCKSTAT: cmd = RUN_SOCKSTAT % (processName, processPid)
-  elif resolutionCmd == CMD_BSD_SOCKSTAT: cmd = RUN_BSD_SOCKSTAT % (processName, processPid)
-  elif resolutionCmd == CMD_BSD_PROCSTAT: cmd = RUN_BSD_PROCSTAT % processPid
-  else: raise ValueError("Unrecognized resolution type: %s" % resolutionCmd)
   
   # raises an IOError if the command fails or isn't available
+  cmd = getResolverCommand(resolutionCmd, processName, processPid)
   results = sysTools.call(cmd)
   
   if not results: raise IOError("No results found using: %s" % cmd)
@@ -114,9 +141,12 @@
   for line in results:
     comp = line.split()
     
-    if resolutionCmd == CMD_NETSTAT or resolutionCmd == CMD_SS:
+    if resolutionCmd == CMD_NETSTAT:
       localIp, localPort = comp[3].split(":")
       foreignIp, foreignPort = comp[4].split(":")
+    elif resolutionCmd == CMD_SS:
+      localIp, localPort = comp[4].split(":")
+      foreignIp, foreignPort = comp[5].split(":")
     elif resolutionCmd == CMD_LSOF:
       local, foreign = comp[8].split("->")
       localIp, localPort = local.split(":")
@@ -180,38 +210,18 @@
   else: RESOLVERS[haltedIndex] = r
   return r
 
-def test():
-  # quick method for testing connection resolution
-  userInput = raw_input("Enter query (<ss, netstat, lsof, sockstat> PROCESS_NAME [PID]): ").split()
+def getSystemResolvers(osType = None):
+  """
+  Provides the types of connection resolvers available on this operating
+  system.
   
-  # checks if there's enough arguments
-  if len(userInput) == 0: sys.exit(0)
-  elif len(userInput) == 1:
-    print "no process name provided"
-    sys.exit(1)
+  Arguments:
+    osType - operating system type, fetched from the os module if undefined
+  """
   
-  # translates resolver string to enum
-  userInput[0] = userInput[0].lower()
-  if userInput[0] == "ss": userInput[0] = CMD_SS
-  elif userInput[0] == "netstat": userInput[0] = CMD_NETSTAT
-  elif userInput[0] == "lsof": userInput[0] = CMD_LSOF
-  elif userInput[0] == "sockstat": userInput[0] = CMD_SOCKSTAT
-  else:
-    print "unrecognized type of resolver: %s" % userInput[2]
-    sys.exit(1)
-  
-  # resolves connections
-  try:
-    if len(userInput) == 2: connections = getConnections(userInput[0], userInput[1])
-    else: connections = getConnections(userInput[0], userInput[1], userInput[2])
-  except IOError, exc:
-    print exc
-    sys.exit(1)
-  
-  # prints results
-  print "-" * 40
-  for lIp, lPort, fIp, fPort in connections:
-    print "%s:%s -> %s:%s" % (lIp, lPort, fIp, fPort)
+  if osType == None: osType = os.uname()[0]
+  if osType == "FreeBSD": return [CMD_BSD_SOCKSTAT, CMD_BSD_PROCSTAT, CMD_LSOF]
+  else: return [CMD_NETSTAT, CMD_SOCKSTAT, CMD_LSOF, CMD_SS]
 
 class ConnectionResolver(threading.Thread):
   """
@@ -279,8 +289,7 @@
     self.defaultResolver = CMD_NETSTAT
     
     osType = os.uname()[0]
-    if osType == "FreeBSD": self.resolverOptions = [CMD_BSD_SOCKSTAT, CMD_BSD_PROCSTAT]
-    else: self.resolverOptions = [CMD_NETSTAT, CMD_SS, CMD_LSOF, CMD_SOCKSTAT]
+    self.resolverOptions = getSystemResolvers(osType)
     
     resolverLabels = ", ".join([CMD_STR[option] for option in self.resolverOptions])
     log.log(CONFIG["log.connResolverOptions"], "Operating System: %s, Connection Resolvers: %s" % (osType, resolverLabels))

Modified: arm/trunk/src/util/sysTools.py
===================================================================
--- arm/trunk/src/util/sysTools.py	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/util/sysTools.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -149,9 +149,19 @@
         return cachedResults
   
   startTime = time.time()
-  commandComp = command.split("|")
   commandCall, results, errorExc = None, None, None
   
+  # Gets all the commands involved, taking piping into consideration. If the
+  # pipe is quoted (ie, echo "an | example") then it's ignored.
+  
+  commandComp = []
+  for component in command.split("|"):
+    if not commandComp or component.count("\"") % 2 == 0:
+      commandComp.append(component)
+    else:
+      # pipe is within quotes
+      commandComp[-1] += "|" + component
+  
   # preprocessing for the commands to prevent anything going to stdout
   for i in range(len(commandComp)):
     subcommand = commandComp[i].strip()

Modified: arm/trunk/src/util/torConfig.py
===================================================================
--- arm/trunk/src/util/torConfig.py	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/util/torConfig.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -313,7 +313,7 @@
     except IOError, exc:
       raise IOError(failureMsg % ("the pwdx call failed: " + str(exc)))
   
-  return torTools.getPathPrefix() + configLocation
+  return conn.getPathPrefix() + configLocation
 
 def getMultilineParameters():
   """

Modified: arm/trunk/src/util/torTools.py
===================================================================
--- arm/trunk/src/util/torTools.py	2010-12-13 02:44:56 UTC (rev 23932)
+++ arm/trunk/src/util/torTools.py	2010-12-13 17:56:26 UTC (rev 23933)
@@ -42,7 +42,7 @@
 CACHE_ARGS = ("version", "config-file", "exit-policy/default", "fingerprint",
               "config/names", "info/names", "features/names", "events/names",
               "nsEntry", "descEntry", "bwRate", "bwBurst", "bwObserved",
-              "bwMeasured", "flags", "pid")
+              "bwMeasured", "flags", "pid", "pathPrefix")
 
 TOR_CTL_CLOSE_MSG = "Tor closed control connection. Exiting event thread."
 UNKNOWN = "UNKNOWN" # value used by cached information if undefined
@@ -52,7 +52,9 @@
           "log.torGetInfo": log.DEBUG,
           "log.torGetConf": log.DEBUG,
           "log.torSetConf": log.INFO,
-          "log.torPrefixPathInvalid": log.NOTICE}
+          "log.torPrefixPathInvalid": log.NOTICE,
+          "log.bsdJailFound": log.INFO,
+          "log.unknownBsdJailId": log.WARN}
 
 # events used for controller functionality:
 # NOTICE - used to detect when tor is shut down
@@ -67,27 +69,7 @@
 
 def loadConfig(config):
   config.update(CONFIG)
-  
-  # make sure the path prefix is valid and exists (providing a notice if not)
-  prefixPath = CONFIG["features.pathPrefix"].strip()
-  
-  if prefixPath:
-    if prefixPath.endswith("/"): prefixPath = prefixPath[:-1]
-    
-    if prefixPath and not os.path.exists(prefixPath):
-      msg = "The prefix path set in your config (%s) doesn't exist." % prefixPath
-      log.log(CONFIG["log.torPrefixPathInvalid"], msg)
-      prefixPath = ""
-  
-  CONFIG["features.pathPrefix"] = prefixPath
 
-def getPathPrefix():
-  """
-  Provides the path prefix that should be used for fetching tor resources.
-  """
-  
-  return CONFIG["features.pathPrefix"]
-
 def getPid(controlPort=9051, pidFilePath=None):
   """
   Attempts to determine the process id for a running tor process, using the
@@ -179,6 +161,29 @@
   
   return None
 
+def getBsdJailId():
+  """
+  Get the FreeBSD jail id for the monitored Tor process.
+  """
+  
+  # Output when called from a FreeBSD jail or when Tor isn't jailed:
+  #   JID
+  #    0
+  # 
+  # Otherwise it's something like:
+  #   JID
+  #    1
+  
+  torPid = getConn().getMyPid()
+  psOutput = sysTools.call("ps -p %s -o jid" % torPid)
+  
+  if len(psOutput) == 2 and len(psOutput[1].split()) == 1:
+    jid = psOutput[1].strip()
+    if jid.isdigit(): return int(jid)
+  
+  log.log(CONFIG["log.unknownBsdJailId"], "Failed to figure out the FreeBSD jail id. Assuming 0.")
+  return 0
+
 def getConn():
   """
   Singleton constructor for a Controller. Be aware that this starts as being
@@ -209,6 +214,11 @@
     self._statusTime = 0                # unix time-stamp for the duration of the status
     self.lastHeartbeat = 0              # time of the last tor event
     
+    # Logs issues and notices when fetching the path prefix if true. This is
+    # only done once for the duration of the application to avoid pointless
+    # messages.
+    self._pathPrefixLogging = True
+    
     # cached GETINFO parameters (None if unset or possibly changed)
     self._cachedParam = dict([(arg, "") for arg in CACHE_ARGS])
     
@@ -609,6 +619,18 @@
     
     return self._getRelayAttr("pid", None)
   
+  def getPathPrefix(self):
+    """
+    Provides the path prefix that should be used for fetching tor resources.
+    If undefined and Tor is inside a jail under FreeBsd then this provides the
+    jail's path.
+    """
+    
+    result = self._getRelayAttr("pathPrefix", "")
+    
+    if result == UNKNOWN: return ""
+    else: return result
+  
   def getStatus(self):
     """
     Provides a tuple consisting of the control port's current status and unix
@@ -1008,6 +1030,39 @@
             break
       elif key == "pid":
         result = getPid(int(self.getOption("ControlPort", 9051)), self.getOption("PidFile"))
+      elif key == "pathPrefix":
+        # make sure the path prefix is valid and exists (providing a notice if not)
+        prefixPath = CONFIG["features.pathPrefix"].strip()
+        
+        # adjusts the prefix path to account for jails under FreeBSD (many
+        # thanks to Fabian Keil!)
+        if not prefixPath and os.uname()[0] == "FreeBSD":
+          jid = getBsdJailId()
+          if jid != 0:
+            # Output should be something like:
+            #    JID  IP Address      Hostname      Path
+            #      1  10.0.0.2        tor-jail      /usr/jails/tor-jail
+            jlsOutput = sysTools.call("jls -j %s" % jid)
+            
+            if len(jlsOutput) == 2 and len(jlsOutput[1].split()) == 4:
+              prefixPath = jlsOutput[1].split()[3]
+              
+              if self._pathPrefixLogging:
+                msg = "Adjusting paths to account for Tor running in a jail at: %s" % prefixPath
+                log.log(CONFIG["log.bsdJailFound"], msg)
+        
+        if prefixPath:
+          # strips off ending slash from the path
+          if prefixPath.endswith("/"): prefixPath = prefixPath[:-1]
+          
+          # avoid using paths that don't exist
+          if self._pathPrefixLogging and prefixPath and not os.path.exists(prefixPath):
+            msg = "The prefix path set in your config (%s) doesn't exist." % prefixPath
+            log.log(CONFIG["log.torPrefixPathInvalid"], msg)
+            prefixPath = ""
+        
+        self._pathPrefixLogging = False # prevents logging if fetched again
+        result = prefixPath
       
       # cache value
       if result: self._cachedParam[key] = result



More information about the tor-commits mailing list