github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/connection/connection_gater.go (about)

     1  package connection
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/libp2p/go-libp2p/core/control"
     8  	"github.com/libp2p/go-libp2p/core/network"
     9  	"github.com/libp2p/go-libp2p/core/peer"
    10  	"github.com/multiformats/go-multiaddr"
    11  	"github.com/rs/zerolog"
    12  
    13  	"github.com/onflow/flow-go/module"
    14  	"github.com/onflow/flow-go/network/p2p"
    15  	p2plogging "github.com/onflow/flow-go/network/p2p/logging"
    16  	"github.com/onflow/flow-go/utils/logging"
    17  )
    18  
    19  var _ p2p.ConnectionGater = (*ConnGater)(nil)
    20  
    21  // ConnGaterOption allow the connection gater to be configured with a list of PeerFilter funcs for a specific conn gater callback.
    22  // In the current implementation of the ConnGater the following callbacks can be configured with peer filters.
    23  // * InterceptPeerDial - peer filters can be configured with WithOnInterceptPeerDialFilters which will allow or disallow outbound connections.
    24  // * InterceptSecured - peer filters can be configured with WithOnInterceptSecuredFilters which will allow or disallow inbound connections after libP2P security handshake.
    25  type ConnGaterOption func(*ConnGater)
    26  
    27  // WithOnInterceptPeerDialFilters sets peer filters for outbound connections.
    28  func WithOnInterceptPeerDialFilters(filters []p2p.PeerFilter) ConnGaterOption {
    29  	return func(c *ConnGater) {
    30  		c.onInterceptPeerDialFilters = filters
    31  	}
    32  }
    33  
    34  // WithOnInterceptSecuredFilters sets peer filters for inbound secured connections.
    35  func WithOnInterceptSecuredFilters(filters []p2p.PeerFilter) ConnGaterOption {
    36  	return func(c *ConnGater) {
    37  		c.onInterceptSecuredFilters = filters
    38  	}
    39  }
    40  
    41  // ConnGater is the implementation of the libp2p connmgr.ConnectionGater interface
    42  // It provides node allowlisting by libp2p peer.ID which is derived from the node public networking key
    43  type ConnGater struct {
    44  	sync.RWMutex
    45  	onInterceptPeerDialFilters []p2p.PeerFilter
    46  	onInterceptSecuredFilters  []p2p.PeerFilter
    47  
    48  	// disallowListOracle is consulted upon every incoming or outgoing connection attempt, and the connection is only
    49  	// allowed if the remote peer is not on the disallow list.
    50  	// A ConnGater must have a disallowListOracle set, and if one is not set the ConnGater will panic.
    51  	disallowListOracle p2p.DisallowListOracle
    52  
    53  	// identityProvider provides the identity of a node given its peer ID for logging purposes only.
    54  	// It is not used for allowlisting or filtering. We use the onInterceptPeerDialFilters and onInterceptSecuredFilters
    55  	// to determine if a node should be allowed to connect.
    56  	identityProvider module.IdentityProvider
    57  	log              zerolog.Logger
    58  }
    59  
    60  func NewConnGater(log zerolog.Logger, identityProvider module.IdentityProvider, opts ...ConnGaterOption) *ConnGater {
    61  	cg := &ConnGater{
    62  		log:              log.With().Str("component", "connection_gater").Logger(),
    63  		identityProvider: identityProvider,
    64  	}
    65  
    66  	for _, opt := range opts {
    67  		opt(cg)
    68  	}
    69  
    70  	return cg
    71  }
    72  
    73  // InterceptPeerDial - a callback which allows or disallows outbound connection
    74  func (c *ConnGater) InterceptPeerDial(p peer.ID) bool {
    75  	lg := c.log.With().Str("peer_id", p2plogging.PeerId(p)).Logger()
    76  
    77  	disallowListCauses, disallowListed := c.disallowListOracle.IsDisallowListed(p)
    78  	if disallowListed {
    79  		lg.Warn().
    80  			Str("disallow_list_causes", fmt.Sprintf("%v", disallowListCauses)).
    81  			Msg("outbound connection attempt to disallow listed peer is rejected")
    82  		return false
    83  	}
    84  
    85  	if len(c.onInterceptPeerDialFilters) == 0 {
    86  		lg.Warn().
    87  			Msg("outbound connection established with no intercept peer dial filters")
    88  		return true
    89  	}
    90  
    91  	identity, ok := c.identityProvider.ByPeerID(p)
    92  	if !ok {
    93  		lg = lg.With().
    94  			Str("remote_node_id", "unknown").
    95  			Str("role", "unknown").
    96  			Logger()
    97  	} else {
    98  		lg = lg.With().
    99  			Hex("remote_node_id", logging.ID(identity.NodeID)).
   100  			Str("role", identity.Role.String()).
   101  			Logger()
   102  	}
   103  
   104  	if err := c.peerIDPassesAllFilters(p, c.onInterceptPeerDialFilters); err != nil {
   105  		// log the filtered outbound connection attempt
   106  		lg.Warn().
   107  			Err(err).
   108  			Msg("rejected outbound connection attempt")
   109  		return false
   110  	}
   111  
   112  	lg.Debug().Msg("outbound connection established")
   113  	return true
   114  }
   115  
   116  // InterceptAddrDial is not used. Currently, allowlisting is only implemented by Peer IDs and not multi-addresses
   117  func (c *ConnGater) InterceptAddrDial(_ peer.ID, ma multiaddr.Multiaddr) bool {
   118  	return true
   119  }
   120  
   121  // InterceptAccept is not used. Currently, allowlisting is only implemented by Peer IDs and not multi-addresses
   122  func (c *ConnGater) InterceptAccept(cm network.ConnMultiaddrs) bool {
   123  	return true
   124  }
   125  
   126  // InterceptSecured a callback executed after the libp2p security handshake. It tests whether to accept or reject
   127  // an inbound connection based on its peer id.
   128  func (c *ConnGater) InterceptSecured(dir network.Direction, p peer.ID, addr network.ConnMultiaddrs) bool {
   129  	switch dir {
   130  	case network.DirInbound:
   131  		lg := c.log.With().
   132  			Str("peer_id", p2plogging.PeerId(p)).
   133  			Str("remote_address", addr.RemoteMultiaddr().String()).
   134  			Logger()
   135  
   136  		disallowListCauses, disallowListed := c.disallowListOracle.IsDisallowListed(p)
   137  		if disallowListed {
   138  			lg.Warn().
   139  				Str("disallow_list_causes", fmt.Sprintf("%v", disallowListCauses)).
   140  				Msg("inbound connection attempt to disallow listed peer is rejected")
   141  			return false
   142  		}
   143  
   144  		if len(c.onInterceptSecuredFilters) == 0 {
   145  			lg.Warn().Msg("inbound connection established with no intercept secured filters")
   146  			return true
   147  		}
   148  
   149  		identity, ok := c.identityProvider.ByPeerID(p)
   150  		if !ok {
   151  			lg = lg.With().
   152  				Str("remote_node_id", "unknown").
   153  				Str("role", "unknown").
   154  				Logger()
   155  		} else {
   156  			lg = lg.With().
   157  				Hex("remote_node_id", logging.ID(identity.NodeID)).
   158  				Str("role", identity.Role.String()).
   159  				Logger()
   160  		}
   161  
   162  		if err := c.peerIDPassesAllFilters(p, c.onInterceptSecuredFilters); err != nil {
   163  			// log the illegal connection attempt from the remote node
   164  			lg.Error().
   165  				Err(err).
   166  				Str("local_address", addr.LocalMultiaddr().String()).
   167  				Bool(logging.KeySuspicious, true).
   168  				Msg("rejected inbound connection")
   169  			return false
   170  		}
   171  
   172  		lg.Debug().Msg("inbound connection established")
   173  		return true
   174  	default:
   175  		// outbound connection should have been already blocked before this call
   176  		return true
   177  	}
   178  }
   179  
   180  // InterceptUpgraded decision to continue or drop the connection should have been made before this call
   181  func (c *ConnGater) InterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason) {
   182  	return true, 0
   183  }
   184  
   185  func (c *ConnGater) peerIDPassesAllFilters(p peer.ID, filters []p2p.PeerFilter) error {
   186  	for _, allowed := range filters {
   187  		if err := allowed(p); err != nil {
   188  			return err
   189  		}
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  // SetDisallowListOracle sets the disallow list oracle for the connection gater.
   196  // If one is set, the oracle is consulted upon every incoming or outgoing connection attempt, and
   197  // the connection is only allowed if the remote peer is not on the disallow list.
   198  // In Flow blockchain, it is not optional to dismiss the disallow list oracle, and if one is not set
   199  // the connection gater will panic.
   200  // Also, it follows a dependency injection pattern and does not allow to set the disallow list oracle more than once,
   201  // any subsequent calls to this method will result in a panic.
   202  // Args:
   203  //
   204  //	oracle: the disallow list oracle to set.
   205  //
   206  // Returns:
   207  //
   208  //	none
   209  //
   210  // Panics:
   211  //
   212  //	if the disallow list oracle is already set.
   213  func (c *ConnGater) SetDisallowListOracle(oracle p2p.DisallowListOracle) {
   214  	if c.disallowListOracle != nil {
   215  		panic("disallow list oracle already set")
   216  	}
   217  	c.disallowListOracle = oracle
   218  }