[tor-commits] r24402: {arm} Presenting full circuit paths in the connection panel. For a (in arm/trunk: . src/interface/connections src/util)
Damian Johnson
atagar1 at gmail.com
Mon Mar 21 16:39:27 UTC 2011
Author: atagar
Date: 2011-03-21 16:39:27 +0000 (Mon, 21 Mar 2011)
New Revision: 24402
Added:
arm/trunk/src/interface/connections/clientEntry.py
Modified:
arm/trunk/armrc.sample
arm/trunk/src/interface/connections/connEntry.py
arm/trunk/src/interface/connections/connPanel.py
arm/trunk/src/util/torTools.py
arm/trunk/src/util/uiTools.py
Log:
Presenting full circuit paths in the connection panel. For a somewhat outdated screenshot of what this includes see 'http://www.atagar.com/transfer/tmp/connPanelWithCircs.png'.
Modified: arm/trunk/armrc.sample
===================================================================
--- arm/trunk/armrc.sample 2011-03-21 15:14:17 UTC (rev 24401)
+++ arm/trunk/armrc.sample 2011-03-21 16:39:27 UTC (rev 24402)
@@ -182,7 +182,7 @@
features.connection.showColumn.fingerprint true
features.connection.showColumn.nickname true
features.connection.showColumn.destination true
-features.connection.showColumn.expanedIp true
+features.connection.showColumn.expandedIp true
# Thread pool size for hostname resolutions
# Determines the maximum number of concurrent requests. Upping this to around
Added: arm/trunk/src/interface/connections/clientEntry.py
===================================================================
--- arm/trunk/src/interface/connections/clientEntry.py (rev 0)
+++ arm/trunk/src/interface/connections/clientEntry.py 2011-03-21 16:39:27 UTC (rev 24402)
@@ -0,0 +1,215 @@
+"""
+Connection panel entries for client circuits. This includes a header entry
+followed by an entry for each hop in the circuit. For instance:
+
+89.188.20.246:42667 --> 217.172.182.26 (de) General / Built 8.6m (CLIENT)
+| 85.8.28.4 (se) 98FBC3B2B93897A78CDD797EF549E6B62C9A8523 1 / Guard
+| 91.121.204.76 (fr) 546387D93F8D40CFF8842BB9D3A8EC477CEDA984 2 / Middle
++- 217.172.182.26 (de) 5CFA9EA136C0EA0AC096E5CEA7EB674F1207CF86 3 / Exit
+"""
+
+import curses
+
+from interface.connections import entries, connEntry
+from util import torTools, uiTools
+
+# cached fingerprint -> (IP Address, ORPort) results
+RELAY_INFO = {}
+
+def getRelayInfo(fingerprint):
+ """
+ Provides the (IP Address, ORPort) tuple for the given relay. If the lookup
+ fails then this returns ("192.168.0.1", "0").
+
+ Arguments:
+ fingerprint - relay to look up
+ """
+
+ if not fingerprint in RELAY_INFO:
+ conn = torTools.getConn()
+ failureResult = ("192.168.0.1", "0")
+
+ nsEntry = conn.getConsensusEntry(fingerprint)
+ if not nsEntry: return failureResult
+
+ nsLineComp = nsEntry.split("\n")[0].split(" ")
+ if len(nsLineComp) < 8: return failureResult
+
+ RELAY_INFO[fingerprint] = (nsLineComp[6], nsLineComp[7])
+
+ return RELAY_INFO[fingerprint]
+
+class ClientEntry(connEntry.ConnectionEntry):
+ def __init__(self, circuitID, status, purpose, path):
+ connEntry.ConnectionEntry.__init__(self, "127.0.0.1", "0", "127.0.0.1", "0")
+
+ self.circuitID = circuitID
+ self.status = status
+
+ # drops to lowercase except the first letter
+ if len(purpose) >= 2:
+ purpose = purpose[0].upper() + purpose[1:].lower()
+
+ self.lines = [ClientHeaderLine(self.circuitID, purpose)]
+
+ # Overwrites attributes of the initial line to make it more fitting as the
+ # header for our listing.
+
+ self.lines[0].baseType = connEntry.Category.CLIENT
+
+ self.update(status, path)
+
+ def update(self, status, path):
+ """
+ Our status and path can change over time if the circuit is still in the
+ process of being built. Updates these attributs of our relay.
+
+ Arguments:
+ status - new status of the circuit
+ path - list of fingerprints for the series of relays involved in the
+ circuit
+ """
+
+ self.status = status
+ self.lines = [self.lines[0]]
+
+ if status == "BUILT" and not self.lines[0].isBuilt:
+ exitIp, exitORPort = getRelayInfo(path[-1])
+ self.lines[0].setExit(exitIp, exitORPort, path[-1])
+
+ for i in range(len(path)):
+ relayFingerprint = path[i]
+ relayIp, relayOrPort = getRelayInfo(relayFingerprint)
+
+ if i == len(path) - 1:
+ if status == "BUILT": placementType = "Exit"
+ else: placementType = "Extending"
+ elif i == 0: placementType = "Guard"
+ else: placementType = "Middle"
+
+ placementLabel = "%i / %s" % (i + 1, placementType)
+
+ self.lines.append(ClientLine(relayIp, relayOrPort, relayFingerprint, placementLabel))
+
+ self.lines[-1].isLast = True
+
+class ClientHeaderLine(connEntry.ConnectionLine):
+ """
+ Initial line of a client entry. This has the same basic format as connection
+ lines except that its etc field has circuit attributes.
+ """
+
+ def __init__(self, circuitID, purpose):
+ connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", "0.0.0.0", "0", False, False)
+ self.circuitID = circuitID
+ self.purpose = purpose
+ self.isBuilt = False
+
+ def setExit(self, exitIpAddr, exitPort, exitFingerprint):
+ connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", exitIpAddr, exitPort, False, False)
+ self.isBuilt = True
+ self.foreign.fingerprintOverwrite = exitFingerprint
+
+ def getType(self):
+ return connEntry.Category.CLIENT
+
+ def getDestinationLabel(self, maxLength, includeLocale=False, includeHostname=False):
+ if not self.isBuilt: return "Building..."
+ return connEntry.ConnectionLine.getDestinationLabel(self, maxLength, includeLocale, includeHostname)
+
+ def getEtcContent(self, width, listingType):
+ """
+ Attempts to provide all circuit related stats. Anything that can't be
+ shown completely (not enough room) is dropped.
+ """
+
+ etcAttr = ["Purpose: %s" % self.purpose, "Circuit ID: %i" % self.circuitID]
+
+ for i in range(len(etcAttr), -1, -1):
+ etcLabel = ", ".join(etcAttr[:i])
+ if len(etcLabel) <= width: return etcLabel
+
+ return ""
+
+ def getDetails(self, width):
+ if not self.isBuilt:
+ detailFormat = curses.A_BOLD | uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()])
+ return [uiTools.DrawEntry("Building Circuit...", detailFormat)]
+ else: return connEntry.ConnectionLine.getDetails(self, width)
+
+class ClientLine(connEntry.ConnectionLine):
+ """
+ An inidividual hop in a circuit. This overwrites the displayed listing, but
+ otherwise makes use of the ConnectionLine attributes (for the detail display,
+ caching, etc).
+ """
+
+ def __init__(self, fIpAddr, fPort, fFingerprint, placementLabel):
+ connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", fIpAddr, fPort)
+ self.foreign.fingerprintOverwrite = fFingerprint
+ self.placementLabel = placementLabel
+ self.includePort = False
+
+ # determines the sort of left hand bracketing we use
+ self.isLast = False
+
+ def getType(self):
+ return connEntry.Category.CLIENT
+
+ def getListingEntry(self, width, currentTime, listingType):
+ """
+ Provides the DrawEntry for this relay in the circuilt listing. Lines are
+ composed of the following components:
+ <bracket> <dst> <etc> <placement label>
+
+ The dst and etc entries largely match their ConnectionEntry counterparts.
+
+ Arguments:
+ width - maximum length of the line
+ currentTime - the current unix time (ignored)
+ listingType - primary attribute we're listing connections by
+ """
+
+ return entries.ConnectionPanelLine.getListingEntry(self, width, currentTime, listingType)
+
+ def _getListingEntry(self, width, currentTime, listingType):
+ lineFormat = uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()])
+
+ # The required widths are the sum of the following:
+ # bracketing (3 characters)
+ # placementLabel (14 characters)
+ # gap between etc and placement label (5 characters)
+
+ if self.isLast: bracket = (curses.ACS_LLCORNER, curses.ACS_HLINE, ord(' '))
+ else: bracket = (curses.ACS_VLINE, ord(' '), ord(' '))
+ baselineSpace = len(bracket) + 14 + 5
+
+ dst, etc = "", ""
+ if listingType == entries.ListingType.IP_ADDRESS:
+ # TODO: include hostname when that's available
+ # dst width is derived as:
+ # src (21) + dst (26) + divider (7) + right gap (2) - bracket (3) = 53 char
+ dst = "%-53s" % self.getDestinationLabel(53, includeLocale = True)
+ etc = self.getEtcContent(width - baselineSpace - len(dst), listingType)
+ elif listingType == entries.ListingType.HOSTNAME:
+ # min space for the hostname is 40 characters
+ etc = self.getEtcContent(width - baselineSpace - 40, listingType)
+ dstLayout = "%%-%is" % (width - baselineSpace - len(etc))
+ dst = dstLayout % self.foreign.getHostname(self.foreign.getIpAddr())
+ elif listingType == entries.ListingType.FINGERPRINT:
+ # dst width is derived as:
+ # src (9) + dst (40) + divider (7) + right gap (2) - bracket (3) = 55 char
+ dst = "%-55s" % self.foreign.getFingerprint()
+ etc = self.getEtcContent(width - baselineSpace - len(dst), listingType)
+ else:
+ # min space for the nickname is 50 characters
+ etc = self.getEtcContent(width - baselineSpace - 50, listingType)
+ dstLayout = "%%-%is" % (width - baselineSpace - len(etc))
+ dst = dstLayout % self.foreign.getNickname()
+
+ drawEntry = uiTools.DrawEntry("%-14s" % self.placementLabel, lineFormat)
+ drawEntry = uiTools.DrawEntry(" " * (width - baselineSpace - len(dst) - len(etc) + 5), lineFormat, drawEntry)
+ drawEntry = uiTools.DrawEntry(dst + etc, lineFormat, drawEntry)
+ drawEntry = uiTools.DrawEntry(bracket, curses.A_NORMAL, drawEntry, lockFormat = True)
+ return drawEntry
+
Modified: arm/trunk/src/interface/connections/connEntry.py
===================================================================
--- arm/trunk/src/interface/connections/connEntry.py 2011-03-21 15:14:17 UTC (rev 24401)
+++ arm/trunk/src/interface/connections/connEntry.py 2011-03-21 16:39:27 UTC (rev 24402)
@@ -32,7 +32,7 @@
CONFIG = {"features.connection.showColumn.fingerprint": True,
"features.connection.showColumn.nickname": True,
"features.connection.showColumn.destination": True,
- "features.connection.showColumn.expanedIp": True}
+ "features.connection.showColumn.expandedIp": True}
def loadConfig(config):
config.update(CONFIG)
@@ -51,6 +51,9 @@
# if true, we treat the port as an ORPort when searching for matching
# fingerprints (otherwise the ORPort is assumed to be unknown)
self.isORPort = False
+
+ # if set then this overwrites fingerprint lookups
+ self.fingerprintOverwrite = None
def getIpAddr(self):
"""
@@ -88,14 +91,16 @@
return default
- def getLocale(self):
+ def getLocale(self, default=None):
"""
- Provides the two letter country code for the IP address' locale. This
- proivdes None if it can't be determined.
+ Provides the two letter country code for the IP address' locale.
+
+ Arguments:
+ default - return value if no locale information is available
"""
conn = torTools.getConn()
- return conn.getInfo("ip-to-country/%s" % self.ipAddr)
+ return conn.getInfo("ip-to-country/%s" % self.ipAddr, default)
def getFingerprint(self):
"""
@@ -103,6 +108,9 @@
determined.
"""
+ if self.fingerprintOverwrite:
+ return self.fingerprintOverwrite
+
conn = torTools.getConn()
orPort = self.port if self.isORPort else None
myFingerprint = conn.getRelayFingerprint(self.ipAddr, orPort)
@@ -157,7 +165,7 @@
return self.lines[0].startTime
elif attr == entries.SortAttr.COUNTRY:
if connections.isIpAddressPrivate(self.lines[0].foreign.getIpAddr()): return ""
- else: return self.lines[0].foreign.getLocale()
+ else: return self.lines[0].foreign.getLocale("")
else:
return entries.ConnectionPanelEntry.getSortValue(self, attr, listingType)
@@ -166,7 +174,7 @@
Display component of the ConnectionEntry.
"""
- def __init__(self, lIpAddr, lPort, fIpAddr, fPort):
+ def __init__(self, lIpAddr, lPort, fIpAddr, fPort, includePort=True, includeExpandedIpAddr=True):
entries.ConnectionPanelLine.__init__(self)
self.local = Endpoint(lIpAddr, lPort)
@@ -206,14 +214,19 @@
self.cachedType = None
+ # includes the port or expanded ip address field when displaying listing
+ # information if true
+ self.includePort = includePort
+ self.includeExpandedIpAddr = includeExpandedIpAddr
+
# cached immutable values used for sorting
self.sortIpAddr = connections.ipToInt(self.foreign.getIpAddr())
self.sortPort = int(self.foreign.getPort())
def getListingEntry(self, width, currentTime, listingType):
"""
- Provides the DrawEntry for this connection's listing. The line is made up
- of six components:
+ Provides the DrawEntry for this connection's listing. Lines are composed
+ of the following components:
<src> --> <dst> <etc> <uptime> (<type>)
ListingType.IP_ADDRESS:
@@ -355,7 +368,7 @@
# a built 1-hop connection since those are most likely a directory
# mirror).
- for status, _, path in myCircuits:
+ for _, status, _, path in myCircuits:
if path[0] == destFingerprint and (status != "BUILT" or len(path) > 1):
self.cachedType = Category.CLIENT # matched a probable guard connection
@@ -366,7 +379,7 @@
if self._possibleDirectory:
# Checks if we match a built, single hop circuit.
- for status, _, path in myCircuits:
+ for _, status, _, path in myCircuits:
if path[0] == destFingerprint and status == "BUILT" and len(path) == 1:
self.cachedType = Category.DIRECTORY
@@ -379,6 +392,78 @@
return self.cachedType
+ def getEtcContent(self, width, listingType):
+ """
+ Provides the optional content for the connection.
+
+ Arguments:
+ width - maximum length of the line
+ listingType - primary attribute we're listing connections by
+ """
+
+ dstAddress = self.getDestinationLabel(26, includeLocale = True)
+ etc, usedSpace = "", 0
+ if listingType == entries.ListingType.IP_ADDRESS:
+ if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]:
+ # show fingerprint (column width: 42 characters)
+ etc += "%-40s " % self.foreign.getFingerprint()
+ usedSpace += 42
+
+ if width > usedSpace + 10 and CONFIG["features.connection.showColumn.nickname"]:
+ # show nickname (column width: remainder)
+ nicknameSpace = width - usedSpace
+ nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0)
+ etc += ("%%-%is " % nicknameSpace) % nicknameLabel
+ usedSpace += nicknameSpace + 2
+ elif listingType == entries.ListingType.HOSTNAME:
+ if width > usedSpace + 28 and CONFIG["features.connection.showColumn.destination"]:
+ # show destination ip/port/locale (column width: 28 characters)
+ etc += "%-26s " % dstAddress
+ usedSpace += 28
+
+ if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]:
+ # show fingerprint (column width: 42 characters)
+ etc += "%-40s " % self.foreign.getFingerprint()
+ usedSpace += 42
+
+ if width > usedSpace + 17 and CONFIG["features.connection.showColumn.nickname"]:
+ # show nickname (column width: min 17 characters, uses half of the remainder)
+ nicknameSpace = 15 + (width - (usedSpace + 17)) / 2
+ nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0)
+ etc += ("%%-%is " % nicknameSpace) % nicknameLabel
+ usedSpace += (nicknameSpace + 2)
+ elif listingType == entries.ListingType.FINGERPRINT:
+ if width > usedSpace + 17:
+ # show nickname (column width: min 17 characters, consumes any remaining space)
+ nicknameSpace = width - usedSpace - 2
+
+ # if there's room then also show a column with the destination
+ # ip/port/locale (column width: 28 characters)
+ isIpLocaleIncluded = width > usedSpace + 45
+ isIpLocaleIncluded &= CONFIG["features.connection.showColumn.destination"]
+ if isIpLocaleIncluded: nicknameSpace -= 28
+
+ if CONFIG["features.connection.showColumn.nickname"]:
+ nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0)
+ etc += ("%%-%is " % nicknameSpace) % nicknameLabel
+ usedSpace += nicknameSpace + 2
+
+ if isIpLocaleIncluded:
+ etc += "%-26s " % dstAddress
+ usedSpace += 28
+ else:
+ if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]:
+ # show fingerprint (column width: 42 characters)
+ etc += "%-40s " % self.foreign.getFingerprint()
+ usedSpace += 42
+
+ if width > usedSpace + 28 and CONFIG["features.connection.showColumn.destination"]:
+ # show destination ip/port/locale (column width: 28 characters)
+ etc += "%-26s " % dstAddress
+ usedSpace += 28
+
+ return etc
+
def _getListingContent(self, width, listingType):
"""
Provides the source, destination, and extra info for our listing.
@@ -390,7 +475,7 @@
conn = torTools.getConn()
myType = self.getType()
- dstAddress = self._getDestinationLabel(26, includeLocale = True)
+ dstAddress = self.getDestinationLabel(26, includeLocale = True)
# The required widths are the sum of the following:
# - room for LABEL_FORMAT and LABEL_MIN_PADDING (11 characters)
@@ -398,70 +483,55 @@
# - that extra field plus any previous
usedSpace = len(LABEL_FORMAT % tuple([""] * 4)) + LABEL_MIN_PADDING
+ localPort = ":%s" % self.local.getPort() if self.includePort else ""
src, dst, etc = "", "", ""
if listingType == entries.ListingType.IP_ADDRESS:
myExternalIpAddr = conn.getInfo("address", self.local.getIpAddr())
addrDiffer = myExternalIpAddr != self.local.getIpAddr()
- srcAddress = "%s:%s" % (myExternalIpAddr, self.local.getPort())
+ srcAddress = myExternalIpAddr + localPort
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
- if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]:
- # show fingerprint (column width: 42 characters)
- etc += "%-40s " % self.foreign.getFingerprint()
- usedSpace += 42
+ # Showing the fingerprint (which has the width of 42) has priority over
+ # an expanded address field. Hence check if we either have space for
+ # both or wouldn't be showing the fingerprint reguardless.
- if addrDiffer and width > usedSpace + 28 and CONFIG["features.connection.showColumn.expanedIp"]:
+ isExpandedAddrVisible = width > usedSpace + 28
+ if isExpandedAddrVisible and CONFIG["features.connection.showColumn.fingerprint"]:
+ isExpandedAddrVisible = width < usedSpace + 42 or width > usedSpace + 70
+
+ if addrDiffer and isExpandedAddrVisible and self.includeExpandedIpAddr and CONFIG["features.connection.showColumn.expandedIp"]:
# include the internal address in the src (extra 28 characters)
- internalAddress = "%s:%s" % (self.local.getIpAddr(), self.local.getPort())
+ internalAddress = self.local.getIpAddr() + localPort
src = "%-21s --> %s" % (internalAddress, src)
usedSpace += 28
- if width > usedSpace + 10 and CONFIG["features.connection.showColumn.nickname"]:
- # show nickname (column width: remainder)
- nicknameSpace = width - usedSpace
- nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0)
- etc += ("%%-%is " % nicknameSpace) % nicknameLabel
- usedSpace += nicknameSpace + 2
+ etc = self.getEtcContent(width - usedSpace, listingType)
+ usedSpace += len(etc)
elif listingType == entries.ListingType.HOSTNAME:
# 15 characters for source, and a min of 40 reserved for the destination
- src = "localhost:%-5s" % self.local.getPort()
+ src = "localhost%-6s" % localPort
usedSpace += len(src)
minHostnameSpace = 40
- if width > usedSpace + minHostnameSpace + 28 and CONFIG["features.connection.showColumn.destination"]:
- # show destination ip/port/locale (column width: 28 characters)
- etc += "%-26s " % dstAddress
- usedSpace += 28
+ etc = self.getEtcContent(width - usedSpace - minHostnameSpace, listingType)
+ usedSpace += len(etc)
- if width > usedSpace + minHostnameSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]:
- # show fingerprint (column width: 42 characters)
- etc += "%-40s " % self.foreign.getFingerprint()
- usedSpace += 42
-
- if width > usedSpace + minHostnameSpace + 17 and CONFIG["features.connection.showColumn.nickname"]:
- # show nickname (column width: min 17 characters, uses half of the remainder)
- nicknameSpace = 15 + (width - (usedSpace + minHostnameSpace + 17)) / 2
- nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0)
- etc += ("%%-%is " % nicknameSpace) % nicknameLabel
- usedSpace += (nicknameSpace + 2)
-
hostnameSpace = width - usedSpace
usedSpace = width # prevents padding at the end
if self.isPrivate():
dst = ("%%-%is" % hostnameSpace) % "<scrubbed>"
else:
hostname = self.foreign.getHostname(self.foreign.getIpAddr())
- port = self.foreign.getPort()
+ portLabel = ":%-5s" % self.foreign.getPort() if self.includePort else ""
# truncates long hostnames and sets dst to <hostname>:<port>
hostname = uiTools.cropStr(hostname, hostnameSpace, 0)
- dst = "%s:%-5s" % (hostname, port)
- dst = ("%%-%is" % hostnameSpace) % dst
+ dst = ("%%-%is" % hostnameSpace) % (hostname + portLabel)
elif listingType == entries.ListingType.FINGERPRINT:
src = "localhost"
if myType == Category.CONTROL: dst = "localhost"
@@ -470,25 +540,8 @@
usedSpace += len(src) + len(dst) # base data requires 49 characters
- if width > usedSpace + 17:
- # show nickname (column width: min 17 characters, consumes any remaining space)
- nicknameSpace = width - usedSpace
-
- # if there's room then also show a column with the destination
- # ip/port/locale (column width: 28 characters)
- isIpLocaleIncluded = width > usedSpace + 45
- isIpLocaleIncluded &= CONFIG["features.connection.showColumn.destination"]
- if isIpLocaleIncluded: nicknameSpace -= 28
-
- if CONFIG["features.connection.showColumn.nickname"]:
- nicknameSpace = width - usedSpace - 28 if isIpLocaleIncluded else width - usedSpace
- nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0)
- etc += ("%%-%is " % nicknameSpace) % nicknameLabel
- usedSpace += nicknameSpace + 2
-
- if isIpLocaleIncluded:
- etc += "%-26s " % dstAddress
- usedSpace += 28
+ etc = self.getEtcContent(width - usedSpace, listingType)
+ usedSpace += len(etc)
else:
# base data requires 50 min characters
src = self.local.getNickname()
@@ -496,16 +549,9 @@
else: dst = self.foreign.getNickname()
minBaseSpace = 50
- if width > usedSpace + minBaseSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]:
- # show fingerprint (column width: 42 characters)
- etc += "%-40s " % self.foreign.getFingerprint()
- usedSpace += 42
+ etc = self.getEtcContent(width - usedSpace - minBaseSpace, listingType)
+ usedSpace += len(etc)
- if width > usedSpace + minBaseSpace + 28 and CONFIG["features.connection.showColumn.destination"]:
- # show destination ip/port/locale (column width: 28 characters)
- etc += "%-26s " % dstAddress
- usedSpace += 28
-
baseSpace = width - usedSpace
usedSpace = width # prevents padding at the end
@@ -529,8 +575,8 @@
"""
lines = [""] * 7
- lines[0] = "address: %s" % self._getDestinationLabel(width - 11)
- lines[1] = "locale: %s" % ("??" if self.isPrivate() else self.foreign.getLocale())
+ lines[0] = "address: %s" % self.getDestinationLabel(width - 11)
+ lines[1] = "locale: %s" % ("??" if self.isPrivate() else self.foreign.getLocale("??"))
# Remaining data concerns the consensus results, with three possible cases:
# - if there's a single match then display its details
@@ -627,7 +673,7 @@
return lines
- def _getDestinationLabel(self, maxLength, includeLocale=False, includeHostname=False):
+ def getDestinationLabel(self, maxLength, includeLocale=False, includeHostname=False):
"""
Provides a short description of the destination. This is made up of two
components, the base <ip addr>:<port> and an extra piece of information in
@@ -646,10 +692,9 @@
"""
# destination of the connection
- if self.isPrivate():
- dstAddress = "<scrubbed>:%s" % self.foreign.getPort()
- else:
- dstAddress = "%s:%s" % (self.foreign.getIpAddr(), self.foreign.getPort())
+ ipLabel = "<scrubbed>" if self.isPrivate() else self.foreign.getIpAddr()
+ portLabel = ":%s" % self.foreign.getPort() if self.includePort else ""
+ dstAddress = ipLabel + portLabel
# Only append the extra info if there's at least a couple characters of
# space (this is what's needed for the country codes).
@@ -673,7 +718,7 @@
extraInfo = []
if includeLocale:
- foreignLocale = self.foreign.getLocale()
+ foreignLocale = self.foreign.getLocale("??")
extraInfo.append(foreignLocale)
spaceAvailable -= len(foreignLocale) + 2
Modified: arm/trunk/src/interface/connections/connPanel.py
===================================================================
--- arm/trunk/src/interface/connections/connPanel.py 2011-03-21 15:14:17 UTC (rev 24401)
+++ arm/trunk/src/interface/connections/connPanel.py 2011-03-21 16:39:27 UTC (rev 24402)
@@ -6,8 +6,8 @@
import curses
import threading
-from interface.connections import entries, connEntry
-from util import connections, enum, panel, uiTools
+from interface.connections import entries, connEntry, clientEntry
+from util import connections, enum, panel, torTools, uiTools
DEFAULT_CONFIG = {"features.connection.listingType": 0,
"features.connection.refreshRate": 10}
@@ -48,8 +48,8 @@
self._listingType = Listing.values()[self._config["features.connection.listingType"]]
self._scroller = uiTools.Scroller(True)
self._title = "Connections:" # title line of the panel
- self._connections = [] # last fetched connections
- self._connectionLines = [] # individual lines in the connection listing
+ self._entries = [] # last fetched display entries
+ self._entryLines = [] # individual lines rendered from the entries listing
self._showDetails = False # presents the details panel if true
self._lastUpdate = -1 # time the content was last revised
@@ -95,11 +95,11 @@
self.valsLock.acquire()
if ordering: self._sortOrdering = ordering
- self._connections.sort(key=lambda i: (i.getSortValues(self._sortOrdering, self._listingType)))
+ self._entries.sort(key=lambda i: (i.getSortValues(self._sortOrdering, self._listingType)))
- self._connectionLines = []
- for entry in self._connections:
- self._connectionLines += entry.getLines()
+ self._entryLines = []
+ for entry in self._entries:
+ self._entryLines += entry.getLines()
self.valsLock.release()
def setListingType(self, listingType):
@@ -125,7 +125,7 @@
if uiTools.isScrollKey(key):
pageHeight = self.getPreferredSize()[0] - 1
if self._showDetails: pageHeight -= (DETAILS_HEIGHT + 1)
- isChanged = self._scroller.handleKey(key, self._connectionLines, pageHeight)
+ isChanged = self._scroller.handleKey(key, self._entryLines, pageHeight)
if isChanged: self.redraw(True)
elif uiTools.isSelectionKey(key):
self._showDetails = not self._showDetails
@@ -157,10 +157,10 @@
# extra line when showing the detail panel is for the bottom border
detailPanelOffset = DETAILS_HEIGHT + 1 if self._showDetails else 0
- isScrollbarVisible = len(self._connectionLines) > height - detailPanelOffset - 1
+ isScrollbarVisible = len(self._entryLines) > height - detailPanelOffset - 1
- scrollLoc = self._scroller.getScrollLoc(self._connectionLines, height - detailPanelOffset - 1)
- cursorSelection = self._scroller.getCursorSelection(self._connectionLines)
+ scrollLoc = self._scroller.getScrollLoc(self._entryLines, height - detailPanelOffset - 1)
+ cursorSelection = self._scroller.getCursorSelection(self._entryLines)
# draws the detail panel if currently displaying it
if self._showDetails:
@@ -177,14 +177,14 @@
title = "Connection Details:" if self._showDetails else self._title
self.addstr(0, 0, title, curses.A_STANDOUT)
- scrollOffset = 0
+ scrollOffset = 1
if isScrollbarVisible:
scrollOffset = 3
- self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelOffset - 1, len(self._connectionLines), 1 + detailPanelOffset)
+ self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelOffset - 1, len(self._entryLines), 1 + detailPanelOffset)
currentTime = self._pauseTime if self._pauseTime else time.time()
- for lineNum in range(scrollLoc, len(self._connectionLines)):
- entryLine = self._connectionLines[lineNum]
+ for lineNum in range(scrollLoc, len(self._entryLines)):
+ entryLine = self._entryLines[lineNum]
# hilighting if this is the selected line
extraFormat = curses.A_STANDOUT if entryLine == cursorSelection else curses.A_NORMAL
@@ -216,41 +216,68 @@
if self._lastResourceFetch != currentResolutionCount:
self.valsLock.acquire()
- currentConnections = connResolver.getConnections()
- # Replacement listing of connections. We first populate it with any of
- # our old entries in currentConnections, then add new ConnectionEntries
- # for whatever remains.
- newConnections = []
+ newEntries = [] # the new results we'll display
- # preserves any ConnectionEntries they already exist
- for entry in self._connections:
- if isinstance(entry, connEntry.ConnectionEntry):
- connLine = entry.getLines()[0]
+ # Fetches new connections and client circuits...
+ # newConnections [(local ip, local port, foreign ip, foreign port)...]
+ # newCircuits {circuitID => (status, purpose, path)...}
+
+ newConnections = connResolver.getConnections()
+ newCircuits = {}
+
+ for circuitID, status, purpose, path in torTools.getConn().getCircuits():
+ # Skips established single-hop circuits (these are for directory
+ # fetches, not client circuits)
+ if not (status == "BUILT" and len(path) == 1):
+ newCircuits[circuitID] = (status, purpose, path)
+
+ # Populates newEntries with any of our old entries that still exist.
+ # This is both for performance and to keep from resetting the uptime
+ # attributes. Note that ClientEntries are a ConnectionEntry subclass so
+ # we need to check for them first.
+
+ for oldEntry in self._entries:
+ if isinstance(oldEntry, clientEntry.ClientEntry):
+ newEntry = newCircuits.get(oldEntry.circuitID)
+
+ if newEntry:
+ oldEntry.update(newEntry[0], newEntry[2])
+ newEntries.append(oldEntry)
+ del newCircuits[oldEntry.circuitID]
+ elif isinstance(oldEntry, connEntry.ConnectionEntry):
+ connLine = oldEntry.getLines()[0]
connAttr = (connLine.local.getIpAddr(), connLine.local.getPort(),
connLine.foreign.getIpAddr(), connLine.foreign.getPort())
- if connAttr in currentConnections:
- newConnections.append(entry)
- currentConnections.remove(connAttr)
+ if connAttr in newConnections:
+ newEntries.append(oldEntry)
+ newConnections.remove(connAttr)
- # reset any display attributes for the entries we're keeping
- for entry in newConnections:
- entry.resetDisplay()
+ # Reset any display attributes for the entries we're keeping
+ for entry in newEntries: entry.resetDisplay()
- # add new entries for any additions
- for lIp, lPort, fIp, fPort in currentConnections:
- newConnections.append(connEntry.ConnectionEntry(lIp, lPort, fIp, fPort))
+ # Adds any new connection and circuit entries.
+ for lIp, lPort, fIp, fPort in newConnections:
+ newConnEntry = connEntry.ConnectionEntry(lIp, lPort, fIp, fPort)
+ if newConnEntry.getLines()[0].getType() != connEntry.Category.CLIENT:
+ newEntries.append(newConnEntry)
+ for circuitID in newCircuits:
+ status, purpose, path = newCircuits[circuitID]
+ newEntries.append(clientEntry.ClientEntry(circuitID, status, purpose, path))
+
# Counts the relays in each of the categories. This also flushes the
# type cache for all of the connections (in case its changed since last
# fetched).
categoryTypes = connEntry.Category.values()
typeCounts = dict((type, 0) for type in categoryTypes)
- for entry in newConnections:
+ for entry in newEntries:
if isinstance(entry, connEntry.ConnectionEntry):
typeCounts[entry.getLines()[0].getType()] += 1
+ elif isinstance(entry, clientEntry.ClientEntry):
+ typeCounts[connEntry.Category.CLIENT] += 1
# makes labels for all the categories with connections (ie,
# "21 outbound", "1 control", etc)
@@ -263,11 +290,11 @@
if countLabels: self._title = "Connections (%s):" % ", ".join(countLabels)
else: self._title = "Connections:"
- self._connections = newConnections
+ self._entries = newEntries
- self._connectionLines = []
- for entry in self._connections:
- self._connectionLines += entry.getLines()
+ self._entryLines = []
+ for entry in self._entries:
+ self._entryLines += entry.getLines()
self.setSortOrder()
self._lastResourceFetch = currentResolutionCount
Modified: arm/trunk/src/util/torTools.py
===================================================================
--- arm/trunk/src/util/torTools.py 2011-03-21 15:14:17 UTC (rev 24401)
+++ arm/trunk/src/util/torTools.py 2011-03-21 16:39:27 UTC (rev 24402)
@@ -606,7 +606,7 @@
def getCircuits(self, default = []):
"""
This provides a list with tuples of the form:
- (status, purpose, (fingerprint1, fingerprint2...))
+ (circuitID, status, purpose, (fingerprint1, fingerprint2...))
Arguments:
default - value provided back if unable to query the circuit-status
@@ -1394,15 +1394,9 @@
for line in orconnResults.split("\n"):
self._fingerprintsAttachedCache.append(line[1:line.find("=")])
- # circuit-status has entries of the form:
- # 7 BUILT $33173252B70A50FE3928C7453077936D71E45C52=shiven,...
- circStatusResults = self.getInfo("circuit-status")
- if circStatusResults:
- for line in circStatusResults.split("\n"):
- clientEntries = line.split(" ")[2].split(",")
-
- for entry in clientEntries:
- self._fingerprintsAttachedCache.append(entry[1:entry.find("=")])
+ # circuit-status results (we only make connections to the first hop)
+ for _, _, _, path in self.getCircuits():
+ self._fingerprintsAttachedCache.append(path[0])
# narrow to only relays we have a connection to
attachedMatches = []
@@ -1608,7 +1602,7 @@
if len(lineComp) < 4: continue
path = tuple([hopEntry[1:41] for hopEntry in lineComp[2].split(",")])
- result.append((lineComp[1], lineComp[3], path))
+ result.append((int(lineComp[0]), lineComp[1], lineComp[3][8:], path))
# cache value
if result != None: self._cachedParam[key] = result
Modified: arm/trunk/src/util/uiTools.py
===================================================================
--- arm/trunk/src/util/uiTools.py 2011-03-21 15:14:17 UTC (rev 24401)
+++ arm/trunk/src/util/uiTools.py 2011-03-21 16:39:27 UTC (rev 24402)
@@ -415,10 +415,23 @@
chained together to compose lines with multiple types of formatting.
"""
- def __init__(self, text, format=curses.A_NORMAL, nextEntry=None):
+ def __init__(self, text, format=curses.A_NORMAL, nextEntry=None, lockFormat=False):
+ """
+ Constructor for prepared draw entries.
+
+ Arguments:
+ text - content to be drawn, this can either be a string or list of
+ integer character codes
+ format - properties to apply when drawing
+ nextEntry - entry to be drawn after this one
+ lockFormat - prevents extra formatting attributes from being applied
+ when rendered if true
+ """
+
self.text = text
self.format = format
self.nextEntry = nextEntry
+ self.lockFormat = lockFormat
def getNext(self):
"""
@@ -449,9 +462,16 @@
extraFormat - additional formatting
"""
- drawFormat = self.format | extraFormat
- drawPanel.addstr(y, x, self.text, drawFormat)
+ if self.lockFormat: drawFormat = self.format
+ else: drawFormat = self.format | extraFormat
+ if isinstance(self.text, str):
+ drawPanel.addstr(y, x, self.text, drawFormat)
+ else:
+ for i in range(len(self.text)):
+ drawChar = self.text[i]
+ drawPanel.addch(y, x + i, drawChar, drawFormat)
+
# if there's additional content to show then render it too
if self.nextEntry:
self.nextEntry.render(drawPanel, y, x + len(self.text), extraFormat)
More information about the tor-commits
mailing list