go.uber.org/yarpc@v1.72.1/transport/grpc/options.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package grpc
    22  
    23  import (
    24  	"context"
    25  	"crypto/tls"
    26  	"math"
    27  	"net"
    28  
    29  	opentracing "github.com/opentracing/opentracing-go"
    30  	"go.uber.org/net/metrics"
    31  	"go.uber.org/yarpc/api/backoff"
    32  	"go.uber.org/yarpc/api/transport"
    33  	yarpctls "go.uber.org/yarpc/api/transport/tls"
    34  	intbackoff "go.uber.org/yarpc/internal/backoff"
    35  	"go.uber.org/yarpc/transport/internal/tls/dialer"
    36  	"go.uber.org/zap"
    37  	"google.golang.org/grpc"
    38  	"google.golang.org/grpc/credentials"
    39  	"google.golang.org/grpc/keepalive"
    40  )
    41  
    42  const (
    43  	// defensive programming
    44  	// these are copied from grpc-go but we set them explicitly here
    45  	// in case these change in grpc-go so that yarpc stays consistent
    46  	defaultServerMaxSendMsgSize = math.MaxInt32
    47  	defaultClientMaxSendMsgSize = math.MaxInt32
    48  	// Overriding default server and client maximum request and response
    49  	// receive sizes to 64MB.
    50  	defaultServerMaxRecvMsgSize = 1024 * 1024 * 64
    51  	defaultClientMaxRecvMsgSize = 1024 * 1024 * 64
    52  )
    53  
    54  // Option is an interface shared by TransportOption, InboundOption, and OutboundOption
    55  // allowing either to be recognized by TransportSpec().
    56  type Option interface {
    57  	grpcOption()
    58  }
    59  
    60  var _ Option = (TransportOption)(nil)
    61  var _ Option = (InboundOption)(nil)
    62  var _ Option = (OutboundOption)(nil)
    63  var _ Option = (DialOption)(nil)
    64  
    65  // TransportOption is an option for a transport.
    66  type TransportOption func(*transportOptions)
    67  
    68  func (TransportOption) grpcOption() {}
    69  
    70  // ServiceName specifices the name of the service used in transport logging
    71  // and metrics.
    72  func ServiceName(name string) TransportOption {
    73  	return func(transportOptions *transportOptions) {
    74  		transportOptions.serviceName = name
    75  	}
    76  }
    77  
    78  // BackoffStrategy specifies the backoff strategy for delays between
    79  // connection attempts for each peer.
    80  //
    81  // The default is exponential backoff starting with 10ms fully jittered,
    82  // doubling each attempt, with a maximum interval of 30s.
    83  func BackoffStrategy(backoffStrategy backoff.Strategy) TransportOption {
    84  	return func(transportOptions *transportOptions) {
    85  		transportOptions.backoffStrategy = backoffStrategy
    86  	}
    87  }
    88  
    89  // Tracer specifies the tracer to use.
    90  //
    91  // By default, opentracing.GlobalTracer() is used.
    92  func Tracer(tracer opentracing.Tracer) TransportOption {
    93  	return func(transportOptions *transportOptions) {
    94  		transportOptions.tracer = tracer
    95  	}
    96  }
    97  
    98  // Logger sets a logger to use for internal logging.
    99  //
   100  // The default is to not write any logs.
   101  func Logger(logger *zap.Logger) TransportOption {
   102  	return func(transportOptions *transportOptions) {
   103  		transportOptions.logger = logger
   104  	}
   105  }
   106  
   107  // Meter sets a meter to use for transport metrics.
   108  //
   109  // The default is to not emit any metrics.
   110  func Meter(meter *metrics.Scope) TransportOption {
   111  	return func(transportOptions *transportOptions) {
   112  		transportOptions.meter = meter
   113  	}
   114  }
   115  
   116  // ServerMaxRecvMsgSize is the maximum message size the server can receive.
   117  //
   118  // The default is 4MB.
   119  func ServerMaxRecvMsgSize(serverMaxRecvMsgSize int) TransportOption {
   120  	return func(transportOptions *transportOptions) {
   121  		transportOptions.serverMaxRecvMsgSize = serverMaxRecvMsgSize
   122  	}
   123  }
   124  
   125  // ServerMaxSendMsgSize is the maximum message size the server can send.
   126  //
   127  // The default is unlimited.
   128  func ServerMaxSendMsgSize(serverMaxSendMsgSize int) TransportOption {
   129  	return func(transportOptions *transportOptions) {
   130  		transportOptions.serverMaxSendMsgSize = serverMaxSendMsgSize
   131  	}
   132  }
   133  
   134  // ServerMaxHeaderListSize returns a transport option for configuring maximum
   135  // header list size the server must accept.
   136  //
   137  // The default is 16MB (gRPC default).
   138  func ServerMaxHeaderListSize(serverMaxHeaderListSize uint32) TransportOption {
   139  	return func(transportOptions *transportOptions) {
   140  		transportOptions.serverMaxHeaderListSize = &serverMaxHeaderListSize
   141  	}
   142  }
   143  
   144  // ClientMaxRecvMsgSize is the maximum message size the client can receive.
   145  //
   146  // The default is 4MB.
   147  func ClientMaxRecvMsgSize(clientMaxRecvMsgSize int) TransportOption {
   148  	return func(transportOptions *transportOptions) {
   149  		transportOptions.clientMaxRecvMsgSize = clientMaxRecvMsgSize
   150  	}
   151  }
   152  
   153  // ClientMaxSendMsgSize is the maximum message size the client can send.
   154  //
   155  // The default is unlimited.
   156  func ClientMaxSendMsgSize(clientMaxSendMsgSize int) TransportOption {
   157  	return func(transportOptions *transportOptions) {
   158  		transportOptions.clientMaxSendMsgSize = clientMaxSendMsgSize
   159  	}
   160  }
   161  
   162  // ClientMaxHeaderListSize returns a transport option for configuring maximum
   163  // header list size the client must accept.
   164  //
   165  // The default is 16MB (gRPC default).
   166  func ClientMaxHeaderListSize(clientMaxHeaderListSize uint32) TransportOption {
   167  	return func(transportOptions *transportOptions) {
   168  		transportOptions.clientMaxHeaderListSize = &clientMaxHeaderListSize
   169  	}
   170  }
   171  
   172  // InboundOption is an option for an inbound.
   173  type InboundOption func(*inboundOptions)
   174  
   175  func (InboundOption) grpcOption() {}
   176  
   177  // InboundCredentials returns an InboundOption that sets credentials for incoming
   178  // connections.
   179  func InboundCredentials(creds credentials.TransportCredentials) InboundOption {
   180  	return func(inboundOptions *inboundOptions) {
   181  		inboundOptions.creds = creds
   182  	}
   183  }
   184  
   185  // InboundTLSConfiguration returns an InboundOption that provides the TLS confiugration
   186  // used for setting up TLS inbound.
   187  func InboundTLSConfiguration(config *tls.Config) InboundOption {
   188  	return func(inboundOptions *inboundOptions) {
   189  		inboundOptions.tlsConfig = config
   190  	}
   191  }
   192  
   193  // InboundTLSMode returns an InboundOption that sets inbound TLS mode.
   194  // It must be noted that TLS configuration must be passed separately using inbound
   195  // option InboundTLSConfiguration.
   196  func InboundTLSMode(mode yarpctls.Mode) InboundOption {
   197  	return func(inboundOptions *inboundOptions) {
   198  		inboundOptions.tlsMode = mode
   199  	}
   200  }
   201  
   202  // OutboundOption is an option for an outbound.
   203  type OutboundOption func(*outboundOptions)
   204  
   205  func (OutboundOption) grpcOption() {}
   206  
   207  // OutboundTLSConfigProvider returns an OutboundOption that provides the
   208  // outbound TLS config provider.
   209  func OutboundTLSConfigProvider(provider yarpctls.OutboundTLSConfigProvider) OutboundOption {
   210  	return func(outboundOptions *outboundOptions) {
   211  		outboundOptions.tlsConfigProvider = provider
   212  	}
   213  }
   214  
   215  // OutboundCompressor returns an OutboundOption that applies compressorion
   216  // for requests in the outbound.
   217  func OutboundCompressor(compressor transport.Compressor) OutboundOption {
   218  	return func(outboundOptions *outboundOptions) {
   219  		if compressor != nil {
   220  			outboundOptions.compressor = compressor.Name()
   221  		}
   222  	}
   223  }
   224  
   225  // DialOption is an option that influences grpc.Dial.
   226  type DialOption func(*dialOptions)
   227  
   228  func (DialOption) grpcOption() {}
   229  
   230  // DialerCredentials returns a DialOption which configures a
   231  // connection level security credentials (e.g., TLS/SSL).
   232  func DialerCredentials(creds credentials.TransportCredentials) DialOption {
   233  	return func(dialOptions *dialOptions) {
   234  		dialOptions.creds = creds
   235  	}
   236  }
   237  
   238  // DialerTLSConfig return a DialOption which configures the TLS config for the
   239  // outbound.
   240  func DialerTLSConfig(config *tls.Config) DialOption {
   241  	return func(dialOptions *dialOptions) {
   242  		dialOptions.tlsConfig = config
   243  	}
   244  }
   245  
   246  // DialerDestinationServiceName returns a DialOption which configures the
   247  // destination service name of the dialer. This is used in TLS dialer metrics.
   248  func DialerDestinationServiceName(service string) DialOption {
   249  	return func(dialOptions *dialOptions) {
   250  		dialOptions.destServiceName = service
   251  	}
   252  }
   253  
   254  // ContextDialer sets the dialer for creating outbound connections.
   255  //
   256  // See https://godoc.org/google.golang.org/grpc#WithContextDialer for more
   257  // details.
   258  func ContextDialer(f func(context.Context, string) (net.Conn, error)) DialOption {
   259  	return func(dialOptions *dialOptions) {
   260  		dialOptions.contextDialer = f
   261  	}
   262  }
   263  
   264  // Compressor sets the compressor to be used by default for gRPC connections
   265  func Compressor(compressor transport.Compressor) DialOption {
   266  	return func(dialOptions *dialOptions) {
   267  		if compressor != nil {
   268  			// We assume that the grpc-go compressor was also globally
   269  			// registered and just use the name.
   270  			// Future implementations may elect to actually use the compressor.
   271  			dialOptions.defaultCompressor = compressor.Name()
   272  		}
   273  	}
   274  }
   275  
   276  // KeepaliveParams sets the gRPC keepalive parameters of the outbound
   277  // connection.
   278  // See https://pkg.go.dev/google.golang.org/grpc#WithKeepaliveParams for more
   279  // details.
   280  func KeepaliveParams(params keepalive.ClientParameters) DialOption {
   281  	return func(dialOptions *dialOptions) {
   282  		dialOptions.keepaliveParams = &params
   283  	}
   284  }
   285  
   286  type transportOptions struct {
   287  	backoffStrategy         backoff.Strategy
   288  	tracer                  opentracing.Tracer
   289  	logger                  *zap.Logger
   290  	meter                   *metrics.Scope
   291  	serviceName             string
   292  	serverMaxRecvMsgSize    int
   293  	serverMaxSendMsgSize    int
   294  	clientMaxRecvMsgSize    int
   295  	clientMaxSendMsgSize    int
   296  	serverMaxHeaderListSize *uint32
   297  	clientMaxHeaderListSize *uint32
   298  }
   299  
   300  func newTransportOptions(options []TransportOption) *transportOptions {
   301  	transportOptions := &transportOptions{
   302  		backoffStrategy:      intbackoff.DefaultExponential,
   303  		serverMaxRecvMsgSize: defaultServerMaxRecvMsgSize,
   304  		serverMaxSendMsgSize: defaultServerMaxSendMsgSize,
   305  		clientMaxRecvMsgSize: defaultClientMaxRecvMsgSize,
   306  		clientMaxSendMsgSize: defaultClientMaxSendMsgSize,
   307  	}
   308  	for _, option := range options {
   309  		option(transportOptions)
   310  	}
   311  	if transportOptions.logger == nil {
   312  		transportOptions.logger = zap.NewNop()
   313  	}
   314  	if transportOptions.tracer == nil {
   315  		transportOptions.tracer = opentracing.GlobalTracer()
   316  	}
   317  	return transportOptions
   318  }
   319  
   320  type inboundOptions struct {
   321  	creds credentials.TransportCredentials
   322  
   323  	tlsConfig *tls.Config
   324  	tlsMode   yarpctls.Mode
   325  }
   326  
   327  func newInboundOptions(options []InboundOption) *inboundOptions {
   328  	inboundOptions := &inboundOptions{}
   329  	for _, option := range options {
   330  		option(inboundOptions)
   331  	}
   332  	return inboundOptions
   333  }
   334  
   335  type outboundOptions struct {
   336  	compressor        string
   337  	tlsConfigProvider yarpctls.OutboundTLSConfigProvider
   338  }
   339  
   340  func newOutboundOptions(options []OutboundOption) *outboundOptions {
   341  	outboundOptions := &outboundOptions{}
   342  	for _, option := range options {
   343  		option(outboundOptions)
   344  	}
   345  	return outboundOptions
   346  }
   347  
   348  type dialOptions struct {
   349  	creds             credentials.TransportCredentials
   350  	contextDialer     func(context.Context, string) (net.Conn, error)
   351  	defaultCompressor string
   352  	keepaliveParams   *keepalive.ClientParameters
   353  	tlsConfig         *tls.Config
   354  	destServiceName   string
   355  }
   356  
   357  func (d *dialOptions) grpcOptions(t *Transport) []grpc.DialOption {
   358  	credsOption := grpc.WithInsecure()
   359  	if d.creds != nil {
   360  		credsOption = grpc.WithTransportCredentials(d.creds)
   361  	}
   362  
   363  	opts := []grpc.DialOption{
   364  		credsOption,
   365  	}
   366  
   367  	if d.defaultCompressor != "" {
   368  		opts = append(opts, grpc.WithDefaultCallOptions(grpc.UseCompressor(d.defaultCompressor)))
   369  	}
   370  
   371  	if d.keepaliveParams != nil {
   372  		opts = append(opts, grpc.WithKeepaliveParams(*d.keepaliveParams))
   373  	}
   374  
   375  	contextDialer := d.contextDialer
   376  	if d.tlsConfig != nil {
   377  		params := dialer.Params{
   378  			Config:        d.tlsConfig,
   379  			Meter:         t.options.meter,
   380  			Logger:        t.options.logger,
   381  			ServiceName:   t.options.serviceName,
   382  			TransportName: TransportName,
   383  			Dest:          d.destServiceName,
   384  		}
   385  
   386  		if d.contextDialer != nil {
   387  			params.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
   388  				return d.contextDialer(ctx, addr)
   389  			}
   390  		}
   391  		tlsDialer := dialer.NewTLSDialer(params)
   392  		contextDialer = func(ctx context.Context, addr string) (net.Conn, error) {
   393  			return tlsDialer.DialContext(ctx, "tcp", addr)
   394  		}
   395  	}
   396  	opts = append(opts, grpc.WithContextDialer(contextDialer))
   397  
   398  	return opts
   399  }
   400  
   401  func newDialOptions(options []DialOption) *dialOptions {
   402  	var dopts dialOptions
   403  	for _, option := range options {
   404  		option(&dopts)
   405  	}
   406  	return &dopts
   407  }