Просмотр исходного кода

net/socks5/tssocks, wgengine: permit SOCKS through subnet routers/exit nodes

Fixes #1970

Change-Id: Ibef45e8796e1d9625716d72539c96d1dbf7b1f76
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick 4 лет назад
Родитель
Сommit
bb91cfeae7
5 измененных файлов с 62 добавлено и 51 удалено
  1. 19 8
      net/socks5/tssocks/tssocks.go
  2. 4 7
      wgengine/pendopen.go
  3. 12 34
      wgengine/userspace.go
  4. 5 0
      wgengine/watchdog.go
  5. 22 2
      wgengine/wgengine.go

+ 19 - 8
net/socks5/tssocks/tssocks.go

@@ -12,7 +12,6 @@ import (
 
 	"inet.af/netaddr"
 	"tailscale.com/net/socks5"
-	"tailscale.com/net/tsaddr"
 	"tailscale.com/types/logger"
 	"tailscale.com/types/netmap"
 	"tailscale.com/wgengine"
@@ -27,7 +26,7 @@ import (
 //
 // If ns is non-nil, it is used for dialing when needed.
 func NewServer(logf logger.Logf, e wgengine.Engine, ns *netstack.Impl) *socks5.Server {
-	d := &dialer{ns: ns}
+	d := &dialer{ns: ns, eng: e}
 	e.AddNetworkMapCallback(d.onNewNetmap)
 	return &socks5.Server{
 		Logf:   logf,
@@ -37,7 +36,8 @@ func NewServer(logf logger.Logf, e wgengine.Engine, ns *netstack.Impl) *socks5.S
 
 // dialer is the Tailscale SOCKS5 dialer.
 type dialer struct {
-	ns *netstack.Impl
+	ns  *netstack.Impl
+	eng wgengine.Engine
 
 	mu  sync.Mutex
 	dns netstack.DNSMap
@@ -69,11 +69,22 @@ func (d *dialer) DialContext(ctx context.Context, network, addr string) (net.Con
 }
 
 func (d *dialer) useNetstackForIP(ip netaddr.IP) bool {
-	if d.ns == nil {
+	if d.ns == nil || !d.ns.ProcessLocalIPs {
+		// If netstack isn't used at all (nil), then obviously don't use it.
+		//
+		// But the ProcessLocalIPs check is more subtle: it really means
+		// whether we should use netstack for incoming traffic to ourselves.
+		// It's only ever true if we're running in full netstack mode (no TUN),
+		// so we can also use it as a proxy here for whether TUN is available.
+		// If it's false, there's tun and OS routes to things we need,
+		// so we don't want to dial with netstack.
 		return false
 	}
-	// TODO(bradfitz): this isn't exactly right.
-	// We should also support subnets when the
-	// prefs are configured as such.
-	return tsaddr.IsTailscaleIP(ip)
+	// Otherwise, we're in netstack mode, so dial via netstack if there's
+	// any peer handling that IP (including exit nodes).
+	//
+	// Otherwise assume it's something else (e.g. dialing
+	// google.com:443 via SOCKS) that the caller can dial directly.
+	_, ok := d.eng.PeerForIP(ip)
+	return ok
 }

+ 4 - 7
wgengine/pendopen.go

@@ -117,7 +117,7 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.Wra
 	// like:
 	//    open-conn-track: timeout opening (100.115.73.60:52501 => 17.125.252.5:443); no associated peer node
 	if runtime.GOOS == "ios" && flow.Dst.Port() == 443 && !tsaddr.IsTailscaleIP(flow.Dst.IP()) {
-		if _, _, err := e.peerForIP(flow.Dst.IP()); err != nil {
+		if _, ok := e.PeerForIP(flow.Dst.IP()); !ok {
 			return
 		}
 	}
@@ -157,15 +157,12 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
 	}
 
 	// Diagnose why it might've timed out.
-	n, _, err := e.peerForIP(flow.Dst.IP())
-	if err != nil {
-		e.logf("open-conn-track: timeout opening %v; peerForIP: %v", flow, err)
-		return
-	}
-	if n == nil {
+	pip, ok := e.PeerForIP(flow.Dst.IP())
+	if !ok {
 		e.logf("open-conn-track: timeout opening %v; no associated peer node", flow)
 		return
 	}
+	n := pip.Node
 	if n.DiscoKey.IsZero() {
 		e.logf("open-conn-track: timeout opening %v; peer node %v running pre-0.100", flow, n.Key.ShortString())
 		return

+ 12 - 34
wgengine/userspace.go

@@ -1271,25 +1271,20 @@ func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
 
 func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) {
 	res := &ipnstate.PingResult{IP: ip.String()}
-	peer, self, err := e.peerForIP(ip)
-	if err != nil {
-		e.logf("ping(%v): %v", ip, err)
-		res.Err = err.Error()
-		cb(res)
-		return
-	}
-	if peer == nil {
+	pip, ok := e.PeerForIP(ip)
+	if !ok {
 		e.logf("ping(%v): no matching peer", ip)
 		res.Err = "no matching peer"
 		cb(res)
 		return
 	}
-	if self {
+	if pip.IsSelf {
 		res.Err = fmt.Sprintf("%v is local Tailscale IP", ip)
 		res.IsLocalIP = true
 		cb(res)
 		return
 	}
+	peer := pip.Node
 
 	pingType := "disco"
 	if useTSMP {
@@ -1424,46 +1419,35 @@ func (e *userspaceEngine) WhoIsIPPort(ipport netaddr.IPPort) (tsIP netaddr.IP, o
 	return tsIP, false
 }
 
-// peerForIP returns the Node in the wireguard config
+// PeerForIP returns the Node in the wireguard config
 // that's responsible for handling the given IP address.
 //
 // If none is found in the wireguard config but one is found in
 // the netmap, it's described in an error.
 //
-// If none is found in either place, (nil, nil) is returned.
 //
 // peerForIP acquires both e.mu and e.wgLock, but neither at the same
 // time.
-func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, isSelf bool, err error) {
+func (e *userspaceEngine) PeerForIP(ip netaddr.IP) (ret PeerForIP, ok bool) {
 	e.mu.Lock()
 	nm := e.netMap
 	e.mu.Unlock()
 	if nm == nil {
-		return nil, false, errors.New("no network map")
+		return ret, false
 	}
 
 	// Check for exact matches before looking for subnet matches.
-	var bestInNMPrefix netaddr.IPPrefix
-	var bestInNM *tailcfg.Node
+	// TODO(bradfitz): add maps for these. on NetworkMap?
 	for _, p := range nm.Peers {
 		for _, a := range p.Addresses {
 			if a.IP() == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
-				return p, false, nil
-			}
-		}
-		for _, cidr := range p.AllowedIPs {
-			if !cidr.Contains(ip) {
-				continue
-			}
-			if bestInNMPrefix.IsZero() || cidr.Bits() > bestInNMPrefix.Bits() {
-				bestInNMPrefix = cidr
-				bestInNM = p
+				return PeerForIP{Node: p, Route: a}, true
 			}
 		}
 	}
 	for _, a := range nm.Addresses {
 		if a.IP() == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
-			return nm.SelfNode, true, nil
+			return PeerForIP{Node: nm.SelfNode, IsSelf: true, Route: a}, true
 		}
 	}
 
@@ -1489,17 +1473,11 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, isSelf bool
 	if !bestKey.IsZero() {
 		for _, p := range nm.Peers {
 			if p.Key == bestKey {
-				return p, false, nil
+				return PeerForIP{Node: p, Route: best}, true
 			}
 		}
 	}
-	if bestInNM == nil {
-		return nil, false, nil
-	}
-	if bestInNMPrefix.Bits() == 0 {
-		return nil, false, errors.New("exit node found but not enabled")
-	}
-	return nil, false, fmt.Errorf("node %q found, but not using its %v route", bestInNM.ComputedNameWithHost, bestInNMPrefix)
+	return ret, false
 }
 
 type closeOnErrorPool []func()

+ 5 - 0
wgengine/watchdog.go

@@ -146,6 +146,11 @@ func (e *watchdogEngine) GetResolver() (r *resolver.Resolver, ok bool) {
 	}
 	return nil, false
 }
+func (e *watchdogEngine) PeerForIP(ip netaddr.IP) (ret PeerForIP, ok bool) {
+	e.watchdog("PeerForIP", func() { ret, ok = e.wrap.PeerForIP(ip) })
+	return ret, ok
+}
+
 func (e *watchdogEngine) Wait() {
 	e.wrap.Wait()
 }

+ 22 - 2
wgengine/wgengine.go

@@ -49,6 +49,20 @@ type someHandle struct{ _ byte }
 // ErrNoChanges is returned by Engine.Reconfig if no changes were made.
 var ErrNoChanges = errors.New("no changes made to Engine config")
 
+// PeerForIP is the type returned by Engine.PeerForIP.
+type PeerForIP struct {
+	// Node is the matched node. It's always non-nil when
+	// Engine.PeerForIP returns ok==true.
+	Node *tailcfg.Node
+
+	// IsSelf is whether the Node is the local process.
+	IsSelf bool
+
+	// Route is the route that matched the IP provided
+	// to Engine.PeerForIP.
+	Route netaddr.IPPrefix
+}
+
 // Engine is the Tailscale WireGuard engine interface.
 type Engine interface {
 	// Reconfig reconfigures WireGuard and makes sure it's running.
@@ -62,6 +76,10 @@ type Engine interface {
 	// The returned error is ErrNoChanges if no changes were made.
 	Reconfig(*wgcfg.Config, *router.Config, *dns.Config, *tailcfg.Debug) error
 
+	// PeerForIP returns the node to which the provided IP routes,
+	// if any. If none is found, (nil, nil) is returned.
+	PeerForIP(netaddr.IP) (_ PeerForIP, ok bool)
+
 	// GetFilter returns the current packet filter, if any.
 	GetFilter() *filter.Filter
 
@@ -141,10 +159,12 @@ type Engine interface {
 	// RegisterIPPortIdentity registers a given node (identified by its
 	// Tailscale IP) as temporarily having the given IP:port for whois lookups.
 	// The IP:port is generally a localhost IP and an ephemeral port, used
-	// while proxying connections to localhost.
+	// while proxying connections to localhost when tailscaled is running
+	// in netstack mode.
 	RegisterIPPortIdentity(netaddr.IPPort, netaddr.IP)
 
-	// UnregisterIPPortIdentity removes a temporary IP:port registration.
+	// UnregisterIPPortIdentity removes a temporary IP:port registration
+	// made previously by RegisterIPPortIdentity.
 	UnregisterIPPortIdentity(netaddr.IPPort)
 
 	// WhoIsIPPort looks up an IP:port in the temporary registrations,