Skip to content

Commit

Permalink
Infer public webtransport addrs from quic-v1 addrs. (#2251)
Browse files Browse the repository at this point in the history
* 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 b63155f.

* Assume input is deduped
  • Loading branch information
MarcoPolo committed Apr 12, 2023
1 parent 7dc3b81 commit fbf11bc
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 1 deletion.
78 changes: 77 additions & 1 deletion p2p/host/basic/basic_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down
71 changes: 71 additions & 0 deletions p2p/host/basic/basic_host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"reflect"
"sort"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -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)
})

}

}

0 comments on commit fbf11bc

Please sign in to comment.