[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [nyx/master] Move conn_panel to be a top level module
commit 7a9c8b78b80e504a3b83fb92588b1bb2d36e3f39
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Mon Aug 31 10:04:38 2015 -0700
Move conn_panel to be a top level module
Plan is to rewrite all of nyx/connections/* and hopefully shrink it to a point
where it can all be in one module. We did this for the graphing module and it
worked well - fingers crossed!
---
nyx/connection_panel.py | 583 +++++++++++++++++++++++++++++++++++++++++
nyx/connections/__init__.py | 1 -
nyx/connections/circ_entry.py | 11 +-
nyx/connections/conn_entry.py | 17 +-
nyx/connections/conn_panel.py | 583 -----------------------------------------
nyx/connections/entries.py | 6 +-
nyx/controller.py | 4 +-
7 files changed, 603 insertions(+), 602 deletions(-)
diff --git a/nyx/connection_panel.py b/nyx/connection_panel.py
new file mode 100644
index 0000000..f2d613e
--- /dev/null
+++ b/nyx/connection_panel.py
@@ -0,0 +1,583 @@
+"""
+Listing of the currently established connections tor has made.
+"""
+
+import re
+import time
+import collections
+import curses
+import itertools
+import threading
+
+import nyx.popups
+import nyx.util.tracker
+
+from nyx.connections import descriptor_popup, entries
+from nyx.util import panel, tor_controller, ui_tools
+
+from stem.control import State
+from stem.util import conf, connection, enum
+
+# height of the detail panel content, not counting top and bottom border
+
+DETAILS_HEIGHT = 7
+
+# listing types
+
+Listing = enum.Enum(('IP_ADDRESS', 'IP Address'), 'FINGERPRINT', 'NICKNAME')
+
+EXIT_USAGE_WIDTH = 15
+UPDATE_RATE = 5 # rate in seconds at which we refresh
+
+# Connection Categories:
+# Inbound Relay connection, coming to us.
+# Outbound Relay connection, leaving us.
+# Exit Outbound relay connection leaving the Tor network.
+# Hidden Connections to a hidden service we're providing.
+# Socks Socks connections for applications using Tor.
+# Circuit Circuits our tor client has created.
+# Directory Fetching tor consensus information.
+# Control Tor controller (nyx, vidalia, etc).
+
+Category = enum.Enum('INBOUND', 'OUTBOUND', 'EXIT', 'HIDDEN', 'SOCKS', 'CIRCUIT', 'DIRECTORY', 'CONTROL')
+
+CATEGORY_COLOR = {
+ Category.INBOUND: 'green',
+ Category.OUTBOUND: 'blue',
+ Category.EXIT: 'red',
+ Category.HIDDEN: 'magenta',
+ Category.SOCKS: 'yellow',
+ Category.CIRCUIT: 'cyan',
+ Category.DIRECTORY: 'magenta',
+ Category.CONTROL: 'red',
+}
+
+SortAttr = enum.Enum('CATEGORY', 'UPTIME', 'LISTING', 'IP_ADDRESS', 'PORT', 'FINGERPRINT', 'NICKNAME', 'COUNTRY')
+
+SORT_COLORS = {
+ SortAttr.CATEGORY: 'red',
+ SortAttr.UPTIME: 'yellow',
+ SortAttr.LISTING: 'green',
+ SortAttr.IP_ADDRESS: 'blue',
+ SortAttr.PORT: 'blue',
+ SortAttr.FINGERPRINT: 'cyan',
+ SortAttr.NICKNAME: 'cyan',
+ SortAttr.COUNTRY: 'blue',
+}
+
+
+def conf_handler(key, value):
+ if key == 'features.connection.listing_type':
+ return conf.parse_enum(key, value, Listing)
+ elif key == 'features.connection.order':
+ return conf.parse_enum_csv(key, value[0], SortAttr, 3)
+
+
+CONFIG = conf.config_dict('nyx', {
+ 'features.connection.resolveApps': True,
+ 'features.connection.listing_type': Listing.IP_ADDRESS,
+ 'features.connection.order': [
+ SortAttr.CATEGORY,
+ SortAttr.LISTING,
+ SortAttr.UPTIME],
+ 'features.connection.showIps': True,
+}, conf_handler)
+
+
+class ConnectionPanel(panel.Panel, threading.Thread):
+ """
+ Listing of connections tor is making, with information correlated against
+ the current consensus and other data sources.
+ """
+
+ def __init__(self, stdscr):
+ panel.Panel.__init__(self, stdscr, 'connections', 0)
+ threading.Thread.__init__(self)
+ self.setDaemon(True)
+
+ # defaults our listing selection to fingerprints if ip address
+ # displaying is disabled
+ #
+ # TODO: This is a little sucky in that it won't work if showIps changes
+ # while we're running (... but nyx doesn't allow for that atm)
+
+ if not CONFIG['features.connection.showIps'] and CONFIG['features.connection.listing_type'] == 0:
+ nyx_config = conf.get_config('nyx')
+ nyx_config.set('features.connection.listing_type', Listing.keys()[Listing.index_of(Listing.FINGERPRINT)])
+
+ self._scroller = ui_tools.Scroller(True)
+ self._entries = [] # last fetched display entries
+ self._entry_lines = [] # individual lines rendered from the entries listing
+ self._show_details = False # presents the details panel if true
+
+ self._last_update = -1 # time the content was last revised
+ self._is_tor_running = True # indicates if tor is currently running or not
+ self._halt_time = None # time when tor was stopped
+ self._vals_lock = threading.RLock()
+
+ self._pause_condition = threading.Condition()
+ self._halt = False # terminates thread if true
+
+ # Tracks exiting port and client country statistics
+
+ self._client_locale_usage = {}
+ self._exit_port_usage = {}
+
+ # If we're a bridge and been running over a day then prepopulates with the
+ # last day's clients.
+
+ controller = tor_controller()
+ bridge_clients = controller.get_info('status/clients-seen', None)
+
+ if bridge_clients:
+ # Response has a couple arguments...
+ # TimeStarted="2011-08-17 15:50:49" CountrySummary=us=16,de=8,uk=8
+
+ country_summary = None
+
+ for line in bridge_clients.split():
+ if line.startswith('CountrySummary='):
+ country_summary = line[15:]
+ break
+
+ if country_summary:
+ for entry in country_summary.split(','):
+ if re.match('^..=[0-9]+$', entry):
+ locale, count = entry.split('=', 1)
+ self._client_locale_usage[locale] = int(count)
+
+ # Last sampling received from the ConnectionResolver, used to detect when
+ # it changes.
+
+ self._last_resource_fetch = -1
+
+ # mark the initially exitsing connection uptimes as being estimates
+
+ from nyx.connections import conn_entry
+
+ for entry in self._entries:
+ if isinstance(entry, conn_entry.ConnectionEntry):
+ entry.get_lines()[0].is_initial_connection = True
+
+ # listens for when tor stops so we know to stop reflecting changes
+
+ controller.add_status_listener(self.tor_state_listener)
+
+ def tor_state_listener(self, controller, event_type, _):
+ """
+ Freezes the connection contents when Tor stops.
+ """
+
+ self._is_tor_running = event_type in (State.INIT, State.RESET)
+ self._halt_time = None if self._is_tor_running else time.time()
+ self.redraw(True)
+
+ def get_pause_time(self):
+ """
+ Provides the time Tor stopped if it isn't running. Otherwise this is the
+ time we were last paused.
+ """
+
+ return self._halt_time if self._halt_time else panel.Panel.get_pause_time(self)
+
+ def set_sort_order(self, ordering = None):
+ """
+ Sets the connection attributes we're sorting by and resorts the contents.
+
+ Arguments:
+ ordering - new ordering, if undefined then this resorts with the last
+ set ordering
+ """
+
+ with self._vals_lock:
+ if ordering:
+ nyx_config = conf.get_config('nyx')
+
+ ordering_keys = [SortAttr.keys()[SortAttr.index_of(v)] for v in ordering]
+ nyx_config.set('features.connection.order', ', '.join(ordering_keys))
+
+ def sort_value(entry, attr):
+ if attr == SortAttr.LISTING:
+ if self.get_listing_type() == Listing.IP_ADDRESS:
+ attr = SortAttr.IP_ADDRESS
+ elif self.get_listing_type() == Listing.FINGERPRINT:
+ attr = SortAttr.FINGERPRINT
+ elif self.get_listing_type() == Listing.NICKNAME:
+ attr = SortAttr.NICKNAME
+
+ connection_line = entry.get_lines()[0]
+
+ if attr == SortAttr.IP_ADDRESS:
+ if entry.is_private():
+ return 255 ** 4 # orders at the end
+
+ ip_value = 0
+
+ for octet in connection_line.connection.remote_address.split('.'):
+ ip_value = ip_value * 255 + int(octet)
+
+ return ip_value * 65536 + connection_line.connection.remote_port
+ elif attr == SortAttr.PORT:
+ return connection_line.connection.remote_port
+ elif attr == SortAttr.FINGERPRINT:
+ return connection_line.get_fingerprint('UNKNOWN')
+ elif attr == SortAttr.NICKNAME:
+ return connection_line.get_nickname('z' * 20)
+ elif attr == SortAttr.CATEGORY:
+ return Category.index_of(entry.get_type())
+ elif attr == SortAttr.UPTIME:
+ return connection_line.connection.start_time
+ elif attr == SortAttr.COUNTRY:
+ return '' if entry.is_private() else connection_line.get_locale('')
+ else:
+ return ''
+
+ self._entries.sort(key = lambda i: [sort_value(i, attr) for attr in CONFIG['features.connection.order']])
+ self._entry_lines = list(itertools.chain.from_iterable([entry.get_lines() for entry in self._entries]))
+
+ def get_listing_type(self):
+ """
+ Provides the priority content we list connections by.
+ """
+
+ return CONFIG['features.connection.listing_type']
+
+ def set_listing_type(self, listing_type):
+ """
+ Sets the priority information presented by the panel.
+
+ Arguments:
+ listing_type - Listing instance for the primary information to be shown
+ """
+
+ if self.get_listing_type() == listing_type:
+ return
+
+ with self._vals_lock:
+ nyx_config = conf.get_config('nyx')
+ nyx_config.set('features.connection.listing_type', Listing.keys()[Listing.index_of(listing_type)])
+
+ # if we're sorting by the listing then we need to resort
+
+ if SortAttr.LISTING in CONFIG['features.connection.order']:
+ self.set_sort_order()
+
+ def show_sort_dialog(self):
+ """
+ Provides the sort dialog for our connections.
+ """
+
+ # set ordering for connection options
+
+ title_label = 'Connection Ordering:'
+ options = list(SortAttr)
+ old_selection = CONFIG['features.connection.order']
+ option_colors = dict([(attr, SORT_COLORS[attr]) for attr in options])
+ results = nyx.popups.show_sort_dialog(title_label, options, old_selection, option_colors)
+
+ if results:
+ self.set_sort_order(results)
+
+ def handle_key(self, key):
+ with self._vals_lock:
+ user_traffic_allowed = tor_controller().is_user_traffic_allowed()
+
+ if key.is_scroll():
+ page_height = self.get_preferred_size()[0] - 1
+
+ if self._show_details:
+ page_height -= (DETAILS_HEIGHT + 1)
+
+ is_changed = self._scroller.handle_key(key, self._entry_lines, page_height)
+
+ if is_changed:
+ self.redraw(True)
+ elif key.is_selection():
+ self._show_details = not self._show_details
+ self.redraw(True)
+ elif key.match('s'):
+ self.show_sort_dialog()
+ elif key.match('u'):
+ # provides a menu to pick the connection resolver
+
+ title = 'Resolver Util:'
+ options = ['auto'] + list(connection.Resolver)
+ conn_resolver = nyx.util.tracker.get_connection_tracker()
+
+ current_overwrite = conn_resolver.get_custom_resolver()
+
+ if current_overwrite is None:
+ old_selection = 0
+ else:
+ old_selection = options.index(current_overwrite)
+
+ selection = nyx.popups.show_menu(title, options, old_selection)
+
+ # applies new setting
+
+ if selection != -1:
+ selected_option = options[selection] if selection != 0 else None
+ conn_resolver.set_custom_resolver(selected_option)
+ elif key.match('l'):
+ # provides a menu to pick the primary information we list connections by
+
+ title = 'List By:'
+ options = list(Listing)
+
+ old_selection = options.index(self.get_listing_type())
+ selection = nyx.popups.show_menu(title, options, old_selection)
+
+ # applies new setting
+
+ if selection != -1:
+ self.set_listing_type(options[selection])
+ elif key.match('d'):
+ self.set_title_visible(False)
+ self.redraw(True)
+
+ while True:
+ selection = self.get_selection()
+
+ if not selection:
+ break
+
+ color = CATEGORY_COLOR[selection.get_type()]
+ fingerprint = selection.get_fingerprint()
+ is_close_key = lambda key: key.is_selection() or key.match('d') or key.match('left') or key.match('right')
+ key = descriptor_popup.show_descriptor_popup(fingerprint, color, self.max_x, is_close_key)
+
+ if not key or key.is_selection() or key.match('d'):
+ break # closes popup
+ elif key.match('left'):
+ self.handle_key(panel.KeyInput(curses.KEY_UP))
+ elif key.match('right'):
+ self.handle_key(panel.KeyInput(curses.KEY_DOWN))
+
+ self.set_title_visible(True)
+ self.redraw(True)
+ elif key.match('c') and user_traffic_allowed.inbound:
+ nyx.popups.show_count_dialog('Client Locales', self._client_locale_usage)
+ elif key.match('e') and user_traffic_allowed.outbound:
+ counts = {}
+ key_width = max(map(len, self._exit_port_usage.keys()))
+
+ for k, v in self._exit_port_usage.items():
+ usage = connection.port_usage(k)
+
+ if usage:
+ k = k.ljust(key_width + 3) + usage.ljust(EXIT_USAGE_WIDTH)
+
+ counts[k] = v
+
+ nyx.popups.show_count_dialog('Exiting Port Usage', counts)
+ else:
+ return False
+
+ return True
+
+ def run(self):
+ """
+ Keeps connections listing updated, checking for new entries at a set rate.
+ """
+
+ last_ran = -1
+
+ while not self._halt:
+ if self.is_paused() or not self._is_tor_running or (time.time() - last_ran) < UPDATE_RATE:
+ with self._pause_condition:
+ if not self._halt:
+ self._pause_condition.wait(0.2)
+
+ continue # done waiting, try again
+
+ self._update()
+ self.redraw(True)
+
+ # If this is our first run then fill in our fingerprint tracker. This
+ # requires fetching all the router status entries which takes a few
+ # seconds, so best done when we're finished with the rest of the first
+ # iteration to hide the lag.
+
+ if last_ran == -1:
+ nyx.util.tracker.get_consensus_tracker().update(tor_controller().get_network_statuses([]))
+
+ last_ran = time.time()
+
+ def get_help(self):
+ resolver_util = nyx.util.tracker.get_connection_tracker().get_custom_resolver()
+ user_traffic_allowed = tor_controller().is_user_traffic_allowed()
+
+ options = [
+ ('up arrow', 'scroll up a line', None),
+ ('down arrow', 'scroll down a line', None),
+ ('page up', 'scroll up a page', None),
+ ('page down', 'scroll down a page', None),
+ ('enter', 'show connection details', None),
+ ('d', 'raw consensus descriptor', None),
+ ('l', 'listed identity', self.get_listing_type().lower()),
+ ('s', 'sort ordering', None),
+ ('u', 'resolving utility', 'auto' if resolver_util is None else resolver_util),
+ ]
+
+ if user_traffic_allowed.inbound:
+ options.append(('c', 'client locale usage summary', None))
+
+ if user_traffic_allowed.outbound:
+ options.append(('e', 'exit port usage summary', None))
+
+ return options
+
+ def get_selection(self):
+ """
+ Provides the currently selected connection entry.
+ """
+
+ return self._scroller.get_cursor_selection(self._entry_lines)
+
+ def draw(self, width, height):
+ with self._vals_lock:
+ # if we don't have any contents then refuse to show details
+
+ if not self._entries:
+ self._show_details = False
+
+ # extra line when showing the detail panel is for the bottom border
+
+ detail_panel_offset = DETAILS_HEIGHT + 1 if self._show_details else 0
+ is_scrollbar_visible = len(self._entry_lines) > height - detail_panel_offset - 1
+
+ scroll_location = self._scroller.get_scroll_location(self._entry_lines, height - detail_panel_offset - 1)
+ cursor_selection = self.get_selection()
+
+ # draws the detail panel if currently displaying it
+
+ if self._show_details and cursor_selection:
+ # This is a solid border unless the scrollbar is visible, in which case a
+ # 'T' pipe connects the border to the bar.
+
+ ui_tools.draw_box(self, 0, 0, width, DETAILS_HEIGHT + 2)
+
+ if is_scrollbar_visible:
+ self.addch(DETAILS_HEIGHT + 1, 1, curses.ACS_TTEE)
+
+ draw_entries = cursor_selection.get_details(width)
+
+ for i in range(min(len(draw_entries), DETAILS_HEIGHT)):
+ self.addstr(1 + i, 2, draw_entries[i][0], *draw_entries[i][1])
+
+ # title label with connection counts
+
+ if self.is_title_visible():
+ self._draw_title(self._entries)
+
+ scroll_offset = 0
+
+ if is_scrollbar_visible:
+ scroll_offset = 2
+ self.add_scroll_bar(scroll_location, scroll_location + height - detail_panel_offset - 1, len(self._entry_lines), 1 + detail_panel_offset)
+
+ if self.is_paused() or not self._is_tor_running:
+ current_time = self.get_pause_time()
+ else:
+ current_time = time.time()
+
+ for line_number in range(scroll_location, len(self._entry_lines)):
+ entry_line = self._entry_lines[line_number]
+
+ # hilighting if this is the selected line
+
+ extra_format = curses.A_STANDOUT if entry_line == cursor_selection else curses.A_NORMAL
+
+ draw_line = line_number + detail_panel_offset + 1 - scroll_location
+
+ prefix = entry_line.get_listing_prefix()
+
+ for i in range(len(prefix)):
+ self.addch(draw_line, scroll_offset + i, prefix[i])
+
+ x_offset = scroll_offset + len(prefix)
+ draw_entry = entry_line.get_listing_entry(width - scroll_offset - len(prefix), current_time, self.get_listing_type())
+
+ for msg, attr in draw_entry:
+ attr |= extra_format
+ self.addstr(draw_line, x_offset, msg, attr)
+ x_offset += len(msg)
+
+ if draw_line >= height:
+ break
+
+ def _draw_title(self, entries):
+ if self._show_details:
+ title = 'Connection Details:'
+ elif not entries:
+ title = 'Connections:'
+ else:
+ counts = collections.Counter([entry.get_type() for entry in entries])
+ count_labels = ['%i %s' % (counts[category], category.lower()) for category in Category if counts[category]]
+ title = 'Connections (%s):' % ', '.join(count_labels)
+
+ self.addstr(0, 0, title, curses.A_STANDOUT)
+
+ def stop(self):
+ """
+ Halts further resolutions and terminates the thread.
+ """
+
+ with self._pause_condition:
+ self._halt = True
+ self._pause_condition.notifyAll()
+
+ def _update(self):
+ """
+ Fetches the newest resolved connections.
+ """
+
+ conn_resolver = nyx.util.tracker.get_connection_tracker()
+ current_resolution_count = conn_resolver.run_counter()
+
+ if not conn_resolver.is_alive():
+ return # if we're not fetching connections then this is a no-op
+ elif current_resolution_count == self._last_resource_fetch:
+ return # no new connections to process
+
+ new_entries = [entries.Entry.from_connection(conn) for conn in conn_resolver.get_value()]
+
+ for circ in tor_controller().get_circuits([]):
+ # Skips established single-hop circuits (these are for directory
+ # fetches, not client circuits)
+
+ if not (circ.status == 'BUILT' and len(circ.path) == 1):
+ new_entries.append(entries.Entry.from_circuit(circ))
+
+ with self._vals_lock:
+ # update stats for client and exit connections
+
+ for entry in new_entries:
+ entry_line = entry.get_lines()[0]
+
+ if entry.is_private() and entry.get_type() == Category.INBOUND:
+ client_locale = entry_line.get_locale(None)
+
+ if client_locale:
+ self._client_locale_usage[client_locale] = self._client_locale_usage.get(client_locale, 0) + 1
+ elif entry.get_type() == Category.EXIT:
+ exit_port = entry_line.connection.remote_port
+ self._exit_port_usage[exit_port] = self._exit_port_usage.get(exit_port, 0) + 1
+
+ self._entries, self._entry_lines = new_entries, list(itertools.chain.from_iterable([entry.get_lines() for entry in new_entries]))
+
+ self.set_sort_order()
+ self._last_resource_fetch = current_resolution_count
+
+ if CONFIG['features.connection.resolveApps']:
+ local_ports, remote_ports = [], []
+
+ for entry in new_entries:
+ line = entry.get_lines()[0]
+
+ if entry.get_type() in (Category.SOCKS, Category.CONTROL):
+ local_ports.append(line.connection.remote_port)
+ elif entry.get_type() == Category.HIDDEN:
+ remote_ports.append(line.connection.local_port)
+
+ nyx.util.tracker.get_port_usage_tracker().query(local_ports, remote_ports)
diff --git a/nyx/connections/__init__.py b/nyx/connections/__init__.py
index c7e90a9..577823a 100644
--- a/nyx/connections/__init__.py
+++ b/nyx/connections/__init__.py
@@ -5,7 +5,6 @@ Resources related to our connection panel.
__all__ = [
'circ_entry',
'conn_entry',
- 'conn_panel',
'descriptor_popup',
'entries',
]
diff --git a/nyx/connections/circ_entry.py b/nyx/connections/circ_entry.py
index cd42fc8..d2b1ef6 100644
--- a/nyx/connections/circ_entry.py
+++ b/nyx/connections/circ_entry.py
@@ -13,8 +13,9 @@ import datetime
import nyx.util.tracker
import nyx.util.ui_tools
+import nyx.connection_panel
-from nyx.connections import conn_entry, conn_panel, entries
+from nyx.connections import conn_entry, entries
from stem.util import str_tools
@@ -69,7 +70,7 @@ class CircHeaderLine(conn_entry.ConnectionLine):
def get_details(self, width):
if not self.is_built:
- detail_format = (curses.A_BOLD, conn_panel.CATEGORY_COLOR[self._entry.get_type()])
+ detail_format = (curses.A_BOLD, nyx.connection_panel.CATEGORY_COLOR[self._entry.get_type()])
return [('Building Circuit...', detail_format)]
else:
return conn_entry.ConnectionLine.get_details(self, width)
@@ -127,7 +128,7 @@ class CircLine(conn_entry.ConnectionLine):
return entries.ConnectionPanelLine.get_listing_entry(self, width, current_time, listing_type)
def _get_listing_entry(self, width, current_time, listing_type):
- line_format = nyx.util.ui_tools.get_color(conn_panel.CATEGORY_COLOR[self._entry.get_type()])
+ line_format = nyx.util.ui_tools.get_color(nyx.connection_panel.CATEGORY_COLOR[self._entry.get_type()])
# The required widths are the sum of the following:
# initial space (1 character)
@@ -139,7 +140,7 @@ class CircLine(conn_entry.ConnectionLine):
dst, etc = '', ''
- if listing_type == conn_panel.Listing.IP_ADDRESS:
+ if listing_type == nyx.connection_panel.Listing.IP_ADDRESS:
# dst width is derived as:
# src (21) + dst (26) + divider (7) + right gap (2) - bracket (3) = 53 char
@@ -150,7 +151,7 @@ class CircLine(conn_entry.ConnectionLine):
dst = '%s%-25s ' % (dst[:25], str_tools.crop(self.get_nickname('UNKNOWN'), 25, 0))
etc = self.get_etc_content(width - baseline_space - len(dst), listing_type)
- elif listing_type == conn_panel.Listing.FINGERPRINT:
+ elif listing_type == nyx.connection_panel.Listing.FINGERPRINT:
# dst width is derived as:
# src (9) + dst (40) + divider (7) + right gap (2) - bracket (3) = 55 char
diff --git a/nyx/connections/conn_entry.py b/nyx/connections/conn_entry.py
index fa85bc6..1f26f4c 100644
--- a/nyx/connections/conn_entry.py
+++ b/nyx/connections/conn_entry.py
@@ -5,12 +5,13 @@ Connection panel entries related to actual connections to or from the system
import curses
+import nyx.connection_panel
import nyx.util.tracker
import nyx.util.ui_tools
from nyx.util import tor_controller
-from nyx.connections import conn_panel, entries
-from nyx.connections.conn_panel import Category
+from nyx.connections import entries
+from nyx.connection_panel import Category
from stem.util import conf, connection, str_tools
@@ -129,7 +130,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
# category - "<type>"
# postType - ") "
- line_format = nyx.util.ui_tools.get_color(conn_panel.CATEGORY_COLOR[entry_type])
+ line_format = nyx.util.ui_tools.get_color(nyx.connection_panel.CATEGORY_COLOR[entry_type])
time_width = 6 if CONFIG['features.connection.markInitialConnections'] else 5
draw_entry = [(' ', line_format),
@@ -150,7 +151,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
width - available space to display in
"""
- detail_format = (curses.A_BOLD, conn_panel.CATEGORY_COLOR[self._entry.get_type()])
+ detail_format = (curses.A_BOLD, nyx.connection_panel.CATEGORY_COLOR[self._entry.get_type()])
return [(line, detail_format) for line in self._get_detail_content(width)]
def get_etc_content(self, width, listing_type):
@@ -185,7 +186,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
destination_address = self.get_destination_label(26, include_locale = True)
etc, used_space = '', 0
- if listing_type == conn_panel.Listing.IP_ADDRESS:
+ if listing_type == nyx.connection_panel.Listing.IP_ADDRESS:
if width > used_space + 42 and CONFIG['features.connection.showColumn.fingerprint']:
# show fingerprint (column width: 42 characters)
@@ -199,7 +200,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
nickname_label = str_tools.crop(self.get_nickname('UNKNOWN'), nickname_space, 0)
etc += ('%%-%is ' % nickname_space) % nickname_label
used_space += nickname_space + 2
- elif listing_type == conn_panel.Listing.FINGERPRINT:
+ elif listing_type == nyx.connection_panel.Listing.FINGERPRINT:
if width > used_space + 17:
# show nickname (column width: min 17 characters, consumes any remaining space)
@@ -258,7 +259,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
src, dst, etc = '', '', ''
- if listing_type == conn_panel.Listing.IP_ADDRESS:
+ if listing_type == nyx.connection_panel.Listing.IP_ADDRESS:
my_external_address = controller.get_info('address', self.connection.local_address)
address_differ = my_external_address != self.connection.local_address
@@ -318,7 +319,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
etc = self.get_etc_content(width - used_space, listing_type)
used_space += len(etc)
- elif listing_type == conn_panel.Listing.FINGERPRINT:
+ elif listing_type == nyx.connection_panel.Listing.FINGERPRINT:
src = 'localhost'
if my_type == Category.CONTROL:
diff --git a/nyx/connections/conn_panel.py b/nyx/connections/conn_panel.py
deleted file mode 100644
index f2d613e..0000000
--- a/nyx/connections/conn_panel.py
+++ /dev/null
@@ -1,583 +0,0 @@
-"""
-Listing of the currently established connections tor has made.
-"""
-
-import re
-import time
-import collections
-import curses
-import itertools
-import threading
-
-import nyx.popups
-import nyx.util.tracker
-
-from nyx.connections import descriptor_popup, entries
-from nyx.util import panel, tor_controller, ui_tools
-
-from stem.control import State
-from stem.util import conf, connection, enum
-
-# height of the detail panel content, not counting top and bottom border
-
-DETAILS_HEIGHT = 7
-
-# listing types
-
-Listing = enum.Enum(('IP_ADDRESS', 'IP Address'), 'FINGERPRINT', 'NICKNAME')
-
-EXIT_USAGE_WIDTH = 15
-UPDATE_RATE = 5 # rate in seconds at which we refresh
-
-# Connection Categories:
-# Inbound Relay connection, coming to us.
-# Outbound Relay connection, leaving us.
-# Exit Outbound relay connection leaving the Tor network.
-# Hidden Connections to a hidden service we're providing.
-# Socks Socks connections for applications using Tor.
-# Circuit Circuits our tor client has created.
-# Directory Fetching tor consensus information.
-# Control Tor controller (nyx, vidalia, etc).
-
-Category = enum.Enum('INBOUND', 'OUTBOUND', 'EXIT', 'HIDDEN', 'SOCKS', 'CIRCUIT', 'DIRECTORY', 'CONTROL')
-
-CATEGORY_COLOR = {
- Category.INBOUND: 'green',
- Category.OUTBOUND: 'blue',
- Category.EXIT: 'red',
- Category.HIDDEN: 'magenta',
- Category.SOCKS: 'yellow',
- Category.CIRCUIT: 'cyan',
- Category.DIRECTORY: 'magenta',
- Category.CONTROL: 'red',
-}
-
-SortAttr = enum.Enum('CATEGORY', 'UPTIME', 'LISTING', 'IP_ADDRESS', 'PORT', 'FINGERPRINT', 'NICKNAME', 'COUNTRY')
-
-SORT_COLORS = {
- SortAttr.CATEGORY: 'red',
- SortAttr.UPTIME: 'yellow',
- SortAttr.LISTING: 'green',
- SortAttr.IP_ADDRESS: 'blue',
- SortAttr.PORT: 'blue',
- SortAttr.FINGERPRINT: 'cyan',
- SortAttr.NICKNAME: 'cyan',
- SortAttr.COUNTRY: 'blue',
-}
-
-
-def conf_handler(key, value):
- if key == 'features.connection.listing_type':
- return conf.parse_enum(key, value, Listing)
- elif key == 'features.connection.order':
- return conf.parse_enum_csv(key, value[0], SortAttr, 3)
-
-
-CONFIG = conf.config_dict('nyx', {
- 'features.connection.resolveApps': True,
- 'features.connection.listing_type': Listing.IP_ADDRESS,
- 'features.connection.order': [
- SortAttr.CATEGORY,
- SortAttr.LISTING,
- SortAttr.UPTIME],
- 'features.connection.showIps': True,
-}, conf_handler)
-
-
-class ConnectionPanel(panel.Panel, threading.Thread):
- """
- Listing of connections tor is making, with information correlated against
- the current consensus and other data sources.
- """
-
- def __init__(self, stdscr):
- panel.Panel.__init__(self, stdscr, 'connections', 0)
- threading.Thread.__init__(self)
- self.setDaemon(True)
-
- # defaults our listing selection to fingerprints if ip address
- # displaying is disabled
- #
- # TODO: This is a little sucky in that it won't work if showIps changes
- # while we're running (... but nyx doesn't allow for that atm)
-
- if not CONFIG['features.connection.showIps'] and CONFIG['features.connection.listing_type'] == 0:
- nyx_config = conf.get_config('nyx')
- nyx_config.set('features.connection.listing_type', Listing.keys()[Listing.index_of(Listing.FINGERPRINT)])
-
- self._scroller = ui_tools.Scroller(True)
- self._entries = [] # last fetched display entries
- self._entry_lines = [] # individual lines rendered from the entries listing
- self._show_details = False # presents the details panel if true
-
- self._last_update = -1 # time the content was last revised
- self._is_tor_running = True # indicates if tor is currently running or not
- self._halt_time = None # time when tor was stopped
- self._vals_lock = threading.RLock()
-
- self._pause_condition = threading.Condition()
- self._halt = False # terminates thread if true
-
- # Tracks exiting port and client country statistics
-
- self._client_locale_usage = {}
- self._exit_port_usage = {}
-
- # If we're a bridge and been running over a day then prepopulates with the
- # last day's clients.
-
- controller = tor_controller()
- bridge_clients = controller.get_info('status/clients-seen', None)
-
- if bridge_clients:
- # Response has a couple arguments...
- # TimeStarted="2011-08-17 15:50:49" CountrySummary=us=16,de=8,uk=8
-
- country_summary = None
-
- for line in bridge_clients.split():
- if line.startswith('CountrySummary='):
- country_summary = line[15:]
- break
-
- if country_summary:
- for entry in country_summary.split(','):
- if re.match('^..=[0-9]+$', entry):
- locale, count = entry.split('=', 1)
- self._client_locale_usage[locale] = int(count)
-
- # Last sampling received from the ConnectionResolver, used to detect when
- # it changes.
-
- self._last_resource_fetch = -1
-
- # mark the initially exitsing connection uptimes as being estimates
-
- from nyx.connections import conn_entry
-
- for entry in self._entries:
- if isinstance(entry, conn_entry.ConnectionEntry):
- entry.get_lines()[0].is_initial_connection = True
-
- # listens for when tor stops so we know to stop reflecting changes
-
- controller.add_status_listener(self.tor_state_listener)
-
- def tor_state_listener(self, controller, event_type, _):
- """
- Freezes the connection contents when Tor stops.
- """
-
- self._is_tor_running = event_type in (State.INIT, State.RESET)
- self._halt_time = None if self._is_tor_running else time.time()
- self.redraw(True)
-
- def get_pause_time(self):
- """
- Provides the time Tor stopped if it isn't running. Otherwise this is the
- time we were last paused.
- """
-
- return self._halt_time if self._halt_time else panel.Panel.get_pause_time(self)
-
- def set_sort_order(self, ordering = None):
- """
- Sets the connection attributes we're sorting by and resorts the contents.
-
- Arguments:
- ordering - new ordering, if undefined then this resorts with the last
- set ordering
- """
-
- with self._vals_lock:
- if ordering:
- nyx_config = conf.get_config('nyx')
-
- ordering_keys = [SortAttr.keys()[SortAttr.index_of(v)] for v in ordering]
- nyx_config.set('features.connection.order', ', '.join(ordering_keys))
-
- def sort_value(entry, attr):
- if attr == SortAttr.LISTING:
- if self.get_listing_type() == Listing.IP_ADDRESS:
- attr = SortAttr.IP_ADDRESS
- elif self.get_listing_type() == Listing.FINGERPRINT:
- attr = SortAttr.FINGERPRINT
- elif self.get_listing_type() == Listing.NICKNAME:
- attr = SortAttr.NICKNAME
-
- connection_line = entry.get_lines()[0]
-
- if attr == SortAttr.IP_ADDRESS:
- if entry.is_private():
- return 255 ** 4 # orders at the end
-
- ip_value = 0
-
- for octet in connection_line.connection.remote_address.split('.'):
- ip_value = ip_value * 255 + int(octet)
-
- return ip_value * 65536 + connection_line.connection.remote_port
- elif attr == SortAttr.PORT:
- return connection_line.connection.remote_port
- elif attr == SortAttr.FINGERPRINT:
- return connection_line.get_fingerprint('UNKNOWN')
- elif attr == SortAttr.NICKNAME:
- return connection_line.get_nickname('z' * 20)
- elif attr == SortAttr.CATEGORY:
- return Category.index_of(entry.get_type())
- elif attr == SortAttr.UPTIME:
- return connection_line.connection.start_time
- elif attr == SortAttr.COUNTRY:
- return '' if entry.is_private() else connection_line.get_locale('')
- else:
- return ''
-
- self._entries.sort(key = lambda i: [sort_value(i, attr) for attr in CONFIG['features.connection.order']])
- self._entry_lines = list(itertools.chain.from_iterable([entry.get_lines() for entry in self._entries]))
-
- def get_listing_type(self):
- """
- Provides the priority content we list connections by.
- """
-
- return CONFIG['features.connection.listing_type']
-
- def set_listing_type(self, listing_type):
- """
- Sets the priority information presented by the panel.
-
- Arguments:
- listing_type - Listing instance for the primary information to be shown
- """
-
- if self.get_listing_type() == listing_type:
- return
-
- with self._vals_lock:
- nyx_config = conf.get_config('nyx')
- nyx_config.set('features.connection.listing_type', Listing.keys()[Listing.index_of(listing_type)])
-
- # if we're sorting by the listing then we need to resort
-
- if SortAttr.LISTING in CONFIG['features.connection.order']:
- self.set_sort_order()
-
- def show_sort_dialog(self):
- """
- Provides the sort dialog for our connections.
- """
-
- # set ordering for connection options
-
- title_label = 'Connection Ordering:'
- options = list(SortAttr)
- old_selection = CONFIG['features.connection.order']
- option_colors = dict([(attr, SORT_COLORS[attr]) for attr in options])
- results = nyx.popups.show_sort_dialog(title_label, options, old_selection, option_colors)
-
- if results:
- self.set_sort_order(results)
-
- def handle_key(self, key):
- with self._vals_lock:
- user_traffic_allowed = tor_controller().is_user_traffic_allowed()
-
- if key.is_scroll():
- page_height = self.get_preferred_size()[0] - 1
-
- if self._show_details:
- page_height -= (DETAILS_HEIGHT + 1)
-
- is_changed = self._scroller.handle_key(key, self._entry_lines, page_height)
-
- if is_changed:
- self.redraw(True)
- elif key.is_selection():
- self._show_details = not self._show_details
- self.redraw(True)
- elif key.match('s'):
- self.show_sort_dialog()
- elif key.match('u'):
- # provides a menu to pick the connection resolver
-
- title = 'Resolver Util:'
- options = ['auto'] + list(connection.Resolver)
- conn_resolver = nyx.util.tracker.get_connection_tracker()
-
- current_overwrite = conn_resolver.get_custom_resolver()
-
- if current_overwrite is None:
- old_selection = 0
- else:
- old_selection = options.index(current_overwrite)
-
- selection = nyx.popups.show_menu(title, options, old_selection)
-
- # applies new setting
-
- if selection != -1:
- selected_option = options[selection] if selection != 0 else None
- conn_resolver.set_custom_resolver(selected_option)
- elif key.match('l'):
- # provides a menu to pick the primary information we list connections by
-
- title = 'List By:'
- options = list(Listing)
-
- old_selection = options.index(self.get_listing_type())
- selection = nyx.popups.show_menu(title, options, old_selection)
-
- # applies new setting
-
- if selection != -1:
- self.set_listing_type(options[selection])
- elif key.match('d'):
- self.set_title_visible(False)
- self.redraw(True)
-
- while True:
- selection = self.get_selection()
-
- if not selection:
- break
-
- color = CATEGORY_COLOR[selection.get_type()]
- fingerprint = selection.get_fingerprint()
- is_close_key = lambda key: key.is_selection() or key.match('d') or key.match('left') or key.match('right')
- key = descriptor_popup.show_descriptor_popup(fingerprint, color, self.max_x, is_close_key)
-
- if not key or key.is_selection() or key.match('d'):
- break # closes popup
- elif key.match('left'):
- self.handle_key(panel.KeyInput(curses.KEY_UP))
- elif key.match('right'):
- self.handle_key(panel.KeyInput(curses.KEY_DOWN))
-
- self.set_title_visible(True)
- self.redraw(True)
- elif key.match('c') and user_traffic_allowed.inbound:
- nyx.popups.show_count_dialog('Client Locales', self._client_locale_usage)
- elif key.match('e') and user_traffic_allowed.outbound:
- counts = {}
- key_width = max(map(len, self._exit_port_usage.keys()))
-
- for k, v in self._exit_port_usage.items():
- usage = connection.port_usage(k)
-
- if usage:
- k = k.ljust(key_width + 3) + usage.ljust(EXIT_USAGE_WIDTH)
-
- counts[k] = v
-
- nyx.popups.show_count_dialog('Exiting Port Usage', counts)
- else:
- return False
-
- return True
-
- def run(self):
- """
- Keeps connections listing updated, checking for new entries at a set rate.
- """
-
- last_ran = -1
-
- while not self._halt:
- if self.is_paused() or not self._is_tor_running or (time.time() - last_ran) < UPDATE_RATE:
- with self._pause_condition:
- if not self._halt:
- self._pause_condition.wait(0.2)
-
- continue # done waiting, try again
-
- self._update()
- self.redraw(True)
-
- # If this is our first run then fill in our fingerprint tracker. This
- # requires fetching all the router status entries which takes a few
- # seconds, so best done when we're finished with the rest of the first
- # iteration to hide the lag.
-
- if last_ran == -1:
- nyx.util.tracker.get_consensus_tracker().update(tor_controller().get_network_statuses([]))
-
- last_ran = time.time()
-
- def get_help(self):
- resolver_util = nyx.util.tracker.get_connection_tracker().get_custom_resolver()
- user_traffic_allowed = tor_controller().is_user_traffic_allowed()
-
- options = [
- ('up arrow', 'scroll up a line', None),
- ('down arrow', 'scroll down a line', None),
- ('page up', 'scroll up a page', None),
- ('page down', 'scroll down a page', None),
- ('enter', 'show connection details', None),
- ('d', 'raw consensus descriptor', None),
- ('l', 'listed identity', self.get_listing_type().lower()),
- ('s', 'sort ordering', None),
- ('u', 'resolving utility', 'auto' if resolver_util is None else resolver_util),
- ]
-
- if user_traffic_allowed.inbound:
- options.append(('c', 'client locale usage summary', None))
-
- if user_traffic_allowed.outbound:
- options.append(('e', 'exit port usage summary', None))
-
- return options
-
- def get_selection(self):
- """
- Provides the currently selected connection entry.
- """
-
- return self._scroller.get_cursor_selection(self._entry_lines)
-
- def draw(self, width, height):
- with self._vals_lock:
- # if we don't have any contents then refuse to show details
-
- if not self._entries:
- self._show_details = False
-
- # extra line when showing the detail panel is for the bottom border
-
- detail_panel_offset = DETAILS_HEIGHT + 1 if self._show_details else 0
- is_scrollbar_visible = len(self._entry_lines) > height - detail_panel_offset - 1
-
- scroll_location = self._scroller.get_scroll_location(self._entry_lines, height - detail_panel_offset - 1)
- cursor_selection = self.get_selection()
-
- # draws the detail panel if currently displaying it
-
- if self._show_details and cursor_selection:
- # This is a solid border unless the scrollbar is visible, in which case a
- # 'T' pipe connects the border to the bar.
-
- ui_tools.draw_box(self, 0, 0, width, DETAILS_HEIGHT + 2)
-
- if is_scrollbar_visible:
- self.addch(DETAILS_HEIGHT + 1, 1, curses.ACS_TTEE)
-
- draw_entries = cursor_selection.get_details(width)
-
- for i in range(min(len(draw_entries), DETAILS_HEIGHT)):
- self.addstr(1 + i, 2, draw_entries[i][0], *draw_entries[i][1])
-
- # title label with connection counts
-
- if self.is_title_visible():
- self._draw_title(self._entries)
-
- scroll_offset = 0
-
- if is_scrollbar_visible:
- scroll_offset = 2
- self.add_scroll_bar(scroll_location, scroll_location + height - detail_panel_offset - 1, len(self._entry_lines), 1 + detail_panel_offset)
-
- if self.is_paused() or not self._is_tor_running:
- current_time = self.get_pause_time()
- else:
- current_time = time.time()
-
- for line_number in range(scroll_location, len(self._entry_lines)):
- entry_line = self._entry_lines[line_number]
-
- # hilighting if this is the selected line
-
- extra_format = curses.A_STANDOUT if entry_line == cursor_selection else curses.A_NORMAL
-
- draw_line = line_number + detail_panel_offset + 1 - scroll_location
-
- prefix = entry_line.get_listing_prefix()
-
- for i in range(len(prefix)):
- self.addch(draw_line, scroll_offset + i, prefix[i])
-
- x_offset = scroll_offset + len(prefix)
- draw_entry = entry_line.get_listing_entry(width - scroll_offset - len(prefix), current_time, self.get_listing_type())
-
- for msg, attr in draw_entry:
- attr |= extra_format
- self.addstr(draw_line, x_offset, msg, attr)
- x_offset += len(msg)
-
- if draw_line >= height:
- break
-
- def _draw_title(self, entries):
- if self._show_details:
- title = 'Connection Details:'
- elif not entries:
- title = 'Connections:'
- else:
- counts = collections.Counter([entry.get_type() for entry in entries])
- count_labels = ['%i %s' % (counts[category], category.lower()) for category in Category if counts[category]]
- title = 'Connections (%s):' % ', '.join(count_labels)
-
- self.addstr(0, 0, title, curses.A_STANDOUT)
-
- def stop(self):
- """
- Halts further resolutions and terminates the thread.
- """
-
- with self._pause_condition:
- self._halt = True
- self._pause_condition.notifyAll()
-
- def _update(self):
- """
- Fetches the newest resolved connections.
- """
-
- conn_resolver = nyx.util.tracker.get_connection_tracker()
- current_resolution_count = conn_resolver.run_counter()
-
- if not conn_resolver.is_alive():
- return # if we're not fetching connections then this is a no-op
- elif current_resolution_count == self._last_resource_fetch:
- return # no new connections to process
-
- new_entries = [entries.Entry.from_connection(conn) for conn in conn_resolver.get_value()]
-
- for circ in tor_controller().get_circuits([]):
- # Skips established single-hop circuits (these are for directory
- # fetches, not client circuits)
-
- if not (circ.status == 'BUILT' and len(circ.path) == 1):
- new_entries.append(entries.Entry.from_circuit(circ))
-
- with self._vals_lock:
- # update stats for client and exit connections
-
- for entry in new_entries:
- entry_line = entry.get_lines()[0]
-
- if entry.is_private() and entry.get_type() == Category.INBOUND:
- client_locale = entry_line.get_locale(None)
-
- if client_locale:
- self._client_locale_usage[client_locale] = self._client_locale_usage.get(client_locale, 0) + 1
- elif entry.get_type() == Category.EXIT:
- exit_port = entry_line.connection.remote_port
- self._exit_port_usage[exit_port] = self._exit_port_usage.get(exit_port, 0) + 1
-
- self._entries, self._entry_lines = new_entries, list(itertools.chain.from_iterable([entry.get_lines() for entry in new_entries]))
-
- self.set_sort_order()
- self._last_resource_fetch = current_resolution_count
-
- if CONFIG['features.connection.resolveApps']:
- local_ports, remote_ports = [], []
-
- for entry in new_entries:
- line = entry.get_lines()[0]
-
- if entry.get_type() in (Category.SOCKS, Category.CONTROL):
- local_ports.append(line.connection.remote_port)
- elif entry.get_type() == Category.HIDDEN:
- remote_ports.append(line.connection.local_port)
-
- nyx.util.tracker.get_port_usage_tracker().query(local_ports, remote_ports)
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index f4cf17e..a372576 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -74,7 +74,7 @@ class ConnectionEntry(Entry):
@lru_cache()
def get_type(self):
- from nyx.connections.conn_panel import Category
+ from nyx.connection_panel import Category
controller = tor_controller()
if self._connection.local_port in controller.get_ports(Listener.OR, []):
@@ -111,7 +111,7 @@ class ConnectionEntry(Entry):
@lru_cache()
def is_private(self):
- from nyx.connections.conn_panel import Category
+ from nyx.connection_panel import Category
if not CONFIG['features.connection.showIps']:
return True
@@ -140,7 +140,7 @@ class CircuitEntry(Entry):
return [CircHeaderLine(self, self._circuit)] + [CircLine(self, self._circuit, fp) for fp, _ in self._circuit.path]
def get_type(self):
- from nyx.connections.conn_panel import Category
+ from nyx.connection_panel import Category
return Category.CIRCUIT
def is_private(self):
diff --git a/nyx/controller.py b/nyx/controller.py
index 35b1e89..d0fb7d0 100644
--- a/nyx/controller.py
+++ b/nyx/controller.py
@@ -16,7 +16,7 @@ import nyx.log_panel
import nyx.config_panel
import nyx.torrc_panel
import nyx.graph_panel
-import nyx.connections.conn_panel
+import nyx.connection_panel
import nyx.util.tracker
import stem
@@ -113,7 +113,7 @@ def init_controller(stdscr, start_time):
# second page: connections
if CONFIG['features.panels.show.connection']:
- page_panels.append([nyx.connections.conn_panel.ConnectionPanel(stdscr)])
+ page_panels.append([nyx.connection_panel.ConnectionPanel(stdscr)])
# The DisableDebuggerAttachment will prevent our connection panel from really
# functioning. It'll have circuits, but little else. If this is the case then
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits