func (g *Gateway) managedConnectPeer(conn net.Conn, remoteVersion string, remoteAddr modules.NetAddress) error {
	g.log.Debugln("Sending sessionHeader with address", g.myAddr, g.myAddr.IsLocal())
	g.mu.RLock()
	ourHeader := sessionHeader{
		GenesisID:  types.GenesisID,
		UniqueID:   g.staticId,
		NetAddress: g.myAddr,
	}
	g.mu.RUnlock()
	if err := exchangeOurHeader(conn, ourHeader); err != nil {
		return err
	} else if _, err := exchangeRemoteHeader(conn, ourHeader); err != nil {
		return err
	}
	return nil
}
managedConnectPeer connects to peers >= v1.3.1. The peer is added as a
node and a peer. The peer is only added if a nil error is returned.
package gateway
import (
	"errors"
	"fmt"
	"net"
	"time"
	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
	"github.com/NebulousLabs/fastrand"
)
var (
	errPeerExists       = errors.New("already connected to this peer")
	errPeerRejectedConn = errors.New("peer rejected connection")
)
type insufficientVersionError string
func (s insufficientVersionError) Error() string {
	return "unacceptable version: " + string(s)
}
type invalidVersionError string
func (s invalidVersionError) Error() string {
	return "invalid version: " + string(s)
}
type peer struct {
	modules.Peer
	sess streamSession
}
type sessionHeader struct {
	GenesisID  types.BlockID
	UniqueID   gatewayID
	NetAddress modules.NetAddress
}
func (p *peer) open() (modules.PeerConn, error) {
	conn, err := p.sess.Open()
	if err != nil {
		return nil, err
	}
	return &peerConn{conn, p.NetAddress}, nil
}
func (p *peer) accept() (modules.PeerConn, error) {
	conn, err := p.sess.Accept()
	if err != nil {
		return nil, err
	}
	return &peerConn{conn, p.NetAddress}, nil
}
func (g *Gateway) addPeer(p *peer) {
	g.peers[p.NetAddress] = p
	go g.threadedListenPeer(p)
}
func (g *Gateway) callInitRPCs(addr modules.NetAddress) {
	for name, fn := range g.initRPCs {
		go func(name string, fn modules.RPCFunc) {
			if g.threads.Add() != nil {
				return
			}
			defer g.threads.Done()
			err := g.managedRPC(addr, name, fn)
			if err != nil {
				g.log.Debugf("INFO: RPC %q on peer %q failed: %v", name, addr, err)
			}
		}(name, fn)
	}
}
func (g *Gateway) randomOutboundPeer() (modules.NetAddress, error) {
	var addrs []modules.NetAddress
	for addr, peer := range g.peers {
		if peer.Inbound {
			continue
		}
		addrs = append(addrs, addr)
	}
	if len(addrs) == 0 {
		return "", errNoPeers
	}
	return addrs[fastrand.Intn(len(addrs))], nil
}
func (g *Gateway) permanentListen(closeChan chan struct{}) {
	defer close(closeChan)
	for {
		conn, err := g.listener.Accept()
		if err != nil {
			g.log.Debugln("[PL] Closing permanentListen:", err)
			return
		}
		go g.threadedAcceptConn(conn)
		select {
		case <-time.After(acceptInterval):
		case <-g.threads.StopChan():
			return
		}
	}
}
func (g *Gateway) threadedAcceptConn(conn net.Conn) {
	if g.threads.Add() != nil {
		conn.Close()
		return
	}
	defer g.threads.Done()
	conn.SetDeadline(time.Now().Add(connStdDeadline))
	addr := modules.NetAddress(conn.RemoteAddr().String())
	g.log.Debugf("INFO: %v wants to connect", addr)
	remoteVersion, err := acceptVersionHandshake(conn, build.Version)
	if err != nil {
		g.log.Debugf("INFO: %v wanted to connect but version handshake failed: %v", addr, err)
		conn.Close()
		return
	}
	if build.VersionCmp(remoteVersion, minimumAcceptablePeerVersion) >= 0 {
		err = g.managedAcceptConnPeer(conn, remoteVersion)
	} else {
		err = errors.New("version number is below threshold")
	}
	if err != nil {
		g.log.Debugf("INFO: %v wanted to connect, but failed: %v", addr, err)
		conn.Close()
		return
	}
	conn.SetDeadline(time.Time{})
	g.log.Debugf("INFO: accepted connection from new peer %v (v%v)", addr, remoteVersion)
}
func acceptableSessionHeader(ourHeader, remoteHeader sessionHeader, remoteAddr string) error {
	if remoteHeader.GenesisID != ourHeader.GenesisID {
		return errPeerGenesisID
	} else if remoteHeader.UniqueID == ourHeader.UniqueID {
		return errOurAddress
	} else if err := remoteHeader.NetAddress.IsStdValid(); err != nil {
		return fmt.Errorf("invalid remote address: %v", err)
	}
	return nil
}
func (g *Gateway) managedAcceptConnPeer(conn net.Conn, remoteVersion string) error {
	g.log.Debugln("Sending sessionHeader with address", g.myAddr, g.myAddr.IsLocal())
	g.mu.RLock()
	ourHeader := sessionHeader{
		GenesisID:  types.GenesisID,
		UniqueID:   g.staticId,
		NetAddress: g.myAddr,
	}
	g.mu.RUnlock()
	remoteHeader, err := exchangeRemoteHeader(conn, ourHeader)
	if err != nil {
		return err
	}
	if err := exchangeOurHeader(conn, ourHeader); err != nil {
		return err
	}
	remoteIP := modules.NetAddress(conn.RemoteAddr().String()).Host()
	remotePort := remoteHeader.NetAddress.Port()
	remoteAddr := modules.NetAddress(net.JoinHostPort(remoteIP, remotePort))
	peer := &peer{
		Peer: modules.Peer{
			Inbound: true,
			Local: remoteAddr.IsLocal(),
			NetAddress: remoteAddr,
			Version:    remoteVersion,
		},
		sess: newServerStream(conn, remoteVersion),
	}
	g.mu.Lock()
	g.acceptPeer(peer)
	g.mu.Unlock()
	go func() {
		err := g.staticPingNode(remoteAddr)
		if err == nil {
			g.mu.Lock()
			g.addNode(remoteAddr)
			g.mu.Unlock()
		}
	}()
	return nil
}
func (g *Gateway) acceptPeer(p *peer) {
	if len(g.peers) < fullyConnectedThreshold {
		g.addPeer(p)
		return
	}
	var addrs []modules.NetAddress
	for addr, peer := range g.peers {
		if !peer.Inbound || peer.Local {
			continue
		}
		if addr.Host() == p.NetAddress.Host() {
			addrs = []modules.NetAddress{addr}
			break
		}
		addrs = append(addrs, addr)
	}
	if len(addrs) == 0 {
		g.addPeer(p)
		return
	}
	kick := addrs[fastrand.Intn(len(addrs))]
	g.peers[kick].sess.Close()
	delete(g.peers, kick)
	g.log.Printf("INFO: disconnected from %v to make room for %v\n", kick, p.NetAddress)
	g.addPeer(p)
}
func acceptableVersion(version string) error {
	if !build.IsVersion(version) {
		return invalidVersionError(version)
	}
	if build.VersionCmp(version, minimumAcceptablePeerVersion) < 0 {
		return insufficientVersionError(version)
	}
	return nil
}
func connectVersionHandshake(conn net.Conn, version string) (remoteVersion string, err error) {
	if err := encoding.WriteObject(conn, version); err != nil {
		return "", fmt.Errorf("failed to write version: %v", err)
	}
	if err := encoding.ReadObject(conn, &remoteVersion, build.MaxEncodedVersionLength); err != nil {
		return "", fmt.Errorf("failed to read remote version: %v", err)
	}
	if remoteVersion == "reject" {
		return "", errPeerRejectedConn
	}
	if err := acceptableVersion(remoteVersion); err != nil {
		return "", err
	}
	return remoteVersion, nil
}
func acceptVersionHandshake(conn net.Conn, version string) (remoteVersion string, err error) {
	if err := encoding.ReadObject(conn, &remoteVersion, build.MaxEncodedVersionLength); err != nil {
		return "", fmt.Errorf("failed to read remote version: %v", err)
	}
	if err := acceptableVersion(remoteVersion); err != nil {
		if err := encoding.WriteObject(conn, "reject"); err != nil {
			return "", fmt.Errorf("failed to write reject: %v", err)
		}
		return "", err
	}
	if err := encoding.WriteObject(conn, version); err != nil {
		return "", fmt.Errorf("failed to write version: %v", err)
	}
	return remoteVersion, nil
}
func exchangeOurHeader(conn net.Conn, ourHeader sessionHeader) error {
	if err := encoding.WriteObject(conn, ourHeader); err != nil {
		return fmt.Errorf("failed to write header: %v", err)
	}
	var response string
	if err := encoding.ReadObject(conn, &response, 100); err != nil {
		return fmt.Errorf("failed to read header acceptance: %v", err)
	} else if response == modules.StopResponse {
		return errors.New("peer did not want a connection")
	} else if response != modules.AcceptResponse {
		return fmt.Errorf("peer rejected our header: %v", response)
	}
	return nil
}
func exchangeRemoteHeader(conn net.Conn, ourHeader sessionHeader) (sessionHeader, error) {
	var remoteHeader sessionHeader
	if err := encoding.ReadObject(conn, &remoteHeader, maxEncodedSessionHeaderSize); err != nil {
		return sessionHeader{}, fmt.Errorf("failed to read remote header: %v", err)
	}
	err := acceptableSessionHeader(ourHeader, remoteHeader, conn.RemoteAddr().String())
	if err != nil {
		encoding.WriteObject(conn, err.Error())  
		return sessionHeader{}, fmt.Errorf("peer's header was not acceptable: %v", err)
	} else if err := encoding.WriteObject(conn, modules.AcceptResponse); err != nil {
		return sessionHeader{}, fmt.Errorf("failed to write header acceptance: %v", err)
	}
	return remoteHeader, nil
}