map_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package controlclient
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "reflect"
  8. "strings"
  9. "testing"
  10. "time"
  11. "go4.org/mem"
  12. "tailscale.com/tailcfg"
  13. "tailscale.com/tstest"
  14. "tailscale.com/types/key"
  15. "tailscale.com/types/netmap"
  16. "tailscale.com/types/opt"
  17. "tailscale.com/types/ptr"
  18. "tailscale.com/util/must"
  19. )
  20. func TestUndeltaPeers(t *testing.T) {
  21. var curTime time.Time
  22. tstest.Replace(t, &clockNow, func() time.Time {
  23. return curTime
  24. })
  25. online := func(v bool) func(*tailcfg.Node) {
  26. return func(n *tailcfg.Node) {
  27. n.Online = &v
  28. }
  29. }
  30. seenAt := func(t time.Time) func(*tailcfg.Node) {
  31. return func(n *tailcfg.Node) {
  32. n.LastSeen = &t
  33. }
  34. }
  35. withDERP := func(d string) func(*tailcfg.Node) {
  36. return func(n *tailcfg.Node) {
  37. n.DERP = d
  38. }
  39. }
  40. withEP := func(ep string) func(*tailcfg.Node) {
  41. return func(n *tailcfg.Node) {
  42. n.Endpoints = []string{ep}
  43. }
  44. }
  45. n := func(id tailcfg.NodeID, name string, mod ...func(*tailcfg.Node)) *tailcfg.Node {
  46. n := &tailcfg.Node{ID: id, Name: name}
  47. for _, f := range mod {
  48. f(n)
  49. }
  50. return n
  51. }
  52. peers := func(nv ...*tailcfg.Node) []*tailcfg.Node { return nv }
  53. tests := []struct {
  54. name string
  55. mapRes *tailcfg.MapResponse
  56. curTime time.Time
  57. prev []*tailcfg.Node
  58. want []*tailcfg.Node
  59. }{
  60. {
  61. name: "full_peers",
  62. mapRes: &tailcfg.MapResponse{
  63. Peers: peers(n(1, "foo"), n(2, "bar")),
  64. },
  65. want: peers(n(1, "foo"), n(2, "bar")),
  66. },
  67. {
  68. name: "full_peers_ignores_deltas",
  69. mapRes: &tailcfg.MapResponse{
  70. Peers: peers(n(1, "foo"), n(2, "bar")),
  71. PeersRemoved: []tailcfg.NodeID{2},
  72. },
  73. want: peers(n(1, "foo"), n(2, "bar")),
  74. },
  75. {
  76. name: "add_and_update",
  77. prev: peers(n(1, "foo"), n(2, "bar")),
  78. mapRes: &tailcfg.MapResponse{
  79. PeersChanged: peers(n(0, "zero"), n(2, "bar2"), n(3, "three")),
  80. },
  81. want: peers(n(0, "zero"), n(1, "foo"), n(2, "bar2"), n(3, "three")),
  82. },
  83. {
  84. name: "remove",
  85. prev: peers(n(1, "foo"), n(2, "bar")),
  86. mapRes: &tailcfg.MapResponse{
  87. PeersRemoved: []tailcfg.NodeID{1},
  88. },
  89. want: peers(n(2, "bar")),
  90. },
  91. {
  92. name: "add_and_remove",
  93. prev: peers(n(1, "foo"), n(2, "bar")),
  94. mapRes: &tailcfg.MapResponse{
  95. PeersChanged: peers(n(1, "foo2")),
  96. PeersRemoved: []tailcfg.NodeID{2},
  97. },
  98. want: peers(n(1, "foo2")),
  99. },
  100. {
  101. name: "unchanged",
  102. prev: peers(n(1, "foo"), n(2, "bar")),
  103. mapRes: &tailcfg.MapResponse{},
  104. want: peers(n(1, "foo"), n(2, "bar")),
  105. },
  106. {
  107. name: "online_change",
  108. prev: peers(n(1, "foo"), n(2, "bar")),
  109. mapRes: &tailcfg.MapResponse{
  110. OnlineChange: map[tailcfg.NodeID]bool{
  111. 1: true,
  112. },
  113. },
  114. want: peers(
  115. n(1, "foo", online(true)),
  116. n(2, "bar"),
  117. ),
  118. },
  119. {
  120. name: "online_change_offline",
  121. prev: peers(n(1, "foo"), n(2, "bar")),
  122. mapRes: &tailcfg.MapResponse{
  123. OnlineChange: map[tailcfg.NodeID]bool{
  124. 1: false,
  125. 2: true,
  126. },
  127. },
  128. want: peers(
  129. n(1, "foo", online(false)),
  130. n(2, "bar", online(true)),
  131. ),
  132. },
  133. {
  134. name: "peer_seen_at",
  135. prev: peers(n(1, "foo", seenAt(time.Unix(111, 0))), n(2, "bar")),
  136. curTime: time.Unix(123, 0),
  137. mapRes: &tailcfg.MapResponse{
  138. PeerSeenChange: map[tailcfg.NodeID]bool{
  139. 1: false,
  140. 2: true,
  141. },
  142. },
  143. want: peers(
  144. n(1, "foo"),
  145. n(2, "bar", seenAt(time.Unix(123, 0))),
  146. ),
  147. },
  148. {
  149. name: "ep_change_derp",
  150. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"))),
  151. mapRes: &tailcfg.MapResponse{
  152. PeersChangedPatch: []*tailcfg.PeerChange{{
  153. NodeID: 1,
  154. DERPRegion: 4,
  155. }},
  156. },
  157. want: peers(n(1, "foo", withDERP("127.3.3.40:4"))),
  158. },
  159. {
  160. name: "ep_change_udp",
  161. prev: peers(n(1, "foo", withEP("1.2.3.4:111"))),
  162. mapRes: &tailcfg.MapResponse{
  163. PeersChangedPatch: []*tailcfg.PeerChange{{
  164. NodeID: 1,
  165. Endpoints: []string{"1.2.3.4:56"},
  166. }},
  167. },
  168. want: peers(n(1, "foo", withEP("1.2.3.4:56"))),
  169. },
  170. {
  171. name: "ep_change_udp",
  172. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
  173. mapRes: &tailcfg.MapResponse{
  174. PeersChangedPatch: []*tailcfg.PeerChange{{
  175. NodeID: 1,
  176. Endpoints: []string{"1.2.3.4:56"},
  177. }},
  178. },
  179. want: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:56"))),
  180. },
  181. {
  182. name: "ep_change_both",
  183. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
  184. mapRes: &tailcfg.MapResponse{
  185. PeersChangedPatch: []*tailcfg.PeerChange{{
  186. NodeID: 1,
  187. DERPRegion: 2,
  188. Endpoints: []string{"1.2.3.4:56"},
  189. }},
  190. },
  191. want: peers(n(1, "foo", withDERP("127.3.3.40:2"), withEP("1.2.3.4:56"))),
  192. },
  193. {
  194. name: "change_key",
  195. prev: peers(n(1, "foo")),
  196. mapRes: &tailcfg.MapResponse{
  197. PeersChangedPatch: []*tailcfg.PeerChange{{
  198. NodeID: 1,
  199. Key: ptr.To(key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  200. }},
  201. }, want: peers(&tailcfg.Node{
  202. ID: 1,
  203. Name: "foo",
  204. Key: key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  205. }),
  206. },
  207. {
  208. name: "change_key_signature",
  209. prev: peers(n(1, "foo")),
  210. mapRes: &tailcfg.MapResponse{
  211. PeersChangedPatch: []*tailcfg.PeerChange{{
  212. NodeID: 1,
  213. KeySignature: []byte{3, 4},
  214. }},
  215. }, want: peers(&tailcfg.Node{
  216. ID: 1,
  217. Name: "foo",
  218. KeySignature: []byte{3, 4},
  219. }),
  220. },
  221. {
  222. name: "change_disco_key",
  223. prev: peers(n(1, "foo")),
  224. mapRes: &tailcfg.MapResponse{
  225. PeersChangedPatch: []*tailcfg.PeerChange{{
  226. NodeID: 1,
  227. DiscoKey: ptr.To(key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  228. }},
  229. }, want: peers(&tailcfg.Node{
  230. ID: 1,
  231. Name: "foo",
  232. DiscoKey: key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  233. }),
  234. },
  235. {
  236. name: "change_online",
  237. prev: peers(n(1, "foo")),
  238. mapRes: &tailcfg.MapResponse{
  239. PeersChangedPatch: []*tailcfg.PeerChange{{
  240. NodeID: 1,
  241. Online: ptr.To(true),
  242. }},
  243. }, want: peers(&tailcfg.Node{
  244. ID: 1,
  245. Name: "foo",
  246. Online: ptr.To(true),
  247. }),
  248. },
  249. {
  250. name: "change_last_seen",
  251. prev: peers(n(1, "foo")),
  252. mapRes: &tailcfg.MapResponse{
  253. PeersChangedPatch: []*tailcfg.PeerChange{{
  254. NodeID: 1,
  255. LastSeen: ptr.To(time.Unix(123, 0).UTC()),
  256. }},
  257. }, want: peers(&tailcfg.Node{
  258. ID: 1,
  259. Name: "foo",
  260. LastSeen: ptr.To(time.Unix(123, 0).UTC()),
  261. }),
  262. },
  263. {
  264. name: "change_key_expiry",
  265. prev: peers(n(1, "foo")),
  266. mapRes: &tailcfg.MapResponse{
  267. PeersChangedPatch: []*tailcfg.PeerChange{{
  268. NodeID: 1,
  269. KeyExpiry: ptr.To(time.Unix(123, 0).UTC()),
  270. }},
  271. }, want: peers(&tailcfg.Node{
  272. ID: 1,
  273. Name: "foo",
  274. KeyExpiry: time.Unix(123, 0).UTC(),
  275. }),
  276. },
  277. {
  278. name: "change_capabilities",
  279. prev: peers(n(1, "foo")),
  280. mapRes: &tailcfg.MapResponse{
  281. PeersChangedPatch: []*tailcfg.PeerChange{{
  282. NodeID: 1,
  283. Capabilities: ptr.To([]string{"foo"}),
  284. }},
  285. }, want: peers(&tailcfg.Node{
  286. ID: 1,
  287. Name: "foo",
  288. Capabilities: []string{"foo"},
  289. }),
  290. }}
  291. for _, tt := range tests {
  292. t.Run(tt.name, func(t *testing.T) {
  293. if !tt.curTime.IsZero() {
  294. curTime = tt.curTime
  295. }
  296. undeltaPeers(tt.mapRes, tt.prev)
  297. if !reflect.DeepEqual(tt.mapRes.Peers, tt.want) {
  298. t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(tt.mapRes.Peers), formatNodes(tt.want))
  299. }
  300. })
  301. }
  302. }
  303. func formatNodes(nodes []*tailcfg.Node) string {
  304. var sb strings.Builder
  305. for i, n := range nodes {
  306. if i > 0 {
  307. sb.WriteString(", ")
  308. }
  309. fmt.Fprintf(&sb, "(%d, %q", n.ID, n.Name)
  310. if n.Online != nil {
  311. fmt.Fprintf(&sb, ", online=%v", *n.Online)
  312. }
  313. if n.LastSeen != nil {
  314. fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen.Unix())
  315. }
  316. if n.Key != (key.NodePublic{}) {
  317. fmt.Fprintf(&sb, ", key=%v", n.Key.String())
  318. }
  319. if n.Expired {
  320. fmt.Fprintf(&sb, ", expired=true")
  321. }
  322. sb.WriteString(")")
  323. }
  324. return sb.String()
  325. }
  326. func newTestMapSession(t *testing.T) *mapSession {
  327. ms := newMapSession(key.NewNode())
  328. ms.logf = t.Logf
  329. return ms
  330. }
  331. func TestNetmapForResponse(t *testing.T) {
  332. t.Run("implicit_packetfilter", func(t *testing.T) {
  333. somePacketFilter := []tailcfg.FilterRule{
  334. {
  335. SrcIPs: []string{"*"},
  336. DstPorts: []tailcfg.NetPortRange{
  337. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  338. },
  339. },
  340. }
  341. ms := newTestMapSession(t)
  342. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  343. Node: new(tailcfg.Node),
  344. PacketFilter: somePacketFilter,
  345. })
  346. if len(nm1.PacketFilter) == 0 {
  347. t.Fatalf("zero length PacketFilter")
  348. }
  349. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  350. Node: new(tailcfg.Node),
  351. PacketFilter: nil, // testing that the server can omit this.
  352. })
  353. if len(nm1.PacketFilter) == 0 {
  354. t.Fatalf("zero length PacketFilter in 2nd netmap")
  355. }
  356. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  357. t.Error("packet filters differ")
  358. }
  359. })
  360. t.Run("implicit_dnsconfig", func(t *testing.T) {
  361. someDNSConfig := &tailcfg.DNSConfig{Domains: []string{"foo", "bar"}}
  362. ms := newTestMapSession(t)
  363. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  364. Node: new(tailcfg.Node),
  365. DNSConfig: someDNSConfig,
  366. })
  367. if !reflect.DeepEqual(nm1.DNS, *someDNSConfig) {
  368. t.Fatalf("1st DNS wrong")
  369. }
  370. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  371. Node: new(tailcfg.Node),
  372. DNSConfig: nil, // implicit
  373. })
  374. if !reflect.DeepEqual(nm2.DNS, *someDNSConfig) {
  375. t.Fatalf("2nd DNS wrong")
  376. }
  377. })
  378. t.Run("collect_services", func(t *testing.T) {
  379. ms := newTestMapSession(t)
  380. var nm *netmap.NetworkMap
  381. wantCollect := func(v bool) {
  382. t.Helper()
  383. if nm.CollectServices != v {
  384. t.Errorf("netmap.CollectServices = %v; want %v", nm.CollectServices, v)
  385. }
  386. }
  387. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  388. Node: new(tailcfg.Node),
  389. })
  390. wantCollect(false)
  391. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  392. Node: new(tailcfg.Node),
  393. CollectServices: "false",
  394. })
  395. wantCollect(false)
  396. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  397. Node: new(tailcfg.Node),
  398. CollectServices: "true",
  399. })
  400. wantCollect(true)
  401. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  402. Node: new(tailcfg.Node),
  403. CollectServices: "",
  404. })
  405. wantCollect(true)
  406. })
  407. t.Run("implicit_domain", func(t *testing.T) {
  408. ms := newTestMapSession(t)
  409. var nm *netmap.NetworkMap
  410. want := func(v string) {
  411. t.Helper()
  412. if nm.Domain != v {
  413. t.Errorf("netmap.Domain = %q; want %q", nm.Domain, v)
  414. }
  415. }
  416. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  417. Node: new(tailcfg.Node),
  418. Domain: "foo.com",
  419. })
  420. want("foo.com")
  421. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  422. Node: new(tailcfg.Node),
  423. })
  424. want("foo.com")
  425. })
  426. t.Run("implicit_node", func(t *testing.T) {
  427. someNode := &tailcfg.Node{
  428. Name: "foo",
  429. }
  430. wantNode := &tailcfg.Node{
  431. Name: "foo",
  432. ComputedName: "foo",
  433. ComputedNameWithHost: "foo",
  434. }
  435. ms := newTestMapSession(t)
  436. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  437. Node: someNode,
  438. })
  439. if nm1.SelfNode == nil {
  440. t.Fatal("nil Node in 1st netmap")
  441. }
  442. if !reflect.DeepEqual(nm1.SelfNode, wantNode) {
  443. j, _ := json.Marshal(nm1.SelfNode)
  444. t.Errorf("Node mismatch in 1st netmap; got: %s", j)
  445. }
  446. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{})
  447. if nm2.SelfNode == nil {
  448. t.Fatal("nil Node in 1st netmap")
  449. }
  450. if !reflect.DeepEqual(nm2.SelfNode, wantNode) {
  451. j, _ := json.Marshal(nm2.SelfNode)
  452. t.Errorf("Node mismatch in 2nd netmap; got: %s", j)
  453. }
  454. })
  455. }
  456. // TestDeltaDebug tests that tailcfg.Debug values can be omitted in MapResponses
  457. // entirely or have their opt.Bool values unspecified between MapResponses in a
  458. // session and that should mean no change. (as of capver 37). But two Debug
  459. // fields existed prior to capver 37 that weren't opt.Bool; we test that we both
  460. // still accept the non-opt.Bool form from control for RandomizeClientPort and
  461. // ForceBackgroundSTUN and also accept the new form, keeping the old form in
  462. // sync.
  463. func TestDeltaDebug(t *testing.T) {
  464. type step struct {
  465. got *tailcfg.Debug
  466. want *tailcfg.Debug
  467. }
  468. tests := []struct {
  469. name string
  470. steps []step
  471. }{
  472. {
  473. name: "nothing-to-nothing",
  474. steps: []step{
  475. {nil, nil},
  476. {nil, nil},
  477. },
  478. },
  479. {
  480. name: "sticky-with-old-style-randomize-client-port",
  481. steps: []step{
  482. {
  483. &tailcfg.Debug{RandomizeClientPort: true},
  484. &tailcfg.Debug{
  485. RandomizeClientPort: true,
  486. SetRandomizeClientPort: "true",
  487. },
  488. },
  489. {
  490. nil, // not sent by server
  491. &tailcfg.Debug{
  492. RandomizeClientPort: true,
  493. SetRandomizeClientPort: "true",
  494. },
  495. },
  496. },
  497. },
  498. {
  499. name: "sticky-with-new-style-randomize-client-port",
  500. steps: []step{
  501. {
  502. &tailcfg.Debug{SetRandomizeClientPort: "true"},
  503. &tailcfg.Debug{
  504. RandomizeClientPort: true,
  505. SetRandomizeClientPort: "true",
  506. },
  507. },
  508. {
  509. nil, // not sent by server
  510. &tailcfg.Debug{
  511. RandomizeClientPort: true,
  512. SetRandomizeClientPort: "true",
  513. },
  514. },
  515. },
  516. },
  517. {
  518. name: "opt-bool-sticky-changing-over-time",
  519. steps: []step{
  520. {nil, nil},
  521. {nil, nil},
  522. {
  523. &tailcfg.Debug{OneCGNATRoute: "true"},
  524. &tailcfg.Debug{OneCGNATRoute: "true"},
  525. },
  526. {
  527. nil,
  528. &tailcfg.Debug{OneCGNATRoute: "true"},
  529. },
  530. {
  531. &tailcfg.Debug{OneCGNATRoute: "false"},
  532. &tailcfg.Debug{OneCGNATRoute: "false"},
  533. },
  534. {
  535. nil,
  536. &tailcfg.Debug{OneCGNATRoute: "false"},
  537. },
  538. },
  539. },
  540. {
  541. name: "legacy-ForceBackgroundSTUN",
  542. steps: []step{
  543. {
  544. &tailcfg.Debug{ForceBackgroundSTUN: true},
  545. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  546. },
  547. },
  548. },
  549. {
  550. name: "opt-bool-SetForceBackgroundSTUN",
  551. steps: []step{
  552. {
  553. &tailcfg.Debug{SetForceBackgroundSTUN: "true"},
  554. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  555. },
  556. },
  557. },
  558. {
  559. name: "server-reset-to-default",
  560. steps: []step{
  561. {
  562. &tailcfg.Debug{SetForceBackgroundSTUN: "true"},
  563. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  564. },
  565. {
  566. &tailcfg.Debug{SetForceBackgroundSTUN: "unset"},
  567. &tailcfg.Debug{ForceBackgroundSTUN: false, SetForceBackgroundSTUN: "unset"},
  568. },
  569. },
  570. },
  571. }
  572. for _, tt := range tests {
  573. t.Run(tt.name, func(t *testing.T) {
  574. ms := newTestMapSession(t)
  575. for stepi, s := range tt.steps {
  576. nm := ms.netmapForResponse(&tailcfg.MapResponse{Debug: s.got})
  577. if !reflect.DeepEqual(nm.Debug, s.want) {
  578. t.Errorf("unexpected result at step index %v; got: %s", stepi, must.Get(json.Marshal(nm.Debug)))
  579. }
  580. }
  581. })
  582. }
  583. }
  584. // Verifies that copyDebugOptBools doesn't missing any opt.Bools.
  585. func TestCopyDebugOptBools(t *testing.T) {
  586. rt := reflect.TypeOf(tailcfg.Debug{})
  587. for i := 0; i < rt.NumField(); i++ {
  588. sf := rt.Field(i)
  589. if sf.Type != reflect.TypeOf(opt.Bool("")) {
  590. continue
  591. }
  592. var src, dst tailcfg.Debug
  593. reflect.ValueOf(&src).Elem().Field(i).Set(reflect.ValueOf(opt.Bool("true")))
  594. if src == (tailcfg.Debug{}) {
  595. t.Fatalf("failed to set field %v", sf.Name)
  596. }
  597. copyDebugOptBools(&dst, &src)
  598. if src != dst {
  599. t.Fatalf("copyDebugOptBools didn't copy field %v", sf.Name)
  600. }
  601. }
  602. }