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 }