From 600f25dac99aa8fbecb5b43f0ec9df516fbb28f6 Mon Sep 17 00:00:00 2001 From: Adrian Dewhurst Date: Mon, 3 Feb 2025 14:08:26 -0500 Subject: [PATCH] tailcfg: add JSON unmarshal helper for view of node/peer capabilities Many places that need to work with node/peer capabilities end up with a something-View and need to either reimplement the helper code or make an expensive copy. We have the machinery to easily handle this now. Updates #cleanup Change-Id: Ic3f55be329f0fc6c178de26b34359d0e8c6ca5fc Signed-off-by: Adrian Dewhurst --- cmd/sniproxy/sniproxy.go | 4 +--- ipn/ipnlocal/local.go | 5 +---- tailcfg/tailcfg.go | 27 +++++++++++++++++++++------ types/netmap/netmap.go | 2 +- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/cmd/sniproxy/sniproxy.go b/cmd/sniproxy/sniproxy.go index fa83aaf4a..c1af977f6 100644 --- a/cmd/sniproxy/sniproxy.go +++ b/cmd/sniproxy/sniproxy.go @@ -157,10 +157,8 @@ func run(ctx context.Context, ts *tsnet.Server, wgPort int, hostname string, pro // NetMap contains app-connector configuration if nm := msg.NetMap; nm != nil && nm.SelfNode.Valid() { - sn := nm.SelfNode.AsStruct() - var c appctype.AppConnectorConfig - nmConf, err := tailcfg.UnmarshalNodeCapJSON[appctype.AppConnectorConfig](sn.CapMap, configCapKey) + nmConf, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.AppConnectorConfig](nm.SelfNode.CapMap(), configCapKey) if err != nil { log.Printf("failed to read app connector configuration from coordination server: %v", err) } else if len(nmConf) > 0 { diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index fb7cc98a3..faf5d13db 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -4463,10 +4463,7 @@ func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs i return } - // TODO(raggi): rework the view infrastructure so the large deep clone is no - // longer required - sn := nm.SelfNode.AsStruct() - attrs, err := tailcfg.UnmarshalNodeCapJSON[appctype.AppConnectorAttr](sn.CapMap, appConnectorCapName) + attrs, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.AppConnectorAttr](nm.SelfNode.CapMap(), appConnectorCapName) if err != nil { b.logf("[unexpected] error parsing app connector mapcap: %v", err) return diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index c17cd5f45..8251b5058 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -25,6 +25,7 @@ import ( "tailscale.com/types/opt" "tailscale.com/types/structs" "tailscale.com/types/tkatype" + "tailscale.com/types/views" "tailscale.com/util/dnsname" "tailscale.com/util/slicesx" "tailscale.com/util/vizerror" @@ -1547,12 +1548,19 @@ func (c NodeCapMap) Equal(c2 NodeCapMap) bool { // If cap does not exist in cm, it returns (nil, nil). // It returns an error if the values cannot be unmarshaled into the provided type. func UnmarshalNodeCapJSON[T any](cm NodeCapMap, cap NodeCapability) ([]T, error) { - vals, ok := cm[cap] + return UnmarshalNodeCapViewJSON[T](views.MapSliceOf(cm), cap) +} + +// UnmarshalNodeCapViewJSON unmarshals each JSON value in cm.Get(cap) as T. +// If cap does not exist in cm, it returns (nil, nil). +// It returns an error if the values cannot be unmarshaled into the provided type. +func UnmarshalNodeCapViewJSON[T any](cm views.MapSlice[NodeCapability, RawMessage], cap NodeCapability) ([]T, error) { + vals, ok := cm.GetOk(cap) if !ok { return nil, nil } - out := make([]T, 0, len(vals)) - for _, v := range vals { + out := make([]T, 0, vals.Len()) + for _, v := range vals.All() { var t T if err := json.Unmarshal([]byte(v), &t); err != nil { return nil, err @@ -1582,12 +1590,19 @@ type PeerCapMap map[PeerCapability][]RawMessage // If cap does not exist in cm, it returns (nil, nil). // It returns an error if the values cannot be unmarshaled into the provided type. func UnmarshalCapJSON[T any](cm PeerCapMap, cap PeerCapability) ([]T, error) { - vals, ok := cm[cap] + return UnmarshalCapViewJSON[T](views.MapSliceOf(cm), cap) +} + +// UnmarshalCapViewJSON unmarshals each JSON value in cm.Get(cap) as T. +// If cap does not exist in cm, it returns (nil, nil). +// It returns an error if the values cannot be unmarshaled into the provided type. +func UnmarshalCapViewJSON[T any](cm views.MapSlice[PeerCapability, RawMessage], cap PeerCapability) ([]T, error) { + vals, ok := cm.GetOk(cap) if !ok { return nil, nil } - out := make([]T, 0, len(vals)) - for _, v := range vals { + out := make([]T, 0, vals.Len()) + for _, v := range vals.All() { var t T if err := json.Unmarshal([]byte(v), &t); err != nil { return nil, err diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index ab22eec3e..051b0f0dc 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -115,7 +115,7 @@ func (nm *NetworkMap) GetVIPServiceIPMap() tailcfg.ServiceIPMappings { return nil } - ipMaps, err := tailcfg.UnmarshalNodeCapJSON[tailcfg.ServiceIPMappings](nm.SelfNode.CapMap().AsMap(), tailcfg.NodeAttrServiceHost) + ipMaps, err := tailcfg.UnmarshalNodeCapViewJSON[tailcfg.ServiceIPMappings](nm.SelfNode.CapMap(), tailcfg.NodeAttrServiceHost) if len(ipMaps) != 1 || err != nil { return nil }