2021-11-30 23:53:34 +00:00
|
|
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// Package tsdial provides a Dialer type that can dial out of tailscaled.
|
|
|
|
package tsdial
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"net"
|
2021-12-01 04:39:12 +00:00
|
|
|
"net/http"
|
2021-11-30 23:53:34 +00:00
|
|
|
"sync"
|
2021-12-01 04:39:12 +00:00
|
|
|
"sync/atomic"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
2021-11-30 23:53:34 +00:00
|
|
|
|
|
|
|
"inet.af/netaddr"
|
2021-12-01 04:39:12 +00:00
|
|
|
"tailscale.com/net/netknob"
|
2021-11-30 23:53:34 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Dialer dials out of tailscaled, while taking care of details while
|
|
|
|
// handling the dozens of edge cases depending on the server mode
|
|
|
|
// (TUN, netstack), the OS network sandboxing style (macOS/iOS
|
|
|
|
// Extension, none), user-selected route acceptance prefs, etc.
|
|
|
|
type Dialer struct {
|
|
|
|
// UseNetstackForIP if non-nil is whether NetstackDialTCP (if
|
|
|
|
// it's non-nil) should be used to dial the provided IP.
|
|
|
|
UseNetstackForIP func(netaddr.IP) bool
|
|
|
|
|
|
|
|
// NetstackDialTCP dials the provided IPPort using netstack.
|
|
|
|
// If nil, it's not used.
|
|
|
|
NetstackDialTCP func(context.Context, netaddr.IPPort) (net.Conn, error)
|
|
|
|
|
2021-12-01 04:39:12 +00:00
|
|
|
peerDialControlFuncAtomic atomic.Value // of func() func(network, address string, c syscall.RawConn) error
|
|
|
|
|
|
|
|
peerClientOnce sync.Once
|
|
|
|
peerClient *http.Client
|
|
|
|
|
2021-11-30 23:53:34 +00:00
|
|
|
mu sync.Mutex
|
|
|
|
dns DNSMap
|
|
|
|
}
|
|
|
|
|
2021-12-01 04:39:12 +00:00
|
|
|
// PeerDialControlFunc returns a function
|
|
|
|
// that can assigned to net.Dialer.Control to set sockopts or whatnot
|
|
|
|
// to make a dial escape the current platform's network sandbox.
|
|
|
|
//
|
|
|
|
// On many platforms the returned func will be nil.
|
|
|
|
//
|
|
|
|
// Notably, this is non-nil on iOS and macOS when run as a Network or
|
|
|
|
// System Extension (the GUI variants).
|
|
|
|
func (d *Dialer) PeerDialControlFunc() func(network, address string, c syscall.RawConn) error {
|
|
|
|
gf, _ := d.peerDialControlFuncAtomic.Load().(func() func(network, address string, c syscall.RawConn) error)
|
|
|
|
if gf == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return gf()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetPeerDialControlFuncGetter sets a function that returns, for the
|
|
|
|
// current network configuration at the time it's called, a function
|
|
|
|
// that can assigned to net.Dialer.Control to set sockopts or whatnot
|
|
|
|
// to make a dial escape the current platform's network sandbox.
|
|
|
|
func (d *Dialer) SetPeerDialControlFuncGetter(getFunc func() func(network, address string, c syscall.RawConn) error) {
|
|
|
|
d.peerDialControlFuncAtomic.Store(getFunc)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetDNSMap sets the current map of DNS names learned from the netmap.
|
2021-11-30 23:53:34 +00:00
|
|
|
func (d *Dialer) SetDNSMap(m DNSMap) {
|
2021-12-01 04:39:12 +00:00
|
|
|
// TODO(bradfitz): update this to be aware of DNSConfig
|
|
|
|
// ExtraNames and CertDomains.
|
2021-11-30 23:53:34 +00:00
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
d.dns = m
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dialer) resolve(ctx context.Context, addr string) (netaddr.IPPort, error) {
|
|
|
|
d.mu.Lock()
|
|
|
|
dns := d.dns
|
|
|
|
d.mu.Unlock()
|
|
|
|
return dns.Resolve(ctx, addr)
|
|
|
|
}
|
|
|
|
|
2021-12-01 04:39:12 +00:00
|
|
|
// UserDial connects to the provided network address as if a user were initiating the dial.
|
|
|
|
// (e.g. from a SOCKS or HTTP outbound proxy)
|
|
|
|
func (d *Dialer) UserDial(ctx context.Context, network, addr string) (net.Conn, error) {
|
2021-11-30 23:53:34 +00:00
|
|
|
ipp, err := d.resolve(ctx, addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if d.UseNetstackForIP != nil && d.UseNetstackForIP(ipp.IP()) {
|
|
|
|
if d.NetstackDialTCP == nil {
|
|
|
|
return nil, errors.New("Dialer not initialized correctly")
|
|
|
|
}
|
|
|
|
return d.NetstackDialTCP(ctx, ipp)
|
|
|
|
}
|
|
|
|
// TODO(bradfitz): netns, etc
|
|
|
|
var stdDialer net.Dialer
|
|
|
|
return stdDialer.DialContext(ctx, network, ipp.String())
|
|
|
|
}
|
2021-12-01 04:39:12 +00:00
|
|
|
|
|
|
|
// PeerAPIHTTPClient returns an HTTP Client to call peers' peerapi
|
|
|
|
// endpoints. //
|
|
|
|
// The returned Client must not be mutated; it's owned by the Dialer
|
|
|
|
// and shared by callers.
|
|
|
|
func (d *Dialer) PeerAPIHTTPClient() *http.Client {
|
|
|
|
d.peerClientOnce.Do(func() {
|
|
|
|
t := http.DefaultTransport.(*http.Transport).Clone()
|
|
|
|
t.Dial = nil
|
|
|
|
dialer := net.Dialer{
|
|
|
|
Timeout: 30 * time.Second,
|
|
|
|
KeepAlive: netknob.PlatformTCPKeepAlive(),
|
|
|
|
Control: d.PeerDialControlFunc(),
|
|
|
|
}
|
|
|
|
t.DialContext = dialer.DialContext
|
|
|
|
d.peerClient = &http.Client{Transport: t}
|
|
|
|
})
|
|
|
|
return d.peerClient
|
|
|
|
}
|
|
|
|
|
|
|
|
// PeerAPITransport returns a Transport to call peers' peerapi
|
|
|
|
// endpoints.
|
|
|
|
//
|
|
|
|
// The returned value must not be mutated; it's owned by the Dialer
|
|
|
|
// and shared by callers.
|
|
|
|
func (d *Dialer) PeerAPITransport() *http.Transport {
|
|
|
|
return d.PeerAPIHTTPClient().Transport.(*http.Transport)
|
|
|
|
}
|