commit 6b4af377238ac7e863f4b001987d6d4786a003ad Author: Damian Johnson atagar@torproject.org Date: Mon Sep 12 20:23:03 2011 -0700
Implementing /info interpretor option
This provides a reader friendly summary of relay information. Relayed can be queried this way by fingerprint, nickname, or IP address. --- src/cli/connections/connEntry.py | 2 +- src/cli/controller.py | 3 +- src/util/torInterpretor.py | 131 +++++++++++++++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 3 deletions(-)
diff --git a/src/cli/connections/connEntry.py b/src/cli/connections/connEntry.py index de3fb01..02f7069 100644 --- a/src/cli/connections/connEntry.py +++ b/src/cli/connections/connEntry.py @@ -718,7 +718,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
dirPortLabel = "" if dirPort == "0" else "dirport: %s" % dirPort lines[2] = "nickname: %-25s orport: %-10s %s" % (nickname, orPort, dirPortLabel) - lines[3] = "published: %s %s" % (pubDate, pubTime) + lines[3] = "published: %s %s" % (pubTime, pubDate) lines[4] = "flags: %s" % flags.replace(" ", ", ") lines[5] = "exit policy: %s" % policyLabel
diff --git a/src/cli/controller.py b/src/cli/controller.py index a0b465c..a841492 100644 --- a/src/cli/controller.py +++ b/src/cli/controller.py @@ -24,7 +24,7 @@ import cli.connections.connPanel
from TorCtl import TorCtl
-from util import connections, conf, enum, log, panel, sysTools, torConfig, torTools +from util import connections, conf, enum, hostnames, log, panel, sysTools, torConfig, torTools
ARM_CONTROLLER = None
@@ -568,6 +568,7 @@ def shutdownDaemons():
# joins on utility daemon threads - this might take a moment since the # internal threadpools being joined might be sleeping + hostnames.stop() resourceTrackers = sysTools.RESOURCE_TRACKERS.values() resolver = connections.getResolver("tor") if connections.isResolverAlive("tor") else None for tracker in resourceTrackers: tracker.stop() diff --git a/src/util/torInterpretor.py b/src/util/torInterpretor.py index 7eaec16..d6f9340 100644 --- a/src/util/torInterpretor.py +++ b/src/util/torInterpretor.py @@ -9,7 +9,7 @@ import readline
import version
-from util import enum, torTools +from util import connections, enum, hostnames, torTools
INIT_MSG = """Arm %s Control Interpretor Enter "/help" for usage information and "/quit" to stop. @@ -269,6 +269,130 @@ class ControlInterpretor: outputEntry.append((lineText[match.end():] + "\n", OUTPUT_FORMAT)) printedLines.append(lineText)
+ def doInfo(self, arg, outputEntry): + """ + Performs the '/info' operation, looking up a relay by fingerprint, IP + address, or nickname and printing its descriptor and consensus entries in a + pretty fashion. + """ + + fingerprint, conn = None, torTools.getConn() + + # TODO: also recognize <ip>:<port> entries? + + # determines the fingerprint, leaving it unset and adding an error message + # if unsuccessful + if not arg: + outputEntry.append(("No fingerprint or nickname to look up", ERROR_FORMAT)) + elif len(arg) == 40 and re.match("^[0-9a-fA-F]+$", arg): + # we got a fingerprint (fourty character hex string) + fingerprint = arg + elif connections.isValidIpAddress(arg): + # we got an ip address, look up the fingerprint + fpMatches = conn.getRelayFingerprint(arg, getAllMatches = True) + + if len(fpMatches) == 0: + outputEntry.append(("No relays found at %s" % arg, ERROR_FORMAT)) + elif len(fpMatches) == 1: + fingerprint = fpMatches[0][1] + else: + outputEntry.append(("Multiple relays at %s, specify which by giving a port" % arg, ERROR_FORMAT)) + + for i in range(len(fpMatches)): + relayEntry = outputEntry[i] + outputEntry.append((" %i. or port: %-5s fingerprint: %s" % (i + 1, relayEntry[0], relayEntry[1]), ERROR_FORMAT)) + else: + # we got something else, treat it as a nickname + fingerprint = conn.getNicknameFingerprint(arg) + + if not fingerprint: + outputEntry.append(("No relay with the nickname of '%s' found" % arg, ERROR_FORMAT)) + + if fingerprint: + consensusEntry = conn.getConsensusEntry(fingerprint) + + # The nickname, address, and port lookups are all based on the consensus + # entry so if this succeeds we should be pretty confident that those + # queries will work too. + + if not consensusEntry: + outputEntry.append(("Unable to find consensus information for %s" % fingerprint, ERROR_FORMAT)) + return + + address, port = conn.getRelayAddress(fingerprint, (None, None)) + + # ... but not sure enough that we won't check + if not address or not port: return + + locale = conn.getInfo("ip-to-country/%s" % address, "??") + hostname = hostnames.resolve(address, 10) + + # TODO: Most of the following is copied from the _getDetailContent method + # of cli/connections/connEntry.py - useful bits should be refactored. + consensusLines = consensusEntry.split("\n") + + firstLineComp = consensusLines[0].split(" ") + if len(firstLineComp) >= 9: + _, nickname, _, _, pubDate, pubTime, _, orPort, _ = firstLineComp[:9] + else: nickname, pubDate, pubTime, orPort = "", "", "", "" + + flags = "unknown" + if len(consensusLines) >= 2 and consensusLines[1].startswith("s "): + flags = consensusLines[1][2:] + + exitPolicy = conn.getRelayExitPolicy(fingerprint) + + if exitPolicy: policyLabel = exitPolicy.getSummary() + else: policyLabel = "unknown" + + # fetches information from the descriptor if it's available + torVersion, platform, contact = "", "", "" + descriptorEntry = conn.getDescriptorEntry(fingerprint) + + if descriptorEntry: + for descLine in descriptorEntry.split("\n"): + if descLine.startswith("platform"): + # has the tor version and platform, ex: + # platform Tor 0.2.1.29 (r318f470bc5f2ad43) on Linux x86_64 + + torVersion = descLine[13:descLine.find(" ", 13)] + platform = descLine[descLine.rfind(" on ") + 4:] + elif descLine.startswith("contact"): + contact = descLine[8:] + + # clears up some highly common obscuring + for alias in (" at ", " AT "): contact = contact.replace(alias, "@") + for alias in (" dot ", " DOT "): contact = contact.replace(alias, ".") + + break # contact lines come after the platform + + headingAttr, infoAttr = (Attr.BOLD, Color.BLUE), () + + outputEntry.append(("%s (%s)\n" % (nickname, fingerprint), infoAttr)) + + outputEntry.append(("address: ", headingAttr)) + outputEntry.append(("%s:%s (%s, %s)\n" % (address, port, locale, hostname), infoAttr)) + + outputEntry.append(("published: ", headingAttr)) + outputEntry.append(("%s %s" % (pubTime, pubDate) + "\n", infoAttr)) + + if torVersion and platform: + outputEntry.append(("os: ", headingAttr)) + outputEntry.append((platform + "\n", infoAttr)) + + outputEntry.append(("version: ", headingAttr)) + outputEntry.append((torVersion + "\n", infoAttr)) + + outputEntry.append(("flags: ", headingAttr)) + outputEntry.append((flags.replace(" ", ", ") + "\n", infoAttr)) + + outputEntry.append(("exit policy: ", headingAttr)) + outputEntry.append((policyLabel + "\n", infoAttr)) + + if contact: + outputEntry.append(("contact: ", headingAttr)) + outputEntry.append((contact + "\n", infoAttr)) + def handleQuery(self, input): """ Processes the given input. Requests starting with a '/' are special @@ -308,6 +432,7 @@ class ControlInterpretor: if cmd == "/quit": raise InterpretorClosed() elif cmd == "/write": self.doWrite(arg, outputEntry) elif cmd == "/find": self.doFind(arg, outputEntry) + elif cmd == "/info": self.doInfo(arg, outputEntry) else: outputEntry.append(("Not yet implemented...", ERROR_FORMAT)) # TODO: implement
@@ -393,6 +518,10 @@ def prompt(): # moves cursor to the next line and terminates (most commonly # KeyboardInterrupt and EOFErro) print + + # stop daemons + hostnames.stop() + break
for line in outputEntry:
tor-commits@lists.torproject.org