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 }