|
@@ -20,8 +20,12 @@ import (
|
|
|
|
|
|
|
|
"inet.af/netaddr"
|
|
"inet.af/netaddr"
|
|
|
"tailscale.com/net/dnscache"
|
|
"tailscale.com/net/dnscache"
|
|
|
|
|
+ "tailscale.com/net/interfaces"
|
|
|
"tailscale.com/net/netknob"
|
|
"tailscale.com/net/netknob"
|
|
|
|
|
+ "tailscale.com/net/netns"
|
|
|
|
|
+ "tailscale.com/types/logger"
|
|
|
"tailscale.com/types/netmap"
|
|
"tailscale.com/types/netmap"
|
|
|
|
|
+ "tailscale.com/util/mak"
|
|
|
"tailscale.com/wgengine/monitor"
|
|
"tailscale.com/wgengine/monitor"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -30,6 +34,7 @@ import (
|
|
|
// (TUN, netstack), the OS network sandboxing style (macOS/iOS
|
|
// (TUN, netstack), the OS network sandboxing style (macOS/iOS
|
|
|
// Extension, none), user-selected route acceptance prefs, etc.
|
|
// Extension, none), user-selected route acceptance prefs, etc.
|
|
|
type Dialer struct {
|
|
type Dialer struct {
|
|
|
|
|
+ Logf logger.Logf
|
|
|
// UseNetstackForIP if non-nil is whether NetstackDialTCP (if
|
|
// UseNetstackForIP if non-nil is whether NetstackDialTCP (if
|
|
|
// it's non-nil) should be used to dial the provided IP.
|
|
// it's non-nil) should be used to dial the provided IP.
|
|
|
UseNetstackForIP func(netaddr.IP) bool
|
|
UseNetstackForIP func(netaddr.IP) bool
|
|
@@ -46,12 +51,33 @@ type Dialer struct {
|
|
|
peerDialerOnce sync.Once
|
|
peerDialerOnce sync.Once
|
|
|
peerDialer *net.Dialer
|
|
peerDialer *net.Dialer
|
|
|
|
|
|
|
|
- mu sync.Mutex
|
|
|
|
|
- dns dnsMap
|
|
|
|
|
- tunName string // tun device name
|
|
|
|
|
- linkMon *monitor.Mon
|
|
|
|
|
- exitDNSDoHBase string // non-empty if DoH-proxying exit node in use; base URL+path (without '?')
|
|
|
|
|
- dnsCache *dnscache.MessageCache // nil until first first non-empty SetExitDNSDoH
|
|
|
|
|
|
|
+ netnsDialerOnce sync.Once
|
|
|
|
|
+ netnsDialer netns.Dialer
|
|
|
|
|
+
|
|
|
|
|
+ mu sync.Mutex
|
|
|
|
|
+ closed bool
|
|
|
|
|
+ dns dnsMap
|
|
|
|
|
+ tunName string // tun device name
|
|
|
|
|
+ linkMon *monitor.Mon
|
|
|
|
|
+ linkMonUnregister func()
|
|
|
|
|
+ exitDNSDoHBase string // non-empty if DoH-proxying exit node in use; base URL+path (without '?')
|
|
|
|
|
+ dnsCache *dnscache.MessageCache // nil until first first non-empty SetExitDNSDoH
|
|
|
|
|
+ nextSysConnID int
|
|
|
|
|
+ activeSysConns map[int]net.Conn // active connections not yet closed
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// sysConn wraps a net.Conn that was created using d.SystemDial.
|
|
|
|
|
+// It exists to track which connections are still open, and should be
|
|
|
|
|
+// closed on major link changes.
|
|
|
|
|
+type sysConn struct {
|
|
|
|
|
+ net.Conn
|
|
|
|
|
+ id int
|
|
|
|
|
+ d *Dialer
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c sysConn) Close() error {
|
|
|
|
|
+ c.d.closeSysConn(c.id)
|
|
|
|
|
+ return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// SetTUNName sets the name of the tun device in use ("tailscale0", "utun6",
|
|
// SetTUNName sets the name of the tun device in use ("tailscale0", "utun6",
|
|
@@ -91,10 +117,53 @@ func (d *Dialer) SetExitDNSDoH(doh string) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (d *Dialer) Close() error {
|
|
|
|
|
+ d.mu.Lock()
|
|
|
|
|
+ defer d.mu.Unlock()
|
|
|
|
|
+ d.closed = true
|
|
|
|
|
+ if d.linkMonUnregister != nil {
|
|
|
|
|
+ d.linkMonUnregister()
|
|
|
|
|
+ d.linkMonUnregister = nil
|
|
|
|
|
+ }
|
|
|
|
|
+ for _, c := range d.activeSysConns {
|
|
|
|
|
+ c.Close()
|
|
|
|
|
+ }
|
|
|
|
|
+ d.activeSysConns = nil
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (d *Dialer) SetLinkMonitor(mon *monitor.Mon) {
|
|
func (d *Dialer) SetLinkMonitor(mon *monitor.Mon) {
|
|
|
d.mu.Lock()
|
|
d.mu.Lock()
|
|
|
defer d.mu.Unlock()
|
|
defer d.mu.Unlock()
|
|
|
|
|
+ if d.linkMonUnregister != nil {
|
|
|
|
|
+ go d.linkMonUnregister()
|
|
|
|
|
+ d.linkMonUnregister = nil
|
|
|
|
|
+ }
|
|
|
d.linkMon = mon
|
|
d.linkMon = mon
|
|
|
|
|
+ d.linkMonUnregister = d.linkMon.RegisterChangeCallback(d.linkChanged)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (d *Dialer) linkChanged(major bool, state *interfaces.State) {
|
|
|
|
|
+ if !major {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ d.mu.Lock()
|
|
|
|
|
+ defer d.mu.Unlock()
|
|
|
|
|
+ for id, c := range d.activeSysConns {
|
|
|
|
|
+ go c.Close()
|
|
|
|
|
+ delete(d.activeSysConns, id)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (d *Dialer) closeSysConn(id int) {
|
|
|
|
|
+ d.mu.Lock()
|
|
|
|
|
+ defer d.mu.Unlock()
|
|
|
|
|
+ c, ok := d.activeSysConns[id]
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ delete(d.activeSysConns, id)
|
|
|
|
|
+ go c.Close() // ignore the error
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (d *Dialer) interfaceIndexLocked(ifName string) (index int, ok bool) {
|
|
func (d *Dialer) interfaceIndexLocked(ifName string) (index int, ok bool) {
|
|
@@ -197,6 +266,42 @@ func ipNetOfNetwork(n string) string {
|
|
|
return "ip"
|
|
return "ip"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// SystemDial connects to the provided network address without going over
|
|
|
|
|
+// Tailscale. It prefers going over the default interface and closes existing
|
|
|
|
|
+// connections if the default interface changes. It is used to connect to
|
|
|
|
|
+// Control and (in the future, as of 2022-04-27) DERPs..
|
|
|
|
|
+func (d *Dialer) SystemDial(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
|
+ d.mu.Lock()
|
|
|
|
|
+ closed := d.closed
|
|
|
|
|
+ d.mu.Unlock()
|
|
|
|
|
+ if closed {
|
|
|
|
|
+ return nil, net.ErrClosed
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ d.netnsDialerOnce.Do(func() {
|
|
|
|
|
+ logf := d.Logf
|
|
|
|
|
+ if logf == nil {
|
|
|
|
|
+ logf = logger.Discard
|
|
|
|
|
+ }
|
|
|
|
|
+ d.netnsDialer = netns.NewDialer(logf)
|
|
|
|
|
+ })
|
|
|
|
|
+ c, err := d.netnsDialer.DialContext(ctx, network, addr)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ d.mu.Lock()
|
|
|
|
|
+ defer d.mu.Unlock()
|
|
|
|
|
+ id := d.nextSysConnID
|
|
|
|
|
+ d.nextSysConnID++
|
|
|
|
|
+ mak.Set(&d.activeSysConns, id, c)
|
|
|
|
|
+
|
|
|
|
|
+ return sysConn{
|
|
|
|
|
+ id: id,
|
|
|
|
|
+ d: d,
|
|
|
|
|
+ Conn: c,
|
|
|
|
|
+ }, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// UserDial connects to the provided network address as if a user were initiating the dial.
|
|
// UserDial connects to the provided network address as if a user were initiating the dial.
|
|
|
// (e.g. from a SOCKS or HTTP outbound proxy)
|
|
// (e.g. from a SOCKS or HTTP outbound proxy)
|
|
|
func (d *Dialer) UserDial(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
func (d *Dialer) UserDial(ctx context.Context, network, addr string) (net.Conn, error) {
|