in dialer.go [328:467]
func (d *Dialer) Dial(ctx context.Context, icn string, opts ...DialOption) (conn net.Conn, err error) {
select {
case <-d.closed:
return nil, ErrDialerClosed
default:
}
startTime := time.Now()
var endDial trace.EndSpanFunc
ctx, endDial = trace.StartSpan(ctx, "cloud.google.com/go/cloudsqlconn.Dial",
trace.AddInstanceName(icn),
trace.AddDialerID(d.dialerID),
)
defer func() {
go trace.RecordDialError(context.Background(), icn, d.dialerID, err)
endDial(err)
}()
cn, err := d.resolver.Resolve(ctx, icn)
if err != nil {
return nil, err
}
// Log if resolver changed the instance name input string.
if cn.String() != icn {
d.logger.Debugf(ctx, "resolved instance %s to %s", icn, cn)
}
cfg := d.defaultDialConfig
for _, opt := range opts {
opt(&cfg)
}
var endInfo trace.EndSpanFunc
ctx, endInfo = trace.StartSpan(ctx, "cloud.google.com/go/cloudsqlconn/internal.InstanceInfo")
c, err := d.connectionInfoCache(ctx, cn, &cfg.useIAMAuthN)
if err != nil {
endInfo(err)
return nil, err
}
ci, err := c.ConnectionInfo(ctx)
if err != nil {
d.removeCached(ctx, cn, c, err)
endInfo(err)
return nil, err
}
endInfo(err)
// If the client certificate has expired (as when the computer goes to
// sleep, and the refresh cycle cannot run), force a refresh immediately.
// The TLS handshake will not fail on an expired client certificate. It's
// not until the first read where the client cert error will be surfaced.
// So check that the certificate is valid before proceeding.
if !validClientCert(ctx, cn, d.logger, ci.Expiration) {
d.logger.Debugf(ctx, "[%v] Refreshing certificate now", cn.String())
c.ForceRefresh()
// Block on refreshed connection info
ci, err = c.ConnectionInfo(ctx)
if err != nil {
d.removeCached(ctx, cn, c, err)
return nil, err
}
}
var connectEnd trace.EndSpanFunc
ctx, connectEnd = trace.StartSpan(ctx, "cloud.google.com/go/cloudsqlconn/internal.Connect")
defer func() { connectEnd(err) }()
addr, err := ci.Addr(cfg.ipType)
if err != nil {
d.removeCached(ctx, cn, c, err)
return nil, err
}
addr = net.JoinHostPort(addr, serverProxyPort)
f := d.dialFunc
if cfg.dialFunc != nil {
f = cfg.dialFunc
}
d.logger.Debugf(ctx, "[%v] Dialing %v", cn.String(), addr)
conn, err = f(ctx, "tcp", addr)
if err != nil {
d.logger.Debugf(ctx, "[%v] Dialing %v failed: %v", cn.String(), addr, err)
// refresh the instance info in case it caused the connection failure
c.ForceRefresh()
return nil, errtype.NewDialError("failed to dial", cn.String(), err)
}
if c, ok := conn.(*net.TCPConn); ok {
if err := c.SetKeepAlive(true); err != nil {
return nil, errtype.NewDialError("failed to set keep-alive", cn.String(), err)
}
if err := c.SetKeepAlivePeriod(cfg.tcpKeepAlive); err != nil {
return nil, errtype.NewDialError("failed to set keep-alive period", cn.String(), err)
}
}
tlsConn := tls.Client(conn, ci.TLSConfig())
err = tlsConn.HandshakeContext(ctx)
if err != nil {
// TLS handshake errors are fatal and require a refresh. Remove the instance
// from the cache so that future calls to Dial() will block until the
// certificate is refreshed successfully.
d.logger.Debugf(ctx, "[%v] TLS handshake failed: %v", cn.String(), err)
d.removeCached(ctx, cn, c, err)
_ = tlsConn.Close() // best effort close attempt
return nil, errtype.NewDialError("handshake failed", cn.String(), err)
}
latency := time.Since(startTime).Milliseconds()
go func() {
n := atomic.AddUint64(c.openConnsCount, 1)
trace.RecordOpenConnections(ctx, int64(n), d.dialerID, cn.String())
trace.RecordDialLatency(ctx, icn, d.dialerID, latency)
}()
closeFunc := func() {
n := atomic.AddUint64(c.openConnsCount, ^uint64(0)) // c.openConnsCount = c.openConnsCount - 1
trace.RecordOpenConnections(context.Background(), int64(n), d.dialerID, cn.String())
}
errFunc := func(err error) {
// io.EOF occurs when the server closes the connection. This is safe to
// ignore.
if err == io.EOF {
return
}
d.logger.Debugf(ctx, "[%v] IO Error on Read or Write: %v", cn.String(), err)
if d.isTLSError(err) {
// TLS handshake errors are fatal. Remove the instance from the cache
// so that future calls to Dial() will block until the certificate
// is refreshed successfully.
d.removeCached(ctx, cn, c, err)
_ = tlsConn.Close() // best effort close attempt
}
}
iConn := newInstrumentedConn(tlsConn, closeFunc, errFunc, d.dialerID, cn.String())
// If this connection was opened using a Domain Name, then store it for later
// in case it needs to be forcibly closed.
if cn.HasDomainName() {
c.mu.Lock()
c.openConns = append(c.openConns, iConn)
c.mu.Unlock()
}
return iConn, nil
}