tailcfg_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package tailcfg_test
  4. import (
  5. "encoding"
  6. "encoding/json"
  7. "net/netip"
  8. "os"
  9. "reflect"
  10. "regexp"
  11. "strconv"
  12. "strings"
  13. "testing"
  14. "time"
  15. . "tailscale.com/tailcfg"
  16. "tailscale.com/types/key"
  17. "tailscale.com/types/opt"
  18. "tailscale.com/types/ptr"
  19. "tailscale.com/util/must"
  20. )
  21. func fieldsOf(t reflect.Type) (fields []string) {
  22. for i := 0; i < t.NumField(); i++ {
  23. fields = append(fields, t.Field(i).Name)
  24. }
  25. return
  26. }
  27. func TestHostinfoEqual(t *testing.T) {
  28. hiHandles := []string{
  29. "IPNVersion",
  30. "FrontendLogID",
  31. "BackendLogID",
  32. "OS",
  33. "OSVersion",
  34. "Container",
  35. "Env",
  36. "Distro",
  37. "DistroVersion",
  38. "DistroCodeName",
  39. "App",
  40. "Desktop",
  41. "Package",
  42. "DeviceModel",
  43. "PushDeviceToken",
  44. "Hostname",
  45. "ShieldsUp",
  46. "ShareeNode",
  47. "NoLogsNoSupport",
  48. "WireIngress",
  49. "AllowsUpdate",
  50. "Machine",
  51. "GoArch",
  52. "GoArchVar",
  53. "GoVersion",
  54. "RoutableIPs",
  55. "RequestTags",
  56. "WoLMACs",
  57. "Services",
  58. "NetInfo",
  59. "SSH_HostKeys",
  60. "Cloud",
  61. "Userspace",
  62. "UserspaceRouter",
  63. "AppConnector",
  64. "Location",
  65. }
  66. if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) {
  67. t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
  68. have, hiHandles)
  69. }
  70. nets := func(strs ...string) (ns []netip.Prefix) {
  71. for _, s := range strs {
  72. n, err := netip.ParsePrefix(s)
  73. if err != nil {
  74. panic(err)
  75. }
  76. ns = append(ns, n)
  77. }
  78. return ns
  79. }
  80. tests := []struct {
  81. a, b *Hostinfo
  82. want bool
  83. }{
  84. {
  85. nil,
  86. nil,
  87. true,
  88. },
  89. {
  90. &Hostinfo{},
  91. nil,
  92. false,
  93. },
  94. {
  95. nil,
  96. &Hostinfo{},
  97. false,
  98. },
  99. {
  100. &Hostinfo{},
  101. &Hostinfo{},
  102. true,
  103. },
  104. {
  105. &Hostinfo{IPNVersion: "1"},
  106. &Hostinfo{IPNVersion: "2"},
  107. false,
  108. },
  109. {
  110. &Hostinfo{IPNVersion: "2"},
  111. &Hostinfo{IPNVersion: "2"},
  112. true,
  113. },
  114. {
  115. &Hostinfo{FrontendLogID: "1"},
  116. &Hostinfo{FrontendLogID: "2"},
  117. false,
  118. },
  119. {
  120. &Hostinfo{FrontendLogID: "2"},
  121. &Hostinfo{FrontendLogID: "2"},
  122. true,
  123. },
  124. {
  125. &Hostinfo{BackendLogID: "1"},
  126. &Hostinfo{BackendLogID: "2"},
  127. false,
  128. },
  129. {
  130. &Hostinfo{BackendLogID: "2"},
  131. &Hostinfo{BackendLogID: "2"},
  132. true,
  133. },
  134. {
  135. &Hostinfo{OS: "windows"},
  136. &Hostinfo{OS: "linux"},
  137. false,
  138. },
  139. {
  140. &Hostinfo{OS: "windows"},
  141. &Hostinfo{OS: "windows"},
  142. true,
  143. },
  144. {
  145. &Hostinfo{Hostname: "vega"},
  146. &Hostinfo{Hostname: "iris"},
  147. false,
  148. },
  149. {
  150. &Hostinfo{Hostname: "vega"},
  151. &Hostinfo{Hostname: "vega"},
  152. true,
  153. },
  154. {
  155. &Hostinfo{RoutableIPs: nil},
  156. &Hostinfo{RoutableIPs: nets("10.0.0.0/16")},
  157. false,
  158. },
  159. {
  160. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
  161. &Hostinfo{RoutableIPs: nets("10.2.0.0/16", "192.168.2.0/24")},
  162. false,
  163. },
  164. {
  165. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
  166. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.2.0/24")},
  167. false,
  168. },
  169. {
  170. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
  171. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
  172. true,
  173. },
  174. {
  175. &Hostinfo{RequestTags: []string{"abc", "def"}},
  176. &Hostinfo{RequestTags: []string{"abc", "def"}},
  177. true,
  178. },
  179. {
  180. &Hostinfo{RequestTags: []string{"abc", "def"}},
  181. &Hostinfo{RequestTags: []string{"abc", "123"}},
  182. false,
  183. },
  184. {
  185. &Hostinfo{RequestTags: []string{}},
  186. &Hostinfo{RequestTags: []string{"abc"}},
  187. false,
  188. },
  189. {
  190. &Hostinfo{Services: []Service{{Proto: TCP, Port: 1234, Description: "foo"}}},
  191. &Hostinfo{Services: []Service{{Proto: UDP, Port: 2345, Description: "bar"}}},
  192. false,
  193. },
  194. {
  195. &Hostinfo{Services: []Service{{Proto: TCP, Port: 1234, Description: "foo"}}},
  196. &Hostinfo{Services: []Service{{Proto: TCP, Port: 1234, Description: "foo"}}},
  197. true,
  198. },
  199. {
  200. &Hostinfo{ShareeNode: true},
  201. &Hostinfo{},
  202. false,
  203. },
  204. {
  205. &Hostinfo{SSH_HostKeys: []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO.... root@bar"}},
  206. &Hostinfo{},
  207. false,
  208. },
  209. {
  210. &Hostinfo{App: "golink"},
  211. &Hostinfo{App: "abc"},
  212. false,
  213. },
  214. {
  215. &Hostinfo{App: "golink"},
  216. &Hostinfo{App: "golink"},
  217. true,
  218. },
  219. {
  220. &Hostinfo{AppConnector: opt.Bool("true")},
  221. &Hostinfo{AppConnector: opt.Bool("true")},
  222. true,
  223. },
  224. {
  225. &Hostinfo{AppConnector: opt.Bool("true")},
  226. &Hostinfo{AppConnector: opt.Bool("false")},
  227. false,
  228. },
  229. }
  230. for i, tt := range tests {
  231. got := tt.a.Equal(tt.b)
  232. if got != tt.want {
  233. t.Errorf("%d. Equal = %v; want %v", i, got, tt.want)
  234. }
  235. }
  236. }
  237. func TestHostinfoHowEqual(t *testing.T) {
  238. tests := []struct {
  239. a, b *Hostinfo
  240. want []string
  241. }{
  242. {
  243. a: nil,
  244. b: nil,
  245. want: nil,
  246. },
  247. {
  248. a: new(Hostinfo),
  249. b: nil,
  250. want: []string{"nil"},
  251. },
  252. {
  253. a: nil,
  254. b: new(Hostinfo),
  255. want: []string{"nil"},
  256. },
  257. {
  258. a: new(Hostinfo),
  259. b: new(Hostinfo),
  260. want: nil,
  261. },
  262. {
  263. a: &Hostinfo{
  264. IPNVersion: "1",
  265. ShieldsUp: false,
  266. RoutableIPs: []netip.Prefix{netip.MustParsePrefix("1.2.3.0/24")},
  267. },
  268. b: &Hostinfo{
  269. IPNVersion: "2",
  270. ShieldsUp: true,
  271. RoutableIPs: []netip.Prefix{netip.MustParsePrefix("1.2.3.0/25")},
  272. },
  273. want: []string{"IPNVersion", "ShieldsUp", "RoutableIPs"},
  274. },
  275. {
  276. a: &Hostinfo{
  277. IPNVersion: "1",
  278. },
  279. b: &Hostinfo{
  280. IPNVersion: "2",
  281. NetInfo: new(NetInfo),
  282. },
  283. want: []string{"IPNVersion", "NetInfo.nil"},
  284. },
  285. {
  286. a: &Hostinfo{
  287. IPNVersion: "1",
  288. NetInfo: &NetInfo{
  289. WorkingIPv6: "true",
  290. HavePortMap: true,
  291. LinkType: "foo",
  292. PreferredDERP: 123,
  293. DERPLatency: map[string]float64{
  294. "foo": 1.0,
  295. },
  296. },
  297. },
  298. b: &Hostinfo{
  299. IPNVersion: "2",
  300. NetInfo: &NetInfo{},
  301. },
  302. want: []string{"IPNVersion", "NetInfo.WorkingIPv6", "NetInfo.HavePortMap", "NetInfo.PreferredDERP", "NetInfo.LinkType", "NetInfo.DERPLatency"},
  303. },
  304. }
  305. for i, tt := range tests {
  306. got := tt.a.HowUnequal(tt.b)
  307. if !reflect.DeepEqual(got, tt.want) {
  308. t.Errorf("%d. got %q; want %q", i, got, tt.want)
  309. }
  310. }
  311. }
  312. func TestHostinfoTailscaleSSHEnabled(t *testing.T) {
  313. tests := []struct {
  314. hi *Hostinfo
  315. want bool
  316. }{
  317. {
  318. nil,
  319. false,
  320. },
  321. {
  322. &Hostinfo{},
  323. false,
  324. },
  325. {
  326. &Hostinfo{SSH_HostKeys: []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO.... root@bar"}},
  327. true,
  328. },
  329. }
  330. for i, tt := range tests {
  331. got := tt.hi.TailscaleSSHEnabled()
  332. if got != tt.want {
  333. t.Errorf("%d. got %v; want %v", i, got, tt.want)
  334. }
  335. }
  336. }
  337. func TestNodeEqual(t *testing.T) {
  338. nodeHandles := []string{
  339. "ID", "StableID", "Name", "User", "Sharer",
  340. "Key", "KeyExpiry", "KeySignature", "Machine", "DiscoKey",
  341. "Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo",
  342. "Created", "Cap", "Tags", "PrimaryRoutes",
  343. "LastSeen", "Online", "MachineAuthorized",
  344. "Capabilities", "CapMap",
  345. "UnsignedPeerAPIOnly",
  346. "ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
  347. "DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer",
  348. "SelfNodeV6MasqAddrForThisPeer", "IsWireGuardOnly", "ExitNodeDNSResolvers",
  349. }
  350. if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
  351. t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
  352. have, nodeHandles)
  353. }
  354. n1 := key.NewNode().Public()
  355. m1 := key.NewMachine().Public()
  356. now := time.Now()
  357. tests := []struct {
  358. a, b *Node
  359. want bool
  360. }{
  361. {
  362. &Node{},
  363. nil,
  364. false,
  365. },
  366. {
  367. nil,
  368. &Node{},
  369. false,
  370. },
  371. {
  372. &Node{},
  373. &Node{},
  374. true,
  375. },
  376. {
  377. &Node{},
  378. &Node{},
  379. true,
  380. },
  381. {
  382. &Node{ID: 1},
  383. &Node{},
  384. false,
  385. },
  386. {
  387. &Node{ID: 1},
  388. &Node{ID: 1},
  389. true,
  390. },
  391. {
  392. &Node{StableID: "node-abcd"},
  393. &Node{},
  394. false,
  395. },
  396. {
  397. &Node{StableID: "node-abcd"},
  398. &Node{StableID: "node-abcd"},
  399. true,
  400. },
  401. {
  402. &Node{User: 0},
  403. &Node{User: 1},
  404. false,
  405. },
  406. {
  407. &Node{User: 1},
  408. &Node{User: 1},
  409. true,
  410. },
  411. {
  412. &Node{Key: n1},
  413. &Node{Key: key.NewNode().Public()},
  414. false,
  415. },
  416. {
  417. &Node{Key: n1},
  418. &Node{Key: n1},
  419. true,
  420. },
  421. {
  422. &Node{KeyExpiry: now},
  423. &Node{KeyExpiry: now.Add(60 * time.Second)},
  424. false,
  425. },
  426. {
  427. &Node{KeyExpiry: now},
  428. &Node{KeyExpiry: now},
  429. true,
  430. },
  431. {
  432. &Node{Machine: m1},
  433. &Node{Machine: key.NewMachine().Public()},
  434. false,
  435. },
  436. {
  437. &Node{Machine: m1},
  438. &Node{Machine: m1},
  439. true,
  440. },
  441. {
  442. &Node{Addresses: []netip.Prefix{}},
  443. &Node{Addresses: nil},
  444. false,
  445. },
  446. {
  447. &Node{Addresses: []netip.Prefix{}},
  448. &Node{Addresses: []netip.Prefix{}},
  449. true,
  450. },
  451. {
  452. &Node{AllowedIPs: []netip.Prefix{}},
  453. &Node{AllowedIPs: nil},
  454. false,
  455. },
  456. {
  457. &Node{Addresses: []netip.Prefix{}},
  458. &Node{Addresses: []netip.Prefix{}},
  459. true,
  460. },
  461. {
  462. &Node{Endpoints: []netip.AddrPort{}},
  463. &Node{Endpoints: nil},
  464. false,
  465. },
  466. {
  467. &Node{Endpoints: []netip.AddrPort{}},
  468. &Node{Endpoints: []netip.AddrPort{}},
  469. true,
  470. },
  471. {
  472. &Node{Hostinfo: (&Hostinfo{Hostname: "alice"}).View()},
  473. &Node{Hostinfo: (&Hostinfo{Hostname: "bob"}).View()},
  474. false,
  475. },
  476. {
  477. &Node{Hostinfo: (&Hostinfo{}).View()},
  478. &Node{Hostinfo: (&Hostinfo{}).View()},
  479. true,
  480. },
  481. {
  482. &Node{Created: now},
  483. &Node{Created: now.Add(60 * time.Second)},
  484. false,
  485. },
  486. {
  487. &Node{Created: now},
  488. &Node{Created: now},
  489. true,
  490. },
  491. {
  492. &Node{LastSeen: &now},
  493. &Node{LastSeen: nil},
  494. false,
  495. },
  496. {
  497. &Node{LastSeen: &now},
  498. &Node{LastSeen: &now},
  499. true,
  500. },
  501. {
  502. &Node{DERP: "foo"},
  503. &Node{DERP: "bar"},
  504. false,
  505. },
  506. {
  507. &Node{Tags: []string{"tag:foo"}},
  508. &Node{Tags: []string{"tag:foo"}},
  509. true,
  510. },
  511. {
  512. &Node{Tags: []string{"tag:foo", "tag:bar"}},
  513. &Node{Tags: []string{"tag:bar"}},
  514. false,
  515. },
  516. {
  517. &Node{Tags: []string{"tag:foo"}},
  518. &Node{Tags: []string{"tag:bar"}},
  519. false,
  520. },
  521. {
  522. &Node{Tags: []string{"tag:foo"}},
  523. &Node{},
  524. false,
  525. },
  526. {
  527. &Node{Expired: true},
  528. &Node{},
  529. false,
  530. },
  531. {
  532. &Node{},
  533. &Node{SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  534. false,
  535. },
  536. {
  537. &Node{SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  538. &Node{SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  539. true,
  540. },
  541. {
  542. &Node{},
  543. &Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  544. false,
  545. },
  546. {
  547. &Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  548. &Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  549. true,
  550. },
  551. {
  552. &Node{
  553. CapMap: NodeCapMap{
  554. "foo": []RawMessage{`"foo"`},
  555. },
  556. },
  557. &Node{
  558. CapMap: NodeCapMap{
  559. "foo": []RawMessage{`"foo"`},
  560. },
  561. },
  562. true,
  563. },
  564. {
  565. &Node{
  566. CapMap: NodeCapMap{
  567. "bar": []RawMessage{`"foo"`},
  568. },
  569. },
  570. &Node{
  571. CapMap: NodeCapMap{
  572. "foo": []RawMessage{`"bar"`},
  573. },
  574. },
  575. false,
  576. },
  577. {
  578. &Node{
  579. CapMap: NodeCapMap{
  580. "foo": nil,
  581. },
  582. },
  583. &Node{
  584. CapMap: NodeCapMap{
  585. "foo": []RawMessage{`"bar"`},
  586. },
  587. },
  588. false,
  589. },
  590. }
  591. for i, tt := range tests {
  592. got := tt.a.Equal(tt.b)
  593. if got != tt.want {
  594. t.Errorf("%d. Equal = %v; want %v", i, got, tt.want)
  595. }
  596. }
  597. }
  598. func TestNetInfoFields(t *testing.T) {
  599. handled := []string{
  600. "MappingVariesByDestIP",
  601. "HairPinning",
  602. "WorkingIPv6",
  603. "OSHasIPv6",
  604. "WorkingUDP",
  605. "WorkingICMPv4",
  606. "HavePortMap",
  607. "UPnP",
  608. "PMP",
  609. "PCP",
  610. "PreferredDERP",
  611. "LinkType",
  612. "DERPLatency",
  613. "FirewallMode",
  614. }
  615. if have := fieldsOf(reflect.TypeOf(NetInfo{})); !reflect.DeepEqual(have, handled) {
  616. t.Errorf("NetInfo.Clone/BasicallyEqually check might be out of sync\nfields: %q\nhandled: %q\n",
  617. have, handled)
  618. }
  619. }
  620. type keyIn interface {
  621. String() string
  622. MarshalText() ([]byte, error)
  623. }
  624. func testKey(t *testing.T, prefix string, in keyIn, out encoding.TextUnmarshaler) {
  625. got, err := in.MarshalText()
  626. if err != nil {
  627. t.Fatal(err)
  628. }
  629. if err := out.UnmarshalText(got); err != nil {
  630. t.Fatal(err)
  631. }
  632. if s := in.String(); string(got) != s {
  633. t.Errorf("MarshalText = %q != String %q", got, s)
  634. }
  635. if !strings.HasPrefix(string(got), prefix) {
  636. t.Errorf("%q didn't start with prefix %q", got, prefix)
  637. }
  638. if reflect.ValueOf(out).Elem().Interface() != in {
  639. t.Errorf("mismatch after unmarshal")
  640. }
  641. }
  642. func TestCloneUser(t *testing.T) {
  643. tests := []struct {
  644. name string
  645. u *User
  646. }{
  647. {"nil_logins", &User{}},
  648. {"zero_logins", &User{Logins: make([]LoginID, 0)}},
  649. }
  650. for _, tt := range tests {
  651. t.Run(tt.name, func(t *testing.T) {
  652. u2 := tt.u.Clone()
  653. if !reflect.DeepEqual(tt.u, u2) {
  654. t.Errorf("not equal")
  655. }
  656. })
  657. }
  658. }
  659. func TestCloneNode(t *testing.T) {
  660. tests := []struct {
  661. name string
  662. v *Node
  663. }{
  664. {"nil_fields", &Node{}},
  665. {"zero_fields", &Node{
  666. Addresses: make([]netip.Prefix, 0),
  667. AllowedIPs: make([]netip.Prefix, 0),
  668. Endpoints: make([]netip.AddrPort, 0),
  669. }},
  670. }
  671. for _, tt := range tests {
  672. t.Run(tt.name, func(t *testing.T) {
  673. v2 := tt.v.Clone()
  674. if !reflect.DeepEqual(tt.v, v2) {
  675. t.Errorf("not equal")
  676. }
  677. })
  678. }
  679. }
  680. func TestUserProfileJSONMarshalForMac(t *testing.T) {
  681. // Old macOS clients had a bug where they required
  682. // UserProfile.Roles to be non-null. Lock that in
  683. // 1.0.x/1.2.x clients are gone in the wild.
  684. // See mac commit 0242c08a2ca496958027db1208f44251bff8488b (Sep 30).
  685. // It was fixed in at least 1.4.x, and perhaps 1.2.x.
  686. j, err := json.Marshal(UserProfile{})
  687. if err != nil {
  688. t.Fatal(err)
  689. }
  690. const wantSub = `"Roles":[]`
  691. if !strings.Contains(string(j), wantSub) {
  692. t.Fatalf("didn't contain %#q; got: %s", wantSub, j)
  693. }
  694. // And back:
  695. var up UserProfile
  696. if err := json.Unmarshal(j, &up); err != nil {
  697. t.Fatalf("Unmarshal: %v", err)
  698. }
  699. }
  700. func TestEndpointTypeMarshal(t *testing.T) {
  701. eps := []EndpointType{
  702. EndpointUnknownType,
  703. EndpointLocal,
  704. EndpointSTUN,
  705. EndpointPortmapped,
  706. EndpointSTUN4LocalPort,
  707. }
  708. got, err := json.Marshal(eps)
  709. if err != nil {
  710. t.Fatal(err)
  711. }
  712. const want = `[0,1,2,3,4]`
  713. if string(got) != want {
  714. t.Errorf("got %s; want %s", got, want)
  715. }
  716. }
  717. func TestRegisterRequestNilClone(t *testing.T) {
  718. var nilReq *RegisterRequest
  719. got := nilReq.Clone()
  720. if got != nil {
  721. t.Errorf("got = %v; want nil", got)
  722. }
  723. }
  724. // Tests that CurrentCapabilityVersion is bumped when the comment block above it gets bumped.
  725. // We've screwed this up several times.
  726. func TestCurrentCapabilityVersion(t *testing.T) {
  727. f := must.Get(os.ReadFile("tailcfg.go"))
  728. matches := regexp.MustCompile(`(?m)^//[\s-]+(\d+): \d\d\d\d-\d\d-\d\d: `).FindAllStringSubmatch(string(f), -1)
  729. max := 0
  730. for _, m := range matches {
  731. n := must.Get(strconv.Atoi(m[1]))
  732. if n > max {
  733. max = n
  734. }
  735. }
  736. if CapabilityVersion(max) != CurrentCapabilityVersion {
  737. t.Errorf("CurrentCapabilityVersion = %d; want %d", CurrentCapabilityVersion, max)
  738. }
  739. }
  740. func TestUnmarshalHealth(t *testing.T) {
  741. tests := []struct {
  742. in string // MapResponse JSON
  743. want []string // MapResponse.Health wanted value post-unmarshal
  744. }{
  745. {in: `{}`},
  746. {in: `{"Health":null}`},
  747. {in: `{"Health":[]}`, want: []string{}},
  748. {in: `{"Health":["bad"]}`, want: []string{"bad"}},
  749. }
  750. for _, tt := range tests {
  751. var mr MapResponse
  752. if err := json.Unmarshal([]byte(tt.in), &mr); err != nil {
  753. t.Fatal(err)
  754. }
  755. if !reflect.DeepEqual(mr.Health, tt.want) {
  756. t.Errorf("for %#q: got %v; want %v", tt.in, mr.Health, tt.want)
  757. }
  758. }
  759. }
  760. func TestRawMessage(t *testing.T) {
  761. // Create a few types of json.RawMessages and then marshal them back and
  762. // forth to make sure they round-trip.
  763. type rule struct {
  764. Ports []int `json:",omitempty"`
  765. }
  766. tests := []struct {
  767. name string
  768. val map[string][]rule
  769. wire map[string][]RawMessage
  770. }{
  771. {
  772. name: "nil",
  773. val: nil,
  774. wire: nil,
  775. },
  776. {
  777. name: "empty",
  778. val: map[string][]rule{},
  779. wire: map[string][]RawMessage{},
  780. },
  781. {
  782. name: "one",
  783. val: map[string][]rule{
  784. "foo": {{Ports: []int{1, 2, 3}}},
  785. },
  786. wire: map[string][]RawMessage{
  787. "foo": {
  788. `{"Ports":[1,2,3]}`,
  789. },
  790. },
  791. },
  792. {
  793. name: "many",
  794. val: map[string][]rule{
  795. "foo": {{Ports: []int{1, 2, 3}}},
  796. "bar": {{Ports: []int{4, 5, 6}}, {Ports: []int{7, 8, 9}}},
  797. "baz": nil,
  798. "abc": {},
  799. "def": {{}},
  800. },
  801. wire: map[string][]RawMessage{
  802. "foo": {
  803. `{"Ports":[1,2,3]}`,
  804. },
  805. "bar": {
  806. `{"Ports":[4,5,6]}`,
  807. `{"Ports":[7,8,9]}`,
  808. },
  809. "baz": nil,
  810. "abc": {},
  811. "def": {"{}"},
  812. },
  813. },
  814. }
  815. for _, tc := range tests {
  816. t.Run(tc.name, func(t *testing.T) {
  817. j := must.Get(json.Marshal(tc.val))
  818. var gotWire map[string][]RawMessage
  819. if err := json.Unmarshal(j, &gotWire); err != nil {
  820. t.Fatalf("unmarshal: %v", err)
  821. }
  822. if !reflect.DeepEqual(gotWire, tc.wire) {
  823. t.Errorf("got %#v; want %#v", gotWire, tc.wire)
  824. }
  825. j = must.Get(json.Marshal(tc.wire))
  826. var gotVal map[string][]rule
  827. if err := json.Unmarshal(j, &gotVal); err != nil {
  828. t.Fatalf("unmarshal: %v", err)
  829. }
  830. if !reflect.DeepEqual(gotVal, tc.val) {
  831. t.Errorf("got %#v; want %#v", gotVal, tc.val)
  832. }
  833. })
  834. }
  835. }