tor-commits
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
September 2015
- 15 participants
- 1411 discussions
commit 41c89b6f53e0146a2512fdef0a043041b63778ae
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Aug 29 14:36:06 2015 -0700
Revise Entry docs
Tidying up the docs a tad and noting parameter/return values.
---
nyx/connections/entries.py | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index 579dd5c..8d1a0d4 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -72,8 +72,9 @@ class ConnectionPanelEntry(object):
def get_type(self):
"""
- Provides our best guess at the current type of the connection. This
- depends on consensus results, our current client circuits, etc.
+ Provides our best guess at the type of connection this is.
+
+ :returns: **Category** for the connection's type
"""
return self._connection_type
@@ -81,12 +82,11 @@ class ConnectionPanelEntry(object):
@lru_cache()
def is_private(self):
"""
- Returns true if the endpoint is private, possibly belonging to a client
- connection or exit traffic.
-
- This is used to scrub private information from the interface. Relaying
+ Checks if information about this endpoint should be scrubbed. Relaying
etiquette (and wiretapping laws) say these are bad things to look at so
DON'T CHANGE THIS UNLESS YOU HAVE A DAMN GOOD REASON!
+
+ :returns: **bool** indicating if connection information is sensive or not
"""
import nyx.connections.conn_entry
@@ -112,7 +112,9 @@ class ConnectionPanelEntry(object):
def get_lines(self):
"""
- Provides the individual lines in the connection listing.
+ Provides individual lines of connection information.
+
+ :returns: **list** of **ConnectionLine** concerning this entry
"""
return self.lines
@@ -120,7 +122,11 @@ class ConnectionPanelEntry(object):
@lru_cache()
def get_sort_value(self, attr):
"""
- Provides the value of a single attribute used for sorting purposes.
+ Value for sorting by a given attribute.
+
+ :param SortAtt attr: attribute to be sorted by
+
+ :returns: comparable object by the given attribute
"""
connection_line = self.lines[0]
1
0
commit 6417b682ea456c0e67d8a6bbc1504b9a805710d1
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Aug 30 10:39:10 2015 -0700
Rename ConnectionPanelEntry to Entry
Class is already scoped to the connection panel so we don't need to be so
verbose.
---
nyx/connections/conn_panel.py | 4 ++--
nyx/connections/entries.py | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/nyx/connections/conn_panel.py b/nyx/connections/conn_panel.py
index 6c17cad..b9c4980 100644
--- a/nyx/connections/conn_panel.py
+++ b/nyx/connections/conn_panel.py
@@ -477,14 +477,14 @@ class ConnectionPanel(panel.Panel, threading.Thread):
elif current_resolution_count == self._last_resource_fetch:
return # no new connections to process
- new_entries = [entries.ConnectionPanelEntry.from_connection(conn) for conn in conn_resolver.get_value()]
+ 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.ConnectionPanelEntry.from_circuit(circ))
+ new_entries.append(entries.Entry.from_circuit(circ))
with self._vals_lock:
# update stats for client and exit connections
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index 9c8d83e..00b0a0a 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -43,7 +43,7 @@ def to_unix_time(dt):
return (dt - datetime.datetime(1970, 1, 1)).total_seconds()
-class ConnectionPanelEntry(object):
+class Entry(object):
def __init__(self, connection_type, start_time):
self.lines = []
self._connection_type = connection_type
@@ -53,7 +53,7 @@ class ConnectionPanelEntry(object):
def from_connection(conn):
import nyx.connections.conn_entry
- entry = ConnectionPanelEntry(get_type(conn), conn.start_time)
+ entry = Entry(get_type(conn), conn.start_time)
entry.lines = [nyx.connections.conn_entry.ConnectionLine(entry, conn)]
return entry
@@ -62,7 +62,7 @@ class ConnectionPanelEntry(object):
import nyx.connections.circ_entry
import nyx.connections.conn_entry
- entry = ConnectionPanelEntry(nyx.connections.conn_entry.Category.CIRCUIT, to_unix_time(circ.created))
+ entry = Entry(nyx.connections.conn_entry.Category.CIRCUIT, to_unix_time(circ.created))
entry.lines = [nyx.connections.circ_entry.CircHeaderLine(entry, circ)]
for fingerprint, _ in circ.path:
1
0
commit 68e66330bb5b042be05261cd0c7e4aae70634cc5
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Aug 29 14:27:12 2015 -0700
Simplify sorting by address
Lots more verbose than this needs to be. Lets make it simple...
---
nyx/connections/entries.py | 33 +++++++--------------------------
1 file changed, 7 insertions(+), 26 deletions(-)
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index 70ca6ac..78945f0 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -28,15 +28,6 @@ SORT_COLORS = {
SortAttr.COUNTRY: 'blue',
}
-# maximum number of ports a system can have
-
-PORT_COUNT = 65536
-
-# sort value for scrubbed ip addresses
-
-SCRUBBED_IP_VAL = 255 ** 4
-ADDRESS_CACHE = {}
-
CONFIG = conf.config_dict('nyx', {
'features.connection.showIps': True,
})
@@ -46,19 +37,6 @@ def to_unix_time(dt):
return (dt - datetime.datetime(1970, 1, 1)).total_seconds()
-def address_to_int(address):
- if address not in ADDRESS_CACHE:
- ip_value = 0
-
- for comp in address.split('.'):
- ip_value *= 255
- ip_value += int(comp)
-
- ADDRESS_CACHE[address] = ip_value
-
- return ADDRESS_CACHE[address]
-
-
class ConnectionPanelEntry:
def __init__(self, connection_type, start_time):
self.lines = []
@@ -141,11 +119,14 @@ class ConnectionPanelEntry:
if attr == SortAttr.IP_ADDRESS:
if self.is_private():
- return SCRUBBED_IP_VAL # orders at the end
+ 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)
- sort_value = address_to_int(connection_line.connection.remote_address) * PORT_COUNT
- sort_value += connection_line.connection.remote_port
- return sort_value
+ return ip_value * 65536 + connection_line.connection.remote_port
elif attr == SortAttr.PORT:
return connection_line.connection.remote_port
elif attr == SortAttr.FINGERPRINT:
1
0
commit 06f14a56002e365cfab594f1bb065af4e92699b7
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Aug 29 14:29:28 2015 -0700
Cache Entry parameters
The Entry class is immutable so we can safely cache the hell out of everything.
No reason to do any work twice.
---
nyx/connections/entries.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index 78945f0..579dd5c 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -11,6 +11,12 @@ from nyx.util import tor_controller
from stem.control import Listener
from stem.util import conf, enum
+try:
+ # added in python 3.2
+ from functools import lru_cache
+except ImportError:
+ from stem.util.lru_cache import lru_cache
+
# attributes we can list entries by
ListingType = enum.Enum(('IP_ADDRESS', 'IP Address'), 'FINGERPRINT', 'NICKNAME')
@@ -37,7 +43,7 @@ def to_unix_time(dt):
return (dt - datetime.datetime(1970, 1, 1)).total_seconds()
-class ConnectionPanelEntry:
+class ConnectionPanelEntry(object):
def __init__(self, connection_type, start_time):
self.lines = []
self._connection_type = connection_type
@@ -72,6 +78,7 @@ class ConnectionPanelEntry:
return self._connection_type
+ @lru_cache()
def is_private(self):
"""
Returns true if the endpoint is private, possibly belonging to a client
@@ -110,6 +117,7 @@ class ConnectionPanelEntry:
return self.lines
+ @lru_cache()
def get_sort_value(self, attr):
"""
Provides the value of a single attribute used for sorting purposes.
1
0

