From a36cfb4d3dba655c7c718f39d4c9b3a9d1e8df39 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 22 Mar 2024 14:18:04 -0700 Subject: [PATCH] tailcfg, ipn/ipnlocal, wgengine/magicsock: add only-tcp-443 node attr Updates tailscale/corp#17879 Change-Id: I0dc305d147b76c409cf729b599a94fa723aef0e0 Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/local.go | 1 + net/portmapper/portmapper.go | 18 ++++++++++++++++++ tailcfg/tailcfg.go | 7 +++++++ wgengine/magicsock/derp.go | 7 +++++++ wgengine/magicsock/magicsock.go | 10 +++++++++- 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 7dfac9595..83358c088 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1230,6 +1230,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control b.e.SetNetworkMap(st.NetMap) b.MagicConn().SetDERPMap(st.NetMap.DERPMap) + b.MagicConn().SetOnlyTCP443(st.NetMap.HasCap(tailcfg.NodeAttrOnlyTCP443)) // Update our cached DERP map dnsfallback.UpdateCache(st.NetMap.DERPMap, b.logf) diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index 414922fd5..01ed150ca 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -49,6 +49,17 @@ type DebugKnobs struct { DisableUPnP bool DisablePMP bool DisablePCP bool + + // DisableAll, if non-nil, is a func that reports whether all port + // mapping attempts should be disabled. + DisableAll func() bool +} + +func (k *DebugKnobs) disableAll() bool { + if k.DisableAll != nil { + return k.DisableAll() + } + return false } // References: @@ -403,6 +414,7 @@ var ( ErrNoPortMappingServices = errors.New("no port mapping services were found") ErrGatewayRange = errors.New("skipping portmap; gateway range likely lacks support") ErrGatewayIPv6 = errors.New("skipping portmap; no IPv6 support for portmapping") + ErrPortMappingDisabled = errors.New("port mapping is disabled") ) // GetCachedMappingOrStartCreatingOne quickly returns with our current cached portmapping, if any. @@ -464,6 +476,9 @@ var wildcardIP = netip.MustParseAddr("0.0.0.0") // If no mapping is available, the error will be of type // NoMappingError; see IsNoMappingError. func (c *Client) createOrGetMapping(ctx context.Context) (external netip.AddrPort, err error) { + if c.debug.disableAll() { + return netip.AddrPort{}, NoMappingError{ErrPortMappingDisabled} + } if c.debug.DisableUPnP && c.debug.DisablePCP && c.debug.DisablePMP { return netip.AddrPort{}, NoMappingError{ErrNoPortMappingServices} } @@ -777,6 +792,9 @@ type ProbeResult struct { // the returned result might be server from the Client's cache, without // sending any network traffic. func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) { + if c.debug.disableAll() { + return res, ErrPortMappingDisabled + } gw, myIP, ok := c.gatewayAndSelfIP() if !ok { return res, ErrGatewayRange diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 353181748..c194063d5 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -2133,6 +2133,13 @@ const ( // e.g. https://tailscale.com/cap/funnel-ports?ports=80,443,8080-8090 CapabilityFunnelPorts NodeCapability = "https://tailscale.com/cap/funnel-ports" + // NodeAttrOnlyTCP443 specifies that the client should not attempt to generate + // any outbound traffic that isn't TCP on port 443 (HTTPS). This is used for + // clients in restricted environments where only HTTPS traffic is allowed + // other types of traffic trips outbound firewall alarms. This thus implies + // all traffic is over DERP. + NodeAttrOnlyTCP443 NodeCapability = "only-tcp-443" + // NodeAttrFunnel grants the ability for a node to host ingress traffic. NodeAttrFunnel NodeCapability = "funnel" // NodeAttrSSHAggregator grants the ability for a node to collect SSH sessions. diff --git a/wgengine/magicsock/derp.go b/wgengine/magicsock/derp.go index 9ef728873..73147e128 100644 --- a/wgengine/magicsock/derp.go +++ b/wgengine/magicsock/derp.go @@ -729,6 +729,13 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en return n, ep } +// SetOnlyTCP443 set whether the magicsock connection is restricted +// to only using TCP port 443 outbound. If true, no UDP is allowed, +// no STUN checks are performend, etc. +func (c *Conn) SetOnlyTCP443(v bool) { + c.onlyTCP443.Store(v) +} + // SetDERPMap controls which (if any) DERP servers are used. // A nil value means to disable DERP; it's disabled by default. func (c *Conn) SetDERPMap(dm *tailcfg.DERPMap) { diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 5191f0604..a82e710b2 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -198,6 +198,8 @@ type Conn struct { mu sync.Mutex muCond *sync.Cond + onlyTCP443 atomic.Bool + closed bool // Close was called closing atomic.Bool // Close is in progress (or done) @@ -444,7 +446,10 @@ func NewConn(opts Options) (*Conn, error) { c.idleFunc = opts.IdleFunc c.testOnlyPacketListener = opts.TestOnlyPacketListener c.noteRecvActivity = opts.NoteRecvActivity - c.portMapper = portmapper.NewClient(logger.WithPrefix(c.logf, "portmapper: "), opts.NetMon, nil, opts.ControlKnobs, c.onPortMapChanged) + portMapOpts := &portmapper.DebugKnobs{ + DisableAll: func() bool { return c.onlyTCP443.Load() }, + } + c.portMapper = portmapper.NewClient(logger.WithPrefix(c.logf, "portmapper: "), opts.NetMon, portMapOpts, opts.ControlKnobs, c.onPortMapChanged) if opts.NetMon != nil { c.portMapper.SetGatewayLookupFunc(opts.NetMon.GatewayAndSelfIP) } @@ -1067,6 +1072,9 @@ func (c *Conn) sendUDP(ipp netip.AddrPort, b []byte) (sent bool, err error) { // sendUDP sends UDP packet b to addr. // See sendAddr's docs on the return value meanings. func (c *Conn) sendUDPStd(addr netip.AddrPort, b []byte) (sent bool, err error) { + if c.onlyTCP443.Load() { + return false, nil + } switch { case addr.Addr().Is4(): _, err = c.pconn4.WriteToUDPAddrPort(b, addr)