github.com/decred/dcrlnd@v0.7.6/ipc.go (about)

     1  // Copyright (c) 2016 The btcsuite developers
     2  // Copyright (c) 2020 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package dcrlnd
     7  
     8  import (
     9  	"bufio"
    10  	"encoding/binary"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  
    15  	"github.com/decred/dcrlnd/signal"
    16  	"golang.org/x/xerrors"
    17  )
    18  
    19  // Messages sent over a pipe are encoded using a simple binary message format:
    20  //
    21  //   - Protocol version (1 byte, currently 1)
    22  //   - Message type length (1 byte)
    23  //   - Message type string (encoded as UTF8, no longer than 255 bytes)
    24  //   - Message payload length (4 bytes, little endian)
    25  //   - Message payload bytes (no longer than 2^32 - 1 bytes)
    26  type pipeMessage interface {
    27  	Type() string
    28  	PayloadSize() uint32
    29  	WritePayload(w io.Writer) error
    30  }
    31  
    32  var outgoingPipeMessages = make(chan pipeMessage)
    33  
    34  // serviceControlPipeRx reads from the file descriptor fd of a read end pipe.
    35  // This is intended to be used as a simple control mechanism for parent
    36  // processes to communicate with and and manage the lifetime of a dcrlnd child
    37  // process using a unidirectional pipe (on Windows, this is an anonymous pipe,
    38  // not a named pipe).
    39  //
    40  // When the pipe is closed or any other errors occur reading the control
    41  // message, shutdown begins.  This prevents dcrlnd from continuing to run
    42  // unsupervised after the parent process closes unexpectedly.
    43  //
    44  // No control messages are currently defined and the only use for the pipe is to
    45  // start clean shutdown when the pipe is closed.  Control messages that follow
    46  // the pipe message format can be added later as needed.
    47  func serviceControlPipeRx(fd uintptr, interceptor *signal.Interceptor) {
    48  	pipe := os.NewFile(fd, fmt.Sprintf("|%v", fd))
    49  	r := bufio.NewReader(pipe)
    50  	for {
    51  		_, err := r.Discard(1024)
    52  		if xerrors.Is(err, io.EOF) {
    53  			break
    54  		}
    55  		if err != nil {
    56  			ltndLog.Errorf("Failed to read from pipe: %v", err)
    57  			break
    58  		}
    59  	}
    60  	interceptor.RequestShutdown()
    61  }
    62  
    63  // serviceControlPipeTx sends pipe messages to the file descriptor fd of a write
    64  // end pipe.  This is intended to be a simple response and notification system
    65  // for a child dcrlnd process to communicate with a parent process without the
    66  // need to go through the RPC server.
    67  //
    68  // See the comment on the pipeMessage interface for the binary encoding of a
    69  // pipe message.
    70  func serviceControlPipeTx(fd uintptr) {
    71  	defer drainOutgoingPipeMessages()
    72  
    73  	pipe := os.NewFile(fd, fmt.Sprintf("|%v", fd))
    74  	w := bufio.NewWriter(pipe)
    75  	headerBuffer := make([]byte, 0, 1+1+255+4) // capped to max header size
    76  	var err error
    77  	for m := range outgoingPipeMessages {
    78  		const protocolVersion byte = 1
    79  
    80  		mtype := m.Type()
    81  		psize := m.PayloadSize()
    82  
    83  		headerBuffer = append(headerBuffer, protocolVersion)
    84  		headerBuffer = append(headerBuffer, byte(len(mtype)))
    85  		headerBuffer = append(headerBuffer, mtype...)
    86  		buf := make([]byte, 4)
    87  		binary.LittleEndian.PutUint32(buf, psize)
    88  		headerBuffer = append(headerBuffer, buf...)
    89  
    90  		_, err = w.Write(headerBuffer)
    91  		if err != nil {
    92  			break
    93  		}
    94  
    95  		err = m.WritePayload(w)
    96  		if err != nil {
    97  			break
    98  		}
    99  
   100  		err = w.Flush()
   101  		if err != nil {
   102  			break
   103  		}
   104  
   105  		headerBuffer = headerBuffer[:0]
   106  	}
   107  
   108  	ltndLog.Errorf("Failed to write to pipe: %v", err)
   109  }
   110  
   111  func drainOutgoingPipeMessages() {
   112  	for range outgoingPipeMessages {
   113  	}
   114  }
   115  
   116  // The jsonrpcListenerEvent is used to notify the listener addresses used for
   117  // the JSON-RPC server.  The message type is "jsonrpclistener".  This event is
   118  // most notably useful when parent processes start the wallet with listener
   119  // addresses bound on port 0 to cause the operating system to select an unused
   120  // port.
   121  //
   122  // The payload is the UTF8 bytes of the listener address, and the payload size
   123  // is the byte length of the string.
   124  type jsonrpcListenerEvent string
   125  
   126  var _ pipeMessage = jsonrpcListenerEvent("")
   127  
   128  func (jsonrpcListenerEvent) Type() string          { return "jsonrpclistener" }
   129  func (e jsonrpcListenerEvent) PayloadSize() uint32 { return uint32(len(e)) }
   130  func (e jsonrpcListenerEvent) WritePayload(w io.Writer) error {
   131  	_, err := w.Write([]byte(e))
   132  	return err
   133  }
   134  
   135  type jsonrpcListenerEventServer chan<- pipeMessage
   136  
   137  func newJSONRPCListenerEventServer(outChan chan<- pipeMessage) jsonrpcListenerEventServer {
   138  	return jsonrpcListenerEventServer(outChan)
   139  }
   140  
   141  func (s jsonrpcListenerEventServer) notify(laddr string) {
   142  	if s == nil {
   143  		return
   144  	}
   145  	s <- jsonrpcListenerEvent(laddr)
   146  }
   147  
   148  // The grpcListenerEvent is used to notify the listener addresses used for the
   149  // gRPC server.  The message type is "grpclistener".  This event is most notably
   150  // useful when parent processes start the wallet with listener addresses bound
   151  // on port 0 to cause the operating system to select an unused port.
   152  //
   153  // The payload is the UTF8 bytes of the listener address, and the payload size
   154  // is the byte length of the string.
   155  type grpcListenerEvent string
   156  
   157  var _ pipeMessage = grpcListenerEvent("")
   158  
   159  func (grpcListenerEvent) Type() string          { return "grpclistener" }
   160  func (e grpcListenerEvent) PayloadSize() uint32 { return uint32(len(e)) }
   161  func (e grpcListenerEvent) WritePayload(w io.Writer) error {
   162  	_, err := w.Write([]byte(e))
   163  	return err
   164  }
   165  
   166  type grpcListenerEventServer chan<- pipeMessage
   167  
   168  func newGRPCListenerEventServer(outChan chan<- pipeMessage) grpcListenerEventServer {
   169  	return grpcListenerEventServer(outChan)
   170  }
   171  
   172  func (s grpcListenerEventServer) notify(laddr string) {
   173  	if s == nil {
   174  		return
   175  	}
   176  	s <- grpcListenerEvent(laddr)
   177  }