mirror of
https://github.com/fullstorydev/grpcurl.git
synced 2026-08-01 08:30:34 +03:00
Compare commits
1 Commits
5f2d0c2dfc
..
tmp
| Author | SHA1 | Date | |
|---|---|---|---|
| 13ca681bad |
@@ -1,5 +1,4 @@
|
||||
dist/
|
||||
.claude/
|
||||
.idea/
|
||||
VERSION
|
||||
.tmp/
|
||||
|
||||
@@ -146,7 +146,7 @@ grpcurl -d @ grpc.server.com:443 my.custom.server.Service/Method <<EOM
|
||||
{
|
||||
"id": 1234,
|
||||
"tags": [
|
||||
"foo",
|
||||
"foor",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 required to use APIs in other grpcurl package
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
+11
-24
@@ -9,11 +9,11 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/desc/protoparse"
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc/protoparse" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc/protoprint"
|
||||
"github.com/jhump/protoreflect/dynamic"
|
||||
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -309,37 +309,24 @@ func WriteProtoFiles(outProtoDirPath string, descSource DescriptorSource, symbol
|
||||
for _, filename := range filenames {
|
||||
allFileDescriptors = addFilesToFileDescriptorList(allFileDescriptors, expandedFiles, fds[filename])
|
||||
}
|
||||
return writeProtoFiles(outProtoDirPath, allFileDescriptors)
|
||||
}
|
||||
|
||||
func writeProtoFiles(outProtoDirPath string, allFileDescriptors []*desc.FileDescriptor) error {
|
||||
if err := os.MkdirAll(outProtoDirPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create output directory %q: %w", outProtoDirPath, err)
|
||||
}
|
||||
root, err := os.OpenRoot(outProtoDirPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open output directory %q: %w", outProtoDirPath, err)
|
||||
}
|
||||
defer root.Close()
|
||||
pr := protoprint.Printer{}
|
||||
// now we can serialize to files
|
||||
for i := range allFileDescriptors {
|
||||
if err := writeProtoFile(root, allFileDescriptors[i], &pr); err != nil {
|
||||
if err := writeProtoFile(outProtoDirPath, allFileDescriptors[i], &pr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeProtoFile(root *os.Root, fd *desc.FileDescriptor, pr *protoprint.Printer) error {
|
||||
// root confines all path operations to the output directory, so a
|
||||
// malicious file name (e.g. "../escape.proto") cannot write outside it
|
||||
outFile := fd.GetFullyQualifiedName()
|
||||
if err := root.MkdirAll(filepath.Dir(outFile), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory for %q: %w", outFile, err)
|
||||
func writeProtoFile(outProtoDirPath string, fd *desc.FileDescriptor, pr *protoprint.Printer) error {
|
||||
outFile := filepath.Join(outProtoDirPath, fd.GetFullyQualifiedName())
|
||||
outDir := filepath.Dir(outFile)
|
||||
if err := os.MkdirAll(outDir, 0777); err != nil {
|
||||
return fmt.Errorf("failed to create directory %q: %w", outDir, err)
|
||||
}
|
||||
|
||||
f, err := root.Create(outFile)
|
||||
f, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create proto file %q: %w", outFile, err)
|
||||
}
|
||||
|
||||
+1
-47
@@ -3,11 +3,9 @@ package grpcurl
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
)
|
||||
|
||||
@@ -62,47 +60,3 @@ func checkWriteProtoset(t *testing.T, descSrc DescriptorSource, protoset *descri
|
||||
t.Fatalf("written protoset not equal to input:\nExpecting: %s\nActual: %s", protoset, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func writeProtoFileForTest(t *testing.T, dir, fdName string) error {
|
||||
t.Helper()
|
||||
fd, err := desc.CreateFileDescriptor(&descriptorpb.FileDescriptorProto{
|
||||
Name: proto.String(fdName),
|
||||
Syntax: proto.String("proto3"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create file descriptor: %v", err)
|
||||
}
|
||||
return writeProtoFiles(dir, []*desc.FileDescriptor{fd})
|
||||
}
|
||||
|
||||
func TestWriteProtoFile_NormalPath(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := writeProtoFileForTest(t, dir, "foo/bar.proto"); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, "foo", "bar.proto")); err != nil {
|
||||
t.Fatalf("expected output file not created: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteProtoFile_RejectsPathTraversal(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := writeProtoFileForTest(t, dir, "../escape.proto"); err == nil {
|
||||
t.Fatal("expected error for path-traversing descriptor name, got nil")
|
||||
}
|
||||
escapePath := filepath.Join(dir, "..", "escape.proto")
|
||||
if _, err := os.Stat(escapePath); err == nil {
|
||||
t.Fatalf("file was created outside output directory at %q", escapePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteProtoFile_RejectsDeepPathTraversal(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := writeProtoFileForTest(t, dir, "foo/../../../escape.proto"); err == nil {
|
||||
t.Fatal("expected error for path-traversing descriptor name, got nil")
|
||||
}
|
||||
escapePath := filepath.Join(dir, "foo", "..", "..", "..", "escape.proto")
|
||||
if _, err := os.Stat(escapePath); err == nil {
|
||||
t.Fatalf("file was created outside output directory at %q", escapePath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/dynamic"
|
||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -330,6 +330,7 @@ func (r anyResolverWithFallback) Resolve(typeUrl string) (proto.Message, error)
|
||||
if slash := strings.LastIndex(mname, "/"); slash >= 0 {
|
||||
mname = mname[slash+1:]
|
||||
}
|
||||
//lint:ignore SA1019 new non-deprecated API requires other code changes; deferring...
|
||||
mt := proto.MessageType(mname)
|
||||
if mt != nil {
|
||||
return reflect.New(mt.Elem()).Interface().(proto.Message), nil
|
||||
|
||||
+3
-3
@@ -7,9 +7,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module github.com/fullstorydev/grpcurl
|
||||
|
||||
go 1.25.0
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.4
|
||||
@@ -22,11 +24,11 @@ require (
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||
)
|
||||
|
||||
@@ -56,16 +56,16 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM=
|
||||
|
||||
+23
-65
@@ -21,12 +21,11 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc/protoprint"
|
||||
"github.com/jhump/protoreflect/dynamic"
|
||||
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@@ -616,8 +615,8 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
||||
}
|
||||
|
||||
var err error
|
||||
if strings.HasPrefix(address, "xds://") {
|
||||
// The xds:// prefix is used to signal to the gRPC client to use an xDS server to resolve the
|
||||
if strings.HasPrefix(address, "xds:///") {
|
||||
// The xds:/// prefix is used to signal to the gRPC client to use an xDS server to resolve the
|
||||
// target. The relevant credentials will be automatically pulled from the GRPC_XDS_BOOTSTRAP or
|
||||
// GRPC_XDS_BOOTSTRAP_CONFIG env vars.
|
||||
creds, err = xdsCredentials.NewClientCredentials(xdsCredentials.ClientOptions{FallbackCreds: creds})
|
||||
@@ -631,16 +630,7 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
||||
// custom dialer that can provide that info. That means we manage the TLS handshake.
|
||||
result := make(chan interface{}, 1)
|
||||
|
||||
// dialCompleted is closed once the outcome of the dial (ready connection
|
||||
// or error) is known, so that connection teardown no longer needs to wait
|
||||
// for a pending TLS alert to be read.
|
||||
dialCompleted := make(chan struct{})
|
||||
var dialCompletedOnce sync.Once
|
||||
completeDial := func() { dialCompletedOnce.Do(func() { close(dialCompleted) }) }
|
||||
defer completeDial()
|
||||
|
||||
writeResult := func(res interface{}) {
|
||||
completeDial()
|
||||
// non-blocking write: we only need the first result
|
||||
select {
|
||||
case result <- res:
|
||||
@@ -653,7 +643,6 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
||||
creds = &errSignalingCreds{
|
||||
TransportCredentials: creds,
|
||||
writeResult: writeResult,
|
||||
dialCompleted: dialCompleted,
|
||||
}
|
||||
|
||||
switch network {
|
||||
@@ -736,8 +725,7 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
||||
// it will use the writeResult function to notify on error.
|
||||
type errSignalingCreds struct {
|
||||
credentials.TransportCredentials
|
||||
writeResult func(res interface{})
|
||||
dialCompleted <-chan struct{}
|
||||
writeResult func(res interface{})
|
||||
}
|
||||
|
||||
func (c *errSignalingCreds) ClientHandshake(ctx context.Context, addr string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
@@ -746,62 +734,32 @@ func (c *errSignalingCreds) ClientHandshake(ctx context.Context, addr string, ra
|
||||
c.writeResult(err)
|
||||
return conn, auth, err
|
||||
}
|
||||
// Wrap the connection to capture post-handshake errors, e.g.:
|
||||
// - TLS 1.3 client cert rejection (server sends alert after handshake)
|
||||
// - Plaintext client to TLS server (server closes conn immediately)
|
||||
return &errSignalingConn{Conn: conn, writeResult: c.writeResult, dialCompleted: c.dialCompleted}, auth, nil
|
||||
// Wrap TLS connections to capture post-handshake errors. With TLS 1.3,
|
||||
// client certificate rejection by the server happens after the client
|
||||
// considers the handshake complete. The server's TLS alert surfaces on the
|
||||
// first Read from the connection. Only TLS connections need this (plaintext
|
||||
// connections don't have post-handshake alerts).
|
||||
if _, isTLS := auth.(credentials.TLSInfo); isTLS {
|
||||
conn = &errSignalingConn{Conn: conn, writeResult: c.writeResult}
|
||||
}
|
||||
return conn, auth, nil
|
||||
}
|
||||
|
||||
// maxTLSAlertWait is an upper bound on how long a failing dial's connection
|
||||
// teardown waits for a pending TLS alert to be read. It is a heuristic: long
|
||||
// enough for the reader goroutine to be scheduled even on a loaded machine,
|
||||
// short enough to not noticeably delay a failing dial.
|
||||
const maxTLSAlertWait = 50 * time.Millisecond
|
||||
|
||||
// errSignalingConn wraps a net.Conn to capture read errors and report
|
||||
// them via writeResult. Close is delayed until the dial has completed to
|
||||
// give the reader a chance to read a pending TLS alert before the
|
||||
// connection is closed.
|
||||
// errSignalingConn wraps a net.Conn to capture the first read error and
|
||||
// report it via writeResult. This allows BlockingDial to surface post-handshake
|
||||
// errors.
|
||||
type errSignalingConn struct {
|
||||
net.Conn
|
||||
writeResult func(res interface{})
|
||||
dialCompleted <-chan struct{}
|
||||
writeResult func(res interface{})
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (c *errSignalingConn) Read(b []byte) (int, error) {
|
||||
n, err := c.Conn.Read(b)
|
||||
if err != nil {
|
||||
c.writeResult(err)
|
||||
c.once.Do(func() {
|
||||
c.writeResult(err)
|
||||
})
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *errSignalingConn) Close() error {
|
||||
// With TLS 1.3, the server may reject the connection *after* the
|
||||
// handshake completes (e.g. a missing client cert), by sending an alert.
|
||||
// If the transport closes the connection before that alert is read, the
|
||||
// caller sees a less useful error (e.g. "use of closed network
|
||||
// connection") instead of the TLS alert. So while the outcome of the
|
||||
// dial is still undecided, give the reader a brief window to read a
|
||||
// pending alert before closing the connection.
|
||||
select {
|
||||
case <-c.dialCompleted:
|
||||
case <-time.After(maxTLSAlertWait):
|
||||
}
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
// UsesXDS forwards the optional UsesXDS marker of the wrapped credentials. The
|
||||
// xDS credentials returned for "xds://" targets implement this method, and
|
||||
// grpc-go's cds balancer relies on a type assertion for it to decide whether to
|
||||
// apply the security configuration (e.g. UpstreamTlsContext) delivered by the
|
||||
// management server. Because errSignalingCreds embeds the TransportCredentials
|
||||
// interface, that extra method is not promoted automatically, so we forward it
|
||||
// explicitly. Without this, xDS-supplied mTLS is silently ignored and the
|
||||
// connection falls back to the plain credentials.
|
||||
func (c *errSignalingCreds) UsesXDS() bool {
|
||||
if x, ok := c.TransportCredentials.(interface{ UsesXDS() bool }); ok {
|
||||
return x.UsesXDS()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+3
-3
@@ -12,9 +12,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/dynamic"
|
||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/dynamic/grpcdynamic"
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
+50
-9
@@ -171,17 +171,58 @@ func TestBrokenTLS_ClientPlainText(t *testing.T) {
|
||||
t.Fatalf("failed to create server creds: %v", err)
|
||||
}
|
||||
|
||||
// Plaintext client to TLS server: the server expects a TLS handshake,
|
||||
// gets an HTTP/2 preface instead, and closes the connection.
|
||||
e, err := createTestServerAndClient(serverCreds, nil)
|
||||
if err == nil {
|
||||
e.Close()
|
||||
t.Fatal("expecting failure when connecting plaintext to TLS server")
|
||||
// client connection (usually) succeeds since client is not waiting for TLS handshake
|
||||
// (we try several times, but if we never get a connection and the error message is
|
||||
// a known/expected possibility, we'll just bail)
|
||||
var e testEnv
|
||||
failCount := 0
|
||||
for {
|
||||
e, err = createTestServerAndClient(serverCreds, nil)
|
||||
if err == nil {
|
||||
// success!
|
||||
defer e.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "deadline exceeded") ||
|
||||
strings.Contains(err.Error(), "use of closed network connection") {
|
||||
// It is possible that the connection never becomes healthy:
|
||||
// 1) grpc connects successfully
|
||||
// 2) grpc client tries to send HTTP/2 preface and settings frame
|
||||
// 3) server, expecting handshake, closes the connection
|
||||
// 4) in the client, the write fails, so the connection never
|
||||
// becomes ready
|
||||
// The client will attempt to reconnect on transient errors, so
|
||||
// may eventually bump into the connect time limit. This used to
|
||||
// result in a "deadline exceeded" error, but more recent versions
|
||||
// of the grpc library report any underlying I/O error instead, so
|
||||
// we also check for "use of closed network connection".
|
||||
failCount++
|
||||
if failCount > 5 {
|
||||
return // bail...
|
||||
}
|
||||
// we'll try again
|
||||
|
||||
} else {
|
||||
// some other error occurred, so we'll consider that a test failure
|
||||
t.Fatalf("failed to setup server and client: %v", err)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(err.Error(), "EOF") &&
|
||||
|
||||
// but request fails because server closes connection upon seeing request
|
||||
// bytes that are not a TLS handshake
|
||||
cl := grpcurl_testing.NewTestServiceClient(e.cc)
|
||||
_, err = cl.UnaryCall(context.Background(), &grpcurl_testing.SimpleRequest{})
|
||||
if err == nil {
|
||||
t.Fatal("expecting failure")
|
||||
}
|
||||
// various errors possible when server closes connection
|
||||
if !strings.Contains(err.Error(), "transport is closing") &&
|
||||
!strings.Contains(err.Error(), "connection is unavailable") &&
|
||||
!strings.Contains(err.Error(), "use of closed network connection") &&
|
||||
!strings.Contains(err.Error(), "connection reset by peer") {
|
||||
t.Fatalf("expecting connection closed error, got: %v", err)
|
||||
!strings.Contains(err.Error(), "all SubConns are in TransientFailure") {
|
||||
|
||||
t.Fatalf("expecting transport failure, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user