22 Sep '15
commit 84596451d0064f5973207712089a4368689a27ea
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Aug 31 09:59:37 2015 -0700
Entry subclasses for connections and circuits
Reintroducing specialized subclasses. Now that it no longer performs sort
functionality this is a very clear fit.
---
nyx/connections/entries.py | 145 ++++++++++++++++++++++++--------------------
1 file changed, 79 insertions(+), 66 deletions(-)
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index 90de9d4..f4cf17e 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -23,30 +23,24 @@ CONFIG = conf.config_dict('nyx', {
class Entry(object):
- def __init__(self, connection_type):
- self._lines = []
- self._connection_type = connection_type
-
@staticmethod
- def from_connection(conn):
- import nyx.connections.conn_entry
-
- entry = Entry(get_type(conn))
- entry._lines = [nyx.connections.conn_entry.ConnectionLine(entry, conn)]
- return entry
+ @lru_cache()
+ def from_connection(connection):
+ return ConnectionEntry(connection)
@staticmethod
- def from_circuit(circ):
- import nyx.connections.circ_entry
- from nyx.connections.conn_panel import Category
+ @lru_cache()
+ def from_circuit(circuit):
+ return CircuitEntry(circuit)
- entry = Entry(Category.CIRCUIT)
- entry._lines = [nyx.connections.circ_entry.CircHeaderLine(entry, circ)]
+ def get_lines(self):
+ """
+ Provides individual lines of connection information.
- for fingerprint, _ in circ.path:
- entry._lines.append(nyx.connections.circ_entry.CircLine(entry, circ, fingerprint))
+ :returns: **list** of **ConnectionLine** concerning this entry
+ """
- return entry
+ raise NotImplementedError('should be implemented by subclasses')
def get_type(self):
"""
@@ -55,9 +49,8 @@ class Entry(object):
:returns: **Category** for the connection's type
"""
- return self._connection_type
+ raise NotImplementedError('should be implemented by subclasses')
- @lru_cache()
def is_private(self):
"""
Checks if information about this endpoint should be scrubbed. Relaying
@@ -67,34 +60,91 @@ class Entry(object):
:returns: **bool** indicating if connection information is sensive or not
"""
+ raise NotImplementedError('should be implemented by subclasses')
+
+
+class ConnectionEntry(Entry):
+ def __init__(self, connection):
+ self._connection = connection
+
+ @lru_cache()
+ def get_lines(self):
+ import nyx.connections.conn_entry
+ return [nyx.connections.conn_entry.ConnectionLine(self, self._connection)]
+
+ @lru_cache()
+ def get_type(self):
+ from nyx.connections.conn_panel import Category
+ controller = tor_controller()
+
+ if self._connection.local_port in controller.get_ports(Listener.OR, []):
+ return Category.INBOUND
+ elif self._connection.local_port in controller.get_ports(Listener.DIR, []):
+ return Category.INBOUND
+ elif self._connection.local_port in controller.get_ports(Listener.SOCKS, []):
+ return Category.SOCKS
+ elif self._connection.local_port in controller.get_ports(Listener.CONTROL, []):
+ return Category.CONTROL
+
+ for hs_config in controller.get_hidden_service_conf({}).values():
+ if self._connection.remote_port == hs_config['HiddenServicePort']:
+ return Category.HIDDEN
+
+ fingerprint = nyx.util.tracker.get_consensus_tracker().get_relay_fingerprint(self._connection.remote_address, self._connection.remote_port)
+
+ if fingerprint:
+ for circ in controller.get_circuits([]):
+ if circ.path[0][0] == fingerprint and circ.status == 'BUILT':
+ # Tor builds one-hop circuits to retrieve directory information.
+ # If longer this is likely a connection to a guard.
+
+ return Category.DIRECTORY if len(circ.path) == 1 else Category.CIRCUIT
+ else:
+ # not a known relay, might be an exit connection
+
+ exit_policy = controller.get_exit_policy(None)
+
+ if exit_policy and exit_policy.can_exit_to(self._connection.remote_address, self._connection.remote_port):
+ return Category.EXIT
+
+ return Category.OUTBOUND
+
+ @lru_cache()
+ def is_private(self):
from nyx.connections.conn_panel import Category
if not CONFIG['features.connection.showIps']:
return True
- connection = self._lines[0].connection
-
if self.get_type() == Category.INBOUND:
controller = tor_controller()
if controller.is_user_traffic_allowed().inbound:
- return len(nyx.util.tracker.get_consensus_tracker().get_all_relay_fingerprints(connection.remote_address)) == 0
+ return len(nyx.util.tracker.get_consensus_tracker().get_all_relay_fingerprints(self._connection.remote_address)) == 0
elif self.get_type() == Category.EXIT:
# DNS connections exiting us aren't private (since they're hitting our
# resolvers). Everything else is.
- return connection.remote_port != 53 or connection.protocol != 'udp'
+ return not (self._connection.remote_port == 53 and self._connection.protocol == 'udp')
return False # for everything else this isn't a concern
+
+class CircuitEntry(Entry):
+ def __init__(self, circuit):
+ self._circuit = circuit
+
+ @lru_cache()
def get_lines(self):
- """
- Provides individual lines of connection information.
+ from nyx.connections.circ_entry import CircHeaderLine, CircLine
+ return [CircHeaderLine(self, self._circuit)] + [CircLine(self, self._circuit, fp) for fp, _ in self._circuit.path]
- :returns: **list** of **ConnectionLine** concerning this entry
- """
+ def get_type(self):
+ from nyx.connections.conn_panel import Category
+ return Category.CIRCUIT
- return self._lines
+ def is_private(self):
+ return False
class ConnectionPanelLine:
@@ -159,40 +209,3 @@ class ConnectionPanelLine:
def _get_details(self, width):
# implementation of get_details
return []
-
-
-def get_type(connection):
- from nyx.connections.conn_panel import Category
- controller = tor_controller()
-
- if connection.local_port in controller.get_ports(Listener.OR, []):
- return Category.INBOUND
- elif connection.local_port in controller.get_ports(Listener.DIR, []):
- return Category.INBOUND
- elif connection.local_port in controller.get_ports(Listener.SOCKS, []):
- return Category.SOCKS
- elif connection.local_port in controller.get_ports(Listener.CONTROL, []):
- return Category.CONTROL
-
- for hs_config in controller.get_hidden_service_conf({}).values():
- if connection.remote_port == hs_config['HiddenServicePort']:
- return Category.HIDDEN
-
- fingerprint = nyx.util.tracker.get_consensus_tracker().get_relay_fingerprint(connection.remote_address, connection.remote_port)
-
- if fingerprint:
- for circ in controller.get_circuits([]):
- if circ.path[0][0] == fingerprint and circ.status == 'BUILT':
- # Tor builds one-hop circuits to retrieve directory information.
- # If longer this is likely a connection to a guard.
-
- return Category.DIRECTORY if len(circ.path) == 1 else Category.CIRCUIT
- else:
- # not a known relay, might be an exit connection
-
- exit_policy = controller.get_exit_policy(None)
-
- if exit_policy and exit_policy.can_exit_to(connection.remote_address, connection.remote_port):
- return Category.EXIT
-
- return Category.OUTBOUND
1
0

[nyx/master] Localize sort functionality in panel's sort function
by atagar@torproject.org 22 Sep '15
by atagar@torproject.org 22 Sep '15
22 Sep '15
commit 77e53454fb75f2477f3ad0473a241bfc770a432b
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Aug 30 12:14:56 2015 -0700
Localize sort functionality in panel's sort function
Merging the get_sort_value() into the sort function. Nice simplification.
---
nyx/connections/conn_panel.py | 39 ++++++++++++++++++++++++++++++++-------
nyx/connections/entries.py | 38 --------------------------------------
2 files changed, 32 insertions(+), 45 deletions(-)
diff --git a/nyx/connections/conn_panel.py b/nyx/connections/conn_panel.py
index b9c4980..f28c943 100644
--- a/nyx/connections/conn_panel.py
+++ b/nyx/connections/conn_panel.py
@@ -158,18 +158,43 @@ class ConnectionPanel(panel.Panel, threading.Thread):
ordering_keys = [entries.SortAttr.keys()[entries.SortAttr.index_of(v)] for v in ordering]
nyx_config.set('features.connection.order', ', '.join(ordering_keys))
- def sort_type(attr):
+ def sort_value(entry, attr):
if attr == entries.SortAttr.LISTING:
if self.get_listing_type() == entries.ListingType.IP_ADDRESS:
- return entries.SortAttr.IP_ADDRESS
+ attr = entries.SortAttr.IP_ADDRESS
elif self.get_listing_type() == entries.ListingType.FINGERPRINT:
- return entries.SortAttr.FINGERPRINT
+ attr = entries.SortAttr.FINGERPRINT
elif self.get_listing_type() == entries.ListingType.NICKNAME:
- return entries.SortAttr.NICKNAME
-
- return attr
+ attr = entries.SortAttr.NICKNAME
+
+ connection_line = entry.get_lines()[0]
+
+ if attr == entries.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 == entries.SortAttr.PORT:
+ return connection_line.connection.remote_port
+ elif attr == entries.SortAttr.FINGERPRINT:
+ return connection_line.get_fingerprint('UNKNOWN')
+ elif attr == entries.SortAttr.NICKNAME:
+ return connection_line.get_nickname('z' * 20)
+ elif attr == entries.SortAttr.CATEGORY:
+ return conn_entry.Category.index_of(entry.get_type())
+ elif attr == entries.SortAttr.UPTIME:
+ return connection_line.connection.start_time
+ elif attr == entries.SortAttr.COUNTRY:
+ return '' if entry.is_private() else connection_line.get_locale('')
+ else:
+ return ''
- self._entries.sort(key = lambda i: [i.get_sort_value(sort_type(attr)) for attr in CONFIG['features.connection.order']])
+ 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):
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index b30ae43..20d19a8 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -113,44 +113,6 @@ class Entry(object):
return self._lines
- @lru_cache()
- def get_sort_value(self, attr):
- """
- Value for sorting by a given attribute.
-
- :param SortAtt attr: attribute to be sorted by
-
- :returns: comparable object by the given attribute
- """
-
- connection_line = self._lines[0]
-
- if attr == SortAttr.IP_ADDRESS:
- if self.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:
- import nyx.connections.conn_entry
- return nyx.connections.conn_entry.Category.index_of(self.get_type())
- elif attr == SortAttr.UPTIME:
- return connection_line.connection.start_time
- elif attr == SortAttr.COUNTRY:
- return '' if self.is_private() else connection_line.get_locale('')
- else:
- return ''
-
class ConnectionPanelLine:
"""
1
0
commit 45558ac8118d78a04c5e560843d5bd54c91e04d7
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Aug 30 10:50:22 2015 -0700
Don't track start_time in the Entry
The entry has access to the connection so no reason for it to track the start
time separately. This also lets us move the to_unix_time() with the circuit
which is nice since that's the only spot where it's now needed.
---
nyx/connections/circ_entry.py | 19 +++++++++++--------
nyx/connections/entries.py | 33 ++++++++++++++-------------------
2 files changed, 25 insertions(+), 27 deletions(-)
diff --git a/nyx/connections/circ_entry.py b/nyx/connections/circ_entry.py
index b1d95c7..673777d 100644
--- a/nyx/connections/circ_entry.py
+++ b/nyx/connections/circ_entry.py
@@ -9,6 +9,7 @@ followed by an entry for each hop in the circuit. For instance:
"""
import curses
+import datetime
import nyx.util.tracker
import nyx.util.ui_tools
@@ -18,6 +19,10 @@ from nyx.connections import entries, conn_entry
from stem.util import str_tools
+def to_unix_time(dt):
+ return (dt - datetime.datetime(1970, 1, 1)).total_seconds()
+
+
class CircHeaderLine(conn_entry.ConnectionLine):
"""
Initial line of a client entry. This has the same basic format as connection
@@ -34,7 +39,7 @@ class CircHeaderLine(conn_entry.ConnectionLine):
self.is_built = False
self._remote_fingerprint = None
- conn_entry.ConnectionLine.__init__(self, entry, nyx.util.tracker.Connection(entries.to_unix_time(circ.created), False, '127.0.0.1', 0, exit_address, exit_port, 'tcp'), False, False)
+ conn_entry.ConnectionLine.__init__(self, entry, nyx.util.tracker.Connection(to_unix_time(circ.created), False, '127.0.0.1', 0, exit_address, exit_port, 'tcp'), False, False)
self.circuit = circ
def get_fingerprint(self, default = None):
@@ -77,16 +82,18 @@ class CircLine(conn_entry.ConnectionLine):
caching, etc).
"""
- def __init__(self, entry, circ, fingerprint, timestamp):
+ def __init__(self, entry, circ, fingerprint):
relay_ip, relay_port = nyx.util.tracker.get_consensus_tracker().get_relay_address(fingerprint, ('192.168.0.1', 0))
- conn_entry.ConnectionLine.__init__(self, entry, nyx.util.tracker.Connection(timestamp, False, '127.0.0.1', 0, relay_ip, relay_port, 'tcp'), False)
+ conn_entry.ConnectionLine.__init__(self, entry, nyx.util.tracker.Connection(to_unix_time(circ.created), False, '127.0.0.1', 0, relay_ip, relay_port, 'tcp'), False)
self._fingerprint = fingerprint
+ self._is_last = False
circ_path = [path_entry[0] for path_entry in circ.path]
circ_index = circ_path.index(fingerprint)
if circ_index == len(circ_path) - 1:
placement_type = 'Exit' if circ.status == 'BUILT' else 'Extending'
+ self._is_last = True
elif circ_index == 0:
placement_type = 'Guard'
else:
@@ -94,15 +101,11 @@ class CircLine(conn_entry.ConnectionLine):
self.placement_label = '%i / %s' % (circ_index + 1, placement_type)
- # determines the sort of left hand bracketing we use
-
- self.is_last = circ_index == len(circ_path) - 1
-
def get_fingerprint(self, default = None):
self._fingerprint
def get_listing_prefix(self):
- if self.is_last:
+ if self._is_last:
return (ord(' '), curses.ACS_LLCORNER, curses.ACS_HLINE, ord(' '))
else:
return (ord(' '), curses.ACS_VLINE, ord(' '), ord(' '))
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index 00b0a0a..fb49f98 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -4,8 +4,6 @@ entry itself (ie, Tor connection, client circuit, etc) and the lines it
consists of in the listing.
"""
-import datetime
-
from nyx.util import tor_controller
from stem.control import Listener
@@ -39,22 +37,17 @@ CONFIG = conf.config_dict('nyx', {
})
-def to_unix_time(dt):
- return (dt - datetime.datetime(1970, 1, 1)).total_seconds()
-
-
class Entry(object):
- def __init__(self, connection_type, start_time):
- self.lines = []
+ def __init__(self, connection_type):
+ self._lines = []
self._connection_type = connection_type
- self._start_time = start_time
@staticmethod
def from_connection(conn):
import nyx.connections.conn_entry
- entry = Entry(get_type(conn), conn.start_time)
- entry.lines = [nyx.connections.conn_entry.ConnectionLine(entry, conn)]
+ entry = Entry(get_type(conn))
+ entry._lines = [nyx.connections.conn_entry.ConnectionLine(entry, conn)]
return entry
@staticmethod
@@ -62,11 +55,11 @@ class Entry(object):
import nyx.connections.circ_entry
import nyx.connections.conn_entry
- entry = Entry(nyx.connections.conn_entry.Category.CIRCUIT, to_unix_time(circ.created))
- entry.lines = [nyx.connections.circ_entry.CircHeaderLine(entry, circ)]
+ entry = Entry(nyx.connections.conn_entry.Category.CIRCUIT)
+ entry._lines = [nyx.connections.circ_entry.CircHeaderLine(entry, circ)]
for fingerprint, _ in circ.path:
- entry.lines.append(nyx.connections.circ_entry.CircLine(entry, circ, fingerprint, to_unix_time(circ.created)))
+ entry._lines.append(nyx.connections.circ_entry.CircLine(entry, circ, fingerprint))
return entry
@@ -95,16 +88,18 @@ class Entry(object):
if not CONFIG['features.connection.showIps']:
return True
+ connection = self._lines[0].connection
+
if self.get_type() == nyx.connections.conn_entry.Category.INBOUND:
controller = tor_controller()
if controller.is_user_traffic_allowed().inbound:
- return len(nyx.util.tracker.get_consensus_tracker().get_all_relay_fingerprints(self.connection.remote_address)) == 0
+ return len(nyx.util.tracker.get_consensus_tracker().get_all_relay_fingerprints(connection.remote_address)) == 0
elif self.get_type() == nyx.connections.conn_entry.Category.EXIT:
# DNS connections exiting us aren't private (since they're hitting our
# resolvers). Everything else is.
- return self.connection.remote_port != 53 or self.connection.protocol != 'udp'
+ return connection.remote_port != 53 or connection.protocol != 'udp'
return False # for everything else this isn't a concern
@@ -115,7 +110,7 @@ class Entry(object):
:returns: **list** of **ConnectionLine** concerning this entry
"""
- return self.lines
+ return self._lines
@lru_cache()
def get_sort_value(self, attr):
@@ -127,7 +122,7 @@ class Entry(object):
:returns: comparable object by the given attribute
"""
- connection_line = self.lines[0]
+ connection_line = self._lines[0]
if attr == SortAttr.IP_ADDRESS:
if self.is_private():
@@ -149,7 +144,7 @@ class Entry(object):
import nyx.connections.conn_entry
return nyx.connections.conn_entry.Category.index_of(self.get_type())
elif attr == SortAttr.UPTIME:
- return self._start_time
+ return connection_line.connection.start_time
elif attr == SortAttr.COUNTRY:
return '' if self.is_private() else connection_line.get_locale('')
else:
1
0
commit 752c4640182b4898c8f0afd5e73d0810f20e5171
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Sep 1 09:10:11 2015 -0700
Drop ConnectionPanelLine parent class
I wrote ConnectionPanelLine long before I knew of @lru_cache, and really the
only functionality it provides is caching. Wrapping that into the child
class.
---
nyx/connections/circ_entry.py | 12 +++++-
nyx/connections/conn_entry.py | 89 +++++++++--------------------------------
2 files changed, 29 insertions(+), 72 deletions(-)
diff --git a/nyx/connections/circ_entry.py b/nyx/connections/circ_entry.py
index 7cf087f..84e5b2b 100644
--- a/nyx/connections/circ_entry.py
+++ b/nyx/connections/circ_entry.py
@@ -19,6 +19,12 @@ from nyx.connections import conn_entry
from stem.util import str_tools
+try:
+ # added in python 3.2
+ from functools import lru_cache
+except ImportError:
+ from stem.util.lru_cache import lru_cache
+
def to_unix_time(dt):
return (dt - datetime.datetime(1970, 1, 1)).total_seconds()
@@ -68,6 +74,7 @@ class CircHeaderLine(conn_entry.ConnectionLine):
return ''
+ @lru_cache()
def get_details(self, width):
if not self.is_built:
detail_format = (curses.A_BOLD, nyx.connection_panel.CATEGORY_COLOR[self._entry.get_type()])
@@ -125,9 +132,10 @@ class CircLine(conn_entry.ConnectionLine):
listing_type - primary attribute we're listing connections by
"""
- return conn_entry.ConnectionPanelLine.get_listing_entry(self, width, current_time, listing_type)
+ return self._get_listing_entry(width, listing_type)
- def _get_listing_entry(self, width, current_time, listing_type):
+ @lru_cache()
+ def _get_listing_entry(self, width, listing_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:
diff --git a/nyx/connections/conn_entry.py b/nyx/connections/conn_entry.py
index 366ecba..34e34d9 100644
--- a/nyx/connections/conn_entry.py
+++ b/nyx/connections/conn_entry.py
@@ -14,6 +14,12 @@ from nyx.connection_panel import Category
from stem.util import conf, connection, str_tools
+try:
+ # added in python 3.2
+ from functools import lru_cache
+except ImportError:
+ from stem.util.lru_cache import lru_cache
+
# static data for listing format
# <src> --> <dst> <etc><padding>
@@ -31,78 +37,12 @@ CONFIG = conf.config_dict('nyx', {
})
-class ConnectionPanelLine:
- """
- Individual line in the connection panel listing.
- """
-
- def __init__(self):
- # cache for displayed information
- self._listing_cache = None
- self._listing_cache_args = (None, None)
-
- self._details_cache = None
- self._details_cache_args = None
-
- self._descriptor_cache = None
- self._descriptor_cache_args = None
-
- def get_listing_prefix(self):
- """
- Provides a list of characters to be appended before the listing entry.
- """
-
- return ()
-
- def get_listing_entry(self, width, current_time, listing_type):
- """
- Provides a [(msg, attr)...] tuple list for contents to be displayed in the
- connection panel listing.
-
- Arguments:
- width - available space to display in
- current_time - unix timestamp for what the results should consider to be
- the current time (this may be ignored due to caching)
- """
-
- if self._listing_cache_args != (width, listing_type):
- self._listing_cache = self._get_listing_entry(width, current_time, listing_type)
- self._listing_cache_args = (width, listing_type)
-
- return self._listing_cache
-
- def _get_listing_entry(self, width, current_time, listing_type):
- # implementation of get_listing_entry
- return None
-
- def get_details(self, width):
- """
- Provides a list of [(msg, attr)...] tuple listings with detailed
- information for this connection.
-
- Arguments:
- width - available space to display in
- """
-
- if self._details_cache_args != width:
- self._details_cache = self._get_details(width)
- self._details_cache_args = width
-
- return self._details_cache
-
- def _get_details(self, width):
- # implementation of get_details
- return []
-
-
-class ConnectionLine(ConnectionPanelLine):
+class ConnectionLine(object):
"""
Display component of the ConnectionEntry.
"""
def __init__(self, entry, conn, include_port=True, include_expanded_addresses=True):
- ConnectionPanelLine.__init__(self)
-
self._entry = entry
self.connection = conn
@@ -112,6 +52,13 @@ class ConnectionLine(ConnectionPanelLine):
self.include_port = include_port
self.include_expanded_addresses = include_expanded_addresses
+ def get_listing_prefix(self):
+ """
+ Provides a list of characters to be appended before the listing entry.
+ """
+
+ return ()
+
def get_locale(self, default = None):
"""
Provides the two letter country code for the remote endpoint.
@@ -168,7 +115,7 @@ class ConnectionLine(ConnectionPanelLine):
# fetch our (most likely cached) display entry for the listing
- my_listing = ConnectionPanelLine.get_listing_entry(self, width, current_time, listing_type)
+ my_listing = self._get_listing_entry(width, listing_type)
# fill in the current uptime and return the results
@@ -182,7 +129,8 @@ class ConnectionLine(ConnectionPanelLine):
return my_listing
- def _get_listing_entry(self, width, current_time, listing_type):
+ @lru_cache()
+ def _get_listing_entry(self, width, listing_type):
entry_type = self._entry.get_type()
# Lines are split into the following components in reverse:
@@ -205,7 +153,8 @@ class ConnectionLine(ConnectionPanelLine):
return draw_entry
- def _get_details(self, width):
+ @lru_cache()
+ def get_details(self, width):
"""
Provides details on the connection, correlated against available consensus
data.
1
0

22 Sep '15
commit 7a9c8b78b80e504a3b83fb92588b1bb2d36e3f39
Author: Damian Johnson <atagar(a)torproject.org>
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
1
0
commit 7d397f9da1fc01775b4e012b34187349f75fc930
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Aug 30 11:09:16 2015 -0700
Drop get_type() from connection lines
The connection lines proxies through to the entry. External callers should (and
are) calling on the entry, and the lines can use their own entry reference.
---
nyx/connections/circ_entry.py | 4 ++--
nyx/connections/conn_entry.py | 19 ++++++++-----------
nyx/connections/entries.py | 4 ++--
3 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/nyx/connections/circ_entry.py b/nyx/connections/circ_entry.py
index 673777d..f396385 100644
--- a/nyx/connections/circ_entry.py
+++ b/nyx/connections/circ_entry.py
@@ -69,7 +69,7 @@ class CircHeaderLine(conn_entry.ConnectionLine):
def get_details(self, width):
if not self.is_built:
- detail_format = (curses.A_BOLD, conn_entry.CATEGORY_COLOR[self.get_type()])
+ detail_format = (curses.A_BOLD, conn_entry.CATEGORY_COLOR[self._entry.get_type()])
return [('Building Circuit...', detail_format)]
else:
return conn_entry.ConnectionLine.get_details(self, width)
@@ -127,7 +127,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_entry.CATEGORY_COLOR[self.get_type()])
+ line_format = nyx.util.ui_tools.get_color(conn_entry.CATEGORY_COLOR[self._entry.get_type()])
# The required widths are the sum of the following:
# initial space (1 character)
diff --git a/nyx/connections/conn_entry.py b/nyx/connections/conn_entry.py
index 13aaadf..40a626f 100644
--- a/nyx/connections/conn_entry.py
+++ b/nyx/connections/conn_entry.py
@@ -82,7 +82,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
Provides the fingerprint of this relay.
"""
- if self.get_type() in (Category.OUTBOUND, Category.CIRCUIT, Category.DIRECTORY, Category.EXIT):
+ if self._entry.get_type() in (Category.OUTBOUND, Category.CIRCUIT, Category.DIRECTORY, Category.EXIT):
my_fingerprint = nyx.util.tracker.get_consensus_tracker().get_relay_fingerprint(self.connection.remote_address, self.connection.remote_port)
return my_fingerprint if my_fingerprint else default
else:
@@ -141,7 +141,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
return my_listing
def _get_listing_entry(self, width, current_time, listing_type):
- entry_type = self.get_type()
+ entry_type = self._entry.get_type()
# Lines are split into the following components in reverse:
# init gap - " "
@@ -172,12 +172,9 @@ class ConnectionLine(entries.ConnectionPanelLine):
width - available space to display in
"""
- detail_format = (curses.A_BOLD, CATEGORY_COLOR[self.get_type()])
+ detail_format = (curses.A_BOLD, CATEGORY_COLOR[self._entry.get_type()])
return [(line, detail_format) for line in self._get_detail_content(width)]
- def get_type(self):
- return self._entry.get_type()
-
def get_etc_content(self, width, listing_type):
"""
Provides the optional content for the connection.
@@ -189,8 +186,8 @@ class ConnectionLine(entries.ConnectionPanelLine):
# for applications show the command/pid
- if self.get_type() in (Category.SOCKS, Category.HIDDEN, Category.CONTROL):
- port = self.connection.local_port if self.get_type() == Category.HIDDEN else self.connection.remote_port
+ if self._entry.get_type() in (Category.SOCKS, Category.HIDDEN, Category.CONTROL):
+ port = self.connection.local_port if self._entry.get_type() == Category.HIDDEN else self.connection.remote_port
try:
process = nyx.util.tracker.get_port_usage_tracker().fetch(port)
@@ -270,7 +267,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
"""
controller = tor_controller()
- my_type = self.get_type()
+ my_type = self._entry.get_type()
destination_address = self.get_destination_label(26, include_locale = True)
# The required widths are the sum of the following:
@@ -541,7 +538,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
# the port and port derived data can be hidden by config or without include_port
- include_port = self.include_port and (CONFIG['features.connection.showExitPort'] or self.get_type() != Category.EXIT)
+ include_port = self.include_port and (CONFIG['features.connection.showExitPort'] or self._entry.get_type() != Category.EXIT)
# destination of the connection
@@ -555,7 +552,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
if len(destination_address) + 5 <= max_length:
space_available = max_length - len(destination_address) - 3
- if self.get_type() == Category.EXIT and include_port:
+ if self._entry.get_type() == Category.EXIT and include_port:
purpose = connection.port_usage(self.connection.remote_port)
if purpose:
diff --git a/nyx/connections/entries.py b/nyx/connections/entries.py
index fb49f98..b30ae43 100644
--- a/nyx/connections/entries.py
+++ b/nyx/connections/entries.py
@@ -4,6 +4,8 @@ entry itself (ie, Tor connection, client circuit, etc) and the lines it
consists of in the listing.
"""
+import nyx.util.tracker
+
from nyx.util import tor_controller
from stem.control import Listener
@@ -83,7 +85,6 @@ class Entry(object):
"""
import nyx.connections.conn_entry
- import nyx.util.tracker
if not CONFIG['features.connection.showIps']:
return True
@@ -216,7 +217,6 @@ class ConnectionPanelLine:
def get_type(connection):
- import nyx.util.tracker
from nyx.connections.conn_entry import Category
controller = tor_controller()
1
0