dcrd/ipc.go
Matheus Degiovani fc017ce3bb server: Add bound addresses IPC events
This adds a new set of IPC events sent over the TX pipe that report the
locally bound addresses of the P2P and RPC interfaces.

The main goal for this feature is to enable binding dcrd to a random
port (using for example, --listen :0) while enabling a controlling
parent process to learn the address that was assigned to dcrd by the OS.

The events are only sent when the --boundaddrevents flag is specified.
2022-11-23 13:26:07 -06:00

264 lines
7.2 KiB
Go

// Copyright (c) 2016-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
)
// Messages sent over a pipe are encoded using a simple binary message format:
//
// - Protocol version (1 byte, currently 1)
// - Message type length (1 byte)
// - Message type string (encoded as UTF8, no longer than 255 bytes)
// - Message payload length (4 bytes, little endian)
// - Message payload bytes (no longer than 2^32 - 1 bytes)
type pipeMessage interface {
Type() string
PayloadSize() uint32
WritePayload(w io.Writer) error
}
var outgoingPipeMessages = make(chan pipeMessage)
// serviceControlPipeRx reads from the file descriptor fd of a read end pipe.
// This is intended to be used as a simple control mechanism for parent
// processes to communicate with and manage the lifetime of a dcrd child
// process using a unidirectional pipe (on Windows, this is an anonymous pipe,
// not a named pipe).
//
// When the pipe is closed or any other errors occur reading the control
// message, shutdown begins. This prevents dcrd from continuing to run
// unsupervised after the parent process closes unexpectedly.
//
// No control messages are currently defined and the only use for the pipe is to
// start clean shutdown when the pipe is closed. Control messages that follow
// the pipe message format can be added later as needed.
func serviceControlPipeRx(fd uintptr) {
pipe := os.NewFile(fd, fmt.Sprintf("|%v", fd))
r := bufio.NewReader(pipe)
for {
_, err := r.Discard(1024)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
dcrdLog.Errorf("Failed to read from pipe: %v", err)
break
}
}
select {
case shutdownRequestChannel <- struct{}{}:
default:
}
}
// serviceControlPipeTx sends pipe messages to the file descriptor fd of a write
// end pipe. This is intended to be a simple response and notification system
// for a child dcrd process to communicate with a parent process without the
// need to go through the RPC server.
//
// See the comment on the pipeMessage interface for the binary encoding of a
// pipe message.
func serviceControlPipeTx(fd uintptr) {
defer drainOutgoingPipeMessages()
pipe := os.NewFile(fd, fmt.Sprintf("|%v", fd))
w := bufio.NewWriter(pipe)
headerBuffer := make([]byte, 0, 1+1+255+4) // capped to max header size
var err error
for m := range outgoingPipeMessages {
const protocolVersion byte = 1
mtype := m.Type()
psize := m.PayloadSize()
headerBuffer = append(headerBuffer, protocolVersion)
headerBuffer = append(headerBuffer, byte(len(mtype)))
headerBuffer = append(headerBuffer, mtype...)
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, psize)
headerBuffer = append(headerBuffer, buf...)
_, err = w.Write(headerBuffer)
if err != nil {
break
}
err = m.WritePayload(w)
if err != nil {
break
}
err = w.Flush()
if err != nil {
break
}
headerBuffer = headerBuffer[:0]
}
dcrdLog.Errorf("Failed to write to pipe: %v", err)
}
func drainOutgoingPipeMessages() {
for range outgoingPipeMessages {
}
}
// The lifetimeEvent describes a startup or shutdown event. The message type
// string is "lifetimeevent".
//
// The payload size is always 2 bytes long. The first byte describes whether a
// service or event is about to run or whether startup has completed. The
// second byte, when applicable, describes which event or service is about to
// start or stop.
//
// 0 <event id>: The startup event is about to run
// 1 <ignored>: All startup tasks have completed
// 2 <event id>: The shutdown event is about to run
//
// Event IDs can take on the following values:
//
// 0: Database opening/closing
// 1: Peer-to-peer server starting/stopping
//
// Note that not all subsystems are started/stopped or events run during the
// program's lifetime depending on what features are enabled through the config.
//
// As an example, the following messages may be sent during a typical execution:
//
// 0 0: The database is being opened
// 0 1: The P2P server is starting
// 1 0: All startup tasks have completed
// 2 1: The P2P server is stopping
// 2 0: The database is being closed
type lifetimeEvent struct {
event lifetimeEventID
action lifetimeAction
}
var _ pipeMessage = (*lifetimeEvent)(nil)
type lifetimeEventID byte
const (
startupEvent lifetimeEventID = iota
startupComplete
shutdownEvent
)
type lifetimeAction byte
const (
lifetimeEventDBOpen lifetimeAction = iota
lifetimeEventP2PServer
)
func (*lifetimeEvent) Type() string { return "lifetimeevent" }
func (e *lifetimeEvent) PayloadSize() uint32 { return 2 }
func (e *lifetimeEvent) WritePayload(w io.Writer) error {
_, err := w.Write([]byte{byte(e.event), byte(e.action)})
return err
}
type lifetimeEventServer chan<- pipeMessage
func newLifetimeEventServer(outChan chan<- pipeMessage) lifetimeEventServer {
return lifetimeEventServer(outChan)
}
func (s lifetimeEventServer) notifyStartupEvent(action lifetimeAction) {
if s == nil {
return
}
s <- &lifetimeEvent{
event: startupEvent,
action: action,
}
}
func (s lifetimeEventServer) notifyStartupComplete() {
if s == nil {
return
}
s <- &lifetimeEvent{
event: startupComplete,
action: 0,
}
}
func (s lifetimeEventServer) notifyShutdownEvent(action lifetimeAction) {
if s == nil {
return
}
s <- &lifetimeEvent{
event: shutdownEvent,
action: action,
}
}
// boundP2PListenAddrEvent are IPC events emitted by dcrd with the bound local
// addresses for the P2P interface. Multiple events of this type may be
// generated.
//
// The type of this event is "p2plistenaddr" and the payload is the UTF-8
// encoded address.
type boundP2PListenAddrEvent string
func (e boundP2PListenAddrEvent) Type() string { return "p2plistenaddr" }
func (e boundP2PListenAddrEvent) PayloadSize() uint32 { return uint32(len(e)) }
func (e boundP2PListenAddrEvent) WritePayload(w io.Writer) error {
_, err := w.Write([]byte(e))
return err
}
// boundRPCListenAddrEvent are IPC events emitted by dcrd with the bound local
// addresses for the RPC interface. Multiple events of this type may be
// generated.
//
// The type of this event is "rpclistenaddr" and the payload is the UTF-8
// encoded address.
type boundRPCListenAddrEvent string
func (e boundRPCListenAddrEvent) Type() string { return "rpclistenaddr" }
func (e boundRPCListenAddrEvent) PayloadSize() uint32 { return uint32(len(e)) }
func (e boundRPCListenAddrEvent) WritePayload(w io.Writer) error {
_, err := w.Write([]byte(e))
return err
}
// boundAddrEventServer is a server that can notify parent processes, via the
// IPC mechanism, the local addresses to which specific subsystems are bound
// during initialization.
//
// The empty value is a valid server and will not send any events if its
// notify* methods are called.
type boundAddrEventServer chan<- pipeMessage
func newBoundAddrEventServer(outChan chan<- pipeMessage) boundAddrEventServer {
return boundAddrEventServer(outChan)
}
func (s boundAddrEventServer) notifyP2PAddress(addr string) {
if s == nil {
return
}
s <- boundP2PListenAddrEvent(addr)
}
func (s boundAddrEventServer) notifyRPCAddress(addr string) {
if s == nil {
return
}
s <- boundRPCListenAddrEvent(addr)
}