From ffd4981247c2d229fff54a35118a278a77070e86 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 13 Apr 2019 22:36:29 +0300 Subject: [PATCH 01/42] increase autorelay discovery limit to 1k --- p2p/host/relay/autorelay.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index fbbbeef565..a580a74d74 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -136,10 +136,7 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { need := DesiredRelays - len(ar.relays) ar.mx.Unlock() - limit := 50 - if need > limit/2 { - limit = 2 * need - } + limit := 1000 dctx, cancel := context.WithTimeout(ctx, 30*time.Second) pis, err := discovery.FindPeers(dctx, ar.discover, RelayRendezvous, limit) From a1ebc4d8525f863d9580e8239df6a12c094dbacb Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 13 Apr 2019 23:46:41 +0300 Subject: [PATCH 02/42] reduce relay find peer and connect timeout to 30s --- p2p/host/relay/autorelay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index a580a74d74..0d85ad5f5c 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -158,7 +158,7 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { } ar.mx.Unlock() - cctx, cancel := context.WithTimeout(ctx, 60*time.Second) + cctx, cancel := context.WithTimeout(ctx, 30*time.Second) if len(pi.Addrs) == 0 { pi, err = ar.router.FindPeer(cctx, pi.ID) From aa03a9b8395cb62680aa9843843bd419d1737e34 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 12:20:57 +0300 Subject: [PATCH 03/42] clean up relay address sets to curtail addrsplosion --- p2p/host/relay/autorelay.go | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 0d85ad5f5c..3c7720728c 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/rand" + "strconv" "sync" "time" @@ -169,6 +170,14 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { } } + cleanupAddressSet(&pi) + + if len(pi.Addrs) == 0 { + log.Debugf("no usable addresses for relay peer %s", pi.ID) + cancel() + continue + } + err = ar.host.Connect(cctx, pi) cancel() if err != nil { @@ -250,6 +259,55 @@ func (ar *AutoRelay) doUpdateAddrs() { ar.addrs = raddrs } +// This function cleans up a relay's address set to remove private addresses and curtail +// addrsplosion. For the latter, we use the following heuristic: +// - if the address set includes a (tcp) address with the default port 4001, +// we remove all tcp addrs with a different port +// - Otherwise we remove all addrs with ephemeral ports (>= 32768) +func cleanupAddressSet(pi *pstore.PeerInfo) { + // pass-1: find default port + has4001 := false + for _, addr := range pi.Addrs { + port, err := tcpPort(addr) + if err != nil { + continue + } + if port == 4001 { + has4001 = true + break + } + } + + // pass-2: cleanup + var newAddrs []ma.Multiaddr + + for _, addr := range pi.Addrs { + if manet.IsPrivateAddr(addr) { + continue + } + + port, err := tcpPort(addr) + if err == nil { + if (has4001 && port != 4001) || port >= 32768 { + continue + } + } + + newAddrs = append(newAddrs, addr) + } + + pi.Addrs = newAddrs +} + +func tcpPort(addr ma.Multiaddr) (int, error) { + val, err := addr.ValueForProtocol(ma.P_TCP) + if err != nil { + // not tcp + return 0, err + } + return strconv.Atoi(val) +} + func shuffleRelays(pis []pstore.PeerInfo) { for i := range pis { j := rand.Intn(i + 1) From 9863e22d4a8d5f987b377e2168b84cb650e61c85 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 14:06:13 +0300 Subject: [PATCH 04/42] rework relay selection logic --- p2p/host/relay/autorelay.go | 67 ++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 3c7720728c..47ec35776b 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -147,8 +147,7 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { return } - pis = ar.selectRelays(pis) - + pis = ar.selectRelays(ctx, pis, 20) update := 0 for _, pi := range pis { @@ -159,25 +158,7 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { } ar.mx.Unlock() - cctx, cancel := context.WithTimeout(ctx, 30*time.Second) - - if len(pi.Addrs) == 0 { - pi, err = ar.router.FindPeer(cctx, pi.ID) - if err != nil { - log.Debugf("error finding relay peer %s: %s", pi.ID, err.Error()) - cancel() - continue - } - } - - cleanupAddressSet(&pi) - - if len(pi.Addrs) == 0 { - log.Debugf("no usable addresses for relay peer %s", pi.ID) - cancel() - continue - } - + cctx, cancel := context.WithTimeout(ctx, 15*time.Second) err = ar.host.Connect(cctx, pi) cancel() if err != nil { @@ -205,11 +186,51 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { } } -func (ar *AutoRelay) selectRelays(pis []pstore.PeerInfo) []pstore.PeerInfo { +func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, count int) []pstore.PeerInfo { // TODO better relay selection strategy; this just selects random relays // but we should probably use ping latency as the selection metric shuffleRelays(pis) - return pis + + if len(pis) < count { + count = len(pis) + } + + // only select relays that can be found by routing + type queryResult struct { + pi pstore.PeerInfo + err error + } + result := make([]pstore.PeerInfo, 0, count) + resultCh := make(chan queryResult, len(pis)) + + qctx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + + for _, pi := range pis { + go func(p peer.ID) { + pi, err := ar.router.FindPeer(qctx, p) + if err != nil { + log.Debugf("Error finding relay peer %s: %s", p, err.Error()) + } + resultCh <- queryResult{pi: pi, err: err} + }(pi.ID) + } + + for len(result) < count { + select { + case qr := <-resultCh: + if qr.err == nil { + cleanupAddressSet(&qr.pi) + result = append(result, qr.pi) + } + + case <-qctx.Done(): + break + } + } + + shuffleRelays(result) + return result } func (ar *AutoRelay) updateAddrs() { From 765ed0e1a63b3ce8c225c8ff697afc7354fb6c68 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 14:13:36 +0300 Subject: [PATCH 05/42] select 50 relays --- p2p/host/relay/autorelay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 47ec35776b..fbb26a74e8 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -147,7 +147,7 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { return } - pis = ar.selectRelays(ctx, pis, 20) + pis = ar.selectRelays(ctx, pis, 50) update := 0 for _, pi := range pis { From 39f2b7ad456c99868dc6f177ad299f9354582c87 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 14:25:30 +0300 Subject: [PATCH 06/42] fix selectRelays to return promptly when there are query errors --- p2p/host/relay/autorelay.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index fbb26a74e8..24035e5361 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -189,7 +189,6 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, count int) []pstore.PeerInfo { // TODO better relay selection strategy; this just selects random relays // but we should probably use ping latency as the selection metric - shuffleRelays(pis) if len(pis) < count { count = len(pis) @@ -216,12 +215,13 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co }(pi.ID) } - for len(result) < count { + rcount := 0 + for len(result) < count && rcount < len(pis) { select { case qr := <-resultCh: + rcount++ if qr.err == nil { - cleanupAddressSet(&qr.pi) - result = append(result, qr.pi) + result = append(result, cleanupAddressSet(qr.pi)) } case <-qctx.Done(): @@ -285,7 +285,7 @@ func (ar *AutoRelay) doUpdateAddrs() { // - if the address set includes a (tcp) address with the default port 4001, // we remove all tcp addrs with a different port // - Otherwise we remove all addrs with ephemeral ports (>= 32768) -func cleanupAddressSet(pi *pstore.PeerInfo) { +func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { // pass-1: find default port has4001 := false for _, addr := range pi.Addrs { @@ -317,7 +317,7 @@ func cleanupAddressSet(pi *pstore.PeerInfo) { newAddrs = append(newAddrs, addr) } - pi.Addrs = newAddrs + return pstore.PeerInfo{ID: pi.ID, Addrs: newAddrs} } func tcpPort(addr ma.Multiaddr) (int, error) { From becb89a2451296e02b7b7cf23443c6fed44be7e3 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 14:32:31 +0300 Subject: [PATCH 07/42] shuffle relay set before queries --- p2p/host/relay/autorelay.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 24035e5361..42e36b439e 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -205,6 +205,8 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co qctx, cancel := context.WithTimeout(ctx, 15*time.Second) defer cancel() + // shuffle to randomize the order of queries + shuffleRelays(pis) for _, pi := range pis { go func(p peer.ID) { pi, err := ar.router.FindPeer(qctx, p) From 1138fb604084ba6dab531e8b3b35fe4cf7ff5e13 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 14:37:33 +0300 Subject: [PATCH 08/42] only determine default port if it is in a public addr --- p2p/host/relay/autorelay.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 42e36b439e..106b6f1e20 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -291,6 +291,10 @@ func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { // pass-1: find default port has4001 := false for _, addr := range pi.Addrs { + if manet.IsPrivateAddr(addr) { + continue + } + port, err := tcpPort(addr) if err != nil { continue From 31dfaf60296b5152f009b496bb351402077d71f2 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 14:53:30 +0300 Subject: [PATCH 09/42] increase relay advertising boot delay to 15min --- p2p/host/relay/relay.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/host/relay/relay.go b/p2p/host/relay/relay.go index 0f38166984..d0af9d227a 100644 --- a/p2p/host/relay/relay.go +++ b/p2p/host/relay/relay.go @@ -10,7 +10,8 @@ import ( ) var ( - AdvertiseBootDelay = 30 * time.Second + // this is purposefully long to require some node stability before advertising as a relay + AdvertiseBootDelay = 15 * time.Minute ) // Advertise advertises this node as a libp2p relay. From c9f627e221ad0780429169b6831924986b52da60 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 15:06:15 +0300 Subject: [PATCH 10/42] increase FindPeer timeout to 30s --- p2p/host/relay/autorelay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 106b6f1e20..55f8c6d8d1 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -202,7 +202,7 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co result := make([]pstore.PeerInfo, 0, count) resultCh := make(chan queryResult, len(pis)) - qctx, cancel := context.WithTimeout(ctx, 15*time.Second) + qctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() // shuffle to randomize the order of queries From 45d28886172bca54341c1eb0a2f0c88f3d5f9fc9 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 15:37:01 +0300 Subject: [PATCH 11/42] don't drop ephemeral ports in address set clean up --- p2p/host/relay/autorelay.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 55f8c6d8d1..b30a300d05 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -286,7 +286,6 @@ func (ar *AutoRelay) doUpdateAddrs() { // addrsplosion. For the latter, we use the following heuristic: // - if the address set includes a (tcp) address with the default port 4001, // we remove all tcp addrs with a different port -// - Otherwise we remove all addrs with ephemeral ports (>= 32768) func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { // pass-1: find default port has4001 := false @@ -313,9 +312,9 @@ func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { continue } - port, err := tcpPort(addr) - if err == nil { - if (has4001 && port != 4001) || port >= 32768 { + if has4001 { + port, err := tcpPort(addr) + if err == nil && port != 4001 { continue } } From da65fd74a6939a697964a0df1d03855814cd888e Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 15:37:20 +0300 Subject: [PATCH 12/42] fix and reinstate autorelay test --- p2p/host/relay/autorelay_test.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/p2p/host/relay/autorelay_test.go b/p2p/host/relay/autorelay_test.go index 379abd0ff0..c9da893920 100644 --- a/p2p/host/relay/autorelay_test.go +++ b/p2p/host/relay/autorelay_test.go @@ -26,8 +26,8 @@ import ( // test specific parameters func init() { - autonat.AutoNATIdentifyDelay = 500 * time.Millisecond - autonat.AutoNATBootDelay = 1 * time.Second + autonat.AutoNATIdentifyDelay = 1 * time.Second + autonat.AutoNATBootDelay = 2 * time.Second relay.BootDelay = 1 * time.Second relay.AdvertiseBootDelay = 1 * time.Millisecond manet.Private4 = []*net.IPNet{} @@ -37,6 +37,7 @@ func init() { type mockRoutingTable struct { mx sync.Mutex providers map[string]map[peer.ID]pstore.PeerInfo + peers map[peer.ID]pstore.PeerInfo } type mockRouting struct { @@ -53,7 +54,13 @@ func newMockRouting(h host.Host, tab *mockRoutingTable) *mockRouting { } func (m *mockRouting) FindPeer(ctx context.Context, p peer.ID) (pstore.PeerInfo, error) { - return pstore.PeerInfo{}, routing.ErrNotFound + m.tab.mx.Lock() + defer m.tab.mx.Unlock() + pi, ok := m.tab.peers[p] + if !ok { + return pstore.PeerInfo{}, routing.ErrNotFound + } + return pi, nil } func (m *mockRouting) Provide(ctx context.Context, cid cid.Cid, bcast bool) error { @@ -66,7 +73,12 @@ func (m *mockRouting) Provide(ctx context.Context, cid cid.Cid, bcast bool) erro m.tab.providers[cid.String()] = pmap } - pmap[m.h.ID()] = pstore.PeerInfo{ID: m.h.ID(), Addrs: m.h.Addrs()} + pi := pstore.PeerInfo{ID: m.h.ID(), Addrs: m.h.Addrs()} + pmap[m.h.ID()] = pi + if m.tab.peers == nil { + m.tab.peers = make(map[peer.ID]pstore.PeerInfo) + } + m.tab.peers[m.h.ID()] = pi return nil } @@ -133,7 +145,7 @@ func connect(t *testing.T, a, b host.Host) { // and the actual test! func TestAutoRelay(t *testing.T) { - t.Skip("fails 99% of the time") + //t.Skip("fails 99% of the time") ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -165,7 +177,7 @@ func TestAutoRelay(t *testing.T) { // connect to AutoNAT and let detection/discovery work its magic connect(t, h1, h3) - time.Sleep(3 * time.Second) + time.Sleep(5 * time.Second) // verify that we now advertise relay addrs (but not unspecific relay addrs) unspecificRelay, err := ma.NewMultiaddr("/p2p-circuit") From 8c7da83b55ee0a3289ef2d52305dcb53296d99b5 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 15:47:46 +0300 Subject: [PATCH 13/42] also check private addresses when looking for 4001 --- p2p/host/relay/autorelay.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index b30a300d05..a86b8e99f0 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -290,10 +290,6 @@ func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { // pass-1: find default port has4001 := false for _, addr := range pi.Addrs { - if manet.IsPrivateAddr(addr) { - continue - } - port, err := tcpPort(addr) if err != nil { continue From 081bb0f7ed4a86870c22f225440b861fa66397f7 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 15:55:45 +0300 Subject: [PATCH 14/42] small tweak in autorelay test --- p2p/host/relay/autorelay_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay_test.go b/p2p/host/relay/autorelay_test.go index c9da893920..cb661aa30b 100644 --- a/p2p/host/relay/autorelay_test.go +++ b/p2p/host/relay/autorelay_test.go @@ -29,7 +29,7 @@ func init() { autonat.AutoNATIdentifyDelay = 1 * time.Second autonat.AutoNATBootDelay = 2 * time.Second relay.BootDelay = 1 * time.Second - relay.AdvertiseBootDelay = 1 * time.Millisecond + relay.AdvertiseBootDelay = 100 * time.Millisecond manet.Private4 = []*net.IPNet{} } From 4a6e767da228ce9f9a2a63e4fd4d4e14e1579481 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 16:03:58 +0300 Subject: [PATCH 15/42] log and ignore relay peers with empty address sets --- p2p/host/relay/autorelay.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index a86b8e99f0..2f7f35055c 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -211,7 +211,7 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co go func(p peer.ID) { pi, err := ar.router.FindPeer(qctx, p) if err != nil { - log.Debugf("Error finding relay peer %s: %s", p, err.Error()) + log.Debugf("error finding relay peer %s: %s", p, err.Error()) } resultCh <- queryResult{pi: pi, err: err} }(pi.ID) @@ -223,7 +223,12 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co case qr := <-resultCh: rcount++ if qr.err == nil { - result = append(result, cleanupAddressSet(qr.pi)) + pi := cleanupAddressSet(qr.pi) + if len(pi.Addrs) > 0 { + result = append(result, pi) + } else { + log.Debugf("ignoring relay peer %s: cleaned up address set is empty", pi.ID) + } } case <-qctx.Done(): From 376379b5b84f423402d6fd01470933ad7f8e1fb9 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 16:34:11 +0300 Subject: [PATCH 16/42] retry to find relays if we fail to connect to any --- p2p/host/relay/autorelay.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 2f7f35055c..c65d8056b4 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -129,8 +129,10 @@ func (ar *AutoRelay) background(ctx context.Context) { } func (ar *AutoRelay) findRelays(ctx context.Context) { +again: ar.mx.Lock() - if len(ar.relays) >= DesiredRelays { + haveRelays := len(ar.relays) + if haveRelays >= DesiredRelays { ar.mx.Unlock() return } @@ -169,6 +171,7 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { log.Debugf("connected to relay %s", pi.ID) ar.mx.Lock() ar.relays[pi.ID] = pi + haveRelays++ ar.mx.Unlock() // tag the connection as very important @@ -181,6 +184,13 @@ func (ar *AutoRelay) findRelays(ctx context.Context) { } } + if haveRelays == 0 { + // we failed to find any relays and we are not connected to any! + // wait a little and try again, the discovery query might have returned only dead peers + time.Sleep(30 * time.Second) + goto again + } + if update > 0 || ar.addrs == nil { ar.updateAddrs() } From c8b8014fd08c0d890b80911f9f767544e1d5d8df Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 16:36:52 +0300 Subject: [PATCH 17/42] gate retry wait with the context --- p2p/host/relay/autorelay.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index c65d8056b4..3a8803f2eb 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -187,8 +187,12 @@ again: if haveRelays == 0 { // we failed to find any relays and we are not connected to any! // wait a little and try again, the discovery query might have returned only dead peers - time.Sleep(30 * time.Second) - goto again + select { + case <-time.After(30 * time.Second): + goto again + case <-ctx.Done(): + return + } } if update > 0 || ar.addrs == nil { From e8e2ab1930008a3979325d1e326aaf95d8afbda1 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 17:01:17 +0300 Subject: [PATCH 18/42] limit relay selection to 20 --- p2p/host/relay/autorelay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 3a8803f2eb..e07d3fdd44 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -149,7 +149,7 @@ again: return } - pis = ar.selectRelays(ctx, pis, 50) + pis = ar.selectRelays(ctx, pis, 20) update := 0 for _, pi := range pis { From 8d5c11c072fd0615cd99b323c6460ad19b03c587 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 17:40:16 +0300 Subject: [PATCH 19/42] fix private->public->private transition issues with address set --- p2p/host/relay/autorelay.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index e07d3fdd44..842f2ea685 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -133,7 +133,14 @@ again: ar.mx.Lock() haveRelays := len(ar.relays) if haveRelays >= DesiredRelays { + // this dance is necessary to cover the Private->Public->Private transition + // where we were already connected to enough relays while private and dropped + // the addrs while public + needUpdate := ar.addrs == nil ar.mx.Unlock() + if needUpdate { + ar.updateAddrs() + } return } need := DesiredRelays - len(ar.relays) From 0c69da940943bdacd51507bd8b49e81233329dc5 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 17:50:22 +0300 Subject: [PATCH 20/42] move the address invalidation check outside the lock --- p2p/host/relay/autorelay.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 842f2ea685..c3edbad881 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -133,12 +133,11 @@ again: ar.mx.Lock() haveRelays := len(ar.relays) if haveRelays >= DesiredRelays { + ar.mx.Unlock() // this dance is necessary to cover the Private->Public->Private transition // where we were already connected to enough relays while private and dropped // the addrs while public - needUpdate := ar.addrs == nil - ar.mx.Unlock() - if needUpdate { + if ar.addrs == nil { ar.updateAddrs() } return From 08306349383187c5af5ee824d17cc46be7597c41 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 21:16:35 +0300 Subject: [PATCH 21/42] limit number of FindPeer queries in relay selection --- p2p/host/relay/autorelay.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index c3edbad881..eb10727711 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -155,7 +155,7 @@ again: return } - pis = ar.selectRelays(ctx, pis, 20) + pis = ar.selectRelays(ctx, pis, 20, 50) update := 0 for _, pi := range pis { @@ -206,28 +206,37 @@ again: } } -func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, count int) []pstore.PeerInfo { +func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, count, maxq int) []pstore.PeerInfo { // TODO better relay selection strategy; this just selects random relays // but we should probably use ping latency as the selection metric + if len(pis) == 0 { + log.Debugf("no relays discovered") + return pis + } + if len(pis) < count { count = len(pis) } + if len(pis) < maxq { + maxq = len(pis) + } + // only select relays that can be found by routing type queryResult struct { pi pstore.PeerInfo err error } result := make([]pstore.PeerInfo, 0, count) - resultCh := make(chan queryResult, len(pis)) + resultCh := make(chan queryResult, maxq) qctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() // shuffle to randomize the order of queries shuffleRelays(pis) - for _, pi := range pis { + for _, pi := range pis[:maxq] { go func(p peer.ID) { pi, err := ar.router.FindPeer(qctx, p) if err != nil { @@ -238,7 +247,7 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co } rcount := 0 - for len(result) < count && rcount < len(pis) { + for len(result) < count && rcount < maxq { select { case qr := <-resultCh: rcount++ From 27f465ee356a54256f23c8ef8d641b99791c6de9 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sun, 14 Apr 2019 21:29:48 +0300 Subject: [PATCH 22/42] some better logging --- p2p/host/relay/autorelay.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index eb10727711..288640eeda 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -155,6 +155,8 @@ again: return } + log.Debugf("discovered %d relays", len(pis)) + pis = ar.selectRelays(ctx, pis, 20, 50) update := 0 @@ -193,6 +195,7 @@ again: if haveRelays == 0 { // we failed to find any relays and we are not connected to any! // wait a little and try again, the discovery query might have returned only dead peers + log.Debug("no relays connected; retrying in 30s") select { case <-time.After(30 * time.Second): goto again @@ -211,7 +214,6 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co // but we should probably use ping latency as the selection metric if len(pis) == 0 { - log.Debugf("no relays discovered") return pis } From 4a4b14819a5ca77312983adc7c5fd086d0cf27e0 Mon Sep 17 00:00:00 2001 From: vyzo Date: Thu, 18 Apr 2019 11:42:20 +0300 Subject: [PATCH 23/42] add comment about eliding the lock on addrs read --- p2p/host/relay/autorelay.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 288640eeda..a0d0523ef7 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -136,7 +136,9 @@ again: ar.mx.Unlock() // this dance is necessary to cover the Private->Public->Private transition // where we were already connected to enough relays while private and dropped - // the addrs while public + // the addrs while public. + // Note tht we don't need the lock for reading addrs, as the only writer is + // the current goroutine. if ar.addrs == nil { ar.updateAddrs() } From 433a0c09086e85fae2cbd76aeaa72d2a6bcef307 Mon Sep 17 00:00:00 2001 From: vyzo Date: Thu, 18 Apr 2019 14:42:06 +0300 Subject: [PATCH 24/42] extract cleanupAddrSet and implement better heuristic --- p2p/host/relay/addrsplosion.go | 137 +++++++++++++++++++++++++++++++++ p2p/host/relay/autorelay.go | 50 +----------- 2 files changed, 138 insertions(+), 49 deletions(-) create mode 100644 p2p/host/relay/addrsplosion.go diff --git a/p2p/host/relay/addrsplosion.go b/p2p/host/relay/addrsplosion.go new file mode 100644 index 0000000000..bdb1435412 --- /dev/null +++ b/p2p/host/relay/addrsplosion.go @@ -0,0 +1,137 @@ +package relay + +import ( + "encoding/binary" + + pstore "github.com/libp2p/go-libp2p-peerstore" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" +) + +// This function cleans up a relay's address set to remove private addresses and curtail +// addrsplosion. +func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { + var public, private []ma.Multiaddr + + for _, a := range pi.Addrs { + if manet.IsPublicAddr(a) || isDNSAddr(a) { + public = append(public, a) + continue + } + + // discard unroutable addrs + if manet.IsPrivateAddr(a) { + private = append(private, a) + } + } + + if !hasAddrsplosion(public) { + return pstore.PeerInfo{ID: pi.ID, Addrs: public} + } + + addrs := sanitizeAddrsplodedSet(public, private) + return pstore.PeerInfo{ID: pi.ID, Addrs: addrs} +} + +func isDNSAddr(a ma.Multiaddr) bool { + dnsaddr := false + ma.ForEach(a, func(c ma.Component) bool { + switch c.Protocol().Code { + case 54, 55, 56: + dnsaddr = true + } + + return false + }) + + return dnsaddr +} + +// we have addrsplosion if for some protocol we advertise multiple ports on +// the same base address. +func hasAddrsplosion(addrs []ma.Multiaddr) bool { + aset := make(map[string]int) + + for _, a := range addrs { + key, port := addrKeyAndPort(a) + xport, ok := aset[key] + if ok && port != xport { + return true + } + aset[key] = port + } + + return false +} + +func addrKeyAndPort(a ma.Multiaddr) (string, int) { + var ( + key string + port int + ) + + ma.ForEach(a, func(c ma.Component) bool { + switch c.Protocol().Code { + case ma.P_TCP, ma.P_UDP: + port = int(binary.BigEndian.Uint16(c.RawValue())) + key += "/" + c.Protocol().Name + default: + val := c.Value() + if val == "" { + val = c.Protocol().Name + } + key += "/" + val + } + return true + }) + + return key, port +} + +// clean up addrsplosion +// the following heuristic is used: +// - for each base address/protocol combination, if there are multiple ports advertised then +// only accept the default port if present. +// - If the default port is not present, we check for non-standard ports by tracking +// private port bindings if present. +// - If there is no default or private port binding, then we can't infer the correct +// port and give up and return all addrs (for that base address) +func sanitizeAddrsplodedSet(public, private []ma.Multiaddr) []ma.Multiaddr { + type portAndAddr struct { + addr ma.Multiaddr + port int + } + + privports := make(map[int]struct{}) + pubaddrs := make(map[string][]portAndAddr) + + for _, a := range private { + _, port := addrKeyAndPort(a) + privports[port] = struct{}{} + } + + for _, a := range public { + key, port := addrKeyAndPort(a) + pubaddrs[key] = append(pubaddrs[key], portAndAddr{addr: a, port: port}) + } + + var result []ma.Multiaddr + for _, pas := range pubaddrs { + if len(pas) == 1 { + result = append(result, pas[0].addr) + continue + } + + for _, pa := range pas { + if pa.port == 4001 || pa.port == 4002 { + result = append(result, pa.addr) + continue + } + if _, ok := privports[pa.port]; ok { + result = append(result, pa.addr) + } + } + } + + return result +} diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index a0d0523ef7..894548c34a 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/rand" - "strconv" "sync" "time" @@ -320,54 +319,6 @@ func (ar *AutoRelay) doUpdateAddrs() { ar.addrs = raddrs } -// This function cleans up a relay's address set to remove private addresses and curtail -// addrsplosion. For the latter, we use the following heuristic: -// - if the address set includes a (tcp) address with the default port 4001, -// we remove all tcp addrs with a different port -func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { - // pass-1: find default port - has4001 := false - for _, addr := range pi.Addrs { - port, err := tcpPort(addr) - if err != nil { - continue - } - if port == 4001 { - has4001 = true - break - } - } - - // pass-2: cleanup - var newAddrs []ma.Multiaddr - - for _, addr := range pi.Addrs { - if manet.IsPrivateAddr(addr) { - continue - } - - if has4001 { - port, err := tcpPort(addr) - if err == nil && port != 4001 { - continue - } - } - - newAddrs = append(newAddrs, addr) - } - - return pstore.PeerInfo{ID: pi.ID, Addrs: newAddrs} -} - -func tcpPort(addr ma.Multiaddr) (int, error) { - val, err := addr.ValueForProtocol(ma.P_TCP) - if err != nil { - // not tcp - return 0, err - } - return strconv.Atoi(val) -} - func shuffleRelays(pis []pstore.PeerInfo) { for i := range pis { j := rand.Intn(i + 1) @@ -375,6 +326,7 @@ func shuffleRelays(pis []pstore.PeerInfo) { } } +// Notifee func (ar *AutoRelay) Listen(inet.Network, ma.Multiaddr) {} func (ar *AutoRelay) ListenClose(inet.Network, ma.Multiaddr) {} func (ar *AutoRelay) Connected(inet.Network, inet.Conn) {} From a331f99b6588ca4b1fd23d229677e6513130e649 Mon Sep 17 00:00:00 2001 From: vyzo Date: Thu, 18 Apr 2019 14:42:31 +0300 Subject: [PATCH 25/42] addrsplosion test --- p2p/host/relay/addrsplosion_test.go | 98 +++++++++++++++++++++++++++++ p2p/host/relay/autorelay_test.go | 5 +- 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 p2p/host/relay/addrsplosion_test.go diff --git a/p2p/host/relay/addrsplosion_test.go b/p2p/host/relay/addrsplosion_test.go new file mode 100644 index 0000000000..0eb0c5be6c --- /dev/null +++ b/p2p/host/relay/addrsplosion_test.go @@ -0,0 +1,98 @@ +package relay + +import ( + "fmt" + "testing" + + pstore "github.com/libp2p/go-libp2p-peerstore" + ma "github.com/multiformats/go-multiaddr" + _ "github.com/multiformats/go-multiaddr-dns" +) + +func TestCleanupAddrs(t *testing.T) { + // test with no addrsplosion + addrs := makeAddrList( + "/ip4/127.0.0.1/tcp/4001", + "/ip4/127.0.0.1/udp/4002/quic", + "/ip4/1.2.3.4/tcp/4001", + "/ip4/1.2.3.4/udp/4002/quic", + "/dnsaddr/somedomain.com/tcp/4002/ws", + ) + clean := makeAddrList( + "/ip4/1.2.3.4/tcp/4001", + "/ip4/1.2.3.4/udp/4002/quic", + "/dnsaddr/somedomain.com/tcp/4002/ws", + ) + + pi := cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) + if !sameAddrs(clean, pi.Addrs) { + fmt.Println(pi.Addrs) + t.Fatal("cleaned up set doesn't match expected") + } + + // test with default port addrspolosion + addrs = makeAddrList( + "/ip4/127.0.0.1/tcp/4001", + "/ip4/1.2.3.4/tcp/4001", + "/ip4/1.2.3.4/tcp/33333", + "/ip4/1.2.3.4/tcp/33334", + "/ip4/1.2.3.4/tcp/33335", + "/ip4/1.2.3.4/udp/4002/quic", + ) + clean = makeAddrList( + "/ip4/1.2.3.4/tcp/4001", + "/ip4/1.2.3.4/udp/4002/quic", + ) + pi = cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) + if !sameAddrs(clean, pi.Addrs) { + t.Fatal("cleaned up set doesn't match expected") + } + + // test with non-standard port addrsplosion + addrs = makeAddrList( + "/ip4/127.0.0.1/tcp/12345", + "/ip4/1.2.3.4/tcp/12345", + "/ip4/1.2.3.4/tcp/33333", + "/ip4/1.2.3.4/tcp/33334", + "/ip4/1.2.3.4/tcp/33335", + ) + clean = makeAddrList( + "/ip4/1.2.3.4/tcp/12345", + ) + pi = cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) + if !sameAddrs(clean, pi.Addrs) { + t.Fatal("cleaned up set doesn't match expected") + } + +} + +func makeAddrList(strs ...string) []ma.Multiaddr { + result := make([]ma.Multiaddr, 0, len(strs)) + for _, s := range strs { + a := ma.StringCast(s) + result = append(result, a) + } + return result +} + +func sameAddrs(as, bs []ma.Multiaddr) bool { + if len(as) != len(bs) { + return false + } + + for _, a := range as { + if !findAddr(a, bs) { + return false + } + } + return true +} + +func findAddr(a ma.Multiaddr, bs []ma.Multiaddr) bool { + for _, b := range bs { + if a.Equal(b) { + return true + } + } + return false +} diff --git a/p2p/host/relay/autorelay_test.go b/p2p/host/relay/autorelay_test.go index cb661aa30b..d86fdf6b35 100644 --- a/p2p/host/relay/autorelay_test.go +++ b/p2p/host/relay/autorelay_test.go @@ -30,7 +30,6 @@ func init() { autonat.AutoNATBootDelay = 2 * time.Second relay.BootDelay = 1 * time.Second relay.AdvertiseBootDelay = 100 * time.Millisecond - manet.Private4 = []*net.IPNet{} } // mock routing @@ -147,6 +146,10 @@ func connect(t *testing.T, a, b host.Host) { func TestAutoRelay(t *testing.T) { //t.Skip("fails 99% of the time") + save := manet.Private4 + manet.Private4 = []*net.IPNet{} + defer func() { manet.Private4 = save }() + ctx, cancel := context.WithCancel(context.Background()) defer cancel() From e82eabe130d5cd2c545ba38efb0d905902aad6cd Mon Sep 17 00:00:00 2001 From: vyzo Date: Thu, 18 Apr 2019 14:49:42 +0300 Subject: [PATCH 26/42] cover the case where we can't select a default port in addrsplosion clean up --- p2p/host/relay/addrsplosion.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/p2p/host/relay/addrsplosion.go b/p2p/host/relay/addrsplosion.go index bdb1435412..a44401b108 100644 --- a/p2p/host/relay/addrsplosion.go +++ b/p2p/host/relay/addrsplosion.go @@ -118,16 +118,30 @@ func sanitizeAddrsplodedSet(public, private []ma.Multiaddr) []ma.Multiaddr { var result []ma.Multiaddr for _, pas := range pubaddrs { if len(pas) == 1 { + // it's not addrsploded result = append(result, pas[0].addr) continue } + haveAddr := false for _, pa := range pas { if pa.port == 4001 || pa.port == 4002 { + // it's a default port, use it result = append(result, pa.addr) + haveAddr = true continue } + if _, ok := privports[pa.port]; ok { + // it matches a privately bound port, use it + result = append(result, pa.addr) + haveAddr = true + } + } + + if !haveAddr { + // we weren't able to select a port; bite the bullet and use them all + for _, pa := range pas { result = append(result, pa.addr) } } From 9d7f6b83e5566ebbe379efaedae4f7096b4ef979 Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 19 Apr 2019 13:48:18 +0300 Subject: [PATCH 27/42] rewrite isDNSAddr to use ma.SplitFirst --- p2p/host/relay/addrsplosion.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/p2p/host/relay/addrsplosion.go b/p2p/host/relay/addrsplosion.go index a44401b108..3eca947b0c 100644 --- a/p2p/host/relay/addrsplosion.go +++ b/p2p/host/relay/addrsplosion.go @@ -34,17 +34,13 @@ func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { } func isDNSAddr(a ma.Multiaddr) bool { - dnsaddr := false - ma.ForEach(a, func(c ma.Component) bool { - switch c.Protocol().Code { + if first, _ := ma.SplitFirst(a); first != nil { + switch first.Protocol().Code { case 54, 55, 56: - dnsaddr = true + return true } - - return false - }) - - return dnsaddr + } + return false } // we have addrsplosion if for some protocol we advertise multiple ports on From 528c473840c7f24f93302133fd133fadd02cdcfc Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 19 Apr 2019 13:53:27 +0300 Subject: [PATCH 28/42] filter relay addrs in address set cleanup --- p2p/host/relay/addrsplosion.go | 21 +++++++++++++++++++++ p2p/host/relay/relay.go | 4 +--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/p2p/host/relay/addrsplosion.go b/p2p/host/relay/addrsplosion.go index 3eca947b0c..51617a3b80 100644 --- a/p2p/host/relay/addrsplosion.go +++ b/p2p/host/relay/addrsplosion.go @@ -3,6 +3,7 @@ package relay import ( "encoding/binary" + circuit "github.com/libp2p/go-libp2p-circuit" pstore "github.com/libp2p/go-libp2p-peerstore" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" @@ -14,6 +15,10 @@ func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { var public, private []ma.Multiaddr for _, a := range pi.Addrs { + if isRelayAddr(a) { + continue + } + if manet.IsPublicAddr(a) || isDNSAddr(a) { public = append(public, a) continue @@ -33,6 +38,22 @@ func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { return pstore.PeerInfo{ID: pi.ID, Addrs: addrs} } +func isRelayAddr(a ma.Multiaddr) bool { + isRelay := false + + ma.ForEach(a, func(c ma.Component) bool { + switch c.Protocol().Code { + case circuit.P_CIRCUIT: + isRelay = true + return false + default: + return true + } + }) + + return isRelay +} + func isDNSAddr(a ma.Multiaddr) bool { if first, _ := ma.SplitFirst(a); first != nil { switch first.Protocol().Code { diff --git a/p2p/host/relay/relay.go b/p2p/host/relay/relay.go index d0af9d227a..2adea8fcc6 100644 --- a/p2p/host/relay/relay.go +++ b/p2p/host/relay/relay.go @@ -4,7 +4,6 @@ import ( "context" "time" - circuit "github.com/libp2p/go-libp2p-circuit" discovery "github.com/libp2p/go-libp2p-discovery" ma "github.com/multiformats/go-multiaddr" ) @@ -29,8 +28,7 @@ func Advertise(ctx context.Context, advertise discovery.Advertiser) { func Filter(addrs []ma.Multiaddr) []ma.Multiaddr { raddrs := make([]ma.Multiaddr, 0, len(addrs)) for _, addr := range addrs { - _, err := addr.ValueForProtocol(circuit.P_CIRCUIT) - if err == nil { + if isRelayAddr(addr) { continue } raddrs = append(raddrs, addr) From 21c4e1d298be6601a4921b20cec963a5ea8d8adf Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 19 Apr 2019 13:58:10 +0300 Subject: [PATCH 29/42] test for privately bound port first when cleaning up addrsplosion --- p2p/host/relay/addrsplosion.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/host/relay/addrsplosion.go b/p2p/host/relay/addrsplosion.go index 51617a3b80..3b114a312a 100644 --- a/p2p/host/relay/addrsplosion.go +++ b/p2p/host/relay/addrsplosion.go @@ -142,15 +142,15 @@ func sanitizeAddrsplodedSet(public, private []ma.Multiaddr) []ma.Multiaddr { haveAddr := false for _, pa := range pas { - if pa.port == 4001 || pa.port == 4002 { - // it's a default port, use it + if _, ok := privports[pa.port]; ok { + // it matches a privately bound port, use it result = append(result, pa.addr) haveAddr = true continue } - if _, ok := privports[pa.port]; ok { - // it matches a privately bound port, use it + if pa.port == 4001 || pa.port == 4002 { + // it's a default port, use it result = append(result, pa.addr) haveAddr = true } From ff4b98a6fc70332a64d0a654a47f7c6ae218762a Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 19 Apr 2019 13:58:45 +0300 Subject: [PATCH 30/42] some more addrsplosion tests --- p2p/host/relay/addrsplosion_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/p2p/host/relay/addrsplosion_test.go b/p2p/host/relay/addrsplosion_test.go index 0eb0c5be6c..9104fc3dde 100644 --- a/p2p/host/relay/addrsplosion_test.go +++ b/p2p/host/relay/addrsplosion_test.go @@ -48,6 +48,23 @@ func TestCleanupAddrs(t *testing.T) { t.Fatal("cleaned up set doesn't match expected") } + // test with default port addrsplosion but no private addrs + addrs = makeAddrList( + "/ip4/1.2.3.4/tcp/4001", + "/ip4/1.2.3.4/tcp/33333", + "/ip4/1.2.3.4/tcp/33334", + "/ip4/1.2.3.4/tcp/33335", + "/ip4/1.2.3.4/udp/4002/quic", + ) + clean = makeAddrList( + "/ip4/1.2.3.4/tcp/4001", + "/ip4/1.2.3.4/udp/4002/quic", + ) + pi = cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) + if !sameAddrs(clean, pi.Addrs) { + t.Fatal("cleaned up set doesn't match expected") + } + // test with non-standard port addrsplosion addrs = makeAddrList( "/ip4/127.0.0.1/tcp/12345", @@ -64,6 +81,16 @@ func TestCleanupAddrs(t *testing.T) { t.Fatal("cleaned up set doesn't match expected") } + // test with a squeaky clean address set + addrs = makeAddrList( + "/ip4/1.2.3.4/tcp/4001", + "/ip4/1.2.3.4/udp/4001/quic", + ) + clean = addrs + pi = cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) + if !sameAddrs(clean, pi.Addrs) { + t.Fatal("cleaned up set doesn't match expected") + } } func makeAddrList(strs ...string) []ma.Multiaddr { From a8d14f9b025d3073c60938257735e60ff8e23b89 Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 19 Apr 2019 14:09:56 +0300 Subject: [PATCH 31/42] use addresses from the peerstore if available --- p2p/host/relay/autorelay.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 894548c34a..b5816683b2 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -240,6 +240,12 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co // shuffle to randomize the order of queries shuffleRelays(pis) for _, pi := range pis[:maxq] { + // first check to see if we already know this peer from a previous query + addrs := ar.host.Peerstore().Addrs(pi.ID) + if len(addrs) > 0 { + resultCh <- queryResult{pi: pstore.PeerInfo{ID: pi.ID, Addrs: addrs}, err: nil} + continue + } go func(p peer.ID) { pi, err := ar.router.FindPeer(qctx, p) if err != nil { From 9b2731e5bbb495a76eb0f1a784c40d651c5f6a3e Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 19 Apr 2019 19:48:28 +0300 Subject: [PATCH 32/42] used named constants for dns address protocols --- p2p/host/relay/addrsplosion.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/host/relay/addrsplosion.go b/p2p/host/relay/addrsplosion.go index 3b114a312a..a36a01f63b 100644 --- a/p2p/host/relay/addrsplosion.go +++ b/p2p/host/relay/addrsplosion.go @@ -6,6 +6,7 @@ import ( circuit "github.com/libp2p/go-libp2p-circuit" pstore "github.com/libp2p/go-libp2p-peerstore" ma "github.com/multiformats/go-multiaddr" + dns "github.com/multiformats/go-multiaddr-dns" manet "github.com/multiformats/go-multiaddr-net" ) @@ -57,7 +58,7 @@ func isRelayAddr(a ma.Multiaddr) bool { func isDNSAddr(a ma.Multiaddr) bool { if first, _ := ma.SplitFirst(a); first != nil { switch first.Protocol().Code { - case 54, 55, 56: + case dns.P_DNS4, dns.P_DNS6, dns.P_DNSADDR: return true } } From bd22c49b0d380e789369a329eb7c638f3a5375a3 Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 19 Apr 2019 20:49:54 +0300 Subject: [PATCH 33/42] remove redundant private addr check when constructing our relay address set findRelays cleans up address sets now for addrsplosion, and this removes private addrs as well. --- p2p/host/relay/autorelay.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index b5816683b2..127ef99b66 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -315,10 +315,8 @@ func (ar *AutoRelay) doUpdateAddrs() { } for _, addr := range pi.Addrs { - if !manet.IsPrivateAddr(addr) { - pub := addr.Encapsulate(circuit) - raddrs = append(raddrs, pub) - } + pub := addr.Encapsulate(circuit) + raddrs = append(raddrs, pub) } } From f4f924e1d4bd28007ef1ff4e07a9684af0fa8992 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 00:04:26 +0300 Subject: [PATCH 34/42] don't track relay addrs, use the peerstore --- p2p/host/relay/addrsplosion.go | 10 ++++------ p2p/host/relay/addrsplosion_test.go | 23 ++++++++++------------- p2p/host/relay/autorelay.go | 21 +++++++++------------ 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/p2p/host/relay/addrsplosion.go b/p2p/host/relay/addrsplosion.go index a36a01f63b..89965bca41 100644 --- a/p2p/host/relay/addrsplosion.go +++ b/p2p/host/relay/addrsplosion.go @@ -4,7 +4,6 @@ import ( "encoding/binary" circuit "github.com/libp2p/go-libp2p-circuit" - pstore "github.com/libp2p/go-libp2p-peerstore" ma "github.com/multiformats/go-multiaddr" dns "github.com/multiformats/go-multiaddr-dns" manet "github.com/multiformats/go-multiaddr-net" @@ -12,10 +11,10 @@ import ( // This function cleans up a relay's address set to remove private addresses and curtail // addrsplosion. -func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { +func cleanupAddressSet(addrs []ma.Multiaddr) []ma.Multiaddr { var public, private []ma.Multiaddr - for _, a := range pi.Addrs { + for _, a := range addrs { if isRelayAddr(a) { continue } @@ -32,11 +31,10 @@ func cleanupAddressSet(pi pstore.PeerInfo) pstore.PeerInfo { } if !hasAddrsplosion(public) { - return pstore.PeerInfo{ID: pi.ID, Addrs: public} + return public } - addrs := sanitizeAddrsplodedSet(public, private) - return pstore.PeerInfo{ID: pi.ID, Addrs: addrs} + return sanitizeAddrsplodedSet(public, private) } func isRelayAddr(a ma.Multiaddr) bool { diff --git a/p2p/host/relay/addrsplosion_test.go b/p2p/host/relay/addrsplosion_test.go index 9104fc3dde..cdb10def63 100644 --- a/p2p/host/relay/addrsplosion_test.go +++ b/p2p/host/relay/addrsplosion_test.go @@ -1,10 +1,8 @@ package relay import ( - "fmt" "testing" - pstore "github.com/libp2p/go-libp2p-peerstore" ma "github.com/multiformats/go-multiaddr" _ "github.com/multiformats/go-multiaddr-dns" ) @@ -24,9 +22,8 @@ func TestCleanupAddrs(t *testing.T) { "/dnsaddr/somedomain.com/tcp/4002/ws", ) - pi := cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) - if !sameAddrs(clean, pi.Addrs) { - fmt.Println(pi.Addrs) + r := cleanupAddressSet(addrs) + if !sameAddrs(clean, r) { t.Fatal("cleaned up set doesn't match expected") } @@ -43,8 +40,8 @@ func TestCleanupAddrs(t *testing.T) { "/ip4/1.2.3.4/tcp/4001", "/ip4/1.2.3.4/udp/4002/quic", ) - pi = cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) - if !sameAddrs(clean, pi.Addrs) { + r = cleanupAddressSet(addrs) + if !sameAddrs(clean, r) { t.Fatal("cleaned up set doesn't match expected") } @@ -60,8 +57,8 @@ func TestCleanupAddrs(t *testing.T) { "/ip4/1.2.3.4/tcp/4001", "/ip4/1.2.3.4/udp/4002/quic", ) - pi = cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) - if !sameAddrs(clean, pi.Addrs) { + r = cleanupAddressSet(addrs) + if !sameAddrs(clean, r) { t.Fatal("cleaned up set doesn't match expected") } @@ -76,8 +73,8 @@ func TestCleanupAddrs(t *testing.T) { clean = makeAddrList( "/ip4/1.2.3.4/tcp/12345", ) - pi = cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) - if !sameAddrs(clean, pi.Addrs) { + r = cleanupAddressSet(addrs) + if !sameAddrs(clean, r) { t.Fatal("cleaned up set doesn't match expected") } @@ -87,8 +84,8 @@ func TestCleanupAddrs(t *testing.T) { "/ip4/1.2.3.4/udp/4001/quic", ) clean = addrs - pi = cleanupAddressSet(pstore.PeerInfo{Addrs: addrs}) - if !sameAddrs(clean, pi.Addrs) { + r = cleanupAddressSet(addrs) + if !sameAddrs(clean, r) { t.Fatal("cleaned up set doesn't match expected") } } diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 127ef99b66..5a88f1541c 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -41,7 +41,7 @@ type AutoRelay struct { disconnect chan struct{} mx sync.Mutex - relays map[peer.ID]pstore.PeerInfo + relays map[peer.ID]struct{} addrs []ma.Multiaddr } @@ -51,7 +51,7 @@ func NewAutoRelay(ctx context.Context, bhost *basic.BasicHost, discover discover discover: discover, router: router, addrsF: bhost.AddrsFactory, - relays: make(map[peer.ID]pstore.PeerInfo), + relays: make(map[peer.ID]struct{}), disconnect: make(chan struct{}, 1), } ar.autonat = autonat.NewAutoNAT(ctx, bhost, ar.baseAddrs) @@ -179,7 +179,7 @@ again: log.Debugf("connected to relay %s", pi.ID) ar.mx.Lock() - ar.relays[pi.ID] = pi + ar.relays[pi.ID] = struct{}{} haveRelays++ ar.mx.Unlock() @@ -261,12 +261,7 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co case qr := <-resultCh: rcount++ if qr.err == nil { - pi := cleanupAddressSet(qr.pi) - if len(pi.Addrs) > 0 { - result = append(result, pi) - } else { - log.Debugf("ignoring relay peer %s: cleaned up address set is empty", pi.ID) - } + result = append(result, qr.pi) } case <-qctx.Done(): @@ -308,13 +303,15 @@ func (ar *AutoRelay) doUpdateAddrs() { } // add relay specific addrs to the list - for _, pi := range ar.relays { - circuit, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s/p2p-circuit", pi.ID.Pretty())) + for p := range ar.relays { + addrs := cleanupAddressSet(ar.host.Peerstore().Addrs(p)) + + circuit, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s/p2p-circuit", p.Pretty())) if err != nil { panic(err) } - for _, addr := range pi.Addrs { + for _, addr := range addrs { pub := addr.Encapsulate(circuit) raddrs = append(raddrs, pub) } From c09717275bf5ea10eceef25971769233b8087d0c Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 00:54:44 +0300 Subject: [PATCH 35/42] compute relay address set dynamically --- p2p/host/relay/autorelay.go | 117 ++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 64 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 5a88f1541c..dc811dd8cc 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -42,7 +42,7 @@ type AutoRelay struct { mx sync.Mutex relays map[peer.ID]struct{} - addrs []ma.Multiaddr + status autonat.NATStatus } func NewAutoRelay(ctx context.Context, bhost *basic.BasicHost, discover discovery.Discoverer, router routing.PeerRouting) *AutoRelay { @@ -53,6 +53,7 @@ func NewAutoRelay(ctx context.Context, bhost *basic.BasicHost, discover discover addrsF: bhost.AddrsFactory, relays: make(map[peer.ID]struct{}), disconnect: make(chan struct{}, 1), + status: autonat.NATStatusUnknown, } ar.autonat = autonat.NewAutoNAT(ctx, bhost, ar.baseAddrs) bhost.AddrsFactory = ar.hostAddrs @@ -61,20 +62,14 @@ func NewAutoRelay(ctx context.Context, bhost *basic.BasicHost, discover discover return ar } -func (ar *AutoRelay) hostAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { - ar.mx.Lock() - defer ar.mx.Unlock() - if ar.addrs != nil && ar.autonat.Status() == autonat.NATStatusPrivate { - return ar.addrs - } else { - return ar.addrsF(addrs) - } -} - func (ar *AutoRelay) baseAddrs() []ma.Multiaddr { return ar.addrsF(ar.host.AllAddrs()) } +func (ar *AutoRelay) hostAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { + return ar.relayAddrs(ar.addrsF(addrs)) +} + func (ar *AutoRelay) background(ctx context.Context) { select { case <-time.After(autonat.AutoNATBootDelay + BootDelay): @@ -89,37 +84,37 @@ func (ar *AutoRelay) background(ctx context.Context) { wait := autonat.AutoNATRefreshInterval switch ar.autonat.Status() { case autonat.NATStatusUnknown: + ar.mx.Lock() + ar.status = autonat.NATStatusUnknown + ar.mx.Unlock() wait = autonat.AutoNATRetryInterval case autonat.NATStatusPublic: - // invalidate addrs ar.mx.Lock() - if ar.addrs != nil { - ar.addrs = nil + if ar.status != autonat.NATStatusPublic { push = true } + ar.status = autonat.NATStatusPublic ar.mx.Unlock() - // if we had previously announced relay addrs, push our public addrs - if push { - push = false - ar.host.PushIdentify() + case autonat.NATStatusPrivate: + update := ar.findRelays(ctx) + ar.mx.Lock() + if update || ar.status != autonat.NATStatusPrivate { + push = true } + ar.status = autonat.NATStatusPrivate + ar.mx.Unlock() + } - case autonat.NATStatusPrivate: - push = false // clear, findRelays pushes as needed - ar.findRelays(ctx) + if push { + push = false + ar.host.PushIdentify() } select { case <-ar.disconnect: - // invalidate addrs - ar.mx.Lock() - if ar.addrs != nil { - ar.addrs = nil - push = true - } - ar.mx.Unlock() + push = true case <-time.After(wait): case <-ctx.Done(): return @@ -127,21 +122,13 @@ func (ar *AutoRelay) background(ctx context.Context) { } } -func (ar *AutoRelay) findRelays(ctx context.Context) { +func (ar *AutoRelay) findRelays(ctx context.Context) bool { again: ar.mx.Lock() haveRelays := len(ar.relays) if haveRelays >= DesiredRelays { ar.mx.Unlock() - // this dance is necessary to cover the Private->Public->Private transition - // where we were already connected to enough relays while private and dropped - // the addrs while public. - // Note tht we don't need the lock for reading addrs, as the only writer is - // the current goroutine. - if ar.addrs == nil { - ar.updateAddrs() - } - return + return false } need := DesiredRelays - len(ar.relays) ar.mx.Unlock() @@ -153,7 +140,16 @@ again: cancel() if err != nil { log.Debugf("error discovering relays: %s", err.Error()) - return + + if haveRelays == 0 { + log.Debug("no relays connected; retrying in 30s") + select { + case <-time.After(30 * time.Second): + goto again + case <-ctx.Done(): + return false + } + } } log.Debugf("discovered %d relays", len(pis)) @@ -201,13 +197,11 @@ again: case <-time.After(30 * time.Second): goto again case <-ctx.Done(): - return + return false } } - if update > 0 || ar.addrs == nil { - ar.updateAddrs() - } + return update > 0 } func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, count, maxq int) []pstore.PeerInfo { @@ -273,33 +267,28 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co return result } -func (ar *AutoRelay) updateAddrs() { - ar.doUpdateAddrs() - ar.host.PushIdentify() -} - -// This function updates our NATed advertised addrs (ar.addrs) -// The public addrs are rewritten so that they only retain the public IP part; they -// become undialable but are useful as a hint to the dialer to determine whether or not -// to dial private addrs. -// The non-public addrs are included verbatim so that peers behind the same NAT/firewall -// can still dial us directly. -// On top of those, we add the relay-specific addrs for the relays to which we are -// connected. For each non-private relay addr, we encapsulate the p2p-circuit addr -// through which we can be dialed. -func (ar *AutoRelay) doUpdateAddrs() { +// This function is computes the NATed relay addrs when our status is private: +// - The public addrs are removed from the address set. +// - The non-public addrs are included verbatim so that peers behind the same NAT/firewall +// can still dial us directly. +// - On top of those, we add the relay-specific addrs for the relays to which we are +// connected. For each non-private relay addr, we encapsulate the p2p-circuit addr +// through which we can be dialed. +func (ar *AutoRelay) relayAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { ar.mx.Lock() defer ar.mx.Unlock() - addrs := ar.baseAddrs() + if ar.status != autonat.NATStatusPrivate { + return addrs + } + raddrs := make([]ma.Multiaddr, 0, len(addrs)+len(ar.relays)) - // remove our public addresses from the list + // only keep private addrs from the original addr set for _, addr := range addrs { - if manet.IsPublicAddr(addr) { - continue + if manet.IsPrivateAddr(addr) { + raddrs = append(raddrs, addr) } - raddrs = append(raddrs, addr) } // add relay specific addrs to the list @@ -317,7 +306,7 @@ func (ar *AutoRelay) doUpdateAddrs() { } } - ar.addrs = raddrs + return raddrs } func shuffleRelays(pis []pstore.PeerInfo) { From 4727d5b8490438869d9f90edb2ebdbf8acce5032 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 01:40:29 +0300 Subject: [PATCH 36/42] don't preallocate result array, we don't know how long it will be --- p2p/host/relay/autorelay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index dc811dd8cc..73e126a2d7 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -282,7 +282,7 @@ func (ar *AutoRelay) relayAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { return addrs } - raddrs := make([]ma.Multiaddr, 0, len(addrs)+len(ar.relays)) + var raddrs []ma.Multiaddr // only keep private addrs from the original addr set for _, addr := range addrs { From 5c9299a45cec4d334c5440273a2e048b01afb04a Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 01:47:41 +0300 Subject: [PATCH 37/42] pacify the race detector --- p2p/host/relay/autorelay_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/p2p/host/relay/autorelay_test.go b/p2p/host/relay/autorelay_test.go index d86fdf6b35..ff3d7601ab 100644 --- a/p2p/host/relay/autorelay_test.go +++ b/p2p/host/relay/autorelay_test.go @@ -146,9 +146,7 @@ func connect(t *testing.T, a, b host.Host) { func TestAutoRelay(t *testing.T) { //t.Skip("fails 99% of the time") - save := manet.Private4 manet.Private4 = []*net.IPNet{} - defer func() { manet.Private4 = save }() ctx, cancel := context.WithCancel(context.Background()) defer cancel() From 1a8111970b8d45b6eee8151d209735878ebcfb24 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 06:25:37 +0300 Subject: [PATCH 38/42] reduce scope of the lock, pre-allocate result slice in relayAddrs --- p2p/host/relay/autorelay.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 73e126a2d7..02f2fff8a2 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -276,13 +276,18 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co // through which we can be dialed. func (ar *AutoRelay) relayAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { ar.mx.Lock() - defer ar.mx.Unlock() - if ar.status != autonat.NATStatusPrivate { + ar.mx.Unlock() return addrs } - var raddrs []ma.Multiaddr + relays := make([]peer.ID, 0, len(ar.relays)) + for p := range ar.relays { + relays = append(relays, p) + } + ar.mx.Unlock() + + raddrs := make([]ma.Multiaddr, 0, 4*len(relays)+2) // only keep private addrs from the original addr set for _, addr := range addrs { @@ -292,7 +297,7 @@ func (ar *AutoRelay) relayAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { } // add relay specific addrs to the list - for p := range ar.relays { + for _, p := range relays { addrs := cleanupAddressSet(ar.host.Peerstore().Addrs(p)) circuit, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s/p2p-circuit", p.Pretty())) From f9e182f7477adeca2b7cb51796b5621ceabbe17a Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 06:31:51 +0300 Subject: [PATCH 39/42] gate max number of retries in findRelays --- p2p/host/relay/autorelay.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 02f2fff8a2..ad54e4d901 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -123,6 +123,8 @@ func (ar *AutoRelay) background(ctx context.Context) { } func (ar *AutoRelay) findRelays(ctx context.Context) bool { + retry := 0 + again: ar.mx.Lock() haveRelays := len(ar.relays) @@ -142,6 +144,12 @@ again: log.Debugf("error discovering relays: %s", err.Error()) if haveRelays == 0 { + retry++ + if retry > 5 { + log.Debug("no relays connected; giving up") + return false + } + log.Debug("no relays connected; retrying in 30s") select { case <-time.After(30 * time.Second): @@ -192,6 +200,12 @@ again: if haveRelays == 0 { // we failed to find any relays and we are not connected to any! // wait a little and try again, the discovery query might have returned only dead peers + retry++ + if retry > 5 { + log.Debug("no relays connected; giving up") + return false + } + log.Debug("no relays connected; retrying in 30s") select { case <-time.After(30 * time.Second): From 4629431a12fcf04e218648240fa9deb6746b4299 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 11:17:10 +0300 Subject: [PATCH 40/42] some tweaks - select 25 of 50 relays instead of 20 - increase connect timeout to 30s --- p2p/host/relay/autorelay.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index ad54e4d901..1de60f507a 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -162,7 +162,7 @@ again: log.Debugf("discovered %d relays", len(pis)) - pis = ar.selectRelays(ctx, pis, 20, 50) + pis = ar.selectRelays(ctx, pis, 25, 50) update := 0 for _, pi := range pis { @@ -173,7 +173,7 @@ again: } ar.mx.Unlock() - cctx, cancel := context.WithTimeout(ctx, 15*time.Second) + cctx, cancel := context.WithTimeout(ctx, 30*time.Second) err = ar.host.Connect(cctx, pi) cancel() if err != nil { @@ -254,6 +254,8 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co resultCh <- queryResult{pi: pstore.PeerInfo{ID: pi.ID, Addrs: addrs}, err: nil} continue } + + // no known addrs, do a query go func(p peer.ID) { pi, err := ar.router.FindPeer(qctx, p) if err != nil { From 35e805dc4120981d3ed9cba9a276bf559cdf36cb Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 11:50:06 +0300 Subject: [PATCH 41/42] add ignore list to account for connection failures --- p2p/host/relay/autorelay.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index 1de60f507a..dcaa239b71 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -123,17 +123,17 @@ func (ar *AutoRelay) background(ctx context.Context) { } func (ar *AutoRelay) findRelays(ctx context.Context) bool { + ignore := make(map[peer.ID]struct{}) retry := 0 again: ar.mx.Lock() haveRelays := len(ar.relays) + ar.mx.Unlock() if haveRelays >= DesiredRelays { - ar.mx.Unlock() return false } - need := DesiredRelays - len(ar.relays) - ar.mx.Unlock() + need := DesiredRelays - haveRelays limit := 1000 @@ -162,7 +162,7 @@ again: log.Debugf("discovered %d relays", len(pis)) - pis = ar.selectRelays(ctx, pis, 25, 50) + pis = ar.selectRelays(ctx, ignore, pis, 25, 50) update := 0 for _, pi := range pis { @@ -178,6 +178,7 @@ again: cancel() if err != nil { log.Debugf("error connecting to relay %s: %s", pi.ID, err.Error()) + ignore[pi.ID] = struct{}{} continue } @@ -206,6 +207,12 @@ again: return false } + // if we failed to select any relays, clear the ignore list as some of the dead + // may have come back + if len(pis) == 0 { + ignore = make(map[peer.ID]struct{}) + } + log.Debug("no relays connected; retrying in 30s") select { case <-time.After(30 * time.Second): @@ -218,7 +225,7 @@ again: return update > 0 } -func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, count, maxq int) []pstore.PeerInfo { +func (ar *AutoRelay) selectRelays(ctx context.Context, ignore map[peer.ID]struct{}, pis []pstore.PeerInfo, count, maxq int) []pstore.PeerInfo { // TODO better relay selection strategy; this just selects random relays // but we should probably use ping latency as the selection metric @@ -248,7 +255,13 @@ func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo, co // shuffle to randomize the order of queries shuffleRelays(pis) for _, pi := range pis[:maxq] { - // first check to see if we already know this peer from a previous query + // check if it is in the ignore list + if _, ok := ignore[pi.ID]; ok { + maxq-- // decrement this for the result collection loop, we are doing one less query + continue + } + + // check to see if we already know this peer from a previous query addrs := ar.host.Peerstore().Addrs(pi.ID) if len(addrs) > 0 { resultCh <- queryResult{pi: pstore.PeerInfo{ID: pi.ID, Addrs: addrs}, err: nil} From 8d073cec9e0d0ac70f46ae60d109ed8b7edc23a6 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 20 Apr 2019 13:10:28 +0300 Subject: [PATCH 42/42] kill the parallel query logic in selectRelays; let it be random the presence of stashed query results from discovery in the peerstore _biases_ the selection towards fully DHT nodes, which penalizes dedicated relays. --- p2p/host/relay/autorelay.go | 89 ++++++------------------------------- 1 file changed, 14 insertions(+), 75 deletions(-) diff --git a/p2p/host/relay/autorelay.go b/p2p/host/relay/autorelay.go index dcaa239b71..0c8b681256 100644 --- a/p2p/host/relay/autorelay.go +++ b/p2p/host/relay/autorelay.go @@ -123,7 +123,6 @@ func (ar *AutoRelay) background(ctx context.Context) { } func (ar *AutoRelay) findRelays(ctx context.Context) bool { - ignore := make(map[peer.ID]struct{}) retry := 0 again: @@ -162,7 +161,7 @@ again: log.Debugf("discovered %d relays", len(pis)) - pis = ar.selectRelays(ctx, ignore, pis, 25, 50) + pis = ar.selectRelays(ctx, pis) update := 0 for _, pi := range pis { @@ -173,12 +172,21 @@ again: } ar.mx.Unlock() - cctx, cancel := context.WithTimeout(ctx, 30*time.Second) + cctx, cancel := context.WithTimeout(ctx, 60*time.Second) + + if len(pi.Addrs) == 0 { + pi, err = ar.router.FindPeer(cctx, pi.ID) + if err != nil { + log.Debugf("error finding relay peer %s: %s", pi.ID, err.Error()) + cancel() + continue + } + } + err = ar.host.Connect(cctx, pi) cancel() if err != nil { log.Debugf("error connecting to relay %s: %s", pi.ID, err.Error()) - ignore[pi.ID] = struct{}{} continue } @@ -207,12 +215,6 @@ again: return false } - // if we failed to select any relays, clear the ignore list as some of the dead - // may have come back - if len(pis) == 0 { - ignore = make(map[peer.ID]struct{}) - } - log.Debug("no relays connected; retrying in 30s") select { case <-time.After(30 * time.Second): @@ -225,75 +227,12 @@ again: return update > 0 } -func (ar *AutoRelay) selectRelays(ctx context.Context, ignore map[peer.ID]struct{}, pis []pstore.PeerInfo, count, maxq int) []pstore.PeerInfo { +func (ar *AutoRelay) selectRelays(ctx context.Context, pis []pstore.PeerInfo) []pstore.PeerInfo { // TODO better relay selection strategy; this just selects random relays // but we should probably use ping latency as the selection metric - if len(pis) == 0 { - return pis - } - - if len(pis) < count { - count = len(pis) - } - - if len(pis) < maxq { - maxq = len(pis) - } - - // only select relays that can be found by routing - type queryResult struct { - pi pstore.PeerInfo - err error - } - result := make([]pstore.PeerInfo, 0, count) - resultCh := make(chan queryResult, maxq) - - qctx, cancel := context.WithTimeout(ctx, 30*time.Second) - defer cancel() - - // shuffle to randomize the order of queries shuffleRelays(pis) - for _, pi := range pis[:maxq] { - // check if it is in the ignore list - if _, ok := ignore[pi.ID]; ok { - maxq-- // decrement this for the result collection loop, we are doing one less query - continue - } - - // check to see if we already know this peer from a previous query - addrs := ar.host.Peerstore().Addrs(pi.ID) - if len(addrs) > 0 { - resultCh <- queryResult{pi: pstore.PeerInfo{ID: pi.ID, Addrs: addrs}, err: nil} - continue - } - - // no known addrs, do a query - go func(p peer.ID) { - pi, err := ar.router.FindPeer(qctx, p) - if err != nil { - log.Debugf("error finding relay peer %s: %s", p, err.Error()) - } - resultCh <- queryResult{pi: pi, err: err} - }(pi.ID) - } - - rcount := 0 - for len(result) < count && rcount < maxq { - select { - case qr := <-resultCh: - rcount++ - if qr.err == nil { - result = append(result, qr.pi) - } - - case <-qctx.Done(): - break - } - } - - shuffleRelays(result) - return result + return pis } // This function is computes the NATed relay addrs when our status is private: