Browse Source

Add dialer.DialTimeout support

Audrius Butkevicius 10 years ago
parent
commit
a4b8c2298a
3 changed files with 188 additions and 102 deletions
  1. 0 102
      lib/dialer/dialer.go
  2. 140 0
      lib/dialer/internal.go
  3. 48 0
      lib/dialer/public.go

+ 0 - 102
lib/dialer/dialer.go

@@ -1,102 +0,0 @@
-// Copyright (C) 2015 The Syncthing Authors.
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this file,
-// You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package dialer
-
-import (
-	"net"
-	"net/http"
-	"os"
-	"strings"
-	"time"
-
-	"golang.org/x/net/proxy"
-
-	"github.com/syncthing/syncthing/lib/logger"
-	"github.com/syncthing/syncthing/lib/osutil"
-)
-
-var (
-	l          = logger.DefaultLogger.NewFacility("dialer", "Dialing connections")
-	dialer     = proxy.FromEnvironment()
-	usingProxy = dialer != proxy.Direct
-)
-
-func init() {
-	l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all")
-	if usingProxy {
-		http.DefaultTransport = &http.Transport{
-			Dial:                Dial,
-			Proxy:               http.ProxyFromEnvironment,
-			TLSHandshakeTimeout: 10 * time.Second,
-		}
-
-		// Defer this, so that logging gets setup.
-		go func() {
-			time.Sleep(500 * time.Millisecond)
-			l.Infoln("Proxy settings detected")
-		}()
-	}
-}
-
-// 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 {
-		conn, err := dialer.Dial(network, addr)
-		if err == nil {
-			l.Debugf("Dialing %s address %s via proxy - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr())
-			if tcpconn, ok := conn.(*net.TCPConn); ok {
-				osutil.SetTCPOptions(tcpconn)
-			}
-			return dialerConn{
-				conn, newDialerAddr(network, addr),
-			}, nil
-		}
-		l.Debugf("Dialing %s address %s via proxy - error %s", network, addr, err)
-	}
-
-	conn, err := proxy.Direct.Dial(network, addr)
-	if err == nil {
-		l.Debugf("Dialing %s address %s directly - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr())
-		if tcpconn, ok := conn.(*net.TCPConn); ok {
-			osutil.SetTCPOptions(tcpconn)
-		}
-	} else {
-		l.Debugf("Dialing %s address %s directly - error %s", network, addr, err)
-	}
-	return conn, err
-}
-
-type dialerConn struct {
-	net.Conn
-	addr net.Addr
-}
-
-func (c dialerConn) RemoteAddr() net.Addr {
-	return c.addr
-}
-
-func newDialerAddr(network, addr string) net.Addr {
-	netaddr, err := net.ResolveIPAddr(network, addr)
-	if err == nil {
-		return netaddr
-	}
-	return fallbackAddr{network, addr}
-}
-
-type fallbackAddr struct {
-	network string
-	addr    string
-}
-
-func (a fallbackAddr) Network() string {
-	return a.network
-}
-
-func (a fallbackAddr) String() string {
-	return a.addr
-}

+ 140 - 0
lib/dialer/internal.go

@@ -0,0 +1,140 @@
+// Copyright (C) 2015 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package dialer
+
+import (
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"strings"
+	"time"
+
+	"golang.org/x/net/proxy"
+
+	"github.com/syncthing/syncthing/lib/logger"
+	"github.com/syncthing/syncthing/lib/osutil"
+)
+
+var (
+	l           = logger.DefaultLogger.NewFacility("dialer", "Dialing connections")
+	proxyDialer = getDialer(proxy.Direct)
+	usingProxy  = proxyDialer != proxy.Direct
+)
+
+type dialFunc func(network, addr string) (net.Conn, error)
+
+func init() {
+	l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all")
+	if usingProxy {
+		http.DefaultTransport = &http.Transport{
+			Dial:                Dial,
+			Proxy:               http.ProxyFromEnvironment,
+			TLSHandshakeTimeout: 10 * time.Second,
+		}
+
+		// Defer this, so that logging gets setup.
+		go func() {
+			time.Sleep(500 * time.Millisecond)
+			l.Infoln("Proxy settings detected")
+		}()
+	} else {
+		go func() {
+			time.Sleep(500 * time.Millisecond)
+			l.Debugln("Dialer logging disabled, as no proxy was detected")
+		}()
+	}
+}
+
+func dialWithFallback(proxyDialFunc dialFunc, fallbackDialFunc dialFunc, network, addr string) (net.Conn, error) {
+	conn, err := proxyDialFunc(network, addr)
+	if err == nil {
+		l.Debugf("Dialing %s address %s via proxy - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr())
+		if tcpconn, ok := conn.(*net.TCPConn); ok {
+			osutil.SetTCPOptions(tcpconn)
+		}
+		return dialerConn{
+			conn, newDialerAddr(network, addr),
+		}, nil
+	}
+	l.Debugf("Dialing %s address %s via proxy - error %s", network, addr, err)
+
+	conn, err = fallbackDialFunc(network, addr)
+	if err == nil {
+		l.Debugf("Dialing %s address %s via fallback - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr())
+		if tcpconn, ok := conn.(*net.TCPConn); ok {
+			osutil.SetTCPOptions(tcpconn)
+		}
+	} else {
+		l.Debugf("Dialing %s address %s via fallback - error %s", network, addr, err)
+	}
+	return conn, err
+}
+
+// This is a rip off of proxy.FromEnvironment with a custom forward dialer
+func getDialer(forward proxy.Dialer) proxy.Dialer {
+	allProxy := os.Getenv("all_proxy")
+	if len(allProxy) == 0 {
+		return forward
+	}
+
+	proxyURL, err := url.Parse(allProxy)
+	if err != nil {
+		return forward
+	}
+	prxy, err := proxy.FromURL(proxyURL, forward)
+	if err != nil {
+		return forward
+	}
+
+	noProxy := os.Getenv("no_proxy")
+	if len(noProxy) == 0 {
+		return prxy
+	}
+
+	perHost := proxy.NewPerHost(prxy, forward)
+	perHost.AddFromString(noProxy)
+	return perHost
+}
+
+type timeoutDirectDialer struct {
+	timeout time.Duration
+}
+
+func (d *timeoutDirectDialer) Dial(network, addr string) (net.Conn, error) {
+	return net.DialTimeout(network, addr, d.timeout)
+}
+
+type dialerConn struct {
+	net.Conn
+	addr net.Addr
+}
+
+func (c dialerConn) RemoteAddr() net.Addr {
+	return c.addr
+}
+
+func newDialerAddr(network, addr string) net.Addr {
+	netaddr, err := net.ResolveIPAddr(network, addr)
+	if err == nil {
+		return netaddr
+	}
+	return fallbackAddr{network, addr}
+}
+
+type fallbackAddr struct {
+	network string
+	addr    string
+}
+
+func (a fallbackAddr) Network() string {
+	return a.network
+}
+
+func (a fallbackAddr) String() string {
+	return a.addr
+}

+ 48 - 0
lib/dialer/public.go

@@ -0,0 +1,48 @@
+// Copyright (C) 2015 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package dialer
+
+import (
+	"net"
+	"time"
+)
+
+// 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)
+}