Commit 39f8df27 authored by Damian Johnson's avatar Damian Johnson
Browse files

Identifying connections we're providing a hidden service on and application...

Identifying connections we're providing a hidden service on and application it's attached to. This was tested by running a local HS via thttpd and using tor2web to download files from it.



svn:r24526
parent ffb0ed99
Loading
Loading
Loading
Loading
+22 −9
Original line number Diff line number Diff line
@@ -18,11 +18,11 @@ from interface.connections import entries
#   Directory    Fetching tor consensus information.
#   Control      Tor controller (arm, vidalia, etc).

Category = enum.Enum("INBOUND", "OUTBOUND", "EXIT", "CIRCUIT", "DIRECTORY", "SOCKS", "CONTROL")
Category = enum.Enum("INBOUND", "OUTBOUND", "EXIT", "CIRCUIT", "DIRECTORY", "SOCKS", "HIDDEN", "CONTROL")
CATEGORY_COLOR = {Category.INBOUND: "green",      Category.OUTBOUND: "blue",
                  Category.EXIT: "red",           Category.CIRCUIT: "cyan",
                  Category.SOCKS: "yellow",     Category.DIRECTORY: "magenta",
                  Category.CONTROL: "red"}
                  Category.DIRECTORY: "magenta",  Category.SOCKS: "yellow",
                  Category.HIDDEN: "magenta",     Category.CONTROL: "red"}

# static data for listing format
# <src>  -->  <dst>  <etc><padding>
@@ -199,7 +199,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
    self._possibleClient = True
    self._possibleDirectory = True
    
    # attributes for SOCKS and CONTROL connections
    # attributes for SOCKS, HIDDEN, and CONTROL connections
    self.appName = None
    self.appPid = None
    self.isAppResolving = False
@@ -209,6 +209,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
    myDirPort = conn.getOption("DirPort")
    mySocksPort = conn.getOption("SocksPort", "9050")
    myCtlPort = conn.getOption("ControlPort")
    myHiddenServicePorts = conn.getHiddenServicePorts()
    
    # the ORListenAddress can overwrite the ORPort
    listenAddr = conn.getOption("ORListenAddress")
@@ -220,6 +221,8 @@ class ConnectionLine(entries.ConnectionPanelLine):
      self.local.isORPort = True
    elif lPort == mySocksPort:
      self.baseType = Category.SOCKS
    elif fPort in myHiddenServicePorts:
      self.baseType = Category.HIDDEN
    elif lPort == myCtlPort:
      self.baseType = Category.CONTROL
    else:
@@ -288,7 +291,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
    True if our display uses application information that hasn't yet been resolved.
    """
    
    return self.appName == None and self.getType() in (Category.SOCKS, Category.CONTROL)
    return self.appName == None and self.getType() in (Category.SOCKS, Category.HIDDEN, Category.CONTROL)
  
  def _getListingEntry(self, width, currentTime, listingType):
    entryType = self.getType()
@@ -428,7 +431,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
    """
    
    # for applications show the command/pid
    if self.getType() in (Category.SOCKS, Category.CONTROL):
    if self.getType() in (Category.SOCKS, Category.HIDDEN, Category.CONTROL):
      displayLabel = ""
      
      if self.appName:
@@ -538,10 +541,20 @@ class ConnectionLine(entries.ConnectionPanelLine):
      # the source and destination addresses are both private, but that might
      # not be perfectly reliable either.
      
      isExpansionType = not myType in (Category.SOCKS, Category.CONTROL)
      isExpansionType = not myType in (Category.SOCKS, Category.HIDDEN, Category.CONTROL)
      
      if isExpansionType: srcAddress = myExternalIpAddr + localPort
      else: srcAddress = self.local.getIpAddr() + localPort
      
      if myType in (Category.SOCKS, Category.CONTROL):
        # These categories are reordered later so the it's dst -> src rather
        # than src -> dst. They are local connections so the dst lacks locale
        # information, so swapping their respective widths to match column
        # alignments.
        
        src = "%-26s" % srcAddress
        dst = "%-21s" % dstAddress
      else:
        src = "%-21s" % srcAddress # ip:port = max of 21 characters
        dst = "%-26s" % dstAddress # ip:port (xx) = max of 26 characters
      
+23 −19
Original line number Diff line number Diff line
@@ -64,14 +64,14 @@ class ConnectionPanel(panel.Panel, threading.Thread):
    # it changes.
    self._lastResourceFetch = -1
    
    # resolver for the command/pid associated with SOCKS and CONTROL connections
    # resolver for the command/pid associated with SOCKS, HIDDEN, and CONTROL connections
    self._appResolver = connections.AppResolver("arm")
    
    # rate limits appResolver queries to once per update
    self.appResolveSinceUpdate = False
    
    self._update()            # populates initial entries
    self._resolveApps(False)  # resolves initial SOCKS and CONTROL applications
    self._resolveApps(False)  # resolves initial applications
    
    # mark the initially exitsing connection uptimes as being estimates
    for entry in self._entries:
@@ -203,7 +203,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
    for lineNum in range(scrollLoc, len(self._entryLines)):
      entryLine = self._entryLines[lineNum]
      
      # if this is an unresolved SOCKS or CONTROL entry then queue up
      # if this is an unresolved SOCKS, HIDDEN, or CONTROL entry then queue up
      # resolution for the applicaitions they belong to
      if isinstance(entryLine, connEntry.ConnectionLine) and entryLine.isUnresolvedApp():
        self._resolveApps()
