Author: atagar Date: 2011-04-01 16:18:15 +0000 (Fri, 01 Apr 2011) New Revision: 24526
Modified: arm/trunk/src/interface/connections/connEntry.py arm/trunk/src/interface/connections/connPanel.py arm/trunk/src/util/connections.py arm/trunk/src/util/enum.py arm/trunk/src/util/torTools.py Log: 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.
Modified: arm/trunk/src/interface/connections/connEntry.py =================================================================== --- arm/trunk/src/interface/connections/connEntry.py 2011-04-01 04:46:33 UTC (rev 24525) +++ arm/trunk/src/interface/connections/connEntry.py 2011-04-01 16:18:15 UTC (rev 24526) @@ -18,11 +18,11 @@ # 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ """
# 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,13 +541,23 @@ # 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 - src = "%-21s" % srcAddress # ip:port = max of 21 characters - dst = "%-26s" % dstAddress # ip:port (xx) = max of 26 characters
+ 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 + usedSpace += len(src) + len(dst) # base data requires 47 characters
# Showing the fingerprint (which has the width of 42) has priority over
Modified: arm/trunk/src/interface/connections/connPanel.py =================================================================== --- arm/trunk/src/interface/connections/connPanel.py 2011-04-01 04:46:33 UTC (rev 24525) +++ arm/trunk/src/interface/connections/connPanel.py 2011-04-01 16:18:15 UTC (rev 24526) @@ -64,14 +64,14 @@ # 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 @@ 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 @@
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 @@ """
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
Modified: arm/trunk/src/util/connections.py =================================================================== --- arm/trunk/src/util/connections.py 2011-04-01 04:46:33 UTC (rev 24525) +++ arm/trunk/src/util/connections.py 2011-04-01 16:18:15 UTC (rev 24526) @@ -659,7 +659,7 @@ 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:
Modified: arm/trunk/src/util/enum.py =================================================================== --- arm/trunk/src/util/enum.py 2011-04-01 04:46:33 UTC (rev 24525) +++ arm/trunk/src/util/enum.py 2011-04-01 16:18:15 UTC (rev 24526) @@ -32,7 +32,8 @@
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)
Modified: arm/trunk/src/util/torTools.py =================================================================== --- arm/trunk/src/util/torTools.py 2011-04-01 04:46:33 UTC (rev 24525) +++ arm/trunk/src/util/torTools.py 2011-04-01 16:18:15 UTC (rev 24526) @@ -55,7 +55,7 @@ "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 @@
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 @@
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