map_test.go 15 KB

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