map_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  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_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: ptrTo(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: ptrTo(true),
  242. }},
  243. }, want: peers(&tailcfg.Node{
  244. ID: 1,
  245. Name: "foo",
  246. Online: ptrTo(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: ptrTo(time.Unix(123, 0).UTC()),
  256. }},
  257. }, want: peers(&tailcfg.Node{
  258. ID: 1,
  259. Name: "foo",
  260. LastSeen: ptrTo(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: ptrTo(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: ptrTo([]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 ptrTo[T any](v T) *T {
  304. return &v
  305. }
  306. func formatNodes(nodes []*tailcfg.Node) string {
  307. var sb strings.Builder
  308. for i, n := range nodes {
  309. if i > 0 {
  310. sb.WriteString(", ")
  311. }
  312. var extra string
  313. if n.Online != nil {
  314. extra += fmt.Sprintf(", online=%v", *n.Online)
  315. }
  316. if n.LastSeen != nil {
  317. extra += fmt.Sprintf(", lastSeen=%v", n.LastSeen.Unix())
  318. }
  319. fmt.Fprintf(&sb, "(%d, %q%s)", n.ID, n.Name, extra)
  320. }
  321. return sb.String()
  322. }
  323. func newTestMapSession(t *testing.T) *mapSession {
  324. return newMapSession(key.NewNode())
  325. }
  326. func TestNetmapForResponse(t *testing.T) {
  327. t.Run("implicit_packetfilter", func(t *testing.T) {
  328. somePacketFilter := []tailcfg.FilterRule{
  329. {
  330. SrcIPs: []string{"*"},
  331. DstPorts: []tailcfg.NetPortRange{
  332. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  333. },
  334. },
  335. }
  336. ms := newTestMapSession(t)
  337. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  338. Node: new(tailcfg.Node),
  339. PacketFilter: somePacketFilter,
  340. })
  341. if len(nm1.PacketFilter) == 0 {
  342. t.Fatalf("zero length PacketFilter")
  343. }
  344. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  345. Node: new(tailcfg.Node),
  346. PacketFilter: nil, // testing that the server can omit this.
  347. })
  348. if len(nm1.PacketFilter) == 0 {
  349. t.Fatalf("zero length PacketFilter in 2nd netmap")
  350. }
  351. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  352. t.Error("packet filters differ")
  353. }
  354. })
  355. t.Run("implicit_dnsconfig", func(t *testing.T) {
  356. someDNSConfig := &tailcfg.DNSConfig{Domains: []string{"foo", "bar"}}
  357. ms := newTestMapSession(t)
  358. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  359. Node: new(tailcfg.Node),
  360. DNSConfig: someDNSConfig,
  361. })
  362. if !reflect.DeepEqual(nm1.DNS, *someDNSConfig) {
  363. t.Fatalf("1st DNS wrong")
  364. }
  365. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  366. Node: new(tailcfg.Node),
  367. DNSConfig: nil, // implicit
  368. })
  369. if !reflect.DeepEqual(nm2.DNS, *someDNSConfig) {
  370. t.Fatalf("2nd DNS wrong")
  371. }
  372. })
  373. t.Run("collect_services", func(t *testing.T) {
  374. ms := newTestMapSession(t)
  375. var nm *netmap.NetworkMap
  376. wantCollect := func(v bool) {
  377. t.Helper()
  378. if nm.CollectServices != v {
  379. t.Errorf("netmap.CollectServices = %v; want %v", nm.CollectServices, v)
  380. }
  381. }
  382. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  383. Node: new(tailcfg.Node),
  384. })
  385. wantCollect(false)
  386. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  387. Node: new(tailcfg.Node),
  388. CollectServices: "false",
  389. })
  390. wantCollect(false)
  391. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  392. Node: new(tailcfg.Node),
  393. CollectServices: "true",
  394. })
  395. wantCollect(true)
  396. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  397. Node: new(tailcfg.Node),
  398. CollectServices: "",
  399. })
  400. wantCollect(true)
  401. })
  402. t.Run("implicit_domain", func(t *testing.T) {
  403. ms := newTestMapSession(t)
  404. var nm *netmap.NetworkMap
  405. want := func(v string) {
  406. t.Helper()
  407. if nm.Domain != v {
  408. t.Errorf("netmap.Domain = %q; want %q", nm.Domain, v)
  409. }
  410. }
  411. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  412. Node: new(tailcfg.Node),
  413. Domain: "foo.com",
  414. })
  415. want("foo.com")
  416. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  417. Node: new(tailcfg.Node),
  418. })
  419. want("foo.com")
  420. })
  421. t.Run("implicit_node", func(t *testing.T) {
  422. someNode := &tailcfg.Node{
  423. Name: "foo",
  424. }
  425. wantNode := &tailcfg.Node{
  426. Name: "foo",
  427. ComputedName: "foo",
  428. ComputedNameWithHost: "foo",
  429. }
  430. ms := newTestMapSession(t)
  431. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  432. Node: someNode,
  433. })
  434. if nm1.SelfNode == nil {
  435. t.Fatal("nil Node in 1st netmap")
  436. }
  437. if !reflect.DeepEqual(nm1.SelfNode, wantNode) {
  438. j, _ := json.Marshal(nm1.SelfNode)
  439. t.Errorf("Node mismatch in 1st netmap; got: %s", j)
  440. }
  441. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{})
  442. if nm2.SelfNode == nil {
  443. t.Fatal("nil Node in 1st netmap")
  444. }
  445. if !reflect.DeepEqual(nm2.SelfNode, wantNode) {
  446. j, _ := json.Marshal(nm2.SelfNode)
  447. t.Errorf("Node mismatch in 2nd netmap; got: %s", j)
  448. }
  449. })
  450. }
  451. // TestDeltaDebug tests that tailcfg.Debug values can be omitted in MapResponses
  452. // entirely or have their opt.Bool values unspecified between MapResponses in a
  453. // session and that should mean no change. (as of capver 37). But two Debug
  454. // fields existed prior to capver 37 that weren't opt.Bool; we test that we both
  455. // still accept the non-opt.Bool form from control for RandomizeClientPort and
  456. // ForceBackgroundSTUN and also accept the new form, keeping the old form in
  457. // sync.
  458. func TestDeltaDebug(t *testing.T) {
  459. type step struct {
  460. got *tailcfg.Debug
  461. want *tailcfg.Debug
  462. }
  463. tests := []struct {
  464. name string
  465. steps []step
  466. }{
  467. {
  468. name: "nothing-to-nothing",
  469. steps: []step{
  470. {nil, nil},
  471. {nil, nil},
  472. },
  473. },
  474. {
  475. name: "sticky-with-old-style-randomize-client-port",
  476. steps: []step{
  477. {
  478. &tailcfg.Debug{RandomizeClientPort: true},
  479. &tailcfg.Debug{
  480. RandomizeClientPort: true,
  481. SetRandomizeClientPort: "true",
  482. },
  483. },
  484. {
  485. nil, // not sent by server
  486. &tailcfg.Debug{
  487. RandomizeClientPort: true,
  488. SetRandomizeClientPort: "true",
  489. },
  490. },
  491. },
  492. },
  493. {
  494. name: "sticky-with-new-style-randomize-client-port",
  495. steps: []step{
  496. {
  497. &tailcfg.Debug{SetRandomizeClientPort: "true"},
  498. &tailcfg.Debug{
  499. RandomizeClientPort: true,
  500. SetRandomizeClientPort: "true",
  501. },
  502. },
  503. {
  504. nil, // not sent by server
  505. &tailcfg.Debug{
  506. RandomizeClientPort: true,
  507. SetRandomizeClientPort: "true",
  508. },
  509. },
  510. },
  511. },
  512. {
  513. name: "opt-bool-sticky-changing-over-time",
  514. steps: []step{
  515. {nil, nil},
  516. {nil, nil},
  517. {
  518. &tailcfg.Debug{OneCGNATRoute: "true"},
  519. &tailcfg.Debug{OneCGNATRoute: "true"},
  520. },
  521. {
  522. nil,
  523. &tailcfg.Debug{OneCGNATRoute: "true"},
  524. },
  525. {
  526. &tailcfg.Debug{OneCGNATRoute: "false"},
  527. &tailcfg.Debug{OneCGNATRoute: "false"},
  528. },
  529. {
  530. nil,
  531. &tailcfg.Debug{OneCGNATRoute: "false"},
  532. },
  533. },
  534. },
  535. {
  536. name: "legacy-ForceBackgroundSTUN",
  537. steps: []step{
  538. {
  539. &tailcfg.Debug{ForceBackgroundSTUN: true},
  540. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  541. },
  542. },
  543. },
  544. {
  545. name: "opt-bool-SetForceBackgroundSTUN",
  546. steps: []step{
  547. {
  548. &tailcfg.Debug{SetForceBackgroundSTUN: "true"},
  549. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  550. },
  551. },
  552. },
  553. {
  554. name: "server-reset-to-default",
  555. steps: []step{
  556. {
  557. &tailcfg.Debug{SetForceBackgroundSTUN: "true"},
  558. &tailcfg.Debug{ForceBackgroundSTUN: true, SetForceBackgroundSTUN: "true"},
  559. },
  560. {
  561. &tailcfg.Debug{SetForceBackgroundSTUN: "unset"},
  562. &tailcfg.Debug{ForceBackgroundSTUN: false, SetForceBackgroundSTUN: "unset"},
  563. },
  564. },
  565. },
  566. }
  567. for _, tt := range tests {
  568. t.Run(tt.name, func(t *testing.T) {
  569. ms := newTestMapSession(t)
  570. for stepi, s := range tt.steps {
  571. nm := ms.netmapForResponse(&tailcfg.MapResponse{Debug: s.got})
  572. if !reflect.DeepEqual(nm.Debug, s.want) {
  573. t.Errorf("unexpected result at step index %v; got: %s", stepi, must.Get(json.Marshal(nm.Debug)))
  574. }
  575. }
  576. })
  577. }
  578. }
  579. // Verifies that copyDebugOptBools doesn't missing any opt.Bools.
  580. func TestCopyDebugOptBools(t *testing.T) {
  581. rt := reflect.TypeOf(tailcfg.Debug{})
  582. for i := 0; i < rt.NumField(); i++ {
  583. sf := rt.Field(i)
  584. if sf.Type != reflect.TypeOf(opt.Bool("")) {
  585. continue
  586. }
  587. var src, dst tailcfg.Debug
  588. reflect.ValueOf(&src).Elem().Field(i).Set(reflect.ValueOf(opt.Bool("true")))
  589. if src == (tailcfg.Debug{}) {
  590. t.Fatalf("failed to set field %v", sf.Name)
  591. }
  592. copyDebugOptBools(&dst, &src)
  593. if src != dst {
  594. t.Fatalf("copyDebugOptBools didn't copy field %v", sf.Name)
  595. }
  596. }
  597. }