diff --git a/cmd/syncthing/usage_report.go b/cmd/syncthing/usage_report.go index 7f21e71da..06a8a2bd7 100644 --- a/cmd/syncthing/usage_report.go +++ b/cmd/syncthing/usage_report.go @@ -20,6 +20,7 @@ import ( "time" "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/upgrade" @@ -261,7 +262,7 @@ func (s *usageReportingService) sendUsageReport() error { if BuildEnv == "android" { // This works around the lack of DNS resolution on Android... :( transp.Dial = func(network, addr string) (net.Conn, error) { - return net.Dial(network, "194.126.249.13:443") + return dialer.Dial(network, "194.126.249.13:443") } } diff --git a/lib/connections/connections_tcp.go b/lib/connections/connections_tcp.go index 1a2b49558..5043c81d6 100644 --- a/lib/connections/connections_tcp.go +++ b/lib/connections/connections_tcp.go @@ -12,6 +12,7 @@ import ( "net/url" "strings" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/osutil" ) @@ -37,13 +38,13 @@ func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) { return nil, err } - conn, err := net.DialTCP("tcp", nil, raddr) + conn, err := dialer.Dial(raddr.Network(), raddr.String()) if err != nil { l.Debugln(err) return nil, err } - err = osutil.SetTCPOptions(conn) + err = osutil.SetTCPOptions(conn.(*net.TCPConn)) if err != nil { l.Infoln(err) } diff --git a/lib/dialer/dialer.go b/lib/dialer/dialer.go new file mode 100644 index 000000000..7d9d5b430 --- /dev/null +++ b/lib/dialer/dialer.go @@ -0,0 +1,88 @@ +// 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" + "os" + "strings" + "time" + + "golang.org/x/net/proxy" + + "github.com/syncthing/syncthing/lib/logger" +) + +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 { + // 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()) + 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()) + } 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/relay/client/client.go b/lib/relay/client/client.go index e86099a1b..aeef8f995 100644 --- a/lib/relay/client/client.go +++ b/lib/relay/client/client.go @@ -9,6 +9,7 @@ import ( "net/url" "time" + "github.com/syncthing/syncthing/lib/dialer" syncthingprotocol "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/relay/protocol" "github.com/syncthing/syncthing/lib/sync" @@ -172,7 +173,7 @@ func (c *ProtocolClient) connect() error { } t0 := time.Now() - tcpConn, err := net.Dial("tcp", c.URI.Host) + tcpConn, err := dialer.Dial("tcp", c.URI.Host) if err != nil { return err } diff --git a/lib/relay/client/methods.go b/lib/relay/client/methods.go index 46afc7602..92a7e9b07 100644 --- a/lib/relay/client/methods.go +++ b/lib/relay/client/methods.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/syncthing/syncthing/lib/dialer" syncthingprotocol "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/relay/protocol" ) @@ -20,10 +21,12 @@ func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs [ return protocol.SessionInvitation{}, fmt.Errorf("Unsupported relay scheme: %v", uri.Scheme) } - conn, err := tls.Dial("tcp", uri.Host, configForCerts(certs)) + rconn, err := dialer.Dial("tcp", uri.Host) if err != nil { return protocol.SessionInvitation{}, err } + + conn := tls.Client(rconn, configForCerts(certs)) conn.SetDeadline(time.Now().Add(10 * time.Second)) if err := performHandshakeAndValidation(conn, uri); err != nil { @@ -63,7 +66,7 @@ func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs [ func JoinSession(invitation protocol.SessionInvitation) (net.Conn, error) { addr := net.JoinHostPort(net.IP(invitation.Address).String(), strconv.Itoa(int(invitation.Port))) - conn, err := net.Dial("tcp", addr) + conn, err := dialer.Dial("tcp", addr) if err != nil { return nil, err } diff --git a/lib/upnp/upnp.go b/lib/upnp/upnp.go index 151c29991..f8e4cf9e1 100644 --- a/lib/upnp/upnp.go +++ b/lib/upnp/upnp.go @@ -25,6 +25,7 @@ import ( "strings" "time" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/sync" ) @@ -289,7 +290,7 @@ func parseResponse(deviceType string, resp []byte) (IGD, error) { } func localIP(url *url.URL) (string, error) { - conn, err := net.Dial("tcp", url.Host) + conn, err := dialer.Dial("tcp", url.Host) if err != nil { return "", err }