2
0

map_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. // Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package controlclient
  5. import (
  6. "encoding/json"
  7. "fmt"
  8. "reflect"
  9. "strings"
  10. "testing"
  11. "time"
  12. "go4.org/mem"
  13. "tailscale.com/tailcfg"
  14. "tailscale.com/types/key"
  15. "tailscale.com/types/netmap"
  16. "tailscale.com/types/opt"
  17. "tailscale.com/util/must"
  18. )
  19. func TestUndeltaPeers(t *testing.T) {
  20. defer func(old func() time.Time) { clockNow = old }(clockNow)
  21. var curTime time.Time
  22. 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: ptrTo(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_disco_key",
  209. prev: peers(n(1, "foo")),
  210. mapRes: &tailcfg.MapResponse{
  211. PeersChangedPatch: []*tailcfg.PeerChange{{
  212. NodeID: 1,
  213. DiscoKey: ptrTo(key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  214. }},
  215. }, want: peers(&tailcfg.Node{
  216. ID: 1,
  217. Name: "foo",
  218. DiscoKey: key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  219. }),
  220. },
  221. {
  222. name: "change_online",
  223. prev: peers(n(1, "foo")),
  224. mapRes: &tailcfg.MapResponse{
  225. PeersChangedPatch: []*tailcfg.PeerChange{{
  226. NodeID: 1,
  227. Online: ptrTo(true),
  228. }},
  229. }, want: peers(&tailcfg.Node{
  230. ID: 1,
  231. Name: "foo",
  232. Online: ptrTo(true),
  233. }),
  234. },
  235. {
  236. name: "change_last_seen",
  237. prev: peers(n(1, "foo")),
  238. mapRes: &tailcfg.MapResponse{
  239. PeersChangedPatch: []*tailcfg.PeerChange{{
  240. NodeID: 1,
  241. LastSeen: ptrTo(time.Unix(123, 0).UTC()),
  242. }},
  243. }, want: peers(&tailcfg.Node{
  244. ID: 1,
  245. Name: "foo",
  246. LastSeen: ptrTo(time.Unix(123, 0).UTC()),
  247. }),
  248. },
  249. {
  250. name: "change_key_expiry",
  251. prev: peers(n(1, "foo")),
  252. mapRes: &tailcfg.MapResponse{
  253. PeersChangedPatch: []*tailcfg.PeerChange{{
  254. NodeID: 1,
  255. KeyExpiry: ptrTo(time.Unix(123, 0).UTC()),
  256. }},
  257. }, want: peers(&tailcfg.Node{
  258. ID: 1,
  259. Name: "foo",
  260. KeyExpiry: time.Unix(123, 0).UTC(),
  261. }),
  262. },
  263. {
  264. name: "change_capabilities",
  265. prev: peers(n(1, "foo")),
  266. mapRes: &tailcfg.MapResponse{
  267. PeersChangedPatch: []*tailcfg.PeerChange{{
  268. NodeID: 1,
  269. Capabilities: ptrTo([]string{"foo"}),
  270. }},
  271. }, want: peers(&tailcfg.Node{
  272. ID: 1,
  273. Name: "foo",
  274. Capabilities: []string{"foo"},
  275. }),
  276. }}
  277. for _, tt := range tests {
  278. t.Run(tt.name, func(t *testing.T) {
  279. if !tt.curTime.IsZero() {
  280. curTime = tt.curTime
  281. }
  282. undeltaPeers(tt.mapRes, tt.prev)
  283. if !reflect.DeepEqual(tt.mapRes.Peers, tt.want) {
  284. t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(tt.mapRes.Peers), formatNodes(tt.want))
  285. }
  286. })
  287. }
  288. }
  289. func ptrTo[T any](v T) *T {
  290. return &v
  291. }
  292. func formatNodes(nodes []*tailcfg.Node) string {
  293. var sb strings.Builder
  294. for i, n := range nodes {
  295. if i > 0 {
  296. sb.WriteString(", ")
  297. }
  298. var extra string
  299. if n.Online != nil {
  300. extra += fmt.Sprintf(", online=%v", *n.Online)
  301. }
  302. if n.LastSeen != nil {
  303. extra += fmt.Sprintf(", lastSeen=%v", n.LastSeen.Unix())
  304. }
  305. fmt.Fprintf(&sb, "(%d, %q%s)", n.ID, n.Name, extra)
  306. }
  307. return sb.String()
  308. }
  309. func newTestMapSession(t *testing.T) *mapSession {
  310. return newMapSession(key.NewNode())
  311. }
  312. func TestNetmapForResponse(t *testing.T) {
  313. t.Run("implicit_packetfilter", func(t *testing.T) {
  314. somePacketFilter := []tailcfg.FilterRule{
  315. {
  316. SrcIPs: []string{"*"},
  317. DstPorts: []tailcfg.NetPortRange{
  318. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  319. },
  320. },
  321. }
  322. ms := newTestMapSession(t)
  323. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  324. Node: new(tailcfg.Node),
  325. PacketFilter: somePacketFilter,
  326. })
  327. if len(nm1.PacketFilter) == 0 {
  328. t.Fatalf("zero length PacketFilter")
  329. }
  330. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  331. Node: new(tailcfg.Node),
  332. PacketFilter: nil, // testing that the server can omit this.
  333. })
  334. if len(nm1.PacketFilter) == 0 {
  335. t.Fatalf("zero length PacketFilter in 2nd netmap")
  336. }
  337. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  338. t.Error("packet filters differ")
  339. }
  340. })
  341. t.Run("implicit_dnsconfig", func(t *testing.T) {
  342. someDNSConfig := &tailcfg.DNSConfig{Domains: []string{"foo", "bar"}}
  343. ms := newTestMapSession(t)
  344. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  345. Node: new(tailcfg.Node),
  346. DNSConfig: someDNSConfig,
  347. })
  348. if !reflect.DeepEqual(nm1.DNS, *someDNSConfig) {
  349. t.Fatalf("1st DNS wrong")
  350. }
  351. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  352. Node: new(tailcfg.Node),
  353. DNSConfig: nil, // implicit
  354. })
  355. if !reflect.DeepEqual(nm2.DNS, *someDNSConfig) {
  356. t.Fatalf("2nd DNS wrong")
  357. }
  358. })
  359. t.Run("collect_services", func(t *testing.T) {
  360. ms := newTestMapSession(t)
  361. var nm *netmap.NetworkMap
  362. wantCollect := func(v bool) {
  363. t.Helper()
  364. if nm.CollectServices != v {
  365. t.Errorf("netmap.CollectServices = %v; want %v", nm.CollectServices, v)
  366. }
  367. }
  368. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  369. Node: new(tailcfg.Node),
  370. })
  371. wantCollect(false)
  372. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  373. Node: new(tailcfg.Node),
  374. CollectServices: "false",
  375. })
  376. wantCollect(false)
  377. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  378. Node: new(tailcfg.Node),
  379. CollectServices: "true",
  380. })
  381. wantCollect(true)
  382. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  383. Node: new(tailcfg.Node),
  384. CollectServices: "",
  385. })
  386. wantCollect(true)
  387. })
  388. t.Run("implicit_domain", func(t *testing.T) {
  389. ms := newTestMapSession(t)
  390. var nm *netmap.NetworkMap
  391. want := func(v string) {
  392. t.Helper()
  393. if nm.Domain != v {
  394. t.Errorf("netmap.Domain = %q; want %q", nm.Domain, v)
  395. }
  396. }
  397. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  398. Node: new(tailcfg.Node),
  399. Domain: "foo.com",
  400. })
  401. want("foo.com")
  402. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  403. Node: new(tailcfg.Node),
  404. })
  405. want("foo.com")
  406. })
  407. t.Run("implicit_node", func(t *testing.T) {
  408. someNode := &tailcfg.Node{
  409. Name: "foo",
  410. }
  411. wantNode := &tailcfg.Node{
  412. Name: "foo",
  413. ComputedName: "foo",
  414. ComputedNameWithHost: "foo",
  415. }
  416. ms := newTestMapSession(t)
  417. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  418. Node: someNode,
  419. })
  420. if nm1.SelfNode == nil {
  421. t.Fatal("nil Node in 1st netmap")
  422. }
  423. if !reflect.DeepEqual(nm1.SelfNode, wantNode) {
  424. j, _ := json.Marshal(nm1.SelfNode)
  425. t.Errorf("Node mismatch in 1st netmap; got: %s", j)
  426. }
  427. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{})
  428. if nm2.SelfNode == nil {
  429. t.Fatal("nil Node in 1st netmap")
  430. }
  431. if !reflect.DeepEqual(nm2.SelfNode, wantNode) {
  432. j, _ := json.Marshal(nm2.SelfNode)
  433. t.Errorf("Node mismatch in 2nd netmap; got: %s", j)
  434. }
  435. })
  436. }
  437. // TestDeltaDebug tests that tailcfg.Debug values can be omitted in MapResposnes
  438. // entirely or have their opt.Bool values unspecified between MapResponses in a
  439. // session and that should mean no change. (as of capver 37). But two Debug
  440. // fields existed prior to capver 37 that weren't opt.Bool; we test that we both
  441. // still accept the non-opt.Bool form from control for RandomizeClientPort and
  442. // ForceBackgroundSTUN and also accept the new form, keeping the old form in
  443. // sync.
  444. func TestDeltaDebug(t *testing.T) {
  445. type step struct {
  446. got *tailcfg.Debug
  447. want *tailcfg.Debug
  448. }
  449. tests := []struct {
  450. name string
  451. steps []step
  452. }{
  453. {
  454. name: "nothing-to-nothing",
  455. steps: []step{
  456. {nil, nil},
  457. {nil, nil},
  458. },
  459. },
  460. {
  461. name: "sticky-with-old-style-randomize-client-port",
  462. steps: []step{
  463. {
  464. &tailcfg.Debug{RandomizeClientPort: true},
  465. &tailcfg.Debug{
  466. RandomizeClientPort: true,
  467. SetRandomizeClientPort: "true",
  468. },
  469. },
  470. {
  471. nil, // not sent by server
  472. &tailcfg.Debug{
  473. RandomizeClientPort: true,
  474. SetRandomizeClientPort: "true",
  475. },
  476. },
  477. },
  478. },
  479. {
  480. name: "sticky-with-new-style-randomize-client-port",
  481. steps: []step{
  482. {
  483. &tailcfg.Debug{SetRandomizeClientPort: "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: "opt-bool-sticky-changing-over-time",
  500. steps: []step{
  501. {nil, nil},
  502. {nil, nil},
  503. {
  504. &tailcfg.Debug{OneCGNATRoute: "true"},
  505. &tailcfg.Debug{OneCGNATRoute: "true"},
  506. },
  507. {
  508. nil,
  509. &tailcfg.Debug{OneCGNATRoute: "true"},
  510. },
  511. {
  512. &tailcfg.Debug{OneCGNATRoute: "false"},
  513. &tailcfg.Debug{OneCGNATRoute: "false"},
  514. },
  515. {
  516. nil,
  517. &tailcfg.Debug{OneCGNATRoute: "false"},
  518. },
  519. },
  520. },
  521. {
  522. name: "legacy-ForceBackgroundSTUN",
  523. steps: []step{
  524. {
  525. &tailcfg.Debug{ForceBackgroundSTUN: true},
  526. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  527. },
  528. },
  529. },
  530. {
  531. name: "opt-bool-SetForceBackgroundSTUN",
  532. steps: []step{
  533. {
  534. &tailcfg.Debug{SetForceBackgroundSTUN: "true"},
  535. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  536. },
  537. },
  538. },
  539. {
  540. name: "server-reset-to-default",
  541. steps: []step{
  542. {
  543. &tailcfg.Debug{SetForceBackgroundSTUN: "true"},
  544. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  545. },
  546. {
  547. &tailcfg.Debug{SetForceBackgroundSTUN: "unset"},
  548. &tailcfg.Debug{ForceBackgroundSTUN: false, SetForceBackgroundSTUN: "unset"},
  549. },
  550. },
  551. },
  552. }
  553. for _, tt := range tests {
  554. t.Run(tt.name, func(t *testing.T) {
  555. ms := newTestMapSession(t)
  556. for stepi, s := range tt.steps {
  557. nm := ms.netmapForResponse(&tailcfg.MapResponse{Debug: s.got})
  558. if !reflect.DeepEqual(nm.Debug, s.want) {
  559. t.Errorf("unexpected result at step index %v; got: %s", stepi, must.Get(json.Marshal(nm.Debug)))
  560. }
  561. }
  562. })
  563. }
  564. }
  565. // Verifies that copyDebugOptBools doesn't missing any opt.Bools.
  566. func TestCopyDebugOptBools(t *testing.T) {
  567. rt := reflect.TypeOf(tailcfg.Debug{})
  568. for i := 0; i < rt.NumField(); i++ {
  569. sf := rt.Field(i)
  570. if sf.Type != reflect.TypeOf(opt.Bool("")) {
  571. continue
  572. }
  573. var src, dst tailcfg.Debug
  574. reflect.ValueOf(&src).Elem().Field(i).Set(reflect.ValueOf(opt.Bool("true")))
  575. if src == (tailcfg.Debug{}) {
  576. t.Fatalf("failed to set field %v", sf.Name)
  577. }
  578. copyDebugOptBools(&dst, &src)
  579. if src != dst {
  580. t.Fatalf("copyDebugOptBools didn't copy field %v", sf.Name)
  581. }
  582. }
  583. }