[tor-commits] [arm/master] Removing dependencies on tor_tools

atagar at torproject.org atagar at torproject.org
Tue Jan 28 04:46:04 UTC 2014


commit 494f3b9538e7a5885bbe4841c045eb84dc78ddfe
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Jan 27 19:38:48 2014 -0800

    Removing dependencies on tor_tools
    
    Swapping modules from tor_tools to directly using a stem controller. In several
    instances tor_tools provided special handling used in only a single module. In
    those cases I'm moving the helper there.
---
 arm/config_panel.py                 |   18 +-
 arm/connections/circ_entry.py       |   43 +++-
 arm/connections/conn_entry.py       |  371 +++++++++++++++++++++++++++---
 arm/connections/conn_panel.py       |   34 ++-
 arm/connections/descriptor_popup.py |    8 +-
 arm/controller.py                   |   18 +-
 arm/graphing/bandwidth_stats.py     |  138 ++++++++++--
 arm/graphing/conn_stats.py          |    8 +-
 arm/graphing/graph_panel.py         |    4 +-
 arm/graphing/resource_stats.py      |    4 +-
 arm/log_panel.py                    |   14 +-
 arm/menu/actions.py                 |   11 +-
 arm/starter.py                      |    6 -
 arm/torrc_panel.py                  |    8 +-
 arm/util/tor_config.py              |   32 +--
 arm/util/tor_tools.py               |  423 +----------------------------------
 16 files changed, 578 insertions(+), 562 deletions(-)

diff --git a/arm/config_panel.py b/arm/config_panel.py
index c2c2ee5..84f7f3a 100644
--- a/arm/config_panel.py
+++ b/arm/config_panel.py
@@ -9,7 +9,7 @@ import threading
 import arm.controller
 import popups
 
-from arm.util import panel, tor_config, tor_tools, ui_tools
+from arm.util import panel, tor_config, tor_controller, ui_tools
 
 import stem.control
 
@@ -185,7 +185,7 @@ class ConfigEntry():
     True if we have no value, false otherwise.
     """
 
-    conf_value = tor_tools.get_conn().get_option(self.get(Field.OPTION), [], True)
+    conf_value = tor_controller().get_conf(self.get(Field.OPTION), [], True)
 
     return not bool(conf_value)
 
@@ -196,7 +196,7 @@ class ConfigEntry():
     value's type to provide a user friendly representation if able.
     """
 
-    conf_value = ", ".join(tor_tools.get_conn().get_option(self.get(Field.OPTION), [], True))
+    conf_value = ", ".join(tor_controller().get_conf(self.get(Field.OPTION), [], True))
 
     # provides nicer values for recognized types
 
@@ -234,10 +234,10 @@ class ConfigPanel(panel.Panel):
 
     # initializes config contents if we're connected
 
-    conn = tor_tools.get_conn()
-    conn.add_status_listener(self.reset_listener)
+    controller = tor_controller()
+    controller.add_status_listener(self.reset_listener)
 
-    if conn.is_alive():
+    if controller.is_alive():
       self.reset_listener(None, stem.control.State.INIT, None)
 
   def reset_listener(self, controller, event_type, _):
@@ -256,9 +256,9 @@ class ConfigPanel(panel.Panel):
     self.conf_important_contents = []
 
     if self.config_type == State.TOR:
-      conn, config_option_lines = tor_tools.get_conn(), []
+      controller, config_option_lines = tor_controller(), []
       custom_options = tor_config.get_custom_options()
-      config_option_query = conn.get_info("config/names", None)
+      config_option_query = controller.get_info("config/names", None)
 
       if config_option_query:
         config_option_lines = config_option_query.strip().split("\n")
@@ -407,7 +407,7 @@ class ConfigPanel(panel.Panel):
               # set_option accepts list inputs when there's multiple values
               new_value = new_value.split(",")
 
-            tor_tools.get_conn().set_option(config_option, new_value)
+            tor_controller().set_conf(config_option, new_value)
 
             # forces the label to be remade with the new value
 
diff --git a/arm/connections/circ_entry.py b/arm/connections/circ_entry.py
index cc6b418..4aa3fc8 100644
--- a/arm/connections/circ_entry.py
+++ b/arm/connections/circ_entry.py
@@ -11,7 +11,9 @@ followed by an entry for each hop in the circuit. For instance:
 import curses
 
 from arm.connections import entries, conn_entry
-from arm.util import tor_tools, ui_tools
+from arm.util import tor_controller, ui_tools
+
+ADDRESS_LOOKUP_CACHE = {}
 
 
 class CircEntry(conn_entry.ConnectionEntry):
@@ -48,15 +50,15 @@ class CircEntry(conn_entry.ConnectionEntry):
 
     self.status = status
     self.lines = [self.lines[0]]
-    conn = tor_tools.get_conn()
+    controller = tor_controller()
 
     if status == "BUILT" and not self.lines[0].is_built:
-      exit_ip, exit_port = conn.get_relay_address(path[-1], ("192.168.0.1", "0"))
+      exit_ip, exit_port = get_relay_address(controller, path[-1], ("192.168.0.1", "0"))
       self.lines[0].set_exit(exit_ip, exit_port, path[-1])
 
     for i in range(len(path)):
       relay_fingerprint = path[i]
-      relay_ip, relay_port = conn.get_relay_address(relay_fingerprint, ("192.168.0.1", "0"))
+      relay_ip, relay_port = get_relay_address(controller, relay_fingerprint, ("192.168.0.1", "0"))
 
       if i == len(path) - 1:
         if status == "BUILT":
@@ -214,3 +216,36 @@ class CircLine(conn_entry.ConnectionLine):
     return ((dst + etc, line_format),
             (" " * (width - baseline_space - len(dst) - len(etc) + 5), line_format),
             ("%-14s" % self.placement_label, line_format))
+
+
+def get_relay_address(controller, relay_fingerprint, default = None):
+  """
+  Provides the (IP Address, ORPort) tuple for a given relay. If the lookup
+  fails then this returns the default.
+
+  Arguments:
+    relay_fingerprint - fingerprint of the relay
+  """
+
+  result = default
+
+  if controller.is_alive():
+    # query the address if it isn't yet cached
+    if not relay_fingerprint in ADDRESS_LOOKUP_CACHE:
+      if relay_fingerprint == controller.get_info("fingerprint", None):
+        # this is us, simply check the config
+        my_address = controller.get_info("address", None)
+        my_or_port = controller.get_conf("ORPort", None)
+
+        if my_address and my_or_port:
+          ADDRESS_LOOKUP_CACHE[relay_fingerprint] = (my_address, my_or_port)
+      else:
+        # check the consensus for the relay
+        relay = controller.get_network_status(relay_fingerprint, None)
+
+        if relay:
+          ADDRESS_LOOKUP_CACHE[relay_fingerprint] = (relay.address, relay.or_port)
+
+    result = ADDRESS_LOOKUP_CACHE.get(relay_fingerprint, default)
+
+  return result
diff --git a/arm/connections/conn_entry.py b/arm/connections/conn_entry.py
index a2bf55c..62c01f3 100644
--- a/arm/connections/conn_entry.py
+++ b/arm/connections/conn_entry.py
@@ -6,9 +6,11 @@ Connection panel entries related to actual connections to or from the system
 import time
 import curses
 
-from arm.util import tor_tools, ui_tools
+from arm.util import tor_controller, ui_tools
 from arm.connections import entries
 
+import stem.control
+
 from stem.util import conf, connection, enum, str_tools
 
 # Connection Categories:
@@ -54,6 +56,17 @@ CONFIG = conf.config_dict("arm", {
   "features.connection.showColumn.expandedIp": True,
 })
 
+FINGERPRINT_TRACKER = None
+
+
+def get_fingerprint_tracker():
+  global FINGERPRINT_TRACKER
+
+  if FINGERPRINT_TRACKER is None:
+    FINGERPRINT_TRACKER = FingerprintTracker()
+
+  return FINGERPRINT_TRACKER
+
 
 class Endpoint:
   """
@@ -120,8 +133,8 @@ class Endpoint:
       default - return value if no locale information is available
     """
 
