|
|
@ -5,10 +5,12 @@ import ( |
|
|
|
"sync" |
|
|
|
"time" |
|
|
|
|
|
|
|
"github.com/libp2p/go-libp2p-core/host" |
|
|
|
"github.com/libp2p/go-libp2p-core/network" |
|
|
|
"github.com/libp2p/go-libp2p-core/peerstore" |
|
|
|
|
|
|
|
ma "github.com/multiformats/go-multiaddr" |
|
|
|
manet "github.com/multiformats/go-multiaddr-net" |
|
|
|
) |
|
|
|
|
|
|
|
// ActivationThresh sets how many times an address must be seen as "activated"
|
|
|
@ -24,9 +26,9 @@ var ActivationThresh = 4 |
|
|
|
// it is older than OwnObservedAddressTTL * ActivationThresh (40 minutes).
|
|
|
|
var GCInterval = 10 * time.Minute |
|
|
|
|
|
|
|
// observedAddrSetWorkerChannelSize defines how many addresses can be enqueued
|
|
|
|
// for adding to an ObservedAddrSet.
|
|
|
|
var observedAddrSetWorkerChannelSize = 16 |
|
|
|
// observedAddrManagerWorkerChannelSize defines how many addresses can be enqueued
|
|
|
|
// for adding to an ObservedAddrManager.
|
|
|
|
var observedAddrManagerWorkerChannelSize = 16 |
|
|
|
|
|
|
|
type observation struct { |
|
|
|
seenTime time.Time |
|
|
@ -52,40 +54,52 @@ func (oa *ObservedAddr) activated(ttl time.Duration) bool { |
|
|
|
} |
|
|
|
|
|
|
|
type newObservation struct { |
|
|
|
observed, local, observer ma.Multiaddr |
|
|
|
direction network.Direction |
|
|
|
conn network.Conn |
|
|
|
observed ma.Multiaddr |
|
|
|
} |
|
|
|
|
|
|
|
// ObservedAddrSet keeps track of a set of ObservedAddrs
|
|
|
|
// the zero-value is ready to be used.
|
|
|
|
type ObservedAddrSet struct { |
|
|
|
sync.RWMutex // guards whole datastruct.
|
|
|
|
// ObservedAddrManager keeps track of a ObservedAddrs.
|
|
|
|
type ObservedAddrManager struct { |
|
|
|
host host.Host |
|
|
|
|
|
|
|
// latest observation from active connections
|
|
|
|
// we'll "re-observe" these when we gc
|
|
|
|
activeConnsMu sync.Mutex |
|
|
|
// active connection -> most recent observation
|
|
|
|
activeConns map[network.Conn]ma.Multiaddr |
|
|
|
|
|
|
|
mu sync.RWMutex |
|
|
|
// local(internal) address -> list of observed(external) addresses
|
|
|
|
addrs map[string][]*ObservedAddr |
|
|
|
ttl time.Duration |
|
|
|
addrs map[string][]*ObservedAddr |
|
|
|
ttl time.Duration |
|
|
|
refreshTimer *time.Timer |
|
|
|
|
|
|
|
// this is the worker channel
|
|
|
|
wch chan newObservation |
|
|
|
} |
|
|
|
|
|
|
|
// NewObservedAddrSet returns a new set using peerstore.OwnObservedAddressTTL
|
|
|
|
// as the TTL.
|
|
|
|
func NewObservedAddrSet(ctx context.Context) *ObservedAddrSet { |
|
|
|
oas := &ObservedAddrSet{ |
|
|
|
addrs: make(map[string][]*ObservedAddr), |
|
|
|
ttl: peerstore.OwnObservedAddrTTL, |
|
|
|
wch: make(chan newObservation, observedAddrSetWorkerChannelSize), |
|
|
|
// NewObservedAddrManager returns a new address manager using
|
|
|
|
// peerstore.OwnObservedAddressTTL as the TTL.
|
|
|
|
func NewObservedAddrManager(ctx context.Context, host host.Host) *ObservedAddrManager { |
|
|
|
oas := &ObservedAddrManager{ |
|
|
|
addrs: make(map[string][]*ObservedAddr), |
|
|
|
ttl: peerstore.OwnObservedAddrTTL, |
|
|
|
wch: make(chan newObservation, observedAddrManagerWorkerChannelSize), |
|
|
|
host: host, |
|
|
|
activeConns: make(map[network.Conn]ma.Multiaddr), |
|
|
|
// refresh every ttl/2 so we don't forget observations from connected peers
|
|
|
|
refreshTimer: time.NewTimer(peerstore.OwnObservedAddrTTL / 2), |
|
|
|
} |
|
|
|
oas.host.Network().Notify((*obsAddrNotifiee)(oas)) |
|
|
|
go oas.worker(ctx) |
|
|
|
return oas |
|
|
|
} |
|
|
|
|
|
|
|
// AddrsFor return all activated observed addresses associated with the given
|
|
|
|
// (resolved) listen address.
|
|
|
|
func (oas *ObservedAddrSet) AddrsFor(addr ma.Multiaddr) (addrs []ma.Multiaddr) { |
|
|
|
oas.RLock() |
|
|
|
defer oas.RUnlock() |
|
|
|
func (oas *ObservedAddrManager) AddrsFor(addr ma.Multiaddr) (addrs []ma.Multiaddr) { |
|
|
|
oas.mu.RLock() |
|
|
|
defer oas.mu.RUnlock() |
|
|
|
|
|
|
|
if len(oas.addrs) == 0 { |
|
|
|
return nil |
|
|
@ -108,9 +122,9 @@ func (oas *ObservedAddrSet) AddrsFor(addr ma.Multiaddr) (addrs []ma.Multiaddr) { |
|
|
|
} |
|
|
|
|
|
|
|
// Addrs return all activated observed addresses
|
|
|
|
func (oas *ObservedAddrSet) Addrs() (addrs []ma.Multiaddr) { |
|
|
|
oas.RLock() |
|
|
|
defer oas.RUnlock() |
|
|
|
func (oas *ObservedAddrManager) Addrs() (addrs []ma.Multiaddr) { |
|
|
|
oas.mu.RLock() |
|
|
|
defer oas.mu.RUnlock() |
|
|
|
|
|
|
|
if len(oas.addrs) == 0 { |
|
|
|
return nil |
|
|
@ -127,43 +141,79 @@ func (oas *ObservedAddrSet) Addrs() (addrs []ma.Multiaddr) { |
|
|
|
return addrs |
|
|
|
} |
|
|
|
|
|
|
|
// Add attemps to queue a new observed address to be added to the set.
|
|
|
|
func (oas *ObservedAddrSet) Add(observed, local, observer ma.Multiaddr, |
|
|
|
direction network.Direction) { |
|
|
|
// Record records an address observation, if valid.
|
|
|
|
func (oas *ObservedAddrManager) Record(conn network.Conn, observed ma.Multiaddr) { |
|
|
|
select { |
|
|
|
case oas.wch <- newObservation{observed: observed, local: local, observer: observer, direction: direction}: |
|
|
|
case oas.wch <- newObservation{ |
|
|
|
conn: conn, |
|
|
|
observed: observed, |
|
|
|
}: |
|
|
|
default: |
|
|
|
log.Debugf("dropping address observation of %s; buffer full", observed) |
|
|
|
log.Debugw("dropping address observation due to full buffer", |
|
|
|
"from", conn.RemoteMultiaddr(), |
|
|
|
"observed", observed, |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (oas *ObservedAddrSet) worker(ctx context.Context) { |
|
|
|
func (oas *ObservedAddrManager) teardown() { |
|
|
|
oas.host.Network().StopNotify((*obsAddrNotifiee)(oas)) |
|
|
|
|
|
|
|
oas.mu.Lock() |
|
|
|
oas.refreshTimer.Stop() |
|
|
|
oas.mu.Unlock() |
|
|
|
} |
|
|
|
|
|
|
|
func (oas *ObservedAddrManager) worker(ctx context.Context) { |
|
|
|
defer oas.teardown() |
|
|
|
|
|
|
|
ticker := time.NewTicker(GCInterval) |
|
|
|
defer ticker.Stop() |
|
|
|
|
|
|
|
hostClosing := oas.host.Network().Process().Closing() |
|
|
|
for { |
|
|
|
select { |
|
|
|
case obs := <-oas.wch: |
|
|
|
oas.doAdd(obs.observed, obs.local, obs.observer, obs.direction) |
|
|
|
|
|
|
|
oas.maybeRecordObservation(obs.conn, obs.observed) |
|
|
|
case <-ticker.C: |
|
|
|
oas.gc() |
|
|
|
|
|
|
|
case <-oas.refreshTimer.C: |
|
|
|
oas.refresh() |
|
|
|
case <-hostClosing: |
|
|
|
return |
|
|
|
case <-ctx.Done(): |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (oas *ObservedAddrSet) gc() { |
|
|
|
oas.Lock() |
|
|
|
defer oas.Unlock() |
|
|
|
func (oas *ObservedAddrManager) refresh() { |
|
|
|
oas.activeConnsMu.Lock() |
|
|
|
recycledObservations := make([]newObservation, 0, len(oas.activeConns)) |
|
|
|
for conn, observed := range oas.activeConns { |
|
|
|
recycledObservations = append(recycledObservations, newObservation{ |
|
|
|
conn: conn, |
|
|
|
observed: observed, |
|
|
|
}) |
|
|
|
} |
|
|
|
oas.activeConnsMu.Unlock() |
|
|
|
|
|
|
|
oas.mu.Lock() |
|
|
|
defer oas.mu.Unlock() |
|
|
|
for _, obs := range recycledObservations { |
|
|
|
oas.recordObservationUnlocked(obs.conn, obs.observed) |
|
|
|
} |
|
|
|
// refresh every ttl/2 so we don't forget observations from connected peers
|
|
|
|
oas.refreshTimer.Reset(oas.ttl / 2) |
|
|
|
} |
|
|
|
|
|
|
|
func (oas *ObservedAddrManager) gc() { |
|
|
|
oas.mu.Lock() |
|
|
|
defer oas.mu.Unlock() |
|
|
|
|
|
|
|
now := time.Now() |
|
|
|
for local, observedAddrs := range oas.addrs { |
|
|
|
// TODO we can do this without allocating by compacting the array in place
|
|
|
|
filteredAddrs := make([]*ObservedAddr, 0, len(observedAddrs)) |
|
|
|
|
|
|
|
filteredAddrs := observedAddrs[:0] |
|
|
|
for _, a := range observedAddrs { |
|
|
|
// clean up SeenBy set
|
|
|
|
for k, ob := range a.SeenBy { |
|
|
@ -185,20 +235,92 @@ func (oas *ObservedAddrSet) gc() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (oas *ObservedAddrSet) doAdd(observed, local, observer ma.Multiaddr, |
|
|
|
direction network.Direction) { |
|
|
|
func (oas *ObservedAddrManager) addConn(conn network.Conn, observed ma.Multiaddr) { |
|
|
|
oas.activeConnsMu.Lock() |
|
|
|
defer oas.activeConnsMu.Unlock() |
|
|
|
|
|
|
|
// We need to make sure we haven't received a disconnect event for this
|
|
|
|
// connection yet. The only way to do that right now is to make sure the
|
|
|
|
// swarm still has the connection.
|
|
|
|
//
|
|
|
|
// Doing this under a lock that we _also_ take in a disconnect event
|
|
|
|
// handler ensures everything happens in the right order.
|
|
|
|
for _, c := range oas.host.Network().ConnsToPeer(conn.RemotePeer()) { |
|
|
|
if c == conn { |
|
|
|
oas.activeConns[conn] = observed |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (oas *ObservedAddrManager) removeConn(conn network.Conn) { |
|
|
|
// DO NOT remove this lock.
|
|
|
|
// This ensures we don't call addConn at the same time:
|
|
|
|
// 1. see that we have a connection and pause inside addConn right before recording it.
|
|
|
|
// 2. process a disconnect event.
|
|
|
|
// 3. record the connection (leaking it).
|
|
|
|
|
|
|
|
oas.activeConnsMu.Lock() |
|
|
|
delete(oas.activeConns, conn) |
|
|
|
oas.activeConnsMu.Unlock() |
|
|
|
} |
|
|
|
|
|
|
|
func (oas *ObservedAddrManager) maybeRecordObservation(conn network.Conn, observed ma.Multiaddr) { |
|
|
|
|
|
|
|
// First, determine if this observation is even worth keeping...
|
|
|
|
|
|
|
|
// Ignore observations from loopback nodes. We already know our loopback
|
|
|
|
// addresses.
|
|
|
|
if manet.IsIPLoopback(observed) { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// we should only use ObservedAddr when our connection's LocalAddr is one
|
|
|
|
// of our ListenAddrs. If we Dial out using an ephemeral addr, knowing that
|
|
|
|
// address's external mapping is not very useful because the port will not be
|
|
|
|
// the same as the listen addr.
|
|
|
|
ifaceaddrs, err := oas.host.Network().InterfaceListenAddresses() |
|
|
|
if err != nil { |
|
|
|
log.Infof("failed to get interface listen addrs", err) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
local := conn.LocalMultiaddr() |
|
|
|
if !addrInAddrs(local, ifaceaddrs) && !addrInAddrs(local, oas.host.Network().ListenAddresses()) { |
|
|
|
// not in our list
|
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// We should reject the connection if the observation doesn't match the
|
|
|
|
// transports of one of our advertised addresses.
|
|
|
|
if !HasConsistentTransport(observed, oas.host.Addrs()) { |
|
|
|
log.Debugw( |
|
|
|
"observed multiaddr doesn't match the transports of any announced addresses", |
|
|
|
"from", conn.RemoteMultiaddr(), |
|
|
|
"observed", observed, |
|
|
|
) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// Ok, the observation is good, record it.
|
|
|
|
log.Debugw("added own observed listen addr", "observed", observed) |
|
|
|
|
|
|
|
defer oas.addConn(conn, observed) |
|
|
|
|
|
|
|
oas.mu.Lock() |
|
|
|
defer oas.mu.Unlock() |
|
|
|
oas.recordObservationUnlocked(conn, observed) |
|
|
|
} |
|
|
|
|
|
|
|
func (oas *ObservedAddrManager) recordObservationUnlocked(conn network.Conn, observed ma.Multiaddr) { |
|
|
|
now := time.Now() |
|
|
|
observerString := observerGroup(observer) |
|
|
|
localString := string(local.Bytes()) |
|
|
|
observerString := observerGroup(conn.RemoteMultiaddr()) |
|
|
|
localString := string(conn.LocalMultiaddr().Bytes()) |
|
|
|
ob := observation{ |
|
|
|
seenTime: now, |
|
|
|
connDirection: direction, |
|
|
|
connDirection: conn.Stat().Direction, |
|
|
|
} |
|
|
|
|
|
|
|
oas.Lock() |
|
|
|
defer oas.Unlock() |
|
|
|
|
|
|
|
observedAddrs := oas.addrs[localString] |
|
|
|
// check if observed address seen yet, if so, update it
|
|
|
|
for i, previousObserved := range observedAddrs { |
|
|
@ -234,16 +356,29 @@ func observerGroup(m ma.Multiaddr) string { |
|
|
|
return string(first.Bytes()) |
|
|
|
} |
|
|
|
|
|
|
|
// SetTTL sets the TTL of an observed address-set.
|
|
|
|
func (oas *ObservedAddrSet) SetTTL(ttl time.Duration) { |
|
|
|
oas.Lock() |
|
|
|
defer oas.Unlock() |
|
|
|
// SetTTL sets the TTL of an observed address manager.
|
|
|
|
func (oas *ObservedAddrManager) SetTTL(ttl time.Duration) { |
|
|
|
oas.mu.Lock() |
|
|
|
defer oas.mu.Unlock() |
|
|
|
oas.ttl = ttl |
|
|
|
// refresh every ttl/2 so we don't forget observations from connected peers
|
|
|
|
oas.refreshTimer.Reset(ttl / 2) |
|
|
|
} |
|
|
|
|
|
|
|
// TTL gets the TTL of an observed address-set.
|
|
|
|
func (oas *ObservedAddrSet) TTL() time.Duration { |
|
|
|
oas.RLock() |
|
|
|
defer oas.RUnlock() |
|
|
|
// TTL gets the TTL of an observed address manager.
|
|
|
|
func (oas *ObservedAddrManager) TTL() time.Duration { |
|
|
|
oas.mu.RLock() |
|
|
|
defer oas.mu.RUnlock() |
|
|
|
return oas.ttl |
|
|
|
} |
|
|
|
|
|
|
|
type obsAddrNotifiee ObservedAddrManager |
|
|
|
|
|
|
|
func (on *obsAddrNotifiee) Listen(n network.Network, a ma.Multiaddr) {} |
|
|
|
func (on *obsAddrNotifiee) ListenClose(n network.Network, a ma.Multiaddr) {} |
|
|
|
func (on *obsAddrNotifiee) Connected(n network.Network, v network.Conn) {} |
|
|
|
func (on *obsAddrNotifiee) Disconnected(n network.Network, v network.Conn) { |
|
|
|
(*ObservedAddrManager)(on).removeConn(v) |
|
|
|
} |
|
|
|
func (on *obsAddrNotifiee) OpenedStream(n network.Network, s network.Stream) {} |
|
|
|
func (on *obsAddrNotifiee) ClosedStream(n network.Network, s network.Stream) {} |
|
|
|