gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/gateway/peers.go (about)

     1  package gateway
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"time"
     8  
     9  	"gitlab.com/NebulousLabs/fastrand"
    10  	"gitlab.com/NebulousLabs/ratelimit"
    11  	"gitlab.com/SiaPrime/SiaPrime/build"
    12  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    13  	"gitlab.com/SiaPrime/SiaPrime/modules"
    14  	"gitlab.com/SiaPrime/SiaPrime/types"
    15  )
    16  
    17  var (
    18  	errPeerExists       = errors.New("already connected to this peer")
    19  	errPeerRejectedConn = errors.New("peer rejected connection")
    20  )
    21  
    22  // insufficientVersionError indicates a peer's version is insufficient.
    23  type insufficientVersionError string
    24  
    25  // Error implements the error interface for insufficientVersionError.
    26  func (s insufficientVersionError) Error() string {
    27  	return "unacceptable version: " + string(s)
    28  }
    29  
    30  // invalidVersionError indicates a peer's version is not a valid version number.
    31  type invalidVersionError string
    32  
    33  // Error implements the error interface for invalidVersionError.
    34  func (s invalidVersionError) Error() string {
    35  	return "invalid version: " + string(s)
    36  }
    37  
    38  type peer struct {
    39  	modules.Peer
    40  	rl   *ratelimit.RateLimit
    41  	sess streamSession
    42  }
    43  
    44  // sessionHeader is sent after the initial version exchange. It prevents peers
    45  // on different blockchains from connecting to each other, and prevents the
    46  // gateway from connecting to itself.
    47  type sessionHeader struct {
    48  	GenesisID  types.BlockID
    49  	UniqueID   gatewayID
    50  	NetAddress modules.NetAddress
    51  }
    52  
    53  func (p *peer) open() (modules.PeerConn, error) {
    54  	conn, err := p.sess.Open()
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	// Apply the local ratelimit.
    59  	conn = ratelimit.NewRLConn(conn, p.rl, nil)
    60  	// Apply the global ratelimit.
    61  	conn = ratelimit.NewRLConn(conn, modules.GlobalRateLimits, nil)
    62  	return &peerConn{conn, p.NetAddress}, nil
    63  }
    64  
    65  func (p *peer) accept() (modules.PeerConn, error) {
    66  	conn, err := p.sess.Accept()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return &peerConn{conn, p.NetAddress}, nil
    71  }
    72  
    73  // addPeer adds a peer to the Gateway's peer list, spawns a listener thread to
    74  // handle its requests and increments the remotePeers accordingly
    75  func (g *Gateway) addPeer(p *peer) {
    76  	g.peers[p.NetAddress] = p
    77  	go g.threadedListenPeer(p)
    78  }
    79  
    80  // callInitRPCs calls the rpcs that are registered to be called upon connecting
    81  // to a peer.
    82  func (g *Gateway) callInitRPCs(addr modules.NetAddress) {
    83  	for name, fn := range g.initRPCs {
    84  		go func(name string, fn modules.RPCFunc) {
    85  			if g.threads.Add() != nil {
    86  				return
    87  			}
    88  			defer g.threads.Done()
    89  
    90  			err := g.managedRPC(addr, name, fn)
    91  			if err != nil {
    92  				g.log.Debugf("INFO: RPC %q on peer %q failed: %v", name, addr, err)
    93  			}
    94  		}(name, fn)
    95  	}
    96  }
    97  
    98  // randomOutboundPeer returns a random outbound peer.
    99  func (g *Gateway) randomOutboundPeer() (modules.NetAddress, error) {
   100  	// Get the list of outbound peers.
   101  	var addrs []modules.NetAddress
   102  	for addr, peer := range g.peers {
   103  		if peer.Inbound {
   104  			continue
   105  		}
   106  		addrs = append(addrs, addr)
   107  	}
   108  	if len(addrs) == 0 {
   109  		return "", errNoPeers
   110  	}
   111  
   112  	// Of the remaining options, select one at random.
   113  	return addrs[fastrand.Intn(len(addrs))], nil
   114  }
   115  
   116  // permanentListen handles incoming connection requests. If the connection is
   117  // accepted, the peer will be added to the Gateway's peer list.
   118  func (g *Gateway) permanentListen(closeChan chan struct{}) {
   119  	// Signal that the permanentListen thread has completed upon returning.
   120  	defer close(closeChan)
   121  
   122  	for {
   123  		conn, err := g.listener.Accept()
   124  		if err != nil {
   125  			g.log.Debugln("[PL] Closing permanentListen:", err)
   126  			return
   127  		}
   128  
   129  		go g.threadedAcceptConn(conn)
   130  
   131  		// Sleep after each accept. This limits the rate at which the Gateway
   132  		// will accept new connections. The intent here is to prevent new
   133  		// incoming connections from kicking out old ones before they have a
   134  		// chance to request additional nodes.
   135  		select {
   136  		case <-time.After(acceptInterval):
   137  		case <-g.threads.StopChan():
   138  			return
   139  		}
   140  	}
   141  }
   142  
   143  // threadedAcceptConn adds a connecting node as a peer.
   144  func (g *Gateway) threadedAcceptConn(conn net.Conn) {
   145  	if g.threads.Add() != nil {
   146  		conn.Close()
   147  		return
   148  	}
   149  	defer g.threads.Done()
   150  	conn.SetDeadline(time.Now().Add(connStdDeadline))
   151  
   152  	addr := modules.NetAddress(conn.RemoteAddr().String())
   153  	g.log.Debugf("INFO: %v wants to connect", addr)
   154  
   155  	g.mu.RLock()
   156  	_, exists := g.blacklist[addr.Host()]
   157  	g.mu.RUnlock()
   158  	if exists {
   159  		g.log.Debugf("INFO: %v was rejected. (blacklisted)", addr)
   160  		conn.Close()
   161  		return
   162  	}
   163  	remoteVersion, err := acceptVersionHandshake(conn, build.Version)
   164  	if err != nil {
   165  		g.log.Debugf("INFO: %v wanted to connect but version handshake failed: %v", addr, err)
   166  		conn.Close()
   167  		return
   168  	}
   169  
   170  	if err = acceptableVersion(remoteVersion); err == nil {
   171  		err = g.managedAcceptConnPeer(conn, remoteVersion)
   172  	}
   173  	if err != nil {
   174  		g.log.Debugf("INFO: %v wanted to connect, but failed: %v", addr, err)
   175  		conn.Close()
   176  		return
   177  	}
   178  
   179  	// Handshake successful, remove the deadline.
   180  	conn.SetDeadline(time.Time{})
   181  
   182  	g.log.Debugf("INFO: accepted connection from new peer %v (v%v)", addr, remoteVersion)
   183  }
   184  
   185  // acceptableSessionHeader returns an error if remoteHeader indicates a peer
   186  // that should not be connected to.
   187  func acceptableSessionHeader(ourHeader, remoteHeader sessionHeader, remoteAddr string) error {
   188  	if remoteHeader.GenesisID != ourHeader.GenesisID {
   189  		return errPeerGenesisID
   190  	} else if remoteHeader.UniqueID == ourHeader.UniqueID {
   191  		return errOurAddress
   192  	} else if err := remoteHeader.NetAddress.IsStdValid(); err != nil {
   193  		return fmt.Errorf("invalid remote address: %v", err)
   194  	}
   195  	return nil
   196  }
   197  
   198  // managedAcceptConnPeer accepts connection requests from peers >= v1.3.1.
   199  // The requesting peer is added as a node and a peer. The peer is only added if
   200  // a nil error is returned.
   201  func (g *Gateway) managedAcceptConnPeer(conn net.Conn, remoteVersion string) error {
   202  	g.log.Debugln("Sending sessionHeader with address", g.myAddr, g.myAddr.IsLocal())
   203  	// Perform header handshake.
   204  	g.mu.RLock()
   205  	ourHeader := sessionHeader{
   206  		GenesisID:  types.GenesisID,
   207  		UniqueID:   g.staticID,
   208  		NetAddress: g.myAddr,
   209  	}
   210  	rl := g.rl
   211  	g.mu.RUnlock()
   212  
   213  	remoteHeader, err := exchangeRemoteHeader(conn, ourHeader)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	if err := exchangeOurHeader(conn, ourHeader); err != nil {
   218  		return err
   219  	}
   220  
   221  	// Get the remote address on which the connecting peer is listening on.
   222  	// This means we need to combine the incoming connections ip address with
   223  	// the announced open port of the peer.
   224  	remoteIP := modules.NetAddress(conn.RemoteAddr().String()).Host()
   225  	remotePort := remoteHeader.NetAddress.Port()
   226  	remoteAddr := modules.NetAddress(net.JoinHostPort(remoteIP, remotePort))
   227  
   228  	// Accept the peer.
   229  	peer := &peer{
   230  		Peer: modules.Peer{
   231  			Inbound: true,
   232  			// NOTE: local may be true even if the supplied NetAddress is not
   233  			// actually reachable.
   234  			Local: remoteAddr.IsLocal(),
   235  			// Ignoring claimed IP address (which should be == to the socket address)
   236  			// by the host but keeping note of the port number so we can call back
   237  			NetAddress: remoteAddr,
   238  			Version:    remoteVersion,
   239  		},
   240  		rl:   rl,
   241  		sess: newServerStream(conn, remoteVersion),
   242  	}
   243  	g.mu.Lock()
   244  	g.acceptPeer(peer)
   245  	g.mu.Unlock()
   246  
   247  	// Attempt to ping the supplied address. If successful, we will add
   248  	// remoteHeader.NetAddress to our node list after accepting the peer. We
   249  	// do this in a goroutine so that we can begin communicating with the peer
   250  	// immediately.
   251  	go func() {
   252  		err := g.staticPingNode(remoteAddr)
   253  		if err == nil {
   254  			g.mu.Lock()
   255  			g.addNode(remoteAddr)
   256  			g.mu.Unlock()
   257  		}
   258  	}()
   259  
   260  	return nil
   261  }
   262  
   263  // acceptPeer makes room for the peer if necessary by kicking out existing
   264  // peers, then adds the peer to the peer list.
   265  func (g *Gateway) acceptPeer(p *peer) {
   266  	// If we are not fully connected, add the peer without kicking any out.
   267  	if len(g.peers) < fullyConnectedThreshold {
   268  		g.addPeer(p)
   269  		return
   270  	}
   271  
   272  	// Select a peer to kick. Outbound peers and local peers are not
   273  	// available to be kicked.
   274  	var addrs []modules.NetAddress
   275  	for addr, peer := range g.peers {
   276  		// Do not kick outbound peers or local peers.
   277  		if !peer.Inbound || peer.Local {
   278  			continue
   279  		}
   280  
   281  		// Prefer kicking a peer with the same hostname.
   282  		if addr.Host() == p.NetAddress.Host() {
   283  			addrs = []modules.NetAddress{addr}
   284  			break
   285  		}
   286  		addrs = append(addrs, addr)
   287  	}
   288  	if len(addrs) == 0 {
   289  		// There is nobody suitable to kick, therefore do not kick anyone.
   290  		g.addPeer(p)
   291  		return
   292  	}
   293  
   294  	// Of the remaining options, select one at random.
   295  	kick := addrs[fastrand.Intn(len(addrs))]
   296  
   297  	g.peers[kick].sess.Close()
   298  	delete(g.peers, kick)
   299  	g.log.Printf("INFO: disconnected from %v to make room for %v\n", kick, p.NetAddress)
   300  	g.addPeer(p)
   301  }
   302  
   303  // acceptableVersion returns an error if the version is unacceptable.
   304  func acceptableVersion(version string) error {
   305  	if !build.IsVersion(version) {
   306  		return invalidVersionError(version)
   307  	}
   308  	if build.VersionCmp(version, minimumAcceptablePeerVersion) < 0 {
   309  		return insufficientVersionError(version)
   310  	}
   311  	return nil
   312  }
   313  
   314  // connectVersionHandshake performs the version handshake and should be called
   315  // on the side making the connection request. The remote version is only
   316  // returned if err == nil.
   317  func connectVersionHandshake(conn net.Conn, version string) (remoteVersion string, err error) {
   318  	// Send our version.
   319  	if err := encoding.WriteObject(conn, version); err != nil {
   320  		return "", fmt.Errorf("failed to write version: %v", err)
   321  	}
   322  	// Read remote version.
   323  	if err := encoding.ReadObject(conn, &remoteVersion, build.MaxEncodedVersionLength); err != nil {
   324  		return "", fmt.Errorf("failed to read remote version: %v", err)
   325  	}
   326  	// Check that their version is acceptable.
   327  	if remoteVersion == "reject" {
   328  		return "", errPeerRejectedConn
   329  	}
   330  	if err := acceptableVersion(remoteVersion); err != nil {
   331  		return "", err
   332  	}
   333  	return remoteVersion, nil
   334  }
   335  
   336  // acceptVersionHandshake performs the version handshake and should be
   337  // called on the side accepting a connection request. The remote version is
   338  // only returned if err == nil.
   339  func acceptVersionHandshake(conn net.Conn, version string) (remoteVersion string, err error) {
   340  	// Read remote version.
   341  	if err := encoding.ReadObject(conn, &remoteVersion, build.MaxEncodedVersionLength); err != nil {
   342  		return "", fmt.Errorf("failed to read remote version: %v", err)
   343  	}
   344  	// Check that their version is acceptable.
   345  	if err := acceptableVersion(remoteVersion); err != nil {
   346  		if err := encoding.WriteObject(conn, "reject"); err != nil {
   347  			return "", fmt.Errorf("failed to write reject: %v", err)
   348  		}
   349  		return "", err
   350  	}
   351  	// Send our version.
   352  	if err := encoding.WriteObject(conn, version); err != nil {
   353  		return "", fmt.Errorf("failed to write version: %v", err)
   354  	}
   355  	return remoteVersion, nil
   356  }
   357  
   358  // exchangeOurHeader writes ourHeader and reads the remote's error response.
   359  func exchangeOurHeader(conn net.Conn, ourHeader sessionHeader) error {
   360  	// Send our header.
   361  	if err := encoding.WriteObject(conn, ourHeader); err != nil {
   362  		return fmt.Errorf("failed to write header: %v", err)
   363  	}
   364  
   365  	// Read remote response.
   366  	var response string
   367  	if err := encoding.ReadObject(conn, &response, 100); err != nil {
   368  		return fmt.Errorf("failed to read header acceptance: %v", err)
   369  	} else if response == modules.StopResponse {
   370  		return errors.New("peer did not want a connection")
   371  	} else if response != modules.AcceptResponse {
   372  		return fmt.Errorf("peer rejected our header: %v", response)
   373  	}
   374  	return nil
   375  }
   376  
   377  // exchangeRemoteHeader reads the remote header and writes an error response.
   378  func exchangeRemoteHeader(conn net.Conn, ourHeader sessionHeader) (sessionHeader, error) {
   379  	// Read remote header.
   380  	var remoteHeader sessionHeader
   381  	if err := encoding.ReadObject(conn, &remoteHeader, maxEncodedSessionHeaderSize); err != nil {
   382  		return sessionHeader{}, fmt.Errorf("failed to read remote header: %v", err)
   383  	}
   384  
   385  	// Validate remote header and write acceptance or rejection.
   386  	err := acceptableSessionHeader(ourHeader, remoteHeader, conn.RemoteAddr().String())
   387  	if err != nil {
   388  		encoding.WriteObject(conn, err.Error()) // error can be ignored
   389  		return sessionHeader{}, fmt.Errorf("peer's header was not acceptable: %v", err)
   390  	} else if err := encoding.WriteObject(conn, modules.AcceptResponse); err != nil {
   391  		return sessionHeader{}, fmt.Errorf("failed to write header acceptance: %v", err)
   392  	}
   393  
   394  	return remoteHeader, nil
   395  }
   396  
   397  // managedConnectPeer connects to peers >= v1.3.1. The peer is added as a
   398  // node and a peer. The peer is only added if a nil error is returned.
   399  func (g *Gateway) managedConnectPeer(conn net.Conn, remoteVersion string, remoteAddr modules.NetAddress) error {
   400  	g.log.Debugln("Sending sessionHeader with address", g.myAddr, g.myAddr.IsLocal())
   401  	// Perform header handshake.
   402  	g.mu.RLock()
   403  	ourHeader := sessionHeader{
   404  		GenesisID:  types.GenesisID,
   405  		UniqueID:   g.staticID,
   406  		NetAddress: g.myAddr,
   407  	}
   408  	g.mu.RUnlock()
   409  
   410  	if err := exchangeOurHeader(conn, ourHeader); err != nil {
   411  		return err
   412  	} else if _, err := exchangeRemoteHeader(conn, ourHeader); err != nil {
   413  		return err
   414  	}
   415  	return nil
   416  }
   417  
   418  // managedConnect establishes a persistent connection to a peer, and adds it to
   419  // the Gateway's peer list.
   420  func (g *Gateway) managedConnect(addr modules.NetAddress) error {
   421  	// Perform verification on the input address.
   422  	g.mu.RLock()
   423  	gaddr := g.myAddr
   424  	g.mu.RUnlock()
   425  	if addr == gaddr {
   426  		return errors.New("can't connect to our own address")
   427  	}
   428  	if err := addr.IsStdValid(); err != nil {
   429  		return errors.New("can't connect to invalid address")
   430  	}
   431  	if net.ParseIP(addr.Host()) == nil {
   432  		return errors.New("address must be an IP address")
   433  	}
   434  	if _, exists := g.blacklist[addr.Host()]; exists {
   435  		return errors.New("can't connect to blacklisted address")
   436  	}
   437  	g.mu.RLock()
   438  	_, exists := g.peers[addr]
   439  	g.mu.RUnlock()
   440  	if exists {
   441  		return errPeerExists
   442  	}
   443  
   444  	// Dial the peer and perform peer initialization.
   445  	conn, err := g.staticDial(addr)
   446  	if err != nil {
   447  		return err
   448  	}
   449  
   450  	// Perform peer initialization.
   451  	remoteVersion, err := connectVersionHandshake(conn, build.Version)
   452  	if err != nil {
   453  		conn.Close()
   454  		return err
   455  	}
   456  
   457  	if err = acceptableVersion(remoteVersion); err == nil {
   458  		err = g.managedConnectPeer(conn, remoteVersion, addr)
   459  	}
   460  	if err != nil {
   461  		conn.Close()
   462  		return err
   463  	}
   464  
   465  	// Connection successful, clear the timeout as to maintain a persistent
   466  	// connection to this peer.
   467  	conn.SetDeadline(time.Time{})
   468  
   469  	// Add the peer.
   470  	g.mu.Lock()
   471  	defer g.mu.Unlock()
   472  
   473  	g.addPeer(&peer{
   474  		Peer: modules.Peer{
   475  			Inbound:    false,
   476  			Local:      addr.IsLocal(),
   477  			NetAddress: addr,
   478  			Version:    remoteVersion,
   479  		},
   480  		rl:   g.rl,
   481  		sess: newClientStream(conn, remoteVersion),
   482  	})
   483  	g.addNode(addr)
   484  	g.nodes[addr].WasOutboundPeer = true
   485  
   486  	if err := g.saveSyncNodes(); err != nil {
   487  		g.log.Println("ERROR: Unable to save new outbound peer to gateway:", err)
   488  	}
   489  
   490  	g.log.Debugln("INFO: connected to new peer", addr)
   491  
   492  	// call initRPCs
   493  	g.callInitRPCs(addr)
   494  
   495  	return nil
   496  }
   497  
   498  // Connect establishes a persistent connection to a peer, and adds it to the
   499  // Gateway's peer list.
   500  func (g *Gateway) Connect(addr modules.NetAddress) error {
   501  	if err := g.threads.Add(); err != nil {
   502  		return err
   503  	}
   504  	defer g.threads.Done()
   505  	return g.managedConnect(addr)
   506  }
   507  
   508  // Disconnect terminates a connection to a peer and removes it from the
   509  // Gateway's peer list. The peer's address remains in the node list.
   510  func (g *Gateway) Disconnect(addr modules.NetAddress) error {
   511  	if err := g.threads.Add(); err != nil {
   512  		return err
   513  	}
   514  	defer g.threads.Done()
   515  
   516  	g.mu.RLock()
   517  	p, exists := g.peers[addr]
   518  	g.mu.RUnlock()
   519  	if !exists {
   520  		return errors.New("not connected to that node")
   521  	}
   522  
   523  	p.sess.Close()
   524  	g.mu.Lock()
   525  	// Peer is removed from the peer list as well as the node list, to prevent
   526  	// the node from being re-connected while looking for a replacement peer.
   527  	delete(g.peers, addr)
   528  	delete(g.nodes, addr)
   529  	g.mu.Unlock()
   530  
   531  	g.log.Println("INFO: disconnected from peer", addr)
   532  	return nil
   533  }
   534  
   535  // ConnectManual is a wrapper for the Connect function. It is specifically used
   536  // if a user wants to connect to a node manually. This also removes the node
   537  // from the blacklist.
   538  func (g *Gateway) ConnectManual(addr modules.NetAddress) error {
   539  	g.mu.Lock()
   540  	var err error
   541  	if _, exists := g.blacklist[addr.Host()]; exists {
   542  		delete(g.blacklist, addr.Host())
   543  		err = g.saveSync()
   544  	}
   545  	g.mu.Unlock()
   546  	return build.ComposeErrors(err, g.Connect(addr))
   547  }
   548  
   549  // DisconnectManual is a wrapper for the Disconnect function. It is
   550  // specifically used if a user wants to connect to a node manually. This also
   551  // adds the node to the blacklist.
   552  func (g *Gateway) DisconnectManual(addr modules.NetAddress) error {
   553  	err := g.Disconnect(addr)
   554  	if err == nil {
   555  		g.mu.Lock()
   556  		g.blacklist[addr.Host()] = struct{}{}
   557  		err = g.saveSync()
   558  		g.mu.Unlock()
   559  	}
   560  	return err
   561  }
   562  
   563  // Online returns true if the node is connected to the internet. During testing
   564  // we always assume that the node is online
   565  func (g *Gateway) Online() bool {
   566  	if build.Release == "dev" || build.Release == "testing" {
   567  		return true
   568  	}
   569  
   570  	g.mu.RLock()
   571  	defer g.mu.RUnlock()
   572  	for _, p := range g.peers {
   573  		if !p.Local {
   574  			return true
   575  		}
   576  	}
   577  	return false
   578  }
   579  
   580  // Peers returns the addresses currently connected to the Gateway.
   581  func (g *Gateway) Peers() []modules.Peer {
   582  	g.mu.RLock()
   583  	defer g.mu.RUnlock()
   584  	var peers []modules.Peer
   585  	for _, p := range g.peers {
   586  		peers = append(peers, p.Peer)
   587  	}
   588  	return peers
   589  }