gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/rpcloop.go (about)

     1  package host
     2  
     3  import (
     4  	"crypto/cipher"
     5  	"errors"
     6  	"net"
     7  	"time"
     8  
     9  	"gitlab.com/NebulousLabs/fastrand"
    10  	"gitlab.com/SiaPrime/SiaPrime/build"
    11  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    12  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    13  	"gitlab.com/SiaPrime/SiaPrime/modules"
    14  	"gitlab.com/SiaPrime/SiaPrime/types"
    15  	"golang.org/x/crypto/chacha20poly1305"
    16  )
    17  
    18  // An rpcSession contains the state of an RPC session with a renter.
    19  type rpcSession struct {
    20  	conn      net.Conn
    21  	aead      cipher.AEAD
    22  	so        storageObligation
    23  	challenge [16]byte
    24  }
    25  
    26  // extendDeadline extends the read/write deadline on the underlying connection
    27  // by d.
    28  func (s *rpcSession) extendDeadline(d time.Duration) {
    29  	s.conn.SetDeadline(time.Now().Add(d))
    30  }
    31  
    32  // readRequest reads an encrypted RPC request from the renter.
    33  func (s *rpcSession) readRequest(resp interface{}, maxLen uint64) error {
    34  	return modules.ReadRPCRequest(s.conn, s.aead, resp, maxLen)
    35  }
    36  
    37  // readResponse reads an encrypted RPC response from the renter.
    38  func (s *rpcSession) readResponse(resp interface{}, maxLen uint64) error {
    39  	return modules.ReadRPCResponse(s.conn, s.aead, resp, maxLen)
    40  }
    41  
    42  // writeResponse sends an encrypted RPC response to the renter.
    43  func (s *rpcSession) writeResponse(resp interface{}) error {
    44  	return modules.WriteRPCResponse(s.conn, s.aead, resp, nil)
    45  }
    46  
    47  // writeError sends an encrypted RPC error to the renter.
    48  func (s *rpcSession) writeError(err error) error {
    49  	return modules.WriteRPCResponse(s.conn, s.aead, nil, err)
    50  }
    51  
    52  // managedRPCLoop reads new RPCs from the renter, each consisting of a single
    53  // request and response. The loop terminates when the an RPC encounters an
    54  // error or the renter sends modules.RPCLoopExit.
    55  func (h *Host) managedRPCLoop(conn net.Conn) error {
    56  	// read renter's half of key exchange
    57  	conn.SetDeadline(time.Now().Add(rpcRequestInterval))
    58  	var req modules.LoopKeyExchangeRequest
    59  	if err := encoding.NewDecoder(conn, encoding.DefaultAllocLimit).Decode(&req); err != nil {
    60  		return err
    61  	}
    62  
    63  	// check for a supported cipher
    64  	var supportsChaCha bool
    65  	for _, c := range req.Ciphers {
    66  		if c == modules.CipherChaCha20Poly1305 {
    67  			supportsChaCha = true
    68  		}
    69  	}
    70  	if !supportsChaCha {
    71  		encoding.NewEncoder(conn).Encode(modules.LoopKeyExchangeResponse{
    72  			Cipher: modules.CipherNoOverlap,
    73  		})
    74  		return errors.New("no supported ciphers")
    75  	}
    76  
    77  	// generate a session key, sign it, and derive the shared secret
    78  	xsk, xpk := crypto.GenerateX25519KeyPair()
    79  	pubkeySig := crypto.SignHash(crypto.HashAll(req.PublicKey, xpk), h.secretKey)
    80  	cipherKey := crypto.DeriveSharedSecret(xsk, req.PublicKey)
    81  
    82  	// send our half of the key exchange
    83  	resp := modules.LoopKeyExchangeResponse{
    84  		Cipher:    modules.CipherChaCha20Poly1305,
    85  		PublicKey: xpk,
    86  		Signature: pubkeySig[:],
    87  	}
    88  	if err := encoding.NewEncoder(conn).Encode(resp); err != nil {
    89  		return err
    90  	}
    91  
    92  	// use cipherKey to initialize an AEAD cipher
    93  	aead, err := chacha20poly1305.New(cipherKey[:])
    94  	if err != nil {
    95  		build.Critical("could not create cipher")
    96  		return err
    97  	}
    98  	// create the session object
    99  	s := &rpcSession{
   100  		conn: conn,
   101  		aead: aead,
   102  	}
   103  	fastrand.Read(s.challenge[:])
   104  
   105  	// send encrypted challenge
   106  	challengeReq := modules.LoopChallengeRequest{
   107  		Challenge: s.challenge,
   108  	}
   109  	if err := modules.WriteRPCMessage(conn, aead, challengeReq); err != nil {
   110  		return err
   111  	}
   112  
   113  	// ensure we unlock any locked contracts when protocol ends
   114  	defer func() {
   115  		if len(s.so.OriginTransactionSet) != 0 {
   116  			h.managedUnlockStorageObligation(s.so.id())
   117  		}
   118  	}()
   119  
   120  	// enter RPC loop
   121  	rpcs := map[types.Specifier]func(*rpcSession) error{
   122  		modules.RPCLoopLock:          h.managedRPCLoopLock,
   123  		modules.RPCLoopUnlock:        h.managedRPCLoopUnlock,
   124  		modules.RPCLoopSettings:      h.managedRPCLoopSettings,
   125  		modules.RPCLoopFormContract:  h.managedRPCLoopFormContract,
   126  		modules.RPCLoopRenewContract: h.managedRPCLoopRenewContract,
   127  		modules.RPCLoopWrite:         h.managedRPCLoopWrite,
   128  		modules.RPCLoopRead:          h.managedRPCLoopRead,
   129  		modules.RPCLoopSectorRoots:   h.managedRPCLoopSectorRoots,
   130  	}
   131  	for {
   132  		conn.SetDeadline(time.Now().Add(rpcRequestInterval))
   133  		id, err := modules.ReadRPCID(conn, aead)
   134  		if err != nil {
   135  			h.log.Debugf("WARN: could not read RPC ID: %v", err)
   136  			s.writeError(err) // try to write, even though this is probably due to a faulty connection
   137  			return err
   138  		} else if id == modules.RPCLoopExit {
   139  			return nil
   140  		}
   141  		if rpcFn, ok := rpcs[id]; !ok {
   142  			return errors.New("invalid or unknown RPC ID: " + id.String())
   143  		} else if err := rpcFn(s); err != nil {
   144  			return extendErr("incoming RPC"+id.String()+" failed: ", err)
   145  		}
   146  	}
   147  }