diff --git a/p2p/host/basic/address_service.go b/p2p/host/basic/address_service.go index c7166e7ac0..ad828bbc5a 100644 --- a/p2p/host/basic/address_service.go +++ b/p2p/host/basic/address_service.go @@ -46,9 +46,7 @@ type addressService struct { ctx context.Context ctxCancel context.CancelFunc - wg sync.WaitGroup - updateLocalIPv4Backoff backoff.ExpBackoff - updateLocalIPv6Backoff backoff.ExpBackoff + wg sync.WaitGroup ifaceAddrs *interfaceAddrsCache } @@ -226,91 +224,15 @@ func (a *addressService) AllAddrs() []ma.Multiaddr { return nil } - filteredIfaceAddrs := a.ifaceAddrs.Filtered() - - // Iterate over all _unresolved_ listen addresses, resolving our primary - // interface only to avoid advertising too many addresses. finalAddrs := make([]ma.Multiaddr, 0, 8) - if resolved, err := manet.ResolveUnspecifiedAddresses(listenAddrs, filteredIfaceAddrs); err != nil { - // This can happen if we're listening on no addrs, or listening - // on IPv6 addrs, but only have IPv4 interface addrs. - log.Debugw("failed to resolve listen addrs", "error", err) - } else { - finalAddrs = append(finalAddrs, resolved...) - } - - finalAddrs = ma.Unique(finalAddrs) + finalAddrs = a.appendInterfaceAddrs(finalAddrs, listenAddrs) // use nat mappings if we have them - if a.natmgr != nil && a.natmgr.HasDiscoveredNAT() { - // We have successfully mapped ports on our NAT. Use those - // instead of observed addresses (mostly). - // Next, apply this mapping to our addresses. - for _, listen := range listenAddrs { - extMaddr := a.natmgr.GetMapping(listen) - if extMaddr == nil { - // not mapped - continue - } - - // if the router reported a sane address - if !manet.IsIPUnspecified(extMaddr) { - // Add in the mapped addr. - finalAddrs = append(finalAddrs, extMaddr) - } else { - log.Warn("NAT device reported an unspecified IP as it's external address") - } - - // Did the router give us a routable public addr? - if manet.IsPublicAddr(extMaddr) { - // well done - continue - } - - // No. - // in case the router gives us a wrong address or we're behind a double-NAT. - // also add observed addresses - resolved, err := manet.ResolveUnspecifiedAddress(listen, a.ifaceAddrs.All()) - if err != nil { - // This can happen if we try to resolve /ip6/::/... - // without any IPv6 interface addresses. - continue - } - - for _, addr := range resolved { - // Now, check if we have any observed addresses that - // differ from the one reported by the router. Routers - // don't always give the most accurate information. - observed := a.ids.ObservedAddrsFor(addr) - - if len(observed) == 0 { - continue - } - - // Drop the IP from the external maddr - _, extMaddrNoIP := ma.SplitFirst(extMaddr) - - for _, obsMaddr := range observed { - // Extract a public observed addr. - ip, _ := ma.SplitFirst(obsMaddr) - if ip == nil || !manet.IsPublicAddr(ip) { - continue - } - - finalAddrs = append(finalAddrs, ma.Join(ip, extMaddrNoIP)) - } - } - } - } else { - var observedAddrs []ma.Multiaddr - if a.ids != nil { - observedAddrs = a.ids.OwnObservedAddrs() - } - finalAddrs = append(finalAddrs, observedAddrs...) - } + finalAddrs = a.appendNATAddrs(finalAddrs, listenAddrs) finalAddrs = ma.Unique(finalAddrs) + // Remove /p2p-circuit addresses from the list. - // The p2p-circuit tranport listener reports its address as just /p2p-circuit + // The p2p-circuit transport listener reports its address as just /p2p-circuit // This is useless for dialing. Users need to manage their circuit addresses themselves, // or use AutoRelay. finalAddrs = slices.DeleteFunc(finalAddrs, func(a ma.Multiaddr) bool { @@ -322,6 +244,35 @@ func (a *addressService) AllAddrs() []ma.Multiaddr { return finalAddrs } +func (a *addressService) appendInterfaceAddrs(result []ma.Multiaddr, listenAddrs []ma.Multiaddr) []ma.Multiaddr { + // resolving any unspecified listen addressees to use only the primary + // interface to avoid advertising too many addresses. + if resolved, err := manet.ResolveUnspecifiedAddresses(listenAddrs, a.ifaceAddrs.Filtered()); err != nil { + log.Warnw("failed to resolve listen addrs", "error", err) + } else { + result = append(result, resolved...) + } + result = ma.Unique(result) + return result +} + +func (a *addressService) appendNATAddrs(result []ma.Multiaddr, listenAddrs []ma.Multiaddr) []ma.Multiaddr { + ifaceAddrs := a.ifaceAddrs.All() + // use nat mappings if we have them + if a.natmgr != nil && a.natmgr.HasDiscoveredNAT() { + // we have a NAT device + for _, listen := range listenAddrs { + extMaddr := a.natmgr.GetMapping(listen) + result = appendValidNATAddrs(result, listen, extMaddr, a.ids.ObservedAddrsFor, ifaceAddrs) + } + } else { + if a.ids != nil { + result = append(result, a.ids.OwnObservedAddrs()...) + } + } + return result +} + func (a *addressService) addCertHashes(addrs []ma.Multiaddr) []ma.Multiaddr { // This is a temporary workaround/hack that fixes #2233. Once we have a // proper address pipeline, rework this. See the issue for more context. @@ -536,3 +487,59 @@ func (i *interfaceAddrsCache) updateUnlocked() { } } } + +func getAllPossibleLocalAddrs(listenAddr ma.Multiaddr, ifaceAddrs []ma.Multiaddr) []ma.Multiaddr { + // If the nat mapping fails, use the observed addrs + resolved, err := manet.ResolveUnspecifiedAddress(listenAddr, ifaceAddrs) + if err != nil { + log.Warnf("failed to resolve listen addr %s, %s: %s", listenAddr, ifaceAddrs, err) + return nil + } + return append(resolved, listenAddr) +} + +// appendValidNATAddrs adds the NAT-ed addresses to the result. If the NAT device doesn't provide +// us with a public IP address, we use the observed addresses. +func appendValidNATAddrs(result []ma.Multiaddr, listenAddr ma.Multiaddr, natMapping ma.Multiaddr, + obsAddrsFunc func(ma.Multiaddr) []ma.Multiaddr, + ifaceAddrs []ma.Multiaddr) []ma.Multiaddr { + if natMapping == nil { + allAddrs := getAllPossibleLocalAddrs(listenAddr, ifaceAddrs) + for _, a := range allAddrs { + result = append(result, obsAddrsFunc(a)...) + } + return result + } + + // if the router reported a sane address, use it. + if !manet.IsIPUnspecified(natMapping) { + result = append(result, natMapping) + } else { + log.Warn("NAT device reported an unspecified IP as it's external address") + } + + // If the router gave us a public address, use it and ignore observed addresses + if manet.IsPublicAddr(natMapping) { + return result + } + + // Router gave us a private IP; maybe we're behind a CGNAT. + // See if we have a public IP from observed addresses. + _, extMaddrNoIP := ma.SplitFirst(natMapping) + if extMaddrNoIP == nil { + return result + } + + allAddrs := getAllPossibleLocalAddrs(listenAddr, ifaceAddrs) + for _, addr := range allAddrs { + for _, obsMaddr := range obsAddrsFunc(addr) { + // Extract a public observed addr. + ip, _ := ma.SplitFirst(obsMaddr) + if ip == nil || !manet.IsPublicAddr(ip) { + continue + } + result = append(result, ma.Join(ip, extMaddrNoIP)) + } + } + return result +} diff --git a/p2p/host/basic/address_service_test.go b/p2p/host/basic/address_service_test.go new file mode 100644 index 0000000000..e099ba5737 --- /dev/null +++ b/p2p/host/basic/address_service_test.go @@ -0,0 +1,98 @@ +package basichost + +import ( + "testing" + + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" + "github.com/stretchr/testify/require" +) + +func TestAppendNATAddrs(t *testing.T) { + if1, if2 := ma.StringCast("/ip4/192.168.0.100"), ma.StringCast("/ip4/1.1.1.1") + ifaceAddrs := []ma.Multiaddr{if1, if2} + tcpListenAddr, udpListenAddr := ma.StringCast("/ip4/0.0.0.0/tcp/1"), ma.StringCast("/ip4/0.0.0.0/udp/2/quic-v1") + cases := []struct { + Name string + Listen ma.Multiaddr + Nat ma.Multiaddr + ObsAddrFunc func(ma.Multiaddr) []ma.Multiaddr + Expected []ma.Multiaddr + }{ + { + Name: "nat map success", + // nat mapping success, obsaddress ignored + Listen: ma.StringCast("/ip4/0.0.0.0/udp/1/quic-v1"), + Nat: ma.StringCast("/ip4/1.1.1.1/udp/10/quic-v1"), + ObsAddrFunc: func(m ma.Multiaddr) []ma.Multiaddr { + return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/udp/100/quic-v1")} + }, + Expected: []ma.Multiaddr{ma.StringCast("/ip4/1.1.1.1/udp/10/quic-v1")}, + }, + { + Name: "nat map failure", + //nat mapping fails, obs addresses added + Listen: ma.StringCast("/ip4/0.0.0.0/tcp/1"), + Nat: nil, + ObsAddrFunc: func(a ma.Multiaddr) []ma.Multiaddr { + ip, _ := ma.SplitFirst(a) + if ip == nil { + return nil + } + if ip.Equal(if1) { + return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/100")} + } else { + return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/tcp/100")} + } + }, + Expected: []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/100"), ma.StringCast("/ip4/3.3.3.3/tcp/100")}, + }, + { + Name: "nat map success but CGNAT", + //nat addr added, obs address added with nat provided port + Listen: tcpListenAddr, + Nat: ma.StringCast("/ip4/100.100.1.1/tcp/100"), + ObsAddrFunc: func(a ma.Multiaddr) []ma.Multiaddr { + ip, _ := ma.SplitFirst(a) + if ip == nil { + return nil + } + if ip.Equal(if1) { + return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/20")} + } else { + return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/tcp/30")} + } + }, + Expected: []ma.Multiaddr{ + ma.StringCast("/ip4/100.100.1.1/tcp/100"), + ma.StringCast("/ip4/2.2.2.2/tcp/100"), + ma.StringCast("/ip4/3.3.3.3/tcp/100"), + }, + }, + { + Name: "uses unspecified address for obs address", + // observed address manager should be queries with both specified and unspecified addresses + // udp observed addresses are mapped to unspecified addresses + Listen: udpListenAddr, + Nat: nil, + ObsAddrFunc: func(a ma.Multiaddr) []ma.Multiaddr { + if manet.IsIPUnspecified(a) { + return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/udp/20/quic-v1")} + } + return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/udp/20/quic-v1")} + }, + Expected: []ma.Multiaddr{ + ma.StringCast("/ip4/2.2.2.2/udp/20/quic-v1"), + ma.StringCast("/ip4/3.3.3.3/udp/20/quic-v1"), + }, + }, + } + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + res := appendValidNATAddrs(nil, + tc.Listen, tc.Nat, tc.ObsAddrFunc, ifaceAddrs) + res = ma.Unique(res) + require.ElementsMatch(t, tc.Expected, res, "%s\n%s", tc.Expected, res) + }) + } +}