map_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package controlclient
  4. import (
  5. "context"
  6. "encoding/json"
  7. "fmt"
  8. "net/netip"
  9. "reflect"
  10. "strings"
  11. "sync/atomic"
  12. "testing"
  13. "time"
  14. "github.com/google/go-cmp/cmp"
  15. "go4.org/mem"
  16. "tailscale.com/control/controlknobs"
  17. "tailscale.com/tailcfg"
  18. "tailscale.com/tstest"
  19. "tailscale.com/tstime"
  20. "tailscale.com/types/dnstype"
  21. "tailscale.com/types/key"
  22. "tailscale.com/types/logger"
  23. "tailscale.com/types/netmap"
  24. "tailscale.com/types/ptr"
  25. "tailscale.com/util/mak"
  26. "tailscale.com/util/must"
  27. )
  28. func eps(s ...string) []netip.AddrPort {
  29. var eps []netip.AddrPort
  30. for _, ep := range s {
  31. eps = append(eps, netip.MustParseAddrPort(ep))
  32. }
  33. return eps
  34. }
  35. func TestUpdatePeersStateFromResponse(t *testing.T) {
  36. var curTime time.Time
  37. online := func(v bool) func(*tailcfg.Node) {
  38. return func(n *tailcfg.Node) {
  39. n.Online = &v
  40. }
  41. }
  42. seenAt := func(t time.Time) func(*tailcfg.Node) {
  43. return func(n *tailcfg.Node) {
  44. n.LastSeen = &t
  45. }
  46. }
  47. withDERP := func(d string) func(*tailcfg.Node) {
  48. return func(n *tailcfg.Node) {
  49. n.DERP = d
  50. }
  51. }
  52. withEP := func(ep string) func(*tailcfg.Node) {
  53. return func(n *tailcfg.Node) {
  54. n.Endpoints = []netip.AddrPort{netip.MustParseAddrPort(ep)}
  55. }
  56. }
  57. n := func(id tailcfg.NodeID, name string, mod ...func(*tailcfg.Node)) *tailcfg.Node {
  58. n := &tailcfg.Node{ID: id, Name: name}
  59. for _, f := range mod {
  60. f(n)
  61. }
  62. return n
  63. }
  64. peers := func(nv ...*tailcfg.Node) []*tailcfg.Node { return nv }
  65. tests := []struct {
  66. name string
  67. mapRes *tailcfg.MapResponse
  68. curTime time.Time
  69. prev []*tailcfg.Node
  70. want []*tailcfg.Node
  71. wantStats updateStats
  72. }{
  73. {
  74. name: "full_peers",
  75. mapRes: &tailcfg.MapResponse{
  76. Peers: peers(n(1, "foo"), n(2, "bar")),
  77. },
  78. want: peers(n(1, "foo"), n(2, "bar")),
  79. wantStats: updateStats{
  80. allNew: true,
  81. added: 2,
  82. },
  83. },
  84. {
  85. name: "full_peers_ignores_deltas",
  86. mapRes: &tailcfg.MapResponse{
  87. Peers: peers(n(1, "foo"), n(2, "bar")),
  88. PeersRemoved: []tailcfg.NodeID{2},
  89. },
  90. want: peers(n(1, "foo"), n(2, "bar")),
  91. wantStats: updateStats{
  92. allNew: true,
  93. added: 2,
  94. },
  95. },
  96. {
  97. name: "add_and_update",
  98. prev: peers(n(1, "foo"), n(2, "bar")),
  99. mapRes: &tailcfg.MapResponse{
  100. PeersChanged: peers(n(0, "zero"), n(2, "bar2"), n(3, "three")),
  101. },
  102. want: peers(n(0, "zero"), n(1, "foo"), n(2, "bar2"), n(3, "three")),
  103. wantStats: updateStats{
  104. added: 2, // added IDs 0 and 3
  105. changed: 1, // changed ID 2
  106. },
  107. },
  108. {
  109. name: "remove",
  110. prev: peers(n(1, "foo"), n(2, "bar")),
  111. mapRes: &tailcfg.MapResponse{
  112. PeersRemoved: []tailcfg.NodeID{1, 3, 4},
  113. },
  114. want: peers(n(2, "bar")),
  115. wantStats: updateStats{
  116. removed: 1, // ID 1
  117. },
  118. },
  119. {
  120. name: "add_and_remove",
  121. prev: peers(n(1, "foo"), n(2, "bar")),
  122. mapRes: &tailcfg.MapResponse{
  123. PeersChanged: peers(n(1, "foo2")),
  124. PeersRemoved: []tailcfg.NodeID{2},
  125. },
  126. want: peers(n(1, "foo2")),
  127. wantStats: updateStats{
  128. changed: 1,
  129. removed: 1,
  130. },
  131. },
  132. {
  133. name: "unchanged",
  134. prev: peers(n(1, "foo"), n(2, "bar")),
  135. mapRes: &tailcfg.MapResponse{},
  136. want: peers(n(1, "foo"), n(2, "bar")),
  137. },
  138. {
  139. name: "online_change",
  140. prev: peers(n(1, "foo"), n(2, "bar")),
  141. mapRes: &tailcfg.MapResponse{
  142. OnlineChange: map[tailcfg.NodeID]bool{
  143. 1: true,
  144. 404: true,
  145. },
  146. },
  147. want: peers(
  148. n(1, "foo", online(true)),
  149. n(2, "bar"),
  150. ),
  151. wantStats: updateStats{changed: 1},
  152. },
  153. {
  154. name: "online_change_offline",
  155. prev: peers(n(1, "foo"), n(2, "bar")),
  156. mapRes: &tailcfg.MapResponse{
  157. OnlineChange: map[tailcfg.NodeID]bool{
  158. 1: false,
  159. 2: true,
  160. },
  161. },
  162. want: peers(
  163. n(1, "foo", online(false)),
  164. n(2, "bar", online(true)),
  165. ),
  166. wantStats: updateStats{changed: 2},
  167. },
  168. {
  169. name: "peer_seen_at",
  170. prev: peers(n(1, "foo", seenAt(time.Unix(111, 0))), n(2, "bar")),
  171. curTime: time.Unix(123, 0),
  172. mapRes: &tailcfg.MapResponse{
  173. PeerSeenChange: map[tailcfg.NodeID]bool{
  174. 1: false,
  175. 2: true,
  176. },
  177. },
  178. want: peers(
  179. n(1, "foo"),
  180. n(2, "bar", seenAt(time.Unix(123, 0))),
  181. ),
  182. wantStats: updateStats{changed: 2},
  183. },
  184. {
  185. name: "ep_change_derp",
  186. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"))),
  187. mapRes: &tailcfg.MapResponse{
  188. PeersChangedPatch: []*tailcfg.PeerChange{{
  189. NodeID: 1,
  190. DERPRegion: 4,
  191. }},
  192. },
  193. want: peers(n(1, "foo", withDERP("127.3.3.40:4"))),
  194. wantStats: updateStats{changed: 1},
  195. },
  196. {
  197. name: "ep_change_udp",
  198. prev: peers(n(1, "foo", withEP("1.2.3.4:111"))),
  199. mapRes: &tailcfg.MapResponse{
  200. PeersChangedPatch: []*tailcfg.PeerChange{{
  201. NodeID: 1,
  202. Endpoints: eps("1.2.3.4:56"),
  203. }},
  204. },
  205. want: peers(n(1, "foo", withEP("1.2.3.4:56"))),
  206. wantStats: updateStats{changed: 1},
  207. },
  208. {
  209. name: "ep_change_udp_2",
  210. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
  211. mapRes: &tailcfg.MapResponse{
  212. PeersChangedPatch: []*tailcfg.PeerChange{{
  213. NodeID: 1,
  214. Endpoints: eps("1.2.3.4:56"),
  215. }},
  216. },
  217. want: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:56"))),
  218. wantStats: updateStats{changed: 1},
  219. },
  220. {
  221. name: "ep_change_both",
  222. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
  223. mapRes: &tailcfg.MapResponse{
  224. PeersChangedPatch: []*tailcfg.PeerChange{{
  225. NodeID: 1,
  226. DERPRegion: 2,
  227. Endpoints: eps("1.2.3.4:56"),
  228. }},
  229. },
  230. want: peers(n(1, "foo", withDERP("127.3.3.40:2"), withEP("1.2.3.4:56"))),
  231. wantStats: updateStats{changed: 1},
  232. },
  233. {
  234. name: "change_key",
  235. prev: peers(n(1, "foo")),
  236. mapRes: &tailcfg.MapResponse{
  237. PeersChangedPatch: []*tailcfg.PeerChange{{
  238. NodeID: 1,
  239. Key: ptr.To(key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  240. }},
  241. }, want: peers(&tailcfg.Node{
  242. ID: 1,
  243. Name: "foo",
  244. Key: key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  245. }),
  246. wantStats: updateStats{changed: 1},
  247. },
  248. {
  249. name: "change_key_signature",
  250. prev: peers(n(1, "foo")),
  251. mapRes: &tailcfg.MapResponse{
  252. PeersChangedPatch: []*tailcfg.PeerChange{{
  253. NodeID: 1,
  254. KeySignature: []byte{3, 4},
  255. }},
  256. },
  257. want: peers(&tailcfg.Node{
  258. ID: 1,
  259. Name: "foo",
  260. KeySignature: []byte{3, 4},
  261. }),
  262. wantStats: updateStats{changed: 1},
  263. },
  264. {
  265. name: "change_disco_key",
  266. prev: peers(n(1, "foo")),
  267. mapRes: &tailcfg.MapResponse{
  268. PeersChangedPatch: []*tailcfg.PeerChange{{
  269. NodeID: 1,
  270. DiscoKey: ptr.To(key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  271. }},
  272. },
  273. want: peers(&tailcfg.Node{
  274. ID: 1,
  275. Name: "foo",
  276. DiscoKey: key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  277. }),
  278. wantStats: updateStats{changed: 1},
  279. },
  280. {
  281. name: "change_online",
  282. prev: peers(n(1, "foo")),
  283. mapRes: &tailcfg.MapResponse{
  284. PeersChangedPatch: []*tailcfg.PeerChange{{
  285. NodeID: 1,
  286. Online: ptr.To(true),
  287. }},
  288. },
  289. want: peers(&tailcfg.Node{
  290. ID: 1,
  291. Name: "foo",
  292. Online: ptr.To(true),
  293. }),
  294. wantStats: updateStats{changed: 1},
  295. },
  296. {
  297. name: "change_last_seen",
  298. prev: peers(n(1, "foo")),
  299. mapRes: &tailcfg.MapResponse{
  300. PeersChangedPatch: []*tailcfg.PeerChange{{
  301. NodeID: 1,
  302. LastSeen: ptr.To(time.Unix(123, 0).UTC()),
  303. }},
  304. },
  305. want: peers(&tailcfg.Node{
  306. ID: 1,
  307. Name: "foo",
  308. LastSeen: ptr.To(time.Unix(123, 0).UTC()),
  309. }),
  310. wantStats: updateStats{changed: 1},
  311. },
  312. {
  313. name: "change_key_expiry",
  314. prev: peers(n(1, "foo")),
  315. mapRes: &tailcfg.MapResponse{
  316. PeersChangedPatch: []*tailcfg.PeerChange{{
  317. NodeID: 1,
  318. KeyExpiry: ptr.To(time.Unix(123, 0).UTC()),
  319. }},
  320. },
  321. want: peers(&tailcfg.Node{
  322. ID: 1,
  323. Name: "foo",
  324. KeyExpiry: time.Unix(123, 0).UTC(),
  325. }),
  326. wantStats: updateStats{changed: 1},
  327. },
  328. {
  329. name: "change_capabilities",
  330. prev: peers(n(1, "foo")),
  331. mapRes: &tailcfg.MapResponse{
  332. PeersChangedPatch: []*tailcfg.PeerChange{{
  333. NodeID: 1,
  334. Capabilities: ptr.To([]tailcfg.NodeCapability{"foo"}),
  335. }},
  336. },
  337. want: peers(&tailcfg.Node{
  338. ID: 1,
  339. Name: "foo",
  340. Capabilities: []tailcfg.NodeCapability{"foo"},
  341. }),
  342. wantStats: updateStats{changed: 1},
  343. }}
  344. for _, tt := range tests {
  345. t.Run(tt.name, func(t *testing.T) {
  346. if !tt.curTime.IsZero() {
  347. curTime = tt.curTime
  348. tstest.Replace(t, &clock, tstime.Clock(tstest.NewClock(tstest.ClockOpts{Start: curTime})))
  349. }
  350. ms := newTestMapSession(t, nil)
  351. for _, n := range tt.prev {
  352. mak.Set(&ms.peers, n.ID, ptr.To(n.View()))
  353. }
  354. ms.rebuildSorted()
  355. gotStats := ms.updatePeersStateFromResponse(tt.mapRes)
  356. got := make([]*tailcfg.Node, len(ms.sortedPeers))
  357. for i, vp := range ms.sortedPeers {
  358. got[i] = vp.AsStruct()
  359. }
  360. if gotStats != tt.wantStats {
  361. t.Errorf("got stats = %+v; want %+v", gotStats, tt.wantStats)
  362. }
  363. if !reflect.DeepEqual(got, tt.want) {
  364. t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(got), formatNodes(tt.want))
  365. }
  366. })
  367. }
  368. }
  369. func formatNodes(nodes []*tailcfg.Node) string {
  370. var sb strings.Builder
  371. for i, n := range nodes {
  372. if i > 0 {
  373. sb.WriteString(", ")
  374. }
  375. fmt.Fprintf(&sb, "(%d, %q", n.ID, n.Name)
  376. if n.Online != nil {
  377. fmt.Fprintf(&sb, ", online=%v", *n.Online)
  378. }
  379. if n.LastSeen != nil {
  380. fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen.Unix())
  381. }
  382. if n.Key != (key.NodePublic{}) {
  383. fmt.Fprintf(&sb, ", key=%v", n.Key.String())
  384. }
  385. if n.Expired {
  386. fmt.Fprintf(&sb, ", expired=true")
  387. }
  388. sb.WriteString(")")
  389. }
  390. return sb.String()
  391. }
  392. func newTestMapSession(t testing.TB, nu NetmapUpdater) *mapSession {
  393. ms := newMapSession(key.NewNode(), nu, new(controlknobs.Knobs))
  394. t.Cleanup(ms.Close)
  395. ms.logf = t.Logf
  396. return ms
  397. }
  398. func (ms *mapSession) netmapForResponse(res *tailcfg.MapResponse) *netmap.NetworkMap {
  399. ms.updateStateFromResponse(res)
  400. return ms.netmap()
  401. }
  402. func TestNetmapForResponse(t *testing.T) {
  403. t.Run("implicit_packetfilter", func(t *testing.T) {
  404. somePacketFilter := []tailcfg.FilterRule{
  405. {
  406. SrcIPs: []string{"*"},
  407. DstPorts: []tailcfg.NetPortRange{
  408. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  409. },
  410. },
  411. }
  412. ms := newTestMapSession(t, nil)
  413. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  414. Node: new(tailcfg.Node),
  415. PacketFilter: somePacketFilter,
  416. })
  417. if len(nm1.PacketFilter) == 0 {
  418. t.Fatalf("zero length PacketFilter")
  419. }
  420. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  421. Node: new(tailcfg.Node),
  422. PacketFilter: nil, // testing that the server can omit this.
  423. })
  424. if len(nm1.PacketFilter) == 0 {
  425. t.Fatalf("zero length PacketFilter in 2nd netmap")
  426. }
  427. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  428. t.Error("packet filters differ")
  429. }
  430. })
  431. t.Run("implicit_dnsconfig", func(t *testing.T) {
  432. someDNSConfig := &tailcfg.DNSConfig{Domains: []string{"foo", "bar"}}
  433. ms := newTestMapSession(t, nil)
  434. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  435. Node: new(tailcfg.Node),
  436. DNSConfig: someDNSConfig,
  437. })
  438. if !reflect.DeepEqual(nm1.DNS, *someDNSConfig) {
  439. t.Fatalf("1st DNS wrong")
  440. }
  441. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  442. Node: new(tailcfg.Node),
  443. DNSConfig: nil, // implicit
  444. })
  445. if !reflect.DeepEqual(nm2.DNS, *someDNSConfig) {
  446. t.Fatalf("2nd DNS wrong")
  447. }
  448. })
  449. t.Run("collect_services", func(t *testing.T) {
  450. ms := newTestMapSession(t, nil)
  451. var nm *netmap.NetworkMap
  452. wantCollect := func(v bool) {
  453. t.Helper()
  454. if nm.CollectServices != v {
  455. t.Errorf("netmap.CollectServices = %v; want %v", nm.CollectServices, v)
  456. }
  457. }
  458. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  459. Node: new(tailcfg.Node),
  460. })
  461. wantCollect(false)
  462. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  463. Node: new(tailcfg.Node),
  464. CollectServices: "false",
  465. })
  466. wantCollect(false)
  467. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  468. Node: new(tailcfg.Node),
  469. CollectServices: "true",
  470. })
  471. wantCollect(true)
  472. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  473. Node: new(tailcfg.Node),
  474. CollectServices: "",
  475. })
  476. wantCollect(true)
  477. })
  478. t.Run("implicit_domain", func(t *testing.T) {
  479. ms := newTestMapSession(t, nil)
  480. var nm *netmap.NetworkMap
  481. want := func(v string) {
  482. t.Helper()
  483. if nm.Domain != v {
  484. t.Errorf("netmap.Domain = %q; want %q", nm.Domain, v)
  485. }
  486. }
  487. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  488. Node: new(tailcfg.Node),
  489. Domain: "foo.com",
  490. })
  491. want("foo.com")
  492. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  493. Node: new(tailcfg.Node),
  494. })
  495. want("foo.com")
  496. })
  497. t.Run("implicit_node", func(t *testing.T) {
  498. someNode := &tailcfg.Node{
  499. Name: "foo",
  500. }
  501. wantNode := (&tailcfg.Node{
  502. Name: "foo",
  503. ComputedName: "foo",
  504. ComputedNameWithHost: "foo",
  505. }).View()
  506. ms := newTestMapSession(t, nil)
  507. mapRes := &tailcfg.MapResponse{
  508. Node: someNode,
  509. }
  510. initDisplayNames(mapRes.Node.View(), mapRes)
  511. ms.updateStateFromResponse(mapRes)
  512. nm1 := ms.netmap()
  513. if !nm1.SelfNode.Valid() {
  514. t.Fatal("nil Node in 1st netmap")
  515. }
  516. if !reflect.DeepEqual(nm1.SelfNode, wantNode) {
  517. j, _ := json.Marshal(nm1.SelfNode)
  518. t.Errorf("Node mismatch in 1st netmap; got: %s", j)
  519. }
  520. ms.updateStateFromResponse(&tailcfg.MapResponse{})
  521. nm2 := ms.netmap()
  522. if !nm2.SelfNode.Valid() {
  523. t.Fatal("nil Node in 1st netmap")
  524. }
  525. if !reflect.DeepEqual(nm2.SelfNode, wantNode) {
  526. j, _ := json.Marshal(nm2.SelfNode)
  527. t.Errorf("Node mismatch in 2nd netmap; got: %s", j)
  528. }
  529. })
  530. }
  531. func TestDeltaDERPMap(t *testing.T) {
  532. regions1 := map[int]*tailcfg.DERPRegion{
  533. 1: {
  534. RegionID: 1,
  535. Nodes: []*tailcfg.DERPNode{{
  536. Name: "derp1a",
  537. RegionID: 1,
  538. HostName: "derp1a" + tailcfg.DotInvalid,
  539. IPv4: "169.254.169.254",
  540. IPv6: "none",
  541. }},
  542. },
  543. }
  544. // As above, but with a changed IPv4 addr
  545. regions2 := map[int]*tailcfg.DERPRegion{1: regions1[1].Clone()}
  546. regions2[1].Nodes[0].IPv4 = "127.0.0.1"
  547. type step struct {
  548. got *tailcfg.DERPMap
  549. want *tailcfg.DERPMap
  550. }
  551. tests := []struct {
  552. name string
  553. steps []step
  554. }{
  555. {
  556. name: "nothing-to-nothing",
  557. steps: []step{
  558. {nil, nil},
  559. {nil, nil},
  560. },
  561. },
  562. {
  563. name: "regions-sticky",
  564. steps: []step{
  565. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  566. {&tailcfg.DERPMap{}, &tailcfg.DERPMap{Regions: regions1}},
  567. },
  568. },
  569. {
  570. name: "regions-change",
  571. steps: []step{
  572. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  573. {&tailcfg.DERPMap{Regions: regions2}, &tailcfg.DERPMap{Regions: regions2}},
  574. },
  575. },
  576. {
  577. name: "home-params",
  578. steps: []step{
  579. // Send a DERP map
  580. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  581. // Send home params, want to still have the same regions
  582. {
  583. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{
  584. RegionScore: map[int]float64{1: 0.5},
  585. }},
  586. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  587. RegionScore: map[int]float64{1: 0.5},
  588. }},
  589. },
  590. },
  591. },
  592. {
  593. name: "home-params-sub-fields",
  594. steps: []step{
  595. // Send a DERP map with home params
  596. {
  597. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  598. RegionScore: map[int]float64{1: 0.5},
  599. }},
  600. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  601. RegionScore: map[int]float64{1: 0.5},
  602. }},
  603. },
  604. // Sending a struct with a 'HomeParams' field but nil RegionScore doesn't change home params...
  605. {
  606. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{RegionScore: nil}},
  607. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  608. RegionScore: map[int]float64{1: 0.5},
  609. }},
  610. },
  611. // ... but sending one with a non-nil and empty RegionScore field zeroes that out.
  612. {
  613. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{RegionScore: map[int]float64{}}},
  614. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  615. RegionScore: map[int]float64{},
  616. }},
  617. },
  618. },
  619. },
  620. }
  621. for _, tt := range tests {
  622. t.Run(tt.name, func(t *testing.T) {
  623. ms := newTestMapSession(t, nil)
  624. for stepi, s := range tt.steps {
  625. nm := ms.netmapForResponse(&tailcfg.MapResponse{DERPMap: s.got})
  626. if !reflect.DeepEqual(nm.DERPMap, s.want) {
  627. t.Errorf("unexpected result at step index %v; got: %s", stepi, logger.AsJSON(nm.DERPMap))
  628. }
  629. }
  630. })
  631. }
  632. }
  633. func TestPeerChangeDiff(t *testing.T) {
  634. tests := []struct {
  635. name string
  636. a, b *tailcfg.Node
  637. want *tailcfg.PeerChange // nil means want ok=false, unless wantEqual is set
  638. wantEqual bool // means test wants (nil, true)
  639. }{
  640. {
  641. name: "eq",
  642. a: &tailcfg.Node{ID: 1},
  643. b: &tailcfg.Node{ID: 1},
  644. wantEqual: true,
  645. },
  646. {
  647. name: "patch-derp",
  648. a: &tailcfg.Node{ID: 1, DERP: "127.3.3.40:1"},
  649. b: &tailcfg.Node{ID: 1, DERP: "127.3.3.40:2"},
  650. want: &tailcfg.PeerChange{NodeID: 1, DERPRegion: 2},
  651. },
  652. {
  653. name: "patch-endpoints",
  654. a: &tailcfg.Node{ID: 1, Endpoints: eps("10.0.0.1:1")},
  655. b: &tailcfg.Node{ID: 1, Endpoints: eps("10.0.0.2:2")},
  656. want: &tailcfg.PeerChange{NodeID: 1, Endpoints: eps("10.0.0.2:2")},
  657. },
  658. {
  659. name: "patch-cap",
  660. a: &tailcfg.Node{ID: 1, Cap: 1},
  661. b: &tailcfg.Node{ID: 1, Cap: 2},
  662. want: &tailcfg.PeerChange{NodeID: 1, Cap: 2},
  663. },
  664. {
  665. name: "patch-lastseen",
  666. a: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(1, 0))},
  667. b: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
  668. want: &tailcfg.PeerChange{NodeID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
  669. },
  670. {
  671. name: "patch-capabilities-to-nonempty",
  672. a: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"foo"}},
  673. b: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"bar"}},
  674. want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]tailcfg.NodeCapability{"bar"})},
  675. },
  676. {
  677. name: "patch-capabilities-to-empty",
  678. a: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"foo"}},
  679. b: &tailcfg.Node{ID: 1},
  680. want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]tailcfg.NodeCapability(nil))},
  681. },
  682. {
  683. name: "patch-online-to-true",
  684. a: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
  685. b: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
  686. want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(true)},
  687. },
  688. {
  689. name: "patch-online-to-false",
  690. a: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
  691. b: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
  692. want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(false)},
  693. },
  694. {
  695. name: "mix-patchable-and-not",
  696. a: &tailcfg.Node{ID: 1, Cap: 1},
  697. b: &tailcfg.Node{ID: 1, Cap: 2, StableID: "foo"},
  698. want: nil,
  699. },
  700. {
  701. name: "miss-change-stableid",
  702. a: &tailcfg.Node{ID: 1},
  703. b: &tailcfg.Node{ID: 1, StableID: "diff"},
  704. want: nil,
  705. },
  706. {
  707. name: "miss-change-id",
  708. a: &tailcfg.Node{ID: 1},
  709. b: &tailcfg.Node{ID: 2},
  710. want: nil,
  711. },
  712. {
  713. name: "miss-change-name",
  714. a: &tailcfg.Node{ID: 1, Name: "foo"},
  715. b: &tailcfg.Node{ID: 1, Name: "bar"},
  716. want: nil,
  717. },
  718. {
  719. name: "miss-change-user",
  720. a: &tailcfg.Node{ID: 1, User: 1},
  721. b: &tailcfg.Node{ID: 1, User: 2},
  722. want: nil,
  723. },
  724. {
  725. name: "miss-change-masq-v4",
  726. a: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  727. b: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.2"))},
  728. want: nil,
  729. },
  730. {
  731. name: "miss-change-masq-v6",
  732. a: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  733. b: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3006"))},
  734. want: nil,
  735. }}
  736. for _, tt := range tests {
  737. t.Run(tt.name, func(t *testing.T) {
  738. pc, ok := peerChangeDiff(tt.a.View(), tt.b)
  739. if tt.wantEqual {
  740. if !ok || pc != nil {
  741. t.Errorf("got (%p, %v); want (nil, true); pc=%v", pc, ok, logger.AsJSON(pc))
  742. }
  743. return
  744. }
  745. if (pc != nil) != ok {
  746. t.Fatalf("inconsistent ok=%v, pc=%p", ok, pc)
  747. }
  748. if !reflect.DeepEqual(pc, tt.want) {
  749. t.Errorf("mismatch\n got: %v\nwant: %v\n", logger.AsJSON(pc), logger.AsJSON(tt.want))
  750. }
  751. })
  752. }
  753. }
  754. func TestPeerChangeDiffAllocs(t *testing.T) {
  755. a := &tailcfg.Node{ID: 1}
  756. b := &tailcfg.Node{ID: 1}
  757. n := testing.AllocsPerRun(10000, func() {
  758. diff, ok := peerChangeDiff(a.View(), b)
  759. if !ok || diff != nil {
  760. t.Fatalf("unexpected result: (%s, %v)", logger.AsJSON(diff), ok)
  761. }
  762. })
  763. if n != 0 {
  764. t.Errorf("allocs = %v; want 0", int(n))
  765. }
  766. }
  767. type countingNetmapUpdater struct {
  768. full atomic.Int64
  769. }
  770. func (nu *countingNetmapUpdater) UpdateFullNetmap(nm *netmap.NetworkMap) {
  771. nu.full.Add(1)
  772. }
  773. // tests (*mapSession).patchifyPeersChanged; smaller tests are in TestPeerChangeDiff
  774. func TestPatchifyPeersChanged(t *testing.T) {
  775. hi := (&tailcfg.Hostinfo{}).View()
  776. tests := []struct {
  777. name string
  778. mr0 *tailcfg.MapResponse // initial
  779. mr1 *tailcfg.MapResponse // incremental
  780. want *tailcfg.MapResponse // what the incremental one should've been mutated to
  781. }{
  782. {
  783. name: "change_one_endpoint",
  784. mr0: &tailcfg.MapResponse{
  785. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  786. Peers: []*tailcfg.Node{
  787. {ID: 1, Hostinfo: hi},
  788. },
  789. },
  790. mr1: &tailcfg.MapResponse{
  791. PeersChanged: []*tailcfg.Node{
  792. {ID: 1, Endpoints: eps("10.0.0.1:1111"), Hostinfo: hi},
  793. },
  794. },
  795. want: &tailcfg.MapResponse{
  796. PeersChanged: nil,
  797. PeersChangedPatch: []*tailcfg.PeerChange{
  798. {NodeID: 1, Endpoints: eps("10.0.0.1:1111")},
  799. },
  800. },
  801. },
  802. {
  803. name: "change_some",
  804. mr0: &tailcfg.MapResponse{
  805. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  806. Peers: []*tailcfg.Node{
  807. {ID: 1, DERP: "127.3.3.40:1", Hostinfo: hi},
  808. {ID: 2, DERP: "127.3.3.40:2", Hostinfo: hi},
  809. {ID: 3, DERP: "127.3.3.40:3", Hostinfo: hi},
  810. },
  811. },
  812. mr1: &tailcfg.MapResponse{
  813. PeersChanged: []*tailcfg.Node{
  814. {ID: 1, DERP: "127.3.3.40:11", Hostinfo: hi},
  815. {ID: 2, StableID: "other-change", Hostinfo: hi},
  816. {ID: 3, DERP: "127.3.3.40:33", Hostinfo: hi},
  817. {ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
  818. },
  819. },
  820. want: &tailcfg.MapResponse{
  821. PeersChanged: []*tailcfg.Node{
  822. {ID: 2, StableID: "other-change", Hostinfo: hi},
  823. {ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
  824. },
  825. PeersChangedPatch: []*tailcfg.PeerChange{
  826. {NodeID: 1, DERPRegion: 11},
  827. {NodeID: 3, DERPRegion: 33},
  828. },
  829. },
  830. },
  831. {
  832. name: "change_exitnodednsresolvers",
  833. mr0: &tailcfg.MapResponse{
  834. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  835. Peers: []*tailcfg.Node{
  836. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  837. },
  838. },
  839. mr1: &tailcfg.MapResponse{
  840. PeersChanged: []*tailcfg.Node{
  841. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  842. },
  843. },
  844. want: &tailcfg.MapResponse{
  845. PeersChanged: []*tailcfg.Node{
  846. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  847. },
  848. },
  849. },
  850. {
  851. name: "same_exitnoderesolvers",
  852. mr0: &tailcfg.MapResponse{
  853. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  854. Peers: []*tailcfg.Node{
  855. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  856. },
  857. },
  858. mr1: &tailcfg.MapResponse{
  859. PeersChanged: []*tailcfg.Node{
  860. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  861. },
  862. },
  863. want: &tailcfg.MapResponse{},
  864. },
  865. }
  866. for _, tt := range tests {
  867. t.Run(tt.name, func(t *testing.T) {
  868. nu := &countingNetmapUpdater{}
  869. ms := newTestMapSession(t, nu)
  870. ms.updateStateFromResponse(tt.mr0)
  871. mr1 := new(tailcfg.MapResponse)
  872. must.Do(json.Unmarshal(must.Get(json.Marshal(tt.mr1)), mr1))
  873. ms.patchifyPeersChanged(mr1)
  874. opts := []cmp.Option{
  875. cmp.Comparer(func(a, b netip.AddrPort) bool { return a == b }),
  876. }
  877. if diff := cmp.Diff(tt.want, mr1, opts...); diff != "" {
  878. t.Errorf("wrong result (-want +got):\n%s", diff)
  879. }
  880. })
  881. }
  882. }
  883. func BenchmarkMapSessionDelta(b *testing.B) {
  884. for _, size := range []int{10, 100, 1_000, 10_000} {
  885. b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
  886. ctx := context.Background()
  887. nu := &countingNetmapUpdater{}
  888. ms := newTestMapSession(b, nu)
  889. res := &tailcfg.MapResponse{
  890. Node: &tailcfg.Node{
  891. ID: 1,
  892. Name: "foo.bar.ts.net.",
  893. },
  894. }
  895. for i := 0; i < size; i++ {
  896. res.Peers = append(res.Peers, &tailcfg.Node{
  897. ID: tailcfg.NodeID(i + 2),
  898. Name: fmt.Sprintf("peer%d.bar.ts.net.", i),
  899. DERP: "127.3.3.40:10",
  900. Addresses: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  901. AllowedIPs: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  902. Endpoints: eps("192.168.1.2:345", "192.168.1.3:678"),
  903. Hostinfo: (&tailcfg.Hostinfo{
  904. OS: "fooOS",
  905. Hostname: "MyHostname",
  906. Services: []tailcfg.Service{
  907. {Proto: "peerapi4", Port: 1234},
  908. {Proto: "peerapi6", Port: 1234},
  909. {Proto: "peerapi-dns-proxy", Port: 1},
  910. },
  911. }).View(),
  912. LastSeen: ptr.To(time.Unix(int64(i), 0)),
  913. })
  914. }
  915. ms.HandleNonKeepAliveMapResponse(ctx, res)
  916. b.ResetTimer()
  917. b.ReportAllocs()
  918. // Now for the core of the benchmark loop, just toggle
  919. // a single node's online status.
  920. for i := 0; i < b.N; i++ {
  921. if err := ms.HandleNonKeepAliveMapResponse(ctx, &tailcfg.MapResponse{
  922. OnlineChange: map[tailcfg.NodeID]bool{
  923. 2: i%2 == 0,
  924. },
  925. }); err != nil {
  926. b.Fatal(err)
  927. }
  928. }
  929. })
  930. }
  931. }