nodemut.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package netmap
  4. import (
  5. "cmp"
  6. "net/netip"
  7. "reflect"
  8. "slices"
  9. "sync"
  10. "time"
  11. "tailscale.com/tailcfg"
  12. "tailscale.com/types/ptr"
  13. )
  14. // NodeMutation is the common interface for types that describe
  15. // the change of a node's state.
  16. type NodeMutation interface {
  17. NodeIDBeingMutated() tailcfg.NodeID
  18. Apply(*tailcfg.Node)
  19. }
  20. type mutatingNodeID tailcfg.NodeID
  21. func (m mutatingNodeID) NodeIDBeingMutated() tailcfg.NodeID { return tailcfg.NodeID(m) }
  22. // NodeMutationDERPHome is a NodeMutation that says a node
  23. // has changed its DERP home region.
  24. type NodeMutationDERPHome struct {
  25. mutatingNodeID
  26. DERPRegion int
  27. }
  28. func (m NodeMutationDERPHome) Apply(n *tailcfg.Node) {
  29. n.HomeDERP = m.DERPRegion
  30. }
  31. // NodeMutationEndpoints is a NodeMutation that says a node's endpoints have changed.
  32. type NodeMutationEndpoints struct {
  33. mutatingNodeID
  34. Endpoints []netip.AddrPort
  35. }
  36. func (m NodeMutationEndpoints) Apply(n *tailcfg.Node) {
  37. n.Endpoints = slices.Clone(m.Endpoints)
  38. }
  39. // NodeMutationOnline is a NodeMutation that says a node is now online or
  40. // offline.
  41. type NodeMutationOnline struct {
  42. mutatingNodeID
  43. Online bool
  44. }
  45. func (m NodeMutationOnline) Apply(n *tailcfg.Node) {
  46. n.Online = ptr.To(m.Online)
  47. }
  48. // NodeMutationLastSeen is a NodeMutation that says a node's LastSeen
  49. // value should be set to the current time.
  50. type NodeMutationLastSeen struct {
  51. mutatingNodeID
  52. LastSeen time.Time
  53. }
  54. func (m NodeMutationLastSeen) Apply(n *tailcfg.Node) {
  55. n.LastSeen = ptr.To(m.LastSeen)
  56. }
  57. // NodeMutationCap is a NodeMutation that says a node's
  58. // [tailcfg.CapabilityVersion] value has changed.
  59. type NodeMutationCap struct {
  60. mutatingNodeID
  61. Cap tailcfg.CapabilityVersion
  62. }
  63. func (m NodeMutationCap) Apply(n *tailcfg.Node) {
  64. n.Cap = m.Cap
  65. }
  66. var peerChangeFields = sync.OnceValue(func() []reflect.StructField {
  67. var fields []reflect.StructField
  68. rt := reflect.TypeFor[tailcfg.PeerChange]()
  69. for i := range rt.NumField() {
  70. fields = append(fields, rt.Field(i))
  71. }
  72. return fields
  73. })
  74. // NodeMutationsFromPatch returns the NodeMutations that
  75. // p describes. If p describes something not yet supported
  76. // by a specific NodeMutation type, it returns (nil, false).
  77. func NodeMutationsFromPatch(p *tailcfg.PeerChange) (_ []NodeMutation, ok bool) {
  78. if p == nil || p.NodeID == 0 {
  79. return nil, false
  80. }
  81. var ret []NodeMutation
  82. rv := reflect.ValueOf(p).Elem()
  83. for i, sf := range peerChangeFields() {
  84. if rv.Field(i).IsZero() {
  85. continue
  86. }
  87. switch sf.Name {
  88. default:
  89. // Unhandled field.
  90. return nil, false
  91. case "NodeID":
  92. continue
  93. case "DERPRegion":
  94. ret = append(ret, NodeMutationDERPHome{mutatingNodeID(p.NodeID), p.DERPRegion})
  95. case "Endpoints":
  96. ret = append(ret, NodeMutationEndpoints{mutatingNodeID(p.NodeID), slices.Clone(p.Endpoints)})
  97. case "Online":
  98. ret = append(ret, NodeMutationOnline{mutatingNodeID(p.NodeID), *p.Online})
  99. case "LastSeen":
  100. ret = append(ret, NodeMutationLastSeen{mutatingNodeID(p.NodeID), *p.LastSeen})
  101. case "Cap":
  102. ret = append(ret, NodeMutationCap{mutatingNodeID(p.NodeID), p.Cap})
  103. }
  104. }
  105. return ret, true
  106. }
  107. // MutationsFromMapResponse returns all the discrete node mutations described
  108. // by res. It returns ok=false if res contains any non-patch field as defined
  109. // by mapResponseContainsNonPatchFields.
  110. func MutationsFromMapResponse(res *tailcfg.MapResponse, now time.Time) (ret []NodeMutation, ok bool) {
  111. if now.IsZero() {
  112. now = time.Now()
  113. }
  114. if mapResponseContainsNonPatchFields(res) {
  115. return nil, false
  116. }
  117. // All that remains is PeersChangedPatch, OnlineChange, and LastSeenChange.
  118. for _, p := range res.PeersChangedPatch {
  119. deltas, ok := NodeMutationsFromPatch(p)
  120. if !ok {
  121. return nil, false
  122. }
  123. ret = append(ret, deltas...)
  124. }
  125. for nid, v := range res.OnlineChange {
  126. ret = append(ret, NodeMutationOnline{mutatingNodeID(nid), v})
  127. }
  128. for nid, v := range res.PeerSeenChange {
  129. if v {
  130. ret = append(ret, NodeMutationLastSeen{mutatingNodeID(nid), now})
  131. }
  132. }
  133. slices.SortStableFunc(ret, func(a, b NodeMutation) int {
  134. return cmp.Compare(a.NodeIDBeingMutated(), b.NodeIDBeingMutated())
  135. })
  136. return ret, true
  137. }
  138. // mapResponseContainsNonPatchFields reports whether res contains only "patch"
  139. // fields set (PeersChangedPatch primarily, but also including the legacy
  140. // PeerSeenChange and OnlineChange fields).
  141. //
  142. // It ignores any of the meta fields that are handled by PollNetMap before the
  143. // peer change handling gets involved.
  144. //
  145. // The purpose of this function is to ask whether this is a tricky enough
  146. // MapResponse to warrant a full netmap update. When this returns false, it
  147. // means the response can be handled incrementally, patching up the local state.
  148. func mapResponseContainsNonPatchFields(res *tailcfg.MapResponse) bool {
  149. return res.Node != nil ||
  150. res.DERPMap != nil ||
  151. res.DNSConfig != nil ||
  152. res.Domain != "" ||
  153. res.CollectServices != "" ||
  154. res.PacketFilter != nil ||
  155. res.PacketFilters != nil ||
  156. res.UserProfiles != nil ||
  157. res.Health != nil ||
  158. res.DisplayMessages != nil ||
  159. res.SSHPolicy != nil ||
  160. res.TKAInfo != nil ||
  161. res.DomainDataPlaneAuditLogID != "" ||
  162. res.Debug != nil ||
  163. res.ControlDialPlan != nil ||
  164. res.ClientVersion != nil ||
  165. res.Peers != nil ||
  166. res.PeersRemoved != nil ||
  167. // PeersChanged is too coarse to be considered a patch. Also, we convert
  168. // PeersChanged to PeersChangedPatch in patchifyPeersChanged before this
  169. // function is called, so it should never be set anyway. But for
  170. // completedness, and for tests, check it too:
  171. res.PeersChanged != nil ||
  172. res.DefaultAutoUpdate != ""
  173. }