-    conn = tor_tools.get_conn()
-    return conn.get_info("ip-to-country/%s" % self.address, default)
+    controller = tor_controller()
+    return controller.get_info("ip-to-country/%s" % self.address, default)
 
   def get_fingerprint(self):
     """
@@ -132,14 +145,13 @@ class Endpoint:
     if self.fingerprint_overwrite:
       return self.fingerprint_overwrite
 
-    conn = tor_tools.get_conn()
-    my_fingerprint = conn.get_relay_fingerprint(self.address)
+    my_fingerprint = get_fingerprint_tracker().get_relay_fingerprint(self.address)
 
     # If there were multiple matches and our port is likely the ORPort then
     # try again with that to narrow the results.
 
     if not my_fingerprint and not self.is_not_or_port:
-      my_fingerprint = conn.get_relay_fingerprint(self.address, self.port)
+      my_fingerprint = get_fingerprint_tracker().get_relay_fingerprint(self.address, self.port)
 
     if my_fingerprint:
       return my_fingerprint
@@ -155,8 +167,7 @@ class Endpoint:
     my_fingerprint = self.get_fingerprint()
 
     if my_fingerprint != "UNKNOWN":
-      conn = tor_tools.get_conn()
-      my_nickname = conn.get_relay_nickname(my_fingerprint)
+      my_nickname = get_fingerprint_tracker().get_relay_nickname(my_fingerprint)
 
       if my_nickname:
         return my_nickname
@@ -233,8 +244,8 @@ class ConnectionLine(entries.ConnectionPanelLine):
 
     # overwrite the local fingerprint with ours
 
-    conn = tor_tools.get_conn()
-    self.local.fingerprint_overwrite = conn.get_info("fingerprint", None)
+    controller = tor_controller()
+    self.local.fingerprint_overwrite = controller.get_info("fingerprint", None)
 
     # True if the connection has matched the properties of a client/directory
     # connection every time we've checked. The criteria we check is...
@@ -251,15 +262,15 @@ class ConnectionLine(entries.ConnectionPanelLine):
     self.application_pid = None
     self.is_application_resolving = False
 
-    my_or_port = conn.get_option("ORPort", None)
-    my_dir_port = conn.get_option("DirPort", None)
-    my_socks_port = conn.get_option("SocksPort", "9050")
-    my_ctl_port = conn.get_option("ControlPort", None)
-    my_hidden_service_ports = conn.get_hidden_service_ports()
+    my_or_port = controller.get_conf("ORPort", None)
+    my_dir_port = controller.get_conf("DirPort", None)
+    my_socks_port = controller.get_conf("SocksPort", "9050")
+    my_ctl_port = controller.get_conf("ControlPort", None)
+    my_hidden_service_ports = get_hidden_service_ports(controller)
 
     # the ORListenAddress can overwrite the ORPort
 
-    listen_addr = conn.get_option("ORListenAddress", None)
+    listen_addr = controller.get_conf("ORListenAddress", None)
 
     if listen_addr and ":" in listen_addr:
       my_or_port = listen_addr[listen_addr.find(":") + 1:]
@@ -410,10 +421,19 @@ class ConnectionLine(entries.ConnectionPanelLine):
       # if we're a guard or bridge and the connection doesn't belong to a
       # known relay then it might be client traffic
 
-      conn = tor_tools.get_conn()
+      controller = tor_controller()
+
+      my_flags = []
+      my_fingerprint = self.get_info("fingerprint", None)
+
+      if my_fingerprint:
+        my_status_entry = self.controller.get_network_status(my_fingerprint)
 
-      if "Guard" in conn.get_my_flags([]) or conn.get_option("BridgeRelay", None) == "1":
-        all_matches = conn.get_relay_fingerprint(self.foreign.get_address(), get_all_matches = True)
+        if my_status_entry:
+          my_flags = my_status_entry.flags
+
+      if "Guard" in my_flags or controller.get_conf("BridgeRelay", None) == "1":
+        all_matches = get_fingerprint_tracker().get_relay_fingerprint(self.foreign.get_address(), get_all_matches = True)
 
         return all_matches == []
     elif my_type == Category.EXIT:
@@ -451,20 +471,20 @@ class ConnectionLine(entries.ConnectionPanelLine):
         # The exitability, circuits, and fingerprints are all cached by the
         # tor_tools util keeping this a quick lookup.
 
-        conn = tor_tools.get_conn()
+        controller = tor_controller()
         destination_fingerprint = self.foreign.get_fingerprint()
 
         if destination_fingerprint == "UNKNOWN":
           # Not a known relay. This might be an exit connection.
 
-          if conn.is_exiting_allowed(self.foreign.get_address(), self.foreign.get_port()):
+          if is_exiting_allowed(controller, self.foreign.get_address(), self.foreign.get_port()):
             self.cached_type = Category.EXIT
         elif self._possible_client or self._possible_directory:
           # This belongs to a known relay. If we haven't eliminated ourselves as
           # a possible client or directory connection then check if it still
           # holds true.
 
-          my_circuits = conn.get_circuits()
+          my_circuits = controller.get_circuits()
 
           if self._possible_client:
             # Checks that this belongs to the first hop in a circuit that's
@@ -472,8 +492,8 @@ class ConnectionLine(entries.ConnectionPanelLine):
             # a built 1-hop connection since those are most likely a directory
             # mirror).
 
-            for _, status, _, path in my_circuits:
-              if path and path[0] == destination_fingerprint and (status != "BUILT" or len(path) > 1):
+            for circ in my_circuits:
+              if circ.path and circ.path[0][0] == destination_fingerprint and (circ.status != "BUILT" or len(circ.path) > 1):
                 self.cached_type = Category.CIRCUIT  # matched a probable guard connection
 
             # if we fell through, we can eliminate ourselves as a guard in the future
@@ -483,8 +503,8 @@ class ConnectionLine(entries.ConnectionPanelLine):
           if self._possible_directory:
             # Checks if we match a built, single hop circuit.
 
-            for _, status, _, path in my_circuits:
-              if path and path[0] == destination_fingerprint and status == "BUILT" and len(path) == 1:
+            for circ in my_circuits:
+              if circ.path and circ.path[0][0] == destination_fingerprint and circ.status == "BUILT" and len(circ.path) == 1:
                 self.cached_type = Category.DIRECTORY
 
             # if we fell through, eliminate ourselves as a directory connection
@@ -606,7 +626,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
       listing_type - primary attribute we're listing connections by
     """
 
-    conn = tor_tools.get_conn()
+    controller = tor_controller()
     my_type = self.get_type()
     destination_address = self.get_destination_label(26, include_locale = True)
 
@@ -621,7 +641,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
     src, dst, etc = "", "", ""
 
     if listing_type == entries.ListingType.IP_ADDRESS:
-      my_external_address = conn.get_info("address", self.local.get_address())
+      my_external_address = controller.get_info("address", self.local.get_address())
       address_differ = my_external_address != self.local.get_address()
 
       # Expanding doesn't make sense, if the connection isn't actually
@@ -771,13 +791,13 @@ class ConnectionLine(entries.ConnectionPanelLine):
     #   exit connection)
 
     fingerprint = self.foreign.get_fingerprint()
-    conn = tor_tools.get_conn()
+    controller = tor_controller()
 
     if fingerprint != "UNKNOWN":
       # single match - display information available about it
 
-      ns_entry = conn.get_consensus_entry(fingerprint)
-      desc_entry = conn.get_descriptor_entry(fingerprint)
+      ns_entry = controller.get_info("ns/id/%s" % fingerprint, None)
+      desc_entry = controller.get_info("desc/id/%s" % fingerprint, None)
 
       # append the fingerprint to the second line
 
@@ -804,7 +824,11 @@ class ConnectionLine(entries.ConnectionPanelLine):
         if len(ns_lines) >= 2 and ns_lines[1].startswith("s "):
           flags = ns_lines[1][2:]
 
-        exit_policy = conn.get_relay_exit_policy(fingerprint)
+        exit_policy = None
+        descriptor = controller.get_server_descriptor(fingerprint, None)
+
+        if descriptor:
+          exit_policy = descriptor.exit_policy
 
         if exit_policy:
           policy_label = exit_policy.summary()
@@ -847,7 +871,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
         if contact:
           lines[6] = "contact: %s" % contact
     else:
-      all_matches = conn.get_relay_fingerprint(self.foreign.get_address(), get_all_matches = True)
+      all_matches = get_fingerprint_tracker().get_relay_fingerprint(self.foreign.get_address(), get_all_matches = True)
 
       if all_matches:
         # multiple matches
@@ -932,9 +956,9 @@ class ConnectionLine(entries.ConnectionPanelLine):
           destination_address += " (%s)" % purpose
       elif not connection.is_private_address(self.foreign.get_address()):
         extra_info = []
-        conn = tor_tools.get_conn()
+        controller = tor_controller()
 
-        if include_locale and not conn.is_geoip_unavailable():
+        if include_locale and not controller.is_geoip_unavailable():
           foreign_locale = self.foreign.get_locale("??")
           extra_info.append(foreign_locale)
           space_available -= len(foreign_locale) + 2
@@ -955,3 +979,280 @@ class ConnectionLine(entries.ConnectionPanelLine):
           destination_address += " (%s)" % ", ".join(extra_info)
 
     return destination_address[:max_length]
