map_test.go 24 KB

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