control/controlknobs: make Knobs.AsDebugJSON automatic, not require maintenance

The AsDebugJSON method (used only for a LocalAPI debug call) always
needed to be updated whenever a new controlknob was added. We had a
test for it, which was nice, but it was a tedious step we don't need
to do. Use reflect instead.

Updates #14788

Change-Id: If59cd776920f3ce7c748f86ed2eddd9323039a0b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2025-01-26 18:23:38 +00:00 committed by Brad Fitzpatrick
parent 66b2e9fd07
commit e701fde6b3
2 changed files with 19 additions and 21 deletions

View File

@ -6,6 +6,8 @@
package controlknobs package controlknobs
import ( import (
"fmt"
"reflect"
"sync/atomic" "sync/atomic"
"tailscale.com/syncs" "tailscale.com/syncs"
@ -174,26 +176,19 @@ func (k *Knobs) AsDebugJSON() map[string]any {
if k == nil { if k == nil {
return nil return nil
} }
return map[string]any{ ret := map[string]any{}
"DisableUPnP": k.DisableUPnP.Load(), rt := reflect.TypeFor[Knobs]()
"KeepFullWGConfig": k.KeepFullWGConfig.Load(), rv := reflect.ValueOf(k).Elem() // of *k
"RandomizeClientPort": k.RandomizeClientPort.Load(), for i := 0; i < rt.NumField(); i++ {
"OneCGNAT": k.OneCGNAT.Load(), name := rt.Field(i).Name
"ForceBackgroundSTUN": k.ForceBackgroundSTUN.Load(), switch v := rv.Field(i).Addr().Interface().(type) {
"DisableDeltaUpdates": k.DisableDeltaUpdates.Load(), case *atomic.Bool:
"PeerMTUEnable": k.PeerMTUEnable.Load(), ret[name] = v.Load()
"DisableDNSForwarderTCPRetries": k.DisableDNSForwarderTCPRetries.Load(), case *syncs.AtomicValue[opt.Bool]:
"SilentDisco": k.SilentDisco.Load(), ret[name] = v.Load()
"LinuxForceIPTables": k.LinuxForceIPTables.Load(), default:
"LinuxForceNfTables": k.LinuxForceNfTables.Load(), panic(fmt.Sprintf("unknown field type %T for %v", v, name))
"SeamlessKeyRenewal": k.SeamlessKeyRenewal.Load(), }
"ProbeUDPLifetime": k.ProbeUDPLifetime.Load(),
"AppCStoreRoutes": k.AppCStoreRoutes.Load(),
"UserDialUseRoutes": k.UserDialUseRoutes.Load(),
"DisableSplitDNSWhenNoCustomResolvers": k.DisableSplitDNSWhenNoCustomResolvers.Load(),
"DisableLocalDNSOverrideViaNRPT": k.DisableLocalDNSOverrideViaNRPT.Load(),
"DisableCryptorouting": k.DisableCryptorouting.Load(),
"DisableCaptivePortalDetection": k.DisableCaptivePortalDetection.Load(),
"DisableSkipStatusQueue": k.DisableSkipStatusQueue.Load(),
} }
return ret
} }

View File

@ -6,6 +6,8 @@ package controlknobs
import ( import (
"reflect" "reflect"
"testing" "testing"
"tailscale.com/types/logger"
) )
func TestAsDebugJSON(t *testing.T) { func TestAsDebugJSON(t *testing.T) {
@ -18,4 +20,5 @@ func TestAsDebugJSON(t *testing.T) {
if want := reflect.TypeFor[Knobs]().NumField(); len(got) != want { if want := reflect.TypeFor[Knobs]().NumField(); len(got) != want {
t.Errorf("AsDebugJSON map has %d fields; want %v", len(got), want) t.Errorf("AsDebugJSON map has %d fields; want %v", len(got), want)
} }
t.Logf("Got: %v", logger.AsJSON(got))
} }