+
+
+def get_hidden_service_ports(controller, default = []):
+  """
+  Provides the target ports hidden services are configured to use.
+
+  Arguments:
+    default - value provided back if unable to query the hidden service ports
+  """
+
+  result = []
+  hs_options = controller.get_conf_map("HiddenServiceOptions", {})
+
+  for entry in hs_options.get("HiddenServicePort", []):
+    # HiddenServicePort entries are of the form...
+    #
+    #   VIRTPORT [TARGET]
+    #
+    # ... with the TARGET being an address, port, or address:port. If the
+    # target port isn't defined then uses the VIRTPORT.
+
+    hs_port = None
+
+    if ' ' in entry:
+      virtport, target = entry.split(' ', 1)
+
+      if ':' in target:
+        hs_port = target.split(':', 1)[1]  # target is an address:port
+      elif target.isdigit():
+        hs_port = target  # target is a port
+      else:
+        hs_port = virtport  # target is an address
+    else:
+      hs_port = entry  # just has the virtual port
+
+    if hs_port.isdigit():
+      result.append(hs_port)
+
+  if result:
+    return result
+  else:
+    return default
+
+
+def is_exiting_allowed(controller, ip_address, port):
+  """
+  Checks if the given destination can be exited to by this relay, returning
+  True if so and False otherwise.
+  """
+
+  result = False
+
+  if controller.is_alive():
+    # If we allow any exiting then this could be relayed DNS queries,
+    # otherwise the policy is checked. Tor still makes DNS connections to
+    # test when exiting isn't allowed, but nothing is relayed over them.
+    # I'm registering these as non-exiting to avoid likely user confusion:
+    # https://trac.torproject.org/projects/tor/ticket/965
+
+    our_policy = controller.get_exit_policy(None)
+
+    if our_policy and our_policy.is_exiting_allowed() and port == "53":
+      result = True
+    else:
+      result = our_policy and our_policy.can_exit_to(ip_address, port)
+
+  return result
+
+
+class FingerprintTracker:
+  def __init__(self):
+    # mappings of ip -> [(port, fingerprint), ...]
+
+    self._fingerprint_mappings = None
+
+    # lookup cache with (ip, port) -> fingerprint mappings
+
+    self._fingerprint_lookup_cache = {}
+
+    # lookup cache with fingerprint -> nickname mappings
+
+    self._nickname_lookup_cache = {}
+
+    controller = tor_controller()
+
+    controller.add_event_listener(self.new_consensus_event, stem.control.EventType.NEWCONSENSUS)
+    controller.add_event_listener(self.new_desc_event, stem.control.EventType.NEWDESC)
+
+  def new_consensus_event(self, event):
+    self._fingerprint_lookup_cache = {}
+    self._nickname_lookup_cache = {}
+
+    if self._fingerprint_mappings is not None:
+      self._fingerprint_mappings = self._get_fingerprint_mappings(event.desc)
+
+  def new_desc_event(self, event):
+    # If we're tracking ip address -> fingerprint mappings then update with
+    # the new relays.
+
+    self._fingerprint_lookup_cache = {}
+
+    if self._fingerprint_mappings is not None:
+      desc_fingerprints = [fingerprint for (fingerprint, nickname) in event.relays]
+
+      for fingerprint in desc_fingerprints:
+        # gets consensus data for the new descriptor
+
+        try:
+          desc = tor_controller().get_network_status(fingerprint)
+        except stem.ControllerError:
+          continue
+
+        # updates fingerprintMappings with new data
+
+        if desc.address in self._fingerprint_mappings:
+          # if entry already exists with the same orport, remove it
+
+          orport_match = None
+
+          for entry_port, entry_fingerprint in self._fingerprint_mappings[desc.address]:
+            if entry_port == desc.or_port:
+              orport_match = (entry_port, entry_fingerprint)
+              break
+
+          if orport_match:
+            self._fingerprint_mappings[desc.address].remove(orport_match)
+
+          # add the new entry
+
+          self._fingerprint_mappings[desc.address].append((desc.or_port, desc.fingerprint))
+        else:
+          self._fingerprint_mappings[desc.address] = [(desc.or_port, desc.fingerprint)]
+
+  def get_relay_fingerprint(self, relay_address, relay_port = None, get_all_matches = False):
+    """
+    Provides the fingerprint associated with the given address. If there's
+    multiple potential matches or the mapping is unknown then this returns
+    None. This disambiguates the fingerprint if there's multiple relays on
+    the same ip address by several methods, one of them being to pick relays
+    we have a connection with.
+
+    Arguments:
+      relay_address  - address of relay to be returned
+      relay_port     - orport of relay (to further narrow the results)
+      get_all_matches - ignores the relay_port and provides all of the
+                      (port, fingerprint) tuples matching the given
+                      address
+    """
+
+    result = None
+    controller = tor_controller()
+
+    if controller.is_alive():
+      if get_all_matches:
+        # populates the ip -> fingerprint mappings if not yet available
+        if self._fingerprint_mappings is None:
+          self._fingerprint_mappings = self._get_fingerprint_mappings()
+
+        if relay_address in self._fingerprint_mappings:
+          result = self._fingerprint_mappings[relay_address]
+        else:
+          result = []
+      else:
+        # query the fingerprint if it isn't yet cached
+        if not (relay_address, relay_port) in self._fingerprint_lookup_cache:
+          relay_fingerprint = self._get_relay_fingerprint(controller, relay_address, relay_port)
+          self._fingerprint_lookup_cache[(relay_address, relay_port)] = relay_fingerprint
+
+        result = self._fingerprint_lookup_cache[(relay_address, relay_port)]
+
+    return result
+
+  def get_relay_nickname(self, relay_fingerprint):
+    """
+    Provides the nickname associated with the given relay. This provides None
+    if no such relay exists, and "Unnamed" if the name hasn't been set.
+
+    Arguments:
+      relay_fingerprint - fingerprint of the relay
+    """
+
+    result = None
+    controller = tor_controller()
+
+    if controller.is_alive():
+      # query the nickname if it isn't yet cached
+      if not relay_fingerprint in self._nickname_lookup_cache:
+        if relay_fingerprint == controller.get_info("fingerprint", None):
+          # this is us, simply check the config
+          my_nickname = controller.get_conf("Nickname", "Unnamed")
+          self._nickname_lookup_cache[relay_fingerprint] = my_nickname
+        else:
+          ns_entry = controller.get_network_status(relay_fingerprint, None)
+
+          if ns_entry:
+            self._nickname_lookup_cache[relay_fingerprint] = ns_entry.nickname
+
+      result = self._nickname_lookup_cache[relay_fingerprint]
+
+    return result
+
+  def _get_relay_fingerprint(self, controller, relay_address, relay_port):
+    """
+    Provides the fingerprint associated with the address/port combination.
+
+    Arguments:
+      relay_address - address of relay to be returned
+      relay_port    - orport of relay (to further narrow the results)
+    """
+
+    # If we were provided with a string port then convert to an int (so
+    # lookups won't mismatch based on type).
+
+    if isinstance(relay_port, str):
+      relay_port = int(relay_port)
+
+    # checks if this matches us
+
+    if relay_address == controller.get_info("address", None):
+      if not relay_port or str(relay_port) == controller.get_conf("ORPort", None):
+        return controller.get_info("fingerprint", None)
+
+    # if we haven't yet populated the ip -> fingerprint mappings then do so
+
+    if self._fingerprint_mappings is None:
+      self._fingerprint_mappings = self._get_fingerprint_mappings()
+
+    potential_matches = self._fingerprint_mappings.get(relay_address)
+
+    if not potential_matches:
+      return None  # no relay matches this ip address
+
+    if len(potential_matches) == 1:
+      # There's only one relay belonging to this ip address. If the port
+      # matches then we're done.
+
+      match = potential_matches[0]
+
+      if relay_port and match[0] != relay_port:
+        return None
+      else:
+        return match[1]
+    elif relay_port:
+      # Multiple potential matches, so trying to match based on the port.
+      for entry_port, entry_fingerprint in potential_matches:
+        if entry_port == relay_port:
+          return entry_fingerprint
+
+    return None
+
+  def _get_fingerprint_mappings(self, descriptors = None):
+    """
+    Provides IP address to (port, fingerprint) tuple mappings for all of the
+    currently cached relays.
+
+    Arguments:
+      descriptors - router status entries (fetched if not provided)
+    """
+
+    results = {}
+    controller = tor_controller()
+
+    if controller.is_alive():
+      # fetch the current network status if not provided
+
+      if not descriptors:
+        try:
+          descriptors = controller.get_network_statuses()
+        except stem.ControllerError:
+          descriptors = []
+
+      # construct mappings of ips to relay data
+
+      for desc in descriptors:
+        results.setdefault(desc.address, []).append((desc.or_port, desc.fingerprint))
+
+    return results
diff --git a/arm/connections/conn_panel.py b/arm/connections/conn_panel.py
index 5ccd880..fe9b5a6 100644
--- a/arm/connections/conn_panel.py
+++ b/arm/connections/conn_panel.py
@@ -11,7 +11,7 @@ import arm.popups
 import arm.util.tracker
 
 from arm.connections import count_popup, descriptor_popup, entries, conn_entry, circ_entry
