server.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. package plugin
  2. import (
  3. "crypto/tls"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "log"
  9. "net"
  10. "os"
  11. "os/signal"
  12. "runtime"
  13. "strconv"
  14. "sync/atomic"
  15. "github.com/hashicorp/go-hclog"
  16. "google.golang.org/grpc"
  17. )
  18. // CoreProtocolVersion is the ProtocolVersion of the plugin system itself.
  19. // We will increment this whenever we change any protocol behavior. This
  20. // will invalidate any prior plugins but will at least allow us to iterate
  21. // on the core in a safe way. We will do our best to do this very
  22. // infrequently.
  23. const CoreProtocolVersion = 1
  24. // HandshakeConfig is the configuration used by client and servers to
  25. // handshake before starting a plugin connection. This is embedded by
  26. // both ServeConfig and ClientConfig.
  27. //
  28. // In practice, the plugin host creates a HandshakeConfig that is exported
  29. // and plugins then can easily consume it.
  30. type HandshakeConfig struct {
  31. // ProtocolVersion is the version that clients must match on to
  32. // agree they can communicate. This should match the ProtocolVersion
  33. // set on ClientConfig when using a plugin.
  34. ProtocolVersion uint
  35. // MagicCookieKey and value are used as a very basic verification
  36. // that a plugin is intended to be launched. This is not a security
  37. // measure, just a UX feature. If the magic cookie doesn't match,
  38. // we show human-friendly output.
  39. MagicCookieKey string
  40. MagicCookieValue string
  41. }
  42. // ServeConfig configures what sorts of plugins are served.
  43. type ServeConfig struct {
  44. // HandshakeConfig is the configuration that must match clients.
  45. HandshakeConfig
  46. // TLSProvider is a function that returns a configured tls.Config.
  47. TLSProvider func() (*tls.Config, error)
  48. // Plugins are the plugins that are served.
  49. Plugins map[string]Plugin
  50. // GRPCServer should be non-nil to enable serving the plugins over
  51. // gRPC. This is a function to create the server when needed with the
  52. // given server options. The server options populated by go-plugin will
  53. // be for TLS if set. You may modify the input slice.
  54. //
  55. // Note that the grpc.Server will automatically be registered with
  56. // the gRPC health checking service. This is not optional since go-plugin
  57. // relies on this to implement Ping().
  58. GRPCServer func([]grpc.ServerOption) *grpc.Server
  59. }
  60. // Protocol returns the protocol that this server should speak.
  61. func (c *ServeConfig) Protocol() Protocol {
  62. result := ProtocolNetRPC
  63. if c.GRPCServer != nil {
  64. result = ProtocolGRPC
  65. }
  66. return result
  67. }
  68. // Serve serves the plugins given by ServeConfig.
  69. //
  70. // Serve doesn't return until the plugin is done being executed. Any
  71. // errors will be outputted to os.Stderr.
  72. //
  73. // This is the method that plugins should call in their main() functions.
  74. func Serve(opts *ServeConfig) {
  75. // Validate the handshake config
  76. if opts.MagicCookieKey == "" || opts.MagicCookieValue == "" {
  77. fmt.Fprintf(os.Stderr,
  78. "Misconfigured ServeConfig given to serve this plugin: no magic cookie\n"+
  79. "key or value was set. Please notify the plugin author and report\n"+
  80. "this as a bug.\n")
  81. os.Exit(1)
  82. }
  83. // First check the cookie
  84. if os.Getenv(opts.MagicCookieKey) != opts.MagicCookieValue {
  85. fmt.Fprintf(os.Stderr,
  86. "This binary is a plugin. These are not meant to be executed directly.\n"+
  87. "Please execute the program that consumes these plugins, which will\n"+
  88. "load any plugins automatically\n")
  89. os.Exit(1)
  90. }
  91. // Logging goes to the original stderr
  92. log.SetOutput(os.Stderr)
  93. // internal logger to os.Stderr
  94. logger := hclog.New(&hclog.LoggerOptions{
  95. Level: hclog.Trace,
  96. Output: os.Stderr,
  97. JSONFormat: true,
  98. })
  99. // Create our new stdout, stderr files. These will override our built-in
  100. // stdout/stderr so that it works across the stream boundary.
  101. stdout_r, stdout_w, err := os.Pipe()
  102. if err != nil {
  103. fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
  104. os.Exit(1)
  105. }
  106. stderr_r, stderr_w, err := os.Pipe()
  107. if err != nil {
  108. fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
  109. os.Exit(1)
  110. }
  111. // Register a listener so we can accept a connection
  112. listener, err := serverListener()
  113. if err != nil {
  114. logger.Error("plugin init error", "error", err)
  115. return
  116. }
  117. // Close the listener on return. We wrap this in a func() on purpose
  118. // because the "listener" reference may change to TLS.
  119. defer func() {
  120. listener.Close()
  121. }()
  122. var tlsConfig *tls.Config
  123. if opts.TLSProvider != nil {
  124. tlsConfig, err = opts.TLSProvider()
  125. if err != nil {
  126. logger.Error("plugin tls init", "error", err)
  127. return
  128. }
  129. }
  130. // Create the channel to tell us when we're done
  131. doneCh := make(chan struct{})
  132. // Build the server type
  133. var server ServerProtocol
  134. switch opts.Protocol() {
  135. case ProtocolNetRPC:
  136. // If we have a TLS configuration then we wrap the listener
  137. // ourselves and do it at that level.
  138. if tlsConfig != nil {
  139. listener = tls.NewListener(listener, tlsConfig)
  140. }
  141. // Create the RPC server to dispense
  142. server = &RPCServer{
  143. Plugins: opts.Plugins,
  144. Stdout: stdout_r,
  145. Stderr: stderr_r,
  146. DoneCh: doneCh,
  147. }
  148. case ProtocolGRPC:
  149. // Create the gRPC server
  150. server = &GRPCServer{
  151. Plugins: opts.Plugins,
  152. Server: opts.GRPCServer,
  153. TLS: tlsConfig,
  154. Stdout: stdout_r,
  155. Stderr: stderr_r,
  156. DoneCh: doneCh,
  157. }
  158. default:
  159. panic("unknown server protocol: " + opts.Protocol())
  160. }
  161. // Initialize the servers
  162. if err := server.Init(); err != nil {
  163. logger.Error("protocol init", "error", err)
  164. return
  165. }
  166. // Build the extra configuration
  167. extra := ""
  168. if v := server.Config(); v != "" {
  169. extra = base64.StdEncoding.EncodeToString([]byte(v))
  170. }
  171. if extra != "" {
  172. extra = "|" + extra
  173. }
  174. logger.Debug("plugin address", "network", listener.Addr().Network(), "address", listener.Addr().String())
  175. // Output the address and service name to stdout so that core can bring it up.
  176. fmt.Printf("%d|%d|%s|%s|%s%s\n",
  177. CoreProtocolVersion,
  178. opts.ProtocolVersion,
  179. listener.Addr().Network(),
  180. listener.Addr().String(),
  181. opts.Protocol(),
  182. extra)
  183. os.Stdout.Sync()
  184. // Eat the interrupts
  185. ch := make(chan os.Signal, 1)
  186. signal.Notify(ch, os.Interrupt)
  187. go func() {
  188. var count int32 = 0
  189. for {
  190. <-ch
  191. newCount := atomic.AddInt32(&count, 1)
  192. logger.Debug("plugin received interrupt signal, ignoring", "count", newCount)
  193. }
  194. }()
  195. // Set our new out, err
  196. os.Stdout = stdout_w
  197. os.Stderr = stderr_w
  198. // Accept connections and wait for completion
  199. go server.Serve(listener)
  200. <-doneCh
  201. }
  202. func serverListener() (net.Listener, error) {
  203. if runtime.GOOS == "windows" {
  204. return serverListener_tcp()
  205. }
  206. return serverListener_unix()
  207. }
  208. func serverListener_tcp() (net.Listener, error) {
  209. minPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MIN_PORT"), 10, 32)
  210. if err != nil {
  211. return nil, err
  212. }
  213. maxPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MAX_PORT"), 10, 32)
  214. if err != nil {
  215. return nil, err
  216. }
  217. for port := minPort; port <= maxPort; port++ {
  218. address := fmt.Sprintf("127.0.0.1:%d", port)
  219. listener, err := net.Listen("tcp", address)
  220. if err == nil {
  221. return listener, nil
  222. }
  223. }
  224. return nil, errors.New("Couldn't bind plugin TCP listener")
  225. }
  226. func serverListener_unix() (net.Listener, error) {
  227. tf, err := ioutil.TempFile("", "plugin")
  228. if err != nil {
  229. return nil, err
  230. }
  231. path := tf.Name()
  232. // Close the file and remove it because it has to not exist for
  233. // the domain socket.
  234. if err := tf.Close(); err != nil {
  235. return nil, err
  236. }
  237. if err := os.Remove(path); err != nil {
  238. return nil, err
  239. }
  240. l, err := net.Listen("unix", path)
  241. if err != nil {
  242. return nil, err
  243. }
  244. // Wrap the listener in rmListener so that the Unix domain socket file
  245. // is removed on close.
  246. return &rmListener{
  247. Listener: l,
  248. Path: path,
  249. }, nil
  250. }
  251. // rmListener is an implementation of net.Listener that forwards most
  252. // calls to the listener but also removes a file as part of the close. We
  253. // use this to cleanup the unix domain socket on close.
  254. type rmListener struct {
  255. net.Listener
  256. Path string
  257. }
  258. func (l *rmListener) Close() error {
  259. // Close the listener itself
  260. if err := l.Listener.Close(); err != nil {
  261. return err
  262. }
  263. // Remove the file
  264. return os.Remove(l.Path)
  265. }