Dial options: what Dial sets and what you can override¶
Dial sets exactly one dial option of its own and passes everything else through. This
page states what that option is, how it interacts with yours, and which options are worth
setting.
The only option Dial sets is the transport credentials¶
dialOpts := append([]grpc.DialOption{grpc.WithTransportCredentials(creds)}, opts...)
conn, err := grpc.NewClient(endpoint, dialOpts...)
creds comes from Target.TLS. Nothing else is set: no keepalive, no message-size
limits, no service config, no default call options, no resolver, no balancer. Whatever
the gRPC SDK's own defaults are, that is what you get.
That means you never pass grpc.WithTransportCredentials yourself. Set TLS.Enabled and
TLS.Cert on the Target instead — see Target fields.
Your options are applied after Dial's, so yours win¶
gRPC applies dial options in order and later ones overwrite earlier ones —
grpc.WithTransportCredentials is a plain assignment to the internal options struct.
Dial places its credentials first, so a caller-supplied
grpc.WithTransportCredentials replaces them.
This is a real hole, not a theoretical one. The following dials in cleartext even though the target asked for TLS:
conn, _ := grpcclient.Dial(
grpcclient.Target{Port: port, TLS: gtls.Pair{Enabled: true}},
grpc.WithTransportCredentials(insecure.NewCredentials()), // wins
)
The connection reaches READY against a plaintext server. Dial returns no error and
logs nothing.
Ordering credentials first is the wrong way round for a security default, and this
documentation previously claimed the opposite — that "a caller's option can never silently
downgrade the target's transport security". It can. Until the ordering changes, the rule
to work to is: Target.TLS is the intent, and any transport-credential option you add
overrides it. Do not accept dial options from configuration or from a caller you do not
control.
Options that Dial rejects at dial time¶
Two option combinations make grpc.NewClient fail, so Dial returns an error rather than
a connection:
// grpc: credentials.Bundle may not be used with individual TransportCredentials
grpcclient.Dial(target, grpc.WithCredentialsBundle(b))
// grpc: the credentials require transport level security
// (use grpc.WithTransportCredentials() to set)
grpcclient.Dial(target /* TLS disabled */, grpc.WithPerRPCCredentials(tokenSource))
grpc.WithCredentialsBundle can never be combined with Dial, because Dial always sets
individual transport credentials. grpc.WithPerRPCCredentials works only when
Target.TLS.Enabled is true, because token-bearing credentials refuse to travel over an
insecure connection.
Options worth passing¶
Anything of type grpc.DialOption is accepted. These are the ones that come up:
| Option | Why |
|---|---|
transitgrpc.OTelClientHandler() |
OpenTelemetry client instrumentation, from go/transit |
grpc.WithChainUnaryInterceptor(transitgrpc.CircuitBreakerInterceptor(log, cfg)) |
Fail-fast circuit breaking on unary calls |
grpc.WithChainStreamInterceptor(transitgrpc.CircuitBreakerStreamInterceptor(log, cfg)) |
The same for streams |
grpc.WithKeepaliveParams(...) |
Detect a dead peer; Dial sets no keepalive |
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(n)) |
Raise the 4 MiB receive limit the SDK defaults to |
grpc.WithDefaultServiceConfig(json) |
Retry policy and load balancing; Dial configures neither |
grpc.WithAuthority(name) |
Verify the server certificate against name rather than Target.Host |
grpc.WithUserAgent(s) |
Identify your client in the server's logs |
Dial a service with middleware shows the interceptors composed into one call.
Verifying a certificate under a different name¶
Dial cannot set ServerName, so the certificate is checked against Target.Host. To
dial an address and verify a different name — a load-balancer VIP, or a sidecar on
loopback fronting a named service — pass the authority yourself:
conn, err := grpcclient.Dial(
grpcclient.Target{Port: 8443, TLS: gtls.Pair{Enabled: true, Cert: caPath}},
grpc.WithAuthority("svc.internal"),
)
That dials localhost:8443 and verifies the certificate against svc.internal. Without
it, the handshake fails with x509: certificate is valid for svc.internal, not localhost.