github.com/ethersphere/bee/v2@v2.2.0/pkg/p2p/libp2p/internal/handshake/handshake.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 handshake
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"sync/atomic"
    12  	"time"
    13  
    14  	"github.com/ethersphere/bee/v2/pkg/bzz"
    15  	"github.com/ethersphere/bee/v2/pkg/crypto"
    16  	"github.com/ethersphere/bee/v2/pkg/log"
    17  	"github.com/ethersphere/bee/v2/pkg/p2p"
    18  	"github.com/ethersphere/bee/v2/pkg/p2p/libp2p/internal/handshake/pb"
    19  	"github.com/ethersphere/bee/v2/pkg/p2p/protobuf"
    20  	"github.com/ethersphere/bee/v2/pkg/swarm"
    21  
    22  	libp2ppeer "github.com/libp2p/go-libp2p/core/peer"
    23  	ma "github.com/multiformats/go-multiaddr"
    24  )
    25  
    26  // loggerName is the tree path name of the logger for this package.
    27  const loggerName = "handshake"
    28  
    29  const (
    30  	// ProtocolName is the text of the name of the handshake protocol.
    31  	ProtocolName = "handshake"
    32  	// ProtocolVersion is the current handshake protocol version.
    33  	ProtocolVersion = "12.0.0"
    34  	// StreamName is the name of the stream used for handshake purposes.
    35  	StreamName = "handshake"
    36  	// MaxWelcomeMessageLength is maximum number of characters allowed in the welcome message.
    37  	MaxWelcomeMessageLength = 140
    38  	handshakeTimeout        = 15 * time.Second
    39  )
    40  
    41  var (
    42  	// ErrNetworkIDIncompatible is returned if response from the other peer does not have valid networkID.
    43  	ErrNetworkIDIncompatible = errors.New("incompatible network ID")
    44  
    45  	// ErrInvalidAck is returned if data in received in ack is not valid (invalid signature for example).
    46  	ErrInvalidAck = errors.New("invalid ack")
    47  
    48  	// ErrInvalidSyn is returned if observable address in ack is not a valid..
    49  	ErrInvalidSyn = errors.New("invalid syn")
    50  
    51  	// ErrWelcomeMessageLength is returned if the welcome message is longer than the maximum length
    52  	ErrWelcomeMessageLength = fmt.Errorf("handshake welcome message longer than maximum of %d characters", MaxWelcomeMessageLength)
    53  
    54  	// ErrPicker is returned if the picker (kademlia) rejects the peer
    55  	ErrPicker = errors.New("picker rejection")
    56  )
    57  
    58  // AdvertisableAddressResolver can Resolve a Multiaddress.
    59  type AdvertisableAddressResolver interface {
    60  	Resolve(observedAddress ma.Multiaddr) (ma.Multiaddr, error)
    61  }
    62  
    63  // Service can perform initiate or handle a handshake between peers.
    64  type Service struct {
    65  	signer                crypto.Signer
    66  	advertisableAddresser AdvertisableAddressResolver
    67  	overlay               swarm.Address
    68  	fullNode              bool
    69  	nonce                 []byte
    70  	networkID             uint64
    71  	validateOverlay       bool
    72  	welcomeMessage        atomic.Value
    73  	logger                log.Logger
    74  	libp2pID              libp2ppeer.ID
    75  	metrics               metrics
    76  	picker                p2p.Picker
    77  }
    78  
    79  // Info contains the information received from the handshake.
    80  type Info struct {
    81  	BzzAddress *bzz.Address
    82  	FullNode   bool
    83  }
    84  
    85  func (i *Info) LightString() string {
    86  	if !i.FullNode {
    87  		return " (light)"
    88  	}
    89  
    90  	return ""
    91  }
    92  
    93  // New creates a new handshake Service.
    94  func New(signer crypto.Signer, advertisableAddresser AdvertisableAddressResolver, overlay swarm.Address, networkID uint64, fullNode bool, nonce []byte, welcomeMessage string, validateOverlay bool, ownPeerID libp2ppeer.ID, logger log.Logger) (*Service, error) {
    95  	if len(welcomeMessage) > MaxWelcomeMessageLength {
    96  		return nil, ErrWelcomeMessageLength
    97  	}
    98  
    99  	svc := &Service{
   100  		signer:                signer,
   101  		advertisableAddresser: advertisableAddresser,
   102  		overlay:               overlay,
   103  		networkID:             networkID,
   104  		fullNode:              fullNode,
   105  		validateOverlay:       validateOverlay,
   106  		nonce:                 nonce,
   107  		libp2pID:              ownPeerID,
   108  		logger:                logger.WithName(loggerName).Register(),
   109  		metrics:               newMetrics(),
   110  	}
   111  	svc.welcomeMessage.Store(welcomeMessage)
   112  
   113  	return svc, nil
   114  }
   115  
   116  func (s *Service) SetPicker(n p2p.Picker) {
   117  	s.picker = n
   118  }
   119  
   120  // Handshake initiates a handshake with a peer.
   121  func (s *Service) Handshake(ctx context.Context, stream p2p.Stream, peerMultiaddr ma.Multiaddr, peerID libp2ppeer.ID) (i *Info, err error) {
   122  	loggerV1 := s.logger.V(1).Register()
   123  
   124  	ctx, cancel := context.WithTimeout(ctx, handshakeTimeout)
   125  	defer cancel()
   126  
   127  	w, r := protobuf.NewWriterAndReader(stream)
   128  	fullRemoteMA, err := buildFullMA(peerMultiaddr, peerID)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	fullRemoteMABytes, err := fullRemoteMA.MarshalBinary()
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	if err := w.WriteMsgWithContext(ctx, &pb.Syn{
   139  		ObservedUnderlay: fullRemoteMABytes,
   140  	}); err != nil {
   141  		return nil, fmt.Errorf("write syn message: %w", err)
   142  	}
   143  
   144  	var resp pb.SynAck
   145  	if err := r.ReadMsgWithContext(ctx, &resp); err != nil {
   146  		return nil, fmt.Errorf("read synack message: %w", err)
   147  	}
   148  
   149  	observedUnderlay, err := ma.NewMultiaddrBytes(resp.Syn.ObservedUnderlay)
   150  	if err != nil {
   151  		return nil, ErrInvalidSyn
   152  	}
   153  
   154  	observedUnderlayAddrInfo, err := libp2ppeer.AddrInfoFromP2pAddr(observedUnderlay)
   155  	if err != nil {
   156  		return nil, fmt.Errorf("extract addr from P2P: %w", err)
   157  	}
   158  
   159  	if s.libp2pID != observedUnderlayAddrInfo.ID {
   160  		// NOTE eventually we will return error here, but for now we want to gather some statistics
   161  		s.logger.Warning("received peer ID does not match ours", "their", observedUnderlayAddrInfo.ID, "ours", s.libp2pID)
   162  	}
   163  
   164  	advertisableUnderlay, err := s.advertisableAddresser.Resolve(observedUnderlay)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	bzzAddress, err := bzz.NewAddress(s.signer, advertisableUnderlay, s.overlay, s.networkID, s.nonce)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	advertisableUnderlayBytes, err := bzzAddress.Underlay.MarshalBinary()
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	if resp.Ack.NetworkID != s.networkID {
   180  		return nil, ErrNetworkIDIncompatible
   181  	}
   182  
   183  	remoteBzzAddress, err := s.parseCheckAck(resp.Ack)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	// Synced read:
   189  	welcomeMessage := s.GetWelcomeMessage()
   190  	msg := &pb.Ack{
   191  		Address: &pb.BzzAddress{
   192  			Underlay:  advertisableUnderlayBytes,
   193  			Overlay:   bzzAddress.Overlay.Bytes(),
   194  			Signature: bzzAddress.Signature,
   195  		},
   196  		NetworkID:      s.networkID,
   197  		FullNode:       s.fullNode,
   198  		Nonce:          s.nonce,
   199  		WelcomeMessage: welcomeMessage,
   200  	}
   201  
   202  	if err := w.WriteMsgWithContext(ctx, msg); err != nil {
   203  		return nil, fmt.Errorf("write ack message: %w", err)
   204  	}
   205  
   206  	loggerV1.Debug("handshake finished for peer (outbound)", "peer_address", remoteBzzAddress.Overlay)
   207  	if len(resp.Ack.WelcomeMessage) > 0 {
   208  		s.logger.Debug("greeting message from peer", "peer_address", remoteBzzAddress.Overlay, "message", resp.Ack.WelcomeMessage)
   209  	}
   210  
   211  	return &Info{
   212  		BzzAddress: remoteBzzAddress,
   213  		FullNode:   resp.Ack.FullNode,
   214  	}, nil
   215  }
   216  
   217  // Handle handles an incoming handshake from a peer.
   218  func (s *Service) Handle(ctx context.Context, stream p2p.Stream, remoteMultiaddr ma.Multiaddr, remotePeerID libp2ppeer.ID) (i *Info, err error) {
   219  	loggerV1 := s.logger.V(1).Register()
   220  
   221  	ctx, cancel := context.WithTimeout(ctx, handshakeTimeout)
   222  	defer cancel()
   223  
   224  	w, r := protobuf.NewWriterAndReader(stream)
   225  	fullRemoteMA, err := buildFullMA(remoteMultiaddr, remotePeerID)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	fullRemoteMABytes, err := fullRemoteMA.MarshalBinary()
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	var syn pb.Syn
   236  	if err := r.ReadMsgWithContext(ctx, &syn); err != nil {
   237  		s.metrics.SynRxFailed.Inc()
   238  		return nil, fmt.Errorf("read syn message: %w", err)
   239  	}
   240  	s.metrics.SynRx.Inc()
   241  
   242  	observedUnderlay, err := ma.NewMultiaddrBytes(syn.ObservedUnderlay)
   243  	if err != nil {
   244  		return nil, ErrInvalidSyn
   245  	}
   246  
   247  	advertisableUnderlay, err := s.advertisableAddresser.Resolve(observedUnderlay)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	bzzAddress, err := bzz.NewAddress(s.signer, advertisableUnderlay, s.overlay, s.networkID, s.nonce)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	advertisableUnderlayBytes, err := bzzAddress.Underlay.MarshalBinary()
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	welcomeMessage := s.GetWelcomeMessage()
   263  
   264  	if err := w.WriteMsgWithContext(ctx, &pb.SynAck{
   265  		Syn: &pb.Syn{
   266  			ObservedUnderlay: fullRemoteMABytes,
   267  		},
   268  		Ack: &pb.Ack{
   269  			Address: &pb.BzzAddress{
   270  				Underlay:  advertisableUnderlayBytes,
   271  				Overlay:   bzzAddress.Overlay.Bytes(),
   272  				Signature: bzzAddress.Signature,
   273  			},
   274  			NetworkID:      s.networkID,
   275  			FullNode:       s.fullNode,
   276  			Nonce:          s.nonce,
   277  			WelcomeMessage: welcomeMessage,
   278  		},
   279  	}); err != nil {
   280  		s.metrics.SynAckTxFailed.Inc()
   281  		return nil, fmt.Errorf("write synack message: %w", err)
   282  	}
   283  	s.metrics.SynAckTx.Inc()
   284  
   285  	var ack pb.Ack
   286  	if err := r.ReadMsgWithContext(ctx, &ack); err != nil {
   287  		s.metrics.AckRxFailed.Inc()
   288  		return nil, fmt.Errorf("read ack message: %w", err)
   289  	}
   290  	s.metrics.AckRx.Inc()
   291  
   292  	if ack.NetworkID != s.networkID {
   293  		return nil, ErrNetworkIDIncompatible
   294  	}
   295  
   296  	overlay := swarm.NewAddress(ack.Address.Overlay)
   297  
   298  	if s.picker != nil {
   299  		if !s.picker.Pick(p2p.Peer{Address: overlay, FullNode: ack.FullNode}) {
   300  			return nil, ErrPicker
   301  		}
   302  	}
   303  
   304  	remoteBzzAddress, err := s.parseCheckAck(&ack)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	loggerV1.Debug("handshake finished for peer (inbound)", "peer_address", remoteBzzAddress.Overlay)
   310  	if len(ack.WelcomeMessage) > 0 {
   311  		loggerV1.Debug("greeting message from peer", "peer_address", remoteBzzAddress.Overlay, "message", ack.WelcomeMessage)
   312  	}
   313  
   314  	return &Info{
   315  		BzzAddress: remoteBzzAddress,
   316  		FullNode:   ack.FullNode,
   317  	}, nil
   318  }
   319  
   320  // SetWelcomeMessage sets the new handshake welcome message.
   321  func (s *Service) SetWelcomeMessage(msg string) (err error) {
   322  	if len(msg) > MaxWelcomeMessageLength {
   323  		return ErrWelcomeMessageLength
   324  	}
   325  	s.welcomeMessage.Store(msg)
   326  	return nil
   327  }
   328  
   329  // GetWelcomeMessage returns the current handshake welcome message.
   330  func (s *Service) GetWelcomeMessage() string {
   331  	return s.welcomeMessage.Load().(string)
   332  }
   333  
   334  func buildFullMA(addr ma.Multiaddr, peerID libp2ppeer.ID) (ma.Multiaddr, error) {
   335  	return ma.NewMultiaddr(fmt.Sprintf("%s/p2p/%s", addr.String(), peerID.String()))
   336  }
   337  
   338  func (s *Service) parseCheckAck(ack *pb.Ack) (*bzz.Address, error) {
   339  	bzzAddress, err := bzz.ParseAddress(ack.Address.Underlay, ack.Address.Overlay, ack.Address.Signature, ack.Nonce, s.validateOverlay, s.networkID)
   340  	if err != nil {
   341  		return nil, ErrInvalidAck
   342  	}
   343  
   344  	return bzzAddress, nil
   345  }