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:
sukun
2025-02-27 22:24:57 +05:30
committed by GitHub
parent 578af0c651
commit 5e6f217d84
6 changed files with 1095 additions and 387 deletions

View File

@@ -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()
}
}

View 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)
}
}
}
}

View 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[:])
}
})
}

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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{