mirror of
https://github.com/libp2p/go-libp2p.git
synced 2025-09-27 04:26:41 +08:00

This introduces addrsReachabilityTracker that tracks reachability on a set of addresses. It probes reachability for addresses periodically and has an exponential backoff in case there are too many errors or we don't have any valid autonatv2 peer. There's no smartness in the address selection logic currently. We just test all provided addresses. It also doesn't use the addresses provided by `AddrsFactory`, so currently there's no way to get a user provided address tested for reachability, something that would be a problem for dns addresses. I intend to introduce an alternative to `AddrsFactory`, something like, `AnnounceAddrs(addrs []ma.Multiaddr)` that's just appended to the set of addresses that we have, and check reachability for those addresses. There's only one method exposed in the BasicHost right now that's `ReachableAddrs() []ma.Multiadd`r that returns the host's reachable addrs. Users can also use the event `EvtHostReachableAddrsChanged` to be notified when any addrs reachability changes.
707 lines
23 KiB
Go
707 lines
23 KiB
Go
package basichost
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"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"
|
|
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
|
|
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 hostAddrs struct {
|
|
addrs []ma.Multiaddr
|
|
localAddrs []ma.Multiaddr
|
|
reachableAddrs []ma.Multiaddr
|
|
unreachableAddrs []ma.Multiaddr
|
|
relayAddrs []ma.Multiaddr
|
|
}
|
|
|
|
type addrsManager struct {
|
|
bus event.Bus
|
|
natManager NATManager
|
|
addrsFactory AddrsFactory
|
|
listenAddrs func() []ma.Multiaddr
|
|
transportForListening func(ma.Multiaddr) transport.Transport
|
|
observedAddrsManager observedAddrsManager
|
|
interfaceAddrs *interfaceAddrsCache
|
|
addrsReachabilityTracker *addrsReachabilityTracker
|
|
|
|
// addrsUpdatedChan is notified when addrs change. This is provided by the caller.
|
|
addrsUpdatedChan chan struct{}
|
|
|
|
// triggerAddrsUpdateChan is used to trigger an addresses update.
|
|
triggerAddrsUpdateChan chan struct{}
|
|
// triggerReachabilityUpdate is notified when reachable addrs are updated.
|
|
triggerReachabilityUpdate chan struct{}
|
|
|
|
hostReachability atomic.Pointer[network.Reachability]
|
|
|
|
addrsMx sync.RWMutex
|
|
currentAddrs hostAddrs
|
|
|
|
wg sync.WaitGroup
|
|
ctx context.Context
|
|
ctxCancel context.CancelFunc
|
|
}
|
|
|
|
func newAddrsManager(
|
|
bus event.Bus,
|
|
natmgr NATManager,
|
|
addrsFactory AddrsFactory,
|
|
listenAddrs func() []ma.Multiaddr,
|
|
transportForListening func(ma.Multiaddr) transport.Transport,
|
|
observedAddrsManager observedAddrsManager,
|
|
addrsUpdatedChan chan struct{},
|
|
client autonatv2Client,
|
|
) (*addrsManager, error) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
as := &addrsManager{
|
|
bus: bus,
|
|
listenAddrs: listenAddrs,
|
|
transportForListening: transportForListening,
|
|
observedAddrsManager: observedAddrsManager,
|
|
natManager: natmgr,
|
|
addrsFactory: addrsFactory,
|
|
triggerAddrsUpdateChan: make(chan struct{}, 1),
|
|
triggerReachabilityUpdate: make(chan struct{}, 1),
|
|
addrsUpdatedChan: addrsUpdatedChan,
|
|
interfaceAddrs: &interfaceAddrsCache{},
|
|
ctx: ctx,
|
|
ctxCancel: cancel,
|
|
}
|
|
unknownReachability := network.ReachabilityUnknown
|
|
as.hostReachability.Store(&unknownReachability)
|
|
|
|
if client != nil {
|
|
as.addrsReachabilityTracker = newAddrsReachabilityTracker(client, as.triggerReachabilityUpdate, nil)
|
|
}
|
|
return as, nil
|
|
}
|
|
|
|
func (a *addrsManager) Start() error {
|
|
// TODO: add Start method to NATMgr
|
|
if a.addrsReachabilityTracker != nil {
|
|
err := a.addrsReachabilityTracker.Start()
|
|
if err != nil {
|
|
return fmt.Errorf("error starting addrs reachability tracker: %s", err)
|
|
}
|
|
}
|
|
|
|
return a.startBackgroundWorker()
|
|
}
|
|
|
|
func (a *addrsManager) Close() {
|
|
a.ctxCancel()
|
|
if a.natManager != nil {
|
|
err := a.natManager.Close()
|
|
if err != nil {
|
|
log.Warnf("error closing natmgr: %s", err)
|
|
}
|
|
}
|
|
if a.addrsReachabilityTracker != nil {
|
|
err := a.addrsReachabilityTracker.Close()
|
|
if err != nil {
|
|
log.Warnf("error closing addrs reachability tracker: %s", err)
|
|
}
|
|
}
|
|
a.wg.Wait()
|
|
}
|
|
|
|
func (a *addrsManager) NetNotifee() network.Notifiee {
|
|
// Updating addrs in sync provides the nice property that
|
|
// host.Addrs() just after host.Network().Listen(x) will return x
|
|
return &network.NotifyBundle{
|
|
ListenF: func(network.Network, ma.Multiaddr) { a.triggerAddrsUpdate() },
|
|
ListenCloseF: func(network.Network, ma.Multiaddr) { a.triggerAddrsUpdate() },
|
|
}
|
|
}
|
|
|
|
func (a *addrsManager) triggerAddrsUpdate() {
|
|
a.updateAddrs(false, nil)
|
|
select {
|
|
case a.triggerAddrsUpdateChan <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (a *addrsManager) startBackgroundWorker() error {
|
|
autoRelayAddrsSub, err := a.bus.Subscribe(new(event.EvtAutoRelayAddrsUpdated), eventbus.Name("addrs-manager"))
|
|
if err != nil {
|
|
return fmt.Errorf("error subscribing to auto relay addrs: %s", err)
|
|
}
|
|
|
|
autonatReachabilitySub, err := a.bus.Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name("addrs-manager"))
|
|
if err != nil {
|
|
err1 := autoRelayAddrsSub.Close()
|
|
if err1 != nil {
|
|
err1 = fmt.Errorf("error closign autorelaysub: %w", err1)
|
|
}
|
|
err = fmt.Errorf("error subscribing to autonat reachability: %s", err)
|
|
return errors.Join(err, err1)
|
|
}
|
|
|
|
emitter, err := a.bus.Emitter(new(event.EvtHostReachableAddrsChanged), eventbus.Stateful)
|
|
if err != nil {
|
|
err1 := autoRelayAddrsSub.Close()
|
|
if err1 != nil {
|
|
err1 = fmt.Errorf("error closing autorelaysub: %w", err1)
|
|
}
|
|
err2 := autonatReachabilitySub.Close()
|
|
if err2 != nil {
|
|
err2 = fmt.Errorf("error closing autonat reachability: %w", err1)
|
|
}
|
|
err = fmt.Errorf("error subscribing to autonat reachability: %s", err)
|
|
return errors.Join(err, err1, err2)
|
|
}
|
|
|
|
var relayAddrs []ma.Multiaddr
|
|
// update relay addrs in case we're private
|
|
select {
|
|
case e := <-autoRelayAddrsSub.Out():
|
|
if evt, ok := e.(event.EvtAutoRelayAddrsUpdated); ok {
|
|
relayAddrs = slices.Clone(evt.RelayAddrs)
|
|
}
|
|
default:
|
|
}
|
|
|
|
select {
|
|
case e := <-autonatReachabilitySub.Out():
|
|
if evt, ok := e.(event.EvtLocalReachabilityChanged); ok {
|
|
a.hostReachability.Store(&evt.Reachability)
|
|
}
|
|
default:
|
|
}
|
|
// update addresses before starting the worker loop. This ensures that any address updates
|
|
// before calling addrsManager.Start are correctly reported after Start returns.
|
|
a.updateAddrs(true, relayAddrs)
|
|
|
|
a.wg.Add(1)
|
|
go a.background(autoRelayAddrsSub, autonatReachabilitySub, emitter, relayAddrs)
|
|
return nil
|
|
}
|
|
|
|
func (a *addrsManager) background(autoRelayAddrsSub, autonatReachabilitySub event.Subscription,
|
|
emitter event.Emitter, relayAddrs []ma.Multiaddr,
|
|
) {
|
|
defer a.wg.Done()
|
|
defer func() {
|
|
err := autoRelayAddrsSub.Close()
|
|
if err != nil {
|
|
log.Warnf("error closing auto relay addrs sub: %s", err)
|
|
}
|
|
err = autonatReachabilitySub.Close()
|
|
if err != nil {
|
|
log.Warnf("error closing autonat reachability sub: %s", err)
|
|
}
|
|
}()
|
|
|
|
ticker := time.NewTicker(addrChangeTickrInterval)
|
|
defer ticker.Stop()
|
|
var previousAddrs hostAddrs
|
|
for {
|
|
currAddrs := a.updateAddrs(true, relayAddrs)
|
|
a.notifyAddrsChanged(emitter, previousAddrs, currAddrs)
|
|
previousAddrs = currAddrs
|
|
select {
|
|
case <-ticker.C:
|
|
case <-a.triggerAddrsUpdateChan:
|
|
case <-a.triggerReachabilityUpdate:
|
|
case e := <-autoRelayAddrsSub.Out():
|
|
if evt, ok := e.(event.EvtAutoRelayAddrsUpdated); ok {
|
|
relayAddrs = slices.Clone(evt.RelayAddrs)
|
|
}
|
|
case e := <-autonatReachabilitySub.Out():
|
|
if evt, ok := e.(event.EvtLocalReachabilityChanged); ok {
|
|
a.hostReachability.Store(&evt.Reachability)
|
|
}
|
|
case <-a.ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateAddrs updates the addresses of the host and returns the new updated
|
|
// addrs
|
|
func (a *addrsManager) updateAddrs(updateRelayAddrs bool, relayAddrs []ma.Multiaddr) hostAddrs {
|
|
// Must lock while doing both recompute and update as this method is called from
|
|
// multiple goroutines.
|
|
a.addrsMx.Lock()
|
|
defer a.addrsMx.Unlock()
|
|
|
|
localAddrs := a.getLocalAddrs()
|
|
var currReachableAddrs, currUnreachableAddrs []ma.Multiaddr
|
|
if a.addrsReachabilityTracker != nil {
|
|
currReachableAddrs, currUnreachableAddrs = a.getConfirmedAddrs(localAddrs)
|
|
}
|
|
if !updateRelayAddrs {
|
|
relayAddrs = a.currentAddrs.relayAddrs
|
|
} else {
|
|
// Copy the callers slice
|
|
relayAddrs = slices.Clone(relayAddrs)
|
|
}
|
|
currAddrs := a.getAddrs(slices.Clone(localAddrs), relayAddrs)
|
|
|
|
a.currentAddrs = hostAddrs{
|
|
addrs: append(a.currentAddrs.addrs[:0], currAddrs...),
|
|
localAddrs: append(a.currentAddrs.localAddrs[:0], localAddrs...),
|
|
reachableAddrs: append(a.currentAddrs.reachableAddrs[:0], currReachableAddrs...),
|
|
unreachableAddrs: append(a.currentAddrs.unreachableAddrs[:0], currUnreachableAddrs...),
|
|
relayAddrs: append(a.currentAddrs.relayAddrs[:0], relayAddrs...),
|
|
}
|
|
|
|
return hostAddrs{
|
|
localAddrs: localAddrs,
|
|
addrs: currAddrs,
|
|
reachableAddrs: currReachableAddrs,
|
|
unreachableAddrs: currUnreachableAddrs,
|
|
relayAddrs: relayAddrs,
|
|
}
|
|
}
|
|
|
|
func (a *addrsManager) notifyAddrsChanged(emitter event.Emitter, previous, current hostAddrs) {
|
|
if areAddrsDifferent(previous.localAddrs, current.localAddrs) {
|
|
log.Debugf("host local addresses updated: %s", current.localAddrs)
|
|
if a.addrsReachabilityTracker != nil {
|
|
a.addrsReachabilityTracker.UpdateAddrs(current.localAddrs)
|
|
}
|
|
}
|
|
if areAddrsDifferent(previous.addrs, current.addrs) {
|
|
log.Debugf("host addresses updated: %s", current.localAddrs)
|
|
select {
|
|
case a.addrsUpdatedChan <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// We *must* send both reachability changed and addrs changed events from the
|
|
// same goroutine to ensure correct ordering
|
|
// Consider the events:
|
|
// - addr x discovered
|
|
// - addr x is reachable
|
|
// - addr x removed
|
|
// We must send these events in the same order. It'll be confusing for consumers
|
|
// if the reachable event is received after the addr removed event.
|
|
if areAddrsDifferent(previous.reachableAddrs, current.reachableAddrs) ||
|
|
areAddrsDifferent(previous.unreachableAddrs, current.unreachableAddrs) {
|
|
log.Debugf("host reachable addrs updated: %s", current.localAddrs)
|
|
if err := emitter.Emit(event.EvtHostReachableAddrsChanged{
|
|
Reachable: slices.Clone(current.reachableAddrs),
|
|
Unreachable: slices.Clone(current.unreachableAddrs),
|
|
}); err != nil {
|
|
log.Errorf("error sending host reachable addrs changed event: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
a.addrsMx.RLock()
|
|
directAddrs := slices.Clone(a.currentAddrs.localAddrs)
|
|
relayAddrs := slices.Clone(a.currentAddrs.relayAddrs)
|
|
a.addrsMx.RUnlock()
|
|
return a.getAddrs(directAddrs, relayAddrs)
|
|
}
|
|
|
|
// getAddrs returns the node's dialable addresses. Mutates localAddrs
|
|
func (a *addrsManager) getAddrs(localAddrs []ma.Multiaddr, relayAddrs []ma.Multiaddr) []ma.Multiaddr {
|
|
addrs := localAddrs
|
|
rch := a.hostReachability.Load()
|
|
if rch != nil && *rch == network.ReachabilityPrivate {
|
|
// Delete public addresses if the node's reachability is private, and we have relay addresses
|
|
if len(relayAddrs) > 0 {
|
|
addrs = slices.DeleteFunc(addrs, manet.IsPublicAddr)
|
|
addrs = append(addrs, relayAddrs...)
|
|
}
|
|
}
|
|
// 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 all the host's direct public addresses, reachable or unreachable,
|
|
// suitable 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.currentAddrs.localAddrs)
|
|
}
|
|
|
|
// ReachableAddrs returns all addresses of the host that are reachable from the internet
|
|
func (a *addrsManager) ReachableAddrs() []ma.Multiaddr {
|
|
a.addrsMx.RLock()
|
|
defer a.addrsMx.RUnlock()
|
|
return slices.Clone(a.currentAddrs.reachableAddrs)
|
|
}
|
|
|
|
func (a *addrsManager) getConfirmedAddrs(localAddrs []ma.Multiaddr) (reachableAddrs, unreachableAddrs []ma.Multiaddr) {
|
|
reachableAddrs, unreachableAddrs = a.addrsReachabilityTracker.ConfirmedAddrs()
|
|
return removeNotInSource(reachableAddrs, localAddrs), removeNotInSource(unreachableAddrs, localAddrs)
|
|
}
|
|
|
|
var p2pCircuitAddr = ma.StringCast("/p2p-circuit")
|
|
|
|
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())
|
|
|
|
// 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)
|
|
finalAddrs = ma.Unique(finalAddrs)
|
|
slices.SortFunc(finalAddrs, func(a, b ma.Multiaddr) int { return a.Compare(b) })
|
|
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 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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// removeNotInSource removes items from addrs that are not present in source.
|
|
// Modifies the addrs slice in place
|
|
// addrs and source must be sorted using multiaddr.Compare.
|
|
func removeNotInSource(addrs, source []ma.Multiaddr) []ma.Multiaddr {
|
|
j := 0
|
|
// mark entries not in source as nil
|
|
for i, a := range addrs {
|
|
// move right as long as a > source[j]
|
|
for j < len(source) && a.Compare(source[j]) > 0 {
|
|
j++
|
|
}
|
|
// a is not in source if we've reached the end, or a is lesser
|
|
if j == len(source) || a.Compare(source[j]) < 0 {
|
|
addrs[i] = nil
|
|
}
|
|
// a is in source, nothing to do
|
|
}
|
|
// j is the current element, i is the lowest index nil element
|
|
i := 0
|
|
for j := range len(addrs) {
|
|
if addrs[j] != nil {
|
|
addrs[i], addrs[j] = addrs[j], addrs[i]
|
|
i++
|
|
}
|
|
}
|
|
return addrs[:i]
|
|
}
|