mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
net/socks5/tssocks, wgengine: permit SOCKS through subnet routers/exit nodes
Fixes #1970 Change-Id: Ibef45e8796e1d9625716d72539c96d1dbf7b1f76 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
3181bbb8e4
commit
bb91cfeae7
@ -12,7 +12,6 @@
|
||||
|
||||
"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 @@
|
||||
//
|
||||
// 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,
|
||||
@ -38,6 +37,7 @@ 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
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -49,6 +49,20 @@ type someHandle struct{
|
||||
// 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,
|
||||
|
Loading…
Reference in New Issue
Block a user