nodemut.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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. var peerChangeFields = sync.OnceValue(func() []reflect.StructField {
  58. var fields []reflect.StructField
  59. rt := reflect.TypeFor[tailcfg.PeerChange]()
  60. for i := range rt.NumField() {
  61. fields = append(fields, rt.Field(i))
  62. }
  63. return fields
  64. })
  65. // NodeMutationsFromPatch returns the NodeMutations that
  66. // p describes. If p describes something not yet supported
  67. // by a specific NodeMutation type, it returns (nil, false).
  68. func NodeMutationsFromPatch(p *tailcfg.PeerChange) (_ []NodeMutation, ok bool) {
  69. if p == nil || p.NodeID == 0 {
  70. return nil, false
  71. }
  72. var ret []NodeMutation
  73. rv := reflect.ValueOf(p).Elem()
  74. for i, sf := range peerChangeFields() {
  75. if rv.Field(i).IsZero() {
  76. continue
  77. }
  78. switch sf.Name {
  79. default:
  80. // Unhandled field.
  81. return nil, false
  82. case "NodeID":
  83. continue
  84. case "DERPRegion":
  85. ret = append(ret, NodeMutationDERPHome{mutatingNodeID(p.NodeID), p.DERPRegion})
  86. case "Endpoints":
  87. ret = append(ret, NodeMutationEndpoints{mutatingNodeID(p.NodeID), slices.Clone(p.Endpoints)})
  88. case "Online":
  89. ret = append(ret, NodeMutationOnline{mutatingNodeID(p.NodeID), *p.Online})
  90. case "LastSeen":
  91. ret = append(ret, NodeMutationLastSeen{mutatingNodeID(p.NodeID), *p.LastSeen})
  92. }
  93. }
  94. return ret, true
  95. }
  96. // MutationsFromMapResponse returns all the discrete node mutations described
  97. // by res. It returns ok=false if res contains any non-patch field as defined
  98. // by mapResponseContainsNonPatchFields.
  99. func MutationsFromMapResponse(res *tailcfg.MapResponse, now time.Time) (ret []NodeMutation, ok bool) {
  100. if now.IsZero() {
  101. now = time.Now()
  102. }
  103. if mapResponseContainsNonPatchFields(res) {
  104. return nil, false
  105. }
  106. // All that remains is PeersChangedPatch, OnlineChange, and LastSeenChange.
  107. for _, p := range res.PeersChangedPatch {
  108. deltas, ok := NodeMutationsFromPatch(p)
  109. if !ok {
  110. return nil, false
  111. }
  112. ret = append(ret, deltas...)
  113. }
  114. for nid, v := range res.OnlineChange {
  115. ret = append(ret, NodeMutationOnline{mutatingNodeID(nid), v})
  116. }
  117. for nid, v := range res.PeerSeenChange {
  118. if v {
  119. ret = append(ret, NodeMutationLastSeen{mutatingNodeID(nid), now})
  120. }
  121. }
  122. slices.SortStableFunc(ret, func(a, b NodeMutation) int {
  123. return cmp.Compare(a.NodeIDBeingMutated(), b.NodeIDBeingMutated())
  124. })
  125. return ret, true
  126. }
  127. // mapResponseContainsNonPatchFields reports whether res contains only "patch"
  128. // fields set (PeersChangedPatch primarily, but also including the legacy
  129. // PeerSeenChange and OnlineChange fields).
  130. //
  131. // It ignores any of the meta fields that are handled by PollNetMap before the
  132. // peer change handling gets involved.
  133. //
  134. // The purpose of this function is to ask whether this is a tricky enough
  135. // MapResponse to warrant a full netmap update. When this returns false, it
  136. // means the response can be handled incrementally, patching up the local state.
  137. func mapResponseContainsNonPatchFields(res *tailcfg.MapResponse) bool {
  138. return res.Node != nil ||
  139. res.DERPMap != nil ||
  140. res.DNSConfig != nil ||
  141. res.Domain != "" ||
  142. res.CollectServices != "" ||
  143. res.PacketFilter != nil ||
  144. res.PacketFilters != nil ||
  145. res.UserProfiles != nil ||
  146. res.Health != nil ||
  147. res.DisplayMessages != nil ||
  148. res.SSHPolicy != nil ||
  149. res.TKAInfo != nil ||
  150. res.DomainDataPlaneAuditLogID != "" ||
  151. res.Debug != nil ||
  152. res.ControlDialPlan != nil ||
  153. res.ClientVersion != nil ||
  154. res.Peers != nil ||
  155. res.PeersRemoved != nil ||
  156. // PeersChanged is too coarse to be considered a patch. Also, we convert
  157. // PeersChanged to PeersChangedPatch in patchifyPeersChanged before this
  158. // function is called, so it should never be set anyway. But for
  159. // completedness, and for tests, check it too:
  160. res.PeersChanged != nil ||
  161. res.DeprecatedDefaultAutoUpdate != ""
  162. }