commit ca331a6fba5ead083c4a010b72e5d4886396e7a1 Author: Damian Johnson atagar@torproject.org Date: Wed Apr 16 10:15:18 2014 -0700
Supporting the /info option
Adding support for a /info option that provides a simple summary of a relay's information. This accepts a fingerprint, nickname, or IP address. If there's a match this provides basic information in a similar fashion to arm. --- stem/interpretor/commands.py | 132 ++++++++++++++++++++++++++++++++++++++++- stem/interpretor/settings.cfg | 6 ++ 2 files changed, 135 insertions(+), 3 deletions(-)
diff --git a/stem/interpretor/commands.py b/stem/interpretor/commands.py index c711ade..2d2d0a5 100644 --- a/stem/interpretor/commands.py +++ b/stem/interpretor/commands.py @@ -6,8 +6,10 @@ import os import re
import stem +import stem.util.connection import stem.util.conf import stem.util.log +import stem.util.tor_tools
from stem.util.term import Attr, Color, format
@@ -48,6 +50,7 @@ SIGNAL_DESCRIPTIONS = ( HELP_OPTIONS = { 'HELP': ("/help [OPTION]", 'help.help'), 'EVENTS': ("/events [types]", 'help.events'), + 'INFO': ("/info [relay fingerprint, nickname, or IP address]", 'help.info'), 'QUIT': ("/quit", 'help.quit'), 'GETINFO': ("GETINFO OPTION", 'help.getinfo'), 'GETCONF': ("GETCONF OPTION", 'help.getconf'), @@ -341,6 +344,127 @@ class ControlInterpretor(object):
return '\n'.join([format(str(event), *OUTPUT_FORMAT) for event in events])
+ def do_info(self, arg): + """ + 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. + """ + + output, fingerprint = '', None + + # determines the fingerprint, leaving it unset and adding an error message + # if unsuccessful + + if not arg: + # uses our fingerprint if we're a relay, otherwise gives an error + + fingerprint = self.controller.get_info('fingerprint', None) + + if not fingerprint: + output += format("We aren't a relay, no information to provide", *ERROR_FORMAT) + elif stem.util.tor_tools.is_valid_fingerprint(arg): + fingerprint = arg + elif stem.util.tor_tools.is_valid_nickname(arg): + desc = self.controller.get_network_status(arg, None) + + if desc: + fingerprint = desc.fingerprint + else: + return format("Unable to find a relay with the nickname of '%s'" % arg, *ERROR_FORMAT) + elif ':' in arg or stem.util.connection.is_valid_ipv4_address(arg): + # we got an address, so looking up the fingerprint + + if ':' in arg: + address, port = arg.split(':', 1) + + if not stem.util.connection.is_valid_ipv4_address(address): + return format("'%s' isn't a valid IPv4 address" % address, *ERROR_FORMAT) + elif port and not stem.util.connection.is_valid_port(port): + return format("'%s' isn't a valid port" % port, *ERROR_FORMAT) + + port = int(port) + else: + address, port = arg, None + + matches = {} + + for desc in self.controller.get_network_statuses(): + if desc.address == address: + if not port or desc.or_port == port: + matches[desc.or_port] = desc.fingerprint + + if len(matches) == 0: + output += format('No relays found at %s' % arg, *ERROR_FORMAT) + elif len(matches) == 1: + fingerprint = matches.values()[0] + else: + output += format("There's multiple relays at %s, include a port to specify which.\n\n" % arg, *ERROR_FORMAT) + + for i, or_port in enumerate(matches): + output += format(" %i. %s:%s, fingerprint: %s\n" % (i + 1, address, or_port, matches[or_port]), *ERROR_FORMAT) + else: + return format("'%s' isn't a fingerprint, nickname, or IP address" % arg, *ERROR_FORMAT) + + if fingerprint: + micro_desc = self.controller.get_microdescriptor(fingerprint, None) + server_desc = self.controller.get_server_descriptor(fingerprint, None) + ns_desc = self.controller.get_network_status(fingerprint, None) + + # We'll mostly rely on the router status entry. Either the server + # descriptor or microdescriptor will be missing, so we'll treat them as + # being optional. + + if not ns_desc: + return format("Unable to find consensus information for %s" % fingerprint, *ERROR_FORMAT) + + locale = self.controller.get_info('ip-to-country/%s' % ns_desc.address, None) + locale_label = ' (%s)' % locale if locale else '' + + if server_desc: + exit_policy_label = server_desc.exit_policy.summary() + elif micro_desc: + exit_policy_label = micro_desc.exit_policy.summary() + else: + exit_policy_label = 'Unknown' + + output += '%s (%s)\n' % (ns_desc.nickname, fingerprint) + + output += format('address: ', *BOLD_OUTPUT_FORMAT) + output += '%s:%s%s\n' % (ns_desc.address, ns_desc.or_port, locale_label) + + output += format('published: ', *BOLD_OUTPUT_FORMAT) + output += ns_desc.published.strftime('%H:%M:%S %d/%m/%Y') + '\n' + + if server_desc: + output += format('os: ', *BOLD_OUTPUT_FORMAT) + output += server_desc.platform.decode('utf-8', 'replace') + '\n' + + output += format('version: ', *BOLD_OUTPUT_FORMAT) + output += str(server_desc.tor_version) + '\n' + + output += format('flags: ', *BOLD_OUTPUT_FORMAT) + output += ', '.join(ns_desc.flags) + '\n' + + output += format('exit policy: ', *BOLD_OUTPUT_FORMAT) + output += exit_policy_label + '\n' + + if server_desc: + contact = server_desc.contact + + # clears up some highly common obscuring + + for alias in (' at ', ' AT '): + contact = contact.replace(alias, '@') + + for alias in (' dot ', ' DOT '): + contact = contact.replace(alias, '.') + + output += format('contact: ', *BOLD_OUTPUT_FORMAT) + output += contact + '\n' + + return output.strip() + def run_command(self, command): """ Runs the given command. Requests starting with a '/' are special commands @@ -378,11 +502,13 @@ class ControlInterpretor(object): output = ''
if cmd.startswith('/'): - if cmd == "/quit": + if cmd == '/quit': raise stem.SocketClosed() - elif cmd == "/events": + elif cmd == '/events': output = self.do_events(arg) - elif cmd == "/help": + elif cmd == '/info': + output = self.do_info(arg) + elif cmd == '/help': output = self.do_help(arg) else: output = format("'%s' isn't a recognized command" % command, *ERROR_FORMAT) diff --git a/stem/interpretor/settings.cfg b/stem/interpretor/settings.cfg index e93b0e8..9430748 100644 --- a/stem/interpretor/settings.cfg +++ b/stem/interpretor/settings.cfg @@ -16,6 +16,7 @@ help.general |Interpretor commands include: | /help - provides information for interpretor and tor commands/config options | /events - prints events that we've received +| /info - general information for a relay | /quit - shuts down the interpretor | |Tor commands include: @@ -47,6 +48,7 @@ help.help |configuration option. | |Example: +| /help info # provides a description of the '/info' option | /help GETINFO # usage information for tor's GETINFO controller option
help.events @@ -54,6 +56,10 @@ help.events |no types are specified then this provides all the messages that we've |received.
+help.info +|Provides general information for a relay that's currently in the consensus. +|If no relay is specified then this provides information on ourselves. + help.getinfo |Queries the tor process for information. Options are... |
tor-commits@lists.torproject.org