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 }