-from arm.util import panel, tor_tools, tracker, ui_tools
+from arm.util import panel, tor_controller, tracker, ui_tools
 
 from stem.control import State
 from stem.util import conf, connection, enum
@@ -88,8 +88,8 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     # If we're a bridge and been running over a day then prepopulates with the
     # last day's clients.
 
-    conn = tor_tools.get_conn()
-    bridge_clients = conn.get_info("status/clients-seen", None)
+    controller = tor_controller()
+    bridge_clients = controller.get_info("status/clients-seen", None)
 
     if bridge_clients:
       # Response has a couple arguments...
@@ -129,7 +129,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
 
     # listens for when tor stops so we know to stop reflecting changes
 
-    conn.add_status_listener(self.tor_state_listener)
+    controller.add_status_listener(self.tor_state_listener)
 
   def tor_state_listener(self, controller, event_type, _):
     """
@@ -217,18 +217,30 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     True if client connections are permissable, false otherwise.
     """
 
-    conn = tor_tools.get_conn()
-    return "Guard" in conn.get_my_flags([]) or conn.get_option("BridgeRelay", None) == "1"
+    controller = tor_controller()
+
+    my_flags = []
+    my_fingerprint = self.get_info("fingerprint", None)
+
+    if my_fingerprint:
+      my_status_entry = self.controller.get_network_status(my_fingerprint)
+
+      if my_status_entry:
+        my_flags = my_status_entry.flags
+
+    return "Guard" in my_flags or controller.get_conf("BridgeRelay", None) == "1"
 
   def is_exits_allowed(self):
     """
     True if exit connections are permissable, false otherwise.
     """
 
-    if not tor_tools.get_conn().get_option("ORPort", None):
+    controller = tor_controller()
+
+    if not controller.get_conf("ORPort", None):
       return False  # no ORPort
 
-    policy = tor_tools.get_conn().get_exit_policy()
+    policy = controller.get_exit_policy(None)
 
     return policy and policy.is_exiting_allowed()
 
@@ -508,12 +520,12 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     new_connections = [(conn.local_address, conn.local_port, conn.remote_address, conn.remote_port) for conn in conn_resolver.get_connections()]
     new_circuits = {}
 
-    for circuit_id, status, purpose, path in tor_tools.get_conn().get_circuits():
+    for circ in tor_controller().get_circuits():
       # Skips established single-hop circuits (these are for directory
       # fetches, not client circuits)
 
-      if not (status == "BUILT" and len(path) == 1):
-        new_circuits[circuit_id] = (status, purpose, path)
+      if not (circ.status == "BUILT" and len(circ.path) == 1):
+        new_circuits[circ.id] = (circ.status, circ.purpose, [entry[0] for entry in circ.path])
 
     # Populates new_entries with any of our old entries that still exist.
     # This is both for performance and to keep from resetting the uptime
diff --git a/arm/connections/descriptor_popup.py b/arm/connections/descriptor_popup.py
index 6dbad9c..9404224 100644
--- a/arm/connections/descriptor_popup.py
+++ b/arm/connections/descriptor_popup.py
@@ -8,7 +8,7 @@ import curses
 import arm.popups
 import arm.connections.conn_entry
 
-from arm.util import panel, tor_tools, ui_tools
+from arm.util import panel, tor_controller, ui_tools
 
 # field keywords used to identify areas for coloring
 
@@ -116,10 +116,10 @@ def get_display_text(fingerprint):
   if not fingerprint:
     return [UNRESOLVED_MSG]
 
-  conn, description = tor_tools.get_conn(), []
+  controller, description = tor_controller(), []
 
   description.append("ns/id/%s" % fingerprint)
-  consensus_entry = conn.get_consensus_entry(fingerprint)
+  consensus_entry = controller.get_info("ns/id/%s" % fingerprint, None)
 
   if consensus_entry:
     description += consensus_entry.split("\n")
@@ -127,7 +127,7 @@ def get_display_text(fingerprint):
     description += [ERROR_MSG, ""]
 
   description.append("desc/id/%s" % fingerprint)
-  descriptor_entry = conn.get_descriptor_entry(fingerprint)
+  descriptor_entry = controller.get_info("desc/id/%s" % fingerprint, None)
 
   if descriptor_entry:
     description += descriptor_entry.split("\n")
diff --git a/arm/controller.py b/arm/controller.py
index e684215..e1c3991 100644
--- a/arm/controller.py
+++ b/arm/controller.py
@@ -22,9 +22,11 @@ import arm.graphing.resource_stats
 import arm.connections.conn_panel
 import arm.util.tracker
 
+import stem
+
 from stem.control import State
 
-from arm.util import panel, tor_config, tor_tools, ui_tools
+from arm.util import panel, tor_config, tor_controller, ui_tools
 
 from stem.util import conf, enum, log, system
 
@@ -128,7 +130,7 @@ def init_controller(stdscr, start_time):
     # functioning. It'll have circuits, but little else. If this is the case then
     # notify the user and tell them what they can do to fix it.
 
-    controller = tor_tools.get_conn().controller
+    controller = tor_controller()
 
     if controller.get_conf("DisableDebuggerAttachment", None) == "1":
       log.notice("Tor is preventing system utilities like netstat and lsof from working. This means that arm can't provide you with connection information. You can change this by adding 'DisableDebuggerAttachment 0' to your torrc and restarting tor. For more information see...\nhttps://trac.torproject.org/3313")
@@ -195,7 +197,7 @@ def init_controller(stdscr, start_time):
 
     # prepopulates bandwidth values from state file
 
-    if CONFIG["features.graph.bw.prepopulate"] and tor_tools.get_conn().is_alive():
+    if CONFIG["features.graph.bw.prepopulate"] and tor_controller().is_alive():
       is_successful = bw_stats.prepopulate_from_state()
 
       if is_successful:
@@ -528,7 +530,7 @@ class Controller:
 
     if is_shutdown_flag_present:
       try:
-        tor_tools.get_conn().shutdown()
+        tor_controller().close()
       except IOError as exc:
         arm.popups.show_msg(str(exc), 3, curses.A_BOLD)
 
