ipn,wgengine/magicsock: allow setting static node endpoints via tailscaled configfile (#12882)

wgengine/magicsock,ipn: allow setting static node endpoints via tailscaled config file.

Adds a new StaticEndpoints field to tailscaled config
that can be used to statically configure the endpoints
that the node advertizes. This field will replace
TS_DEBUG_PRETENDPOINTS env var that can be used to achieve the same.

Additionally adds some functionality that ensures that endpoints
are updated when configfile is reloaded.

Also, refactor configuring/reconfiguring components to use the
same functionality when configfile is parsed the first time or
subsequent times (after reload). Previously a configfile reload
did not result in resetting of prefs. Now it does- but does not yet
tell the relevant components to consume the new prefs. This is to
be done in a follow-up.

Updates tailscale/tailscale#12578


Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
Irbe Krumina 2024-07-23 18:50:55 +03:00 committed by GitHub
parent 9904421853
commit 57856fc0d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 85 additions and 17 deletions

View File

@ -42,6 +42,10 @@ type ConfigVAlpha struct {
AutoUpdate *AutoUpdatePrefs `json:",omitempty"`
ServeConfigTemp *ServeConfig `json:",omitempty"` // TODO(bradfitz,maisem): make separate stable type for this
// StaticEndpoints are additional, user-defined endpoints that this node
// should advertise amongst its wireguard endpoints.
StaticEndpoints []netip.AddrPort `json:",omitempty"`
// TODO(bradfitz,maisem): future something like:
// Profile map[string]*Config // keyed by alice@gmail.com, corp.com (TailnetSID)
}

View File

@ -25,6 +25,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"slices"
"sort"
@ -391,18 +392,6 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
sds.SetDialer(dialer.SystemDial)
}
if sys.InitialConfig != nil {
p := pm.CurrentPrefs().AsStruct()
mp, err := sys.InitialConfig.Parsed.ToPrefs()
if err != nil {
return nil, err
}
p.ApplyEdits(&mp)
if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil {
return nil, err
}
}
envknob.LogCurrent(logf)
osshare.SetFileSharingEnabled(false, logf)
@ -417,7 +406,6 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
statsLogf: logger.LogOnChange(logf, 5*time.Minute, clock.Now),
sys: sys,
health: sys.HealthTracker(),
conf: sys.InitialConfig,
e: e,
dialer: dialer,
store: store,
@ -434,6 +422,12 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
}
mConn.SetNetInfoCallback(b.setNetInfo)
if sys.InitialConfig != nil {
if err := b.setConfigLocked(sys.InitialConfig); err != nil {
return nil, err
}
}
netMon := sys.NetMon.Get()
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon, sys.HealthTracker())
if err != nil {
@ -616,11 +610,50 @@ func (b *LocalBackend) ReloadConfig() (ok bool, err error) {
if err != nil {
return false, err
}
b.conf = conf
// TODO(bradfitz): apply things
if err := b.setConfigLocked(conf); err != nil {
return false, fmt.Errorf("error setting config: %w", err)
}
return true, nil
}
func (b *LocalBackend) setConfigLocked(conf *conffile.Config) error {
// TODO(irbekrm): notify the relevant components to consume any prefs
// updates. Currently only initial configfile settings are applied
// immediately.
p := b.pm.CurrentPrefs().AsStruct()
mp, err := conf.Parsed.ToPrefs()
if err != nil {
return fmt.Errorf("error parsing config to prefs: %w", err)
}
p.ApplyEdits(&mp)
if err := b.pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil {
return err
}
defer func() {
b.conf = conf
}()
if conf.Parsed.StaticEndpoints == nil && (b.conf == nil || b.conf.Parsed.StaticEndpoints == nil) {
return nil
}
// Ensure that magicsock conn has the up to date static wireguard
// endpoints. Setting the endpoints here triggers an asynchronous update
// of the node's advertised endpoints.
if b.conf == nil && len(conf.Parsed.StaticEndpoints) != 0 || !reflect.DeepEqual(conf.Parsed.StaticEndpoints, b.conf.Parsed.StaticEndpoints) {
ms, ok := b.sys.MagicSock.GetOK()
if !ok {
b.logf("[unexpected] ReloadConfig: MagicSock not set")
} else {
ms.SetStaticEndpoints(views.SliceOf(conf.Parsed.StaticEndpoints))
}
}
return nil
}
var assumeNetworkUpdateForTest = envknob.RegisterBool("TS_ASSUME_NETWORK_UP_FOR_TEST")
// pauseOrResumeControlClientLocked pauses b.cc if there is no network available

View File

@ -14,6 +14,7 @@ import (
"io"
"net"
"net/netip"
"reflect"
"runtime"
"strconv"
"strings"
@ -311,6 +312,12 @@ type Conn struct {
// lastEPERMRebind tracks the last time a rebind was performed
// after experiencing a syscall.EPERM.
lastEPERMRebind syncs.AtomicValue[time.Time]
// staticEndpoints are user set endpoints that this node should
// advertise amongst its wireguard endpoints. It is user's
// responsibility to ensure that traffic from these endpoints is routed
// to the node.
staticEndpoints views.Slice[netip.AddrPort]
}
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
@ -636,6 +643,22 @@ func (c *Conn) setEndpoints(endpoints []tailcfg.Endpoint) (changed bool) {
return true
}
// SetStaticEndpoints sets static endpoints to the provided value and triggers
// an asynchronous update of the endpoints that this node advertises.
// Static endpoints are endpoints explicitly configured by user.
func (c *Conn) SetStaticEndpoints(ep views.Slice[netip.AddrPort]) {
c.mu.Lock()
if reflect.DeepEqual(c.staticEndpoints.AsSlice(), ep.AsSlice()) {
return
}
c.staticEndpoints = ep
c.mu.Unlock()
// Technically this is not a reSTUNning, but ReSTUN does what we need at
// this point- calls updateEndpoints or queues an update if there is
// already an in-progress update.
c.ReSTUN("static-endpoint-change")
}
// setNetInfoHavePortMap updates NetInfo.HavePortMap to true.
func (c *Conn) setNetInfoHavePortMap() {
c.mu.Lock()
@ -845,8 +868,10 @@ func (c *Conn) DiscoPublicKey() key.DiscoPublic {
return c.discoPublic
}
// determineEndpoints returns the machine's endpoint addresses. It
// does a STUN lookup (via netcheck) to determine its public address.
// determineEndpoints returns the machine's endpoint addresses. It does a STUN
// lookup (via netcheck) to determine its public address. Additionally any
// static enpoints provided by user are always added to the returned endpoints
// without validating if the node can be reached via those endpoints.
//
// c.mu must NOT be held.
func (c *Conn) determineEndpoints(ctx context.Context) ([]tailcfg.Endpoint, error) {
@ -943,6 +968,10 @@ func (c *Conn) determineEndpoints(ctx context.Context) ([]tailcfg.Endpoint, erro
// re-run.
eps = c.endpointTracker.update(time.Now(), eps)
for i := range c.staticEndpoints.Len() {
addAddr(c.staticEndpoints.At(i), tailcfg.EndpointExplicitConf)
}
if localAddr := c.pconn4.LocalAddr(); localAddr.IP.IsUnspecified() {
ips, loopback, err := netmon.LocalAddresses()
if err != nil {
@ -2359,6 +2388,8 @@ func (c *Conn) onPortMapChanged() { c.ReSTUN("portmap-changed") }
// ReSTUN triggers an address discovery.
// The provided why string is for debug logging only.
// If Conn.staticEndpoints have been updated, calling ReSTUN will also result in
// the new endpoints being advertised.
func (c *Conn) ReSTUN(why string) {
c.mu.Lock()
defer c.mu.Unlock()