github.com/koko1123/flow-go-1@v0.29.6/network/p2p/connection/connector.go (about)

     1  package connection
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/rand"
     8  	"time"
     9  
    10  	"github.com/hashicorp/go-multierror"
    11  	"github.com/libp2p/go-libp2p/core/host"
    12  	"github.com/libp2p/go-libp2p/core/peer"
    13  	discoveryBackoff "github.com/libp2p/go-libp2p/p2p/discovery/backoff"
    14  	"github.com/rs/zerolog"
    15  
    16  	"github.com/koko1123/flow-go-1/model/flow"
    17  	"github.com/koko1123/flow-go-1/network/internal/p2putils"
    18  	"github.com/koko1123/flow-go-1/network/p2p"
    19  	"github.com/koko1123/flow-go-1/utils/logging"
    20  )
    21  
    22  const (
    23  	ConnectionPruningEnabled  = true
    24  	ConnectionPruningDisabled = false
    25  )
    26  
    27  // Libp2pConnector is a libp2p based Connector implementation to connect and disconnect from peers
    28  type Libp2pConnector struct {
    29  	backoffConnector *discoveryBackoff.BackoffConnector
    30  	host             host.Host
    31  	log              zerolog.Logger
    32  	pruneConnections bool
    33  }
    34  
    35  var _ p2p.Connector = &Libp2pConnector{}
    36  
    37  // UnconvertibleIdentitiesError is an error which reports all the flow.Identifiers that could not be converted to
    38  // peer.AddrInfo
    39  type UnconvertibleIdentitiesError struct {
    40  	errs map[flow.Identifier]error
    41  }
    42  
    43  func NewUnconvertableIdentitiesError(errs map[flow.Identifier]error) error {
    44  	return UnconvertibleIdentitiesError{
    45  		errs: errs,
    46  	}
    47  }
    48  
    49  func (e UnconvertibleIdentitiesError) Error() string {
    50  	multierr := new(multierror.Error)
    51  	for id, err := range e.errs {
    52  		multierr = multierror.Append(multierr, fmt.Errorf("failed to connect to %s: %w", id.String(), err))
    53  	}
    54  	return multierr.Error()
    55  }
    56  
    57  // IsUnconvertibleIdentitiesError returns whether the given error is an UnconvertibleIdentitiesError error
    58  func IsUnconvertibleIdentitiesError(err error) bool {
    59  	var errUnconvertableIdentitiesError UnconvertibleIdentitiesError
    60  	return errors.As(err, &errUnconvertableIdentitiesError)
    61  }
    62  
    63  func NewLibp2pConnector(log zerolog.Logger, host host.Host, pruning bool) (*Libp2pConnector, error) {
    64  	connector, err := defaultLibp2pBackoffConnector(host)
    65  	if err != nil {
    66  		return nil, fmt.Errorf("failed to create libP2P connector: %w", err)
    67  	}
    68  	libP2PConnector := &Libp2pConnector{
    69  		log:              log,
    70  		backoffConnector: connector,
    71  		host:             host,
    72  		pruneConnections: pruning,
    73  	}
    74  
    75  	return libP2PConnector, nil
    76  }
    77  
    78  // UpdatePeers is the implementation of the Connector.UpdatePeers function. It connects to all of the ids and
    79  // disconnects from any other connection that the libp2p node might have.
    80  func (l *Libp2pConnector) UpdatePeers(ctx context.Context, peerIDs peer.IDSlice) {
    81  	// connect to each of the peer.AddrInfo in pInfos
    82  	l.connectToPeers(ctx, peerIDs)
    83  
    84  	if l.pruneConnections {
    85  		// disconnect from any other peers not in pInfos
    86  		// Note: by default almost on all roles, we run on a full topology,
    87  		// this trimming only affects evicted peers from protocol state.
    88  		l.pruneAllConnectionsExcept(peerIDs)
    89  	}
    90  }
    91  
    92  // connectToPeers connects each of the peer in pInfos
    93  func (l *Libp2pConnector) connectToPeers(ctx context.Context, peerIDs peer.IDSlice) {
    94  
    95  	// create a channel of peer.AddrInfo as expected by the connector
    96  	peerCh := make(chan peer.AddrInfo, len(peerIDs))
    97  
    98  	// stuff all the peer.AddrInfo it into the channel
    99  	for _, peerID := range peerIDs {
   100  		peerCh <- peer.AddrInfo{ID: peerID}
   101  	}
   102  
   103  	// close the channel to ensure Connect does not block
   104  	close(peerCh)
   105  
   106  	// ask the connector to connect to all the peers
   107  	l.backoffConnector.Connect(ctx, peerCh)
   108  }
   109  
   110  // pruneAllConnectionsExcept trims all connections of the node from peers not part of peerIDs.
   111  // A node would have created such extra connections earlier when the identity list may have been different, or
   112  // it may have been target of such connections from node which have now been excluded.
   113  func (l *Libp2pConnector) pruneAllConnectionsExcept(peerIDs peer.IDSlice) {
   114  	// convert the peerInfos to a peer.ID -> bool map
   115  	peersToKeep := make(map[peer.ID]bool, len(peerIDs))
   116  	for _, pid := range peerIDs {
   117  		peersToKeep[pid] = true
   118  	}
   119  
   120  	// get all current node connections
   121  	allCurrentConns := l.host.Network().Conns()
   122  
   123  	// for each connection, check if that connection should be trimmed
   124  	for _, conn := range allCurrentConns {
   125  
   126  		// get the remote peer ID for this connection
   127  		peerID := conn.RemotePeer()
   128  
   129  		// check if the peer ID is included in the current fanout
   130  		if peersToKeep[peerID] {
   131  			continue // skip pruning
   132  		}
   133  
   134  		peerInfo := l.host.Network().Peerstore().PeerInfo(peerID)
   135  		lg := l.log.With().Str("remote_peer", peerInfo.String()).Logger()
   136  
   137  		// log the protected status of the connection
   138  		protected := l.host.ConnManager().IsProtected(peerID, "")
   139  		lg = lg.With().Bool("protected", protected).Logger()
   140  
   141  		// log if any stream is open on this connection.
   142  		flowStream := p2putils.FlowStream(conn)
   143  		if flowStream != nil {
   144  			lg = lg.With().Str("flow_stream", string(flowStream.Protocol())).Logger()
   145  		}
   146  
   147  		// close the connection with the peer if it is not part of the current fanout
   148  		err := l.host.Network().ClosePeer(peerID)
   149  		if err != nil {
   150  			// logging with suspicious level as failure to disconnect from a peer can be a security issue.
   151  			// e.g., failure to disconnect from a malicious peer can lead to a DoS attack.
   152  			lg.Error().
   153  				Bool(logging.KeySuspicious, true).
   154  				Err(err).Msg("failed to disconnect from peer")
   155  			continue
   156  		}
   157  		// logging with suspicious level as we only expect to disconnect from a peer if it is not part of the
   158  		// protocol state.
   159  		lg.Warn().
   160  			Bool(logging.KeySuspicious, true).
   161  			Msg("disconnected from peer")
   162  	}
   163  }
   164  
   165  // defaultLibp2pBackoffConnector creates a default libp2p backoff connector similar to the one created by libp2p.pubsub
   166  // (https://github.com/libp2p/go-libp2p-pubsub/blob/master/discovery.go#L34)
   167  func defaultLibp2pBackoffConnector(host host.Host) (*discoveryBackoff.BackoffConnector, error) {
   168  	rngSrc := rand.NewSource(rand.Int63())
   169  	minBackoff, maxBackoff := time.Second*10, time.Hour
   170  	cacheSize := 100
   171  	dialTimeout := time.Minute * 2
   172  	backoff := discoveryBackoff.NewExponentialBackoff(minBackoff, maxBackoff, discoveryBackoff.FullJitter, time.Second, 5.0, 0, rand.New(rngSrc))
   173  	backoffConnector, err := discoveryBackoff.NewBackoffConnector(host, cacheSize, dialTimeout, backoff)
   174  	if err != nil {
   175  		return nil, fmt.Errorf("failed to create backoff connector: %w", err)
   176  	}
   177  	return backoffConnector, nil
   178  }