@@ -541,10 +543,10 @@ def heartbeat_check(is_unresponsive):
     is_unresponsive - flag for if we've indicated to be responsive or not
   """
 
-  conn = tor_tools.get_conn()
-  last_heartbeat = conn.controller.get_latest_heartbeat()
+  controller = tor_controller()
+  last_heartbeat = controller.get_latest_heartbeat()
 
-  if conn.is_alive():
+  if controller.is_alive():
     if not is_unresponsive and (time.time() - last_heartbeat) >= 10:
       is_unresponsive = True
       log.notice("Relay unresponsive (last heartbeat: %s)" % time.ctime(last_heartbeat))
@@ -674,7 +676,7 @@ def start_arm(stdscr):
 
       if confirmation_key in (ord('x'), ord('X')):
         try:
-          tor_tools.get_conn().reload()
+          tor_controller().signal(stem.Signal.RELOAD)
         except IOError as exc:
           log.error("Error detected when reloading tor: %s" % exc.strerror)
     elif key == ord('h') or key == ord('H'):
diff --git a/arm/graphing/bandwidth_stats.py b/arm/graphing/bandwidth_stats.py
index 9cba96f..b781d3e 100644
--- a/arm/graphing/bandwidth_stats.py
+++ b/arm/graphing/bandwidth_stats.py
@@ -9,7 +9,7 @@ import curses
 import arm.controller
 
 from arm.graphing import graph_panel
-from arm.util import tor_tools, ui_tools
+from arm.util import tor_controller, ui_tools
 
 from stem.control import State
 from stem.util import conf, log, str_tools, system
@@ -65,13 +65,13 @@ class BandwidthStats(graph_panel.GraphStats):
     # listens for tor reload (sighup) events which can reset the bandwidth
     # rate/burst and if tor's using accounting
 
-    conn = tor_tools.get_conn()
+    controller = tor_controller()
     self._title_stats, self.is_accounting = [], False
 
     if not is_pause_buffer:
-      self.reset_listener(conn.get_controller(), State.INIT, None)  # initializes values
+      self.reset_listener(controller, State.INIT, None)  # initializes values
 
-    conn.add_status_listener(self.reset_listener)
+    controller.add_status_listener(self.reset_listener)
 
     # Initialized the bandwidth totals to the values reported by Tor. This
     # uses a controller options introduced in ticket 2345:
@@ -83,12 +83,12 @@ class BandwidthStats(graph_panel.GraphStats):
     self.initial_primary_total = 0
     self.initial_secondary_total = 0
 
-    read_total = conn.get_info("traffic/read", None)
+    read_total = controller.get_info("traffic/read", None)
 
     if read_total and read_total.isdigit():
       self.initial_primary_total = int(read_total) / 1024  # Bytes -> KB
 
-    write_total = conn.get_info("traffic/written", None)
+    write_total = controller.get_info("traffic/written", None)
 
     if write_total and write_total.isdigit():
       self.initial_secondary_total = int(write_total) / 1024  # Bytes -> KB
@@ -138,8 +138,8 @@ class BandwidthStats(graph_panel.GraphStats):
 
     # checks that this is a relay (if ORPort is unset, then skip)
 
-    conn = tor_tools.get_conn()
-    or_port = conn.get_option("ORPort", None)
+    controller = tor_controller()
+    or_port = controller.get_conf("ORPort", None)
 
     if or_port == "0":
       return
@@ -150,7 +150,7 @@ class BandwidthStats(graph_panel.GraphStats):
     # something else
 
     uptime = None
-    query_pid = conn.controller.get_pid(None)
+    query_pid = controller.get_pid(None)
 
     if query_pid:
       query_param = ["%cpu", "rss", "%mem", "etime"]
@@ -174,7 +174,7 @@ class BandwidthStats(graph_panel.GraphStats):
 
     # get the user's data directory (usually '~/.tor')
 
-    data_dir = conn.get_option("DataDirectory", None)
+    data_dir = controller.get_conf("DataDirectory", None)
 
     if not data_dir:
       msg = PREPOPULATE_FAILURE_MSG % "data directory not found"
@@ -313,7 +313,7 @@ class BandwidthStats(graph_panel.GraphStats):
     # provides accounting stats if enabled
 
     if self.is_accounting:
-      if tor_tools.get_conn().is_alive():
+      if tor_controller().is_alive():
         status = self.accounting_info["status"]
 
         hibernate_color = "green"
@@ -402,19 +402,19 @@ class BandwidthStats(graph_panel.GraphStats):
   def new_desc_event(self, event):
     # updates self._title_stats with updated values
 
-    conn = tor_tools.get_conn()
+    controller = tor_controller()
 
-    if not conn.is_alive():
+    if not controller.is_alive():
       return  # keep old values
 
-    my_fingerprint = conn.get_info("fingerprint", None)
+    my_fingerprint = controller.get_info("fingerprint", None)
 
     if not self._title_stats or not my_fingerprint or (event and my_fingerprint in event.idlist):
       stats = []
-      bw_rate = conn.get_my_bandwidth_rate()
-      bw_burst = conn.get_my_bandwidth_burst()
-      bw_observed = conn.get_my_bandwidth_observed()
-      bw_measured = conn.get_my_bandwidth_measured()
+      bw_rate = get_my_bandwidth_rate(controller)
+      bw_burst = get_my_bandwidth_burst(controller)
+      bw_observed = get_my_bandwidth_observed(controller)
+      bw_measured = get_my_bandwidth_measured(controller)
       label_in_bytes = CONFIG["features.graph.bw.transferInBytes"]
 
       if bw_rate and bw_burst:
@@ -460,13 +460,13 @@ class BandwidthStats(graph_panel.GraphStats):
     Any failed lookups result in a mapping to an empty string.
     """
 
-    conn = tor_tools.get_conn()
+    controller = tor_controller()
     queried = dict([(arg, "") for arg in ACCOUNTING_ARGS])
-    queried["status"] = conn.get_info("accounting/hibernating", None)
+    queried["status"] = controller.get_info("accounting/hibernating", None)
 
     # provides a nicely formatted reset time
 
-    end_interval = conn.get_info("accounting/interval-end", None)
+    end_interval = controller.get_info("accounting/interval-end", None)
 
     if end_interval:
       # converts from gmt to local with respect to DST
@@ -491,8 +491,8 @@ class BandwidthStats(graph_panel.GraphStats):
 
     # number of bytes used and in total for the accounting period
 
-    used = conn.get_info("accounting/bytes", None)
-    left = conn.get_info("accounting/bytes-left", None)
+    used = controller.get_info("accounting/bytes", None)
+    left = controller.get_info("accounting/bytes-left", None)
 
     if used and left:
       used_comp, left_comp = used.split(" "), left.split(" ")
@@ -506,3 +506,95 @@ class BandwidthStats(graph_panel.GraphStats):
 
     self.accounting_info = queried
     self.accounting_last_updated = time.time()
+
+
+def get_my_bandwidth_rate(controller):
+  """
+  Provides the effective relaying bandwidth rate of this relay. Currently
+  this doesn't account for SETCONF events.
+  """
+
+  # effective relayed bandwidth is the minimum of BandwidthRate,
+  # MaxAdvertisedBandwidth, and RelayBandwidthRate (if set)
+
+  effective_rate = int(controller.get_conf("BandwidthRate", None))
+
+  relay_rate = controller.get_conf("RelayBandwidthRate", None)
+
+  if relay_rate and relay_rate != "0":
+    effective_rate = min(effective_rate, int(relay_rate))
+
+  max_advertised = controller.get_conf("MaxAdvertisedBandwidth", None)
+
+  if max_advertised:
+    effective_rate = min(effective_rate, int(max_advertised))
+
+  if effective_rate is not None:
+    return effective_rate
+  else:
+    return None
+
+
+def get_my_bandwidth_burst(controller):
+  """
+  Provides the effective bandwidth burst rate of this relay. Currently this
+  doesn't account for SETCONF events.
+  """
+
+  # effective burst (same for BandwidthBurst and RelayBandwidthBurst)
+  effective_burst = int(controller.get_conf("BandwidthBurst", None))
+
+  relay_burst = controller.get_conf("RelayBandwidthBurst", None)
+
+  if relay_burst and relay_burst != "0":
+    effective_burst = min(effective_burst, int(relay_burst))
+
+  if effective_burst is not None:
+    return effective_burst
+  else:
+    return None
+
+
+def get_my_bandwidth_observed(controller):
+  """
+  Provides the relay's current observed bandwidth (the throughput determined
+  from historical measurements on the client side). This is used in the
+  heuristic used for path selection if the measured bandwidth is undefined.
+  This is fetched from the descriptors and hence will get stale if
+  descriptors aren't periodically updated.
+  """
+
+  my_fingerprint = controller.get_info("fingerprint", None)
+
+  if my_fingerprint:
+    my_descriptor = controller.get_server_descriptor(my_fingerprint)
+
+    if my_descriptor:
+      return my_descriptor.observed_bandwidth
+
+  return None
+
+
+def get_my_bandwidth_measured(controller):
+  """
+  Provides the relay's current measured bandwidth (the throughput as noted by
+  the directory authorities and used by clients for relay selection). This is
+  undefined if not in the consensus or with older versions of Tor. Depending
+  on the circumstances this can be from a variety of things (observed,
+  measured, weighted measured, etc) as described by:
+  https://trac.torproject.org/projects/tor/ticket/1566
+  """
+
+  # TODO: Tor is documented as providing v2 router status entries but
+  # actually looks to be v3. This needs to be sorted out between stem
+  # and tor.
+
+  my_fingerprint = controller.get_info("fingerprint", None)
+
+  if my_fingerprint:
+    my_status_entry = controller.get_network_status(my_fingerprint)
+
+    if my_status_entry and hasattr(my_status_entry, 'bandwidth'):
+      return my_status_entry.bandwidth
+
+  return None
diff --git a/arm/graphing/conn_stats.py b/arm/graphing/conn_stats.py
index 6694647..5e2d2f5 100644
--- a/arm/graphing/conn_stats.py
+++ b/arm/graphing/conn_stats.py
@@ -5,7 +5,7 @@ Tracks stats concerning tor's current connections.
 import arm.util.tracker
 
 from arm.graphing import graph_panel
-from arm.util import tor_tools
+from arm.util import tor_controller
 
 from stem.control import State
 
@@ -21,10 +21,10 @@ class ConnStats(graph_panel.GraphStats):
 
     # listens for tor reload (sighup) events which can reset the ports tor uses
 
-    conn = tor_tools.get_conn()
+    controller = tor_controller()
     self.or_port, self.dir_port, self.control_port = "0", "0", "0"
-    self.reset_listener(conn.get_controller(), State.INIT, None)  # initialize port values
-    conn.add_status_listener(self.reset_listener)
+    self.reset_listener(controller, State.INIT, None)  # initialize port values
+    controller.add_status_listener(self.reset_listener)
 
   def clone(self, new_copy=None):
     if not new_copy:
diff --git a/arm/graphing/graph_panel.py b/arm/graphing/graph_panel.py
index 8d2b5ca..f36b090 100644
--- a/arm/graphing/graph_panel.py
+++ b/arm/graphing/graph_panel.py
@@ -24,7 +24,7 @@ import arm.controller
 
 import stem.control
 
-from arm.util import panel, tor_tools, ui_tools
+from arm.util import panel, tor_controller, ui_tools
 
 from stem.util import conf, enum, str_tools
 
@@ -121,7 +121,7 @@ class GraphStats:
 
     # tracks BW events
 
