github.com/ethersphere/bee/v2@v2.2.0/pkg/hive/hive.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package hive exposes the hive protocol implementation
     6  // which is the discovery protocol used to inform and be
     7  // informed about other peers in the network. It gossips
     8  // about all peers by default and performs no specific
     9  // prioritization about which peers are gossipped to
    10  // others.
    11  package hive
    12  
    13  import (
    14  	"context"
    15  	"encoding/hex"
    16  	"errors"
    17  	"fmt"
    18  	"sync"
    19  	"time"
    20  
    21  	"golang.org/x/sync/semaphore"
    22  
    23  	"github.com/ethersphere/bee/v2/pkg/addressbook"
    24  	"github.com/ethersphere/bee/v2/pkg/bzz"
    25  	"github.com/ethersphere/bee/v2/pkg/hive/pb"
    26  	"github.com/ethersphere/bee/v2/pkg/log"
    27  	"github.com/ethersphere/bee/v2/pkg/p2p"
    28  	"github.com/ethersphere/bee/v2/pkg/p2p/protobuf"
    29  	"github.com/ethersphere/bee/v2/pkg/ratelimit"
    30  	"github.com/ethersphere/bee/v2/pkg/swarm"
    31  	ma "github.com/multiformats/go-multiaddr"
    32  	manet "github.com/multiformats/go-multiaddr/net"
    33  )
    34  
    35  // loggerName is the tree path name of the logger for this package.
    36  const loggerName = "hive"
    37  
    38  const (
    39  	protocolName           = "hive"
    40  	protocolVersion        = "1.1.0"
    41  	peersStreamName        = "peers"
    42  	messageTimeout         = 1 * time.Minute // maximum allowed time for a message to be read or written.
    43  	maxBatchSize           = 30
    44  	pingTimeout            = time.Second * 15 // time to wait for ping to succeed
    45  	batchValidationTimeout = 5 * time.Minute  // prevent lock contention on peer validation
    46  )
    47  
    48  var (
    49  	limitBurst = 4 * int(swarm.MaxBins)
    50  	limitRate  = time.Minute
    51  
    52  	ErrRateLimitExceeded = errors.New("rate limit exceeded")
    53  )
    54  
    55  type Service struct {
    56  	streamer          p2p.StreamerPinger
    57  	addressBook       addressbook.GetPutter
    58  	addPeersHandler   func(...swarm.Address)
    59  	networkID         uint64
    60  	logger            log.Logger
    61  	metrics           metrics
    62  	inLimiter         *ratelimit.Limiter
    63  	outLimiter        *ratelimit.Limiter
    64  	quit              chan struct{}
    65  	wg                sync.WaitGroup
    66  	peersChan         chan pb.Peers
    67  	sem               *semaphore.Weighted
    68  	bootnode          bool
    69  	allowPrivateCIDRs bool
    70  }
    71  
    72  func New(streamer p2p.StreamerPinger, addressbook addressbook.GetPutter, networkID uint64, bootnode bool, allowPrivateCIDRs bool, logger log.Logger) *Service {
    73  	svc := &Service{
    74  		streamer:          streamer,
    75  		logger:            logger.WithName(loggerName).Register(),
    76  		addressBook:       addressbook,
    77  		networkID:         networkID,
    78  		metrics:           newMetrics(),
    79  		inLimiter:         ratelimit.New(limitRate, limitBurst),
    80  		outLimiter:        ratelimit.New(limitRate, limitBurst),
    81  		quit:              make(chan struct{}),
    82  		peersChan:         make(chan pb.Peers),
    83  		sem:               semaphore.NewWeighted(int64(swarm.MaxBins)),
    84  		bootnode:          bootnode,
    85  		allowPrivateCIDRs: allowPrivateCIDRs,
    86  	}
    87  
    88  	if !bootnode {
    89  		svc.startCheckPeersHandler()
    90  	}
    91  
    92  	return svc
    93  }
    94  
    95  func (s *Service) Protocol() p2p.ProtocolSpec {
    96  	return p2p.ProtocolSpec{
    97  		Name:    protocolName,
    98  		Version: protocolVersion,
    99  		StreamSpecs: []p2p.StreamSpec{
   100  			{
   101  				Name:    peersStreamName,
   102  				Handler: s.peersHandler,
   103  			},
   104  		},
   105  		DisconnectIn:  s.disconnect,
   106  		DisconnectOut: s.disconnect,
   107  	}
   108  }
   109  
   110  var ErrShutdownInProgress = errors.New("shutdown in progress")
   111  
   112  func (s *Service) BroadcastPeers(ctx context.Context, addressee swarm.Address, peers ...swarm.Address) error {
   113  	max := maxBatchSize
   114  	s.metrics.BroadcastPeers.Inc()
   115  	s.metrics.BroadcastPeersPeers.Add(float64(len(peers)))
   116  
   117  	for len(peers) > 0 {
   118  		if max > len(peers) {
   119  			max = len(peers)
   120  		}
   121  
   122  		// If broadcasting limit is exceeded, return early
   123  		if !s.outLimiter.Allow(addressee.ByteString(), max) {
   124  			return nil
   125  		}
   126  
   127  		select {
   128  		case <-s.quit:
   129  			return ErrShutdownInProgress
   130  		default:
   131  		}
   132  
   133  		if err := s.sendPeers(ctx, addressee, peers[:max]); err != nil {
   134  			return err
   135  		}
   136  
   137  		peers = peers[max:]
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  func (s *Service) SetAddPeersHandler(h func(addr ...swarm.Address)) {
   144  	s.addPeersHandler = h
   145  }
   146  
   147  func (s *Service) Close() error {
   148  	close(s.quit)
   149  
   150  	stopped := make(chan struct{})
   151  	go func() {
   152  		defer close(stopped)
   153  		s.wg.Wait()
   154  	}()
   155  
   156  	select {
   157  	case <-stopped:
   158  		return nil
   159  	case <-time.After(time.Second * 5):
   160  		return errors.New("hive: waited 5 seconds to close active goroutines")
   161  	}
   162  }
   163  
   164  func (s *Service) sendPeers(ctx context.Context, peer swarm.Address, peers []swarm.Address) (err error) {
   165  	s.metrics.BroadcastPeersSends.Inc()
   166  	stream, err := s.streamer.NewStream(ctx, peer, nil, protocolName, protocolVersion, peersStreamName)
   167  	if err != nil {
   168  		return fmt.Errorf("new stream: %w", err)
   169  	}
   170  	defer func() {
   171  		if err != nil {
   172  			_ = stream.Reset()
   173  		} else {
   174  			_ = stream.Close()
   175  		}
   176  	}()
   177  	w, _ := protobuf.NewWriterAndReader(stream)
   178  	var peersRequest pb.Peers
   179  	for _, p := range peers {
   180  		addr, err := s.addressBook.Get(p)
   181  		if err != nil {
   182  			if errors.Is(err, addressbook.ErrNotFound) {
   183  				s.logger.Debug("broadcast peers; peer not found in the addressbook, skipping...", "peer_address", p)
   184  				continue
   185  			}
   186  			return err
   187  		}
   188  
   189  		if !s.allowPrivateCIDRs && manet.IsPrivateAddr(addr.Underlay) {
   190  			continue // Don't advertise private CIDRs to the public network.
   191  		}
   192  
   193  		peersRequest.Peers = append(peersRequest.Peers, &pb.BzzAddress{
   194  			Overlay:   addr.Overlay.Bytes(),
   195  			Underlay:  addr.Underlay.Bytes(),
   196  			Signature: addr.Signature,
   197  			Nonce:     addr.Nonce,
   198  		})
   199  	}
   200  
   201  	if err := w.WriteMsgWithContext(ctx, &peersRequest); err != nil {
   202  		return fmt.Errorf("write Peers message: %w", err)
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  func (s *Service) peersHandler(ctx context.Context, peer p2p.Peer, stream p2p.Stream) error {
   209  	s.metrics.PeersHandler.Inc()
   210  	_, r := protobuf.NewWriterAndReader(stream)
   211  	ctx, cancel := context.WithTimeout(ctx, messageTimeout)
   212  	defer cancel()
   213  	var peersReq pb.Peers
   214  	if err := r.ReadMsgWithContext(ctx, &peersReq); err != nil {
   215  		_ = stream.Reset()
   216  		return fmt.Errorf("read requestPeers message: %w", err)
   217  	}
   218  
   219  	s.metrics.PeersHandlerPeers.Add(float64(len(peersReq.Peers)))
   220  
   221  	if !s.inLimiter.Allow(peer.Address.ByteString(), len(peersReq.Peers)) {
   222  		_ = stream.Reset()
   223  		return ErrRateLimitExceeded
   224  	}
   225  
   226  	// close the stream before processing in order to unblock the sending side
   227  	// fullclose is called async because there is no need to wait for confirmation,
   228  	// but we still want to handle not closed stream from the other side to avoid zombie stream
   229  	go stream.FullClose()
   230  
   231  	if s.bootnode {
   232  		return nil
   233  	}
   234  
   235  	select {
   236  	case s.peersChan <- peersReq:
   237  	case <-s.quit:
   238  		return errors.New("failed to process peers, shutting down hive")
   239  	}
   240  
   241  	return nil
   242  }
   243  
   244  func (s *Service) disconnect(peer p2p.Peer) error {
   245  	s.inLimiter.Clear(peer.Address.ByteString())
   246  	s.outLimiter.Clear(peer.Address.ByteString())
   247  	return nil
   248  }
   249  
   250  func (s *Service) startCheckPeersHandler() {
   251  	ctx, cancel := context.WithCancel(context.Background())
   252  	s.wg.Add(1)
   253  	go func() {
   254  		defer s.wg.Done()
   255  		<-s.quit
   256  		cancel()
   257  	}()
   258  
   259  	s.wg.Add(1)
   260  	go func() {
   261  		defer s.wg.Done()
   262  		for {
   263  			select {
   264  			case <-ctx.Done():
   265  				return
   266  			case newPeers := <-s.peersChan:
   267  				s.wg.Add(1)
   268  				go func() {
   269  					defer s.wg.Done()
   270  					cctx, cancel := context.WithTimeout(ctx, batchValidationTimeout)
   271  					defer cancel()
   272  					s.checkAndAddPeers(cctx, newPeers)
   273  				}()
   274  			}
   275  		}
   276  	}()
   277  }
   278  
   279  func (s *Service) checkAndAddPeers(ctx context.Context, peers pb.Peers) {
   280  
   281  	var peersToAdd []swarm.Address
   282  	mtx := sync.Mutex{}
   283  	wg := sync.WaitGroup{}
   284  
   285  	addPeer := func(newPeer *pb.BzzAddress, multiUnderlay ma.Multiaddr) {
   286  
   287  		err := s.sem.Acquire(ctx, 1)
   288  		if err != nil {
   289  			return
   290  		}
   291  
   292  		wg.Add(1)
   293  		go func() {
   294  			s.metrics.PeerConnectAttempts.Inc()
   295  
   296  			defer func() {
   297  				s.sem.Release(1)
   298  				wg.Done()
   299  			}()
   300  
   301  			ctx, cancel := context.WithTimeout(ctx, pingTimeout)
   302  			defer cancel()
   303  
   304  			start := time.Now()
   305  
   306  			// check if the underlay is usable by doing a raw ping using libp2p
   307  			if _, err := s.streamer.Ping(ctx, multiUnderlay); err != nil {
   308  				s.metrics.PingFailureTime.Observe(time.Since(start).Seconds())
   309  				s.metrics.UnreachablePeers.Inc()
   310  				s.logger.Debug("unreachable peer underlay", "peer_address", hex.EncodeToString(newPeer.Overlay), "underlay", multiUnderlay)
   311  				return
   312  			}
   313  			s.metrics.PingTime.Observe(time.Since(start).Seconds())
   314  
   315  			s.metrics.ReachablePeers.Inc()
   316  
   317  			bzzAddress := bzz.Address{
   318  				Overlay:   swarm.NewAddress(newPeer.Overlay),
   319  				Underlay:  multiUnderlay,
   320  				Signature: newPeer.Signature,
   321  				Nonce:     newPeer.Nonce,
   322  			}
   323  
   324  			err := s.addressBook.Put(bzzAddress.Overlay, bzzAddress)
   325  			if err != nil {
   326  				s.metrics.StorePeerErr.Inc()
   327  				s.logger.Warning("skipping peer in response", "peer_address", newPeer.String(), "error", err)
   328  				return
   329  			}
   330  
   331  			mtx.Lock()
   332  			peersToAdd = append(peersToAdd, bzzAddress.Overlay)
   333  			mtx.Unlock()
   334  		}()
   335  
   336  	}
   337  
   338  	for _, p := range peers.Peers {
   339  
   340  		multiUnderlay, err := ma.NewMultiaddrBytes(p.Underlay)
   341  		if err != nil {
   342  			s.metrics.PeerUnderlayErr.Inc()
   343  			s.logger.Debug("multi address underlay", "error", err)
   344  			continue
   345  		}
   346  
   347  		// if peer exists already in the addressBook
   348  		// and if the underlays match, skip
   349  		addr, err := s.addressBook.Get(swarm.NewAddress(p.Overlay))
   350  		if err == nil && addr.Underlay.Equal(multiUnderlay) {
   351  			continue
   352  		}
   353  
   354  		// add peer does not exist in the addressbook
   355  		addPeer(p, multiUnderlay)
   356  	}
   357  	wg.Wait()
   358  
   359  	if s.addPeersHandler != nil && len(peersToAdd) > 0 {
   360  		s.addPeersHandler(peersToAdd...)
   361  	}
   362  }