From fbf11bc5c0a911a667114241c061b0a460a294f7 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Wed, 12 Apr 2023 14:44:48 -0700 Subject: [PATCH] Infer public webtransport addrs from quic-v1 addrs. (#2251) * Infer external webtransport addrs from external quic addrs * Infer external webtransport addrs from external quic addrs is now pure * Remove ConnManager method * Nits * Dedupe addrs * Revert "Dedupe addrs" This reverts commit b63155f4a3997d7adfebc337d0262defca329c15. * Assume input is deduped --- p2p/host/basic/basic_host.go | 78 ++++++++++++++++++++++++++++++- p2p/host/basic/basic_host_test.go | 71 ++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/p2p/host/basic/basic_host.go b/p2p/host/basic/basic_host.go index 7cd97663e5..ce82ad331d 100644 --- a/p2p/host/basic/basic_host.go +++ b/p2p/host/basic/basic_host.go @@ -769,6 +769,7 @@ func (h *BasicHost) Addrs() []ma.Multiaddr { type transportForListeninger interface { TransportForListening(a ma.Multiaddr) transport.Transport } + type addCertHasher interface { AddCertHashes(m ma.Multiaddr) ma.Multiaddr } @@ -1005,8 +1006,83 @@ func (h *BasicHost) AllAddrs() []ma.Multiaddr { } finalAddrs = append(finalAddrs, observedAddrs...) } + finalAddrs = dedupAddrs(finalAddrs) + finalAddrs = inferWebtransportAddrsFromQuic(finalAddrs) + + return finalAddrs +} + +var wtComponent = ma.StringCast("/webtransport") + +// inferWebtransportAddrsFromQuic infers more webtransport addresses from QUIC addresses. +// This is useful when we discover our public QUIC address, but haven't discovered our public WebTransport addrs. +// If we see that we are listening on the same port for QUIC and WebTransport, +// we can be pretty sure that the WebTransport addr will be reachable if the +// QUIC one is. +// We assume the input is deduped. +func inferWebtransportAddrsFromQuic(in []ma.Multiaddr) []ma.Multiaddr { + // We need to check if we are listening on the same ip+port for QUIC and WebTransport. + // If not, there's nothing to do since we can't infer anything. + + // Count the number of QUIC addrs, this will let us allocate just once at the beginning. + quicAddrCount := 0 + for _, addr := range in { + if _, lastComponent := ma.SplitLast(addr); lastComponent.Protocol().Code == ma.P_QUIC_V1 { + quicAddrCount++ + } + } + quicOrWebtransportAddrs := make(map[string]struct{}, quicAddrCount) + webtransportAddrs := make(map[string]struct{}, quicAddrCount) + foundSameListeningAddr := false + for _, addr := range in { + isWebtransport, numCertHashes := libp2pwebtransport.IsWebtransportMultiaddr(addr) + if isWebtransport { + for i := 0; i < numCertHashes; i++ { + // Remove certhashes + addr, _ = ma.SplitLast(addr) + } + webtransportAddrs[addr.String()] = struct{}{} + // Remove webtransport component, now it's a multiaddr that ends in /quic-v1 + addr, _ = ma.SplitLast(addr) + } + + if _, lastComponent := ma.SplitLast(addr); lastComponent.Protocol().Code == ma.P_QUIC_V1 { + addrStr := addr.String() + if _, ok := quicOrWebtransportAddrs[addrStr]; ok { + foundSameListeningAddr = true + } else { + quicOrWebtransportAddrs[addrStr] = struct{}{} + } + } + } + + if !foundSameListeningAddr { + return in + } + + if len(webtransportAddrs) == 0 { + // No webtransport addresses, we aren't listening on any webtransport + // address, so we shouldn't add any. + return in + } + + out := make([]ma.Multiaddr, 0, len(in)+(quicAddrCount-len(webtransportAddrs))) + for _, addr := range in { + // Add all the original addresses + out = append(out, addr) + if _, lastComponent := ma.SplitLast(addr); lastComponent.Protocol().Code == ma.P_QUIC_V1 { + // Convert quic to webtransport + addr = addr.Encapsulate(wtComponent) + if _, ok := webtransportAddrs[addr.String()]; ok { + // We already have this address + continue + } + // Add the new inferred address + out = append(out, addr) + } + } - return dedupAddrs(finalAddrs) + return out } // SetAutoNat sets the autonat service for the host. diff --git a/p2p/host/basic/basic_host_test.go b/p2p/host/basic/basic_host_test.go index c09df46895..19ea2bd02f 100644 --- a/p2p/host/basic/basic_host_test.go +++ b/p2p/host/basic/basic_host_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "reflect" + "sort" "strings" "sync" "testing" @@ -849,3 +850,73 @@ func TestDedupAddrs(t *testing.T) { }) } } + +func TestInferWebtransportAddrsFromQuic(t *testing.T) { + type testCase struct { + name string + in []string + out []string + } + + testCases := []testCase{ + { + name: "Happy Path", + in: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/0.0.0.0/udp/9999/quic-v1/webtransport", "/ip4/1.2.3.4/udp/9999/quic-v1"}, + out: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/0.0.0.0/udp/9999/quic-v1/webtransport", "/ip4/1.2.3.4/udp/9999/quic-v1", "/ip4/1.2.3.4/udp/9999/quic-v1/webtransport"}, + }, + { + name: "Already discovered", + in: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/0.0.0.0/udp/9999/quic-v1/webtransport", "/ip4/1.2.3.4/udp/9999/quic-v1", "/ip4/1.2.3.4/udp/9999/quic-v1/webtransport"}, + out: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/0.0.0.0/udp/9999/quic-v1/webtransport", "/ip4/1.2.3.4/udp/9999/quic-v1", "/ip4/1.2.3.4/udp/9999/quic-v1/webtransport"}, + }, + { + name: "Infer Many", + in: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/0.0.0.0/udp/9999/quic-v1/webtransport", "/ip4/1.2.3.4/udp/9999/quic-v1", "/ip4/4.3.2.1/udp/9999/quic-v1"}, + out: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/0.0.0.0/udp/9999/quic-v1/webtransport", "/ip4/1.2.3.4/udp/9999/quic-v1", "/ip4/4.3.2.1/udp/9999/quic-v1", "/ip4/1.2.3.4/udp/9999/quic-v1/webtransport", "/ip4/4.3.2.1/udp/9999/quic-v1/webtransport"}, + }, + { + name: "No Common listeners", + in: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/0.0.0.0/udp/1111/quic-v1/webtransport", "/ip4/1.2.3.4/udp/9999/quic-v1"}, + out: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/0.0.0.0/udp/1111/quic-v1/webtransport", "/ip4/1.2.3.4/udp/9999/quic-v1"}, + }, + { + name: "No WebTransport", + in: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/1.2.3.4/udp/9999/quic-v1"}, + out: []string{"/ip4/0.0.0.0/udp/9999/quic-v1", "/ip4/1.2.3.4/udp/9999/quic-v1"}, + }, + } + + // Make sure the testCases are all valid multiaddrs + for _, tc := range testCases { + for _, addr := range tc.in { + _, err := ma.NewMultiaddr(addr) + require.NoError(t, err) + } + for _, addr := range tc.out { + _, err := ma.NewMultiaddr(addr) + require.NoError(t, err) + } + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sort.StringSlice(tc.in).Sort() + sort.StringSlice(tc.out).Sort() + min := make([]ma.Multiaddr, 0, len(tc.in)) + sort.Slice(tc.in, func(i, j int) bool { + return tc.in[i] < tc.in[j] + }) + for _, addr := range tc.in { + min = append(min, ma.StringCast(addr)) + } + outMa := inferWebtransportAddrsFromQuic(min) + outStr := make([]string, 0, len(outMa)) + for _, addr := range outMa { + outStr = append(outStr, addr.String()) + } + require.Equal(t, tc.out, outStr) + }) + + } + +}