-    tor_tools.get_conn().add_event_listener(self.bandwidth_event, stem.control.EventType.BW)
+    tor_controller().add_event_listener(self.bandwidth_event, stem.control.EventType.BW)
 
   def clone(self, new_copy=None):
     """
diff --git a/arm/graphing/resource_stats.py b/arm/graphing/resource_stats.py
index 2803bd1..71b6790 100644
--- a/arm/graphing/resource_stats.py
+++ b/arm/graphing/resource_stats.py
@@ -5,7 +5,7 @@ Tracks the system resource usage (cpu and memory) of the tor process.
 import arm.util.tracker
 
 from arm.graphing import graph_panel
-from arm.util import tor_tools
+from arm.util import tor_controller
 
 from stem.util import str_tools
 
@@ -17,7 +17,7 @@ class ResourceStats(graph_panel.GraphStats):
 
   def __init__(self):
     graph_panel.GraphStats.__init__(self)
-    self.query_pid = tor_tools.get_conn().controller.get_pid(None)
+    self.query_pid = tor_controller().get_pid(None)
     self.last_counter = None
 
   def clone(self, new_copy=None):
diff --git a/arm/log_panel.py b/arm/log_panel.py
index bf96782..f782e6b 100644
--- a/arm/log_panel.py
+++ b/arm/log_panel.py
@@ -19,7 +19,7 @@ from stem.util import conf, log, system
 import arm.arguments
 import arm.popups
 from arm import __version__
-from arm.util import panel, tor_tools, ui_tools
+from arm.util import panel, tor_controller, ui_tools
 
 RUNLEVEL_EVENT_COLOR = {
   log.DEBUG: "magenta",
@@ -146,7 +146,7 @@ def get_log_file_entries(runlevels, read_limit = None, add_limit = None):
 
   logging_types, logging_location = None, None
 
-  for logging_entry in tor_tools.get_conn().get_option("Log", [], True):
+  for logging_entry in tor_controller().get_conf("Log", [], True):
     # looks for an entry like: notice file /var/log/tor/notices.log
 
     entry_comp = logging_entry.split()
@@ -539,8 +539,8 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
     # adds listeners for tor and stem events
 
-    conn = tor_tools.get_conn()
-    conn.add_status_listener(self._reset_listener)
+    controller = tor_controller()
+    controller.add_status_listener(self._reset_listener)
 
     # opens log file if we'll be saving entries
 
@@ -1191,12 +1191,12 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     if "UNKNOWN" in events:
       tor_events.update(set(arm.arguments.missing_event_types()))
 
-    tor_conn = tor_tools.get_conn()
-    tor_conn.remove_event_listener(self.register_tor_event)
+    controller = tor_controller()
+    controller.remove_event_listener(self.register_tor_event)
 
     for event_type in list(tor_events):
       try:
-        tor_conn.add_event_listener(self.register_tor_event, event_type)
+        controller.add_event_listener(self.register_tor_event, event_type)
       except stem.ProtocolError:
         tor_events.remove(event_type)
 
diff --git a/arm/menu/actions.py b/arm/menu/actions.py
index c968099..3aa941d 100644
--- a/arm/menu/actions.py
+++ b/arm/menu/actions.py
@@ -10,8 +10,9 @@ import arm.menu.item
 import arm.graphing.graph_panel
 import arm.util.tracker
 
-from arm.util import tor_tools, ui_tools
+from arm.util import tor_controller, ui_tools
 
+import stem
 import stem.util.connection
 
 from stem.util import conf, str_tools
@@ -60,16 +61,16 @@ def make_actions_menu():
   """
 
   control = arm.controller.get_controller()
-  conn = tor_tools.get_conn()
+  controller = tor_controller()
   header_panel = control.get_panel("header")
   actions_menu = arm.menu.item.Submenu("Actions")
   actions_menu.add(arm.menu.item.MenuItem("Close Menu", None))
   actions_menu.add(arm.menu.item.MenuItem("New Identity", header_panel.send_newnym))
 
-  if conn.is_alive():
-    actions_menu.add(arm.menu.item.MenuItem("Stop Tor", conn.shutdown))
+  if controller.is_alive():
+    actions_menu.add(arm.menu.item.MenuItem("Stop Tor", controller.close))
 
-  actions_menu.add(arm.menu.item.MenuItem("Reset Tor", conn.reload))
+  actions_menu.add(arm.menu.item.MenuItem("Reset Tor", functools.partial(controller.signal, stem.Signal.RELOAD)))
 
   if control.is_paused():
     label, arg = "Unpause", False
diff --git a/arm/starter.py b/arm/starter.py
index 8bb1300..9493415 100644
--- a/arm/starter.py
+++ b/arm/starter.py
@@ -18,7 +18,6 @@ import arm.arguments
 import arm.controller
 import arm.util.panel
 import arm.util.tor_config
-import arm.util.tor_tools
 import arm.util.tracker
 import arm.util.ui_tools
 
@@ -68,11 +67,6 @@ def main():
   try:
     controller = init_controller(args)
     authenticate(controller, CONFIG.get('tor.password', None), CONFIG.get('tor.chroot', ''))
-
-    # TODO: Our tor_controller() method will gradually replace the tor_tools
-    # module, but until that we need to initialize it too.
-
-    arm.util.tor_tools.get_conn().init(controller)
   except ValueError as exc:
     print exc
     exit(1)
diff --git a/arm/torrc_panel.py b/arm/torrc_panel.py
index 7957abc..e561478 100644
--- a/arm/torrc_panel.py
+++ b/arm/torrc_panel.py
@@ -8,7 +8,7 @@ import threading
 
 import arm.popups
 
-from arm.util import panel, tor_config, tor_tools, ui_tools
+from arm.util import panel, tor_config, tor_controller, ui_tools
 
 from stem.control import State
 from stem.util import conf, enum
@@ -52,10 +52,10 @@ class TorrcPanel(panel.Panel):
 
     # listens for tor reload (sighup) events
 
-    conn = tor_tools.get_conn()
-    conn.add_status_listener(self.reset_listener)
+    controller = tor_controller()
+    controller.add_status_listener(self.reset_listener)
 
-    if conn.is_alive():
+    if controller.is_alive():
       self.reset_listener(None, State.INIT, None)
 
   def reset_listener(self, controller, event_type, _):
diff --git a/arm/util/tor_config.py b/arm/util/tor_config.py
index 84d2882..33b53a3 100644
--- a/arm/util/tor_config.py
+++ b/arm/util/tor_config.py
@@ -9,7 +9,7 @@ import threading
 
 import stem.version
 
-from arm.util import tor_tools, ui_tools
+from arm.util import tor_controller, ui_tools
 
 from stem.util import conf, enum, log, str_tools, system
 
@@ -171,7 +171,7 @@ def load_option_descriptions(load_path = None, check_version = True):
         if version_line.startswith("Tor Version "):
           file_version = version_line[12:]
           loaded_version = file_version
-          tor_version = tor_tools.get_conn().get_info("version", "")
+          tor_version = tor_controller().get_info("version", "")
 
           if check_version and file_version != tor_version:
             msg = "wrong version, tor is %s but the file's from %s" % (tor_version, file_version)
@@ -226,8 +226,8 @@ def load_option_descriptions(load_path = None, check_version = True):
       # Fetches all options available with this tor instance. This isn't
       # vital, and the valid_options are left empty if the call fails.
 
-      conn, valid_options = tor_tools.get_conn(), []
-      config_option_query = conn.get_info("config/names", None)
+      controller, valid_options = tor_controller(), []
+      config_option_query = controller.get_info("config/names", None)
 
       if config_option_query:
         for line in config_option_query.strip().split("\n"):
@@ -340,7 +340,7 @@ def save_option_descriptions(path):
   sorted_options = CONFIG_DESCRIPTIONS.keys()
   sorted_options.sort()
 
-  tor_version = tor_tools.get_conn().get_info("version", "")
+  tor_version = tor_controller().get_info("version", "")
   output_file.write("Tor Version %s\n" % tor_version)
 
   for i in range(len(sorted_options)):
@@ -420,9 +420,9 @@ def get_config_location():
   path can't be determined.
   """
 
-  conn = tor_tools.get_conn()
-  config_location = conn.get_info("config-file", None)
-  tor_pid, tor_prefix = conn.controller.get_pid(None), CONFIG['tor.chroot']
+  controller = tor_controller()
+  config_location = controller.get_info("config-file", None)
+  tor_pid, tor_prefix = controller.controller.get_pid(None), CONFIG['tor.chroot']
 
   if not config_location:
     raise IOError("unable to query the torrc location")
@@ -446,9 +446,9 @@ def get_multiline_parameters():
   global MULTILINE_PARAM
 
   if MULTILINE_PARAM is None:
-    conn, multiline_entries = tor_tools.get_conn(), []
+    controller, multiline_entries = tor_controller(), []
 
-    config_option_query = conn.get_info("config/names", None)
+    config_option_query = controller.get_info("config/names", None)
 
     if config_option_query:
       for line in config_option_query.strip().split("\n"):
@@ -474,7 +474,7 @@ def get_custom_options(include_value = False):
                    this just contains the options
   """
 
-  config_text = tor_tools.get_conn().get_info("config-text", "").strip()
+  config_text = tor_controller().get_info("config-text", "").strip()
   config_lines = config_text.split("\n")
 
   # removes any duplicates
@@ -548,7 +548,7 @@ def save_conf(destination = None, contents = None):
     # double check that "GETINFO config-text" is unavailable rather than just
     # giving an empty result
 
-    if tor_tools.get_conn().get_info("config-text", None) is None:
+    if tor_controller().get_info("config-text", None) is None:
       raise IOError("determining the torrc requires Tor version 0.2.2.7")
 
   current_location = None
@@ -572,7 +572,7 @@ def save_conf(destination = None, contents = None):
 
   if is_saveconf:
     try:
-      tor_tools.get_conn().save_conf()
+      tor_controller().save_conf()
 
       try:
         get_torrc().load()
@@ -623,7 +623,7 @@ def validate(contents = None):
     contents - torrc contents
   """
 
-  conn = tor_tools.get_conn()
+  controller = tor_controller()
   custom_options = get_custom_options()
   issues_found, seen_options = [], []
 
@@ -708,7 +708,7 @@ def validate(contents = None):
 
     # issues GETCONF to get the values tor's currently configured to use
 
-    tor_values = conn.get_option(option, [], True)
+    tor_values = controller.get_conf(option, [], True)
 
     # multiline entries can be comma separated values (for both tor and conf)
 
@@ -950,7 +950,7 @@ class Torrc():
     if not self.is_loaded():
       return_val = None
     else:
-      tor_version = tor_tools.get_conn().get_version()
+      tor_version = tor_controller().get_version(None)
       skip_validation = not CONFIG["features.torrc.validate"]
       skip_validation |= (tor_version is None or not tor_version >= stem.version.Requirement.GETINFO_CONFIG_TEXT)
 
diff --git a/arm/util/tor_tools.py b/arm/util/tor_tools.py
index 4575616..1cefa98 100644
--- a/arm/util/tor_tools.py
+++ b/arm/util/tor_tools.py
@@ -8,7 +8,7 @@ import threading
 import stem
 import stem.control
 
-from stem.util import log, system
+from stem.util import log
 
 CONTROLLER = None  # singleton Controller instance
 
@@ -39,10 +39,6 @@ class Controller:
   def __init__(self):
     self.controller = None
     self.conn_lock = threading.RLock()
-    self._fingerprint_mappings = None     # mappings of ip -> [(port, fingerprint), ...]
-    self._fingerprint_lookup_cache = {}   # lookup cache with (ip, port) -> fingerprint mappings
-    self._nickname_lookup_cache = {}      # lookup cache with fingerprint -> nickname mappings
-    self._address_lookup_cache = {}       # lookup cache with fingerprint -> (ip address, or port) mappings
     self._consensus_lookup_cache = {}     # lookup cache with network status entries
     self._descriptor_lookup_cache = {}    # lookup cache with relay descriptors
 
@@ -73,10 +69,6 @@ class Controller:
 
       # reset caches for ip -> fingerprint lookups
 
-      self._fingerprint_mappings = None
-      self._fingerprint_lookup_cache = {}
-      self._nickname_lookup_cache = {}
-      self._address_lookup_cache = {}
       self._consensus_lookup_cache = {}
       self._descriptor_lookup_cache = {}
 
@@ -258,147 +250,6 @@ class Controller:
     else:
       return default
 
-  def get_hidden_service_ports(self, default = []):
-    """
-    Provides the target ports hidden services are configured to use.
-
-    Arguments:
-      default - value provided back if unable to query the hidden service ports
-    """
-
-    result = []
-    hs_options = self.controller.get_conf_map("HiddenServiceOptions", {})
-
-    for entry in hs_options.get("HiddenServicePort", []):
-      # HiddenServicePort entries are of the form...
-      #
-      #   VIRTPORT [TARGET]
-      #
-      # ... with the TARGET being an address, port, or address:port. If the
-      # target port isn't defined then uses the VIRTPORT.
-
-      hs_port = None
-
-      if ' ' in entry:
-        virtport, target = entry.split(' ', 1)
-
-        if ':' in target:
-          hs_port = target.split(':', 1)[1]  # target is an address:port
-        elif target.isdigit():
-          hs_port = target  # target is a port
-        else:
-          hs_port = virtport  # target is an address
-      else:
-        hs_port = entry  # just has the virtual port
-
-      if hs_port.isdigit():
-        result.append(hs_port)
-
-    if result:
-      return result
-    else:
-      return default
-
-  def get_my_bandwidth_rate(self, default = None):
-    """
-    Provides the effective relaying bandwidth rate of this relay. Currently
-    this doesn't account for SETCONF events.
-
-    Arguments:
-      default - result if the query fails
-    """
-
-    # effective relayed bandwidth is the minimum of BandwidthRate,
-    # MaxAdvertisedBandwidth, and RelayBandwidthRate (if set)
-
-    effective_rate = int(self.get_option("BandwidthRate", None))
-
-    relay_rate = self.get_option("RelayBandwidthRate", None)
-
-    if relay_rate and relay_rate != "0":
-      effective_rate = min(effective_rate, int(relay_rate))
-
-    max_advertised = self.get_option("MaxAdvertisedBandwidth", None)
-
-    if max_advertised:
-      effective_rate = min(effective_rate, int(max_advertised))
-
-    if effective_rate is not None:
-      return effective_rate
-    else:
-      return default
-
-  def get_my_bandwidth_burst(self, default = None):
-    """
-    Provides the effective bandwidth burst rate of this relay. Currently this
-    doesn't account for SETCONF events.
-
-    Arguments:
-      default - result if the query fails
-    """
-
-    # effective burst (same for BandwidthBurst and RelayBandwidthBurst)
-    effective_burst = int(self.get_option("BandwidthBurst", None))
-
-    relay_burst = self.get_option("RelayBandwidthBurst", None)
-
-    if relay_burst and relay_burst != "0":
-      effective_burst = min(effective_burst, int(relay_burst))
-
-    if effective_burst is not None:
-      return effective_burst
-    else:
-      return default
-
-  def get_my_bandwidth_observed(self, default = None):
-    """
-    Provides the relay's current observed bandwidth (the throughput determined
-    from historical measurements on the client side). This is used in the
-    heuristic used for path selection if the measured bandwidth is undefined.
-    This is fetched from the descriptors and hence will get stale if
-    descriptors aren't periodically updated.
-
-    Arguments:
-      default - result if the query fails
-    """
-
-    my_fingerprint = self.get_info("fingerprint", None)
-
-    if my_fingerprint:
-      my_descriptor = self.controller.get_server_descriptor(my_fingerprint)
-
-      if my_descriptor:
-        return my_descriptor.observed_bandwidth
-
-    return default
-
-  def get_my_bandwidth_measured(self, default = None):
-    """
-    Provides the relay's current measured bandwidth (the throughput as noted by
-    the directory authorities and used by clients for relay selection). This is
-    undefined if not in the consensus or with older versions of Tor. Depending
-    on the circumstances this can be from a variety of things (observed,
-    measured, weighted measured, etc) as described by:
-    https://trac.torproject.org/projects/tor/ticket/1566
-
-    Arguments:
-      default - result if the query fails
-    """
-
-    # TODO: Tor is documented as providing v2 router status entries but
-    # actually looks to be v3. This needs to be sorted out between stem
-    # and tor.
-
-    my_fingerprint = self.get_info("fingerprint", None)
-
-    if my_fingerprint:
-      my_status_entry = self.controller.get_network_status(my_fingerprint)
-
-      if my_status_entry and hasattr(my_status_entry, 'bandwidth'):
-        return my_status_entry.bandwidth
-
-    return default
-
   def get_my_flags(self, default = None):
     """
     Provides the flags held by this relay.
@@ -435,17 +286,6 @@ class Controller:
     finally:
       self.conn_lock.release()
 
-  def is_geoip_unavailable(self):
-    """
-    Provides true if we've concluded that our geoip database is unavailable,
-    false otherwise.
-    """
-
-    if self.is_alive():
-      return self.controller.is_geoip_unavailable()
-    else:
-      return False
-
   def get_my_user(self):
     """
     Provides the user this process is running under. If unavailable this
@@ -454,34 +294,6 @@ class Controller:
 
     return self.controller.get_user(None)
 
-  def is_exiting_allowed(self, ip_address, port):
-    """
-    Checks if the given destination can be exited to by this relay, returning
-    True if so and False otherwise.
-    """
-
-    self.conn_lock.acquire()
-
-    result = False
-
-    if self.is_alive():
-      # If we allow any exiting then this could be relayed DNS queries,
-      # otherwise the policy is checked. Tor still makes DNS connections to
-      # test when exiting isn't allowed, but nothing is relayed over them.
-      # I'm registering these as non-exiting to avoid likely user confusion:
-      # https://trac.torproject.org/projects/tor/ticket/965
-
-      our_policy = self.get_exit_policy()
-
-      if our_policy and our_policy.is_exiting_allowed() and port == "53":
-        result = True
-      else:
-        result = our_policy and our_policy.can_exit_to(ip_address, port)
-
-    self.conn_lock.release()
-
-    return result
-
   def get_exit_policy(self):
     """
     Provides an ExitPolicy instance for the head of this relay's exit policy
