diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index d6342b5e5..095f57a8e 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -135,7 +135,7 @@ func NewDirect(opts Options) (*Direct, error) { httpc := opts.HTTPTestClient if httpc == nil { - dialer := netns.Dialer() + dialer := netns.NewDialer() tr := http.DefaultTransport.(*http.Transport).Clone() tr.DialContext = dialer.DialContext tr.ForceAttemptHTTP2 = true diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go index e4d5a1332..e0f4e83cd 100644 --- a/derp/derphttp/derphttp_client.go +++ b/derp/derphttp/derphttp_client.go @@ -103,11 +103,6 @@ func NewClient(privateKey key.Private, serverURL string, logf logger.Logf) (*Cli return c, nil } -type dialer interface { - Dial(network, address string) (net.Conn, error) - DialContext(ctx context.Context, network, address string) (net.Conn, error) -} - // Connect connects or reconnects to the server, unless already connected. // It returns nil if there was already a good connection, or if one was made. func (c *Client) Connect(ctx context.Context) error { @@ -297,18 +292,14 @@ func (c *Client) dialURL(ctx context.Context) (net.Conn, error) { host := c.url.Hostname() hostOrIP := host - var stdDialer dialer = netns.Dialer() - var dialer = stdDialer - if wrapDialer != nil { - dialer = wrapDialer(dialer) - } + dialer := netns.NewDialer() if c.DNSCache != nil { ip, err := c.DNSCache.LookupIP(ctx, host) if err == nil { hostOrIP = ip.String() } - if err != nil && dialer == stdDialer { + if err != nil && netns.IsSOCKSDialer(dialer) { // Return an error if we're not using a dial // proxy that can do DNS lookups for us. return nil, err @@ -387,12 +378,7 @@ func (c *Client) DialRegionTLS(ctx context.Context, reg *tailcfg.DERPRegion) (tl } func (c *Client) dialContext(ctx context.Context, proto, addr string) (net.Conn, error) { - var stdDialer dialer = netns.Dialer() - var dialer = stdDialer - if wrapDialer != nil { - dialer = wrapDialer(dialer) - } - return dialer.DialContext(ctx, proto, addr) + return netns.NewDialer().DialContext(ctx, proto, addr) } // shouldDialProto reports whether an explicitly provided IPv4 or IPv6 @@ -559,7 +545,3 @@ func (c *Client) closeForReconnect(brokenClient *derp.Client) { } var ErrClientClosed = errors.New("derphttp.Client closed") - -// wrapDialer, if non-nil, specifies a function to wrap a dialer in a -// SOCKS-using dialer. It's set conditionally by socks.go. -var wrapDialer func(dialer) dialer diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index abd8fefcc..4fa3389b9 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -250,9 +250,10 @@ func newLogtailTransport(host string) *http.Transport { // Log whenever we dial: tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) { - nd := netns.Dialer() - nd.Timeout = 30 * time.Second - nd.KeepAlive = 30 * time.Second + nd := netns.FromDialer(&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }) t0 := time.Now() c, err := nd.DialContext(ctx, netw, addr) d := time.Since(t0).Round(time.Millisecond) diff --git a/net/netns/netns.go b/net/netns/netns.go index e45680e45..fcb130a64 100644 --- a/net/netns/netns.go +++ b/net/netns/netns.go @@ -9,9 +9,15 @@ // // Despite the name netns, the exact mechanism used differs by // operating system, and perhaps even by version of the OS. +// +// The netns package also handles connecting via SOCKS proxies when +// configured by the environment. package netns -import "net" +import ( + "context" + "net" +) // Listener returns a new net.Listener with its Control hook func // initialized as necessary to run in logical network namespace that @@ -20,9 +26,43 @@ func Listener() *net.ListenConfig { return &net.ListenConfig{Control: control} } -// Dialer returns a new net.Dialer with its Control hook func -// initialized as necessary to run in a logical network namespace that -// doesn't route back into Tailscale. -func Dialer() *net.Dialer { - return &net.Dialer{Control: control} +// NewDialer returns a new Dialer using a net.Dialer with its Control +// hook func initialized as necessary to run in a logical network +// namespace that doesn't route back into Tailscale. It also handles +// using a SOCKS if configured in the environment with ALL_PROXY. +func NewDialer() Dialer { + return FromDialer(new(net.Dialer)) +} + +// FromDialer returns sets d.Control as necessary to run in a logical +// network namespace that doesn't route back into Tailscale. It also +// handles using a SOCKS if configured in the environment with +// ALL_PROXY. +func FromDialer(d *net.Dialer) Dialer { + d.Control = control + if wrapDialer != nil { + return wrapDialer(d) + } + return d +} + +// IsSOCKSDialer reports whether d is SOCKS-proxying dialer as returned by +// NewDialer or FromDialer. +func IsSOCKSDialer(d Dialer) bool { + if d == nil { + return false + } + _, ok := d.(*net.Dialer) + return !ok +} + +// wrapDialer, if non-nil, specifies a function to wrap a dialer in a +// SOCKS-using dialer. It's set conditionally by socks.go. +var wrapDialer func(Dialer) Dialer + +// Dialer is the interface for a dialer that can dial with or without a context. +// It's the type implemented both by net.Dialer and the Go SOCKS dialer. +type Dialer interface { + Dial(network, address string) (net.Conn, error) + DialContext(ctx context.Context, network, address string) (net.Conn, error) } diff --git a/derp/derphttp/socks.go b/net/netns/socks.go similarity index 72% rename from derp/derphttp/socks.go rename to net/netns/socks.go index 24e0fee1b..f1c19f259 100644 --- a/derp/derphttp/socks.go +++ b/net/netns/socks.go @@ -4,7 +4,7 @@ // +build !ios -package derphttp +package netns import "golang.org/x/net/proxy" @@ -12,8 +12,8 @@ func init() { wrapDialer = wrapSocks } -func wrapSocks(d dialer) dialer { - if cd, ok := proxy.FromEnvironmentUsing(d).(dialer); ok { +func wrapSocks(d Dialer) Dialer { + if cd, ok := proxy.FromEnvironmentUsing(d).(Dialer); ok { return cd } return d