[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Support for ORCONN events
commit a1a5784d480421af4394b3e012d6f4bbb8ee6c8b
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Sun Nov 18 14:44:05 2012 -0800
Support for ORCONN events
Implementation and tests for ORCONN events. These have several holes in its
documentation (https://trac.torproject.org/7513) so I'm not really sure what
these events actually are. Reguardless, got some samples by connecting to TBB
and issuing a NEWNYM.
AUTHENTICATE
250 OK
SETEVENTS ORCONN
250 OK
650 ORCONN $1D024F41EDBF3F061E1341D516543090D8A44B42=AccessNowKromyon21 CONNECTED
650 ORCONN $7ED90E2833EE38A75795BA9237B0A4560E51E1A0=GreenDragon CONNECTED
650 ORCONN $A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama CLOSED REASON=DONE
---
stem/__init__.py | 82 ++++++++++++++++++++++++++++++++++++----
stem/control.py | 70 ++++++++++++++++++++--------------
stem/response/events.py | 86 ++++++++++++++++++++++++++++++++++++++---
test/unit/response/events.py | 32 +++++++++++++++
4 files changed, 227 insertions(+), 43 deletions(-)
diff --git a/stem/__init__.py b/stem/__init__.py
index 7b3f6ee..f0fca41 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -62,7 +62,7 @@ Library for working with the tor process.
.. data:: CircClosureReason (enum)
Reason that a circuit is being closed or failed to be established. Tor may
- provide purposes not in this enum.
+ provide reasons not in this enum.
========================= ===========
CircClosureReason Description
@@ -138,7 +138,7 @@ Library for working with the tor process.
.. data:: StreamClosureReason (enum)
Reason that a stream is being closed or failed to be established. Tor may
- provide purposes not in this enum.
+ provide reasons not in this enum.
===================== ===========
StreamClosureReason Description
@@ -163,7 +163,8 @@ Library for working with the tor process.
.. data:: StreamSource (enum)
- Cause of a stream being remapped to another address.
+ Cause of a stream being remapped to another address. Tor may provide sources
+ not in this enum.
============= ===========
StreamSource Description
@@ -177,15 +178,58 @@ Library for working with the tor process.
Purpsoe of the stream. This is only provided with new streams and tor may
provide purposes not in this enum.
+ Enum descriptions are pending...
+ https://trac.torproject.org/7508
+
================= ===========
StreamPurpose Description
================= ===========
- **DIR_FETCH** unknown (https://trac.torproject.org/7508)
- **UPLOAD_DESC** unknown (https://trac.torproject.org/7508)
- **DNS_REQUEST** unknown (https://trac.torproject.org/7508)
- **USER** unknown (https://trac.torproject.org/7508)
- **DIRPORT_TEST** unknown (https://trac.torproject.org/7508)
+ **DIR_FETCH** unknown
+ **UPLOAD_DESC** unknown
+ **DNS_REQUEST** unknown
+ **USER** unknown
+ **DIRPORT_TEST** unknown
================= ===========
+
+.. data:: ORStatus (enum)
+
+ State that an OR connection can have. Tor may provide states not in this
+ enum.
+
+ Enum descriptions are pending...
+ https://trac.torproject.org/7513
+
+ =============== ===========
+ ORStatus Description
+ =============== ===========
+ **NEW** unknown
+ **LAUNCHED** unknown
+ **CONNECTED** unknown
+ **FAILED** unknown
+ **CLOSED** unknown
+ =============== ===========
+
+.. data:: ORClosureReason (enum)
+
+ Reason that an OR connection is being closed or failed to be established. Tor
+ may provide reasons not in this enum.
+
+ Enum descriptions are pending...
+ https://trac.torproject.org/7513
+
+ =================== ===========
+ ORClosureReason Description
+ =================== ===========
+ **MISC** unknown
+ **DONE** unknown
+ **CONNECTREFUSED** unknown
+ **IDENTITY** unknown
+ **CONNECTRESET** unknown
+ **TIMEOUT** unknown
+ **NOROUTE** unknown
+ **IOERROR** unknown
+ **RESOURCELIMIT** unknown
+ =================== ===========
"""
__version__ = '0.0.1'
@@ -222,6 +266,8 @@ __all__ = [
"StreamClosureReason",
"StreamSource",
"StreamPurpose",
+ "ORStatus",
+ "ORClosureReason",
]
import stem.util.enum
@@ -377,3 +423,23 @@ StreamPurpose = stem.util.enum.UppercaseEnum(
"DIRPORT_TEST",
)
+ORStatus = stem.util.enum.UppercaseEnum(
+ "NEW",
+ "LAUNCHED",
+ "CONNECTED",
+ "FAILED",
+ "CLOSED",
+)
+
+ORClosureReason = stem.util.enum.UppercaseEnum(
+ "MISC",
+ "DONE",
+ "CONNECTREFUSED",
+ "IDENTITY",
+ "CONNECTRESET",
+ "TIMEOUT",
+ "NOROUTE",
+ "IOERROR",
+ "RESOURCELIMIT",
+)
+
diff --git a/stem/control.py b/stem/control.py
index d7dec9a..4a91129 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -1500,36 +1500,50 @@ def _parse_circ_path(path):
:raises: :class:`stem.ProtocolError` if the path is malformed
"""
- if not path: return []
-
- circ_path = []
-
- for path_component in path.split(','):
- if '=' in path_component:
- # common case
- fingerprint, nickname = path_component.split('=')
- elif '~' in path_component:
- # this is allowed for by the spec, but I've never seen it used
- fingerprint, nickname = path_component.split('~')
- elif path_component[0] == '$':
- # old style, fingerprint only
- fingerprint, nickname = path_component, None
- else:
- # old style, nickname only
- fingerprint, nickname = None, path_component
-
- if fingerprint != None:
- if not stem.util.tor_tools.is_valid_fingerprint(fingerprint, True):
- raise stem.ProtocolError("Fingerprint in the circuit path is malformed (%s): %s" % (fingerprint, path))
-
- fingerprint = fingerprint[1:] # strip off the leading '$'
-
- if nickname != None and not stem.util.tor_tools.is_valid_nickname(nickname):
- raise stem.ProtocolError("Nickname in the circuit path is malformed (%s): %s" % (fingerprint, path))
+ if path:
+ try:
+ return [_parse_circ_entry(entry) for entry in path.split(',')]
+ except stem.ProtocolError, exc:
+ # include the path with the exception
+ raise stem.ProtocolError("%s: %s" % (exc, path))
+ else:
+ return []
+
+def _parse_circ_entry(entry):
+ """
+ Parses a single relay's 'LongName' or 'ServerID'. See the
+ :func:`~_stem.control._parse_circ_path` function for more information.
+
+ :param str entry: relay information to be parsed
+
+ :returns: **(fingerprint, nickname)** tuple
+
+ :raises: :class:`stem.ProtocolError` if the entry is malformed
+ """
+
+ if '=' in entry:
+ # common case
+ fingerprint, nickname = entry.split('=')
+ elif '~' in entry:
+ # this is allowed for by the spec, but I've never seen it used
+ fingerprint, nickname = entry.split('~')
+ elif entry[0] == '$':
+ # old style, fingerprint only
+ fingerprint, nickname = entry, None
+ else:
+ # old style, nickname only
+ fingerprint, nickname = None, entry
+
+ if fingerprint != None:
+ if not stem.util.tor_tools.is_valid_fingerprint(fingerprint, True):
+ raise stem.ProtocolError("Fingerprint in the circuit path is malformed (%s)" % fingerprint)
- circ_path.append((fingerprint, nickname))
+ fingerprint = fingerprint[1:] # strip off the leading '$'
+
+ if nickname != None and not stem.util.tor_tools.is_valid_nickname(nickname):
+ raise stem.ProtocolError("Nickname in the circuit path is malformed (%s)" % fingerprint)
- return circ_path
+ return (fingerprint, nickname)
def _case_insensitive_lookup(entries, key, default = UNDEFINED):
"""
diff --git a/stem/response/events.py b/stem/response/events.py
index 0e7a71e..6a98ef6 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -12,6 +12,10 @@ from stem.util import connection, log, str_tools, tor_tools
KW_ARG = re.compile("([A-Za-z0-9_]+)=(.*)")
+# base message for when we get attributes not covered by our enums
+
+UNRECOGNIZED_ATTR_MSG = "%s event had an unrecognized %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'"
+
class Event(stem.response.ControlMessage):
"""
Base for events we receive asynchronously, as described in section 4.1 of the
@@ -150,7 +154,7 @@ class CircuitEvent(Event):
# log if we have an unrecognized status, build flag, purpose, hidden
# service state, or closure reason
- unrecognized_msg = "CIRC event had an unrecognized %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
+ unrecognized_msg = UNRECOGNIZED_ATTR_MSG % ("CIRC", self)
if self.status and (not self.status in stem.CircStatus):
log_id = "event.circ.unknown_status.%s" % self.status
@@ -195,6 +199,73 @@ class LogEvent(Event):
self.message = str(self)[len(self.runlevel) + 1:].rstrip("\nOK")
+class ORConnEvent(Event):
+ """
+ Event that indicates a change in a relay connection. The 'endpoint' could be
+ any of several things including a...
+
+ * fingerprint
+ * nickname
+ * 'fingerprint=nickname' pair
+ * address:port
+
+ The derived 'endpoint_*' attributes are generally more useful.
+
+ :var str endpoint: relay that the event concerns
+ :var str endpoint_fingerprint: endpoint's finterprint if it was provided
+ :var str endpoint_nickname: endpoint's nickname if it was provided
+ :var str endpoint_address: endpoint's address if it was provided
+ :var int endpoint_port: endpoint's port if it was provided
+ :var stem.ORStatus status: state of the connection
+ :var stem.ORClosureReason reason: reason for the connection to be closed
+ :var int circ_count: number of established and pending circuits
+ """
+
+ _POSITIONAL_ARGS = ("endpoint", "status")
+ _KEYWORD_ARGS = {
+ "REASON": "reason",
+ "NCIRCS": "circ_count",
+ }
+
+ def _parse(self):
+ self.endpoint_fingerprint = None
+ self.endpoint_nickname = None
+ self.endpoint_address = None
+ self.endpoint_port = None
+
+ try:
+ self.endpoint_fingerprint, self.endpoint_nickname = \
+ stem.control._parse_circ_entry(self.endpoint)
+ except stem.ProtocolError:
+ if not ':' in self.endpoint:
+ raise stem.ProtocolError("ORCONN endpoint is neither a relay nor 'address:port': %s" % self)
+
+ address, port = self.endpoint.split(':', 1)
+
+ if not connection.is_valid_port(port):
+ raise stem.ProtocolError("ORCONN's endpoint location's port is invalid: %s" % self)
+
+ self.endpoint_address = address
+ self.endpoint_port = int(port)
+
+ if self.circ_count != None:
+ if not self.circ_count.isdigit():
+ raise stem.ProtocolError("ORCONN event got a non-numeric circuit count (%s): %s" % (self.circ_count, self))
+
+ self.circ_count = int(self.circ_count)
+
+ # log if we have an unrecognized status or reason
+
+ unrecognized_msg = UNRECOGNIZED_ATTR_MSG % ("ORCONN", self)
+
+ if self.status and (not self.status in stem.ORStatus):
+ log_id = "event.orconn.unknown_status.%s" % self.status
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('status', self.status))
+
+ if self.reason and (not self.reason in stem.ORClosureReason):
+ log_id = "event.orconn.unknown_reason.%s" % self.reason
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('reason', self.reason))
+
class StreamEvent(Event):
"""
Event that indicates that a stream has changed.
@@ -231,7 +302,7 @@ class StreamEvent(Event):
if not ':' in self.target:
raise stem.ProtocolError("Target location must be of the form 'address:port': %s" % self)
- address, port = self.target.split(':')
+ address, port = self.target.split(':', 1)
if not connection.is_valid_port(port):
raise stem.ProtocolError("Target location's port is invalid: %s" % self)
@@ -246,7 +317,7 @@ class StreamEvent(Event):
if not ':' in self.source_addr:
raise stem.ProtocolError("Source location must be of the form 'address:port': %s" % self)
- address, port = self.source_addr.split(':')
+ address, port = self.source_addr.split(':', 1)
if not connection.is_valid_port(port):
raise stem.ProtocolError("Source location's port is invalid: %s" % self)
@@ -261,7 +332,7 @@ class StreamEvent(Event):
# log if we have an unrecognized closure reason or purpose
- unrecognized_msg = "STREAM event had an unrecognized %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
+ unrecognized_msg = UNRECOGNIZED_ATTR_MSG % ("STREAM", self)
if self.reason and (not self.reason in stem.StreamClosureReason):
log_id = "event.stream.reason.%s" % self.reason
@@ -276,13 +347,14 @@ class StreamEvent(Event):
log.log_once(log_id, log.INFO, unrecognized_msg % ('purpose', self.purpose))
EVENT_TYPE_TO_CLASS = {
- "CIRC": CircuitEvent,
- "STREAM": StreamEvent,
- "BW": BandwidthEvent,
"DEBUG": LogEvent,
"INFO": LogEvent,
"NOTICE": LogEvent,
"WARN": LogEvent,
"ERR": LogEvent,
+ "BW": BandwidthEvent,
+ "CIRC": CircuitEvent,
+ "ORCONN": ORConnEvent,
+ "STREAM": StreamEvent,
}
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index 3fe22de..c17e84d 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -54,6 +54,11 @@ STREAM_SUCCEEDED = "650 STREAM 18 SUCCEEDED 26 74.125.227.129:443"
STREAM_CLOSED_RESET = "650 STREAM 21 CLOSED 26 74.125.227.129:443 REASON=CONNRESET"
STREAM_CLOSED_DONE = "650 STREAM 25 CLOSED 26 199.7.52.72:80 REASON=DONE"
+# ORCONN events from starting tor 0.2.2.39 via TBB
+
+ORCONN_CONNECTED = "650 ORCONN $7ED90E2833EE38A75795BA9237B0A4560E51E1A0=GreenDragon CONNECTED"
+ORCONN_CLOSED = "650 ORCONN $A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama CLOSED REASON=DONE"
+
def _get_event(content):
controller_event = mocking.get_message(content)
stem.response.convert("EVENT", controller_event, arrived_at = 25)
@@ -181,6 +186,33 @@ class TestEvents(unittest.TestCase):
self.assertEqual(None, event.reason)
self.assertEqual(None, event.remote_reason)
+ def test_orconn_event(self):
+ event = _get_event(ORCONN_CONNECTED)
+
+ self.assertTrue(isinstance(event, stem.response.events.ORConnEvent))
+ self.assertEqual(ORCONN_CONNECTED.lstrip("650 "), str(event))
+ self.assertEqual("$7ED90E2833EE38A75795BA9237B0A4560E51E1A0=GreenDragon", event.endpoint)
+ self.assertEqual("7ED90E2833EE38A75795BA9237B0A4560E51E1A0", event.endpoint_fingerprint)
+ self.assertEqual("GreenDragon", event.endpoint_nickname)
+ self.assertEqual(None, event.endpoint_address)
+ self.assertEqual(None, event.endpoint_port)
+ self.assertEqual(ORStatus.CONNECTED, event.status)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.circ_count)
+
+ event = _get_event(ORCONN_CLOSED)
+
+ self.assertTrue(isinstance(event, stem.response.events.ORConnEvent))
+ self.assertEqual(ORCONN_CLOSED.lstrip("650 "), str(event))
+ self.assertEqual("$A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama", event.endpoint)
+ self.assertEqual("A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1", event.endpoint_fingerprint)
+ self.assertEqual("tama", event.endpoint_nickname)
+ self.assertEqual(None, event.endpoint_address)
+ self.assertEqual(None, event.endpoint_port)
+ self.assertEqual(ORStatus.CLOSED, event.status)
+ self.assertEqual(ORClosureReason.DONE, event.reason)
+ self.assertEqual(None, event.circ_count)
+
def test_stream_event(self):
event = _get_event(STREAM_NEW)
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits