github.com/hashicorp/go-plugin@v1.6.0/rpc_server.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package plugin
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"log"
    11  	"net"
    12  	"net/rpc"
    13  	"sync"
    14  
    15  	"github.com/hashicorp/yamux"
    16  )
    17  
    18  // RPCServer listens for network connections and then dispenses interface
    19  // implementations over net/rpc.
    20  //
    21  // After setting the fields below, they shouldn't be read again directly
    22  // from the structure which may be reading/writing them concurrently.
    23  type RPCServer struct {
    24  	Plugins map[string]Plugin
    25  
    26  	// Stdout, Stderr are what this server will use instead of the
    27  	// normal stdin/out/err. This is because due to the multi-process nature
    28  	// of our plugin system, we can't use the normal process values so we
    29  	// make our own custom one we pipe across.
    30  	Stdout io.Reader
    31  	Stderr io.Reader
    32  
    33  	// DoneCh should be set to a non-nil channel that will be closed
    34  	// when the control requests the RPC server to end.
    35  	DoneCh chan<- struct{}
    36  
    37  	lock sync.Mutex
    38  }
    39  
    40  // ServerProtocol impl.
    41  func (s *RPCServer) Init() error { return nil }
    42  
    43  // ServerProtocol impl.
    44  func (s *RPCServer) Config() string { return "" }
    45  
    46  // ServerProtocol impl.
    47  func (s *RPCServer) Serve(lis net.Listener) {
    48  	defer s.done()
    49  
    50  	for {
    51  		conn, err := lis.Accept()
    52  		if err != nil {
    53  			severity := "ERR"
    54  			if errors.Is(err, net.ErrClosed) {
    55  				severity = "DEBUG"
    56  			}
    57  			log.Printf("[%s] plugin: plugin server: %s", severity, err)
    58  			return
    59  		}
    60  
    61  		go s.ServeConn(conn)
    62  	}
    63  }
    64  
    65  // ServeConn runs a single connection.
    66  //
    67  // ServeConn blocks, serving the connection until the client hangs up.
    68  func (s *RPCServer) ServeConn(conn io.ReadWriteCloser) {
    69  	// First create the yamux server to wrap this connection
    70  	mux, err := yamux.Server(conn, nil)
    71  	if err != nil {
    72  		conn.Close()
    73  		log.Printf("[ERR] plugin: error creating yamux server: %s", err)
    74  		return
    75  	}
    76  
    77  	// Accept the control connection
    78  	control, err := mux.Accept()
    79  	if err != nil {
    80  		mux.Close()
    81  		if err != io.EOF {
    82  			log.Printf("[ERR] plugin: error accepting control connection: %s", err)
    83  		}
    84  
    85  		return
    86  	}
    87  
    88  	// Connect the stdstreams (in, out, err)
    89  	stdstream := make([]net.Conn, 2)
    90  	for i := range stdstream {
    91  		stdstream[i], err = mux.Accept()
    92  		if err != nil {
    93  			mux.Close()
    94  			log.Printf("[ERR] plugin: accepting stream %d: %s", i, err)
    95  			return
    96  		}
    97  	}
    98  
    99  	// Copy std streams out to the proper place
   100  	go copyStream("stdout", stdstream[0], s.Stdout)
   101  	go copyStream("stderr", stdstream[1], s.Stderr)
   102  
   103  	// Create the broker and start it up
   104  	broker := newMuxBroker(mux)
   105  	go broker.Run()
   106  
   107  	// Use the control connection to build the dispenser and serve the
   108  	// connection.
   109  	server := rpc.NewServer()
   110  	server.RegisterName("Control", &controlServer{
   111  		server: s,
   112  	})
   113  	server.RegisterName("Dispenser", &dispenseServer{
   114  		broker:  broker,
   115  		plugins: s.Plugins,
   116  	})
   117  	server.ServeConn(control)
   118  }
   119  
   120  // done is called internally by the control server to trigger the
   121  // doneCh to close which is listened to by the main process to cleanly
   122  // exit.
   123  func (s *RPCServer) done() {
   124  	s.lock.Lock()
   125  	defer s.lock.Unlock()
   126  
   127  	if s.DoneCh != nil {
   128  		close(s.DoneCh)
   129  		s.DoneCh = nil
   130  	}
   131  }
   132  
   133  // dispenseServer dispenses variousinterface implementations for Terraform.
   134  type controlServer struct {
   135  	server *RPCServer
   136  }
   137  
   138  // Ping can be called to verify the connection (and likely the binary)
   139  // is still alive to a plugin.
   140  func (c *controlServer) Ping(
   141  	null bool, response *struct{},
   142  ) error {
   143  	*response = struct{}{}
   144  	return nil
   145  }
   146  
   147  func (c *controlServer) Quit(
   148  	null bool, response *struct{},
   149  ) error {
   150  	// End the server
   151  	c.server.done()
   152  
   153  	// Always return true
   154  	*response = struct{}{}
   155  
   156  	return nil
   157  }
   158  
   159  // dispenseServer dispenses variousinterface implementations for Terraform.
   160  type dispenseServer struct {
   161  	broker  *MuxBroker
   162  	plugins map[string]Plugin
   163  }
   164  
   165  func (d *dispenseServer) Dispense(
   166  	name string, response *uint32,
   167  ) error {
   168  	// Find the function to create this implementation
   169  	p, ok := d.plugins[name]
   170  	if !ok {
   171  		return fmt.Errorf("unknown plugin type: %s", name)
   172  	}
   173  
   174  	// Create the implementation first so we know if there is an error.
   175  	impl, err := p.Server(d.broker)
   176  	if err != nil {
   177  		// We turn the error into an errors error so that it works across RPC
   178  		return errors.New(err.Error())
   179  	}
   180  
   181  	// Reserve an ID for our implementation
   182  	id := d.broker.NextId()
   183  	*response = id
   184  
   185  	// Run the rest in a goroutine since it can only happen once this RPC
   186  	// call returns. We wait for a connection for the plugin implementation
   187  	// and serve it.
   188  	go func() {
   189  		conn, err := d.broker.Accept(id)
   190  		if err != nil {
   191  			log.Printf("[ERR] go-plugin: plugin dispense error: %s: %s", name, err)
   192  			return
   193  		}
   194  
   195  		serve(conn, "Plugin", impl)
   196  	}()
   197  
   198  	return nil
   199  }
   200  
   201  func serve(conn io.ReadWriteCloser, name string, v interface{}) {
   202  	server := rpc.NewServer()
   203  	if err := server.RegisterName(name, v); err != nil {
   204  		log.Printf("[ERR] go-plugin: plugin dispense error: %s", err)
   205  		return
   206  	}
   207  
   208  	server.ServeConn(conn)
   209  }