@@ -551,80 +363,6 @@ class Controller:
 
     return result
 
-  def get_relay_fingerprint(self, relay_address, relay_port = None, get_all_matches = False):
-    """
-    Provides the fingerprint associated with the given address. If there's
-    multiple potential matches or the mapping is unknown then this returns
-    None. This disambiguates the fingerprint if there's multiple relays on
-    the same ip address by several methods, one of them being to pick relays
-    we have a connection with.
-
-    Arguments:
-      relay_address  - address of relay to be returned
-      relay_port     - orport of relay (to further narrow the results)
-      get_all_matches - ignores the relay_port and provides all of the
-                      (port, fingerprint) tuples matching the given
-                      address
-    """
-
-    self.conn_lock.acquire()
-
-    result = None
-
-    if self.is_alive():
-      if get_all_matches:
-        # populates the ip -> fingerprint mappings if not yet available
-        if self._fingerprint_mappings is None:
-          self._fingerprint_mappings = self._get_fingerprint_mappings()
-
-        if relay_address in self._fingerprint_mappings:
-          result = self._fingerprint_mappings[relay_address]
-        else:
-          result = []
-      else:
-        # query the fingerprint if it isn't yet cached
-        if not (relay_address, relay_port) in self._fingerprint_lookup_cache:
-          relay_fingerprint = self._get_relay_fingerprint(relay_address, relay_port)
-          self._fingerprint_lookup_cache[(relay_address, relay_port)] = relay_fingerprint
-
-        result = self._fingerprint_lookup_cache[(relay_address, relay_port)]
-
-    self.conn_lock.release()
-
-    return result
-
-  def get_relay_nickname(self, relay_fingerprint):
-    """
-    Provides the nickname associated with the given relay. This provides None
-    if no such relay exists, and "Unnamed" if the name hasn't been set.
-
-    Arguments:
-      relay_fingerprint - fingerprint of the relay
-    """
-
-    self.conn_lock.acquire()
-
-    result = None
-
-    if self.is_alive():
-      # query the nickname if it isn't yet cached
-      if not relay_fingerprint in self._nickname_lookup_cache:
-        if relay_fingerprint == self.get_info("fingerprint", None):
-          # this is us, simply check the config
-          my_nickname = self.get_option("Nickname", "Unnamed")
-          self._nickname_lookup_cache[relay_fingerprint] = my_nickname
-        else:
-          ns_entry = self.controller.get_network_status(relay_fingerprint, None)
-
-          if ns_entry:
-            self._nickname_lookup_cache[relay_fingerprint] = ns_entry.nickname
-
-      result = self._nickname_lookup_cache[relay_fingerprint]
-
-    self.conn_lock.release()
-
-    return result
-
   def get_relay_exit_policy(self, relay_fingerprint):
     """
     Provides the ExitPolicy instance associated with the given relay. The tor
@@ -652,45 +390,6 @@ class Controller:
 
     return result
 
-  def get_relay_address(self, relay_fingerprint, default = None):
-    """
-    Provides the (IP Address, ORPort) tuple for a given relay. If the lookup
-    fails then this returns the default.
-
-    Arguments:
-      relay_fingerprint - fingerprint of the relay
-    """
-
-    self.conn_lock.acquire()
-
-    result = default
-
-    if self.is_alive():
-      # query the address if it isn't yet cached
-      if not relay_fingerprint in self._address_lookup_cache:
-        if relay_fingerprint == self.get_info("fingerprint", None):
-          # this is us, simply check the config
-          my_address = self.get_info("address", None)
-          my_or_port = self.get_option("ORPort", None)
-
-          if my_address and my_or_port:
-            self._address_lookup_cache[relay_fingerprint] = (my_address, my_or_port)
-        else:
-          # check the consensus for the relay
-          ns_entry = self.get_consensus_entry(relay_fingerprint)
-
-          if ns_entry:
-            ns_line_comp = ns_entry.split("\n")[0].split(" ")
-
-            if len(ns_line_comp) >= 8:
-              self._address_lookup_cache[relay_fingerprint] = (ns_line_comp[6], ns_line_comp[7])
-
-      result = self._address_lookup_cache.get(relay_fingerprint, default)
-
-    self.conn_lock.release()
-
-    return result
-
   def add_event_listener(self, listener, *event_types):
     """
     Directs further tor controller events to callback functions of the
@@ -790,131 +489,11 @@ class Controller:
 
     # reconstructs consensus based mappings
 
-    self._fingerprint_lookup_cache = {}
-    self._nickname_lookup_cache = {}
-    self._address_lookup_cache = {}
     self._consensus_lookup_cache = {}
 
-    if self._fingerprint_mappings is not None:
-      self._fingerprint_mappings = self._get_fingerprint_mappings(event.desc)
-
     self.conn_lock.release()
 
   def new_desc_event(self, event):
     self.conn_lock.acquire()
-
-    desc_fingerprints = [fingerprint for (fingerprint, nickname) in event.relays]
-
-    # If we're tracking ip address -> fingerprint mappings then update with
-    # the new relays.
-
-    self._fingerprint_lookup_cache = {}
     self._descriptor_lookup_cache = {}
-
-    if self._fingerprint_mappings is not None:
-      for fingerprint in desc_fingerprints:
-        # gets consensus data for the new descriptor
-
-        try:
-          desc = self.controller.get_network_status(fingerprint)
-        except stem.ControllerError:
-          continue
-
-        # updates fingerprintMappings with new data
-
-        if desc.address in self._fingerprint_mappings:
-          # if entry already exists with the same orport, remove it
-
-          orport_match = None
-
-          for entry_port, entry_fingerprint in self._fingerprint_mappings[desc.address]:
-            if entry_port == desc.or_port:
-              orport_match = (entry_port, entry_fingerprint)
-              break
-
-          if orport_match:
-            self._fingerprint_mappings[desc.address].remove(orport_match)
-
-          # add the new entry
-
-          self._fingerprint_mappings[desc.address].append((desc.or_port, desc.fingerprint))
-        else:
-          self._fingerprint_mappings[desc.address] = [(desc.or_port, desc.fingerprint)]
-
     self.conn_lock.release()
-
-  def _get_fingerprint_mappings(self, descriptors = None):
-    """
-    Provides IP address to (port, fingerprint) tuple mappings for all of the
-    currently cached relays.
-
-    Arguments:
-      descriptors - router status entries (fetched if not provided)
-    """
-
-    results = {}
-
-    if self.is_alive():
-      # fetch the current network status if not provided
-
-      if not descriptors:
-        try:
-          descriptors = self.controller.get_network_statuses()
-        except stem.ControllerError:
-          descriptors = []
-
-      # construct mappings of ips to relay data
-
-      for desc in descriptors:
-        results.setdefault(desc.address, []).append((desc.or_port, desc.fingerprint))
-
-    return results
-
-  def _get_relay_fingerprint(self, relay_address, relay_port):
-    """
-    Provides the fingerprint associated with the address/port combination.
-
-    Arguments:
-      relay_address - address of relay to be returned
-      relay_port    - orport of relay (to further narrow the results)
-    """
-
-    # If we were provided with a string port then convert to an int (so
-    # lookups won't mismatch based on type).
-
-    if isinstance(relay_port, str):
-      relay_port = int(relay_port)
-
-    # checks if this matches us
-
-    if relay_address == self.get_info("address", None):
-      if not relay_port or relay_port == self.get_option("ORPort", None):
-        return self.get_info("fingerprint", None)
-
-    # if we haven't yet populated the ip -> fingerprint mappings then do so
-
-    if self._fingerprint_mappings is None:
-      self._fingerprint_mappings = self._get_fingerprint_mappings()
-
-    potential_matches = self._fingerprint_mappings.get(relay_address)
-
-    if not potential_matches:
-      return None  # no relay matches this ip address
-
-    if len(potential_matches) == 1:
-      # There's only one relay belonging to this ip address. If the port
-      # matches then we're done.
-
-      match = potential_matches[0]
-
-      if relay_port and match[0] != relay_port:
-        return None
-      else:
-        return match[1]
-    elif relay_port:
-      # Multiple potential matches, so trying to match based on the port.
-      for entry_port, entry_fingerprint in potential_matches:
-        if entry_port == relay_port:
-          return entry_fingerprint
-
-    return None





More information about the tor-commits mailing list