mirror of
https://github.com/libp2p/go-libp2p.git
synced 2025-09-26 20:21:26 +08:00
addrsmanager: extract out addressing logic from basichost (#3075)
Benchmark for AllAddrs: ``` goos: linux goarch: amd64 pkg: github.com/libp2p/go-libp2p cpu: AMD Ryzen 7 7840U w/ Radeon 780M Graphics BenchmarkAllAddrs-16 16737 122245 ns/op 21240 B/op 218 allocs/op ``` after: ``` goos: linux goarch: amd64 pkg: github.com/libp2p/go-libp2p cpu: AMD Ryzen 7 7840U w/ Radeon 780M Graphics BenchmarkAllAddrs-16 11103236 105.7 ns/op 192 B/op 1 allocs/op ```
This commit is contained in:
@@ -811,3 +811,17 @@ func TestCustomTCPDialer(t *testing.T) {
|
||||
})
|
||||
require.ErrorContains(t, err, expectedErr.Error())
|
||||
}
|
||||
|
||||
func BenchmarkAllAddrs(b *testing.B) {
|
||||
h, err := New()
|
||||
|
||||
addrsHost := h.(interface{ AllAddrs() []ma.Multiaddr })
|
||||
require.NoError(b, err)
|
||||
defer h.Close()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
addrsHost.AllAddrs()
|
||||
}
|
||||
}
|
||||
|
549
p2p/host/basic/addrs_manager.go
Normal file
549
p2p/host/basic/addrs_manager.go
Normal file
@@ -0,0 +1,549 @@
|
||||
package basichost
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/event"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/transport"
|
||||
"github.com/libp2p/go-libp2p/p2p/host/basic/internal/backoff"
|
||||
libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc"
|
||||
libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
|
||||
"github.com/libp2p/go-netroute"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
)
|
||||
|
||||
const maxObservedAddrsPerListenAddr = 5
|
||||
|
||||
type observedAddrsManager interface {
|
||||
OwnObservedAddrs() []ma.Multiaddr
|
||||
ObservedAddrsFor(local ma.Multiaddr) []ma.Multiaddr
|
||||
}
|
||||
|
||||
type addrsManager struct {
|
||||
eventbus event.Bus
|
||||
natManager NATManager
|
||||
addrsFactory AddrsFactory
|
||||
listenAddrs func() []ma.Multiaddr
|
||||
transportForListening func(ma.Multiaddr) transport.Transport
|
||||
observedAddrsManager observedAddrsManager
|
||||
interfaceAddrs *interfaceAddrsCache
|
||||
|
||||
// triggerAddrsUpdateChan is used to trigger an addresses update.
|
||||
triggerAddrsUpdateChan chan struct{}
|
||||
// addrsUpdatedChan is notified when addresses change.
|
||||
addrsUpdatedChan chan struct{}
|
||||
hostReachability atomic.Pointer[network.Reachability]
|
||||
|
||||
addrsMx sync.RWMutex // protects fields below
|
||||
localAddrs []ma.Multiaddr
|
||||
relayAddrs []ma.Multiaddr
|
||||
|
||||
wg sync.WaitGroup
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func newAddrsManager(
|
||||
eventbus event.Bus,
|
||||
natmgr NATManager,
|
||||
addrsFactory AddrsFactory,
|
||||
listenAddrs func() []ma.Multiaddr,
|
||||
transportForListening func(ma.Multiaddr) transport.Transport,
|
||||
observedAddrsManager observedAddrsManager,
|
||||
addrsUpdatedChan chan struct{},
|
||||
) (*addrsManager, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
as := &addrsManager{
|
||||
eventbus: eventbus,
|
||||
listenAddrs: listenAddrs,
|
||||
transportForListening: transportForListening,
|
||||
observedAddrsManager: observedAddrsManager,
|
||||
natManager: natmgr,
|
||||
addrsFactory: addrsFactory,
|
||||
triggerAddrsUpdateChan: make(chan struct{}, 1),
|
||||
addrsUpdatedChan: addrsUpdatedChan,
|
||||
interfaceAddrs: &interfaceAddrsCache{},
|
||||
ctx: ctx,
|
||||
ctxCancel: cancel,
|
||||
}
|
||||
unknownReachability := network.ReachabilityUnknown
|
||||
as.hostReachability.Store(&unknownReachability)
|
||||
return as, nil
|
||||
}
|
||||
|
||||
func (a *addrsManager) Start() error {
|
||||
return a.background()
|
||||
}
|
||||
|
||||
func (a *addrsManager) Close() {
|
||||
a.ctxCancel()
|
||||
if a.natManager != nil {
|
||||
err := a.natManager.Close()
|
||||
if err != nil {
|
||||
log.Warnf("error closing natmgr: %s", err)
|
||||
}
|
||||
}
|
||||
a.wg.Wait()
|
||||
}
|
||||
|
||||
func (a *addrsManager) NetNotifee() network.Notifiee {
|
||||
return &network.NotifyBundle{
|
||||
ListenF: func(network.Network, ma.Multiaddr) { a.triggerAddrsUpdate() },
|
||||
ListenCloseF: func(network.Network, ma.Multiaddr) { a.triggerAddrsUpdate() },
|
||||
}
|
||||
}
|
||||
|
||||
func (a *addrsManager) triggerAddrsUpdate() {
|
||||
// This is ugly, we update here *and* in the background loop, but this ensures the nice property
|
||||
// that host.Addrs after host.Network().Listen(...) will return the recently added listen address.
|
||||
a.updateLocalAddrs()
|
||||
select {
|
||||
case a.triggerAddrsUpdateChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (a *addrsManager) background() error {
|
||||
autoRelayAddrsSub, err := a.eventbus.Subscribe(new(event.EvtAutoRelayAddrsUpdated))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error subscribing to auto relay addrs: %s", err)
|
||||
}
|
||||
|
||||
autonatReachabilitySub, err := a.eventbus.Subscribe(new(event.EvtLocalReachabilityChanged))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error subscribing to autonat reachability: %s", err)
|
||||
}
|
||||
|
||||
// ensure that we have the correct address after returning from Start()
|
||||
// update local addrs
|
||||
a.updateLocalAddrs()
|
||||
// update relay addrs in case we're private
|
||||
select {
|
||||
case e := <-autoRelayAddrsSub.Out():
|
||||
if evt, ok := e.(event.EvtAutoRelayAddrsUpdated); ok {
|
||||
a.updateRelayAddrs(evt.RelayAddrs)
|
||||
}
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case e := <-autonatReachabilitySub.Out():
|
||||
if evt, ok := e.(event.EvtLocalReachabilityChanged); ok {
|
||||
a.hostReachability.Store(&evt.Reachability)
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
defer func() {
|
||||
err := autoRelayAddrsSub.Close()
|
||||
if err != nil {
|
||||
log.Warnf("error closing auto relay addrs sub: %s", err)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
err := autonatReachabilitySub.Close()
|
||||
if err != nil {
|
||||
log.Warnf("error closing autonat reachability sub: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ticker := time.NewTicker(addrChangeTickrInterval)
|
||||
defer ticker.Stop()
|
||||
var prev []ma.Multiaddr
|
||||
for {
|
||||
a.updateLocalAddrs()
|
||||
curr := a.Addrs()
|
||||
if a.areAddrsDifferent(prev, curr) {
|
||||
log.Debugf("host addresses updated: %s", curr)
|
||||
select {
|
||||
case a.addrsUpdatedChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
prev = curr
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-a.triggerAddrsUpdateChan:
|
||||
case e := <-autoRelayAddrsSub.Out():
|
||||
if evt, ok := e.(event.EvtAutoRelayAddrsUpdated); ok {
|
||||
a.updateRelayAddrs(evt.RelayAddrs)
|
||||
}
|
||||
case e := <-autonatReachabilitySub.Out():
|
||||
if evt, ok := e.(event.EvtLocalReachabilityChanged); ok {
|
||||
a.hostReachability.Store(&evt.Reachability)
|
||||
}
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Addrs returns the node's dialable addresses both public and private.
|
||||
// If autorelay is enabled and node reachability is private, it returns
|
||||
// the node's relay addresses and private network addresses.
|
||||
func (a *addrsManager) Addrs() []ma.Multiaddr {
|
||||
addrs := a.DirectAddrs()
|
||||
rch := a.hostReachability.Load()
|
||||
if rch != nil && *rch == network.ReachabilityPrivate {
|
||||
a.addrsMx.RLock()
|
||||
// Delete public addresses if the node's reachability is private, and we have relay addresses
|
||||
if len(a.relayAddrs) > 0 {
|
||||
addrs = slices.DeleteFunc(addrs, manet.IsPublicAddr)
|
||||
addrs = append(addrs, a.relayAddrs...)
|
||||
}
|
||||
a.addrsMx.RUnlock()
|
||||
}
|
||||
// Make a copy. Consumers can modify the slice elements
|
||||
addrs = slices.Clone(a.addrsFactory(addrs))
|
||||
// Add certhashes for the addresses provided by the user via address factory.
|
||||
addrs = a.addCertHashes(ma.Unique(addrs))
|
||||
slices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return a.Compare(b) })
|
||||
return addrs
|
||||
}
|
||||
|
||||
// HolePunchAddrs returns the node's public direct listen addresses for hole punching.
|
||||
func (a *addrsManager) HolePunchAddrs() []ma.Multiaddr {
|
||||
addrs := a.DirectAddrs()
|
||||
addrs = slices.Clone(a.addrsFactory(addrs))
|
||||
// AllAddrs may ignore observed addresses in favour of NAT mappings.
|
||||
// Use both for hole punching.
|
||||
if a.observedAddrsManager != nil {
|
||||
addrs = append(addrs, a.observedAddrsManager.OwnObservedAddrs()...)
|
||||
}
|
||||
addrs = ma.Unique(addrs)
|
||||
return slices.DeleteFunc(addrs, func(a ma.Multiaddr) bool { return !manet.IsPublicAddr(a) })
|
||||
}
|
||||
|
||||
// DirectAddrs returns all the addresses the host is listening on except circuit addresses.
|
||||
func (a *addrsManager) DirectAddrs() []ma.Multiaddr {
|
||||
a.addrsMx.RLock()
|
||||
defer a.addrsMx.RUnlock()
|
||||
return slices.Clone(a.localAddrs)
|
||||
}
|
||||
|
||||
func (a *addrsManager) updateRelayAddrs(addrs []ma.Multiaddr) {
|
||||
a.addrsMx.Lock()
|
||||
defer a.addrsMx.Unlock()
|
||||
a.relayAddrs = append(a.relayAddrs[:0], addrs...)
|
||||
}
|
||||
|
||||
var p2pCircuitAddr = ma.StringCast("/p2p-circuit")
|
||||
|
||||
func (a *addrsManager) updateLocalAddrs() {
|
||||
localAddrs := a.getLocalAddrs()
|
||||
slices.SortFunc(localAddrs, func(a, b ma.Multiaddr) int { return a.Compare(b) })
|
||||
|
||||
a.addrsMx.Lock()
|
||||
a.localAddrs = localAddrs
|
||||
a.addrsMx.Unlock()
|
||||
}
|
||||
|
||||
func (a *addrsManager) getLocalAddrs() []ma.Multiaddr {
|
||||
listenAddrs := a.listenAddrs()
|
||||
if len(listenAddrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
finalAddrs := make([]ma.Multiaddr, 0, 8)
|
||||
finalAddrs = a.appendPrimaryInterfaceAddrs(finalAddrs, listenAddrs)
|
||||
finalAddrs = a.appendNATAddrs(finalAddrs, listenAddrs, a.interfaceAddrs.All())
|
||||
|
||||
finalAddrs = ma.Unique(finalAddrs)
|
||||
|
||||
// Remove "/p2p-circuit" addresses from the list.
|
||||
// The p2p-circuit 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 {
|
||||
return a.Equal(p2pCircuitAddr)
|
||||
})
|
||||
|
||||
// Remove any unspecified address from the list
|
||||
finalAddrs = slices.DeleteFunc(finalAddrs, func(a ma.Multiaddr) bool {
|
||||
return manet.IsIPUnspecified(a)
|
||||
})
|
||||
|
||||
// Add certhashes for /webrtc-direct, /webtransport, etc addresses discovered
|
||||
// using identify.
|
||||
finalAddrs = a.addCertHashes(finalAddrs)
|
||||
return finalAddrs
|
||||
}
|
||||
|
||||
// appendPrimaryInterfaceAddrs appends the primary interface addresses to `dst`.
|
||||
func (a *addrsManager) appendPrimaryInterfaceAddrs(dst []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.interfaceAddrs.Filtered()); err != nil {
|
||||
log.Warnw("failed to resolve listen addrs", "error", err)
|
||||
} else {
|
||||
dst = append(dst, resolved...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// appendNATAddrs appends the NAT-ed addrs for the listenAddrs. For unspecified listen addrs it appends the
|
||||
// public address for all the interfaces.
|
||||
// Inferring WebTransport from QUIC depends on the observed address manager.
|
||||
//
|
||||
// TODO: Merge the natmgr and identify.ObservedAddrManager in to one NatMapper module.
|
||||
func (a *addrsManager) appendNATAddrs(dst []ma.Multiaddr, listenAddrs []ma.Multiaddr, ifaceAddrs []ma.Multiaddr) []ma.Multiaddr {
|
||||
var obsAddrs []ma.Multiaddr
|
||||
for _, listenAddr := range listenAddrs {
|
||||
var natAddr ma.Multiaddr
|
||||
if a.natManager != nil {
|
||||
natAddr = a.natManager.GetMapping(listenAddr)
|
||||
}
|
||||
|
||||
// The order of the cases below is important.
|
||||
switch {
|
||||
case natAddr == nil: // no nat mapping
|
||||
dst = a.appendObservedAddrs(dst, listenAddr, ifaceAddrs)
|
||||
case manet.IsIPUnspecified(natAddr):
|
||||
log.Infof("NAT device reported an unspecified IP as it's external address: %s", natAddr)
|
||||
_, natRest := ma.SplitFirst(natAddr)
|
||||
obsAddrs = a.appendObservedAddrs(obsAddrs[:0], listenAddr, ifaceAddrs)
|
||||
for _, addr := range obsAddrs {
|
||||
obsIP, _ := ma.SplitFirst(addr)
|
||||
if obsIP != nil && manet.IsPublicAddr(obsIP.Multiaddr()) {
|
||||
dst = append(dst, obsIP.Encapsulate(natRest))
|
||||
}
|
||||
}
|
||||
// This is !Public as opposed to IsPrivate intentionally.
|
||||
// Public is a more restrictive classification in some cases, like IPv6 addresses which only
|
||||
// consider unicast IPv6 addresses allocated so far as public(2000::/3).
|
||||
case !manet.IsPublicAddr(natAddr): // nat reported non public addr(maybe CGNAT?)
|
||||
// use both NAT and observed addr
|
||||
dst = append(dst, natAddr)
|
||||
dst = a.appendObservedAddrs(dst, listenAddr, ifaceAddrs)
|
||||
default: // public addr
|
||||
dst = append(dst, natAddr)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (a *addrsManager) appendObservedAddrs(dst []ma.Multiaddr, listenAddr ma.Multiaddr, ifaceAddrs []ma.Multiaddr) []ma.Multiaddr {
|
||||
if a.observedAddrsManager == nil {
|
||||
return dst
|
||||
}
|
||||
// Add it for the listenAddr first.
|
||||
// listenAddr maybe unspecified. That's okay as connections on UDP transports
|
||||
// will have the unspecified address as the local address.
|
||||
obsAddrs := a.observedAddrsManager.ObservedAddrsFor(listenAddr)
|
||||
if len(obsAddrs) > maxObservedAddrsPerListenAddr {
|
||||
obsAddrs = obsAddrs[:maxObservedAddrsPerListenAddr]
|
||||
}
|
||||
dst = append(dst, obsAddrs...)
|
||||
|
||||
// if it can be resolved into more addresses, add them too
|
||||
resolved, err := manet.ResolveUnspecifiedAddress(listenAddr, ifaceAddrs)
|
||||
if err != nil {
|
||||
log.Warnf("failed to resolve listen addr %s, %s: %s", listenAddr, ifaceAddrs, err)
|
||||
return dst
|
||||
}
|
||||
for _, addr := range resolved {
|
||||
obsAddrs = a.observedAddrsManager.ObservedAddrsFor(addr)
|
||||
if len(obsAddrs) > maxObservedAddrsPerListenAddr {
|
||||
obsAddrs = obsAddrs[:maxObservedAddrsPerListenAddr]
|
||||
}
|
||||
dst = append(dst, obsAddrs...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (a *addrsManager) addCertHashes(addrs []ma.Multiaddr) []ma.Multiaddr {
|
||||
if a.transportForListening == nil {
|
||||
return addrs
|
||||
}
|
||||
|
||||
// TODO(sukunrt): Move this to swarm.
|
||||
// There are two parts to determining our external address
|
||||
// 1. From the NAT device, or identify, or other such STUN like mechanism.
|
||||
// All that matters here is (internal_ip, internal_port, tcp) => (external_ip, external_port, tcp)
|
||||
// The rest of the address should be cut and appended to the external one.
|
||||
// 2. The user provides us with the address (/ip4/1.2.3.4/udp/1/webrtc-direct) and we add the certhash.
|
||||
// This API should be where the transports are, i.e. swarm.
|
||||
//
|
||||
// It would have been nice to remove this completely and just work with
|
||||
// mapping the interface thinwaist addresses (tcp, 192.168.18.18:4000 => 1.2.3.4:4577)
|
||||
// but that is only convenient if we're using the same port for listening on
|
||||
// all transports which share the same thinwaist protocol. If you listen
|
||||
// on 4001 for tcp, and 4002 for websocket, then it's a terrible API.
|
||||
type addCertHasher interface {
|
||||
AddCertHashes(m ma.Multiaddr) (ma.Multiaddr, bool)
|
||||
}
|
||||
|
||||
for i, addr := range addrs {
|
||||
wtOK, wtN := libp2pwebtransport.IsWebtransportMultiaddr(addr)
|
||||
webrtcOK, webrtcN := libp2pwebrtc.IsWebRTCDirectMultiaddr(addr)
|
||||
if (wtOK && wtN == 0) || (webrtcOK && webrtcN == 0) {
|
||||
t := a.transportForListening(addr)
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
tpt, ok := t.(addCertHasher)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
addrWithCerthash, added := tpt.AddCertHashes(addr)
|
||||
if !added {
|
||||
log.Warnf("Couldn't add certhashes to multiaddr: %s", addr)
|
||||
continue
|
||||
}
|
||||
addrs[i] = addrWithCerthash
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (a *addrsManager) areAddrsDifferent(prev, current []ma.Multiaddr) bool {
|
||||
// TODO: make the sorted nature of ma.Unique a guarantee in multiaddrs
|
||||
prev = ma.Unique(prev)
|
||||
current = ma.Unique(current)
|
||||
if len(prev) != len(current) {
|
||||
return true
|
||||
}
|
||||
slices.SortFunc(prev, func(a, b ma.Multiaddr) int { return a.Compare(b) })
|
||||
slices.SortFunc(current, func(a, b ma.Multiaddr) int { return a.Compare(b) })
|
||||
for i := range prev {
|
||||
if !prev[i].Equal(current[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const interfaceAddrsCacheTTL = time.Minute
|
||||
|
||||
type interfaceAddrsCache struct {
|
||||
mx sync.RWMutex
|
||||
filtered []ma.Multiaddr
|
||||
all []ma.Multiaddr
|
||||
updateLocalIPv4Backoff backoff.ExpBackoff
|
||||
updateLocalIPv6Backoff backoff.ExpBackoff
|
||||
lastUpdated time.Time
|
||||
}
|
||||
|
||||
func (i *interfaceAddrsCache) Filtered() []ma.Multiaddr {
|
||||
i.mx.RLock()
|
||||
if time.Now().After(i.lastUpdated.Add(interfaceAddrsCacheTTL)) {
|
||||
i.mx.RUnlock()
|
||||
return i.update(true)
|
||||
}
|
||||
defer i.mx.RUnlock()
|
||||
return i.filtered
|
||||
}
|
||||
|
||||
func (i *interfaceAddrsCache) All() []ma.Multiaddr {
|
||||
i.mx.RLock()
|
||||
if time.Now().After(i.lastUpdated.Add(interfaceAddrsCacheTTL)) {
|
||||
i.mx.RUnlock()
|
||||
return i.update(false)
|
||||
}
|
||||
defer i.mx.RUnlock()
|
||||
return i.all
|
||||
}
|
||||
|
||||
func (i *interfaceAddrsCache) update(filtered bool) []ma.Multiaddr {
|
||||
i.mx.Lock()
|
||||
defer i.mx.Unlock()
|
||||
if !time.Now().After(i.lastUpdated.Add(interfaceAddrsCacheTTL)) {
|
||||
if filtered {
|
||||
return i.filtered
|
||||
}
|
||||
return i.all
|
||||
}
|
||||
i.updateUnlocked()
|
||||
i.lastUpdated = time.Now()
|
||||
if filtered {
|
||||
return i.filtered
|
||||
}
|
||||
return i.all
|
||||
}
|
||||
|
||||
func (i *interfaceAddrsCache) updateUnlocked() {
|
||||
i.filtered = nil
|
||||
i.all = nil
|
||||
|
||||
// Try to use the default ipv4/6 addresses.
|
||||
// TODO: Remove this. We should advertise all interface addresses.
|
||||
if r, err := netroute.New(); err != nil {
|
||||
log.Debugw("failed to build Router for kernel's routing table", "error", err)
|
||||
} else {
|
||||
|
||||
var localIPv4 net.IP
|
||||
var ran bool
|
||||
err, ran = i.updateLocalIPv4Backoff.Run(func() error {
|
||||
_, _, localIPv4, err = r.Route(net.IPv4zero)
|
||||
return err
|
||||
})
|
||||
|
||||
if ran && err != nil {
|
||||
log.Debugw("failed to fetch local IPv4 address", "error", err)
|
||||
} else if ran && localIPv4.IsGlobalUnicast() {
|
||||
maddr, err := manet.FromIP(localIPv4)
|
||||
if err == nil {
|
||||
i.filtered = append(i.filtered, maddr)
|
||||
}
|
||||
}
|
||||
|
||||
var localIPv6 net.IP
|
||||
err, ran = i.updateLocalIPv6Backoff.Run(func() error {
|
||||
_, _, localIPv6, err = r.Route(net.IPv6unspecified)
|
||||
return err
|
||||
})
|
||||
|
||||
if ran && err != nil {
|
||||
log.Debugw("failed to fetch local IPv6 address", "error", err)
|
||||
} else if ran && localIPv6.IsGlobalUnicast() {
|
||||
maddr, err := manet.FromIP(localIPv6)
|
||||
if err == nil {
|
||||
i.filtered = append(i.filtered, maddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the interface addresses
|
||||
ifaceAddrs, err := manet.InterfaceMultiaddrs()
|
||||
if err != nil {
|
||||
// This usually shouldn't happen, but we could be in some kind
|
||||
// of funky restricted environment.
|
||||
log.Errorw("failed to resolve local interface addresses", "error", err)
|
||||
|
||||
// Add the loopback addresses to the filtered addrs and use them as the non-filtered addrs.
|
||||
// Then bail. There's nothing else we can do here.
|
||||
i.filtered = append(i.filtered, manet.IP4Loopback, manet.IP6Loopback)
|
||||
i.all = i.filtered
|
||||
return
|
||||
}
|
||||
|
||||
// remove link local ipv6 addresses
|
||||
i.all = slices.DeleteFunc(ifaceAddrs, manet.IsIP6LinkLocal)
|
||||
|
||||
// If netroute failed to get us any interface addresses, use all of
|
||||
// them.
|
||||
if len(i.filtered) == 0 {
|
||||
// Add all addresses.
|
||||
i.filtered = i.all
|
||||
} else {
|
||||
// Only add loopback addresses. Filter these because we might
|
||||
// not _have_ an IPv6 loopback address.
|
||||
for _, addr := range i.all {
|
||||
if manet.IsIPLoopback(addr) {
|
||||
i.filtered = append(i.filtered, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
441
p2p/host/basic/addrs_manager_test.go
Normal file
441
p2p/host/basic/addrs_manager_test.go
Normal file
@@ -0,0 +1,441 @@
|
||||
package basichost
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/event"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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 {
|
||||
ipC, _ := ma.SplitFirst(a)
|
||||
ip := ipC.Multiaddr()
|
||||
switch {
|
||||
case ip.Equal(if1):
|
||||
return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/100")}
|
||||
case ip.Equal(if2):
|
||||
return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/tcp/100")}
|
||||
default:
|
||||
return []ma.Multiaddr{}
|
||||
}
|
||||
},
|
||||
Expected: []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/100"), ma.StringCast("/ip4/3.3.3.3/tcp/100")},
|
||||
},
|
||||
{
|
||||
Name: "if addrs ignored if not listening on unspecified",
|
||||
// nat mapping fails, obs addresses added
|
||||
Listen: ma.StringCast("/ip4/192.168.1.1/tcp/1"),
|
||||
Nat: nil,
|
||||
ObsAddrFunc: func(a ma.Multiaddr) []ma.Multiaddr {
|
||||
ipC, _ := ma.SplitFirst(a)
|
||||
ip := ipC.Multiaddr()
|
||||
switch {
|
||||
case ip.Equal(if1):
|
||||
return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/100")}
|
||||
case ip.Equal(if2):
|
||||
return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/tcp/100")}
|
||||
case ip.Equal(ma.StringCast("/ip4/192.168.1.1")):
|
||||
return []ma.Multiaddr{ma.StringCast("/ip4/4.4.4.4/tcp/100")}
|
||||
default:
|
||||
return []ma.Multiaddr{}
|
||||
}
|
||||
},
|
||||
Expected: []ma.Multiaddr{ma.StringCast("/ip4/4.4.4.4/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 {
|
||||
ipC, _ := ma.SplitFirst(a)
|
||||
ip := ipC.Multiaddr()
|
||||
if ip.Equal(if1) {
|
||||
return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/20")}
|
||||
}
|
||||
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/20"),
|
||||
ma.StringCast("/ip4/3.3.3.3/tcp/30"),
|
||||
},
|
||||
},
|
||||
{
|
||||
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) {
|
||||
as := &addrsManager{
|
||||
natManager: &mockNatManager{
|
||||
GetMappingFunc: func(addr ma.Multiaddr) ma.Multiaddr {
|
||||
return tc.Nat
|
||||
},
|
||||
},
|
||||
observedAddrsManager: &mockObservedAddrs{
|
||||
ObservedAddrsForFunc: tc.ObsAddrFunc,
|
||||
},
|
||||
}
|
||||
res := as.appendNATAddrs(nil, []ma.Multiaddr{tc.Listen}, ifaceAddrs)
|
||||
res = ma.Unique(res)
|
||||
require.ElementsMatch(t, tc.Expected, res, "%s\n%s", tc.Expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockNatManager struct {
|
||||
GetMappingFunc func(addr ma.Multiaddr) ma.Multiaddr
|
||||
}
|
||||
|
||||
func (m *mockNatManager) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockNatManager) GetMapping(addr ma.Multiaddr) ma.Multiaddr {
|
||||
if m.GetMappingFunc == nil {
|
||||
return nil
|
||||
}
|
||||
return m.GetMappingFunc(addr)
|
||||
}
|
||||
|
||||
func (m *mockNatManager) HasDiscoveredNAT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var _ NATManager = &mockNatManager{}
|
||||
|
||||
type mockObservedAddrs struct {
|
||||
OwnObservedAddrsFunc func() []ma.Multiaddr
|
||||
ObservedAddrsForFunc func(ma.Multiaddr) []ma.Multiaddr
|
||||
}
|
||||
|
||||
func (m *mockObservedAddrs) OwnObservedAddrs() []ma.Multiaddr {
|
||||
return m.OwnObservedAddrsFunc()
|
||||
}
|
||||
|
||||
func (m *mockObservedAddrs) ObservedAddrsFor(local ma.Multiaddr) []ma.Multiaddr {
|
||||
return m.ObservedAddrsForFunc(local)
|
||||
}
|
||||
|
||||
type addrsManagerArgs struct {
|
||||
NATManager NATManager
|
||||
AddrsFactory AddrsFactory
|
||||
ObservedAddrsManager observedAddrsManager
|
||||
ListenAddrs func() []ma.Multiaddr
|
||||
}
|
||||
|
||||
type addrsManagerTestCase struct {
|
||||
*addrsManager
|
||||
PushRelay func(relayAddrs []ma.Multiaddr)
|
||||
PushReachability func(rch network.Reachability)
|
||||
}
|
||||
|
||||
func newAddrsManagerTestCase(t *testing.T, args addrsManagerArgs) addrsManagerTestCase {
|
||||
eb := eventbus.NewBus()
|
||||
if args.AddrsFactory == nil {
|
||||
args.AddrsFactory = func(addrs []ma.Multiaddr) []ma.Multiaddr { return addrs }
|
||||
}
|
||||
addrsUpdatedChan := make(chan struct{}, 1)
|
||||
am, err := newAddrsManager(
|
||||
eb, args.NATManager, args.AddrsFactory, args.ListenAddrs, nil, args.ObservedAddrsManager, addrsUpdatedChan,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, am.Start())
|
||||
raEm, err := eb.Emitter(new(event.EvtAutoRelayAddrsUpdated), eventbus.Stateful)
|
||||
require.NoError(t, err)
|
||||
|
||||
rchEm, err := eb.Emitter(new(event.EvtLocalReachabilityChanged), eventbus.Stateful)
|
||||
require.NoError(t, err)
|
||||
|
||||
return addrsManagerTestCase{
|
||||
addrsManager: am,
|
||||
PushRelay: func(relayAddrs []ma.Multiaddr) {
|
||||
err := raEm.Emit(event.EvtAutoRelayAddrsUpdated{RelayAddrs: relayAddrs})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
PushReachability: func(rch network.Reachability) {
|
||||
err := rchEm.Emit(event.EvtLocalReachabilityChanged{Reachability: rch})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddrsManager(t *testing.T) {
|
||||
lhquic := ma.StringCast("/ip4/127.0.0.1/udp/1/quic-v1")
|
||||
lhtcp := ma.StringCast("/ip4/127.0.0.1/tcp/1")
|
||||
|
||||
publicQUIC := ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1")
|
||||
publicTCP := ma.StringCast("/ip4/1.2.3.4/tcp/1")
|
||||
|
||||
t.Run("only nat", func(t *testing.T) {
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
NATManager: &mockNatManager{
|
||||
GetMappingFunc: func(addr ma.Multiaddr) ma.Multiaddr {
|
||||
if _, err := addr.ValueForProtocol(ma.P_UDP); err == nil {
|
||||
return publicQUIC
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },
|
||||
})
|
||||
am.triggerAddrsUpdate()
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
expected := []ma.Multiaddr{publicQUIC, lhquic, lhtcp}
|
||||
assert.ElementsMatch(collect, am.Addrs(), expected, "%s\n%s", am.Addrs(), expected)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
})
|
||||
|
||||
t.Run("nat and observed addrs", func(t *testing.T) {
|
||||
// nat mapping for udp, observed addrs for tcp
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
NATManager: &mockNatManager{
|
||||
GetMappingFunc: func(addr ma.Multiaddr) ma.Multiaddr {
|
||||
if _, err := addr.ValueForProtocol(ma.P_UDP); err == nil {
|
||||
return publicQUIC
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ObservedAddrsManager: &mockObservedAddrs{
|
||||
ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {
|
||||
if _, err := addr.ValueForProtocol(ma.P_TCP); err == nil {
|
||||
return []ma.Multiaddr{publicTCP}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },
|
||||
})
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
expected := []ma.Multiaddr{lhquic, lhtcp, publicQUIC, publicTCP}
|
||||
assert.ElementsMatch(collect, am.Addrs(), expected, "%s\n%s", am.Addrs(), expected)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
})
|
||||
t.Run("nat returns unspecified addr", func(t *testing.T) {
|
||||
quicPort1 := ma.StringCast("/ip4/3.3.3.3/udp/1/quic-v1")
|
||||
quicPort2 := ma.StringCast("/ip4/3.3.3.3/udp/2/quic-v1")
|
||||
// port from nat, IP from observed addr
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
NATManager: &mockNatManager{
|
||||
GetMappingFunc: func(addr ma.Multiaddr) ma.Multiaddr {
|
||||
if addr.Equal(lhquic) {
|
||||
return ma.StringCast("/ip4/0.0.0.0/udp/2/quic-v1")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ObservedAddrsManager: &mockObservedAddrs{
|
||||
ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {
|
||||
if addr.Equal(lhquic) {
|
||||
return []ma.Multiaddr{quicPort1}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic} },
|
||||
})
|
||||
expected := []ma.Multiaddr{lhquic, quicPort2}
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
assert.ElementsMatch(collect, am.Addrs(), expected, "%s\n%s", am.Addrs(), expected)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
})
|
||||
t.Run("only observed addrs", func(t *testing.T) {
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
ObservedAddrsManager: &mockObservedAddrs{
|
||||
ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {
|
||||
if addr.Equal(lhtcp) {
|
||||
return []ma.Multiaddr{publicTCP}
|
||||
}
|
||||
if addr.Equal(lhquic) {
|
||||
return []ma.Multiaddr{publicQUIC}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },
|
||||
})
|
||||
am.triggerAddrsUpdate()
|
||||
expected := []ma.Multiaddr{lhquic, lhtcp, publicTCP, publicQUIC}
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
assert.ElementsMatch(collect, am.Addrs(), expected, "%s\n%s", am.Addrs(), expected)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
})
|
||||
|
||||
t.Run("observed addrs limit", func(t *testing.T) {
|
||||
quicAddrs := []ma.Multiaddr{
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/2/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/3/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/4/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/5/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/6/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/7/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/8/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/9/quic-v1"),
|
||||
ma.StringCast("/ip4/1.2.3.4/udp/10/quic-v1"),
|
||||
}
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
ObservedAddrsManager: &mockObservedAddrs{
|
||||
ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {
|
||||
return quicAddrs
|
||||
},
|
||||
},
|
||||
ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic} },
|
||||
})
|
||||
am.triggerAddrsUpdate()
|
||||
expected := []ma.Multiaddr{lhquic}
|
||||
expected = append(expected, quicAddrs[:maxObservedAddrsPerListenAddr]...)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
assert.ElementsMatch(collect, am.Addrs(), expected, "%s\n%s", am.Addrs(), expected)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
})
|
||||
t.Run("public addrs removed when private", func(t *testing.T) {
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
ObservedAddrsManager: &mockObservedAddrs{
|
||||
ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {
|
||||
return []ma.Multiaddr{publicQUIC}
|
||||
},
|
||||
},
|
||||
ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },
|
||||
})
|
||||
|
||||
// remove public addrs
|
||||
am.PushReachability(network.ReachabilityPrivate)
|
||||
relayAddr := ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1/p2p/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo/p2p-circuit")
|
||||
am.PushRelay([]ma.Multiaddr{relayAddr})
|
||||
|
||||
expectedAddrs := []ma.Multiaddr{relayAddr, lhquic, lhtcp}
|
||||
expectedAllAddrs := []ma.Multiaddr{publicQUIC, lhquic, lhtcp}
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
assert.ElementsMatch(collect, am.Addrs(), expectedAddrs, "%s\n%s", am.Addrs(), expectedAddrs)
|
||||
assert.ElementsMatch(collect, am.DirectAddrs(), expectedAllAddrs, "%s\n%s", am.DirectAddrs(), expectedAllAddrs)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
|
||||
// add public addrs
|
||||
am.PushReachability(network.ReachabilityPublic)
|
||||
|
||||
expectedAddrs = expectedAllAddrs
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
assert.ElementsMatch(collect, am.Addrs(), expectedAddrs, "%s\n%s", am.Addrs(), expectedAddrs)
|
||||
assert.ElementsMatch(collect, am.DirectAddrs(), expectedAllAddrs, "%s\n%s", am.DirectAddrs(), expectedAllAddrs)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
})
|
||||
|
||||
t.Run("addrs factory gets relay addrs", func(t *testing.T) {
|
||||
relayAddr := ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1/p2p/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo/p2p-circuit")
|
||||
publicQUIC2 := ma.StringCast("/ip4/1.2.3.4/udp/2/quic-v1")
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
AddrsFactory: func(addrs []ma.Multiaddr) []ma.Multiaddr {
|
||||
for _, a := range addrs {
|
||||
if a.Equal(relayAddr) {
|
||||
return []ma.Multiaddr{publicQUIC2}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ObservedAddrsManager: &mockObservedAddrs{
|
||||
ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr {
|
||||
return []ma.Multiaddr{publicQUIC}
|
||||
},
|
||||
},
|
||||
ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },
|
||||
})
|
||||
am.PushReachability(network.ReachabilityPrivate)
|
||||
am.PushRelay([]ma.Multiaddr{relayAddr})
|
||||
|
||||
expectedAddrs := []ma.Multiaddr{publicQUIC2}
|
||||
expectedAllAddrs := []ma.Multiaddr{publicQUIC, lhquic, lhtcp}
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
assert.ElementsMatch(collect, am.Addrs(), expectedAddrs, "%s\n%s", am.Addrs(), expectedAddrs)
|
||||
assert.ElementsMatch(collect, am.DirectAddrs(), expectedAllAddrs, "%s\n%s", am.DirectAddrs(), expectedAllAddrs)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
})
|
||||
|
||||
t.Run("updates addresses on signaling", func(t *testing.T) {
|
||||
updateChan := make(chan struct{})
|
||||
am := newAddrsManagerTestCase(t, addrsManagerArgs{
|
||||
AddrsFactory: func(addrs []ma.Multiaddr) []ma.Multiaddr {
|
||||
select {
|
||||
case <-updateChan:
|
||||
return []ma.Multiaddr{publicQUIC}
|
||||
default:
|
||||
return []ma.Multiaddr{publicTCP}
|
||||
}
|
||||
},
|
||||
ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} },
|
||||
})
|
||||
require.Contains(t, am.Addrs(), publicTCP)
|
||||
require.NotContains(t, am.Addrs(), publicQUIC)
|
||||
close(updateChan)
|
||||
am.triggerAddrsUpdate()
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
assert.Contains(collect, am.Addrs(), publicQUIC)
|
||||
assert.NotContains(collect, am.Addrs(), publicTCP)
|
||||
}, 1*time.Second, 50*time.Millisecond)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkAreAddrsDifferent(b *testing.B) {
|
||||
var addrs [10]ma.Multiaddr
|
||||
for i := 0; i < len(addrs); i++ {
|
||||
addrs[i] = ma.StringCast(fmt.Sprintf("/ip4/1.1.1.%d/tcp/1", i))
|
||||
}
|
||||
am := &addrsManager{}
|
||||
b.Run("areAddrsDifferent", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
am.areAddrsDifferent(addrs[:], addrs[:])
|
||||
}
|
||||
})
|
||||
}
|
@@ -5,10 +5,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/connmgr"
|
||||
@@ -22,7 +20,6 @@ import (
|
||||
"github.com/libp2p/go-libp2p/core/record"
|
||||
"github.com/libp2p/go-libp2p/core/transport"
|
||||
"github.com/libp2p/go-libp2p/p2p/host/autonat"
|
||||
"github.com/libp2p/go-libp2p/p2p/host/basic/internal/backoff"
|
||||
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
|
||||
"github.com/libp2p/go-libp2p/p2p/host/pstoremanager"
|
||||
"github.com/libp2p/go-libp2p/p2p/host/relaysvc"
|
||||
@@ -35,8 +32,6 @@ import (
|
||||
libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/libp2p/go-netroute"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
@@ -81,13 +76,10 @@ type BasicHost struct {
|
||||
ids identify.IDService
|
||||
hps *holepunch.Service
|
||||
pings *ping.PingService
|
||||
natmgr NATManager
|
||||
cmgr connmgr.ConnManager
|
||||
eventbus event.Bus
|
||||
relayManager *relaysvc.RelayManager
|
||||
|
||||
AddrsFactory AddrsFactory
|
||||
|
||||
negtimeout time.Duration
|
||||
|
||||
emitters struct {
|
||||
@@ -95,23 +87,16 @@ type BasicHost struct {
|
||||
evtLocalAddrsUpdated event.Emitter
|
||||
}
|
||||
|
||||
addrChangeChan chan struct{}
|
||||
|
||||
addrMu sync.RWMutex // protects the following fields
|
||||
updateLocalIPv4Backoff backoff.ExpBackoff
|
||||
updateLocalIPv6Backoff backoff.ExpBackoff
|
||||
filteredInterfaceAddrs []ma.Multiaddr
|
||||
allInterfaceAddrs []ma.Multiaddr
|
||||
|
||||
relayAddrs atomic.Pointer[[]ma.Multiaddr]
|
||||
|
||||
disableSignedPeerRecord bool
|
||||
signKey crypto.PrivKey
|
||||
caBook peerstore.CertifiedAddrBook
|
||||
|
||||
autoNat autonat.AutoNAT
|
||||
autoNATMx sync.RWMutex
|
||||
autoNat autonat.AutoNAT
|
||||
|
||||
autonatv2 *autonatv2.AutoNAT
|
||||
autonatv2 *autonatv2.AutoNAT
|
||||
addressManager *addrsManager
|
||||
addrsUpdatedChan chan struct{}
|
||||
}
|
||||
|
||||
var _ host.Host = (*BasicHost)(nil)
|
||||
@@ -195,18 +180,13 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
|
||||
psManager: psManager,
|
||||
mux: msmux.NewMultistreamMuxer[protocol.ID](),
|
||||
negtimeout: DefaultNegotiationTimeout,
|
||||
AddrsFactory: DefaultAddrsFactory,
|
||||
eventbus: opts.EventBus,
|
||||
addrChangeChan: make(chan struct{}, 1),
|
||||
ctx: hostCtx,
|
||||
ctxCancel: cancel,
|
||||
disableSignedPeerRecord: opts.DisableSignedPeerRecord,
|
||||
addrsUpdatedChan: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
var relayAddrs []ma.Multiaddr
|
||||
h.relayAddrs.Store(&relayAddrs)
|
||||
h.updateLocalIpAddr()
|
||||
|
||||
if h.emitters.evtLocalProtocolsUpdated, err = h.eventbus.Emitter(&event.EvtLocalProtocolsUpdated{}, eventbus.Stateful); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -214,32 +194,6 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !h.disableSignedPeerRecord {
|
||||
cab, ok := peerstore.GetCertifiedAddrBook(n.Peerstore())
|
||||
if !ok {
|
||||
return nil, errors.New("peerstore should also be a certified address book")
|
||||
}
|
||||
h.caBook = cab
|
||||
|
||||
h.signKey = h.Peerstore().PrivKey(h.ID())
|
||||
if h.signKey == nil {
|
||||
return nil, errors.New("unable to access host key")
|
||||
}
|
||||
|
||||
// persist a signed peer record for self to the peerstore.
|
||||
rec := peer.PeerRecordFromAddrInfo(peer.AddrInfo{
|
||||
ID: h.ID(),
|
||||
Addrs: h.Addrs(),
|
||||
})
|
||||
ev, err := record.Seal(rec, h.signKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create signed record for self: %w", err)
|
||||
}
|
||||
if _, err := cab.ConsumePeerRecord(ev, peerstore.PermanentAddrTTL); err != nil {
|
||||
return nil, fmt.Errorf("failed to persist signed record to peerstore: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.MultistreamMuxer != nil {
|
||||
h.mux = opts.MultistreamMuxer
|
||||
}
|
||||
@@ -267,6 +221,29 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
|
||||
return nil, fmt.Errorf("failed to create Identify service: %s", err)
|
||||
}
|
||||
|
||||
addrFactory := DefaultAddrsFactory
|
||||
if opts.AddrsFactory != nil {
|
||||
addrFactory = opts.AddrsFactory
|
||||
}
|
||||
|
||||
var natmgr NATManager
|
||||
if opts.NATManager != nil {
|
||||
natmgr = opts.NATManager(h.Network())
|
||||
}
|
||||
var tfl func(ma.Multiaddr) transport.Transport
|
||||
if s, ok := h.Network().(interface {
|
||||
TransportForListening(ma.Multiaddr) transport.Transport
|
||||
}); ok {
|
||||
tfl = s.TransportForListening
|
||||
}
|
||||
h.addressManager, err = newAddrsManager(h.eventbus, natmgr, addrFactory, h.Network().ListenAddresses, tfl, h.ids, h.addrsUpdatedChan)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create address service: %w", err)
|
||||
}
|
||||
// register to be notified when the network's listen addrs change,
|
||||
// so we can update our address set and push events if needed
|
||||
h.Network().Notify(h.addressManager.NetNotifee())
|
||||
|
||||
if opts.EnableHolePunching {
|
||||
if opts.EnableMetrics {
|
||||
hpOpts := []holepunch.Option{
|
||||
@@ -274,16 +251,7 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
|
||||
opts.HolePunchingOptions = append(hpOpts, opts.HolePunchingOptions...)
|
||||
|
||||
}
|
||||
h.hps, err = holepunch.NewService(h, h.ids, func() []ma.Multiaddr {
|
||||
addrs := h.AllAddrs()
|
||||
if opts.AddrsFactory != nil {
|
||||
addrs = slices.Clone(opts.AddrsFactory(addrs))
|
||||
}
|
||||
// AllAddrs may ignore observed addresses in favour of NAT mappings. Use both for hole punching.
|
||||
addrs = append(addrs, h.ids.OwnObservedAddrs()...)
|
||||
addrs = ma.Unique(addrs)
|
||||
return slices.DeleteFunc(addrs, func(a ma.Multiaddr) bool { return !manet.IsPublicAddr(a) })
|
||||
}, opts.HolePunchingOptions...)
|
||||
h.hps, err = holepunch.NewService(h, h.ids, h.addressManager.HolePunchAddrs, opts.HolePunchingOptions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create hole punch service: %w", err)
|
||||
}
|
||||
@@ -293,14 +261,6 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
|
||||
h.negtimeout = opts.NegotiationTimeout
|
||||
}
|
||||
|
||||
if opts.AddrsFactory != nil {
|
||||
h.AddrsFactory = opts.AddrsFactory
|
||||
}
|
||||
|
||||
if opts.NATManager != nil {
|
||||
h.natmgr = opts.NATManager(n)
|
||||
}
|
||||
|
||||
if opts.ConnManager == nil {
|
||||
h.cmgr = &connmgr.NullConnMgr{}
|
||||
} else {
|
||||
@@ -334,114 +294,55 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
|
||||
}
|
||||
}
|
||||
|
||||
n.SetStreamHandler(h.newStreamHandler)
|
||||
if !h.disableSignedPeerRecord {
|
||||
h.signKey = h.Peerstore().PrivKey(h.ID())
|
||||
cab, ok := peerstore.GetCertifiedAddrBook(h.Peerstore())
|
||||
if !ok {
|
||||
return nil, errors.New("peerstore should also be a certified address book")
|
||||
}
|
||||
h.caBook = cab
|
||||
|
||||
// register to be notified when the network's listen addrs change,
|
||||
// so we can update our address set and push events if needed
|
||||
listenHandler := func(network.Network, ma.Multiaddr) {
|
||||
h.SignalAddressChange()
|
||||
rec, err := h.makeSignedPeerRecord(h.addressManager.Addrs())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create signed record for self: %w", err)
|
||||
}
|
||||
if _, err := h.caBook.ConsumePeerRecord(rec, peerstore.PermanentAddrTTL); err != nil {
|
||||
return nil, fmt.Errorf("failed to persist signed record to peerstore: %w", err)
|
||||
}
|
||||
}
|
||||
n.Notify(&network.NotifyBundle{
|
||||
ListenF: listenHandler,
|
||||
ListenCloseF: listenHandler,
|
||||
})
|
||||
n.SetStreamHandler(h.newStreamHandler)
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *BasicHost) updateLocalIpAddr() {
|
||||
h.addrMu.Lock()
|
||||
defer h.addrMu.Unlock()
|
||||
|
||||
h.filteredInterfaceAddrs = nil
|
||||
h.allInterfaceAddrs = nil
|
||||
|
||||
// Try to use the default ipv4/6 addresses.
|
||||
|
||||
if r, err := netroute.New(); err != nil {
|
||||
log.Debugw("failed to build Router for kernel's routing table", "error", err)
|
||||
} else {
|
||||
|
||||
var localIPv4 net.IP
|
||||
var ran bool
|
||||
err, ran = h.updateLocalIPv4Backoff.Run(func() error {
|
||||
_, _, localIPv4, err = r.Route(net.IPv4zero)
|
||||
return err
|
||||
})
|
||||
|
||||
if ran && err != nil {
|
||||
log.Debugw("failed to fetch local IPv4 address", "error", err)
|
||||
} else if ran && localIPv4.IsGlobalUnicast() {
|
||||
maddr, err := manet.FromIP(localIPv4)
|
||||
if err == nil {
|
||||
h.filteredInterfaceAddrs = append(h.filteredInterfaceAddrs, maddr)
|
||||
}
|
||||
}
|
||||
|
||||
var localIPv6 net.IP
|
||||
err, ran = h.updateLocalIPv6Backoff.Run(func() error {
|
||||
_, _, localIPv6, err = r.Route(net.IPv6unspecified)
|
||||
return err
|
||||
})
|
||||
|
||||
if ran && err != nil {
|
||||
log.Debugw("failed to fetch local IPv6 address", "error", err)
|
||||
} else if ran && localIPv6.IsGlobalUnicast() {
|
||||
maddr, err := manet.FromIP(localIPv6)
|
||||
if err == nil {
|
||||
h.filteredInterfaceAddrs = append(h.filteredInterfaceAddrs, maddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the interface addresses
|
||||
ifaceAddrs, err := manet.InterfaceMultiaddrs()
|
||||
if err != nil {
|
||||
// This usually shouldn't happen, but we could be in some kind
|
||||
// of funky restricted environment.
|
||||
log.Errorw("failed to resolve local interface addresses", "error", err)
|
||||
|
||||
// Add the loopback addresses to the filtered addrs and use them as the non-filtered addrs.
|
||||
// Then bail. There's nothing else we can do here.
|
||||
h.filteredInterfaceAddrs = append(h.filteredInterfaceAddrs, manet.IP4Loopback, manet.IP6Loopback)
|
||||
h.allInterfaceAddrs = h.filteredInterfaceAddrs
|
||||
return
|
||||
}
|
||||
|
||||
for _, addr := range ifaceAddrs {
|
||||
// Skip link-local addrs, they're mostly useless.
|
||||
if !manet.IsIP6LinkLocal(addr) {
|
||||
h.allInterfaceAddrs = append(h.allInterfaceAddrs, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// If netroute failed to get us any interface addresses, use all of
|
||||
// them.
|
||||
if len(h.filteredInterfaceAddrs) == 0 {
|
||||
// Add all addresses.
|
||||
h.filteredInterfaceAddrs = h.allInterfaceAddrs
|
||||
} else {
|
||||
// Only add loopback addresses. Filter these because we might
|
||||
// not _have_ an IPv6 loopback address.
|
||||
for _, addr := range h.allInterfaceAddrs {
|
||||
if manet.IsIPLoopback(addr) {
|
||||
h.filteredInterfaceAddrs = append(h.filteredInterfaceAddrs, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts background tasks in the host
|
||||
// TODO: Return error and handle it in the caller?
|
||||
func (h *BasicHost) Start() {
|
||||
h.psManager.Start()
|
||||
h.refCount.Add(1)
|
||||
h.ids.Start()
|
||||
if h.autonatv2 != nil {
|
||||
err := h.autonatv2.Start()
|
||||
if err != nil {
|
||||
log.Errorf("autonat v2 failed to start: %s", err)
|
||||
}
|
||||
}
|
||||
if err := h.addressManager.Start(); err != nil {
|
||||
log.Errorf("address service failed to start: %s", err)
|
||||
}
|
||||
|
||||
if !h.disableSignedPeerRecord {
|
||||
// Ensure we have the correct peer record after Start returns
|
||||
rec, err := h.makeSignedPeerRecord(h.addressManager.Addrs())
|
||||
if err != nil {
|
||||
log.Errorf("failed to create signed record: %w", err)
|
||||
}
|
||||
if _, err := h.caBook.ConsumePeerRecord(rec, peerstore.PermanentAddrTTL); err != nil {
|
||||
log.Errorf("failed to persist signed record to peerstore: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
h.ids.Start()
|
||||
|
||||
h.refCount.Add(1)
|
||||
go h.background()
|
||||
}
|
||||
|
||||
@@ -493,16 +394,6 @@ func (h *BasicHost) newStreamHandler(s network.Stream) {
|
||||
handle(protoID, s)
|
||||
}
|
||||
|
||||
// SignalAddressChange signals to the host that it needs to determine whether our listen addresses have recently
|
||||
// changed.
|
||||
// Warning: this interface is unstable and may disappear in the future.
|
||||
func (h *BasicHost) SignalAddressChange() {
|
||||
select {
|
||||
case h.addrChangeChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BasicHost) makeUpdatedAddrEvent(prev, current []ma.Multiaddr) *event.EvtLocalAddressesUpdated {
|
||||
if prev == nil && current == nil {
|
||||
return nil
|
||||
@@ -601,39 +492,13 @@ func (h *BasicHost) background() {
|
||||
}
|
||||
}
|
||||
|
||||
addrSub, err := h.EventBus().Subscribe(new(event.EvtAutoRelayAddrsUpdated), eventbus.Name("basic host address loop"))
|
||||
if err != nil {
|
||||
log.Errorf("failed to subscribe to the EvtAutoRelayAddrs: %s", err)
|
||||
return
|
||||
}
|
||||
defer addrSub.Close()
|
||||
|
||||
// periodically schedules an IdentifyPush to update our peers for changes
|
||||
// in our address set (if needed)
|
||||
ticker := time.NewTicker(addrChangeTickrInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
// Update our local IP addresses before checking our current addresses.
|
||||
if len(h.network.ListenAddresses()) > 0 {
|
||||
h.updateLocalIpAddr()
|
||||
}
|
||||
curr := h.Addrs()
|
||||
emitAddrChange(curr, lastAddrs)
|
||||
lastAddrs = curr
|
||||
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-h.addrChangeChan:
|
||||
case e := <-addrSub.Out():
|
||||
if evt, ok := e.(event.EvtAutoRelayAddrsUpdated); ok {
|
||||
// Copy to a new slice. Copying to the slice pointed to by relayAddrs
|
||||
// will introduce a race.
|
||||
addrs := slices.Clone(evt.RelayAddrs)
|
||||
h.relayAddrs.Store(&addrs)
|
||||
} else {
|
||||
log.Errorf("received unexpected event: %T %v", e, e)
|
||||
}
|
||||
case <-h.addrsUpdatedChan:
|
||||
case <-h.ctx.Done():
|
||||
return
|
||||
}
|
||||
@@ -864,19 +729,7 @@ func (h *BasicHost) ConnManager() connmgr.ConnManager {
|
||||
// When used with AutoRelay, and if the host is not publicly reachable,
|
||||
// this will only have host's private, relay, and no public addresses.
|
||||
func (h *BasicHost) Addrs() []ma.Multiaddr {
|
||||
addrs := h.AllAddrs()
|
||||
// Make a copy. Consumers can modify the slice elements
|
||||
if h.GetAutoNat() != nil && h.GetAutoNat().Status() == network.ReachabilityPrivate {
|
||||
relayAddrsPtr := h.relayAddrs.Load()
|
||||
// Only remove public addresses if we have relay addresses.
|
||||
if relayAddrsPtr != nil && len(*relayAddrsPtr) > 0 {
|
||||
addrs = slices.DeleteFunc(addrs, func(a ma.Multiaddr) bool { return manet.IsPublicAddr(a) })
|
||||
addrs = append(addrs, *relayAddrsPtr...)
|
||||
}
|
||||
}
|
||||
addrs = slices.Clone(h.AddrsFactory(addrs))
|
||||
// Add certhashes for the addresses provided by the user via address factory.
|
||||
return h.addCertHashes(ma.Unique(addrs))
|
||||
return h.addressManager.Addrs()
|
||||
}
|
||||
|
||||
// NormalizeMultiaddr returns a multiaddr suitable for equality checks.
|
||||
@@ -896,149 +749,9 @@ func (h *BasicHost) NormalizeMultiaddr(addr ma.Multiaddr) ma.Multiaddr {
|
||||
return addr
|
||||
}
|
||||
|
||||
var p2pCircuitAddr = ma.StringCast("/p2p-circuit")
|
||||
|
||||
// AllAddrs returns all the addresses the host is listening on except circuit addresses.
|
||||
func (h *BasicHost) AllAddrs() []ma.Multiaddr {
|
||||
listenAddrs := h.Network().ListenAddresses()
|
||||
if len(listenAddrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
h.addrMu.RLock()
|
||||
filteredIfaceAddrs := h.filteredInterfaceAddrs
|
||||
allIfaceAddrs := h.allInterfaceAddrs
|
||||
h.addrMu.RUnlock()
|
||||
|
||||
// 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)
|
||||
|
||||
// use nat mappings if we have them
|
||||
if h.natmgr != nil && h.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 := h.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, allIfaceAddrs)
|
||||
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 := h.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.Multiaddr()) {
|
||||
continue
|
||||
}
|
||||
|
||||
finalAddrs = append(finalAddrs, ip.Encapsulate(extMaddrNoIP))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var observedAddrs []ma.Multiaddr
|
||||
if h.ids != nil {
|
||||
observedAddrs = h.ids.OwnObservedAddrs()
|
||||
}
|
||||
finalAddrs = append(finalAddrs, observedAddrs...)
|
||||
}
|
||||
finalAddrs = ma.Unique(finalAddrs)
|
||||
// Remove /p2p-circuit addresses from the list.
|
||||
// The p2p-circuit tranport 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 {
|
||||
return a.Equal(p2pCircuitAddr)
|
||||
})
|
||||
// Add certhashes for /webrtc-direct, /webtransport, etc addresses discovered
|
||||
// using identify.
|
||||
finalAddrs = h.addCertHashes(finalAddrs)
|
||||
return finalAddrs
|
||||
}
|
||||
|
||||
// addCertHashes adds certhashes to the relevant addresses. It modifies `addrs` in place.
|
||||
func (h *BasicHost) 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.
|
||||
type transportForListeninger interface {
|
||||
TransportForListening(a ma.Multiaddr) transport.Transport
|
||||
}
|
||||
|
||||
type addCertHasher interface {
|
||||
AddCertHashes(m ma.Multiaddr) (ma.Multiaddr, bool)
|
||||
}
|
||||
|
||||
s, ok := h.Network().(transportForListeninger)
|
||||
if !ok {
|
||||
return addrs
|
||||
}
|
||||
|
||||
for i, addr := range addrs {
|
||||
wtOK, wtN := libp2pwebtransport.IsWebtransportMultiaddr(addr)
|
||||
webrtcOK, webrtcN := libp2pwebrtc.IsWebRTCDirectMultiaddr(addr)
|
||||
if (wtOK && wtN == 0) || (webrtcOK && webrtcN == 0) {
|
||||
t := s.TransportForListening(addr)
|
||||
tpt, ok := t.(addCertHasher)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
addrWithCerthash, added := tpt.AddCertHashes(addr)
|
||||
if !added {
|
||||
log.Debugf("Couldn't add certhashes to multiaddr: %s", addr)
|
||||
continue
|
||||
}
|
||||
addrs[i] = addrWithCerthash
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
return h.addressManager.DirectAddrs()
|
||||
}
|
||||
|
||||
func trimHostAddrList(addrs []ma.Multiaddr, maxSize int) []ma.Multiaddr {
|
||||
@@ -1095,30 +808,37 @@ func trimHostAddrList(addrs []ma.Multiaddr, maxSize int) []ma.Multiaddr {
|
||||
|
||||
// SetAutoNat sets the autonat service for the host.
|
||||
func (h *BasicHost) SetAutoNat(a autonat.AutoNAT) {
|
||||
h.addrMu.Lock()
|
||||
defer h.addrMu.Unlock()
|
||||
h.autoNATMx.Lock()
|
||||
defer h.autoNATMx.Unlock()
|
||||
if h.autoNat == nil {
|
||||
h.autoNat = a
|
||||
}
|
||||
}
|
||||
|
||||
// GetAutoNat returns the host's AutoNAT service, if AutoNAT is enabled.
|
||||
//
|
||||
// Deprecated: Use `BasicHost.Reachability` to get the host's reachability.
|
||||
func (h *BasicHost) GetAutoNat() autonat.AutoNAT {
|
||||
h.addrMu.Lock()
|
||||
defer h.addrMu.Unlock()
|
||||
h.autoNATMx.Lock()
|
||||
defer h.autoNATMx.Unlock()
|
||||
return h.autoNat
|
||||
}
|
||||
|
||||
// Reachability returns the host's reachability status.
|
||||
func (h *BasicHost) Reachability() network.Reachability {
|
||||
return *h.addressManager.hostReachability.Load()
|
||||
}
|
||||
|
||||
// Close shuts down the Host's services (network, etc).
|
||||
func (h *BasicHost) Close() error {
|
||||
h.closeSync.Do(func() {
|
||||
h.ctxCancel()
|
||||
if h.natmgr != nil {
|
||||
h.natmgr.Close()
|
||||
}
|
||||
if h.cmgr != nil {
|
||||
h.cmgr.Close()
|
||||
}
|
||||
|
||||
h.addressManager.Close()
|
||||
|
||||
if h.ids != nil {
|
||||
h.ids.Close()
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -206,29 +207,6 @@ func TestHostAddrsFactory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalIPChangesWhenListenAddrChanges(t *testing.T) {
|
||||
// no listen addrs
|
||||
h, err := NewHost(swarmt.GenSwarm(t, swarmt.OptDialOnly), nil)
|
||||
require.NoError(t, err)
|
||||
h.Start()
|
||||
defer h.Close()
|
||||
|
||||
h.addrMu.Lock()
|
||||
h.filteredInterfaceAddrs = nil
|
||||
h.allInterfaceAddrs = nil
|
||||
h.addrMu.Unlock()
|
||||
|
||||
// change listen addrs and verify local IP addr is not nil again
|
||||
require.NoError(t, h.Network().Listen(ma.StringCast("/ip4/0.0.0.0/tcp/0")))
|
||||
h.SignalAddressChange()
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
h.addrMu.RLock()
|
||||
defer h.addrMu.RUnlock()
|
||||
require.NotEmpty(t, h.filteredInterfaceAddrs)
|
||||
require.NotEmpty(t, h.allInterfaceAddrs)
|
||||
}
|
||||
|
||||
func TestAllAddrs(t *testing.T) {
|
||||
// no listen addrs
|
||||
h, err := NewHost(swarmt.GenSwarm(t, swarmt.OptDialOnly), nil)
|
||||
@@ -620,8 +598,13 @@ func TestAddrChangeImmediatelyIfAddressNonEmpty(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
taddrs := []ma.Multiaddr{ma.StringCast("/ip4/1.2.3.4/tcp/1234")}
|
||||
|
||||
starting := make(chan struct{})
|
||||
starting := make(chan struct{}, 1)
|
||||
var count atomic.Int32
|
||||
h, err := NewHost(swarmt.GenSwarm(t), &HostOpts{AddrsFactory: func(addrs []ma.Multiaddr) []ma.Multiaddr {
|
||||
// The first call here is made from the constructor. Don't block.
|
||||
if count.Add(1) == 1 {
|
||||
return addrs
|
||||
}
|
||||
<-starting
|
||||
return taddrs
|
||||
}})
|
||||
@@ -629,11 +612,11 @@ func TestAddrChangeImmediatelyIfAddressNonEmpty(t *testing.T) {
|
||||
defer h.Close()
|
||||
|
||||
sub, err := h.EventBus().Subscribe(&event.EvtLocalAddressesUpdated{})
|
||||
close(starting)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer sub.Close()
|
||||
close(starting)
|
||||
h.Start()
|
||||
|
||||
expected := event.EvtLocalAddressesUpdated{
|
||||
@@ -752,7 +735,7 @@ func TestHostAddrChangeDetection(t *testing.T) {
|
||||
lk.Lock()
|
||||
currentAddrSet = i
|
||||
lk.Unlock()
|
||||
h.SignalAddressChange()
|
||||
h.addressManager.triggerAddrsUpdate()
|
||||
evt := waitForAddrChangeEvent(ctx, sub, t)
|
||||
if !updatedAddrEventsEqual(expectedEvents[i-1], evt) {
|
||||
t.Errorf("change events not equal: \n\texpected: %v \n\tactual: %v", expectedEvents[i-1], evt)
|
||||
|
@@ -33,6 +33,7 @@ func TestRoutedHostConnectToObsoleteAddresses(t *testing.T) {
|
||||
h2, err := basic.NewHost(swarmt.GenSwarm(t), nil)
|
||||
require.NoError(t, err)
|
||||
defer h2.Close()
|
||||
h2.Start()
|
||||
|
||||
// assemble the AddrInfo struct to use for the connection attempt
|
||||
pi := peer.AddrInfo{
|
||||
|
Reference in New Issue
Block a user