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:
committed by
Audrius Butkevicius
parent
4e151d380c
commit
1bae4b7f50
23
lib/dialer/debug.go
Normal file
23
lib/dialer/debug.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2019 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 https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
l = logger.DefaultLogger.NewFacility("dialer", "Dialing connections")
|
||||
// To run before init() of other files that log on init.
|
||||
_ = func() error {
|
||||
l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all")
|
||||
return nil
|
||||
}()
|
||||
)
|
||||
@@ -7,34 +7,24 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
l = logger.DefaultLogger.NewFacility("dialer", "Dialing connections")
|
||||
proxyDialer proxy.Dialer
|
||||
usingProxy bool
|
||||
noFallback = os.Getenv("ALL_PROXY_NO_FALLBACK") != ""
|
||||
noFallback = os.Getenv("ALL_PROXY_NO_FALLBACK") != ""
|
||||
)
|
||||
|
||||
type dialFunc func(network, addr string) (net.Conn, error)
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialerType("socks", socksDialerFunction)
|
||||
proxyDialer = getDialer(proxy.Direct)
|
||||
usingProxy = proxyDialer != proxy.Direct
|
||||
|
||||
if usingProxy {
|
||||
if proxyDialer := proxy.FromEnvironment(); proxyDialer != proxy.Direct {
|
||||
http.DefaultTransport = &http.Transport{
|
||||
Dial: Dial,
|
||||
DialContext: DialContext,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
@@ -55,31 +45,6 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
SetTCPOptions(conn)
|
||||
return dialerConn{
|
||||
conn, newDialerAddr(network, addr),
|
||||
}, nil
|
||||
}
|
||||
l.Debugf("Dialing %s address %s via proxy - error %s", network, addr, err)
|
||||
|
||||
if noFallback {
|
||||
return conn, 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())
|
||||
SetTCPOptions(conn)
|
||||
} else {
|
||||
l.Debugf("Dialing %s address %s via fallback - error %s", network, addr, err)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// This is a rip off of proxy.FromURL for "socks" URL scheme
|
||||
func socksDialerFunction(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
|
||||
var auth *proxy.Auth
|
||||
@@ -93,67 +58,3 @@ func socksDialerFunction(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error)
|
||||
|
||||
return proxy.SOCKS5("tcp", u.Host, auth, forward)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user