Bump reva deps (#8412)

* bump dependencies

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* bump reva and add config options

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

---------

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2024-02-21 10:20:36 +01:00
committed by GitHub
parent c92ebf4b46
commit 5ed57cc09a
490 changed files with 19130 additions and 11163 deletions
+1 -3
View File
@@ -1,6 +1,4 @@
MIT License
Copyright (c) 2017 HashiCorp
Copyright (c) 2017 HashiCorp, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+3 -2
View File
@@ -140,9 +140,10 @@ log.Printf("[DEBUG] %d", 42)
... [DEBUG] my-app: 42
```
Notice that if `appLogger` is initialized with the `INFO` log level _and_ you
Notice that if `appLogger` is initialized with the `INFO` log level, _and_ you
specify `InferLevels: true`, you will not see any output here. You must change
`appLogger` to `DEBUG` to see output. See the docs for more information.
If the log lines start with a timestamp you can use the
`InferLevelsWithTimestamp` option to try and ignore them.
`InferLevelsWithTimestamp` option to try and ignore them. Please note that in order
for `InferLevelsWithTimestamp` to be relevant, `InferLevels` must be set to `true`.
+25 -10
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
//go:build !windows
// +build !windows
@@ -7,23 +10,35 @@ import (
"github.com/mattn/go-isatty"
)
// hasFD is used to check if the writer has an Fd value to check
// if it's a terminal.
type hasFD interface {
Fd() uintptr
}
// setColorization will mutate the values of this logger
// to appropriately configure colorization options. It provides
// a wrapper to the output stream on Windows systems.
func (l *intLogger) setColorization(opts *LoggerOptions) {
switch opts.Color {
case ColorOff:
fallthrough
case ForceColor:
if opts.Color != AutoColor {
return
case AutoColor:
fi := l.checkWriterIsFile()
isUnixTerm := isatty.IsTerminal(fi.Fd())
isCygwinTerm := isatty.IsCygwinTerminal(fi.Fd())
isTerm := isUnixTerm || isCygwinTerm
if !isTerm {
}
if sc, ok := l.writer.w.(SupportsColor); ok {
if !sc.SupportsColor() {
l.headerColor = ColorOff
l.writer.color = ColorOff
}
return
}
fi, ok := l.writer.w.(hasFD)
if !ok {
return
}
if !isatty.IsTerminal(fi.Fd()) {
l.headerColor = ColorOff
l.writer.color = ColorOff
}
}
+22 -19
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
//go:build windows
// +build windows
@@ -7,32 +10,32 @@ import (
"os"
colorable "github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
// setColorization will mutate the values of this logger
// to appropriately configure colorization options. It provides
// a wrapper to the output stream on Windows systems.
func (l *intLogger) setColorization(opts *LoggerOptions) {
switch opts.Color {
case ColorOff:
if opts.Color == ColorOff {
return
case ForceColor:
fi := l.checkWriterIsFile()
l.writer.w = colorable.NewColorable(fi)
case AutoColor:
fi := l.checkWriterIsFile()
isUnixTerm := isatty.IsTerminal(os.Stdout.Fd())
isCygwinTerm := isatty.IsCygwinTerminal(os.Stdout.Fd())
isTerm := isUnixTerm || isCygwinTerm
if !isTerm {
l.writer.color = ColorOff
l.headerColor = ColorOff
return
}
}
if l.headerColor == ColorOff {
l.writer.w = colorable.NewColorable(fi)
}
fi, ok := l.writer.w.(*os.File)
if !ok {
l.writer.color = ColorOff
l.headerColor = ColorOff
return
}
cfi := colorable.NewColorable(fi)
// NewColorable detects if color is possible and if it's not, then it
// returns the original value. So we can test if we got the original
// value back to know if color is possible.
if cfi == fi {
l.writer.color = ColorOff
l.headerColor = ColorOff
} else {
l.writer.w = cfi
}
}
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
+119 -29
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
@@ -8,7 +11,6 @@ import (
"fmt"
"io"
"log"
"os"
"reflect"
"runtime"
"sort"
@@ -53,11 +55,25 @@ var (
faintBoldColor = color.New(color.Faint, color.Bold)
faintColor = color.New(color.Faint)
faintMultiLinePrefix = faintColor.Sprint(" | ")
faintFieldSeparator = faintColor.Sprint("=")
faintFieldSeparatorWithNewLine = faintColor.Sprint("=\n")
faintMultiLinePrefix string
faintFieldSeparator string
faintFieldSeparatorWithNewLine string
)
func init() {
// Force all the colors to enabled because we do our own detection of color usage.
for _, c := range _levelToColor {
c.EnableColor()
}
faintBoldColor.EnableColor()
faintColor.EnableColor()
faintMultiLinePrefix = faintColor.Sprint(" | ")
faintFieldSeparator = faintColor.Sprint("=")
faintFieldSeparatorWithNewLine = faintColor.Sprint("=\n")
}
// Make sure that intLogger is a Logger
var _ Logger = &intLogger{}
@@ -77,6 +93,19 @@ type intLogger struct {
writer *writer
level *int32
// The value of curEpoch when our level was set
setEpoch uint64
// The value of curEpoch the last time we performed the level sync process
ownEpoch uint64
// Shared amongst all the loggers created in this hierachy, used to determine
// if the level sync process should be run by comparing it with ownEpoch
curEpoch *uint64
// The logger this one was created from. Only set when syncParentLevel is set
parent *intLogger
headerColor ColorOption
fieldColor ColorOption
@@ -86,6 +115,9 @@ type intLogger struct {
// create subloggers with their own level setting
independentLevels bool
syncParentLevel bool
subloggerHook func(sub Logger) Logger
}
// New returns a configured logger.
@@ -125,9 +157,9 @@ func newLogger(opts *LoggerOptions) *intLogger {
}
var (
primaryColor ColorOption = ColorOff
headerColor ColorOption = ColorOff
fieldColor ColorOption = ColorOff
primaryColor = ColorOff
headerColor = ColorOff
fieldColor = ColorOff
)
switch {
case opts.ColorHeaderOnly:
@@ -148,10 +180,13 @@ func newLogger(opts *LoggerOptions) *intLogger {
mutex: mutex,
writer: newWriter(output, primaryColor),
level: new(int32),
curEpoch: new(uint64),
exclude: opts.Exclude,
independentLevels: opts.IndependentLevels,
syncParentLevel: opts.SyncParentLevel,
headerColor: headerColor,
fieldColor: fieldColor,
subloggerHook: opts.SubloggerHook,
}
if opts.IncludeLocation {
l.callerOffset = offsetIntLogger + opts.AdditionalLocationOffset
@@ -167,6 +202,10 @@ func newLogger(opts *LoggerOptions) *intLogger {
l.timeFormat = opts.TimeFormat
}
if l.subloggerHook == nil {
l.subloggerHook = identityHook
}
l.setColorization(opts)
atomic.StoreInt32(l.level, int32(level))
@@ -174,6 +213,10 @@ func newLogger(opts *LoggerOptions) *intLogger {
return l
}
func identityHook(logger Logger) Logger {
return logger
}
// offsetIntLogger is the stack frame offset in the call stack for the caller to
// one of the Warn, Info, Log, etc methods.
const offsetIntLogger = 3
@@ -181,7 +224,7 @@ const offsetIntLogger = 3
// Log a message and a set of key/value pairs if the given level is at
// or more severe that the threshold configured in the Logger.
func (l *intLogger) log(name string, level Level, msg string, args ...interface{}) {
if level < Level(atomic.LoadInt32(l.level)) {
if level < l.GetLevel() {
return
}
@@ -261,7 +304,6 @@ func needsQuoting(str string) bool {
// 2. Color the whole log line, based on the level.
// 3. Color only the header (level) part of the log line.
// 4. Color both the header and fields of the log line.
//
func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, args ...interface{}) {
if !l.disableTime {
@@ -585,7 +627,7 @@ func (l *intLogger) logJSON(t time.Time, name string, level Level, msg string, a
vals := l.jsonMapEntry(t, name, level, msg)
args = append(l.implied, args...)
if args != nil && len(args) > 0 {
if len(args) > 0 {
if len(args)%2 != 0 {
cs, ok := args[len(args)-1].(CapturedStacktrace)
if ok {
@@ -706,27 +748,27 @@ func (l *intLogger) Error(msg string, args ...interface{}) {
// Indicate that the logger would emit TRACE level logs
func (l *intLogger) IsTrace() bool {
return Level(atomic.LoadInt32(l.level)) == Trace
return l.GetLevel() == Trace
}
// Indicate that the logger would emit DEBUG level logs
func (l *intLogger) IsDebug() bool {
return Level(atomic.LoadInt32(l.level)) <= Debug
return l.GetLevel() <= Debug
}
// Indicate that the logger would emit INFO level logs
func (l *intLogger) IsInfo() bool {
return Level(atomic.LoadInt32(l.level)) <= Info
return l.GetLevel() <= Info
}
// Indicate that the logger would emit WARN level logs
func (l *intLogger) IsWarn() bool {
return Level(atomic.LoadInt32(l.level)) <= Warn
return l.GetLevel() <= Warn
}
// Indicate that the logger would emit ERROR level logs
func (l *intLogger) IsError() bool {
return Level(atomic.LoadInt32(l.level)) <= Error
return l.GetLevel() <= Error
}
const MissingKey = "EXTRA_VALUE_AT_END"
@@ -776,7 +818,7 @@ func (l *intLogger) With(args ...interface{}) Logger {
sl.implied = append(sl.implied, MissingKey, extra)
}
return sl
return l.subloggerHook(sl)
}
// Create a new sub-Logger that a name decending from the current name.
@@ -790,7 +832,7 @@ func (l *intLogger) Named(name string) Logger {
sl.name = name
}
return sl
return l.subloggerHook(sl)
}
// Create a new sub-Logger with an explicit name. This ignores the current
@@ -801,7 +843,7 @@ func (l *intLogger) ResetNamed(name string) Logger {
sl.name = name
return sl
return l.subloggerHook(sl)
}
func (l *intLogger) ResetOutput(opts *LoggerOptions) error {
@@ -842,7 +884,63 @@ func (l *intLogger) resetOutput(opts *LoggerOptions) error {
// Update the logging level on-the-fly. This will affect all subloggers as
// well.
func (l *intLogger) SetLevel(level Level) {
atomic.StoreInt32(l.level, int32(level))
if !l.syncParentLevel {
atomic.StoreInt32(l.level, int32(level))
return
}
nsl := new(int32)
*nsl = int32(level)
l.level = nsl
l.ownEpoch = atomic.AddUint64(l.curEpoch, 1)
l.setEpoch = l.ownEpoch
}
func (l *intLogger) searchLevelPtr() *int32 {
p := l.parent
ptr := l.level
max := l.setEpoch
for p != nil {
if p.setEpoch > max {
max = p.setEpoch
ptr = p.level
}
p = p.parent
}
return ptr
}
// Returns the current level
func (l *intLogger) GetLevel() Level {
// We perform the loads immediately to keep the CPU pipeline busy, which
// effectively makes the second load cost nothing. Once loaded into registers
// the comparison returns the already loaded value. The comparison is almost
// always true, so the branch predictor should hit consistently with it.
var (
curEpoch = atomic.LoadUint64(l.curEpoch)
level = Level(atomic.LoadInt32(l.level))
own = l.ownEpoch
)
if curEpoch == own {
return level
}
// Perform the level sync process. We'll avoid doing this next time by seeing the
// epoch as current.
ptr := l.searchLevelPtr()
l.level = ptr
l.ownEpoch = curEpoch
return Level(atomic.LoadInt32(ptr))
}
// Create a *log.Logger that will send it's data through this Logger. This
@@ -872,16 +970,6 @@ func (l *intLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer {
}
}
// checks if the underlying io.Writer is a file, and
// panics if not. For use by colorization.
func (l *intLogger) checkWriterIsFile() *os.File {
fi, ok := l.writer.w.(*os.File)
if !ok {
panic("Cannot enable coloring of non-file Writers")
}
return fi
}
// Accept implements the SinkAdapter interface
func (i *intLogger) Accept(name string, level Level, msg string, args ...interface{}) {
i.log(name, level, msg, args...)
@@ -905,6 +993,8 @@ func (l *intLogger) copy() *intLogger {
if l.independentLevels {
sl.level = new(int32)
*sl.level = *l.level
} else if l.syncParentLevel {
sl.parent = l
}
return &sl
+39
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
@@ -89,6 +92,13 @@ const (
ForceColor
)
// SupportsColor is an optional interface that can be implemented by the output
// value. If implemented and SupportsColor() returns true, then AutoColor will
// enable colorization.
type SupportsColor interface {
SupportsColor() bool
}
// LevelFromString returns a Level type for the named log level, or "NoLevel" if
// the level string is invalid. This facilitates setting the log level via
// config or environment variable by name in a predictable way.
@@ -198,6 +208,9 @@ type Logger interface {
// implementation cannot update the level on the fly, it should no-op.
SetLevel(level Level)
// Returns the current level
GetLevel() Level
// Return a value that conforms to the stdlib log.Logger interface
StandardLogger(opts *StandardLoggerOptions) *log.Logger
@@ -220,6 +233,7 @@ type StandardLoggerOptions struct {
// [DEBUG] and strip it off before reapplying it.
// The timestamp detection may result in false positives and incomplete
// string outputs.
// InferLevelsWithTimestamp is only relevant if InferLevels is true.
InferLevelsWithTimestamp bool
// ForceLevel is used to force all output from the standard logger to be at
@@ -289,6 +303,31 @@ type LoggerOptions struct {
// logger will not affect any subloggers, and SetLevel on any subloggers
// will not affect the parent or sibling loggers.
IndependentLevels bool
// When set, changing the level of a logger effects only it's direct sub-loggers
// rather than all sub-loggers. For example:
// a := logger.Named("a")
// a.SetLevel(Error)
// b := a.Named("b")
// c := a.Named("c")
// b.GetLevel() => Error
// c.GetLevel() => Error
// b.SetLevel(Info)
// a.GetLevel() => Error
// b.GetLevel() => Info
// c.GetLevel() => Error
// a.SetLevel(Warn)
// a.GetLevel() => Warn
// b.GetLevel() => Warn
// c.GetLevel() => Warn
SyncParentLevel bool
// SubloggerHook registers a function that is called when a sublogger via
// Named, With, or ResetNamed is created. If defined, the function is passed
// the newly created Logger and the returned Logger is returned from the
// original function. This option allows customization via interception and
// wrapping of Logger instances.
SubloggerHook func(sub Logger) Logger
}
// InterceptLogger describes the interface for using a logger
+5
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
@@ -49,6 +52,8 @@ func (l *nullLogger) ResetNamed(name string) Logger { return l }
func (l *nullLogger) SetLevel(level Level) {}
func (l *nullLogger) GetLevel() Level { return NoLevel }
func (l *nullLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
return log.New(l.StandardWriter(opts), "", log.LstdFlags)
}
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog
import (
+85 -2
View File
@@ -1,3 +1,88 @@
## v1.6.0
CHANGES:
* plugin: Plugins written in other languages can optionally start to advertise whether they support gRPC broker multiplexing.
If the environment variable `PLUGIN_MULTIPLEX_GRPC` is set, it is safe to include a seventh field containing a boolean
value in the `|`-separated protocol negotiation line.
ENHANCEMENTS:
* Support muxing gRPC broker connections over a single listener [[GH-288](https://github.com/hashicorp/go-plugin/pull/288)]
* client: Configurable buffer size for reading plugin log lines [[GH-265](https://github.com/hashicorp/go-plugin/pull/265)]
* Use `buf` for proto generation [[GH-286](https://github.com/hashicorp/go-plugin/pull/286)]
* deps: bump golang.org/x/net to v0.17.0 [[GH-285](https://github.com/hashicorp/go-plugin/pull/285)]
* deps: bump golang.org/x/sys to v0.13.0 [[GH-285](https://github.com/hashicorp/go-plugin/pull/285)]
* deps: bump golang.org/x/text to v0.13.0 [[GH-285](https://github.com/hashicorp/go-plugin/pull/285)]
## v1.5.2
ENHANCEMENTS:
client: New `UnixSocketConfig.TempDir` option allows setting the directory to use when creating plugin-specific Unix socket directories [[GH-282](https://github.com/hashicorp/go-plugin/pull/282)]
## v1.5.1
BUGS:
* server: `PLUGIN_UNIX_SOCKET_DIR` is consistently used for gRPC broker sockets as well as the initial socket [[GH-277](https://github.com/hashicorp/go-plugin/pull/277)]
ENHANCEMENTS:
* client: New `UnixSocketConfig` option in `ClientConfig` to support making the client's Unix sockets group-writable [[GH-277](https://github.com/hashicorp/go-plugin/pull/277)]
## v1.5.0
ENHANCEMENTS:
* client: New `runner.Runner` interface to support clients providing custom plugin command runner implementations [[GH-270](https://github.com/hashicorp/go-plugin/pull/270)]
* Accessible via new `ClientConfig` field `RunnerFunc`, which is mutually exclusive with `Cmd` and `Reattach`
* Reattaching support via `ReattachConfig` field `ReattachFunc`
* client: New `ClientConfig` field `SkipHostEnv` allows omitting the client process' own environment variables from the plugin command's environment [[GH-270](https://github.com/hashicorp/go-plugin/pull/270)]
* client: Add `ID()` method to `Client` for retrieving the pid or other unique ID of a running plugin [[GH-272](https://github.com/hashicorp/go-plugin/pull/272)]
* server: Support setting the directory to create Unix sockets in with the env var `PLUGIN_UNIX_SOCKET_DIR` [[GH-270](https://github.com/hashicorp/go-plugin/pull/270)]
* server: Support setting group write permission and a custom group name or gid owner with the env var `PLUGIN_UNIX_SOCKET_GROUP` [[GH-270](https://github.com/hashicorp/go-plugin/pull/270)]
## v1.4.11-rc1
ENHANCEMENTS:
* deps: bump protoreflect to v1.15.1 [[GH-264](https://github.com/hashicorp/go-plugin/pull/264)]
## v1.4.10
BUG FIXES:
* additional notes: ensure to close files [[GH-241](https://github.com/hashicorp/go-plugin/pull/241)]
ENHANCEMENTS:
* deps: Remove direct dependency on golang.org/x/net [[GH-240](https://github.com/hashicorp/go-plugin/pull/240)]
## v1.4.9
ENHANCEMENTS:
* client: Remove log warning introduced in 1.4.5 when SecureConfig is nil. [[GH-238](https://github.com/hashicorp/go-plugin/pull/238)]
## v1.4.8
BUG FIXES:
* Fix windows build: [[GH-227](https://github.com/hashicorp/go-plugin/pull/227)]
## v1.4.7
ENHANCEMENTS:
* More detailed error message on plugin start failure: [[GH-223](https://github.com/hashicorp/go-plugin/pull/223)]
## v1.4.6
BUG FIXES:
* server: Prevent gRPC broker goroutine leak when using `GRPCServer` type `GracefulStop()` or `Stop()` methods [[GH-220](https://github.com/hashicorp/go-plugin/pull/220)]
## v1.4.5
ENHANCEMENTS:
@@ -15,5 +100,3 @@ BUG FIXES:
* Bidirectional communication: fix bidirectional communication when AutoMTLS is enabled [[GH-193](https://github.com/hashicorp/go-plugin/pull/193)]
* RPC: Trim a spurious log message for plugins using RPC [[GH-186](https://github.com/hashicorp/go-plugin/pull/186)]
+2
View File
@@ -1,3 +1,5 @@
Copyright (c) 2016 HashiCorp, Inc.
Mozilla Public License, version 2.0
1. Definitions
+3 -2
View File
@@ -4,8 +4,9 @@
that has been in use by HashiCorp tooling for over 4 years. While initially
created for [Packer](https://www.packer.io), it is additionally in use by
[Terraform](https://www.terraform.io), [Nomad](https://www.nomadproject.io),
[Vault](https://www.vaultproject.io), and
[Boundary](https://www.boundaryproject.io).
[Vault](https://www.vaultproject.io),
[Boundary](https://www.boundaryproject.io),
and [Waypoint](https://www.waypointproject.io).
While the plugin system is over RPC, it is currently only designed to work
over a local [reliable] network. Plugins over a real network are not supported
+14
View File
@@ -0,0 +1,14 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
version: v1
plugins:
- plugin: buf.build/protocolbuffers/go
out: .
opt:
- paths=source_relative
- plugin: buf.build/grpc/go:v1.3.0
out: .
opt:
- paths=source_relative
- require_unimplemented_servers=false
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
version: v1
build:
excludes:
- examples/
+287 -103
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
@@ -23,6 +26,9 @@ import (
"time"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin/internal/cmdrunner"
"github.com/hashicorp/go-plugin/internal/grpcmux"
"github.com/hashicorp/go-plugin/runner"
"google.golang.org/grpc"
)
@@ -41,7 +47,7 @@ var managedClientsLock sync.Mutex
var (
// ErrProcessNotFound is returned when a client is instantiated to
// reattach to an existing process and it isn't found.
ErrProcessNotFound = errors.New("Reattachment process not found")
ErrProcessNotFound = cmdrunner.ErrProcessNotFound
// ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match
// the one provided in the SecureConfig.
@@ -58,8 +64,18 @@ var (
// ErrSecureConfigAndReattach is returned when both Reattach and
// SecureConfig are set.
ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set")
// ErrGRPCBrokerMuxNotSupported is returned when the client requests
// multiplexing over the gRPC broker, but the plugin does not support the
// feature. In most cases, this should be resolvable by updating and
// rebuilding the plugin, or restarting the plugin with
// ClientConfig.GRPCBrokerMultiplex set to false.
ErrGRPCBrokerMuxNotSupported = errors.New("client requested gRPC broker multiplexing but plugin does not support the feature")
)
// defaultPluginLogBufferSize is the default size of the buffer used to read from stderr for plugin log lines.
const defaultPluginLogBufferSize = 64 * 1024
// Client handles the lifecycle of a plugin application. It launches
// plugins, connects to them, dispenses interface implementations, and handles
// killing the process.
@@ -76,7 +92,7 @@ type Client struct {
exited bool
l sync.Mutex
address net.Addr
process *os.Process
runner runner.AttachedRunner
client ClientProtocol
protocol Protocol
logger hclog.Logger
@@ -95,6 +111,11 @@ type Client struct {
// processKilled is used for testing only, to flag when the process was
// forcefully killed.
processKilled bool
unixSocketCfg UnixSocketConfig
grpcMuxerOnce sync.Once
grpcMuxer *grpcmux.GRPCClientMuxer
}
// NegotiatedVersion returns the protocol version negotiated with the server.
@@ -103,6 +124,19 @@ func (c *Client) NegotiatedVersion() int {
return c.negotiatedVersion
}
// ID returns a unique ID for the running plugin. By default this is the process
// ID (pid), but it could take other forms if RunnerFunc was provided.
func (c *Client) ID() string {
c.l.Lock()
defer c.l.Unlock()
if c.runner != nil {
return c.runner.ID()
}
return ""
}
// ClientConfig is the configuration used to initialize a new
// plugin client. After being used to initialize a plugin client,
// that configuration must not be modified again.
@@ -130,6 +164,13 @@ type ClientConfig struct {
Cmd *exec.Cmd
Reattach *ReattachConfig
// RunnerFunc allows consumers to provide their own implementation of
// runner.Runner and control the context within which a plugin is executed.
// The cmd argument will have been copied from the config and populated with
// environment variables that a go-plugin server expects to read such as
// AutoMTLS certs and the magic cookie key.
RunnerFunc func(l hclog.Logger, cmd *exec.Cmd, tmpDir string) (runner.Runner, error)
// SecureConfig is configuration for verifying the integrity of the
// executable. It can not be used with Reattach.
SecureConfig *SecureConfig
@@ -182,6 +223,10 @@ type ClientConfig struct {
// it will default to hclog's default logger.
Logger hclog.Logger
// PluginLogBufferSize is the buffer size(bytes) to read from stderr for plugin log lines.
// If this is 0, then the default of 64KB is used.
PluginLogBufferSize int
// AutoMTLS has the client and server automatically negotiate mTLS for
// transport authentication. This ensures that only the original client will
// be allowed to connect to the server, and all other connections will be
@@ -209,6 +254,44 @@ type ClientConfig struct {
// to create gRPC connections. This only affects plugins using the gRPC
// protocol.
GRPCDialOptions []grpc.DialOption
// GRPCBrokerMultiplex turns on multiplexing for the gRPC broker. The gRPC
// broker will multiplex all brokered gRPC servers over the plugin's original
// listener socket instead of making a new listener for each server. The
// go-plugin library currently only includes a Go implementation for the
// server (i.e. plugin) side of gRPC broker multiplexing.
//
// Does not support reattaching.
//
// Multiplexed gRPC streams MUST be established sequentially, i.e. after
// calling AcceptAndServe from one side, wait for the other side to Dial
// before calling AcceptAndServe again.
GRPCBrokerMultiplex bool
// SkipHostEnv allows plugins to run without inheriting the parent process'
// environment variables.
SkipHostEnv bool
// UnixSocketConfig configures additional options for any Unix sockets
// that are created. Not normally required. Not supported on Windows.
UnixSocketConfig *UnixSocketConfig
}
type UnixSocketConfig struct {
// If set, go-plugin will change the owner of any Unix sockets created to
// this group, and set them as group-writable. Can be a name or gid. The
// client process must be a member of this group or chown will fail.
Group string
// TempDir specifies the base directory to use when creating a plugin-specific
// temporary directory. It is expected to already exist and be writable. If
// not set, defaults to the directory chosen by os.MkdirTemp.
TempDir string
// The directory to create Unix sockets in. Internally created and managed
// by go-plugin and deleted when the plugin is killed. Will be created
// inside TempDir if specified.
socketDir string
}
// ReattachConfig is used to configure a client to reattach to an
@@ -220,6 +303,11 @@ type ReattachConfig struct {
Addr net.Addr
Pid int
// ReattachFunc allows consumers to provide their own implementation of
// runner.AttachedRunner and attach to something other than a plain process.
// At least one of Pid or ReattachFunc must be set.
ReattachFunc runner.ReattachFunc
// Test is set to true if this is reattaching to to a plugin in "test mode"
// (see ServeConfig.Test). In this mode, client.Kill will NOT kill the
// process and instead will rely on the plugin to terminate itself. This
@@ -295,11 +383,11 @@ func CleanupClients() {
wg.Wait()
}
// Creates a new plugin client which manages the lifecycle of an external
// NewClient creates a new plugin client which manages the lifecycle of an external
// plugin and gets the address for the RPC connection.
//
// The client must be cleaned up at some point by calling Kill(). If
// the client is a managed client (created with NewManagedClient) you
// the client is a managed client (created with ClientConfig.Managed) you
// can just call CleanupClients at the end of your program and they will
// be properly cleaned.
func NewClient(config *ClientConfig) (c *Client) {
@@ -317,10 +405,10 @@ func NewClient(config *ClientConfig) (c *Client) {
}
if config.SyncStdout == nil {
config.SyncStdout = ioutil.Discard
config.SyncStdout = io.Discard
}
if config.SyncStderr == nil {
config.SyncStderr = ioutil.Discard
config.SyncStderr = io.Discard
}
if config.AllowedProtocols == nil {
@@ -335,6 +423,10 @@ func NewClient(config *ClientConfig) (c *Client) {
})
}
if config.PluginLogBufferSize == 0 {
config.PluginLogBufferSize = defaultPluginLogBufferSize
}
c = &Client{
config: config,
logger: config.Logger,
@@ -407,12 +499,13 @@ func (c *Client) killed() bool {
func (c *Client) Kill() {
// Grab a lock to read some private fields.
c.l.Lock()
process := c.process
runner := c.runner
addr := c.address
hostSocketDir := c.unixSocketCfg.socketDir
c.l.Unlock()
// If there is no process, there is nothing to kill.
if process == nil {
// If there is no runner or ID, there is nothing to kill.
if runner == nil || runner.ID() == "" {
return
}
@@ -420,10 +513,14 @@ func (c *Client) Kill() {
// Wait for the all client goroutines to finish.
c.clientWaitGroup.Wait()
if hostSocketDir != "" {
os.RemoveAll(hostSocketDir)
}
// Make sure there is no reference to the old process after it has been
// killed.
c.l.Lock()
c.process = nil
c.runner = nil
c.l.Unlock()
}()
@@ -466,14 +563,16 @@ func (c *Client) Kill() {
// If graceful exiting failed, just kill it
c.logger.Warn("plugin failed to exit gracefully")
process.Kill()
if err := runner.Kill(context.Background()); err != nil {
c.logger.Debug("error killing plugin", "error", err)
}
c.l.Lock()
c.processKilled = true
c.l.Unlock()
}
// Starts the underlying subprocess, communicating with it to negotiate
// Start the underlying subprocess, communicating with it to negotiate
// a port for RPC connections, and returning the address to connect via RPC.
//
// This method is safe to call multiple times. Subsequent calls have no effect.
@@ -491,16 +590,27 @@ func (c *Client) Start() (addr net.Addr, err error) {
// this in a {} for scoping reasons, and hopeful that the escape
// analysis will pop the stack here.
{
cmdSet := c.config.Cmd != nil
attachSet := c.config.Reattach != nil
secureSet := c.config.SecureConfig != nil
if cmdSet == attachSet {
return nil, fmt.Errorf("Only one of Cmd or Reattach must be set")
var mutuallyExclusiveOptions int
if c.config.Cmd != nil {
mutuallyExclusiveOptions += 1
}
if c.config.Reattach != nil {
mutuallyExclusiveOptions += 1
}
if c.config.RunnerFunc != nil {
mutuallyExclusiveOptions += 1
}
if mutuallyExclusiveOptions != 1 {
return nil, fmt.Errorf("exactly one of Cmd, or Reattach, or RunnerFunc must be set")
}
if secureSet && attachSet {
if c.config.SecureConfig != nil && c.config.Reattach != nil {
return nil, ErrSecureConfigAndReattach
}
if c.config.GRPCBrokerMultiplex && c.config.Reattach != nil {
return nil, fmt.Errorf("gRPC broker multiplexing is not supported with Reattach config")
}
}
if c.config.Reattach != nil {
@@ -532,24 +642,24 @@ func (c *Client) Start() (addr net.Addr, err error) {
fmt.Sprintf("PLUGIN_MAX_PORT=%d", c.config.MaxPort),
fmt.Sprintf("PLUGIN_PROTOCOL_VERSIONS=%s", strings.Join(versionStrings, ",")),
}
if c.config.GRPCBrokerMultiplex {
env = append(env, fmt.Sprintf("%s=true", envMultiplexGRPC))
}
cmd := c.config.Cmd
cmd.Env = append(cmd.Env, os.Environ()...)
if cmd == nil {
// It's only possible to get here if RunnerFunc is non-nil, but we'll
// still use cmd as a spec to populate metadata for the external
// implementation to consume.
cmd = exec.Command("")
}
if !c.config.SkipHostEnv {
cmd.Env = append(cmd.Env, os.Environ()...)
}
cmd.Env = append(cmd.Env, env...)
cmd.Stdin = os.Stdin
cmdStdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
cmdStderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
if c.config.SecureConfig == nil {
c.logger.Warn("plugin configured with a nil SecureConfig")
} else {
if c.config.SecureConfig != nil {
if ok, err := c.config.SecureConfig.Check(cmd.Path); err != nil {
return nil, fmt.Errorf("error verifying checksum: %s", err)
} else if !ok {
@@ -582,26 +692,62 @@ func (c *Client) Start() (addr net.Addr, err error) {
}
}
c.logger.Debug("starting plugin", "path", cmd.Path, "args", cmd.Args)
err = cmd.Start()
if err != nil {
return
if c.config.UnixSocketConfig != nil {
c.unixSocketCfg = *c.config.UnixSocketConfig
}
// Set the process
c.process = cmd.Process
c.logger.Debug("plugin started", "path", cmd.Path, "pid", c.process.Pid)
if c.unixSocketCfg.Group != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvUnixSocketGroup, c.unixSocketCfg.Group))
}
var runner runner.Runner
switch {
case c.config.RunnerFunc != nil:
c.unixSocketCfg.socketDir, err = os.MkdirTemp(c.unixSocketCfg.TempDir, "plugin-dir")
if err != nil {
return nil, err
}
// os.MkdirTemp creates folders with 0o700, so if we have a group
// configured we need to make it group-writable.
if c.unixSocketCfg.Group != "" {
err = setGroupWritable(c.unixSocketCfg.socketDir, c.unixSocketCfg.Group, 0o770)
if err != nil {
return nil, err
}
}
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvUnixSocketDir, c.unixSocketCfg.socketDir))
c.logger.Trace("created temporary directory for unix sockets", "dir", c.unixSocketCfg.socketDir)
runner, err = c.config.RunnerFunc(c.logger, cmd, c.unixSocketCfg.socketDir)
if err != nil {
return nil, err
}
default:
runner, err = cmdrunner.NewCmdRunner(c.logger, cmd)
if err != nil {
return nil, err
}
}
c.runner = runner
startCtx, startCtxCancel := context.WithTimeout(context.Background(), c.config.StartTimeout)
defer startCtxCancel()
err = runner.Start(startCtx)
if err != nil {
return nil, err
}
// Make sure the command is properly cleaned up if there is an error
defer func() {
r := recover()
rErr := recover()
if err != nil || r != nil {
cmd.Process.Kill()
if err != nil || rErr != nil {
runner.Kill(context.Background())
}
if r != nil {
panic(r)
if rErr != nil {
panic(rErr)
}
}()
@@ -612,7 +758,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
c.clientWaitGroup.Add(1)
c.stderrWaitGroup.Add(1)
// logStderr calls Done()
go c.logStderr(cmdStderr)
go c.logStderr(runner.Name(), runner.Stderr())
c.clientWaitGroup.Add(1)
go func() {
@@ -621,29 +767,17 @@ func (c *Client) Start() (addr net.Addr, err error) {
defer c.clientWaitGroup.Done()
// get the cmd info early, since the process information will be removed
// in Kill.
pid := c.process.Pid
path := cmd.Path
// wait to finish reading from stderr since the stderr pipe reader
// will be closed by the subsequent call to cmd.Wait().
c.stderrWaitGroup.Wait()
// Wait for the command to end.
err := cmd.Wait()
msgArgs := []interface{}{
"path", path,
"pid", pid,
}
err := runner.Wait(context.Background())
if err != nil {
msgArgs = append(msgArgs,
[]interface{}{"error", err.Error()}...)
c.logger.Error("plugin process exited", msgArgs...)
c.logger.Error("plugin process exited", "plugin", runner.Name(), "id", runner.ID(), "error", err.Error())
} else {
// Log and make sure to flush the logs right away
c.logger.Info("plugin process exited", msgArgs...)
c.logger.Info("plugin process exited", "plugin", runner.Name(), "id", runner.ID())
}
os.Stderr.Sync()
@@ -662,10 +796,13 @@ func (c *Client) Start() (addr net.Addr, err error) {
defer c.clientWaitGroup.Done()
defer close(linesCh)
scanner := bufio.NewScanner(cmdStdout)
scanner := bufio.NewScanner(runner.Stdout())
for scanner.Scan() {
linesCh <- scanner.Text()
}
if scanner.Err() != nil {
c.logger.Error("error encountered while scanning stdout", "error", scanner.Err())
}
}()
// Make sure after we exit we read the lines from stdout forever
@@ -685,22 +822,27 @@ func (c *Client) Start() (addr net.Addr, err error) {
timeout := time.After(c.config.StartTimeout)
// Start looking for the address
c.logger.Debug("waiting for RPC address", "path", cmd.Path)
c.logger.Debug("waiting for RPC address", "plugin", runner.Name())
select {
case <-timeout:
err = errors.New("timeout while waiting for plugin to start")
case <-c.doneCtx.Done():
err = errors.New("plugin exited before we could connect")
case line := <-linesCh:
case line, ok := <-linesCh:
// Trim the line and split by "|" in order to get the parts of
// the output.
line = strings.TrimSpace(line)
parts := strings.SplitN(line, "|", 6)
parts := strings.Split(line, "|")
if len(parts) < 4 {
err = fmt.Errorf(
"Unrecognized remote plugin message: %s\n\n"+
"This usually means that the plugin is either invalid or simply\n"+
"needs to be recompiled to support the latest protocol.", line)
errText := fmt.Sprintf("Unrecognized remote plugin message: %s", line)
if !ok {
errText += "\n" + "Failed to read any lines from plugin's stdout"
}
additionalNotes := runner.Diagnose(context.Background())
if additionalNotes != "" {
errText += "\n" + additionalNotes
}
err = errors.New(errText)
return
}
@@ -735,13 +877,18 @@ func (c *Client) Start() (addr net.Addr, err error) {
c.negotiatedVersion = version
c.logger.Debug("using plugin", "version", version)
switch parts[2] {
network, address, err := runner.PluginToHost(parts[2], parts[3])
if err != nil {
return addr, err
}
switch network {
case "tcp":
addr, err = net.ResolveTCPAddr("tcp", parts[3])
addr, err = net.ResolveTCPAddr("tcp", address)
case "unix":
addr, err = net.ResolveUnixAddr("unix", parts[3])
addr, err = net.ResolveUnixAddr("unix", address)
default:
err = fmt.Errorf("Unknown address type: %s", parts[3])
err = fmt.Errorf("Unknown address type: %s", address)
}
// If we have a server type, then record that. We default to net/rpc
@@ -773,6 +920,18 @@ func (c *Client) Start() (addr net.Addr, err error) {
return nil, fmt.Errorf("error parsing server cert: %s", err)
}
}
if c.config.GRPCBrokerMultiplex && c.protocol == ProtocolGRPC {
if len(parts) <= 6 {
return nil, fmt.Errorf("%w; for Go plugins, you will need to update the "+
"github.com/hashicorp/go-plugin dependency and recompile", ErrGRPCBrokerMuxNotSupported)
}
if muxSupported, err := strconv.ParseBool(parts[6]); err != nil {
return nil, fmt.Errorf("error parsing %q as a boolean for gRPC broker multiplexing support", parts[6])
} else if !muxSupported {
return nil, ErrGRPCBrokerMuxNotSupported
}
}
}
c.address = addr
@@ -802,39 +961,30 @@ func (c *Client) loadServerCert(cert string) error {
}
func (c *Client) reattach() (net.Addr, error) {
// Verify the process still exists. If not, then it is an error
p, err := os.FindProcess(c.config.Reattach.Pid)
if err != nil {
// On Unix systems, FindProcess never returns an error.
// On Windows, for non-existent pids it returns:
// os.SyscallError - 'OpenProcess: the paremter is incorrect'
return nil, ErrProcessNotFound
reattachFunc := c.config.Reattach.ReattachFunc
// For backwards compatibility default to cmdrunner.ReattachFunc
if reattachFunc == nil {
reattachFunc = cmdrunner.ReattachFunc(c.config.Reattach.Pid, c.config.Reattach.Addr)
}
// Attempt to connect to the addr since on Unix systems FindProcess
// doesn't actually return an error if it can't find the process.
conn, err := net.Dial(
c.config.Reattach.Addr.Network(),
c.config.Reattach.Addr.String())
r, err := reattachFunc()
if err != nil {
p.Kill()
return nil, ErrProcessNotFound
return nil, err
}
conn.Close()
// Create a context for when we kill
c.doneCtx, c.ctxCancel = context.WithCancel(context.Background())
c.clientWaitGroup.Add(1)
// Goroutine to mark exit status
go func(pid int) {
go func(r runner.AttachedRunner) {
defer c.clientWaitGroup.Done()
// ensure the context is cancelled when we're done
defer c.ctxCancel()
// Wait for the process to die
pidWait(pid)
r.Wait(context.Background())
// Log so we can see it
c.logger.Debug("reattached plugin process exited")
@@ -843,7 +993,7 @@ func (c *Client) reattach() (net.Addr, error) {
c.l.Lock()
defer c.l.Unlock()
c.exited = true
}(p.Pid)
}(r)
// Set the address and protocol
c.address = c.config.Reattach.Addr
@@ -855,13 +1005,12 @@ func (c *Client) reattach() (net.Addr, error) {
if c.config.Reattach.Test {
c.negotiatedVersion = c.config.Reattach.ProtocolVersion
}
// If we're in test mode, we do NOT set the process. This avoids the
// process being killed (the only purpose we have for c.process), since
// in test mode the process is responsible for exiting on its own.
if !c.config.Reattach.Test {
c.process = p
} else {
// If we're in test mode, we do NOT set the runner. This avoids the
// runner being killed (the only purpose we have for setting c.runner
// when reattaching), since in test mode the process is responsible for
// exiting on its own.
c.runner = r
}
return c.address, nil
@@ -900,6 +1049,9 @@ func (c *Client) checkProtoVersion(protoVersion string) (int, PluginSet, error)
//
// If this returns nil then the process hasn't been started yet. Please
// call Start or Client before calling this.
//
// Clients who specified a RunnerFunc will need to populate their own
// ReattachFunc in the returned ReattachConfig before it can be used.
func (c *Client) ReattachConfig() *ReattachConfig {
c.l.Lock()
defer c.l.Unlock()
@@ -917,11 +1069,16 @@ func (c *Client) ReattachConfig() *ReattachConfig {
return c.config.Reattach
}
return &ReattachConfig{
reattach := &ReattachConfig{
Protocol: c.protocol,
Addr: c.address,
Pid: c.config.Cmd.Process.Pid,
}
if c.config.Cmd != nil && c.config.Cmd.Process != nil {
reattach.Pid = c.config.Cmd.Process.Pid
}
return reattach
}
// Protocol returns the protocol of server on the remote end. This will
@@ -957,11 +1114,24 @@ func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error)
// dialer is compatible with grpc.WithDialer and creates the connection
// to the plugin.
func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
conn, err := netAddrDialer(c.address)("", timeout)
muxer, err := c.getGRPCMuxer(c.address)
if err != nil {
return nil, err
}
var conn net.Conn
if muxer.Enabled() {
conn, err = muxer.Dial()
if err != nil {
return nil, err
}
} else {
conn, err = netAddrDialer(c.address)("", timeout)
if err != nil {
return nil, err
}
}
// If we have a TLS config we wrap our connection. We only do this
// for net/rpc since gRPC uses its own mechanism for TLS.
if c.protocol == ProtocolNetRPC && c.config.TLSConfig != nil {
@@ -971,14 +1141,28 @@ func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
return conn, nil
}
var stdErrBufferSize = 64 * 1024
func (c *Client) getGRPCMuxer(addr net.Addr) (*grpcmux.GRPCClientMuxer, error) {
if c.protocol != ProtocolGRPC || !c.config.GRPCBrokerMultiplex {
return nil, nil
}
func (c *Client) logStderr(r io.Reader) {
var err error
c.grpcMuxerOnce.Do(func() {
c.grpcMuxer, err = grpcmux.NewGRPCClientMuxer(c.logger, addr)
})
if err != nil {
return nil, err
}
return c.grpcMuxer, nil
}
func (c *Client) logStderr(name string, r io.Reader) {
defer c.clientWaitGroup.Done()
defer c.stderrWaitGroup.Done()
l := c.logger.Named(filepath.Base(c.config.Cmd.Path))
l := c.logger.Named(filepath.Base(name))
reader := bufio.NewReaderSize(r, stdErrBufferSize)
reader := bufio.NewReaderSize(r, c.config.PluginLogBufferSize)
// continuation indicates the previous line was a prefix
continuation := false
+16
View File
@@ -0,0 +1,16 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
const (
// EnvUnixSocketDir specifies the directory that _plugins_ should create unix
// sockets in. Does not affect client behavior.
EnvUnixSocketDir = "PLUGIN_UNIX_SOCKET_DIR"
// EnvUnixSocketGroup specifies the owning, writable group to set for Unix
// sockets created by _plugins_. Does not affect client behavior.
EnvUnixSocketGroup = "PLUGIN_UNIX_SOCKET_GROUP"
envMultiplexGRPC = "PLUGIN_MULTIPLEX_GRPC"
)
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
// This is a type that wraps error types so that they can be messaged
+222 -25
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
@@ -11,7 +14,9 @@ import (
"sync/atomic"
"time"
"github.com/hashicorp/go-plugin/internal/grpcmux"
"github.com/hashicorp/go-plugin/internal/plugin"
"github.com/hashicorp/go-plugin/runner"
"github.com/oklog/run"
"google.golang.org/grpc"
@@ -36,6 +41,8 @@ type sendErr struct {
// connection information to/from the plugin. Implements GRPCBrokerServer and
// streamer interfaces.
type gRPCBrokerServer struct {
plugin.UnimplementedGRPCBrokerServer
// send is used to send connection info to the gRPC stream.
send chan *sendErr
@@ -259,25 +266,41 @@ func (s *gRPCBrokerClientImpl) Close() {
type GRPCBroker struct {
nextId uint32
streamer streamer
streams map[uint32]*gRPCBrokerPending
tls *tls.Config
doneCh chan struct{}
o sync.Once
clientStreams map[uint32]*gRPCBrokerPending
serverStreams map[uint32]*gRPCBrokerPending
unixSocketCfg UnixSocketConfig
addrTranslator runner.AddrTranslator
dialMutex sync.Mutex
muxer grpcmux.GRPCMuxer
sync.Mutex
}
type gRPCBrokerPending struct {
ch chan *plugin.ConnInfo
doneCh chan struct{}
once sync.Once
}
func newGRPCBroker(s streamer, tls *tls.Config) *GRPCBroker {
func newGRPCBroker(s streamer, tls *tls.Config, unixSocketCfg UnixSocketConfig, addrTranslator runner.AddrTranslator, muxer grpcmux.GRPCMuxer) *GRPCBroker {
return &GRPCBroker{
streamer: s,
streams: make(map[uint32]*gRPCBrokerPending),
tls: tls,
doneCh: make(chan struct{}),
clientStreams: make(map[uint32]*gRPCBrokerPending),
serverStreams: make(map[uint32]*gRPCBrokerPending),
muxer: muxer,
unixSocketCfg: unixSocketCfg,
addrTranslator: addrTranslator,
}
}
@@ -285,15 +308,59 @@ func newGRPCBroker(s streamer, tls *tls.Config) *GRPCBroker {
//
// This should not be called multiple times with the same ID at one time.
func (b *GRPCBroker) Accept(id uint32) (net.Listener, error) {
listener, err := serverListener()
if b.muxer.Enabled() {
p := b.getServerStream(id)
go func() {
err := b.listenForKnocks(id)
if err != nil {
log.Printf("[ERR]: error listening for knocks, id: %d, error: %s", id, err)
}
}()
ln, err := b.muxer.Listener(id, p.doneCh)
if err != nil {
return nil, err
}
ln = &rmListener{
Listener: ln,
close: func() error {
// We could have multiple listeners on the same ID, so use sync.Once
// for closing doneCh to ensure we don't get a panic.
p.once.Do(func() {
close(p.doneCh)
})
b.Lock()
defer b.Unlock()
// No longer need to listen for knocks once the listener is closed.
delete(b.serverStreams, id)
return nil
},
}
return ln, nil
}
listener, err := serverListener(b.unixSocketCfg)
if err != nil {
return nil, err
}
advertiseNet := listener.Addr().Network()
advertiseAddr := listener.Addr().String()
if b.addrTranslator != nil {
advertiseNet, advertiseAddr, err = b.addrTranslator.HostToPlugin(advertiseNet, advertiseAddr)
if err != nil {
return nil, err
}
}
err = b.streamer.Send(&plugin.ConnInfo{
ServiceId: id,
Network: listener.Addr().Network(),
Address: listener.Addr().String(),
Network: advertiseNet,
Address: advertiseAddr,
})
if err != nil {
return nil, err
@@ -309,20 +376,20 @@ func (b *GRPCBroker) Accept(id uint32) (net.Listener, error) {
// connection is opened every call, these calls should be used sparingly.
// Multiple gRPC server implementations can be registered to a single
// AcceptAndServe call.
func (b *GRPCBroker) AcceptAndServe(id uint32, s func([]grpc.ServerOption) *grpc.Server) {
listener, err := b.Accept(id)
func (b *GRPCBroker) AcceptAndServe(id uint32, newGRPCServer func([]grpc.ServerOption) *grpc.Server) {
ln, err := b.Accept(id)
if err != nil {
log.Printf("[ERR] plugin: plugin acceptAndServe error: %s", err)
return
}
defer listener.Close()
defer ln.Close()
var opts []grpc.ServerOption
if b.tls != nil {
opts = []grpc.ServerOption{grpc.Creds(credentials.NewTLS(b.tls))}
}
server := s(opts)
server := newGRPCServer(opts)
// Here we use a run group to close this goroutine if the server is shutdown
// or the broker is shutdown.
@@ -330,7 +397,7 @@ func (b *GRPCBroker) AcceptAndServe(id uint32, s func([]grpc.ServerOption) *grpc
{
// Serve on the listener, if shutting down call GracefulStop.
g.Add(func() error {
return server.Serve(listener)
return server.Serve(ln)
}, func(err error) {
server.GracefulStop()
})
@@ -363,12 +430,108 @@ func (b *GRPCBroker) Close() error {
return nil
}
func (b *GRPCBroker) listenForKnocks(id uint32) error {
p := b.getServerStream(id)
for {
select {
case msg := <-p.ch:
// Shouldn't be possible.
if msg.ServiceId != id {
return fmt.Errorf("knock received with wrong service ID; expected %d but got %d", id, msg.ServiceId)
}
// Also shouldn't be possible.
if msg.Knock == nil || !msg.Knock.Knock || msg.Knock.Ack {
return fmt.Errorf("knock received for service ID %d with incorrect values; knock=%+v", id, msg.Knock)
}
// Successful knock, open the door for the given ID.
var ackError string
err := b.muxer.AcceptKnock(id)
if err != nil {
ackError = err.Error()
}
// Send back an acknowledgement to allow the client to start dialling.
err = b.streamer.Send(&plugin.ConnInfo{
ServiceId: id,
Knock: &plugin.ConnInfo_Knock{
Knock: true,
Ack: true,
Error: ackError,
},
})
if err != nil {
return fmt.Errorf("error sending back knock acknowledgement: %w", err)
}
case <-p.doneCh:
return nil
}
}
}
func (b *GRPCBroker) knock(id uint32) error {
// Send a knock.
err := b.streamer.Send(&plugin.ConnInfo{
ServiceId: id,
Knock: &plugin.ConnInfo_Knock{
Knock: true,
},
})
if err != nil {
return err
}
// Wait for the ack.
p := b.getClientStream(id)
select {
case msg := <-p.ch:
if msg.ServiceId != id {
return fmt.Errorf("handshake failed for multiplexing on id %d; got response for %d", id, msg.ServiceId)
}
if msg.Knock == nil || !msg.Knock.Knock || !msg.Knock.Ack {
return fmt.Errorf("handshake failed for multiplexing on id %d; expected knock and ack, but got %+v", id, msg.Knock)
}
if msg.Knock.Error != "" {
return fmt.Errorf("failed to knock for id %d: %s", id, msg.Knock.Error)
}
case <-time.After(5 * time.Second):
return fmt.Errorf("timeout waiting for multiplexing knock handshake on id %d", id)
}
return nil
}
func (b *GRPCBroker) muxDial(id uint32) func(string, time.Duration) (net.Conn, error) {
return func(string, time.Duration) (net.Conn, error) {
b.dialMutex.Lock()
defer b.dialMutex.Unlock()
// Tell the other side the listener ID it should give the next stream to.
err := b.knock(id)
if err != nil {
return nil, fmt.Errorf("failed to knock before dialling client: %w", err)
}
conn, err := b.muxer.Dial()
if err != nil {
return nil, err
}
return conn, nil
}
}
// Dial opens a connection by ID.
func (b *GRPCBroker) Dial(id uint32) (conn *grpc.ClientConn, err error) {
if b.muxer.Enabled() {
return dialGRPCConn(b.tls, b.muxDial(id))
}
var c *plugin.ConnInfo
// Open the stream
p := b.getStream(id)
p := b.getClientStream(id)
select {
case c = <-p.ch:
close(p.doneCh)
@@ -376,12 +539,20 @@ func (b *GRPCBroker) Dial(id uint32) (conn *grpc.ClientConn, err error) {
return nil, fmt.Errorf("timeout waiting for connection info")
}
network, address := c.Network, c.Address
if b.addrTranslator != nil {
network, address, err = b.addrTranslator.PluginToHost(network, address)
if err != nil {
return nil, err
}
}
var addr net.Addr
switch c.Network {
switch network {
case "tcp":
addr, err = net.ResolveTCPAddr("tcp", c.Address)
addr, err = net.ResolveTCPAddr("tcp", address)
case "unix":
addr, err = net.ResolveUnixAddr("unix", c.Address)
addr, err = net.ResolveUnixAddr("unix", address)
default:
err = fmt.Errorf("Unknown address type: %s", c.Address)
}
@@ -408,37 +579,63 @@ func (m *GRPCBroker) NextId() uint32 {
// the plugin host/client.
func (m *GRPCBroker) Run() {
for {
stream, err := m.streamer.Recv()
msg, err := m.streamer.Recv()
if err != nil {
// Once we receive an error, just exit
break
}
// Initialize the waiter
p := m.getStream(stream.ServiceId)
var p *gRPCBrokerPending
if msg.Knock != nil && msg.Knock.Knock && !msg.Knock.Ack {
p = m.getServerStream(msg.ServiceId)
// The server side doesn't close the channel immediately as it needs
// to continuously listen for knocks.
} else {
p = m.getClientStream(msg.ServiceId)
go m.timeoutWait(msg.ServiceId, p)
}
select {
case p.ch <- stream:
case p.ch <- msg:
default:
}
go m.timeoutWait(stream.ServiceId, p)
}
}
func (m *GRPCBroker) getStream(id uint32) *gRPCBrokerPending {
// getClientStream is a buffer to receive new connection info and knock acks
// by stream ID.
func (m *GRPCBroker) getClientStream(id uint32) *gRPCBrokerPending {
m.Lock()
defer m.Unlock()
p, ok := m.streams[id]
p, ok := m.clientStreams[id]
if ok {
return p
}
m.streams[id] = &gRPCBrokerPending{
m.clientStreams[id] = &gRPCBrokerPending{
ch: make(chan *plugin.ConnInfo, 1),
doneCh: make(chan struct{}),
}
return m.streams[id]
return m.clientStreams[id]
}
// getServerStream is a buffer to receive knocks to a multiplexed stream ID
// that its side is listening on. Not used unless multiplexing is enabled.
func (m *GRPCBroker) getServerStream(id uint32) *gRPCBrokerPending {
m.Lock()
defer m.Unlock()
p, ok := m.serverStreams[id]
if ok {
return p
}
m.serverStreams[id] = &gRPCBrokerPending{
ch: make(chan *plugin.ConnInfo, 1),
doneCh: make(chan struct{}),
}
return m.serverStreams[id]
}
func (m *GRPCBroker) timeoutWait(id uint32, p *gRPCBrokerPending) {
@@ -453,5 +650,5 @@ func (m *GRPCBroker) timeoutWait(id uint32, p *gRPCBrokerPending) {
defer m.Unlock()
// Delete the stream so no one else can grab it
delete(m.streams, id)
delete(m.clientStreams, id)
}
+10 -2
View File
@@ -1,6 +1,10 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
"context"
"crypto/tls"
"fmt"
"math"
@@ -8,7 +12,6 @@ import (
"time"
"github.com/hashicorp/go-plugin/internal/plugin"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/health/grpc_health_v1"
@@ -58,9 +61,14 @@ func newGRPCClient(doneCtx context.Context, c *Client) (*GRPCClient, error) {
return nil, err
}
muxer, err := c.getGRPCMuxer(c.address)
if err != nil {
return nil, err
}
// Start the broker.
brokerGRPCClient := newGRPCBrokerClient(conn)
broker := newGRPCBroker(brokerGRPCClient, c.config.TLSConfig)
broker := newGRPCBroker(brokerGRPCClient, c.config.TLSConfig, c.unixSocketCfg, c.runner, muxer)
go broker.Run()
go brokerGRPCClient.StartStream()
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+21 -3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
@@ -9,6 +12,7 @@ import (
"net"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin/internal/grpcmux"
"github.com/hashicorp/go-plugin/internal/plugin"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@@ -58,6 +62,8 @@ type GRPCServer struct {
stdioServer *grpcStdioServer
logger hclog.Logger
muxer *grpcmux.GRPCServerMuxer
}
// ServerProtocol impl.
@@ -81,7 +87,7 @@ func (s *GRPCServer) Init() error {
// Register the broker service
brokerServer := newGRPCBrokerServer()
plugin.RegisterGRPCBrokerServer(s.server, brokerServer)
s.broker = newGRPCBroker(brokerServer, s.TLS)
s.broker = newGRPCBroker(brokerServer, s.TLS, unixSocketConfigFromEnv(), nil, s.muxer)
go s.broker.Run()
// Register the controller
@@ -107,14 +113,26 @@ func (s *GRPCServer) Init() error {
return nil
}
// Stop calls Stop on the underlying grpc.Server
// Stop calls Stop on the underlying grpc.Server and Close on the underlying
// grpc.Broker if present.
func (s *GRPCServer) Stop() {
s.server.Stop()
if s.broker != nil {
s.broker.Close()
s.broker = nil
}
}
// GracefulStop calls GracefulStop on the underlying grpc.Server
// GracefulStop calls GracefulStop on the underlying grpc.Server and Close on
// the underlying grpc.Broker if present.
func (s *GRPCServer) GracefulStop() {
s.server.GracefulStop()
if s.broker != nil {
s.broker.Close()
s.broker = nil
}
}
// Config is the GRPCServerConfig encoded as JSON then base64.
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
@@ -0,0 +1,16 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cmdrunner
// addrTranslator implements stateless identity functions, as the host and plugin
// run in the same context wrt Unix and network addresses.
type addrTranslator struct{}
func (*addrTranslator) PluginToHost(pluginNet, pluginAddr string) (string, string, error) {
return pluginNet, pluginAddr, nil
}
func (*addrTranslator) HostToPlugin(hostNet, hostAddr string) (string, string, error) {
return hostNet, hostAddr, nil
}
@@ -0,0 +1,63 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cmdrunner
import (
"context"
"fmt"
"net"
"os"
"github.com/hashicorp/go-plugin/runner"
)
// ReattachFunc returns a function that allows reattaching to a plugin running
// as a plain process. The process may or may not be a child process.
func ReattachFunc(pid int, addr net.Addr) runner.ReattachFunc {
return func() (runner.AttachedRunner, error) {
p, err := os.FindProcess(pid)
if err != nil {
// On Unix systems, FindProcess never returns an error.
// On Windows, for non-existent pids it returns:
// os.SyscallError - 'OpenProcess: the paremter is incorrect'
return nil, ErrProcessNotFound
}
// Attempt to connect to the addr since on Unix systems FindProcess
// doesn't actually return an error if it can't find the process.
conn, err := net.Dial(addr.Network(), addr.String())
if err != nil {
p.Kill()
return nil, ErrProcessNotFound
}
conn.Close()
return &CmdAttachedRunner{
pid: pid,
process: p,
}, nil
}
}
// CmdAttachedRunner is mostly a subset of CmdRunner, except the Wait function
// does not assume the process is a child of the host process, and so uses a
// different implementation to wait on the process.
type CmdAttachedRunner struct {
pid int
process *os.Process
addrTranslator
}
func (c *CmdAttachedRunner) Wait(_ context.Context) error {
return pidWait(c.pid)
}
func (c *CmdAttachedRunner) Kill(_ context.Context) error {
return c.process.Kill()
}
func (c *CmdAttachedRunner) ID() string {
return fmt.Sprintf("%d", c.pid)
}
+129
View File
@@ -0,0 +1,129 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cmdrunner
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin/runner"
)
var (
_ runner.Runner = (*CmdRunner)(nil)
// ErrProcessNotFound is returned when a client is instantiated to
// reattach to an existing process and it isn't found.
ErrProcessNotFound = errors.New("Reattachment process not found")
)
const unrecognizedRemotePluginMessage = `This usually means
the plugin was not compiled for this architecture,
the plugin is missing dynamic-link libraries necessary to run,
the plugin is not executable by this process due to file permissions, or
the plugin failed to negotiate the initial go-plugin protocol handshake
%s`
// CmdRunner implements the runner.Runner interface. It mostly just passes through
// to exec.Cmd methods.
type CmdRunner struct {
logger hclog.Logger
cmd *exec.Cmd
stdout io.ReadCloser
stderr io.ReadCloser
// Cmd info is persisted early, since the process information will be removed
// after Kill is called.
path string
pid int
addrTranslator
}
// NewCmdRunner returns an implementation of runner.Runner for running a plugin
// as a subprocess. It must be passed a cmd that hasn't yet been started.
func NewCmdRunner(logger hclog.Logger, cmd *exec.Cmd) (*CmdRunner, error) {
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
return &CmdRunner{
logger: logger,
cmd: cmd,
stdout: stdout,
stderr: stderr,
path: cmd.Path,
}, nil
}
func (c *CmdRunner) Start(_ context.Context) error {
c.logger.Debug("starting plugin", "path", c.cmd.Path, "args", c.cmd.Args)
err := c.cmd.Start()
if err != nil {
return err
}
c.pid = c.cmd.Process.Pid
c.logger.Debug("plugin started", "path", c.path, "pid", c.pid)
return nil
}
func (c *CmdRunner) Wait(_ context.Context) error {
return c.cmd.Wait()
}
func (c *CmdRunner) Kill(_ context.Context) error {
if c.cmd.Process != nil {
err := c.cmd.Process.Kill()
// Swallow ErrProcessDone, we support calling Kill multiple times.
if !errors.Is(err, os.ErrProcessDone) {
return err
}
return nil
}
return nil
}
func (c *CmdRunner) Stdout() io.ReadCloser {
return c.stdout
}
func (c *CmdRunner) Stderr() io.ReadCloser {
return c.stderr
}
func (c *CmdRunner) Name() string {
return c.path
}
func (c *CmdRunner) ID() string {
return fmt.Sprintf("%d", c.pid)
}
// peTypes is a list of Portable Executable (PE) machine types from https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
// mapped to GOARCH types. It is not comprehensive, and only includes machine types that Go supports.
var peTypes = map[uint16]string{
0x14c: "386",
0x1c0: "arm",
0x6264: "loong64",
0x8664: "amd64",
0xaa64: "arm64",
}
func (c *CmdRunner) Diagnose(_ context.Context) string {
return fmt.Sprintf(unrecognizedRemotePluginMessage, additionalNotesAboutCommand(c.cmd.Path))
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build !windows
// +build !windows
package cmdrunner
import (
"debug/elf"
"debug/macho"
"debug/pe"
"fmt"
"os"
"os/user"
"runtime"
"strconv"
"syscall"
)
// additionalNotesAboutCommand tries to get additional information about a command that might help diagnose
// why it won't run correctly. It runs as a best effort only.
func additionalNotesAboutCommand(path string) string {
notes := ""
stat, err := os.Stat(path)
if err != nil {
return notes
}
notes += "\nAdditional notes about plugin:\n"
notes += fmt.Sprintf(" Path: %s\n", path)
notes += fmt.Sprintf(" Mode: %s\n", stat.Mode())
statT, ok := stat.Sys().(*syscall.Stat_t)
if ok {
currentUsername := "?"
if u, err := user.LookupId(strconv.FormatUint(uint64(os.Getuid()), 10)); err == nil {
currentUsername = u.Username
}
currentGroup := "?"
if g, err := user.LookupGroupId(strconv.FormatUint(uint64(os.Getgid()), 10)); err == nil {
currentGroup = g.Name
}
username := "?"
if u, err := user.LookupId(strconv.FormatUint(uint64(statT.Uid), 10)); err == nil {
username = u.Username
}
group := "?"
if g, err := user.LookupGroupId(strconv.FormatUint(uint64(statT.Gid), 10)); err == nil {
group = g.Name
}
notes += fmt.Sprintf(" Owner: %d [%s] (current: %d [%s])\n", statT.Uid, username, os.Getuid(), currentUsername)
notes += fmt.Sprintf(" Group: %d [%s] (current: %d [%s])\n", statT.Gid, group, os.Getgid(), currentGroup)
}
if elfFile, err := elf.Open(path); err == nil {
defer elfFile.Close()
notes += fmt.Sprintf(" ELF architecture: %s (current architecture: %s)\n", elfFile.Machine, runtime.GOARCH)
} else if machoFile, err := macho.Open(path); err == nil {
defer machoFile.Close()
notes += fmt.Sprintf(" MachO architecture: %s (current architecture: %s)\n", machoFile.Cpu, runtime.GOARCH)
} else if peFile, err := pe.Open(path); err == nil {
defer peFile.Close()
machine, ok := peTypes[peFile.Machine]
if !ok {
machine = "unknown"
}
notes += fmt.Sprintf(" PE architecture: %s (current architecture: %s)\n", machine, runtime.GOARCH)
}
return notes
}
@@ -0,0 +1,46 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build windows
// +build windows
package cmdrunner
import (
"debug/elf"
"debug/macho"
"debug/pe"
"fmt"
"os"
"runtime"
)
// additionalNotesAboutCommand tries to get additional information about a command that might help diagnose
// why it won't run correctly. It runs as a best effort only.
func additionalNotesAboutCommand(path string) string {
notes := ""
stat, err := os.Stat(path)
if err != nil {
return notes
}
notes += "\nAdditional notes about plugin:\n"
notes += fmt.Sprintf(" Path: %s\n", path)
notes += fmt.Sprintf(" Mode: %s\n", stat.Mode())
if elfFile, err := elf.Open(path); err == nil {
defer elfFile.Close()
notes += fmt.Sprintf(" ELF architecture: %s (current architecture: %s)\n", elfFile.Machine, runtime.GOARCH)
} else if machoFile, err := macho.Open(path); err == nil {
defer machoFile.Close()
notes += fmt.Sprintf(" MachO architecture: %s (current architecture: %s)\n", machoFile.Cpu, runtime.GOARCH)
} else if peFile, err := pe.Open(path); err == nil {
defer peFile.Close()
machine, ok := peTypes[peFile.Machine]
if !ok {
machine = "unknown"
}
notes += fmt.Sprintf(" PE architecture: %s (current architecture: %s)\n", machine, runtime.GOARCH)
}
return notes
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cmdrunner
import "time"
// pidAlive checks whether a pid is alive.
func pidAlive(pid int) bool {
return _pidAlive(pid)
}
// pidWait blocks for a process to exit.
func pidWait(pid int) error {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
if !pidAlive(pid) {
break
}
}
return nil
}
@@ -1,7 +1,10 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build !windows
// +build !windows
package plugin
package cmdrunner
import (
"os"
@@ -1,4 +1,7 @@
package plugin
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cmdrunner
import (
"syscall"
@@ -0,0 +1,51 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package grpcmux
import (
"io"
"net"
"github.com/hashicorp/yamux"
)
var _ net.Listener = (*blockedClientListener)(nil)
// blockedClientListener accepts connections for a specific gRPC broker stream
// ID on the client (host) side of the connection.
type blockedClientListener struct {
session *yamux.Session
waitCh chan struct{}
doneCh <-chan struct{}
}
func newBlockedClientListener(session *yamux.Session, doneCh <-chan struct{}) *blockedClientListener {
return &blockedClientListener{
waitCh: make(chan struct{}, 1),
doneCh: doneCh,
session: session,
}
}
func (b *blockedClientListener) Accept() (net.Conn, error) {
select {
case <-b.waitCh:
return b.session.Accept()
case <-b.doneCh:
return nil, io.EOF
}
}
func (b *blockedClientListener) Addr() net.Addr {
return b.session.Addr()
}
func (b *blockedClientListener) Close() error {
// We don't close the session, the client muxer is responsible for that.
return nil
}
func (b *blockedClientListener) unblock() {
b.waitCh <- struct{}{}
}
@@ -0,0 +1,49 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package grpcmux
import (
"io"
"net"
)
var _ net.Listener = (*blockedServerListener)(nil)
// blockedServerListener accepts connections for a specific gRPC broker stream
// ID on the server (plugin) side of the connection.
type blockedServerListener struct {
addr net.Addr
acceptCh chan acceptResult
doneCh <-chan struct{}
}
type acceptResult struct {
conn net.Conn
err error
}
func newBlockedServerListener(addr net.Addr, doneCh <-chan struct{}) *blockedServerListener {
return &blockedServerListener{
addr: addr,
acceptCh: make(chan acceptResult),
doneCh: doneCh,
}
}
func (b *blockedServerListener) Accept() (net.Conn, error) {
select {
case accept := <-b.acceptCh:
return accept.conn, accept.err
case <-b.doneCh:
return nil, io.EOF
}
}
func (b *blockedServerListener) Addr() net.Addr {
return b.addr
}
func (b *blockedServerListener) Close() error {
return nil
}
@@ -0,0 +1,105 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package grpcmux
import (
"fmt"
"net"
"sync"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/yamux"
)
var _ GRPCMuxer = (*GRPCClientMuxer)(nil)
// GRPCClientMuxer implements the client (host) side of the gRPC broker's
// GRPCMuxer interface for multiplexing multiple gRPC broker connections over
// a single net.Conn.
//
// The client dials the initial net.Conn eagerly, and creates a yamux.Session
// as the implementation for multiplexing any additional connections.
//
// Each net.Listener returned from Listener will block until the client receives
// a knock that matches its gRPC broker stream ID. There is no default listener
// on the client, as it is a client for the gRPC broker's control services. (See
// GRPCServerMuxer for more details).
type GRPCClientMuxer struct {
logger hclog.Logger
session *yamux.Session
acceptMutex sync.Mutex
acceptListeners map[uint32]*blockedClientListener
}
func NewGRPCClientMuxer(logger hclog.Logger, addr net.Addr) (*GRPCClientMuxer, error) {
// Eagerly establish the underlying connection as early as possible.
logger.Debug("making new client mux initial connection", "addr", addr)
conn, err := net.Dial(addr.Network(), addr.String())
if err != nil {
return nil, err
}
if tcpConn, ok := conn.(*net.TCPConn); ok {
// Make sure to set keep alive so that the connection doesn't die
_ = tcpConn.SetKeepAlive(true)
}
cfg := yamux.DefaultConfig()
cfg.Logger = logger.Named("yamux").StandardLogger(&hclog.StandardLoggerOptions{
InferLevels: true,
})
cfg.LogOutput = nil
sess, err := yamux.Client(conn, cfg)
if err != nil {
return nil, err
}
logger.Debug("client muxer connected", "addr", addr)
m := &GRPCClientMuxer{
logger: logger,
session: sess,
acceptListeners: make(map[uint32]*blockedClientListener),
}
return m, nil
}
func (m *GRPCClientMuxer) Enabled() bool {
return m != nil
}
func (m *GRPCClientMuxer) Listener(id uint32, doneCh <-chan struct{}) (net.Listener, error) {
ln := newBlockedClientListener(m.session, doneCh)
m.acceptMutex.Lock()
m.acceptListeners[id] = ln
m.acceptMutex.Unlock()
return ln, nil
}
func (m *GRPCClientMuxer) AcceptKnock(id uint32) error {
m.acceptMutex.Lock()
defer m.acceptMutex.Unlock()
ln, ok := m.acceptListeners[id]
if !ok {
return fmt.Errorf("no listener for id %d", id)
}
ln.unblock()
return nil
}
func (m *GRPCClientMuxer) Dial() (net.Conn, error) {
stream, err := m.session.Open()
if err != nil {
return nil, fmt.Errorf("error dialling new client stream: %w", err)
}
return stream, nil
}
func (m *GRPCClientMuxer) Close() error {
return m.session.Close()
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package grpcmux
import (
"net"
)
// GRPCMuxer enables multiple implementations of net.Listener to accept
// connections over a single "main" multiplexed net.Conn, and dial multiple
// client connections over the same multiplexed net.Conn.
//
// The first multiplexed connection is used to serve the gRPC broker's own
// control services: plugin.GRPCBroker, plugin.GRPCController, plugin.GRPCStdio.
//
// Clients must "knock" before dialling, to tell the server side that the
// next net.Conn should be accepted onto a specific stream ID. The knock is a
// bidirectional streaming message on the plugin.GRPCBroker service.
type GRPCMuxer interface {
// Enabled determines whether multiplexing should be used. It saves users
// of the interface from having to compare an interface with nil, which
// is a bit awkward to do correctly.
Enabled() bool
// Listener returns a multiplexed listener that will wait until AcceptKnock
// is called with a matching ID before its Accept function returns.
Listener(id uint32, doneCh <-chan struct{}) (net.Listener, error)
// AcceptKnock unblocks the listener with the matching ID, and returns an
// error if it hasn't been created yet.
AcceptKnock(id uint32) error
// Dial makes a new multiplexed client connection. To dial a specific ID,
// a knock must be sent first.
Dial() (net.Conn, error)
// Close closes connections and releases any resources associated with the
// muxer.
Close() error
}
@@ -0,0 +1,190 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package grpcmux
import (
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/yamux"
)
var _ GRPCMuxer = (*GRPCServerMuxer)(nil)
var _ net.Listener = (*GRPCServerMuxer)(nil)
// GRPCServerMuxer implements the server (plugin) side of the gRPC broker's
// GRPCMuxer interface for multiplexing multiple gRPC broker connections over
// a single net.Conn.
//
// The server side needs a listener to serve the gRPC broker's control services,
// which includes the service we will receive knocks on. That means we always
// accept the first connection onto a "default" main listener, and if we accept
// any further connections without receiving a knock first, they are also given
// to the default listener.
//
// When creating additional multiplexed listeners for specific stream IDs, we
// can't control the order in which gRPC servers will call Accept() on each
// listener, but we do need to control which gRPC server accepts which connection.
// As such, each multiplexed listener blocks waiting on a channel. It will be
// unblocked when a knock is received for the matching stream ID.
type GRPCServerMuxer struct {
addr net.Addr
logger hclog.Logger
sessionErrCh chan error
sess *yamux.Session
knockCh chan uint32
acceptMutex sync.Mutex
acceptChannels map[uint32]chan acceptResult
}
func NewGRPCServerMuxer(logger hclog.Logger, ln net.Listener) *GRPCServerMuxer {
m := &GRPCServerMuxer{
addr: ln.Addr(),
logger: logger,
sessionErrCh: make(chan error),
knockCh: make(chan uint32, 1),
acceptChannels: make(map[uint32]chan acceptResult),
}
go m.acceptSession(ln)
return m
}
// acceptSessionAndMuxAccept is responsible for establishing the yamux session,
// and then kicking off the acceptLoop function.
func (m *GRPCServerMuxer) acceptSession(ln net.Listener) {
defer close(m.sessionErrCh)
m.logger.Debug("accepting initial connection", "addr", m.addr)
conn, err := ln.Accept()
if err != nil {
m.sessionErrCh <- err
return
}
m.logger.Debug("initial server connection accepted", "addr", m.addr)
cfg := yamux.DefaultConfig()
cfg.Logger = m.logger.Named("yamux").StandardLogger(&hclog.StandardLoggerOptions{
InferLevels: true,
})
cfg.LogOutput = nil
m.sess, err = yamux.Server(conn, cfg)
if err != nil {
m.sessionErrCh <- err
return
}
}
func (m *GRPCServerMuxer) session() (*yamux.Session, error) {
select {
case err := <-m.sessionErrCh:
if err != nil {
return nil, err
}
case <-time.After(5 * time.Second):
return nil, errors.New("timed out waiting for connection to be established")
}
// Should never happen.
if m.sess == nil {
return nil, errors.New("no connection established and no error received")
}
return m.sess, nil
}
// Accept accepts all incoming connections and routes them to the correct
// stream ID based on the most recent knock received.
func (m *GRPCServerMuxer) Accept() (net.Conn, error) {
session, err := m.session()
if err != nil {
return nil, fmt.Errorf("error establishing yamux session: %w", err)
}
for {
conn, acceptErr := session.Accept()
select {
case id := <-m.knockCh:
m.acceptMutex.Lock()
acceptCh, ok := m.acceptChannels[id]
m.acceptMutex.Unlock()
if !ok {
if conn != nil {
_ = conn.Close()
}
return nil, fmt.Errorf("received knock on ID %d that doesn't have a listener", id)
}
m.logger.Debug("sending conn to brokered listener", "id", id)
acceptCh <- acceptResult{
conn: conn,
err: acceptErr,
}
default:
m.logger.Debug("sending conn to default listener")
return conn, acceptErr
}
}
}
func (m *GRPCServerMuxer) Addr() net.Addr {
return m.addr
}
func (m *GRPCServerMuxer) Close() error {
session, err := m.session()
if err != nil {
return err
}
return session.Close()
}
func (m *GRPCServerMuxer) Enabled() bool {
return m != nil
}
func (m *GRPCServerMuxer) Listener(id uint32, doneCh <-chan struct{}) (net.Listener, error) {
sess, err := m.session()
if err != nil {
return nil, err
}
ln := newBlockedServerListener(sess.Addr(), doneCh)
m.acceptMutex.Lock()
m.acceptChannels[id] = ln.acceptCh
m.acceptMutex.Unlock()
return ln, nil
}
func (m *GRPCServerMuxer) Dial() (net.Conn, error) {
sess, err := m.session()
if err != nil {
return nil, err
}
stream, err := sess.OpenStream()
if err != nil {
return nil, fmt.Errorf("error dialling new server stream: %w", err)
}
return stream, nil
}
func (m *GRPCServerMuxer) AcceptKnock(id uint32) error {
m.knockCh <- id
return nil
}
-3
View File
@@ -1,3 +0,0 @@
//go:generate protoc -I ./ ./grpc_broker.proto ./grpc_controller.proto ./grpc_stdio.proto --go_out=plugins=grpc:.
package plugin
+222 -161
View File
@@ -1,203 +1,264 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: grpc_broker.proto
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: internal/plugin/grpc_broker.proto
package plugin
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ConnInfo struct {
ServiceId uint32 `protobuf:"varint,1,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"`
Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ServiceId uint32 `protobuf:"varint,1,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"`
Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
Knock *ConnInfo_Knock `protobuf:"bytes,4,opt,name=knock,proto3" json:"knock,omitempty"`
}
func (m *ConnInfo) Reset() { *m = ConnInfo{} }
func (m *ConnInfo) String() string { return proto.CompactTextString(m) }
func (*ConnInfo) ProtoMessage() {}
func (x *ConnInfo) Reset() {
*x = ConnInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_plugin_grpc_broker_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConnInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConnInfo) ProtoMessage() {}
func (x *ConnInfo) ProtoReflect() protoreflect.Message {
mi := &file_internal_plugin_grpc_broker_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConnInfo.ProtoReflect.Descriptor instead.
func (*ConnInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_grpc_broker_3322b07398605250, []int{0}
}
func (m *ConnInfo) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConnInfo.Unmarshal(m, b)
}
func (m *ConnInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ConnInfo.Marshal(b, m, deterministic)
}
func (dst *ConnInfo) XXX_Merge(src proto.Message) {
xxx_messageInfo_ConnInfo.Merge(dst, src)
}
func (m *ConnInfo) XXX_Size() int {
return xxx_messageInfo_ConnInfo.Size(m)
}
func (m *ConnInfo) XXX_DiscardUnknown() {
xxx_messageInfo_ConnInfo.DiscardUnknown(m)
return file_internal_plugin_grpc_broker_proto_rawDescGZIP(), []int{0}
}
var xxx_messageInfo_ConnInfo proto.InternalMessageInfo
func (m *ConnInfo) GetServiceId() uint32 {
if m != nil {
return m.ServiceId
func (x *ConnInfo) GetServiceId() uint32 {
if x != nil {
return x.ServiceId
}
return 0
}
func (m *ConnInfo) GetNetwork() string {
if m != nil {
return m.Network
func (x *ConnInfo) GetNetwork() string {
if x != nil {
return x.Network
}
return ""
}
func (m *ConnInfo) GetAddress() string {
if m != nil {
return m.Address
func (x *ConnInfo) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
func init() {
proto.RegisterType((*ConnInfo)(nil), "plugin.ConnInfo")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// GRPCBrokerClient is the client API for GRPCBroker service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GRPCBrokerClient interface {
StartStream(ctx context.Context, opts ...grpc.CallOption) (GRPCBroker_StartStreamClient, error)
}
type gRPCBrokerClient struct {
cc *grpc.ClientConn
}
func NewGRPCBrokerClient(cc *grpc.ClientConn) GRPCBrokerClient {
return &gRPCBrokerClient{cc}
}
func (c *gRPCBrokerClient) StartStream(ctx context.Context, opts ...grpc.CallOption) (GRPCBroker_StartStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &_GRPCBroker_serviceDesc.Streams[0], "/plugin.GRPCBroker/StartStream", opts...)
if err != nil {
return nil, err
func (x *ConnInfo) GetKnock() *ConnInfo_Knock {
if x != nil {
return x.Knock
}
x := &gRPCBrokerStartStreamClient{stream}
return x, nil
return nil
}
type GRPCBroker_StartStreamClient interface {
Send(*ConnInfo) error
Recv() (*ConnInfo, error)
grpc.ClientStream
type ConnInfo_Knock struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Knock bool `protobuf:"varint,1,opt,name=knock,proto3" json:"knock,omitempty"`
Ack bool `protobuf:"varint,2,opt,name=ack,proto3" json:"ack,omitempty"`
Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
}
type gRPCBrokerStartStreamClient struct {
grpc.ClientStream
}
func (x *gRPCBrokerStartStreamClient) Send(m *ConnInfo) error {
return x.ClientStream.SendMsg(m)
}
func (x *gRPCBrokerStartStreamClient) Recv() (*ConnInfo, error) {
m := new(ConnInfo)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
func (x *ConnInfo_Knock) Reset() {
*x = ConnInfo_Knock{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_plugin_grpc_broker_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
return m, nil
}
// GRPCBrokerServer is the server API for GRPCBroker service.
type GRPCBrokerServer interface {
StartStream(GRPCBroker_StartStreamServer) error
func (x *ConnInfo_Knock) String() string {
return protoimpl.X.MessageStringOf(x)
}
func RegisterGRPCBrokerServer(s *grpc.Server, srv GRPCBrokerServer) {
s.RegisterService(&_GRPCBroker_serviceDesc, srv)
}
func (*ConnInfo_Knock) ProtoMessage() {}
func _GRPCBroker_StartStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GRPCBrokerServer).StartStream(&gRPCBrokerStartStreamServer{stream})
}
type GRPCBroker_StartStreamServer interface {
Send(*ConnInfo) error
Recv() (*ConnInfo, error)
grpc.ServerStream
}
type gRPCBrokerStartStreamServer struct {
grpc.ServerStream
}
func (x *gRPCBrokerStartStreamServer) Send(m *ConnInfo) error {
return x.ServerStream.SendMsg(m)
}
func (x *gRPCBrokerStartStreamServer) Recv() (*ConnInfo, error) {
m := new(ConnInfo)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
func (x *ConnInfo_Knock) ProtoReflect() protoreflect.Message {
mi := &file_internal_plugin_grpc_broker_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return m, nil
return mi.MessageOf(x)
}
var _GRPCBroker_serviceDesc = grpc.ServiceDesc{
ServiceName: "plugin.GRPCBroker",
HandlerType: (*GRPCBrokerServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "StartStream",
Handler: _GRPCBroker_StartStream_Handler,
ServerStreams: true,
ClientStreams: true,
// Deprecated: Use ConnInfo_Knock.ProtoReflect.Descriptor instead.
func (*ConnInfo_Knock) Descriptor() ([]byte, []int) {
return file_internal_plugin_grpc_broker_proto_rawDescGZIP(), []int{0, 0}
}
func (x *ConnInfo_Knock) GetKnock() bool {
if x != nil {
return x.Knock
}
return false
}
func (x *ConnInfo_Knock) GetAck() bool {
if x != nil {
return x.Ack
}
return false
}
func (x *ConnInfo_Knock) GetError() string {
if x != nil {
return x.Error
}
return ""
}
var File_internal_plugin_grpc_broker_proto protoreflect.FileDescriptor
var file_internal_plugin_grpc_broker_proto_rawDesc = []byte{
0x0a, 0x21, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69,
0x6e, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x22, 0xd2, 0x01, 0x0a, 0x08,
0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f,
0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
0x6b, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x05, 0x6b,
0x6e, 0x6f, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x6c, 0x75,
0x67, 0x69, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4b, 0x6e, 0x6f,
0x63, 0x6b, 0x52, 0x05, 0x6b, 0x6e, 0x6f, 0x63, 0x6b, 0x1a, 0x45, 0x0a, 0x05, 0x4b, 0x6e, 0x6f,
0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6b, 0x6e, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x05, 0x6b, 0x6e, 0x6f, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, 0x6b, 0x18,
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
0x32, 0x43, 0x0a, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x42, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x12, 0x35,
0x0a, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x10, 0x2e,
0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x1a,
0x10, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x6e, 0x66,
0x6f, 0x28, 0x01, 0x30, 0x01, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69,
0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_internal_plugin_grpc_broker_proto_rawDescOnce sync.Once
file_internal_plugin_grpc_broker_proto_rawDescData = file_internal_plugin_grpc_broker_proto_rawDesc
)
func file_internal_plugin_grpc_broker_proto_rawDescGZIP() []byte {
file_internal_plugin_grpc_broker_proto_rawDescOnce.Do(func() {
file_internal_plugin_grpc_broker_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_plugin_grpc_broker_proto_rawDescData)
})
return file_internal_plugin_grpc_broker_proto_rawDescData
}
var file_internal_plugin_grpc_broker_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_internal_plugin_grpc_broker_proto_goTypes = []interface{}{
(*ConnInfo)(nil), // 0: plugin.ConnInfo
(*ConnInfo_Knock)(nil), // 1: plugin.ConnInfo.Knock
}
var file_internal_plugin_grpc_broker_proto_depIdxs = []int32{
1, // 0: plugin.ConnInfo.knock:type_name -> plugin.ConnInfo.Knock
0, // 1: plugin.GRPCBroker.StartStream:input_type -> plugin.ConnInfo
0, // 2: plugin.GRPCBroker.StartStream:output_type -> plugin.ConnInfo
2, // [2:3] is the sub-list for method output_type
1, // [1:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_internal_plugin_grpc_broker_proto_init() }
func file_internal_plugin_grpc_broker_proto_init() {
if File_internal_plugin_grpc_broker_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_internal_plugin_grpc_broker_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConnInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_internal_plugin_grpc_broker_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConnInfo_Knock); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_internal_plugin_grpc_broker_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
},
Metadata: "grpc_broker.proto",
}
func init() { proto.RegisterFile("grpc_broker.proto", fileDescriptor_grpc_broker_3322b07398605250) }
var fileDescriptor_grpc_broker_3322b07398605250 = []byte{
// 175 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2f, 0x2a, 0x48,
0x8e, 0x4f, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b,
0xc8, 0x29, 0x4d, 0xcf, 0xcc, 0x53, 0x8a, 0xe5, 0xe2, 0x70, 0xce, 0xcf, 0xcb, 0xf3, 0xcc, 0x4b,
0xcb, 0x17, 0x92, 0xe5, 0xe2, 0x2a, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x8d, 0xcf, 0x4c, 0x91,
0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0xe2, 0x84, 0x8a, 0x78, 0xa6, 0x08, 0x49, 0x70, 0xb1, 0xe7,
0xa5, 0x96, 0x94, 0xe7, 0x17, 0x65, 0x4b, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xb8, 0x20,
0x99, 0xc4, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0x62, 0x09, 0x66, 0x88, 0x0c, 0x94, 0x6b, 0xe4, 0xcc,
0xc5, 0xe5, 0x1e, 0x14, 0xe0, 0xec, 0x04, 0xb6, 0x5a, 0xc8, 0x94, 0x8b, 0x3b, 0xb8, 0x24, 0xb1,
0xa8, 0x24, 0xb8, 0xa4, 0x28, 0x35, 0x31, 0x57, 0x48, 0x40, 0x0f, 0xe2, 0x08, 0x3d, 0x98, 0x0b,
0xa4, 0x30, 0x44, 0x34, 0x18, 0x0d, 0x18, 0x9d, 0x38, 0xa2, 0xa0, 0xae, 0x4d, 0x62, 0x03, 0x3b,
0xde, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x10, 0x15, 0x39, 0x47, 0xd1, 0x00, 0x00, 0x00,
GoTypes: file_internal_plugin_grpc_broker_proto_goTypes,
DependencyIndexes: file_internal_plugin_grpc_broker_proto_depIdxs,
MessageInfos: file_internal_plugin_grpc_broker_proto_msgTypes,
}.Build()
File_internal_plugin_grpc_broker_proto = out.File
file_internal_plugin_grpc_broker_proto_rawDesc = nil
file_internal_plugin_grpc_broker_proto_goTypes = nil
file_internal_plugin_grpc_broker_proto_depIdxs = nil
}
+10 -1
View File
@@ -1,11 +1,20 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
syntax = "proto3";
package plugin;
option go_package = "plugin";
option go_package = "./plugin";
message ConnInfo {
uint32 service_id = 1;
string network = 2;
string address = 3;
message Knock {
bool knock = 1;
bool ack = 2;
string error = 3;
}
Knock knock = 4;
}
service GRPCBroker {
@@ -0,0 +1,142 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: internal/plugin/grpc_broker.proto
package plugin
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
GRPCBroker_StartStream_FullMethodName = "/plugin.GRPCBroker/StartStream"
)
// GRPCBrokerClient is the client API for GRPCBroker service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GRPCBrokerClient interface {
StartStream(ctx context.Context, opts ...grpc.CallOption) (GRPCBroker_StartStreamClient, error)
}
type gRPCBrokerClient struct {
cc grpc.ClientConnInterface
}
func NewGRPCBrokerClient(cc grpc.ClientConnInterface) GRPCBrokerClient {
return &gRPCBrokerClient{cc}
}
func (c *gRPCBrokerClient) StartStream(ctx context.Context, opts ...grpc.CallOption) (GRPCBroker_StartStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &GRPCBroker_ServiceDesc.Streams[0], GRPCBroker_StartStream_FullMethodName, opts...)
if err != nil {
return nil, err
}
x := &gRPCBrokerStartStreamClient{stream}
return x, nil
}
type GRPCBroker_StartStreamClient interface {
Send(*ConnInfo) error
Recv() (*ConnInfo, error)
grpc.ClientStream
}
type gRPCBrokerStartStreamClient struct {
grpc.ClientStream
}
func (x *gRPCBrokerStartStreamClient) Send(m *ConnInfo) error {
return x.ClientStream.SendMsg(m)
}
func (x *gRPCBrokerStartStreamClient) Recv() (*ConnInfo, error) {
m := new(ConnInfo)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GRPCBrokerServer is the server API for GRPCBroker service.
// All implementations should embed UnimplementedGRPCBrokerServer
// for forward compatibility
type GRPCBrokerServer interface {
StartStream(GRPCBroker_StartStreamServer) error
}
// UnimplementedGRPCBrokerServer should be embedded to have forward compatible implementations.
type UnimplementedGRPCBrokerServer struct {
}
func (UnimplementedGRPCBrokerServer) StartStream(GRPCBroker_StartStreamServer) error {
return status.Errorf(codes.Unimplemented, "method StartStream not implemented")
}
// UnsafeGRPCBrokerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GRPCBrokerServer will
// result in compilation errors.
type UnsafeGRPCBrokerServer interface {
mustEmbedUnimplementedGRPCBrokerServer()
}
func RegisterGRPCBrokerServer(s grpc.ServiceRegistrar, srv GRPCBrokerServer) {
s.RegisterService(&GRPCBroker_ServiceDesc, srv)
}
func _GRPCBroker_StartStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GRPCBrokerServer).StartStream(&gRPCBrokerStartStreamServer{stream})
}
type GRPCBroker_StartStreamServer interface {
Send(*ConnInfo) error
Recv() (*ConnInfo, error)
grpc.ServerStream
}
type gRPCBrokerStartStreamServer struct {
grpc.ServerStream
}
func (x *gRPCBrokerStartStreamServer) Send(m *ConnInfo) error {
return x.ServerStream.SendMsg(m)
}
func (x *gRPCBrokerStartStreamServer) Recv() (*ConnInfo, error) {
m := new(ConnInfo)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GRPCBroker_ServiceDesc is the grpc.ServiceDesc for GRPCBroker service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var GRPCBroker_ServiceDesc = grpc.ServiceDesc{
ServiceName: "plugin.GRPCBroker",
HandlerType: (*GRPCBrokerServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "StartStream",
Handler: _GRPCBroker_StartStream_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "internal/plugin/grpc_broker.proto",
}
+115 -119
View File
@@ -1,145 +1,141 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: grpc_controller.proto
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: internal/plugin/grpc_controller.proto
package plugin
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Empty struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (m *Empty) Reset() { *m = Empty{} }
func (m *Empty) String() string { return proto.CompactTextString(m) }
func (*Empty) ProtoMessage() {}
func (x *Empty) Reset() {
*x = Empty{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_plugin_grpc_controller_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Empty) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Empty) ProtoMessage() {}
func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_internal_plugin_grpc_controller_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
func (*Empty) Descriptor() ([]byte, []int) {
return fileDescriptor_grpc_controller_08f8296ef6d80436, []int{0}
}
func (m *Empty) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Empty.Unmarshal(m, b)
}
func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Empty.Marshal(b, m, deterministic)
}
func (dst *Empty) XXX_Merge(src proto.Message) {
xxx_messageInfo_Empty.Merge(dst, src)
}
func (m *Empty) XXX_Size() int {
return xxx_messageInfo_Empty.Size(m)
}
func (m *Empty) XXX_DiscardUnknown() {
xxx_messageInfo_Empty.DiscardUnknown(m)
return file_internal_plugin_grpc_controller_proto_rawDescGZIP(), []int{0}
}
var xxx_messageInfo_Empty proto.InternalMessageInfo
var File_internal_plugin_grpc_controller_proto protoreflect.FileDescriptor
func init() {
proto.RegisterType((*Empty)(nil), "plugin.Empty")
var file_internal_plugin_grpc_controller_proto_rawDesc = []byte{
0x0a, 0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69,
0x6e, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x22,
0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x3a, 0x0a, 0x0e, 0x47, 0x52, 0x50, 0x43,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x08, 0x53, 0x68,
0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x0d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
var (
file_internal_plugin_grpc_controller_proto_rawDescOnce sync.Once
file_internal_plugin_grpc_controller_proto_rawDescData = file_internal_plugin_grpc_controller_proto_rawDesc
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// GRPCControllerClient is the client API for GRPCController service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GRPCControllerClient interface {
Shutdown(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
func file_internal_plugin_grpc_controller_proto_rawDescGZIP() []byte {
file_internal_plugin_grpc_controller_proto_rawDescOnce.Do(func() {
file_internal_plugin_grpc_controller_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_plugin_grpc_controller_proto_rawDescData)
})
return file_internal_plugin_grpc_controller_proto_rawDescData
}
type gRPCControllerClient struct {
cc *grpc.ClientConn
var file_internal_plugin_grpc_controller_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_internal_plugin_grpc_controller_proto_goTypes = []interface{}{
(*Empty)(nil), // 0: plugin.Empty
}
var file_internal_plugin_grpc_controller_proto_depIdxs = []int32{
0, // 0: plugin.GRPCController.Shutdown:input_type -> plugin.Empty
0, // 1: plugin.GRPCController.Shutdown:output_type -> plugin.Empty
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func NewGRPCControllerClient(cc *grpc.ClientConn) GRPCControllerClient {
return &gRPCControllerClient{cc}
}
func (c *gRPCControllerClient) Shutdown(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty)
err := c.cc.Invoke(ctx, "/plugin.GRPCController/Shutdown", in, out, opts...)
if err != nil {
return nil, err
func init() { file_internal_plugin_grpc_controller_proto_init() }
func file_internal_plugin_grpc_controller_proto_init() {
if File_internal_plugin_grpc_controller_proto != nil {
return
}
return out, nil
}
// GRPCControllerServer is the server API for GRPCController service.
type GRPCControllerServer interface {
Shutdown(context.Context, *Empty) (*Empty, error)
}
func RegisterGRPCControllerServer(s *grpc.Server, srv GRPCControllerServer) {
s.RegisterService(&_GRPCController_serviceDesc, srv)
}
func _GRPCController_Shutdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Empty)
if err := dec(in); err != nil {
return nil, err
if !protoimpl.UnsafeEnabled {
file_internal_plugin_grpc_controller_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Empty); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
if interceptor == nil {
return srv.(GRPCControllerServer).Shutdown(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/plugin.GRPCController/Shutdown",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GRPCControllerServer).Shutdown(ctx, req.(*Empty))
}
return interceptor(ctx, in, info, handler)
}
var _GRPCController_serviceDesc = grpc.ServiceDesc{
ServiceName: "plugin.GRPCController",
HandlerType: (*GRPCControllerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Shutdown",
Handler: _GRPCController_Shutdown_Handler,
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_internal_plugin_grpc_controller_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 1,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "grpc_controller.proto",
}
func init() {
proto.RegisterFile("grpc_controller.proto", fileDescriptor_grpc_controller_08f8296ef6d80436)
}
var fileDescriptor_grpc_controller_08f8296ef6d80436 = []byte{
// 108 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4d, 0x2f, 0x2a, 0x48,
0x8e, 0x4f, 0xce, 0xcf, 0x2b, 0x29, 0xca, 0xcf, 0xc9, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f,
0xc9, 0x17, 0x62, 0x2b, 0xc8, 0x29, 0x4d, 0xcf, 0xcc, 0x53, 0x62, 0xe7, 0x62, 0x75, 0xcd, 0x2d,
0x28, 0xa9, 0x34, 0xb2, 0xe2, 0xe2, 0x73, 0x0f, 0x0a, 0x70, 0x76, 0x86, 0x2b, 0x14, 0xd2, 0xe0,
0xe2, 0x08, 0xce, 0x28, 0x2d, 0x49, 0xc9, 0x2f, 0xcf, 0x13, 0xe2, 0xd5, 0x83, 0xa8, 0xd7, 0x03,
0x2b, 0x96, 0x42, 0xe5, 0x3a, 0x71, 0x44, 0x41, 0x8d, 0x4b, 0x62, 0x03, 0x9b, 0x6e, 0x0c, 0x08,
0x00, 0x00, 0xff, 0xff, 0xab, 0x7c, 0x27, 0xe5, 0x76, 0x00, 0x00, 0x00,
GoTypes: file_internal_plugin_grpc_controller_proto_goTypes,
DependencyIndexes: file_internal_plugin_grpc_controller_proto_depIdxs,
MessageInfos: file_internal_plugin_grpc_controller_proto_msgTypes,
}.Build()
File_internal_plugin_grpc_controller_proto = out.File
file_internal_plugin_grpc_controller_proto_rawDesc = nil
file_internal_plugin_grpc_controller_proto_goTypes = nil
file_internal_plugin_grpc_controller_proto_depIdxs = nil
}
@@ -1,6 +1,9 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
syntax = "proto3";
package plugin;
option go_package = "plugin";
option go_package = "./plugin";
message Empty {
}
@@ -0,0 +1,110 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: internal/plugin/grpc_controller.proto
package plugin
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
GRPCController_Shutdown_FullMethodName = "/plugin.GRPCController/Shutdown"
)
// GRPCControllerClient is the client API for GRPCController service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GRPCControllerClient interface {
Shutdown(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
}
type gRPCControllerClient struct {
cc grpc.ClientConnInterface
}
func NewGRPCControllerClient(cc grpc.ClientConnInterface) GRPCControllerClient {
return &gRPCControllerClient{cc}
}
func (c *gRPCControllerClient) Shutdown(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty)
err := c.cc.Invoke(ctx, GRPCController_Shutdown_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GRPCControllerServer is the server API for GRPCController service.
// All implementations should embed UnimplementedGRPCControllerServer
// for forward compatibility
type GRPCControllerServer interface {
Shutdown(context.Context, *Empty) (*Empty, error)
}
// UnimplementedGRPCControllerServer should be embedded to have forward compatible implementations.
type UnimplementedGRPCControllerServer struct {
}
func (UnimplementedGRPCControllerServer) Shutdown(context.Context, *Empty) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Shutdown not implemented")
}
// UnsafeGRPCControllerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GRPCControllerServer will
// result in compilation errors.
type UnsafeGRPCControllerServer interface {
mustEmbedUnimplementedGRPCControllerServer()
}
func RegisterGRPCControllerServer(s grpc.ServiceRegistrar, srv GRPCControllerServer) {
s.RegisterService(&GRPCController_ServiceDesc, srv)
}
func _GRPCController_Shutdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GRPCControllerServer).Shutdown(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: GRPCController_Shutdown_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GRPCControllerServer).Shutdown(ctx, req.(*Empty))
}
return interceptor(ctx, in, info, handler)
}
// GRPCController_ServiceDesc is the grpc.ServiceDesc for GRPCController service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var GRPCController_ServiceDesc = grpc.ServiceDesc{
ServiceName: "plugin.GRPCController",
HandlerType: (*GRPCControllerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Shutdown",
Handler: _GRPCController_Shutdown_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "internal/plugin/grpc_controller.proto",
}
+175 -183
View File
@@ -1,28 +1,28 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: grpc_stdio.proto
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: internal/plugin/grpc_stdio.proto
package plugin
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import empty "github.com/golang/protobuf/ptypes/empty"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
emptypb "google.golang.org/protobuf/types/known/emptypb"
reflect "reflect"
sync "sync"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type StdioData_Channel int32
@@ -32,202 +32,194 @@ const (
StdioData_STDERR StdioData_Channel = 2
)
var StdioData_Channel_name = map[int32]string{
0: "INVALID",
1: "STDOUT",
2: "STDERR",
}
var StdioData_Channel_value = map[string]int32{
"INVALID": 0,
"STDOUT": 1,
"STDERR": 2,
// Enum value maps for StdioData_Channel.
var (
StdioData_Channel_name = map[int32]string{
0: "INVALID",
1: "STDOUT",
2: "STDERR",
}
StdioData_Channel_value = map[string]int32{
"INVALID": 0,
"STDOUT": 1,
"STDERR": 2,
}
)
func (x StdioData_Channel) Enum() *StdioData_Channel {
p := new(StdioData_Channel)
*p = x
return p
}
func (x StdioData_Channel) String() string {
return proto.EnumName(StdioData_Channel_name, int32(x))
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (StdioData_Channel) Descriptor() protoreflect.EnumDescriptor {
return file_internal_plugin_grpc_stdio_proto_enumTypes[0].Descriptor()
}
func (StdioData_Channel) Type() protoreflect.EnumType {
return &file_internal_plugin_grpc_stdio_proto_enumTypes[0]
}
func (x StdioData_Channel) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use StdioData_Channel.Descriptor instead.
func (StdioData_Channel) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_grpc_stdio_db2934322ca63bd5, []int{0, 0}
return file_internal_plugin_grpc_stdio_proto_rawDescGZIP(), []int{0, 0}
}
// StdioData is a single chunk of stdout or stderr data that is streamed
// from GRPCStdio.
type StdioData struct {
Channel StdioData_Channel `protobuf:"varint,1,opt,name=channel,proto3,enum=plugin.StdioData_Channel" json:"channel,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Channel StdioData_Channel `protobuf:"varint,1,opt,name=channel,proto3,enum=plugin.StdioData_Channel" json:"channel,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}
func (m *StdioData) Reset() { *m = StdioData{} }
func (m *StdioData) String() string { return proto.CompactTextString(m) }
func (*StdioData) ProtoMessage() {}
func (x *StdioData) Reset() {
*x = StdioData{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_plugin_grpc_stdio_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StdioData) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StdioData) ProtoMessage() {}
func (x *StdioData) ProtoReflect() protoreflect.Message {
mi := &file_internal_plugin_grpc_stdio_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StdioData.ProtoReflect.Descriptor instead.
func (*StdioData) Descriptor() ([]byte, []int) {
return fileDescriptor_grpc_stdio_db2934322ca63bd5, []int{0}
}
func (m *StdioData) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StdioData.Unmarshal(m, b)
}
func (m *StdioData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_StdioData.Marshal(b, m, deterministic)
}
func (dst *StdioData) XXX_Merge(src proto.Message) {
xxx_messageInfo_StdioData.Merge(dst, src)
}
func (m *StdioData) XXX_Size() int {
return xxx_messageInfo_StdioData.Size(m)
}
func (m *StdioData) XXX_DiscardUnknown() {
xxx_messageInfo_StdioData.DiscardUnknown(m)
return file_internal_plugin_grpc_stdio_proto_rawDescGZIP(), []int{0}
}
var xxx_messageInfo_StdioData proto.InternalMessageInfo
func (m *StdioData) GetChannel() StdioData_Channel {
if m != nil {
return m.Channel
func (x *StdioData) GetChannel() StdioData_Channel {
if x != nil {
return x.Channel
}
return StdioData_INVALID
}
func (m *StdioData) GetData() []byte {
if m != nil {
return m.Data
func (x *StdioData) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
func init() {
proto.RegisterType((*StdioData)(nil), "plugin.StdioData")
proto.RegisterEnum("plugin.StdioData_Channel", StdioData_Channel_name, StdioData_Channel_value)
var File_internal_plugin_grpc_stdio_proto protoreflect.FileDescriptor
var file_internal_plugin_grpc_stdio_proto_rawDesc = []byte{
0x0a, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69,
0x6e, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x74, 0x64, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x84, 0x01, 0x0a, 0x09, 0x53, 0x74, 0x64, 0x69,
0x6f, 0x44, 0x61, 0x74, 0x61, 0x12, 0x33, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e,
0x53, 0x74, 0x64, 0x69, 0x6f, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61,
0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2e,
0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56,
0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54,
0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x32, 0x47,
0x0a, 0x09, 0x47, 0x52, 0x50, 0x43, 0x53, 0x74, 0x64, 0x69, 0x6f, 0x12, 0x3a, 0x0a, 0x0b, 0x53,
0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x64, 0x69, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x1a, 0x11, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x53, 0x74, 0x64, 0x69,
0x6f, 0x44, 0x61, 0x74, 0x61, 0x30, 0x01, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x70, 0x6c, 0x75,
0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
var (
file_internal_plugin_grpc_stdio_proto_rawDescOnce sync.Once
file_internal_plugin_grpc_stdio_proto_rawDescData = file_internal_plugin_grpc_stdio_proto_rawDesc
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// GRPCStdioClient is the client API for GRPCStdio service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GRPCStdioClient interface {
// StreamStdio returns a stream that contains all the stdout/stderr.
// This RPC endpoint must only be called ONCE. Once stdio data is consumed
// it is not sent again.
//
// Callers should connect early to prevent blocking on the plugin process.
StreamStdio(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (GRPCStdio_StreamStdioClient, error)
func file_internal_plugin_grpc_stdio_proto_rawDescGZIP() []byte {
file_internal_plugin_grpc_stdio_proto_rawDescOnce.Do(func() {
file_internal_plugin_grpc_stdio_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_plugin_grpc_stdio_proto_rawDescData)
})
return file_internal_plugin_grpc_stdio_proto_rawDescData
}
type gRPCStdioClient struct {
cc *grpc.ClientConn
var file_internal_plugin_grpc_stdio_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_internal_plugin_grpc_stdio_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_internal_plugin_grpc_stdio_proto_goTypes = []interface{}{
(StdioData_Channel)(0), // 0: plugin.StdioData.Channel
(*StdioData)(nil), // 1: plugin.StdioData
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
}
var file_internal_plugin_grpc_stdio_proto_depIdxs = []int32{
0, // 0: plugin.StdioData.channel:type_name -> plugin.StdioData.Channel
2, // 1: plugin.GRPCStdio.StreamStdio:input_type -> google.protobuf.Empty
1, // 2: plugin.GRPCStdio.StreamStdio:output_type -> plugin.StdioData
2, // [2:3] is the sub-list for method output_type
1, // [1:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func NewGRPCStdioClient(cc *grpc.ClientConn) GRPCStdioClient {
return &gRPCStdioClient{cc}
}
func (c *gRPCStdioClient) StreamStdio(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (GRPCStdio_StreamStdioClient, error) {
stream, err := c.cc.NewStream(ctx, &_GRPCStdio_serviceDesc.Streams[0], "/plugin.GRPCStdio/StreamStdio", opts...)
if err != nil {
return nil, err
func init() { file_internal_plugin_grpc_stdio_proto_init() }
func file_internal_plugin_grpc_stdio_proto_init() {
if File_internal_plugin_grpc_stdio_proto != nil {
return
}
x := &gRPCStdioStreamStdioClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
if !protoimpl.UnsafeEnabled {
file_internal_plugin_grpc_stdio_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StdioData); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type GRPCStdio_StreamStdioClient interface {
Recv() (*StdioData, error)
grpc.ClientStream
}
type gRPCStdioStreamStdioClient struct {
grpc.ClientStream
}
func (x *gRPCStdioStreamStdioClient) Recv() (*StdioData, error) {
m := new(StdioData)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GRPCStdioServer is the server API for GRPCStdio service.
type GRPCStdioServer interface {
// StreamStdio returns a stream that contains all the stdout/stderr.
// This RPC endpoint must only be called ONCE. Once stdio data is consumed
// it is not sent again.
//
// Callers should connect early to prevent blocking on the plugin process.
StreamStdio(*empty.Empty, GRPCStdio_StreamStdioServer) error
}
func RegisterGRPCStdioServer(s *grpc.Server, srv GRPCStdioServer) {
s.RegisterService(&_GRPCStdio_serviceDesc, srv)
}
func _GRPCStdio_StreamStdio_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(empty.Empty)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(GRPCStdioServer).StreamStdio(m, &gRPCStdioStreamStdioServer{stream})
}
type GRPCStdio_StreamStdioServer interface {
Send(*StdioData) error
grpc.ServerStream
}
type gRPCStdioStreamStdioServer struct {
grpc.ServerStream
}
func (x *gRPCStdioStreamStdioServer) Send(m *StdioData) error {
return x.ServerStream.SendMsg(m)
}
var _GRPCStdio_serviceDesc = grpc.ServiceDesc{
ServiceName: "plugin.GRPCStdio",
HandlerType: (*GRPCStdioServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "StreamStdio",
Handler: _GRPCStdio_StreamStdio_Handler,
ServerStreams: true,
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_internal_plugin_grpc_stdio_proto_rawDesc,
NumEnums: 1,
NumMessages: 1,
NumExtensions: 0,
NumServices: 1,
},
},
Metadata: "grpc_stdio.proto",
}
func init() { proto.RegisterFile("grpc_stdio.proto", fileDescriptor_grpc_stdio_db2934322ca63bd5) }
var fileDescriptor_grpc_stdio_db2934322ca63bd5 = []byte{
// 221 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x2f, 0x2a, 0x48,
0x8e, 0x2f, 0x2e, 0x49, 0xc9, 0xcc, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, 0xc8,
0x29, 0x4d, 0xcf, 0xcc, 0x93, 0x92, 0x4e, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x8b, 0x26,
0x95, 0xa6, 0xe9, 0xa7, 0xe6, 0x16, 0x94, 0x54, 0x42, 0x14, 0x29, 0xb5, 0x30, 0x72, 0x71, 0x06,
0x83, 0x34, 0xb9, 0x24, 0x96, 0x24, 0x0a, 0x19, 0x73, 0xb1, 0x27, 0x67, 0x24, 0xe6, 0xe5, 0xa5,
0xe6, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x19, 0x49, 0xea, 0x41, 0x0c, 0xd1, 0x83, 0xab, 0xd1,
0x73, 0x86, 0x28, 0x08, 0x82, 0xa9, 0x14, 0x12, 0xe2, 0x62, 0x49, 0x49, 0x2c, 0x49, 0x94, 0x60,
0x52, 0x60, 0xd4, 0xe0, 0x09, 0x02, 0xb3, 0x95, 0xf4, 0xb8, 0xd8, 0xa1, 0xea, 0x84, 0xb8, 0xb9,
0xd8, 0x3d, 0xfd, 0xc2, 0x1c, 0x7d, 0x3c, 0x5d, 0x04, 0x18, 0x84, 0xb8, 0xb8, 0xd8, 0x82, 0x43,
0x5c, 0xfc, 0x43, 0x43, 0x04, 0x18, 0xa1, 0x6c, 0xd7, 0xa0, 0x20, 0x01, 0x26, 0x23, 0x77, 0x2e,
0x4e, 0xf7, 0xa0, 0x00, 0x67, 0xb0, 0x2d, 0x42, 0x56, 0x5c, 0xdc, 0xc1, 0x25, 0x45, 0xa9, 0x89,
0xb9, 0x10, 0xae, 0x98, 0x1e, 0xc4, 0x03, 0x7a, 0x30, 0x0f, 0xe8, 0xb9, 0x82, 0x3c, 0x20, 0x25,
0x88, 0xe1, 0x36, 0x03, 0x46, 0x27, 0x8e, 0x28, 0xa8, 0xb7, 0x93, 0xd8, 0xc0, 0xca, 0x8d, 0x01,
0x01, 0x00, 0x00, 0xff, 0xff, 0x5d, 0xbb, 0xe0, 0x69, 0x19, 0x01, 0x00, 0x00,
GoTypes: file_internal_plugin_grpc_stdio_proto_goTypes,
DependencyIndexes: file_internal_plugin_grpc_stdio_proto_depIdxs,
EnumInfos: file_internal_plugin_grpc_stdio_proto_enumTypes,
MessageInfos: file_internal_plugin_grpc_stdio_proto_msgTypes,
}.Build()
File_internal_plugin_grpc_stdio_proto = out.File
file_internal_plugin_grpc_stdio_proto_rawDesc = nil
file_internal_plugin_grpc_stdio_proto_goTypes = nil
file_internal_plugin_grpc_stdio_proto_depIdxs = nil
}
+4 -1
View File
@@ -1,6 +1,9 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
syntax = "proto3";
package plugin;
option go_package = "plugin";
option go_package = "./plugin";
import "google/protobuf/empty.proto";
@@ -0,0 +1,148 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: internal/plugin/grpc_stdio.proto
package plugin
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
GRPCStdio_StreamStdio_FullMethodName = "/plugin.GRPCStdio/StreamStdio"
)
// GRPCStdioClient is the client API for GRPCStdio service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GRPCStdioClient interface {
// StreamStdio returns a stream that contains all the stdout/stderr.
// This RPC endpoint must only be called ONCE. Once stdio data is consumed
// it is not sent again.
//
// Callers should connect early to prevent blocking on the plugin process.
StreamStdio(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (GRPCStdio_StreamStdioClient, error)
}
type gRPCStdioClient struct {
cc grpc.ClientConnInterface
}
func NewGRPCStdioClient(cc grpc.ClientConnInterface) GRPCStdioClient {
return &gRPCStdioClient{cc}
}
func (c *gRPCStdioClient) StreamStdio(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (GRPCStdio_StreamStdioClient, error) {
stream, err := c.cc.NewStream(ctx, &GRPCStdio_ServiceDesc.Streams[0], GRPCStdio_StreamStdio_FullMethodName, opts...)
if err != nil {
return nil, err
}
x := &gRPCStdioStreamStdioClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type GRPCStdio_StreamStdioClient interface {
Recv() (*StdioData, error)
grpc.ClientStream
}
type gRPCStdioStreamStdioClient struct {
grpc.ClientStream
}
func (x *gRPCStdioStreamStdioClient) Recv() (*StdioData, error) {
m := new(StdioData)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GRPCStdioServer is the server API for GRPCStdio service.
// All implementations should embed UnimplementedGRPCStdioServer
// for forward compatibility
type GRPCStdioServer interface {
// StreamStdio returns a stream that contains all the stdout/stderr.
// This RPC endpoint must only be called ONCE. Once stdio data is consumed
// it is not sent again.
//
// Callers should connect early to prevent blocking on the plugin process.
StreamStdio(*emptypb.Empty, GRPCStdio_StreamStdioServer) error
}
// UnimplementedGRPCStdioServer should be embedded to have forward compatible implementations.
type UnimplementedGRPCStdioServer struct {
}
func (UnimplementedGRPCStdioServer) StreamStdio(*emptypb.Empty, GRPCStdio_StreamStdioServer) error {
return status.Errorf(codes.Unimplemented, "method StreamStdio not implemented")
}
// UnsafeGRPCStdioServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GRPCStdioServer will
// result in compilation errors.
type UnsafeGRPCStdioServer interface {
mustEmbedUnimplementedGRPCStdioServer()
}
func RegisterGRPCStdioServer(s grpc.ServiceRegistrar, srv GRPCStdioServer) {
s.RegisterService(&GRPCStdio_ServiceDesc, srv)
}
func _GRPCStdio_StreamStdio_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(emptypb.Empty)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(GRPCStdioServer).StreamStdio(m, &gRPCStdioStreamStdioServer{stream})
}
type GRPCStdio_StreamStdioServer interface {
Send(*StdioData) error
grpc.ServerStream
}
type gRPCStdioStreamStdioServer struct {
grpc.ServerStream
}
func (x *gRPCStdioStreamStdioServer) Send(m *StdioData) error {
return x.ServerStream.SendMsg(m)
}
// GRPCStdio_ServiceDesc is the grpc.ServiceDesc for GRPCStdio service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var GRPCStdio_ServiceDesc = grpc.ServiceDesc{
ServiceName: "plugin.GRPCStdio",
HandlerType: (*GRPCStdioServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "StreamStdio",
Handler: _GRPCStdio_StreamStdio_Handler,
ServerStreams: true,
},
},
Metadata: "internal/plugin/grpc_stdio.proto",
}
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// The plugin package exposes functions and helpers for communicating to
// plugins which are implemented as standalone binary applications.
//
+3 -23
View File
@@ -1,24 +1,4 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
"time"
)
// pidAlive checks whether a pid is alive.
func pidAlive(pid int) bool {
return _pidAlive(pid)
}
// pidWait blocks for a process to exit.
func pidWait(pid int) error {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
if !pidAlive(pid) {
break
}
}
return nil
}
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+12 -4
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
@@ -42,6 +45,8 @@ func (s *RPCServer) Config() string { return "" }
// ServerProtocol impl.
func (s *RPCServer) Serve(lis net.Listener) {
defer s.done()
for {
conn, err := lis.Accept()
if err != nil {
@@ -82,7 +87,7 @@ func (s *RPCServer) ServeConn(conn io.ReadWriteCloser) {
// Connect the stdstreams (in, out, err)
stdstream := make([]net.Conn, 2)
for i, _ := range stdstream {
for i := range stdstream {
stdstream[i], err = mux.Accept()
if err != nil {
mux.Close()
@@ -133,13 +138,15 @@ type controlServer struct {
// Ping can be called to verify the connection (and likely the binary)
// is still alive to a plugin.
func (c *controlServer) Ping(
null bool, response *struct{}) error {
null bool, response *struct{},
) error {
*response = struct{}{}
return nil
}
func (c *controlServer) Quit(
null bool, response *struct{}) error {
null bool, response *struct{},
) error {
// End the server
c.server.done()
@@ -156,7 +163,8 @@ type dispenseServer struct {
}
func (d *dispenseServer) Dispense(
name string, response *uint32) error {
name string, response *uint32,
) error {
// Find the function to create this implementation
p, ok := d.plugins[name]
if !ok {
+72
View File
@@ -0,0 +1,72 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package runner
import (
"context"
"io"
)
// Runner defines the interface required by go-plugin to manage the lifecycle of
// of a plugin and attempt to negotiate a connection with it. Note that this
// is orthogonal to the protocol and transport used, which is negotiated over stdout.
type Runner interface {
// Start should start the plugin and ensure any work required for servicing
// other interface methods is done. If the context is cancelled, it should
// only abort any attempts to _start_ the plugin. Waiting and shutdown are
// handled separately.
Start(ctx context.Context) error
// Diagnose makes a best-effort attempt to return any debug information that
// might help users understand why a plugin failed to start and negotiate a
// connection.
Diagnose(ctx context.Context) string
// Stdout is used to negotiate the go-plugin protocol.
Stdout() io.ReadCloser
// Stderr is used for forwarding plugin logs to the host process logger.
Stderr() io.ReadCloser
// Name is a human-friendly name for the plugin, such as the path to the
// executable. It does not have to be unique.
Name() string
AttachedRunner
}
// AttachedRunner defines a limited subset of Runner's interface to represent the
// reduced responsibility for plugin lifecycle when attaching to an already running
// plugin.
type AttachedRunner interface {
// Wait should wait until the plugin stops running, whether in response to
// an out of band signal or in response to calling Kill().
Wait(ctx context.Context) error
// Kill should stop the plugin and perform any cleanup required.
Kill(ctx context.Context) error
// ID is a unique identifier to represent the running plugin. e.g. pid or
// container ID.
ID() string
AddrTranslator
}
// AddrTranslator translates addresses between the execution context of the host
// process and the plugin. For example, if the plugin is in a container, the file
// path for a Unix socket may be different between the host and the container.
//
// It is only intended to be used by the host process.
type AddrTranslator interface {
// Called before connecting on any addresses received back from the plugin.
PluginToHost(pluginNet, pluginAddr string) (hostNet string, hostAddr string, err error)
// Called on any host process addresses before they are sent to the plugin.
HostToPlugin(hostNet, hostAddr string) (pluginNet string, pluginAddr string, err error)
}
// ReattachFunc can be passed to a client's reattach config to reattach to an
// already running plugin instead of starting it ourselves.
type ReattachFunc func() (AttachedRunner, error)
+89 -15
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
@@ -8,16 +11,17 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/signal"
"os/user"
"runtime"
"sort"
"strconv"
"strings"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin/internal/grpcmux"
"google.golang.org/grpc"
)
@@ -131,6 +135,13 @@ type ServeTestConfig struct {
SyncStdio bool
}
func unixSocketConfigFromEnv() UnixSocketConfig {
return UnixSocketConfig{
Group: os.Getenv(EnvUnixSocketGroup),
socketDir: os.Getenv(EnvUnixSocketDir),
}
}
// protocolVersion determines the protocol version and plugin set to be used by
// the server. In the event that there is no suitable version, the last version
// in the config is returned leaving the client to report the incompatibility.
@@ -270,7 +281,7 @@ func Serve(opts *ServeConfig) {
}
// Register a listener so we can accept a connection
listener, err := serverListener()
listener, err := serverListener(unixSocketConfigFromEnv())
if err != nil {
logger.Error("plugin init error", "error", err)
return
@@ -377,6 +388,12 @@ func Serve(opts *ServeConfig) {
}
case ProtocolGRPC:
var muxer *grpcmux.GRPCServerMuxer
if multiplex, _ := strconv.ParseBool(os.Getenv(envMultiplexGRPC)); multiplex {
muxer = grpcmux.NewGRPCServerMuxer(logger, listener)
listener = muxer
}
// Create the gRPC server
server = &GRPCServer{
Plugins: pluginSet,
@@ -386,6 +403,7 @@ func Serve(opts *ServeConfig) {
Stderr: stderr_r,
DoneCh: doneCh,
logger: logger,
muxer: muxer,
}
default:
@@ -404,13 +422,27 @@ func Serve(opts *ServeConfig) {
// bring it up. In test mode, we don't do this because clients will
// attach via a reattach config.
if opts.Test == nil {
fmt.Printf("%d|%d|%s|%s|%s|%s\n",
const grpcBrokerMultiplexingSupported = true
protocolLine := fmt.Sprintf("%d|%d|%s|%s|%s|%s",
CoreProtocolVersion,
protoVersion,
listener.Addr().Network(),
listener.Addr().String(),
protoType,
serverCert)
// Old clients will error with new plugins if we blindly append the
// seventh segment for gRPC broker multiplexing support, because old
// client code uses strings.SplitN(line, "|", 6), which means a seventh
// segment will get appended to the sixth segment as "sixthpart|true".
//
// If the environment variable is set, we assume the client is new enough
// to handle a seventh segment, as it should now use
// strings.Split(line, "|") and always handle each segment individually.
if os.Getenv(envMultiplexGRPC) != "" {
protocolLine += fmt.Sprintf("|%v", grpcBrokerMultiplexingSupported)
}
fmt.Printf("%s\n", protocolLine)
os.Stdout.Sync()
} else if ch := opts.Test.ReattachConfigCh; ch != nil {
// Send back the reattach config that can be used. This isn't
@@ -493,12 +525,12 @@ func Serve(opts *ServeConfig) {
}
}
func serverListener() (net.Listener, error) {
func serverListener(unixSocketCfg UnixSocketConfig) (net.Listener, error) {
if runtime.GOOS == "windows" {
return serverListener_tcp()
}
return serverListener_unix()
return serverListener_unix(unixSocketCfg)
}
func serverListener_tcp() (net.Listener, error) {
@@ -543,8 +575,8 @@ func serverListener_tcp() (net.Listener, error) {
return nil, errors.New("Couldn't bind plugin TCP listener")
}
func serverListener_unix() (net.Listener, error) {
tf, err := ioutil.TempFile("", "plugin")
func serverListener_unix(unixSocketCfg UnixSocketConfig) (net.Listener, error) {
tf, err := os.CreateTemp(unixSocketCfg.socketDir, "plugin")
if err != nil {
return nil, err
}
@@ -564,20 +596,62 @@ func serverListener_unix() (net.Listener, error) {
return nil, err
}
// By default, unix sockets are only writable by the owner. Set up a custom
// group owner and group write permissions if configured.
if unixSocketCfg.Group != "" {
err = setGroupWritable(path, unixSocketCfg.Group, 0o660)
if err != nil {
return nil, err
}
}
// Wrap the listener in rmListener so that the Unix domain socket file
// is removed on close.
return &rmListener{
Listener: l,
Path: path,
}, nil
return newDeleteFileListener(l, path), nil
}
func setGroupWritable(path, groupString string, mode os.FileMode) error {
groupID, err := strconv.Atoi(groupString)
if err != nil {
group, err := user.LookupGroup(groupString)
if err != nil {
return fmt.Errorf("failed to find gid from %q: %w", groupString, err)
}
groupID, err = strconv.Atoi(group.Gid)
if err != nil {
return fmt.Errorf("failed to parse %q group's gid as an integer: %w", groupString, err)
}
}
err = os.Chown(path, -1, groupID)
if err != nil {
return err
}
err = os.Chmod(path, mode)
if err != nil {
return err
}
return nil
}
// rmListener is an implementation of net.Listener that forwards most
// calls to the listener but also removes a file as part of the close. We
// use this to cleanup the unix domain socket on close.
// calls to the listener but also calls an additional close function. We
// use this to cleanup the unix domain socket on close, as well as clean
// up multiplexed listeners.
type rmListener struct {
net.Listener
Path string
close func() error
}
func newDeleteFileListener(ln net.Listener, path string) *rmListener {
return &rmListener{
Listener: ln,
close: func() error {
return os.Remove(path)
},
}
}
func (l *rmListener) Close() error {
@@ -587,5 +661,5 @@ func (l *rmListener) Close() error {
}
// Remove the file
return os.Remove(l.Path)
return l.close()
}
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
+32 -27
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
@@ -8,7 +11,7 @@ import (
"net/rpc"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin/internal/plugin"
"github.com/hashicorp/go-plugin/internal/grpcmux"
"github.com/mitchellh/go-testing-interface"
"google.golang.org/grpc"
)
@@ -132,49 +135,51 @@ func TestGRPCConn(t testing.T, register func(*grpc.Server)) (*grpc.ClientConn, *
// TestPluginGRPCConn returns a plugin gRPC client and server that are connected
// together and configured. This is used to test gRPC connections.
func TestPluginGRPCConn(t testing.T, ps map[string]Plugin) (*GRPCClient, *GRPCServer) {
func TestPluginGRPCConn(t testing.T, multiplex bool, ps map[string]Plugin) (*GRPCClient, *GRPCServer) {
// Create a listener
l, err := net.Listen("tcp", "127.0.0.1:0")
ln, err := serverListener(UnixSocketConfig{})
if err != nil {
t.Fatalf("err: %s", err)
t.Fatal(err)
}
logger := hclog.New(&hclog.LoggerOptions{
Level: hclog.Debug,
})
// Start up the server
var muxer *grpcmux.GRPCServerMuxer
if multiplex {
muxer = grpcmux.NewGRPCServerMuxer(logger, ln)
ln = muxer
}
server := &GRPCServer{
Plugins: ps,
DoneCh: make(chan struct{}),
Server: DefaultGRPCServer,
Stdout: new(bytes.Buffer),
Stderr: new(bytes.Buffer),
logger: hclog.Default(),
logger: logger,
muxer: muxer,
}
if err := server.Init(); err != nil {
t.Fatalf("err: %s", err)
}
go server.Serve(l)
go server.Serve(ln)
// Connect to the server
conn, err := grpc.Dial(
l.Addr().String(),
grpc.WithBlock(),
grpc.WithInsecure())
client := &Client{
address: ln.Addr(),
protocol: ProtocolGRPC,
config: &ClientConfig{
Plugins: ps,
GRPCBrokerMultiplex: multiplex,
},
logger: logger,
}
grpcClient, err := newGRPCClient(context.Background(), client)
if err != nil {
t.Fatalf("err: %s", err)
t.Fatal(err)
}
brokerGRPCClient := newGRPCBrokerClient(conn)
broker := newGRPCBroker(brokerGRPCClient, nil)
go broker.Run()
go brokerGRPCClient.StartStream()
// Create the client
client := &GRPCClient{
Conn: conn,
Plugins: ps,
broker: broker,
doneCtx: context.Background(),
controller: plugin.NewGRPCControllerClient(conn),
}
return client, server
return grpcClient, server
}
+81 -27
View File
@@ -2,6 +2,7 @@ package yamux
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
@@ -63,23 +64,26 @@ type Session struct {
// sendCh is used to mark a stream as ready to send,
// or to send a header out directly.
sendCh chan sendReady
sendCh chan *sendReady
// recvDoneCh is closed when recv() exits to avoid a race
// between stream registration and stream shutdown
recvDoneCh chan struct{}
sendDoneCh chan struct{}
// shutdown is used to safely close a session
shutdown bool
shutdownErr error
shutdownCh chan struct{}
shutdownLock sync.Mutex
shutdown bool
shutdownErr error
shutdownCh chan struct{}
shutdownLock sync.Mutex
shutdownErrLock sync.Mutex
}
// sendReady is used to either mark a stream as ready
// or to directly send a header
type sendReady struct {
Hdr []byte
mu sync.Mutex // Protects Body from unsafe reads.
Body []byte
Err chan error
}
@@ -101,8 +105,9 @@ func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
inflight: make(map[uint32]struct{}),
synCh: make(chan struct{}, config.AcceptBacklog),
acceptCh: make(chan *Stream, config.AcceptBacklog),
sendCh: make(chan sendReady, 64),
sendCh: make(chan *sendReady, 64),
recvDoneCh: make(chan struct{}),
sendDoneCh: make(chan struct{}),
shutdownCh: make(chan struct{}),
}
if client {
@@ -255,10 +260,15 @@ func (s *Session) Close() error {
return nil
}
s.shutdown = true
s.shutdownErrLock.Lock()
if s.shutdownErr == nil {
s.shutdownErr = ErrSessionShutdown
}
s.shutdownErrLock.Unlock()
close(s.shutdownCh)
s.conn.Close()
<-s.recvDoneCh
@@ -267,17 +277,18 @@ func (s *Session) Close() error {
for _, stream := range s.streams {
stream.forceClose()
}
<-s.sendDoneCh
return nil
}
// exitErr is used to handle an error that is causing the
// session to terminate.
func (s *Session) exitErr(err error) {
s.shutdownLock.Lock()
s.shutdownErrLock.Lock()
if s.shutdownErr == nil {
s.shutdownErr = err
}
s.shutdownLock.Unlock()
s.shutdownErrLock.Unlock()
s.Close()
}
@@ -373,7 +384,7 @@ func (s *Session) waitForSendErr(hdr header, body []byte, errCh chan error) erro
timerPool.Put(t)
}()
ready := sendReady{Hdr: hdr, Body: body, Err: errCh}
ready := &sendReady{Hdr: hdr, Body: body, Err: errCh}
select {
case s.sendCh <- ready:
case <-s.shutdownCh:
@@ -382,12 +393,34 @@ func (s *Session) waitForSendErr(hdr header, body []byte, errCh chan error) erro
return ErrConnectionWriteTimeout
}
bodyCopy := func() {
if body == nil {
return // A nil body is ignored.
}
// In the event of session shutdown or connection write timeout,
// we need to prevent `send` from reading the body buffer after
// returning from this function since the caller may re-use the
// underlying array.
ready.mu.Lock()
defer ready.mu.Unlock()
if ready.Body == nil {
return // Body was already copied in `send`.
}
newBody := make([]byte, len(body))
copy(newBody, body)
ready.Body = newBody
}
select {
case err := <-errCh:
return err
case <-s.shutdownCh:
bodyCopy()
return ErrSessionShutdown
case <-timer.C:
bodyCopy()
return ErrConnectionWriteTimeout
}
}
@@ -409,7 +442,7 @@ func (s *Session) sendNoWait(hdr header) error {
}()
select {
case s.sendCh <- sendReady{Hdr: hdr}:
case s.sendCh <- &sendReady{Hdr: hdr}:
return nil
case <-s.shutdownCh:
return ErrSessionShutdown
@@ -420,39 +453,59 @@ func (s *Session) sendNoWait(hdr header) error {
// send is a long running goroutine that sends data
func (s *Session) send() {
if err := s.sendLoop(); err != nil {
s.exitErr(err)
}
}
func (s *Session) sendLoop() error {
defer close(s.sendDoneCh)
var bodyBuf bytes.Buffer
for {
bodyBuf.Reset()
select {
case ready := <-s.sendCh:
// Send a header if ready
if ready.Hdr != nil {
sent := 0
for sent < len(ready.Hdr) {
n, err := s.conn.Write(ready.Hdr[sent:])
if err != nil {
s.logger.Printf("[ERR] yamux: Failed to write header: %v", err)
asyncSendErr(ready.Err, err)
s.exitErr(err)
return
}
sent += n
_, err := s.conn.Write(ready.Hdr)
if err != nil {
s.logger.Printf("[ERR] yamux: Failed to write header: %v", err)
asyncSendErr(ready.Err, err)
return err
}
}
// Send data from a body if given
ready.mu.Lock()
if ready.Body != nil {
_, err := s.conn.Write(ready.Body)
// Copy the body into the buffer to avoid
// holding a mutex lock during the write.
_, err := bodyBuf.Write(ready.Body)
if err != nil {
ready.Body = nil
ready.mu.Unlock()
s.logger.Printf("[ERR] yamux: Failed to copy body into buffer: %v", err)
asyncSendErr(ready.Err, err)
return err
}
ready.Body = nil
}
ready.mu.Unlock()
if bodyBuf.Len() > 0 {
// Send data from a body if given
_, err := s.conn.Write(bodyBuf.Bytes())
if err != nil {
s.logger.Printf("[ERR] yamux: Failed to write body: %v", err)
asyncSendErr(ready.Err, err)
s.exitErr(err)
return
return err
}
}
// No error, successful send
asyncSendErr(ready.Err, nil)
case <-s.shutdownCh:
return
return nil
}
}
}
@@ -639,8 +692,9 @@ func (s *Session) incomingStream(id uint32) error {
// Backlog exceeded! RST the stream
s.logger.Printf("[WARN] yamux: backlog exceeded, forcing connection reset")
delete(s.streams, id)
stream.sendHdr.encode(typeWindowUpdate, flagRST, id, 0)
return s.sendNoWait(stream.sendHdr)
hdr := header(make([]byte, headerSize))
hdr.encode(typeWindowUpdate, flagRST, id, 0)
return s.sendNoWait(hdr)
}
}
+23 -4
View File
@@ -2,6 +2,7 @@ package yamux
import (
"bytes"
"errors"
"io"
"sync"
"sync/atomic"
@@ -127,6 +128,9 @@ START:
// Send a window update potentially
err = s.sendWindowUpdate()
if err == ErrSessionShutdown {
err = nil
}
return n, err
WAIT:
@@ -200,6 +204,10 @@ START:
// Send the header
s.sendHdr.encode(typeData, flags, s.id, max)
if err = s.session.waitForSendErr(s.sendHdr, body, s.sendErr); err != nil {
if errors.Is(err, ErrSessionShutdown) || errors.Is(err, ErrConnectionWriteTimeout) {
// Message left in ready queue, header re-use is unsafe.
s.sendHdr = header(make([]byte, headerSize))
}
return 0, err
}
@@ -273,6 +281,10 @@ func (s *Stream) sendWindowUpdate() error {
// Send the header
s.controlHdr.encode(typeWindowUpdate, flags, s.id, delta)
if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil {
if errors.Is(err, ErrSessionShutdown) || errors.Is(err, ErrConnectionWriteTimeout) {
// Message left in ready queue, header re-use is unsafe.
s.controlHdr = header(make([]byte, headerSize))
}
return err
}
return nil
@@ -287,6 +299,10 @@ func (s *Stream) sendClose() error {
flags |= flagFIN
s.controlHdr.encode(typeWindowUpdate, flags, s.id, 0)
if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil {
if errors.Is(err, ErrSessionShutdown) || errors.Is(err, ErrConnectionWriteTimeout) {
// Message left in ready queue, header re-use is unsafe.
s.controlHdr = header(make([]byte, headerSize))
}
return err
}
return nil
@@ -362,8 +378,9 @@ func (s *Stream) closeTimeout() {
// Send a RST so the remote side closes too.
s.sendLock.Lock()
defer s.sendLock.Unlock()
s.sendHdr.encode(typeWindowUpdate, flagRST, s.id, 0)
s.session.sendNoWait(s.sendHdr)
hdr := header(make([]byte, headerSize))
hdr.encode(typeWindowUpdate, flagRST, s.id, 0)
s.session.sendNoWait(hdr)
}
// forceClose is used for when the session is exiting
@@ -465,6 +482,7 @@ func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error {
if length > s.recvWindow {
s.session.logger.Printf("[ERR] yamux: receive window exceeded (stream: %d, remain: %d, recv: %d)", s.id, s.recvWindow, length)
s.recvLock.Unlock()
return ErrRecvWindowExceeded
}
@@ -473,14 +491,15 @@ func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error {
// This way we can read in the whole packet without further allocations.
s.recvBuf = bytes.NewBuffer(make([]byte, 0, length))
}
if _, err := io.Copy(s.recvBuf, conn); err != nil {
copiedLength, err := io.Copy(s.recvBuf, conn)
if err != nil {
s.session.logger.Printf("[ERR] yamux: Failed to read stream data: %v", err)
s.recvLock.Unlock()
return err
}
// Decrement the receive window
s.recvWindow -= length
s.recvWindow -= uint32(copiedLength)
s.recvLock.Unlock()
// Unblock any readers