| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package netmap
- import (
- "cmp"
- "net/netip"
- "reflect"
- "slices"
- "sync"
- "time"
- "tailscale.com/tailcfg"
- "tailscale.com/types/ptr"
- )
- // NodeMutation is the common interface for types that describe
- // the change of a node's state.
- type NodeMutation interface {
- NodeIDBeingMutated() tailcfg.NodeID
- Apply(*tailcfg.Node)
- }
- type mutatingNodeID tailcfg.NodeID
- func (m mutatingNodeID) NodeIDBeingMutated() tailcfg.NodeID { return tailcfg.NodeID(m) }
- // NodeMutationDERPHome is a NodeMutation that says a node
- // has changed its DERP home region.
- type NodeMutationDERPHome struct {
- mutatingNodeID
- DERPRegion int
- }
- func (m NodeMutationDERPHome) Apply(n *tailcfg.Node) {
- n.HomeDERP = m.DERPRegion
- }
- // NodeMutationEndpoints is a NodeMutation that says a node's endpoints have changed.
- type NodeMutationEndpoints struct {
- mutatingNodeID
- Endpoints []netip.AddrPort
- }
- func (m NodeMutationEndpoints) Apply(n *tailcfg.Node) {
- n.Endpoints = slices.Clone(m.Endpoints)
- }
- // NodeMutationOnline is a NodeMutation that says a node is now online or
- // offline.
- type NodeMutationOnline struct {
- mutatingNodeID
- Online bool
- }
- func (m NodeMutationOnline) Apply(n *tailcfg.Node) {
- n.Online = ptr.To(m.Online)
- }
- // NodeMutationLastSeen is a NodeMutation that says a node's LastSeen
- // value should be set to the current time.
- type NodeMutationLastSeen struct {
- mutatingNodeID
- LastSeen time.Time
- }
- func (m NodeMutationLastSeen) Apply(n *tailcfg.Node) {
- n.LastSeen = ptr.To(m.LastSeen)
- }
- var peerChangeFields = sync.OnceValue(func() []reflect.StructField {
- var fields []reflect.StructField
- rt := reflect.TypeFor[tailcfg.PeerChange]()
- for i := range rt.NumField() {
- fields = append(fields, rt.Field(i))
- }
- return fields
- })
- // NodeMutationsFromPatch returns the NodeMutations that
- // p describes. If p describes something not yet supported
- // by a specific NodeMutation type, it returns (nil, false).
- func NodeMutationsFromPatch(p *tailcfg.PeerChange) (_ []NodeMutation, ok bool) {
- if p == nil || p.NodeID == 0 {
- return nil, false
- }
- var ret []NodeMutation
- rv := reflect.ValueOf(p).Elem()
- for i, sf := range peerChangeFields() {
- if rv.Field(i).IsZero() {
- continue
- }
- switch sf.Name {
- default:
- // Unhandled field.
- return nil, false
- case "NodeID":
- continue
- case "DERPRegion":
- ret = append(ret, NodeMutationDERPHome{mutatingNodeID(p.NodeID), p.DERPRegion})
- case "Endpoints":
- ret = append(ret, NodeMutationEndpoints{mutatingNodeID(p.NodeID), slices.Clone(p.Endpoints)})
- case "Online":
- ret = append(ret, NodeMutationOnline{mutatingNodeID(p.NodeID), *p.Online})
- case "LastSeen":
- ret = append(ret, NodeMutationLastSeen{mutatingNodeID(p.NodeID), *p.LastSeen})
- }
- }
- return ret, true
- }
- // MutationsFromMapResponse returns all the discrete node mutations described
- // by res. It returns ok=false if res contains any non-patch field as defined
- // by mapResponseContainsNonPatchFields.
- func MutationsFromMapResponse(res *tailcfg.MapResponse, now time.Time) (ret []NodeMutation, ok bool) {
- if now.IsZero() {
- now = time.Now()
- }
- if mapResponseContainsNonPatchFields(res) {
- return nil, false
- }
- // All that remains is PeersChangedPatch, OnlineChange, and LastSeenChange.
- for _, p := range res.PeersChangedPatch {
- deltas, ok := NodeMutationsFromPatch(p)
- if !ok {
- return nil, false
- }
- ret = append(ret, deltas...)
- }
- for nid, v := range res.OnlineChange {
- ret = append(ret, NodeMutationOnline{mutatingNodeID(nid), v})
- }
- for nid, v := range res.PeerSeenChange {
- if v {
- ret = append(ret, NodeMutationLastSeen{mutatingNodeID(nid), now})
- }
- }
- slices.SortStableFunc(ret, func(a, b NodeMutation) int {
- return cmp.Compare(a.NodeIDBeingMutated(), b.NodeIDBeingMutated())
- })
- return ret, true
- }
- // mapResponseContainsNonPatchFields reports whether res contains only "patch"
- // fields set (PeersChangedPatch primarily, but also including the legacy
- // PeerSeenChange and OnlineChange fields).
- //
- // It ignores any of the meta fields that are handled by PollNetMap before the
- // peer change handling gets involved.
- //
- // The purpose of this function is to ask whether this is a tricky enough
- // MapResponse to warrant a full netmap update. When this returns false, it
- // means the response can be handled incrementally, patching up the local state.
- func mapResponseContainsNonPatchFields(res *tailcfg.MapResponse) bool {
- return res.Node != nil ||
- res.DERPMap != nil ||
- res.DNSConfig != nil ||
- res.Domain != "" ||
- res.CollectServices != "" ||
- res.PacketFilter != nil ||
- res.PacketFilters != nil ||
- res.UserProfiles != nil ||
- res.Health != nil ||
- res.DisplayMessages != nil ||
- res.SSHPolicy != nil ||
- res.TKAInfo != nil ||
- res.DomainDataPlaneAuditLogID != "" ||
- res.Debug != nil ||
- res.ControlDialPlan != nil ||
- res.ClientVersion != nil ||
- res.Peers != nil ||
- res.PeersRemoved != nil ||
- // PeersChanged is too coarse to be considered a patch. Also, we convert
- // PeersChanged to PeersChangedPatch in patchifyPeersChanged before this
- // function is called, so it should never be set anyway. But for
- // completedness, and for tests, check it too:
- res.PeersChanged != nil ||
- res.DeprecatedDefaultAutoUpdate != ""
- }
|