This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+169 -31
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package controlclient
@@ -20,10 +20,12 @@ import (
"tailscale.com/types/netmap"
"tailscale.com/types/persist"
"tailscale.com/types/structs"
"tailscale.com/types/views"
"tailscale.com/util/backoff"
"tailscale.com/util/clientmetric"
"tailscale.com/util/execqueue"
"tailscale.com/util/testenv"
"tailscale.com/wgengine/filter"
)
type LoginGoal struct {
@@ -34,6 +36,10 @@ type LoginGoal struct {
var _ Client = (*Auto)(nil)
// maxRetryWindow defines the upper bound on how long control is allowed
// to tell the client to wait before retrying a request.
const maxRetryWindow = 5 * time.Minute
// waitUnpause waits until either the client is unpaused or the Auto client is
// shut down. It reports whether the client should keep running (i.e. it's not
// closed).
@@ -88,10 +94,14 @@ func (c *Auto) updateRoutine() {
if ctx.Err() == nil {
c.direct.logf("lite map update error after %v: %v", d, err)
}
bo.BackOff(ctx, err)
if rle, rateLimited := errors.AsType[*rateLimitError](err); rateLimited {
c.waitRetryAfter(ctx, "updateRoutine", rle)
} else {
bo.BackOff(ctx, err)
}
continue
}
bo.BackOff(ctx, nil)
bo.Reset()
c.direct.logf("[v1] successful lite map update in %v", d)
lastUpdateGenInformed = gen
@@ -356,7 +366,11 @@ func (c *Auto) authRoutine() {
if err != nil {
c.direct.health.SetAuthRoutineInError(err)
report(err, f)
bo.BackOff(ctx, err)
if rle, ok := errors.AsType[*rateLimitError](err); ok {
c.waitRetryAfter(ctx, "authRoutine", rle)
} else {
bo.BackOff(ctx, err)
}
continue
}
if url != "" {
@@ -370,9 +384,15 @@ func (c *Auto) authRoutine() {
}
c.mu.Lock()
c.urlToVisit = url
c.loginGoal = &LoginGoal{
flags: LoginDefault,
url: url,
// Only store the URL follow-up goal if no concurrent Login() call has
// replaced the goal we were processing while our control plane request
// was in flight. Otherwise, the intent from the more recent goal gets
// lost.
if c.loginGoal == goal {
c.loginGoal = &LoginGoal{
flags: LoginDefault,
url: url,
}
}
c.mu.Unlock()
@@ -382,7 +402,7 @@ func (c *Auto) authRoutine() {
// backoff to avoid a busy loop.
bo.BackOff(ctx, errors.New("login URL not changing"))
} else {
bo.BackOff(ctx, nil)
bo.Reset()
}
continue
}
@@ -390,14 +410,26 @@ func (c *Auto) authRoutine() {
// success
c.direct.health.SetAuthRoutineInError(nil)
c.mu.Lock()
c.urlToVisit = ""
c.loggedIn = true
c.loginGoal = nil
// Only commit the login success if no concurrent Login()
// call has reset the goal and no Logout() has moved on
// while our control plane request was in flight. In the
// first case, clearing the goal would prevent the next
// iteration from picking it up and running with it. In
// the second case, we would record that we're loggedIn
// even though we're logged out.
goalStillCurrentGoal := c.loginGoal == goal
if goalStillCurrentGoal {
c.urlToVisit = ""
c.loggedIn = true
c.loginGoal = nil
}
c.mu.Unlock()
c.sendStatus("authRoutine-success", nil, "", nil)
c.restartMap()
bo.BackOff(ctx, nil)
if goalStillCurrentGoal {
c.sendStatus("authRoutine-success", nil, "", nil)
c.restartMap()
}
bo.Reset()
}
}
@@ -446,13 +478,14 @@ func (mrs mapRoutineState) UpdateFullNetmap(nm *netmap.NetworkMap) {
c.expiry = nm.SelfKeyExpiry()
stillAuthed := c.loggedIn
c.logf("[v1] mapRoutine: netmap received: loggedIn=%v inMapPoll=true", stillAuthed)
// Reset the backoff timer if we got a netmap.
mrs.bo.Reset()
c.mu.Unlock()
if stillAuthed {
c.sendStatus("mapRoutine-got-netmap", nil, "", nm)
}
// Reset the backoff timer if we got a netmap.
mrs.bo.Reset()
}
func (mrs mapRoutineState) UpdateNetmapDelta(muts []netmap.NodeMutation) bool {
@@ -461,20 +494,89 @@ func (mrs mapRoutineState) UpdateNetmapDelta(muts []netmap.NodeMutation) bool {
c.mu.Lock()
goodState := c.loggedIn && c.inMapPoll
ndu, canDelta := c.observer.(NetmapDeltaUpdater)
mapCtx := c.mapCtx
c.mu.Unlock()
if !goodState || !canDelta {
return false
}
ctx, cancel := context.WithTimeout(c.mapCtx, 2*time.Second)
ctx, cancel := context.WithTimeout(mapCtx, 2*time.Second)
defer cancel()
var ok bool
err := c.observerQueue.RunSync(ctx, func() {
ok = ndu.UpdateNetmapDelta(muts)
ch := make(chan bool, 1)
c.observerQueue.Add(func() {
ch <- ndu.UpdateNetmapDelta(muts)
})
return err == nil && ok
select {
case ok := <-ch:
return ok
case <-ctx.Done():
return false
}
}
var (
_ PacketFilterUpdater = mapRoutineState{}
_ UserProfileUpdater = mapRoutineState{}
)
// UpdatePacketFilter implements [PacketFilterUpdater] by forwarding to
// [Auto.observer] if it implements [PacketFilterUpdater]. It returns
// false (signaling fall back to a full netmap rebuild) if the
// downstream observer doesn't implement [PacketFilterUpdater] or isn't
// in a state to accept updates.
func (mrs mapRoutineState) UpdatePacketFilter(rules views.Slice[tailcfg.FilterRule], parsed []filter.Match) bool {
c := mrs.c
c.mu.Lock()
goodState := c.loggedIn && c.inMapPoll
pfu, ok := c.observer.(PacketFilterUpdater)
mapCtx := c.mapCtx
c.mu.Unlock()
if !goodState || !ok {
return false
}
ctx, cancel := context.WithTimeout(mapCtx, 2*time.Second)
defer cancel()
ch := make(chan bool, 1)
c.observerQueue.Add(func() {
ch <- pfu.UpdatePacketFilter(rules, parsed)
})
select {
case applied := <-ch:
return applied
case <-ctx.Done():
return false
}
}
// UpdateUserProfiles implements [UserProfileUpdater] by forwarding to
// [Auto.observer] if it implements [UserProfileUpdater]. It returns
// false (signaling fall back to a full netmap rebuild) if the
// downstream observer doesn't implement [UserProfileUpdater] or isn't
// in a state to accept updates.
func (mrs mapRoutineState) UpdateUserProfiles(profiles map[tailcfg.UserID]tailcfg.UserProfileView) bool {
c := mrs.c
c.mu.Lock()
goodState := c.loggedIn && c.inMapPoll
upu, ok := c.observer.(UserProfileUpdater)
mapCtx := c.mapCtx
c.mu.Unlock()
if !goodState || !ok {
return false
}
ctx, cancel := context.WithTimeout(mapCtx, 2*time.Second)
defer cancel()
ch := make(chan bool, 1)
c.observerQueue.Add(func() {
ch <- upu.UpdateUserProfiles(profiles)
})
select {
case applied := <-ch:
return applied
case <-ctx.Done():
return false
}
}
// mapRoutine is responsible for keeping a read-only streaming connection to the
@@ -528,13 +630,41 @@ func (c *Auto) mapRoutine() {
paused := c.paused
c.mu.Unlock()
rle, rateLimited := errors.AsType[*rateLimitError](err)
if paused {
mrs.bo.Reset()
} else if !rateLimited {
mrs.bo.BackOff(ctx, err)
}
if paused {
mrs.bo.BackOff(ctx, nil)
c.logf("mapRoutine: paused")
} else {
mrs.bo.BackOff(ctx, err)
report(err, "PollNetMap")
}
if rateLimited {
c.waitRetryAfter(ctx, "mapRoutine", rle)
}
}
}
// waitRetryAfter sleeps for the delay the server requested in rle, capped at
// [maxRetryWindow] or until ctx is done, whichever comes first.
func (c *Auto) waitRetryAfter(ctx context.Context, routine string, rle *rateLimitError) {
if rle.retryAfter > maxRetryWindow {
rle.retryAfter = maxRetryWindow
}
c.logf("%s: %s", routine, rle)
t, ch := c.clock.NewTimer(rle.retryAfter)
defer t.Stop()
select {
case <-ctx.Done():
case <-ch:
}
}
@@ -674,17 +804,16 @@ func canSkipStatus(s1, s2 *Status) bool {
// we can't skip it.
return false
}
if s1.Err != nil || s1.URL != "" || s1.LoggedIn {
// If s1 has an error, a URL, or LoginFinished set, we shouldn't skip it,
// lest the error go away in s2 or in-between. We want to make sure all
// the subsystems see it. Plus there aren't many of these, so not worth
// skipping.
if s1.Err != nil || s1.URL != "" {
// If s1 has an error or an URL, we shouldn't skip it, lest the error go
// away in s2 or in-between. We want to make sure all the subsystems see
// it. Plus there aren't many of these, so not worth skipping.
return false
}
if !s1.Persist.Equals(s2.Persist) || s1.LoggedIn != s2.LoggedIn || s1.InMapPoll != s2.InMapPoll || s1.URL != s2.URL {
// If s1 has a different Persist, LoginFinished, Synced, or URL than s2,
// don't skip it. We only care about skipping the typical
// entries where the only difference is the NetMap.
// If s1 has a different Persist, has changed login state, changed map
// poll state, or has a new login URL, don't skip it. We only care about
// skipping the typical entries where the only difference is the NetMap.
return false
}
// If nothing above precludes it, and both s1 and s2 have NetMaps, then
@@ -774,6 +903,15 @@ func (c *Auto) SetDiscoPublicKey(key key.DiscoPublic) {
c.updateControl()
}
// SetIPForwardingBroken updates the IP forwarding broken state and sends
// a control update if the value changed.
func (c *Auto) SetIPForwardingBroken(v bool) {
if !c.direct.SetIPForwardingBroken(v) {
return
}
c.updateControl()
}
func (c *Auto) Shutdown() {
c.mu.Lock()
if c.closed {