diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index b2971b290a27d..481e9ce9aec81 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -214,6 +214,10 @@ type Conn struct { // that will call Conn.doPeriodicSTUN. periodicReSTUNTimer *time.Timer + // blockEndpoints is whether to avoid capturing, storing and sending + // endpoints gathered from local interfaces or STUN. Only DERP endpoints + // will be sent. + blockEndpoints bool // endpointsUpdateActive indicates that updateEndpoints is // currently running. It's used to deduplicate concurrent endpoint // update requests. @@ -330,6 +334,13 @@ type Options struct { // endpoints change. The called func does not own the slice. EndpointsFunc func([]tailcfg.Endpoint) + // BlockEndpoints is whether to avoid capturing, storing and sending + // endpoints gathered from local interfaces or STUN. Only DERP endpoints + // will be sent. + // This does not disable the UDP socket or portmapping attempts as this + // setting can be toggled at runtime. + BlockEndpoints bool + // DERPActiveFunc optionally provides a func to be called when // a connection is made to a DERP server. DERPActiveFunc func() @@ -580,6 +591,11 @@ func (c *Conn) setEndpoints(endpoints []tailcfg.Endpoint) (changed bool) { c.mu.Lock() defer c.mu.Unlock() + if c.blockEndpoints { + anySTUN = false + endpoints = []tailcfg.Endpoint{} + } + if !anySTUN && c.derpMap == nil && !inTest() { // Don't bother storing or reporting this yet. We // don't have a DERP map or any STUN entries, so we're @@ -826,6 +842,21 @@ func (c *Conn) DiscoPublicKey() key.DiscoPublic { return c.discoPublic } +// SetBlockEndpoints sets the blockEndpoints field. If changed, endpoints will +// be updated to apply the new settings. Existing connections may continue to +// use the old setting until they are reestablished. Disabling endpoints does +// not affect the UDP socket or portmapper. +func (c *Conn) SetBlockEndpoints(block bool) { + c.mu.Lock() + didChange := c.blockEndpoints != block + c.blockEndpoints = block + c.mu.Unlock() + + if didChange { + go c.updateEndpoints("SetBlockEndpoints") + } +} + // determineEndpoints returns the machine's endpoint addresses. It // does a STUN lookup (via netcheck) to determine its public address. // @@ -1648,6 +1679,9 @@ func (c *Conn) enqueueCallMeMaybe(derpAddr netip.AddrPort, de *endpoint) { for _, ep := range c.lastEndpoints { eps = append(eps, ep.Addr) } + // NOTE: sending an empty call-me-maybe (e.g. when BlockEndpoints is true) + // is still valid and results in the other side forgetting all the endpoints + // it knows of ours. go de.c.sendDiscoMessage(derpAddr, de.publicKey, epDisco.key, &disco.CallMeMaybe{MyNumber: eps}, discoLog) if debugSendCallMeUnknownPeer() { // Send a callMeMaybe packet to a non-existent peer diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 254e0ccb0e54b..4881a32ce951c 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -3000,3 +3000,54 @@ func TestDERPForceWebsockets(t *testing.T) { t.Errorf("no websocket upgrade requests seen") } } + +func TestBlockEndpoints(t *testing.T) { + logf, closeLogf := logger.LogfCloser(t.Logf) + defer closeLogf() + + derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1)) + defer cleanup() + + m := &natlab.Machine{Name: "m1"} + ms := newMagicStackFunc(t, logger.WithPrefix(logf, "conn1: "), m, derpMap, nil) + defer ms.Close() + + // Check that some endpoints exist. This should be the case as we should use + // interface addressess as endpoints instantly on startup, and we already + // have a DERP connection due to newMagicStackFunc. + ms.conn.mu.Lock() + haveEndpoint := false + for _, ep := range ms.conn.lastEndpoints { + if ep.Addr.Addr() == tailcfg.DerpMagicIPAddr { + t.Fatal("DERP IP in endpoints list?", ep.Addr) + } + haveEndpoint = true + break + } + ms.conn.mu.Unlock() + if !haveEndpoint { + t.Fatal("no endpoints found") + } + + // Block endpoints, should result in an update. + ms.conn.SetBlockEndpoints(true) + + // Wait for endpoints to finish updating. + ok := false +parentLoop: + for i := 0; i < 50; i++ { + time.Sleep(100 * time.Millisecond) + ms.conn.mu.Lock() + for _, ep := range ms.conn.lastEndpoints { + t.Errorf("endpoint %v was not blocked", ep.Addr) + ms.conn.mu.Unlock() + continue parentLoop + } + ms.conn.mu.Unlock() + ok = true + break + } + if !ok { + t.Fatal("endpoints were not blocked after 50 attempts") + } +}