From a4b8c2298af819b1e4269f0ddd41170ae23a86fc Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Thu, 26 Nov 2015 23:31:37 +0000 Subject: [PATCH] Add dialer.DialTimeout support --- lib/dialer/dialer.go | 102 ------------------------------ lib/dialer/internal.go | 140 +++++++++++++++++++++++++++++++++++++++++ lib/dialer/public.go | 48 ++++++++++++++ 3 files changed, 188 insertions(+), 102 deletions(-) delete mode 100644 lib/dialer/dialer.go create mode 100644 lib/dialer/internal.go create mode 100644 lib/dialer/public.go diff --git a/lib/dialer/dialer.go b/lib/dialer/dialer.go deleted file mode 100644 index 5294b7da4..000000000 --- a/lib/dialer/dialer.go +++ /dev/null @@ -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 -} diff --git a/lib/dialer/internal.go b/lib/dialer/internal.go new file mode 100644 index 000000000..3948b0e8e --- /dev/null +++ b/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 +} diff --git a/lib/dialer/public.go b/lib/dialer/public.go new file mode 100644 index 000000000..4bf17c5b9 --- /dev/null +++ b/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) +}