github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/gateway/peers.go (about)

     1  package gateway
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/NebulousLabs/Sia/build"
     9  	"github.com/NebulousLabs/Sia/crypto"
    10  	"github.com/NebulousLabs/Sia/encoding"
    11  	"github.com/NebulousLabs/Sia/modules"
    12  	"github.com/NebulousLabs/muxado"
    13  )
    14  
    15  const (
    16  	// the gateway will abort a connection attempt after this long
    17  	dialTimeout = 2 * time.Minute
    18  	// the gateway will not accept inbound connections above this threshold
    19  	fullyConnectedThreshold = 128
    20  	// the gateway will ask for more addresses below this threshold
    21  	minNodeListLen = 100
    22  )
    23  
    24  var (
    25  	// The gateway will sleep this long between incoming connections.
    26  	acceptInterval = func() time.Duration {
    27  		switch build.Release {
    28  		case "dev":
    29  			return 3 * time.Second
    30  		case "standard":
    31  			return 3 * time.Second
    32  		case "testing":
    33  			return 10 * time.Millisecond
    34  		default:
    35  			panic("unrecognized build.Release")
    36  		}
    37  	}()
    38  
    39  	errPeerRejectedConn = errors.New("peer rejected connection")
    40  )
    41  
    42  // insufficientVersionError indicates a peer's version is insufficient.
    43  type insufficientVersionError string
    44  
    45  // Error implements the error interface for insufficientVersionError.
    46  func (s insufficientVersionError) Error() string {
    47  	return "unacceptable version: " + string(s)
    48  }
    49  
    50  type peer struct {
    51  	modules.Peer
    52  	sess muxado.Session
    53  }
    54  
    55  func (p *peer) open() (modules.PeerConn, error) {
    56  	conn, err := p.sess.Open()
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	return &peerConn{conn}, nil
    61  }
    62  
    63  func (p *peer) accept() (modules.PeerConn, error) {
    64  	conn, err := p.sess.Accept()
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	return &peerConn{conn}, nil
    69  }
    70  
    71  // addPeer adds a peer to the Gateway's peer list and spawns a listener thread
    72  // to handle its requests.
    73  func (g *Gateway) addPeer(p *peer) {
    74  	g.peers[p.NetAddress] = p
    75  	go g.listenPeer(p)
    76  }
    77  
    78  // randomPeer returns a random peer from the gateway's peer list.
    79  func (g *Gateway) randomPeer() (modules.NetAddress, error) {
    80  	if len(g.peers) > 0 {
    81  		r, _ := crypto.RandIntn(len(g.peers))
    82  		for addr := range g.peers {
    83  			if r <= 0 {
    84  				return addr, nil
    85  			}
    86  			r--
    87  		}
    88  	}
    89  
    90  	return "", errNoPeers
    91  }
    92  
    93  // randomInboundPeer returns a random peer that initiated its connection.
    94  func (g *Gateway) randomInboundPeer() (modules.NetAddress, error) {
    95  	if len(g.peers) > 0 {
    96  		r, _ := crypto.RandIntn(len(g.peers))
    97  		for addr, p := range g.peers {
    98  			// only select inbound peers
    99  			if !p.Inbound {
   100  				continue
   101  			}
   102  			if r <= 0 {
   103  				return addr, nil
   104  			}
   105  			r--
   106  		}
   107  	}
   108  
   109  	return "", errNoPeers
   110  }
   111  
   112  // listen handles incoming connection requests. If the connection is accepted,
   113  // the peer will be added to the Gateway's peer list.
   114  func (g *Gateway) listen() {
   115  	for {
   116  		conn, err := g.listener.Accept()
   117  		if err != nil {
   118  			return
   119  		}
   120  
   121  		go g.acceptConn(conn)
   122  
   123  		// Sleep after each accept. This limits the rate at which the Gateway
   124  		// will accept new connections. The intent here is to prevent new
   125  		// incoming connections from kicking out old ones before they have a
   126  		// chance to request additional nodes.
   127  		time.Sleep(acceptInterval)
   128  	}
   129  }
   130  
   131  // acceptConn adds a connecting node as a peer.
   132  func (g *Gateway) acceptConn(conn net.Conn) {
   133  	addr := modules.NetAddress(conn.RemoteAddr().String())
   134  	g.log.Printf("INFO: %v wants to connect", addr)
   135  
   136  	// read version
   137  	var remoteVersion string
   138  	if err := encoding.ReadObject(conn, &remoteVersion, maxAddrLength); err != nil {
   139  		conn.Close()
   140  		g.log.Printf("INFO: %v wanted to connect, but we could not read their version: %v", addr, err)
   141  		return
   142  	}
   143  
   144  	// Check that version is acceptable.
   145  	//
   146  	// Reject peers < v0.4.0 as the previous version is v0.3.3 which is
   147  	// pre-hardfork.
   148  	//
   149  	// NOTE: this version must be bumped whenever the gateway or consensus
   150  	// breaks compatibility.
   151  	if build.VersionCmp(remoteVersion, "0.4.0") < 0 {
   152  		encoding.WriteObject(conn, "reject")
   153  		conn.Close()
   154  		g.log.Printf("INFO: %v wanted to connect, but their version (%v) was unacceptable", addr, remoteVersion)
   155  		return
   156  	}
   157  
   158  	// respond with our version
   159  	if err := encoding.WriteObject(conn, build.Version); err != nil {
   160  		conn.Close()
   161  		g.log.Printf("INFO: could not write version ack to %v: %v", addr, err)
   162  		return
   163  	}
   164  
   165  	// If we are already fully connected, kick out an old peer to make room
   166  	// for the new one. Importantly, prioritize kicking a peer with the same
   167  	// IP as the connecting peer. This protects against Sybil attacks.
   168  	id := g.mu.Lock()
   169  	if len(g.peers) >= fullyConnectedThreshold {
   170  		// first choose a random peer, preferably inbound. If have only
   171  		// outbound peers, we'll wind up kicking an outbound peer; but
   172  		// subsequent inbound connections will kick each other instead of
   173  		// continuing to replace outbound peers.
   174  		kick, err := g.randomInboundPeer()
   175  		if err != nil {
   176  			kick, _ = g.randomPeer()
   177  		}
   178  		// if another peer shares this IP, choose that one instead
   179  		for p := range g.peers {
   180  			if p.Host() == addr.Host() {
   181  				kick = p
   182  				break
   183  			}
   184  		}
   185  		g.peers[kick].sess.Close()
   186  		delete(g.peers, kick)
   187  		g.log.Printf("INFO: disconnected from %v to make room for %v", kick, addr)
   188  	}
   189  	// add the peer
   190  	g.addPeer(&peer{
   191  		Peer: modules.Peer{
   192  			NetAddress: addr,
   193  			Inbound:    true,
   194  			Version:    remoteVersion,
   195  		},
   196  		sess: muxado.Server(conn),
   197  	})
   198  	g.mu.Unlock(id)
   199  
   200  	g.log.Printf("INFO: accepted connection from new peer %v (v%v)", addr, remoteVersion)
   201  }
   202  
   203  // Connect establishes a persistent connection to a peer, and adds it to the
   204  // Gateway's peer list.
   205  func (g *Gateway) Connect(addr modules.NetAddress) error {
   206  	if addr == g.Address() {
   207  		return errors.New("can't connect to our own address")
   208  	}
   209  	if err := addr.IsValid(); err != nil {
   210  		return errors.New("can't connect to invalid address")
   211  	}
   212  
   213  	id := g.mu.RLock()
   214  	_, exists := g.peers[addr]
   215  	g.mu.RUnlock(id)
   216  	if exists {
   217  		return errors.New("peer already added")
   218  	}
   219  
   220  	conn, err := net.DialTimeout("tcp", string(addr), dialTimeout)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	// send our version
   225  	if err := encoding.WriteObject(conn, build.Version); err != nil {
   226  		return err
   227  	}
   228  	// read version ack
   229  	var remoteVersion string
   230  	if err := encoding.ReadObject(conn, &remoteVersion, maxAddrLength); err != nil {
   231  		return err
   232  	}
   233  	// decide whether to accept this version
   234  	if remoteVersion == "reject" {
   235  		return errPeerRejectedConn
   236  	}
   237  	// Check that version is acceptable.
   238  	//
   239  	// Reject peers < v0.4.0 as the previous version is v0.3.3 which is
   240  	// pre-hardfork.
   241  	//
   242  	// NOTE: this version must be bumped whenever the gateway or consensus
   243  	// breaks compatibility.
   244  	if build.VersionCmp(remoteVersion, "0.4.0") < 0 {
   245  		conn.Close()
   246  		return insufficientVersionError(remoteVersion)
   247  	}
   248  
   249  	g.log.Println("INFO: connected to new peer", addr)
   250  
   251  	id = g.mu.Lock()
   252  	g.addPeer(&peer{
   253  		Peer: modules.Peer{
   254  			NetAddress: addr,
   255  			Inbound:    false,
   256  			Version:    remoteVersion,
   257  		},
   258  		sess: muxado.Client(conn),
   259  	})
   260  	g.mu.Unlock(id)
   261  
   262  	// call initRPCs
   263  	id = g.mu.RLock()
   264  	for name, fn := range g.initRPCs {
   265  		go g.RPC(addr, name, fn)
   266  	}
   267  	g.mu.RUnlock(id)
   268  
   269  	return nil
   270  }
   271  
   272  // Disconnect terminates a connection to a peer and removes it from the
   273  // Gateway's peer list. The peer's address remains in the node list.
   274  func (g *Gateway) Disconnect(addr modules.NetAddress) error {
   275  	id := g.mu.RLock()
   276  	p, exists := g.peers[addr]
   277  	g.mu.RUnlock(id)
   278  	if !exists {
   279  		return errors.New("not connected to that node")
   280  	}
   281  	p.sess.Close()
   282  	id = g.mu.Lock()
   283  	delete(g.peers, addr)
   284  	g.mu.Unlock(id)
   285  
   286  	g.log.Println("INFO: disconnected from peer", addr)
   287  	return nil
   288  }
   289  
   290  // threadedPeerManager tries to keep the Gateway well-connected. As long as
   291  // the Gateway is not well-connected, it tries to connect to random nodes.
   292  func (g *Gateway) threadedPeerManager() {
   293  	for {
   294  		// If we are well-connected, sleep in increments of five minutes until
   295  		// we are no longer well-connected.
   296  		id := g.mu.RLock()
   297  		numOutboundPeers := 0
   298  		for _, p := range g.peers {
   299  			if !p.Inbound {
   300  				numOutboundPeers++
   301  			}
   302  		}
   303  		addr, err := g.randomNode()
   304  		g.mu.RUnlock(id)
   305  		if numOutboundPeers >= modules.WellConnectedThreshold {
   306  			select {
   307  			case <-time.After(5 * time.Minute):
   308  			case <-g.closeChan:
   309  				return
   310  			}
   311  			continue
   312  		}
   313  
   314  		// Try to connect to a random node. Instead of blocking on Connect, we
   315  		// spawn a goroutine and sleep for five seconds. This allows us to
   316  		// continue making connections if the node is unresponsive.
   317  		if err == nil {
   318  			go g.Connect(addr)
   319  		}
   320  		select {
   321  		case <-time.After(5 * time.Second):
   322  		case <-g.closeChan:
   323  			return
   324  		}
   325  	}
   326  }
   327  
   328  // Peers returns the addresses currently connected to the Gateway.
   329  func (g *Gateway) Peers() []modules.Peer {
   330  	id := g.mu.RLock()
   331  	defer g.mu.RUnlock(id)
   332  	var peers []modules.Peer
   333  	for _, p := range g.peers {
   334  		peers = append(peers, p.Peer)
   335  	}
   336  	return peers
   337  }