d9284cd4cb
This improves offline behavior for some tests, if we get spurious DNS lookup errors.
145 lines
5.7 KiB
Python
145 lines
5.7 KiB
Python
from __future__ import print_function, unicode_literals
|
|
import sys
|
|
import re
|
|
import six
|
|
from collections import namedtuple
|
|
from twisted.internet.endpoints import TCP4ClientEndpoint, TCP6ClientEndpoint, HostnameEndpoint
|
|
from twisted.internet.abstract import isIPAddress, isIPv6Address
|
|
from twisted.python import log
|
|
|
|
# These namedtuples are "hint objects". The JSON-serializable dictionaries
|
|
# are "hint dicts".
|
|
|
|
# DirectTCPV1Hint and TorTCPV1Hint mean the following protocol:
|
|
# * make a TCP connection (possibly via Tor)
|
|
# * send the sender/receiver handshake bytes first
|
|
# * expect to see the receiver/sender handshake bytes from the other side
|
|
# * the sender writes "go\n", the receiver waits for "go\n"
|
|
# * the rest of the connection contains transit data
|
|
DirectTCPV1Hint = namedtuple("DirectTCPV1Hint",
|
|
["hostname", "port", "priority"])
|
|
TorTCPV1Hint = namedtuple("TorTCPV1Hint", ["hostname", "port", "priority"])
|
|
# RelayV1Hint contains a tuple of DirectTCPV1Hint and TorTCPV1Hint hints (we
|
|
# use a tuple rather than a list so they'll be hashable into a set). For each
|
|
# one, make the TCP connection, send the relay handshake, then complete the
|
|
# rest of the V1 protocol. Only one hint per relay is useful.
|
|
RelayV1Hint = namedtuple("RelayV1Hint", ["hints"])
|
|
|
|
def describe_hint_obj(hint, relay, tor):
|
|
prefix = "tor->" if tor else "->"
|
|
if relay:
|
|
prefix = prefix + "relay:"
|
|
if isinstance(hint, DirectTCPV1Hint):
|
|
return prefix + "tcp:%s:%d" % (hint.hostname, hint.port)
|
|
elif isinstance(hint, TorTCPV1Hint):
|
|
return prefix + "tor:%s:%d" % (hint.hostname, hint.port)
|
|
else:
|
|
return prefix + str(hint)
|
|
|
|
def parse_hint_argv(hint, stderr=sys.stderr):
|
|
assert isinstance(hint, type(u""))
|
|
# return tuple or None for an unparseable hint
|
|
priority = 0.0
|
|
mo = re.search(r'^([a-zA-Z0-9]+):(.*)$', hint)
|
|
if not mo:
|
|
print("unparseable hint '%s'" % (hint, ), file=stderr)
|
|
return None
|
|
hint_type = mo.group(1)
|
|
if hint_type != "tcp":
|
|
print("unknown hint type '%s' in '%s'" % (hint_type, hint),
|
|
file=stderr)
|
|
return None
|
|
hint_value = mo.group(2)
|
|
pieces = hint_value.split(":")
|
|
if len(pieces) < 2:
|
|
print("unparseable TCP hint (need more colons) '%s'" % (hint, ),
|
|
file=stderr)
|
|
return None
|
|
mo = re.search(r'^(\d+)$', pieces[1])
|
|
if not mo:
|
|
print("non-numeric port in TCP hint '%s'" % (hint, ), file=stderr)
|
|
return None
|
|
hint_host = pieces[0]
|
|
hint_port = int(pieces[1])
|
|
for more in pieces[2:]:
|
|
if more.startswith("priority="):
|
|
more_pieces = more.split("=")
|
|
try:
|
|
priority = float(more_pieces[1])
|
|
except ValueError:
|
|
print("non-float priority= in TCP hint '%s'" % (hint, ),
|
|
file=stderr)
|
|
return None
|
|
return DirectTCPV1Hint(hint_host, hint_port, priority)
|
|
|
|
def endpoint_from_hint_obj(hint, tor, reactor):
|
|
if tor:
|
|
if isinstance(hint, (DirectTCPV1Hint, TorTCPV1Hint)):
|
|
# this Tor object will throw ValueError for non-public IPv4
|
|
# addresses and any IPv6 address
|
|
try:
|
|
return tor.stream_via(hint.hostname, hint.port)
|
|
except ValueError:
|
|
return None
|
|
return None
|
|
if isinstance(hint, DirectTCPV1Hint):
|
|
# avoid DNS lookup unless necessary
|
|
if isIPAddress(hint.hostname):
|
|
return TCP4ClientEndpoint(reactor, hint.hostname, hint.port)
|
|
if isIPv6Address(hint.hostname):
|
|
return TCP6ClientEndpoint(reactor, hint.hostname, hint.port)
|
|
return HostnameEndpoint(reactor, hint.hostname, hint.port)
|
|
return None
|
|
|
|
def parse_tcp_v1_hint(hint): # hint_struct -> hint_obj
|
|
hint_type = hint.get("type", "")
|
|
if hint_type not in ["direct-tcp-v1", "tor-tcp-v1"]:
|
|
log.msg("unknown hint type: %r" % (hint, ))
|
|
return None
|
|
if not ("hostname" in hint and
|
|
isinstance(hint["hostname"], type(""))):
|
|
log.msg("invalid hostname in hint: %r" % (hint, ))
|
|
return None
|
|
if not ("port" in hint and
|
|
isinstance(hint["port"], six.integer_types)):
|
|
log.msg("invalid port in hint: %r" % (hint, ))
|
|
return None
|
|
priority = hint.get("priority", 0.0)
|
|
if hint_type == "direct-tcp-v1":
|
|
return DirectTCPV1Hint(hint["hostname"], hint["port"], priority)
|
|
else:
|
|
return TorTCPV1Hint(hint["hostname"], hint["port"], priority)
|
|
|
|
def parse_hint(hint_struct):
|
|
hint_type = hint_struct.get("type", "")
|
|
if hint_type == "relay-v1":
|
|
# the struct can include multiple ways to reach the same relay
|
|
rhints = filter(lambda h: h, # drop None (unrecognized)
|
|
[parse_tcp_v1_hint(rh) for rh in hint_struct["hints"]])
|
|
return RelayV1Hint(list(rhints))
|
|
return parse_tcp_v1_hint(hint_struct)
|
|
|
|
|
|
def encode_hint(h):
|
|
if isinstance(h, DirectTCPV1Hint):
|
|
return {"type": "direct-tcp-v1",
|
|
"priority": h.priority,
|
|
"hostname": h.hostname,
|
|
"port": h.port, # integer
|
|
}
|
|
elif isinstance(h, RelayV1Hint):
|
|
rhint = {"type": "relay-v1", "hints": []}
|
|
for rh in h.hints:
|
|
rhint["hints"].append({"type": "direct-tcp-v1",
|
|
"priority": rh.priority,
|
|
"hostname": rh.hostname,
|
|
"port": rh.port})
|
|
return rhint
|
|
elif isinstance(h, TorTCPV1Hint):
|
|
return {"type": "tor-tcp-v1",
|
|
"priority": h.priority,
|
|
"hostname": h.hostname,
|
|
"port": h.port, # integer
|
|
}
|
|
raise ValueError("unknown hint type", h)
|