all: Use context in lib/dialer (#6177)

* all: Use context in lib/dialer

* a bit slimmer

* https://github.com/syncthing/syncthing/pull/5753

* bot

* missed adding debug.go

* errors.Cause

* simultaneous dialing

* anti-leak
This commit is contained in:
Simon Frei
2019-11-26 08:39:51 +01:00
committed by Audrius Butkevicius
parent 4e151d380c
commit 1bae4b7f50
24 changed files with 175 additions and 204 deletions

View File

@@ -7,49 +7,18 @@
package dialer
import (
"context"
"errors"
"fmt"
"net"
"time"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"golang.org/x/net/proxy"
)
// Dial tries dialing via proxy if a proxy is configured, and falls back to
// a direct connection if no proxy is defined, or connecting via proxy fails.
func Dial(network, addr string) (net.Conn, error) {
if usingProxy {
return dialWithFallback(proxyDialer.Dial, net.Dial, network, addr)
}
return net.Dial(network, addr)
}
// DialTimeout tries dialing via proxy with a timeout if a proxy is configured,
// and falls back to a direct connection if no proxy is defined, or connecting
// via proxy fails. The timeout can potentially be applied twice, once trying
// to connect via the proxy connection, and second time trying to connect
// directly.
func DialTimeout(network, addr string, timeout time.Duration) (net.Conn, error) {
if usingProxy {
// Because the proxy package is poorly structured, we have to
// construct a struct that matches proxy.Dialer but has a timeout
// and reconstrcut the proxy dialer using that, in order to be able to
// set a timeout.
dd := &timeoutDirectDialer{
timeout: timeout,
}
// Check if the dialer we are getting is not timeoutDirectDialer we just
// created. It could happen that usingProxy is true, but getDialer
// returns timeoutDirectDialer due to env vars changing.
if timeoutProxyDialer := getDialer(dd); timeoutProxyDialer != dd {
directDialFunc := func(inetwork, iaddr string) (net.Conn, error) {
return net.DialTimeout(inetwork, iaddr, timeout)
}
return dialWithFallback(timeoutProxyDialer.Dial, directDialFunc, network, addr)
}
}
return net.DialTimeout(network, addr, timeout)
}
var errUnexpectedInterfaceType = errors.New("unexpected interface type")
// SetTCPOptions sets our default TCP options on a TCP connection, possibly
// digging through dialerConn to extract the *net.TCPConn
@@ -70,10 +39,6 @@ func SetTCPOptions(conn net.Conn) error {
return err
}
return nil
case dialerConn:
return SetTCPOptions(conn.Conn)
default:
return fmt.Errorf("unknown connection type %T", conn)
}
@@ -89,11 +54,54 @@ func SetTrafficClass(conn net.Conn, class int) error {
return e1
}
return e2
case dialerConn:
return SetTrafficClass(conn.Conn, class)
default:
return fmt.Errorf("unknown connection type %T", conn)
}
}
func dialContextWithFallback(ctx context.Context, fallback proxy.ContextDialer, network, addr string) (net.Conn, error) {
dialer, ok := proxy.FromEnvironment().(proxy.ContextDialer)
if !ok {
return nil, errUnexpectedInterfaceType
}
if dialer == proxy.Direct {
return fallback.DialContext(ctx, network, addr)
}
if noFallback {
return dialer.DialContext(ctx, network, addr)
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var proxyConn, fallbackConn net.Conn
var proxyErr, fallbackErr error
proxyDone := make(chan struct{})
fallbackDone := make(chan struct{})
go func() {
proxyConn, proxyErr = dialer.DialContext(ctx, network, addr)
close(proxyDone)
}()
go func() {
fallbackConn, fallbackErr = fallback.DialContext(ctx, network, addr)
close(fallbackDone)
}()
<-proxyDone
if proxyErr == nil {
go func() {
<-fallbackDone
if fallbackErr == nil {
fallbackConn.Close()
}
}()
return proxyConn, nil
}
<-fallbackDone
return fallbackConn, fallbackErr
}
// DialContext dials via context and/or directly, depending on how it is configured.
// If dialing via proxy and allowing fallback, dialing for both happens simultaneously
// and the proxy connection is returned if successful.
func DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return dialContextWithFallback(ctx, proxy.Direct, network, addr)
}