summaryrefslogtreecommitdiff
path: root/libgo/go/net/dial.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/net/dial.go')
-rw-r--r--libgo/go/net/dial.go279
1 files changed, 204 insertions, 75 deletions
diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go
index e6f0436cdd3..cb4ec216d53 100644
--- a/libgo/go/net/dial.go
+++ b/libgo/go/net/dial.go
@@ -21,6 +21,9 @@ type Dialer struct {
//
// The default is no timeout.
//
+ // When dialing a name with multiple IP addresses, the timeout
+ // may be divided between them.
+ //
// With or without a timeout, the operating system may impose
// its own earlier timeout. For instance, TCP timeouts are
// often around 3 minutes.
@@ -38,13 +41,17 @@ type Dialer struct {
// If nil, a local address is automatically chosen.
LocalAddr Addr
- // DualStack allows a single dial to attempt to establish
- // multiple IPv4 and IPv6 connections and to return the first
- // established connection when the network is "tcp" and the
- // destination is a host name that has multiple address family
- // DNS records.
+ // DualStack enables RFC 6555-compliant "Happy Eyeballs" dialing
+ // when the network is "tcp" and the destination is a host name
+ // with both IPv4 and IPv6 addresses. This allows a client to
+ // tolerate networks where one address family is silently broken.
DualStack bool
+ // FallbackDelay specifies the length of time to wait before
+ // spawning a fallback connection, when DualStack is enabled.
+ // If zero, a default delay of 300ms is used.
+ FallbackDelay time.Duration
+
// KeepAlive specifies the keep-alive period for an active
// network connection.
// If zero, keep-alives are not enabled. Network protocols
@@ -54,11 +61,11 @@ type Dialer struct {
// Return either now+Timeout or Deadline, whichever comes first.
// Or zero, if neither is set.
-func (d *Dialer) deadline() time.Time {
+func (d *Dialer) deadline(now time.Time) time.Time {
if d.Timeout == 0 {
return d.Deadline
}
- timeoutDeadline := time.Now().Add(d.Timeout)
+ timeoutDeadline := now.Add(d.Timeout)
if d.Deadline.IsZero() || timeoutDeadline.Before(d.Deadline) {
return timeoutDeadline
} else {
@@ -66,6 +73,38 @@ func (d *Dialer) deadline() time.Time {
}
}
+// partialDeadline returns the deadline to use for a single address,
+// when multiple addresses are pending.
+func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) {
+ if deadline.IsZero() {
+ return deadline, nil
+ }
+ timeRemaining := deadline.Sub(now)
+ if timeRemaining <= 0 {
+ return time.Time{}, errTimeout
+ }
+ // Tentatively allocate equal time to each remaining address.
+ timeout := timeRemaining / time.Duration(addrsRemaining)
+ // If the time per address is too short, steal from the end of the list.
+ const saneMinimum = 2 * time.Second
+ if timeout < saneMinimum {
+ if timeRemaining < saneMinimum {
+ timeout = timeRemaining
+ } else {
+ timeout = saneMinimum
+ }
+ }
+ return now.Add(timeout), nil
+}
+
+func (d *Dialer) fallbackDelay() time.Duration {
+ if d.FallbackDelay > 0 {
+ return d.FallbackDelay
+ } else {
+ return 300 * time.Millisecond
+ }
+}
+
func parseNetwork(net string) (afnet string, proto int, err error) {
i := last(net, ':')
if i < 0 { // no colon
@@ -95,7 +134,7 @@ func parseNetwork(net string) (afnet string, proto int, err error) {
return "", 0, UnknownNetworkError(net)
}
-func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) {
+func resolveAddrList(op, net, addr string, deadline time.Time) (addrList, error) {
afnet, _, err := parseNetwork(net)
if err != nil {
return nil, err
@@ -105,9 +144,13 @@ func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) {
}
switch afnet {
case "unix", "unixgram", "unixpacket":
- return ResolveUnixAddr(afnet, addr)
+ addr, err := ResolveUnixAddr(afnet, addr)
+ if err != nil {
+ return nil, err
+ }
+ return addrList{addr}, nil
}
- return resolveInternetAddr(afnet, addr, deadline)
+ return internetAddrList(afnet, addr, deadline)
}
// Dial connects to the address on the named network.
@@ -150,100 +193,186 @@ func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
return d.Dial(network, address)
}
+// dialContext holds common state for all dial operations.
+type dialContext struct {
+ Dialer
+ network, address string
+ finalDeadline time.Time
+}
+
// Dial connects to the address on the named network.
//
// See func Dial for a description of the network and address
// parameters.
func (d *Dialer) Dial(network, address string) (Conn, error) {
- ra, err := resolveAddr("dial", network, address, d.deadline())
+ finalDeadline := d.deadline(time.Now())
+ addrs, err := resolveAddrList("dial", network, address, finalDeadline)
if err != nil {
- return nil, &OpError{Op: "dial", Net: network, Addr: nil, Err: err}
+ return nil, &OpError{Op: "dial", Net: network, Source: nil, Addr: nil, Err: err}
}
- dialer := func(deadline time.Time) (Conn, error) {
- return dialSingle(network, address, d.LocalAddr, ra.toAddr(), deadline)
+
+ ctx := &dialContext{
+ Dialer: *d,
+ network: network,
+ address: address,
+ finalDeadline: finalDeadline,
}
- if ras, ok := ra.(addrList); ok && d.DualStack && network == "tcp" {
- dialer = func(deadline time.Time) (Conn, error) {
- return dialMulti(network, address, d.LocalAddr, ras, deadline)
- }
+
+ var primaries, fallbacks addrList
+ if d.DualStack && network == "tcp" {
+ primaries, fallbacks = addrs.partition(isIPv4)
+ } else {
+ primaries = addrs
}
- c, err := dial(network, ra.toAddr(), dialer, d.deadline())
+
+ var c Conn
+ if len(fallbacks) == 0 {
+ // dialParallel can accept an empty fallbacks list,
+ // but this shortcut avoids the goroutine/channel overhead.
+ c, err = dialSerial(ctx, primaries, nil)
+ } else {
+ c, err = dialParallel(ctx, primaries, fallbacks)
+ }
+
if d.KeepAlive > 0 && err == nil {
if tc, ok := c.(*TCPConn); ok {
- tc.SetKeepAlive(true)
- tc.SetKeepAlivePeriod(d.KeepAlive)
+ setKeepAlive(tc.fd, true)
+ setKeepAlivePeriod(tc.fd, d.KeepAlive)
testHookSetKeepAlive()
}
}
return c, err
}
-var testHookSetKeepAlive = func() {} // changed by dial_test.go
+// dialParallel races two copies of dialSerial, giving the first a
+// head start. It returns the first established connection and
+// closes the others. Otherwise it returns an error from the first
+// primary address.
+func dialParallel(ctx *dialContext, primaries, fallbacks addrList) (Conn, error) {
+ results := make(chan dialResult) // unbuffered, so dialSerialAsync can detect race loss & cleanup
+ cancel := make(chan struct{})
+ defer close(cancel)
+
+ // Spawn the primary racer.
+ go dialSerialAsync(ctx, primaries, nil, cancel, results)
+
+ // Spawn the fallback racer.
+ fallbackTimer := time.NewTimer(ctx.fallbackDelay())
+ go dialSerialAsync(ctx, fallbacks, fallbackTimer, cancel, results)
-// dialMulti attempts to establish connections to each destination of
-// the list of addresses. It will return the first established
-// connection and close the other connections. Otherwise it returns
-// error on the last attempt.
-func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Conn, error) {
- type racer struct {
- Conn
- error
+ var primaryErr error
+ for nracers := 2; nracers > 0; nracers-- {
+ res := <-results
+ // If we're still waiting for a connection, then hasten the delay.
+ // Otherwise, disable the Timer and let cancel take over.
+ if fallbackTimer.Stop() && res.error != nil {
+ fallbackTimer.Reset(0)
+ }
+ if res.error == nil {
+ return res.Conn, nil
+ }
+ if res.primary {
+ primaryErr = res.error
+ }
}
- // Sig controls the flow of dial results on lane. It passes a
- // token to the next racer and also indicates the end of flow
- // by using closed channel.
- sig := make(chan bool, 1)
- lane := make(chan racer, 1)
- for _, ra := range ras {
- go func(ra Addr) {
- c, err := dialSingle(net, addr, la, ra, deadline)
- if _, ok := <-sig; ok {
- lane <- racer{c, err}
- } else if err == nil {
- // We have to return the resources
- // that belong to the other
- // connections here for avoiding
- // unnecessary resource starvation.
- c.Close()
- }
- }(ra.toAddr())
+ return nil, primaryErr
+}
+
+type dialResult struct {
+ Conn
+ error
+ primary bool
+}
+
+// dialSerialAsync runs dialSerial after some delay, and returns the
+// resulting connection through a channel. When racing two connections,
+// the primary goroutine uses a nil timer to omit the delay.
+func dialSerialAsync(ctx *dialContext, ras addrList, timer *time.Timer, cancel <-chan struct{}, results chan<- dialResult) {
+ if timer != nil {
+ // We're in the fallback goroutine; sleep before connecting.
+ select {
+ case <-timer.C:
+ case <-cancel:
+ return
+ }
}
- defer close(sig)
- lastErr := errTimeout
- nracers := len(ras)
- for nracers > 0 {
- sig <- true
- racer := <-lane
- if racer.error == nil {
- return racer.Conn, nil
+ c, err := dialSerial(ctx, ras, cancel)
+ select {
+ case results <- dialResult{c, err, timer == nil}:
+ // We won the race.
+ case <-cancel:
+ // The other goroutine won the race.
+ if c != nil {
+ c.Close()
+ }
+ }
+}
+
+// dialSerial connects to a list of addresses in sequence, returning
+// either the first successful connection, or the first error.
+func dialSerial(ctx *dialContext, ras addrList, cancel <-chan struct{}) (Conn, error) {
+ var firstErr error // The error from the first address is most relevant.
+
+ for i, ra := range ras {
+ select {
+ case <-cancel:
+ return nil, &OpError{Op: "dial", Net: ctx.network, Source: ctx.LocalAddr, Addr: ra, Err: errCanceled}
+ default:
+ }
+
+ partialDeadline, err := partialDeadline(time.Now(), ctx.finalDeadline, len(ras)-i)
+ if err != nil {
+ // Ran out of time.
+ if firstErr == nil {
+ firstErr = &OpError{Op: "dial", Net: ctx.network, Source: ctx.LocalAddr, Addr: ra, Err: err}
+ }
+ break
}
- lastErr = racer.error
- nracers--
+
+ // dialTCP does not support cancelation (see golang.org/issue/11225),
+ // so if cancel fires, we'll continue trying to connect until the next
+ // timeout, or return a spurious connection for the caller to close.
+ dialer := func(d time.Time) (Conn, error) {
+ return dialSingle(ctx, ra, d)
+ }
+ c, err := dial(ctx.network, ra, dialer, partialDeadline)
+ if err == nil {
+ return c, nil
+ }
+ if firstErr == nil {
+ firstErr = err
+ }
+ }
+
+ if firstErr == nil {
+ firstErr = &OpError{Op: "dial", Net: ctx.network, Source: nil, Addr: nil, Err: errMissingAddress}
}
- return nil, lastErr
+ return nil, firstErr
}
// dialSingle attempts to establish and returns a single connection to
-// the destination address.
-func dialSingle(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) {
+// the destination address. This must be called through the OS-specific
+// dial function, because some OSes don't implement the deadline feature.
+func dialSingle(ctx *dialContext, ra Addr, deadline time.Time) (c Conn, err error) {
+ la := ctx.LocalAddr
if la != nil && la.Network() != ra.Network() {
- return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())}
+ return nil, &OpError{Op: "dial", Net: ctx.network, Source: la, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())}
}
switch ra := ra.(type) {
case *TCPAddr:
la, _ := la.(*TCPAddr)
- c, err = dialTCP(net, la, ra, deadline)
+ c, err = testHookDialTCP(ctx.network, la, ra, deadline)
case *UDPAddr:
la, _ := la.(*UDPAddr)
- c, err = dialUDP(net, la, ra, deadline)
+ c, err = dialUDP(ctx.network, la, ra, deadline)
case *IPAddr:
la, _ := la.(*IPAddr)
- c, err = dialIP(net, la, ra, deadline)
+ c, err = dialIP(ctx.network, la, ra, deadline)
case *UnixAddr:
la, _ := la.(*UnixAddr)
- c, err = dialUnix(net, la, ra, deadline)
+ c, err = dialUnix(ctx.network, la, ra, deadline)
default:
- return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}
+ return nil, &OpError{Op: "dial", Net: ctx.network, Source: la, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: ctx.address}}
}
if err != nil {
return nil, err // c is non-nil interface containing nil pointer
@@ -256,18 +385,18 @@ func dialSingle(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err
// "tcp6", "unix" or "unixpacket".
// See Dial for the syntax of laddr.
func Listen(net, laddr string) (Listener, error) {
- la, err := resolveAddr("listen", net, laddr, noDeadline)
+ addrs, err := resolveAddrList("listen", net, laddr, noDeadline)
if err != nil {
- return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
+ return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: nil, Err: err}
}
var l Listener
- switch la := la.toAddr().(type) {
+ switch la := addrs.first(isIPv4).(type) {
case *TCPAddr:
l, err = ListenTCP(net, la)
case *UnixAddr:
l, err = ListenUnix(net, la)
default:
- return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
+ return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
}
if err != nil {
return nil, err // l is non-nil interface containing nil pointer
@@ -280,12 +409,12 @@ func Listen(net, laddr string) (Listener, error) {
// "udp6", "ip", "ip4", "ip6" or "unixgram".
// See Dial for the syntax of laddr.
func ListenPacket(net, laddr string) (PacketConn, error) {
- la, err := resolveAddr("listen", net, laddr, noDeadline)
+ addrs, err := resolveAddrList("listen", net, laddr, noDeadline)
if err != nil {
- return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
+ return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: nil, Err: err}
}
var l PacketConn
- switch la := la.toAddr().(type) {
+ switch la := addrs.first(isIPv4).(type) {
case *UDPAddr:
l, err = ListenUDP(net, la)
case *IPAddr:
@@ -293,7 +422,7 @@ func ListenPacket(net, laddr string) (PacketConn, error) {
case *UnixAddr:
l, err = ListenUnixgram(net, la)
default:
- return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
+ return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
}
if err != nil {
return nil, err // l is non-nil interface containing nil pointer