From a8446d2bc5ee6ae342d530902aa0f66c9bab87e0 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 15 Apr 2016 17:42:38 -0700 Subject: [PATCH] merge transit_common into twisted.transit --- src/wormhole/transit_common.py | 87 ------------------------------ src/wormhole/twisted/transit.py | 93 ++++++++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 94 deletions(-) delete mode 100644 src/wormhole/transit_common.py diff --git a/src/wormhole/transit_common.py b/src/wormhole/transit_common.py deleted file mode 100644 index eb18c92..0000000 --- a/src/wormhole/transit_common.py +++ /dev/null @@ -1,87 +0,0 @@ -import re -from binascii import hexlify -from .util.hkdf import HKDF - -class TransitError(Exception): - pass - -class BadHandshake(Exception): - pass - -class TransitClosed(TransitError): - pass - -class BadNonce(TransitError): - pass - -# The beginning of each TCP connection consists of the following handshake -# messages. The sender transmits the same text regardless of whether it is on -# the initiating/connecting end of the TCP connection, or on the -# listening/accepting side. Same for the receiver. -# -# sender -> receiver: transit sender TXID_HEX ready\n\n -# receiver -> sender: transit receiver RXID_HEX ready\n\n -# -# Any deviations from this result in the socket being closed. The handshake -# messages are designed to provoke an invalid response from other sorts of -# servers (HTTP, SMTP, echo). -# -# If the sender is satisfied with the handshake, and this is the first socket -# to complete negotiation, the sender does: -# -# sender -> receiver: go\n -# -# and the next byte on the wire will be from the application. -# -# If this is not the first socket, the sender does: -# -# sender -> receiver: nevermind\n -# -# and closes the socket. - -# So the receiver looks for "transit sender TXID_HEX ready\n\ngo\n" and hangs -# up upon the first wrong byte. The sender lookgs for "transit receiver -# RXID_HEX ready\n\n" and then makes a first/not-first decision about sending -# "go\n" or "nevermind\n"+close(). - -def build_receiver_handshake(key): - hexid = HKDF(key, 32, CTXinfo=b"transit_receiver") - return b"transit receiver "+hexlify(hexid)+b" ready\n\n" - -def build_sender_handshake(key): - hexid = HKDF(key, 32, CTXinfo=b"transit_sender") - return b"transit sender "+hexlify(hexid)+b" ready\n\n" - -def build_relay_handshake(key): - token = HKDF(key, 32, CTXinfo=b"transit_relay_token") - return b"please relay "+hexlify(token)+b"\n" - -# The hint format is: TYPE,VALUE= /^([a-zA-Z0-9]+):(.*)$/ . VALUE depends -# upon TYPE, and it can have more colons in it. For TYPE=tcp (the only one -# currently defined), ADDR,PORT = /^(.*):(\d+)$/ , so ADDR can have colons. -# ADDR can be a hostname, ipv4 dotted-quad, or ipv6 colon-hex. If the hint -# publisher wants anonymity, their only hint's ADDR will end in .onion . - -def parse_hint_tcp(hint): - assert isinstance(hint, type(u"")) - # return tuple or None for an unparseable hint - mo = re.search(r'^([a-zA-Z0-9]+):(.*)$', hint) - if not mo: - print("unparseable hint '%s'" % (hint,)) - return None - hint_type = mo.group(1) - if hint_type != "tcp": - print("unknown hint type '%s' in '%s'" % (hint_type, hint)) - return None - hint_value = mo.group(2) - mo = re.search(r'^(.*):(\d+)$', hint_value) - if not mo: - print("unparseable TCP hint '%s'" % (hint,)) - return None - hint_host = mo.group(1) - try: - hint_port = int(mo.group(2)) - except ValueError: - print("non-numeric port in TCP hint '%s'" % (hint,)) - return None - return hint_host, hint_port diff --git a/src/wormhole/twisted/transit.py b/src/wormhole/twisted/transit.py index cc8bed8..199b361 100644 --- a/src/wormhole/twisted/transit.py +++ b/src/wormhole/twisted/transit.py @@ -1,5 +1,5 @@ from __future__ import print_function -import sys, time, socket, collections +import re, sys, time, socket, collections from binascii import hexlify, unhexlify from zope.interface import implementer from twisted.python.runtime import platformType @@ -12,11 +12,6 @@ from ..util import ipaddrs from ..util.hkdf import HKDF from ..errors import UsageError from ..timing import DebugTiming -from ..transit_common import (BadHandshake, - BadNonce, - build_receiver_handshake, - build_sender_handshake, - build_relay_handshake) def debug(msg): if False: @@ -24,6 +19,90 @@ def debug(msg): def since(start): return time.time() - start +class TransitError(Exception): + pass + +class BadHandshake(Exception): + pass + +class TransitClosed(TransitError): + pass + +class BadNonce(TransitError): + pass + +# The beginning of each TCP connection consists of the following handshake +# messages. The sender transmits the same text regardless of whether it is on +# the initiating/connecting end of the TCP connection, or on the +# listening/accepting side. Same for the receiver. +# +# sender -> receiver: transit sender TXID_HEX ready\n\n +# receiver -> sender: transit receiver RXID_HEX ready\n\n +# +# Any deviations from this result in the socket being closed. The handshake +# messages are designed to provoke an invalid response from other sorts of +# servers (HTTP, SMTP, echo). +# +# If the sender is satisfied with the handshake, and this is the first socket +# to complete negotiation, the sender does: +# +# sender -> receiver: go\n +# +# and the next byte on the wire will be from the application. +# +# If this is not the first socket, the sender does: +# +# sender -> receiver: nevermind\n +# +# and closes the socket. + +# So the receiver looks for "transit sender TXID_HEX ready\n\ngo\n" and hangs +# up upon the first wrong byte. The sender lookgs for "transit receiver +# RXID_HEX ready\n\n" and then makes a first/not-first decision about sending +# "go\n" or "nevermind\n"+close(). + +def build_receiver_handshake(key): + hexid = HKDF(key, 32, CTXinfo=b"transit_receiver") + return b"transit receiver "+hexlify(hexid)+b" ready\n\n" + +def build_sender_handshake(key): + hexid = HKDF(key, 32, CTXinfo=b"transit_sender") + return b"transit sender "+hexlify(hexid)+b" ready\n\n" + +def build_relay_handshake(key): + token = HKDF(key, 32, CTXinfo=b"transit_relay_token") + return b"please relay "+hexlify(token)+b"\n" + +# The hint format is: TYPE,VALUE= /^([a-zA-Z0-9]+):(.*)$/ . VALUE depends +# upon TYPE, and it can have more colons in it. For TYPE=tcp (the only one +# currently defined), ADDR,PORT = /^(.*):(\d+)$/ , so ADDR can have colons. +# ADDR can be a hostname, ipv4 dotted-quad, or ipv6 colon-hex. If the hint +# publisher wants anonymity, their only hint's ADDR will end in .onion . + +def parse_hint_tcp(hint): + assert isinstance(hint, type(u"")) + # return tuple or None for an unparseable hint + mo = re.search(r'^([a-zA-Z0-9]+):(.*)$', hint) + if not mo: + print("unparseable hint '%s'" % (hint,)) + return None + hint_type = mo.group(1) + if hint_type != "tcp": + print("unknown hint type '%s' in '%s'" % (hint_type, hint)) + return None + hint_value = mo.group(2) + mo = re.search(r'^(.*):(\d+)$', hint_value) + if not mo: + print("unparseable TCP hint '%s'" % (hint,)) + return None + hint_host = mo.group(1) + try: + hint_port = int(mo.group(2)) + except ValueError: + print("non-numeric port in TCP hint '%s'" % (hint,)) + return None + return hint_host, hint_port + TIMEOUT=15 @implementer(interfaces.IProducer, interfaces.IConsumer) @@ -684,7 +763,7 @@ class Common: return d def _endpoint_from_hint(self, hint): - # TODO: use transit_common.parse_hint_tcp + # TODO: use parse_hint_tcp if ":" not in hint: return None pieces = hint.split(":")