dedup relays, include our own relay when connecting

* Previously, we only connected to the relay supplied by our partner, which
  meant that if our relay differed from theirs, we'd never connect
* But we must de-duplicate the relays because when our relay *is* the same as
  theirs, we'd have two copies, which means two connections. Now that we
  deliver sided handshakes, we can tolerate that (previously, our two
  connections would be matched with each other), but it's still wasteful.

This also fixes our handling of relay hints to accept multiple specific
endpoints in each RelayHint. The idea here is that we might know multiple
addresses for a single relay (maybe one IPv4, one IPv6, a Tor .onion, and an
I2P address). Any one connection is good enough, and the connections we can
try depend upon what local interfaces we discover. So a clever implementation
could refrain from making some of those connections when it knows the sibling
hints are just as good. However we might still have multiple relays entirely,
for which it is *not* sufficient to connect to just one.

The change is to create and process RelayV1Hint objects properly, and to set
the connection loop to try every endpoint inside each RelayV1Hint. This is
not "clever" (we could nominally make fewer connection attempts), but it's
plenty good for now.

refs #115

fix relay hints
This commit is contained in:
Brian Warner 2016-12-22 18:28:58 -05:00
parent 80ae9236df
commit b8313b4595
2 changed files with 21 additions and 16 deletions

View File

@ -176,7 +176,7 @@ class Basic(unittest.TestCase):
c.add_connection_hints([{"type": "relay-v1", c.add_connection_hints([{"type": "relay-v1",
"hints": [{"type": "unknown"}]}]) "hints": [{"type": "unknown"}]}])
self.assertEqual(c._their_direct_hints, []) self.assertEqual(c._their_direct_hints, [])
self.assertEqual(c._their_relay_hints, []) self.assertEqual(c._our_relay_hints, set())
def test_ignore_localhost_hint(self): def test_ignore_localhost_hint(self):
# this actually starts the listener # this actually starts the listener

View File

@ -590,7 +590,7 @@ class Common:
else: else:
self._transit_relays = [] self._transit_relays = []
self._their_direct_hints = [] # hintobjs self._their_direct_hints = [] # hintobjs
self._their_relay_hints = [] self._our_relay_hints = set(self._transit_relays)
self._tor_manager = tor_manager self._tor_manager = tor_manager
self._transit_key = None self._transit_key = None
self._no_listen = no_listen self._no_listen = no_listen
@ -710,10 +710,14 @@ class Common:
# with a set of equally-valid ways to connect to it. Treat # with a set of equally-valid ways to connect to it. Treat
# them as separate relays, instead of merging them all # them as separate relays, instead of merging them all
# together like this. # together like this.
relay_hints = []
for rhs in h.get(u"hints", []): for rhs in h.get(u"hints", []):
rh = self._parse_tcp_v1_hint(rhs) h = self._parse_tcp_v1_hint(rhs)
if rh: if h:
self._their_relay_hints.append(rh) relay_hints.append(h)
if relay_hints:
rh = RelayV1Hint(hints=tuple(sorted(relay_hints)))
self._our_relay_hints.add(rh)
else: else:
log.msg("unknown hint type: %r" % (h,)) log.msg("unknown hint type: %r" % (h,))
@ -804,21 +808,22 @@ class Common:
contenders.append(d) contenders.append(d)
relay_delay = self.RELAY_DELAY relay_delay = self.RELAY_DELAY
# Start trying the relay a few seconds after we start to try the # Start trying the relays a few seconds after we start to try the
# direct hints. The idea is to prefer direct connections, but not be # direct hints. The idea is to prefer direct connections, but not be
# afraid of using the relay when we have direct hints that don't # afraid of using a relay when we have direct hints that don't
# resolve quickly. Many direct hints will be to unused local-network # resolve quickly. Many direct hints will be to unused local-network
# IP addresses, which won't answer, and would take the full TCP # IP addresses, which won't answer, and would take the full TCP
# timeout (30s or more) to fail. # timeout (30s or more) to fail.
for hint_obj in self._their_relay_hints: for rh in self._our_relay_hints:
ep = self._endpoint_from_hint_obj(hint_obj) for hint_obj in rh.hints:
if not ep: ep = self._endpoint_from_hint_obj(hint_obj)
continue if not ep:
description = "->relay:%s" % describe_hint_obj(hint_obj) continue
d = task.deferLater(self._reactor, relay_delay, description = "->relay:%s" % describe_hint_obj(hint_obj)
self._start_connector, ep, description, d = task.deferLater(self._reactor, relay_delay,
is_relay=True) self._start_connector, ep, description,
contenders.append(d) is_relay=True)
contenders.append(d)
if not contenders: if not contenders:
raise TransitError("No contenders for connection") raise TransitError("No contenders for connection")