nodemut.go 5.2 KB

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