@@ -325,8 +325,8 @@ class ConnectionPanel(panel.Panel, threading.Thread):
  
  def _resolveApps(self, flagQuery = True):
    """
    Triggers an asynchronous query for all unresolved SOCKS and CONTROL
    entries.
    Triggers an asynchronous query for all unresolved SOCKS, HIDDEN, and
    CONTROL entries.
    
    Arguments:
      flagQuery - sets a flag to prevent further call from being respected
@@ -334,42 +334,46 @@ class ConnectionPanel(panel.Panel, threading.Thread):
    """
    
    if self.appResolveSinceUpdate or not self._config["features.connection.resolveApps"]: return
    unresolvedLines = [l for l in self._entryLines if isinstance(l, connEntry.ConnectionLine) and l.isUnresolvedApp()]
    
    # fetch the unresolved SOCKS and CONTROL lines
    unresolvedLines = []
    # get the ports used for unresolved applications
    appPorts = []
    
    for line in self._entryLines:
      if isinstance(line, connEntry.ConnectionLine) and line.isUnresolvedApp():
        unresolvedLines.append(line)
    for line in unresolvedLines:
      appConn = line.local if line.getType() == connEntry.Category.HIDDEN else line.foreign
      appPorts.append(appConn.getPort())
    
    # Queue up resolution for the unresolved ports (skips if it's still working
    # on the last query).
    if not self._appResolver.isResolving:
      self._appResolver.resolve([line.foreign.getPort() for line in unresolvedLines])
    if appPorts and not self._appResolver.isResolving:
      self._appResolver.resolve(appPorts)
    
    # Fetches results. If the query finishes quickly then this is what we just
    # asked for, otherwise these belong to an earlier resolution.
    #
    # The application resolver might have given up querying (for instance, if
    # the lsof lookups aren't working on this platform or lacks permissions).
    # The isAppResolving flag lets the unresolved entries indicate if there's
    # a lookup in progress for them or not.
    
    for line in unresolvedLines:
      line.isAppResolving = self._appResolver.isResolving
    
    # Fetches results. If the query finishes quickly then this is what we just
    # asked for, otherwise these belong to the last resolution.
    appResults = self._appResolver.getResults(0.2)
    
    for line in unresolvedLines:
      linePort = line.foreign.getPort()
      isLocal = line.getType() == connEntry.Category.HIDDEN
      linePort = line.local.getPort() if isLocal else line.foreign.getPort()
      
      if linePort in appResults:
        # sets application attributes if there's a result with this as the
        # inbound port
        for inboundPort, outboundPort, cmd, pid in appResults[linePort]:
          if linePort == inboundPort:
          appPort = outboundPort if isLocal else inboundPort
          
          if linePort == appPort:
            line.appName = cmd
            line.appPid = pid
            line.isAppResolving = False
      else:
        line.isAppResolving = self._appResolver.isResolving
    
    if flagQuery:
      self.appResolveSinceUpdate = True
+1 −1
Original line number Diff line number Diff line
@@ -659,7 +659,7 @@ class AppResolver:
      else: lsofArgs.append("-i tcp:%s" % port)
    
    if lsofArgs:
      lsofResults = sysTools.call("lsof -n " + " ".join(lsofArgs))
      lsofResults = sysTools.call("lsof -nP " + " ".join(lsofArgs))
    else: lsofResults = None
    
    if not lsofResults and self.failureCount != -1:
+2 −1
Original line number Diff line number Diff line
@@ -32,7 +32,8 @@ def toCamelCase(label):
  
  words = []
  for entry in label.split("_"):
    if len(entry) == 1: words.append(entry.upper())
    if len(entry) == 0: words.append("")
    elif len(entry) == 1: words.append(entry.upper())
    else: words.append(entry[0].upper() + entry[1:].lower())
  
  return " ".join(words)
+36 −1
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ CACHE_ARGS = ("version", "config-file", "exit-policy/default", "fingerprint",
              "config/names", "info/names", "features/names", "events/names",
              "nsEntry", "descEntry", "address", "bwRate", "bwBurst",
              "bwObserved", "bwMeasured", "flags", "parsedVersion", "pid",
              "pathPrefix", "startTime", "authorities", "circuits")
              "pathPrefix", "startTime", "authorities", "circuits", "hsPorts")

# Tor has a couple messages (in or/router.c) for when our ip address changes:
# "Our IP Address has changed from <previous> to <current>; rebuilding
@@ -649,6 +649,16 @@ class Controller(TorCtl.PostEventListener):
    
    return self._getRelayAttr("circuits", default)
  
  def getHiddenServicePorts(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
    """
    
    return self._getRelayAttr("hsPorts", default)
  
  def getMyNetworkStatus(self, default = None):
    """
    Provides the network status entry for this relay if available. This is
@@ -1691,6 +1701,31 @@ class Controller(TorCtl.PostEventListener):
            
            path = tuple([hopEntry[1:41] for hopEntry in lineComp[2].split(",")])
            result.append((int(lineComp[0]), lineComp[1], lineComp[3][8:], path))
      elif key == "hsPorts":
        result = []
        hsOptions = self.getOptionMap("HiddenServiceOptions")
        
        if hsOptions and "HiddenServicePort" in hsOptions:
          for hsEntry in hsOptions["HiddenServicePort"]:
            # hidden service port entries are of the form:
            # VIRTPORT [TARGET]
            # with the TARGET being an IP, port, or IP:Port. If the target port
            # isn't defined then uses the VIRTPORT.
            
            hsPort = None
            
            if " " in hsEntry:
              # parses the target, checking if it's a port or IP:Port combination
              hsTarget = hsEntry.split(" ")[1]
              
              if ":" in hsTarget:
                hsPort = hsTarget.split(":")[1] # target is the IP:Port
              elif hsTarget.isdigit():
                hsPort = hsTarget # target is just the port
            else: hsPort = hsEntry # just has the virtual port
            
            if hsPort.isdigit():
              result.append(hsPort)
      
      # cache value
      if result != None: self._cachedParam[key] = result