map_test.go 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485
  1. // Copyright (c) Tailscale Inc & contributors
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package controlclient
  4. import (
  5. "bytes"
  6. "context"
  7. "encoding/json"
  8. "fmt"
  9. "maps"
  10. "net/netip"
  11. "reflect"
  12. "strings"
  13. "sync/atomic"
  14. "testing"
  15. "time"
  16. "github.com/google/go-cmp/cmp"
  17. "github.com/google/go-cmp/cmp/cmpopts"
  18. "go4.org/mem"
  19. "tailscale.com/control/controlknobs"
  20. "tailscale.com/health"
  21. "tailscale.com/ipn"
  22. "tailscale.com/tailcfg"
  23. "tailscale.com/tstest"
  24. "tailscale.com/tstime"
  25. "tailscale.com/types/dnstype"
  26. "tailscale.com/types/key"
  27. "tailscale.com/types/logger"
  28. "tailscale.com/types/netmap"
  29. "tailscale.com/types/persist"
  30. "tailscale.com/types/ptr"
  31. "tailscale.com/util/eventbus/eventbustest"
  32. "tailscale.com/util/mak"
  33. "tailscale.com/util/must"
  34. "tailscale.com/util/zstdframe"
  35. )
  36. func eps(s ...string) []netip.AddrPort {
  37. var eps []netip.AddrPort
  38. for _, ep := range s {
  39. eps = append(eps, netip.MustParseAddrPort(ep))
  40. }
  41. return eps
  42. }
  43. func TestUpdatePeersStateFromResponse(t *testing.T) {
  44. var curTime time.Time
  45. online := func(v bool) func(*tailcfg.Node) {
  46. return func(n *tailcfg.Node) {
  47. n.Online = &v
  48. }
  49. }
  50. seenAt := func(t time.Time) func(*tailcfg.Node) {
  51. return func(n *tailcfg.Node) {
  52. n.LastSeen = &t
  53. }
  54. }
  55. withDERP := func(regionID int) func(*tailcfg.Node) {
  56. return func(n *tailcfg.Node) {
  57. n.HomeDERP = regionID
  58. }
  59. }
  60. withEP := func(ep string) func(*tailcfg.Node) {
  61. return func(n *tailcfg.Node) {
  62. n.Endpoints = []netip.AddrPort{netip.MustParseAddrPort(ep)}
  63. }
  64. }
  65. n := func(id tailcfg.NodeID, name string, mod ...func(*tailcfg.Node)) *tailcfg.Node {
  66. n := &tailcfg.Node{ID: id, Name: name}
  67. for _, f := range mod {
  68. f(n)
  69. }
  70. return n
  71. }
  72. peers := func(nv ...*tailcfg.Node) []*tailcfg.Node { return nv }
  73. tests := []struct {
  74. name string
  75. mapRes *tailcfg.MapResponse
  76. curTime time.Time
  77. prev []*tailcfg.Node
  78. want []*tailcfg.Node
  79. wantStats updateStats
  80. }{
  81. {
  82. name: "full_peers",
  83. mapRes: &tailcfg.MapResponse{
  84. Peers: peers(n(1, "foo"), n(2, "bar")),
  85. },
  86. want: peers(n(1, "foo"), n(2, "bar")),
  87. wantStats: updateStats{
  88. allNew: true,
  89. added: 2,
  90. },
  91. },
  92. {
  93. name: "full_peers_ignores_deltas",
  94. mapRes: &tailcfg.MapResponse{
  95. Peers: peers(n(1, "foo"), n(2, "bar")),
  96. PeersRemoved: []tailcfg.NodeID{2},
  97. },
  98. want: peers(n(1, "foo"), n(2, "bar")),
  99. wantStats: updateStats{
  100. allNew: true,
  101. added: 2,
  102. },
  103. },
  104. {
  105. name: "add_and_update",
  106. prev: peers(n(1, "foo"), n(2, "bar")),
  107. mapRes: &tailcfg.MapResponse{
  108. PeersChanged: peers(n(0, "zero"), n(2, "bar2"), n(3, "three")),
  109. },
  110. want: peers(n(0, "zero"), n(1, "foo"), n(2, "bar2"), n(3, "three")),
  111. wantStats: updateStats{
  112. added: 2, // added IDs 0 and 3
  113. changed: 1, // changed ID 2
  114. },
  115. },
  116. {
  117. name: "remove",
  118. prev: peers(n(1, "foo"), n(2, "bar")),
  119. mapRes: &tailcfg.MapResponse{
  120. PeersRemoved: []tailcfg.NodeID{1, 3, 4},
  121. },
  122. want: peers(n(2, "bar")),
  123. wantStats: updateStats{
  124. removed: 1, // ID 1
  125. },
  126. },
  127. {
  128. name: "add_and_remove",
  129. prev: peers(n(1, "foo"), n(2, "bar")),
  130. mapRes: &tailcfg.MapResponse{
  131. PeersChanged: peers(n(1, "foo2")),
  132. PeersRemoved: []tailcfg.NodeID{2},
  133. },
  134. want: peers(n(1, "foo2")),
  135. wantStats: updateStats{
  136. changed: 1,
  137. removed: 1,
  138. },
  139. },
  140. {
  141. name: "unchanged",
  142. prev: peers(n(1, "foo"), n(2, "bar")),
  143. mapRes: &tailcfg.MapResponse{},
  144. want: peers(n(1, "foo"), n(2, "bar")),
  145. },
  146. {
  147. name: "online_change",
  148. prev: peers(n(1, "foo"), n(2, "bar")),
  149. mapRes: &tailcfg.MapResponse{
  150. OnlineChange: map[tailcfg.NodeID]bool{
  151. 1: true,
  152. 404: true,
  153. },
  154. },
  155. want: peers(
  156. n(1, "foo", online(true)),
  157. n(2, "bar"),
  158. ),
  159. wantStats: updateStats{changed: 1},
  160. },
  161. {
  162. name: "online_change_offline",
  163. prev: peers(n(1, "foo"), n(2, "bar")),
  164. mapRes: &tailcfg.MapResponse{
  165. OnlineChange: map[tailcfg.NodeID]bool{
  166. 1: false,
  167. 2: true,
  168. },
  169. },
  170. want: peers(
  171. n(1, "foo", online(false)),
  172. n(2, "bar", online(true)),
  173. ),
  174. wantStats: updateStats{changed: 2},
  175. },
  176. {
  177. name: "peer_seen_at",
  178. prev: peers(n(1, "foo", seenAt(time.Unix(111, 0))), n(2, "bar")),
  179. curTime: time.Unix(123, 0),
  180. mapRes: &tailcfg.MapResponse{
  181. PeerSeenChange: map[tailcfg.NodeID]bool{
  182. 1: false,
  183. 2: true,
  184. },
  185. },
  186. want: peers(
  187. n(1, "foo"),
  188. n(2, "bar", seenAt(time.Unix(123, 0))),
  189. ),
  190. wantStats: updateStats{changed: 2},
  191. },
  192. {
  193. name: "ep_change_derp",
  194. prev: peers(n(1, "foo", withDERP(3))),
  195. mapRes: &tailcfg.MapResponse{
  196. PeersChangedPatch: []*tailcfg.PeerChange{{
  197. NodeID: 1,
  198. DERPRegion: 4,
  199. }},
  200. },
  201. want: peers(n(1, "foo", withDERP(4))),
  202. wantStats: updateStats{changed: 1},
  203. },
  204. {
  205. name: "ep_change_udp",
  206. prev: peers(n(1, "foo", withEP("1.2.3.4:111"))),
  207. mapRes: &tailcfg.MapResponse{
  208. PeersChangedPatch: []*tailcfg.PeerChange{{
  209. NodeID: 1,
  210. Endpoints: eps("1.2.3.4:56"),
  211. }},
  212. },
  213. want: peers(n(1, "foo", withEP("1.2.3.4:56"))),
  214. wantStats: updateStats{changed: 1},
  215. },
  216. {
  217. name: "ep_change_udp_2",
  218. prev: peers(n(1, "foo", withDERP(3), withEP("1.2.3.4:111"))),
  219. mapRes: &tailcfg.MapResponse{
  220. PeersChangedPatch: []*tailcfg.PeerChange{{
  221. NodeID: 1,
  222. Endpoints: eps("1.2.3.4:56"),
  223. }},
  224. },
  225. want: peers(n(1, "foo", withDERP(3), withEP("1.2.3.4:56"))),
  226. wantStats: updateStats{changed: 1},
  227. },
  228. {
  229. name: "ep_change_both",
  230. prev: peers(n(1, "foo", withDERP(3), withEP("1.2.3.4:111"))),
  231. mapRes: &tailcfg.MapResponse{
  232. PeersChangedPatch: []*tailcfg.PeerChange{{
  233. NodeID: 1,
  234. DERPRegion: 2,
  235. Endpoints: eps("1.2.3.4:56"),
  236. }},
  237. },
  238. want: peers(n(1, "foo", withDERP(2), withEP("1.2.3.4:56"))),
  239. wantStats: updateStats{changed: 1},
  240. },
  241. {
  242. name: "change_key",
  243. prev: peers(n(1, "foo")),
  244. mapRes: &tailcfg.MapResponse{
  245. PeersChangedPatch: []*tailcfg.PeerChange{{
  246. NodeID: 1,
  247. Key: ptr.To(key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  248. }},
  249. }, want: peers(&tailcfg.Node{
  250. ID: 1,
  251. Name: "foo",
  252. Key: key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  253. }),
  254. wantStats: updateStats{changed: 1},
  255. },
  256. {
  257. name: "change_key_signature",
  258. prev: peers(n(1, "foo")),
  259. mapRes: &tailcfg.MapResponse{
  260. PeersChangedPatch: []*tailcfg.PeerChange{{
  261. NodeID: 1,
  262. KeySignature: []byte{3, 4},
  263. }},
  264. },
  265. want: peers(&tailcfg.Node{
  266. ID: 1,
  267. Name: "foo",
  268. KeySignature: []byte{3, 4},
  269. }),
  270. wantStats: updateStats{changed: 1},
  271. },
  272. {
  273. name: "change_disco_key",
  274. prev: peers(n(1, "foo")),
  275. mapRes: &tailcfg.MapResponse{
  276. PeersChangedPatch: []*tailcfg.PeerChange{{
  277. NodeID: 1,
  278. DiscoKey: ptr.To(key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  279. }},
  280. },
  281. want: peers(&tailcfg.Node{
  282. ID: 1,
  283. Name: "foo",
  284. DiscoKey: key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  285. }),
  286. wantStats: updateStats{changed: 1},
  287. },
  288. {
  289. name: "change_online",
  290. prev: peers(n(1, "foo")),
  291. mapRes: &tailcfg.MapResponse{
  292. PeersChangedPatch: []*tailcfg.PeerChange{{
  293. NodeID: 1,
  294. Online: ptr.To(true),
  295. }},
  296. },
  297. want: peers(&tailcfg.Node{
  298. ID: 1,
  299. Name: "foo",
  300. Online: ptr.To(true),
  301. }),
  302. wantStats: updateStats{changed: 1},
  303. },
  304. {
  305. name: "change_last_seen",
  306. prev: peers(n(1, "foo")),
  307. mapRes: &tailcfg.MapResponse{
  308. PeersChangedPatch: []*tailcfg.PeerChange{{
  309. NodeID: 1,
  310. LastSeen: ptr.To(time.Unix(123, 0).UTC()),
  311. }},
  312. },
  313. want: peers(&tailcfg.Node{
  314. ID: 1,
  315. Name: "foo",
  316. LastSeen: ptr.To(time.Unix(123, 0).UTC()),
  317. }),
  318. wantStats: updateStats{changed: 1},
  319. },
  320. {
  321. name: "change_key_expiry",
  322. prev: peers(n(1, "foo")),
  323. mapRes: &tailcfg.MapResponse{
  324. PeersChangedPatch: []*tailcfg.PeerChange{{
  325. NodeID: 1,
  326. KeyExpiry: ptr.To(time.Unix(123, 0).UTC()),
  327. }},
  328. },
  329. want: peers(&tailcfg.Node{
  330. ID: 1,
  331. Name: "foo",
  332. KeyExpiry: time.Unix(123, 0).UTC(),
  333. }),
  334. wantStats: updateStats{changed: 1},
  335. },
  336. }
  337. for _, tt := range tests {
  338. t.Run(tt.name, func(t *testing.T) {
  339. if !tt.curTime.IsZero() {
  340. curTime = tt.curTime
  341. tstest.Replace(t, &clock, tstime.Clock(tstest.NewClock(tstest.ClockOpts{Start: curTime})))
  342. }
  343. ms := newTestMapSession(t, nil)
  344. for _, n := range tt.prev {
  345. mak.Set(&ms.peers, n.ID, n.View())
  346. }
  347. gotStats := ms.updatePeersStateFromResponse(tt.mapRes)
  348. if gotStats != tt.wantStats {
  349. t.Errorf("got stats = %+v; want %+v", gotStats, tt.wantStats)
  350. }
  351. var got []*tailcfg.Node
  352. for _, vp := range ms.sortedPeers() {
  353. got = append(got, vp.AsStruct())
  354. }
  355. if !reflect.DeepEqual(got, tt.want) {
  356. t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(got), formatNodes(tt.want))
  357. }
  358. })
  359. }
  360. }
  361. func formatNodes(nodes []*tailcfg.Node) string {
  362. var sb strings.Builder
  363. for i, n := range nodes {
  364. if i > 0 {
  365. sb.WriteString(", ")
  366. }
  367. fmt.Fprintf(&sb, "(%d, %q", n.ID, n.Name)
  368. if n.Online != nil {
  369. fmt.Fprintf(&sb, ", online=%v", *n.Online)
  370. }
  371. if n.LastSeen != nil {
  372. fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen.Unix())
  373. }
  374. if n.Key != (key.NodePublic{}) {
  375. fmt.Fprintf(&sb, ", key=%v", n.Key.String())
  376. }
  377. if n.Expired {
  378. fmt.Fprintf(&sb, ", expired=true")
  379. }
  380. sb.WriteString(")")
  381. }
  382. return sb.String()
  383. }
  384. func newTestMapSession(t testing.TB, nu NetmapUpdater) *mapSession {
  385. ms := newMapSession(key.NewNode(), nu, new(controlknobs.Knobs))
  386. t.Cleanup(ms.Close)
  387. ms.logf = t.Logf
  388. return ms
  389. }
  390. func (ms *mapSession) netmapForResponse(res *tailcfg.MapResponse) *netmap.NetworkMap {
  391. ms.updateStateFromResponse(res)
  392. return ms.netmap()
  393. }
  394. func TestNetmapForResponse(t *testing.T) {
  395. t.Run("implicit_packetfilter", func(t *testing.T) {
  396. somePacketFilter := []tailcfg.FilterRule{
  397. {
  398. SrcIPs: []string{"*"},
  399. DstPorts: []tailcfg.NetPortRange{
  400. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  401. },
  402. },
  403. }
  404. ms := newTestMapSession(t, nil)
  405. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  406. Node: new(tailcfg.Node),
  407. PacketFilter: somePacketFilter,
  408. })
  409. if len(nm1.PacketFilter) == 0 {
  410. t.Fatalf("zero length PacketFilter")
  411. }
  412. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  413. Node: new(tailcfg.Node),
  414. PacketFilter: nil, // testing that the server can omit this.
  415. })
  416. if len(nm1.PacketFilter) == 0 {
  417. t.Fatalf("zero length PacketFilter in 2nd netmap")
  418. }
  419. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  420. t.Error("packet filters differ")
  421. }
  422. })
  423. t.Run("implicit_dnsconfig", func(t *testing.T) {
  424. someDNSConfig := &tailcfg.DNSConfig{Domains: []string{"foo", "bar"}}
  425. ms := newTestMapSession(t, nil)
  426. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  427. Node: new(tailcfg.Node),
  428. DNSConfig: someDNSConfig,
  429. })
  430. if !reflect.DeepEqual(nm1.DNS, *someDNSConfig) {
  431. t.Fatalf("1st DNS wrong")
  432. }
  433. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  434. Node: new(tailcfg.Node),
  435. DNSConfig: nil, // implicit
  436. })
  437. if !reflect.DeepEqual(nm2.DNS, *someDNSConfig) {
  438. t.Fatalf("2nd DNS wrong")
  439. }
  440. })
  441. t.Run("collect_services", func(t *testing.T) {
  442. ms := newTestMapSession(t, nil)
  443. var nm *netmap.NetworkMap
  444. wantCollect := func(v bool) {
  445. t.Helper()
  446. if nm.CollectServices != v {
  447. t.Errorf("netmap.CollectServices = %v; want %v", nm.CollectServices, v)
  448. }
  449. }
  450. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  451. Node: new(tailcfg.Node),
  452. })
  453. wantCollect(false)
  454. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  455. Node: new(tailcfg.Node),
  456. CollectServices: "false",
  457. })
  458. wantCollect(false)
  459. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  460. Node: new(tailcfg.Node),
  461. CollectServices: "true",
  462. })
  463. wantCollect(true)
  464. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  465. Node: new(tailcfg.Node),
  466. CollectServices: "",
  467. })
  468. wantCollect(true)
  469. })
  470. t.Run("implicit_domain", func(t *testing.T) {
  471. ms := newTestMapSession(t, nil)
  472. var nm *netmap.NetworkMap
  473. want := func(v string) {
  474. t.Helper()
  475. if nm.Domain != v {
  476. t.Errorf("netmap.Domain = %q; want %q", nm.Domain, v)
  477. }
  478. }
  479. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  480. Node: new(tailcfg.Node),
  481. Domain: "foo.com",
  482. })
  483. want("foo.com")
  484. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  485. Node: new(tailcfg.Node),
  486. })
  487. want("foo.com")
  488. })
  489. t.Run("implicit_node", func(t *testing.T) {
  490. someNode := &tailcfg.Node{
  491. Name: "foo",
  492. }
  493. wantNode := (&tailcfg.Node{
  494. Name: "foo",
  495. ComputedName: "foo",
  496. ComputedNameWithHost: "foo",
  497. }).View()
  498. ms := newTestMapSession(t, nil)
  499. mapRes := &tailcfg.MapResponse{
  500. Node: someNode,
  501. }
  502. initDisplayNames(mapRes.Node.View(), mapRes)
  503. ms.updateStateFromResponse(mapRes)
  504. nm1 := ms.netmap()
  505. if !nm1.SelfNode.Valid() {
  506. t.Fatal("nil Node in 1st netmap")
  507. }
  508. if !reflect.DeepEqual(nm1.SelfNode, wantNode) {
  509. j, _ := json.Marshal(nm1.SelfNode)
  510. t.Errorf("Node mismatch in 1st netmap; got: %s", j)
  511. }
  512. ms.updateStateFromResponse(&tailcfg.MapResponse{})
  513. nm2 := ms.netmap()
  514. if !nm2.SelfNode.Valid() {
  515. t.Fatal("nil Node in 1st netmap")
  516. }
  517. if !reflect.DeepEqual(nm2.SelfNode, wantNode) {
  518. j, _ := json.Marshal(nm2.SelfNode)
  519. t.Errorf("Node mismatch in 2nd netmap; got: %s", j)
  520. }
  521. })
  522. t.Run("named_packetfilter", func(t *testing.T) {
  523. pfA := []tailcfg.FilterRule{
  524. {
  525. SrcIPs: []string{"10.0.0.1"},
  526. DstPorts: []tailcfg.NetPortRange{
  527. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  528. },
  529. },
  530. }
  531. pfB := []tailcfg.FilterRule{
  532. {
  533. SrcIPs: []string{"10.0.0.2"},
  534. DstPorts: []tailcfg.NetPortRange{
  535. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  536. },
  537. },
  538. }
  539. ms := newTestMapSession(t, nil)
  540. // Mix of old & new style (PacketFilter and PacketFilters).
  541. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  542. Node: new(tailcfg.Node),
  543. PacketFilter: pfA,
  544. PacketFilters: map[string][]tailcfg.FilterRule{
  545. "pf-b": pfB,
  546. },
  547. })
  548. if got, want := len(nm1.PacketFilter), 2; got != want {
  549. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  550. }
  551. if got, want := first(nm1.PacketFilter[0].Srcs).String(), "10.0.0.1/32"; got != want {
  552. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  553. }
  554. if got, want := first(nm1.PacketFilter[1].Srcs).String(), "10.0.0.2/32"; got != want {
  555. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  556. }
  557. // No-op change. Remember the old stuff.
  558. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  559. Node: new(tailcfg.Node),
  560. PacketFilter: nil,
  561. PacketFilters: nil,
  562. })
  563. if got, want := len(nm2.PacketFilter), 2; got != want {
  564. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  565. }
  566. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  567. t.Error("packet filters differ")
  568. }
  569. // New style only, with clear.
  570. nm3 := ms.netmapForResponse(&tailcfg.MapResponse{
  571. Node: new(tailcfg.Node),
  572. PacketFilter: nil,
  573. PacketFilters: map[string][]tailcfg.FilterRule{
  574. "*": nil,
  575. "pf-b": pfB,
  576. },
  577. })
  578. if got, want := len(nm3.PacketFilter), 1; got != want {
  579. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  580. }
  581. if got, want := first(nm3.PacketFilter[0].Srcs).String(), "10.0.0.2/32"; got != want {
  582. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  583. }
  584. // New style only, adding pfA back, not as the legacy "base" layer:.
  585. nm4 := ms.netmapForResponse(&tailcfg.MapResponse{
  586. Node: new(tailcfg.Node),
  587. PacketFilter: nil,
  588. PacketFilters: map[string][]tailcfg.FilterRule{
  589. "pf-a": pfA,
  590. },
  591. })
  592. if got, want := len(nm4.PacketFilter), 2; got != want {
  593. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  594. }
  595. if got, want := first(nm4.PacketFilter[0].Srcs).String(), "10.0.0.1/32"; got != want {
  596. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  597. }
  598. if got, want := first(nm4.PacketFilter[1].Srcs).String(), "10.0.0.2/32"; got != want {
  599. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  600. }
  601. })
  602. }
  603. func first[T any](s []T) T {
  604. if len(s) == 0 {
  605. var zero T
  606. return zero
  607. }
  608. return s[0]
  609. }
  610. func TestDeltaDERPMap(t *testing.T) {
  611. regions1 := map[int]*tailcfg.DERPRegion{
  612. 1: {
  613. RegionID: 1,
  614. Nodes: []*tailcfg.DERPNode{{
  615. Name: "derp1a",
  616. RegionID: 1,
  617. HostName: "derp1a" + tailcfg.DotInvalid,
  618. IPv4: "169.254.169.254",
  619. IPv6: "none",
  620. }},
  621. },
  622. }
  623. // As above, but with a changed IPv4 addr
  624. regions2 := map[int]*tailcfg.DERPRegion{1: regions1[1].Clone()}
  625. regions2[1].Nodes[0].IPv4 = "127.0.0.1"
  626. type step struct {
  627. got *tailcfg.DERPMap
  628. want *tailcfg.DERPMap
  629. }
  630. tests := []struct {
  631. name string
  632. steps []step
  633. }{
  634. {
  635. name: "nothing-to-nothing",
  636. steps: []step{
  637. {nil, nil},
  638. {nil, nil},
  639. },
  640. },
  641. {
  642. name: "regions-sticky",
  643. steps: []step{
  644. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  645. {&tailcfg.DERPMap{}, &tailcfg.DERPMap{Regions: regions1}},
  646. },
  647. },
  648. {
  649. name: "regions-change",
  650. steps: []step{
  651. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  652. {&tailcfg.DERPMap{Regions: regions2}, &tailcfg.DERPMap{Regions: regions2}},
  653. },
  654. },
  655. {
  656. name: "home-params",
  657. steps: []step{
  658. // Send a DERP map
  659. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  660. // Send home params, want to still have the same regions
  661. {
  662. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{
  663. RegionScore: map[int]float64{1: 0.5},
  664. }},
  665. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  666. RegionScore: map[int]float64{1: 0.5},
  667. }},
  668. },
  669. },
  670. },
  671. {
  672. name: "home-params-sub-fields",
  673. steps: []step{
  674. // Send a DERP map with home params
  675. {
  676. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  677. RegionScore: map[int]float64{1: 0.5},
  678. }},
  679. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  680. RegionScore: map[int]float64{1: 0.5},
  681. }},
  682. },
  683. // Sending a struct with a 'HomeParams' field but nil RegionScore doesn't change home params...
  684. {
  685. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{RegionScore: nil}},
  686. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  687. RegionScore: map[int]float64{1: 0.5},
  688. }},
  689. },
  690. // ... but sending one with a non-nil and empty RegionScore field zeroes that out.
  691. {
  692. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{RegionScore: map[int]float64{}}},
  693. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  694. RegionScore: map[int]float64{},
  695. }},
  696. },
  697. },
  698. },
  699. }
  700. for _, tt := range tests {
  701. t.Run(tt.name, func(t *testing.T) {
  702. ms := newTestMapSession(t, nil)
  703. for stepi, s := range tt.steps {
  704. nm := ms.netmapForResponse(&tailcfg.MapResponse{DERPMap: s.got})
  705. if !reflect.DeepEqual(nm.DERPMap, s.want) {
  706. t.Errorf("unexpected result at step index %v; got: %s", stepi, logger.AsJSON(nm.DERPMap))
  707. }
  708. }
  709. })
  710. }
  711. }
  712. func TestPeerChangeDiff(t *testing.T) {
  713. tests := []struct {
  714. name string
  715. a, b *tailcfg.Node
  716. want *tailcfg.PeerChange // nil means want ok=false, unless wantEqual is set
  717. wantEqual bool // means test wants (nil, true)
  718. }{
  719. {
  720. name: "eq",
  721. a: &tailcfg.Node{ID: 1},
  722. b: &tailcfg.Node{ID: 1},
  723. wantEqual: true,
  724. },
  725. {
  726. name: "patch-derp",
  727. a: &tailcfg.Node{ID: 1, HomeDERP: 1},
  728. b: &tailcfg.Node{ID: 1, HomeDERP: 2},
  729. want: &tailcfg.PeerChange{NodeID: 1, DERPRegion: 2},
  730. },
  731. {
  732. name: "patch-endpoints",
  733. a: &tailcfg.Node{ID: 1, Endpoints: eps("10.0.0.1:1")},
  734. b: &tailcfg.Node{ID: 1, Endpoints: eps("10.0.0.2:2")},
  735. want: &tailcfg.PeerChange{NodeID: 1, Endpoints: eps("10.0.0.2:2")},
  736. },
  737. {
  738. name: "patch-cap",
  739. a: &tailcfg.Node{ID: 1, Cap: 1},
  740. b: &tailcfg.Node{ID: 1, Cap: 2},
  741. want: &tailcfg.PeerChange{NodeID: 1, Cap: 2},
  742. },
  743. {
  744. name: "patch-lastseen",
  745. a: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(1, 0))},
  746. b: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
  747. want: &tailcfg.PeerChange{NodeID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
  748. },
  749. {
  750. name: "patch-online-to-true",
  751. a: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
  752. b: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
  753. want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(true)},
  754. },
  755. {
  756. name: "patch-online-to-false",
  757. a: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
  758. b: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
  759. want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(false)},
  760. },
  761. {
  762. name: "mix-patchable-and-not",
  763. a: &tailcfg.Node{ID: 1, Cap: 1},
  764. b: &tailcfg.Node{ID: 1, Cap: 2, StableID: "foo"},
  765. want: nil,
  766. },
  767. {
  768. name: "miss-change-stableid",
  769. a: &tailcfg.Node{ID: 1},
  770. b: &tailcfg.Node{ID: 1, StableID: "diff"},
  771. want: nil,
  772. },
  773. {
  774. name: "miss-change-id",
  775. a: &tailcfg.Node{ID: 1},
  776. b: &tailcfg.Node{ID: 2},
  777. want: nil,
  778. },
  779. {
  780. name: "miss-change-name",
  781. a: &tailcfg.Node{ID: 1, Name: "foo"},
  782. b: &tailcfg.Node{ID: 1, Name: "bar"},
  783. want: nil,
  784. },
  785. {
  786. name: "miss-change-user",
  787. a: &tailcfg.Node{ID: 1, User: 1},
  788. b: &tailcfg.Node{ID: 1, User: 2},
  789. want: nil,
  790. },
  791. {
  792. name: "miss-change-masq-v4",
  793. a: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  794. b: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.2"))},
  795. want: nil,
  796. },
  797. {
  798. name: "miss-change-masq-v6",
  799. a: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  800. b: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3006"))},
  801. want: nil,
  802. },
  803. {
  804. name: "patch-capmap-add-value-to-existing-key",
  805. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  806. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: []tailcfg.RawMessage{"true"}}},
  807. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: []tailcfg.RawMessage{"true"}}},
  808. },
  809. {
  810. name: "patch-capmap-add-new-key",
  811. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  812. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}},
  813. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}},
  814. }, {
  815. name: "patch-capmap-remove-key",
  816. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  817. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{}},
  818. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}},
  819. }, {
  820. name: "patch-capmap-remove-as-nil",
  821. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  822. b: &tailcfg.Node{ID: 1},
  823. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}},
  824. }, {
  825. name: "patch-capmap-add-key-to-empty-map",
  826. a: &tailcfg.Node{ID: 1},
  827. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  828. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  829. },
  830. {
  831. name: "patch-capmap-no-change",
  832. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  833. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  834. wantEqual: true,
  835. },
  836. }
  837. for _, tt := range tests {
  838. t.Run(tt.name, func(t *testing.T) {
  839. pc, ok := peerChangeDiff(tt.a.View(), tt.b)
  840. if tt.wantEqual {
  841. if !ok || pc != nil {
  842. t.Errorf("got (%p, %v); want (nil, true); pc=%v", pc, ok, logger.AsJSON(pc))
  843. }
  844. return
  845. }
  846. if (pc != nil) != ok {
  847. t.Fatalf("inconsistent ok=%v, pc=%p", ok, pc)
  848. }
  849. if !reflect.DeepEqual(pc, tt.want) {
  850. t.Errorf("mismatch\n got: %v\nwant: %v\n", logger.AsJSON(pc), logger.AsJSON(tt.want))
  851. }
  852. })
  853. }
  854. }
  855. func TestPeerChangeDiffAllocs(t *testing.T) {
  856. a := &tailcfg.Node{ID: 1}
  857. b := &tailcfg.Node{ID: 1}
  858. n := testing.AllocsPerRun(10000, func() {
  859. diff, ok := peerChangeDiff(a.View(), b)
  860. if !ok || diff != nil {
  861. t.Fatalf("unexpected result: (%s, %v)", logger.AsJSON(diff), ok)
  862. }
  863. })
  864. if n != 0 {
  865. t.Errorf("allocs = %v; want 0", int(n))
  866. }
  867. }
  868. type countingNetmapUpdater struct {
  869. full atomic.Int64
  870. }
  871. func (nu *countingNetmapUpdater) UpdateFullNetmap(nm *netmap.NetworkMap) {
  872. nu.full.Add(1)
  873. }
  874. // tests (*mapSession).patchifyPeersChanged; smaller tests are in TestPeerChangeDiff
  875. func TestPatchifyPeersChanged(t *testing.T) {
  876. hi := (&tailcfg.Hostinfo{}).View()
  877. tests := []struct {
  878. name string
  879. mr0 *tailcfg.MapResponse // initial
  880. mr1 *tailcfg.MapResponse // incremental
  881. want *tailcfg.MapResponse // what the incremental one should've been mutated to
  882. }{
  883. {
  884. name: "change_one_endpoint",
  885. mr0: &tailcfg.MapResponse{
  886. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  887. Peers: []*tailcfg.Node{
  888. {ID: 1, Hostinfo: hi},
  889. },
  890. },
  891. mr1: &tailcfg.MapResponse{
  892. PeersChanged: []*tailcfg.Node{
  893. {ID: 1, Endpoints: eps("10.0.0.1:1111"), Hostinfo: hi},
  894. },
  895. },
  896. want: &tailcfg.MapResponse{
  897. PeersChanged: nil,
  898. PeersChangedPatch: []*tailcfg.PeerChange{
  899. {NodeID: 1, Endpoints: eps("10.0.0.1:1111")},
  900. },
  901. },
  902. },
  903. {
  904. name: "change_some",
  905. mr0: &tailcfg.MapResponse{
  906. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  907. Peers: []*tailcfg.Node{
  908. {ID: 1, HomeDERP: 1, Hostinfo: hi},
  909. {ID: 2, HomeDERP: 2, Hostinfo: hi},
  910. {ID: 3, HomeDERP: 3, Hostinfo: hi},
  911. },
  912. },
  913. mr1: &tailcfg.MapResponse{
  914. PeersChanged: []*tailcfg.Node{
  915. {ID: 1, HomeDERP: 11, Hostinfo: hi},
  916. {ID: 2, StableID: "other-change", Hostinfo: hi},
  917. {ID: 3, HomeDERP: 33, Hostinfo: hi},
  918. {ID: 4, HomeDERP: 4, Hostinfo: hi},
  919. },
  920. },
  921. want: &tailcfg.MapResponse{
  922. PeersChanged: []*tailcfg.Node{
  923. {ID: 2, StableID: "other-change", Hostinfo: hi},
  924. {ID: 4, HomeDERP: 4, Hostinfo: hi},
  925. },
  926. PeersChangedPatch: []*tailcfg.PeerChange{
  927. {NodeID: 1, DERPRegion: 11},
  928. {NodeID: 3, DERPRegion: 33},
  929. },
  930. },
  931. },
  932. {
  933. name: "change_exitnodednsresolvers",
  934. mr0: &tailcfg.MapResponse{
  935. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  936. Peers: []*tailcfg.Node{
  937. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  938. },
  939. },
  940. mr1: &tailcfg.MapResponse{
  941. PeersChanged: []*tailcfg.Node{
  942. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  943. },
  944. },
  945. want: &tailcfg.MapResponse{
  946. PeersChanged: []*tailcfg.Node{
  947. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  948. },
  949. },
  950. },
  951. {
  952. name: "same_exitnoderesolvers",
  953. mr0: &tailcfg.MapResponse{
  954. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  955. Peers: []*tailcfg.Node{
  956. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  957. },
  958. },
  959. mr1: &tailcfg.MapResponse{
  960. PeersChanged: []*tailcfg.Node{
  961. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  962. },
  963. },
  964. want: &tailcfg.MapResponse{},
  965. },
  966. }
  967. for _, tt := range tests {
  968. t.Run(tt.name, func(t *testing.T) {
  969. nu := &countingNetmapUpdater{}
  970. ms := newTestMapSession(t, nu)
  971. ms.updateStateFromResponse(tt.mr0)
  972. mr1 := new(tailcfg.MapResponse)
  973. must.Do(json.Unmarshal(must.Get(json.Marshal(tt.mr1)), mr1))
  974. ms.patchifyPeersChanged(mr1)
  975. opts := []cmp.Option{
  976. cmp.Comparer(func(a, b netip.AddrPort) bool { return a == b }),
  977. }
  978. if diff := cmp.Diff(tt.want, mr1, opts...); diff != "" {
  979. t.Errorf("wrong result (-want +got):\n%s", diff)
  980. }
  981. })
  982. }
  983. }
  984. func TestUpgradeNode(t *testing.T) {
  985. a1 := netip.MustParsePrefix("0.0.0.1/32")
  986. a2 := netip.MustParsePrefix("0.0.0.2/32")
  987. a3 := netip.MustParsePrefix("0.0.0.3/32")
  988. a4 := netip.MustParsePrefix("0.0.0.4/32")
  989. tests := []struct {
  990. name string
  991. in *tailcfg.Node
  992. want *tailcfg.Node
  993. also func(t *testing.T, got *tailcfg.Node) // optional
  994. }{
  995. {
  996. name: "nil",
  997. in: nil,
  998. want: nil,
  999. },
  1000. {
  1001. name: "empty",
  1002. in: new(tailcfg.Node),
  1003. want: new(tailcfg.Node),
  1004. },
  1005. {
  1006. name: "derp-both",
  1007. in: &tailcfg.Node{HomeDERP: 1, LegacyDERPString: tailcfg.DerpMagicIP + ":2"},
  1008. want: &tailcfg.Node{HomeDERP: 1},
  1009. },
  1010. {
  1011. name: "derp-str-only",
  1012. in: &tailcfg.Node{LegacyDERPString: tailcfg.DerpMagicIP + ":2"},
  1013. want: &tailcfg.Node{HomeDERP: 2},
  1014. },
  1015. {
  1016. name: "derp-int-only",
  1017. in: &tailcfg.Node{HomeDERP: 2},
  1018. want: &tailcfg.Node{HomeDERP: 2},
  1019. },
  1020. {
  1021. name: "implicit-allowed-ips-all-set",
  1022. in: &tailcfg.Node{Addresses: []netip.Prefix{a1, a2}, AllowedIPs: []netip.Prefix{a3, a4}},
  1023. want: &tailcfg.Node{Addresses: []netip.Prefix{a1, a2}, AllowedIPs: []netip.Prefix{a3, a4}},
  1024. },
  1025. {
  1026. name: "implicit-allowed-ips-only-address-set",
  1027. in: &tailcfg.Node{Addresses: []netip.Prefix{a1, a2}},
  1028. want: &tailcfg.Node{Addresses: []netip.Prefix{a1, a2}, AllowedIPs: []netip.Prefix{a1, a2}},
  1029. also: func(t *testing.T, got *tailcfg.Node) {
  1030. if t.Failed() {
  1031. return
  1032. }
  1033. if &got.Addresses[0] == &got.AllowedIPs[0] {
  1034. t.Error("Addresses and AllowIPs alias the same memory")
  1035. }
  1036. },
  1037. },
  1038. {
  1039. name: "implicit-allowed-ips-set-empty-slice",
  1040. in: &tailcfg.Node{Addresses: []netip.Prefix{a1, a2}, AllowedIPs: []netip.Prefix{}},
  1041. want: &tailcfg.Node{Addresses: []netip.Prefix{a1, a2}, AllowedIPs: []netip.Prefix{}},
  1042. },
  1043. }
  1044. for _, tt := range tests {
  1045. t.Run(tt.name, func(t *testing.T) {
  1046. var got *tailcfg.Node
  1047. if tt.in != nil {
  1048. got = ptr.To(*tt.in) // shallow clone
  1049. }
  1050. upgradeNode(got)
  1051. if diff := cmp.Diff(tt.want, got); diff != "" {
  1052. t.Errorf("wrong result (-want +got):\n%s", diff)
  1053. }
  1054. if tt.also != nil {
  1055. tt.also(t, got)
  1056. }
  1057. })
  1058. }
  1059. }
  1060. func BenchmarkMapSessionDelta(b *testing.B) {
  1061. for _, size := range []int{10, 100, 1_000, 10_000} {
  1062. b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
  1063. ctx := context.Background()
  1064. nu := &countingNetmapUpdater{}
  1065. ms := newTestMapSession(b, nu)
  1066. res := &tailcfg.MapResponse{
  1067. Node: &tailcfg.Node{
  1068. ID: 1,
  1069. Name: "foo.bar.ts.net.",
  1070. },
  1071. }
  1072. for i := range size {
  1073. res.Peers = append(res.Peers, &tailcfg.Node{
  1074. ID: tailcfg.NodeID(i + 2),
  1075. Name: fmt.Sprintf("peer%d.bar.ts.net.", i),
  1076. HomeDERP: 10,
  1077. Addresses: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  1078. AllowedIPs: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  1079. Endpoints: eps("192.168.1.2:345", "192.168.1.3:678"),
  1080. Hostinfo: (&tailcfg.Hostinfo{
  1081. OS: "fooOS",
  1082. Hostname: "MyHostname",
  1083. Services: []tailcfg.Service{
  1084. {Proto: "peerapi4", Port: 1234},
  1085. {Proto: "peerapi6", Port: 1234},
  1086. {Proto: "peerapi-dns-proxy", Port: 1},
  1087. },
  1088. }).View(),
  1089. LastSeen: ptr.To(time.Unix(int64(i), 0)),
  1090. })
  1091. }
  1092. ms.HandleNonKeepAliveMapResponse(ctx, res)
  1093. b.ResetTimer()
  1094. b.ReportAllocs()
  1095. // Now for the core of the benchmark loop, just toggle
  1096. // a single node's online status.
  1097. for i := range b.N {
  1098. if err := ms.HandleNonKeepAliveMapResponse(ctx, &tailcfg.MapResponse{
  1099. OnlineChange: map[tailcfg.NodeID]bool{
  1100. 2: i%2 == 0,
  1101. },
  1102. }); err != nil {
  1103. b.Fatal(err)
  1104. }
  1105. }
  1106. })
  1107. }
  1108. }
  1109. // TestNetmapDisplayMessage checks that the various diff operations
  1110. // (add/update/delete/clear) for [tailcfg.DisplayMessage] in a
  1111. // [tailcfg.MapResponse] work as expected.
  1112. func TestNetmapDisplayMessage(t *testing.T) {
  1113. type test struct {
  1114. name string
  1115. initialState *tailcfg.MapResponse
  1116. mapResponse tailcfg.MapResponse
  1117. wantMessages map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage
  1118. }
  1119. tests := []test{
  1120. {
  1121. name: "basic-set",
  1122. mapResponse: tailcfg.MapResponse{
  1123. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1124. "test-message": {
  1125. Title: "Testing",
  1126. Text: "This is a test message",
  1127. Severity: tailcfg.SeverityHigh,
  1128. ImpactsConnectivity: true,
  1129. PrimaryAction: &tailcfg.DisplayMessageAction{
  1130. URL: "https://www.example.com",
  1131. Label: "Learn more",
  1132. },
  1133. },
  1134. },
  1135. },
  1136. wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
  1137. "test-message": {
  1138. Title: "Testing",
  1139. Text: "This is a test message",
  1140. Severity: tailcfg.SeverityHigh,
  1141. ImpactsConnectivity: true,
  1142. PrimaryAction: &tailcfg.DisplayMessageAction{
  1143. URL: "https://www.example.com",
  1144. Label: "Learn more",
  1145. },
  1146. },
  1147. },
  1148. },
  1149. {
  1150. name: "delete-one",
  1151. initialState: &tailcfg.MapResponse{
  1152. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1153. "message-a": {
  1154. Title: "Message A",
  1155. },
  1156. "message-b": {
  1157. Title: "Message B",
  1158. },
  1159. },
  1160. },
  1161. mapResponse: tailcfg.MapResponse{
  1162. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1163. "message-a": nil,
  1164. },
  1165. },
  1166. wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
  1167. "message-b": {
  1168. Title: "Message B",
  1169. },
  1170. },
  1171. },
  1172. {
  1173. name: "update-one",
  1174. initialState: &tailcfg.MapResponse{
  1175. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1176. "message-a": {
  1177. Title: "Message A",
  1178. },
  1179. "message-b": {
  1180. Title: "Message B",
  1181. },
  1182. },
  1183. },
  1184. mapResponse: tailcfg.MapResponse{
  1185. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1186. "message-a": {
  1187. Title: "Message A updated",
  1188. },
  1189. },
  1190. },
  1191. wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
  1192. "message-a": {
  1193. Title: "Message A updated",
  1194. },
  1195. "message-b": {
  1196. Title: "Message B",
  1197. },
  1198. },
  1199. },
  1200. {
  1201. name: "add-one",
  1202. initialState: &tailcfg.MapResponse{
  1203. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1204. "message-a": {
  1205. Title: "Message A",
  1206. },
  1207. },
  1208. },
  1209. mapResponse: tailcfg.MapResponse{
  1210. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1211. "message-b": {
  1212. Title: "Message B",
  1213. },
  1214. },
  1215. },
  1216. wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
  1217. "message-a": {
  1218. Title: "Message A",
  1219. },
  1220. "message-b": {
  1221. Title: "Message B",
  1222. },
  1223. },
  1224. },
  1225. {
  1226. name: "delete-all",
  1227. initialState: &tailcfg.MapResponse{
  1228. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1229. "message-a": {
  1230. Title: "Message A",
  1231. },
  1232. "message-b": {
  1233. Title: "Message B",
  1234. },
  1235. },
  1236. },
  1237. mapResponse: tailcfg.MapResponse{
  1238. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1239. "*": nil,
  1240. },
  1241. },
  1242. wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{},
  1243. },
  1244. {
  1245. name: "delete-all-and-add",
  1246. initialState: &tailcfg.MapResponse{
  1247. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1248. "message-a": {
  1249. Title: "Message A",
  1250. },
  1251. "message-b": {
  1252. Title: "Message B",
  1253. },
  1254. },
  1255. },
  1256. mapResponse: tailcfg.MapResponse{
  1257. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1258. "*": nil,
  1259. "message-c": {
  1260. Title: "Message C",
  1261. },
  1262. },
  1263. },
  1264. wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
  1265. "message-c": {
  1266. Title: "Message C",
  1267. },
  1268. },
  1269. },
  1270. }
  1271. for _, test := range tests {
  1272. t.Run(test.name, func(t *testing.T) {
  1273. ms := newTestMapSession(t, nil)
  1274. if test.initialState != nil {
  1275. ms.netmapForResponse(test.initialState)
  1276. }
  1277. nm := ms.netmapForResponse(&test.mapResponse)
  1278. if diff := cmp.Diff(test.wantMessages, nm.DisplayMessages, cmpopts.EquateEmpty()); diff != "" {
  1279. t.Errorf("unexpected warnings (-want +got):\n%s", diff)
  1280. }
  1281. })
  1282. }
  1283. }
  1284. // TestNetmapHealthIntegration checks that we get the expected health warnings
  1285. // from processing a [tailcfg.MapResponse] containing health messages and passing the
  1286. // [netmap.NetworkMap] to a [health.Tracker].
  1287. func TestNetmapHealthIntegration(t *testing.T) {
  1288. ms := newTestMapSession(t, nil)
  1289. ht := health.NewTracker(eventbustest.NewBus(t))
  1290. ht.SetIPNState("NeedsLogin", true)
  1291. ht.GotStreamedMapResponse()
  1292. nm := ms.netmapForResponse(&tailcfg.MapResponse{
  1293. Health: []string{
  1294. "Test message",
  1295. "Another message",
  1296. },
  1297. })
  1298. ht.SetControlHealth(nm.DisplayMessages)
  1299. want := map[health.WarnableCode]health.UnhealthyState{
  1300. "control-health.health-c0719e9a8d5d838d861dc6f675c899d2b309a3a65bb9fe6b11e5afcbf9a2c0b1": {
  1301. WarnableCode: "control-health.health-c0719e9a8d5d838d861dc6f675c899d2b309a3a65bb9fe6b11e5afcbf9a2c0b1",
  1302. Title: "Coordination server reports an issue",
  1303. Severity: health.SeverityMedium,
  1304. Text: "The coordination server is reporting a health issue: Test message",
  1305. },
  1306. "control-health.health-1dc7017a73a3c55c0d6a8423e3813c7ab6562d9d3064c2ec6ac7822f61b1db9c": {
  1307. WarnableCode: "control-health.health-1dc7017a73a3c55c0d6a8423e3813c7ab6562d9d3064c2ec6ac7822f61b1db9c",
  1308. Title: "Coordination server reports an issue",
  1309. Severity: health.SeverityMedium,
  1310. Text: "The coordination server is reporting a health issue: Another message",
  1311. },
  1312. }
  1313. got := maps.Clone(ht.CurrentState().Warnings)
  1314. for k := range got {
  1315. if !strings.HasPrefix(string(k), "control-health") {
  1316. delete(got, k)
  1317. }
  1318. }
  1319. if d := cmp.Diff(want, got, cmpopts.IgnoreFields(health.UnhealthyState{}, "ETag")); d != "" {
  1320. t.Fatalf("CurrentStatus().Warnings[\"control-health*\"] different than expected (-want +got)\n%s", d)
  1321. }
  1322. }
  1323. // TestNetmapDisplayMessageIntegration checks that we get the expected health
  1324. // warnings from processing a [tailcfg.MapResponse] that contains DisplayMessages and
  1325. // passing the [netmap.NetworkMap] to a [health.Tracker].
  1326. func TestNetmapDisplayMessageIntegration(t *testing.T) {
  1327. ms := newTestMapSession(t, nil)
  1328. ht := health.NewTracker(eventbustest.NewBus(t))
  1329. ht.SetIPNState("NeedsLogin", true)
  1330. ht.GotStreamedMapResponse()
  1331. baseWarnings := ht.CurrentState().Warnings
  1332. nm := ms.netmapForResponse(&tailcfg.MapResponse{
  1333. DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
  1334. "test-message": {
  1335. Title: "Testing",
  1336. Text: "This is a test message",
  1337. Severity: tailcfg.SeverityHigh,
  1338. ImpactsConnectivity: true,
  1339. PrimaryAction: &tailcfg.DisplayMessageAction{
  1340. URL: "https://www.example.com",
  1341. Label: "Learn more",
  1342. },
  1343. },
  1344. },
  1345. })
  1346. ht.SetControlHealth(nm.DisplayMessages)
  1347. state := ht.CurrentState()
  1348. // Ignore warnings that aren't from the netmap
  1349. for k := range baseWarnings {
  1350. delete(state.Warnings, k)
  1351. }
  1352. want := map[health.WarnableCode]health.UnhealthyState{
  1353. "control-health.test-message": {
  1354. WarnableCode: "control-health.test-message",
  1355. Title: "Testing",
  1356. Text: "This is a test message",
  1357. Severity: health.SeverityHigh,
  1358. ImpactsConnectivity: true,
  1359. PrimaryAction: &health.UnhealthyStateAction{
  1360. URL: "https://www.example.com",
  1361. Label: "Learn more",
  1362. },
  1363. },
  1364. }
  1365. if diff := cmp.Diff(want, state.Warnings, cmpopts.IgnoreFields(health.UnhealthyState{}, "ETag")); diff != "" {
  1366. t.Errorf("unexpected message contents (-want +got):\n%s", diff)
  1367. }
  1368. }
  1369. func TestNetmapForMapResponseForDebug(t *testing.T) {
  1370. mr := &tailcfg.MapResponse{
  1371. Node: &tailcfg.Node{
  1372. ID: 1,
  1373. Name: "foo.bar.ts.net.",
  1374. },
  1375. Peers: []*tailcfg.Node{
  1376. {ID: 2, Name: "peer1.bar.ts.net.", HomeDERP: 1},
  1377. {ID: 3, Name: "peer2.bar.ts.net.", HomeDERP: 1},
  1378. },
  1379. }
  1380. ms := newTestMapSession(t, nil)
  1381. nm1 := ms.netmapForResponse(mr)
  1382. prefs := &ipn.Prefs{Persist: &persist.Persist{PrivateNodeKey: ms.privateNodeKey}}
  1383. nm2, err := NetmapFromMapResponseForDebug(t.Context(), prefs.View().Persist(), mr)
  1384. if err != nil {
  1385. t.Fatal(err)
  1386. }
  1387. if !reflect.DeepEqual(nm1, nm2) {
  1388. t.Errorf("mismatch\nnm1: %s\nnm2: %s\n", logger.AsJSON(nm1), logger.AsJSON(nm2))
  1389. }
  1390. }
  1391. func TestLearnZstdOfKeepAlive(t *testing.T) {
  1392. keepAliveMsgZstd := (func() []byte {
  1393. msg := must.Get(json.Marshal(tailcfg.MapResponse{
  1394. KeepAlive: true,
  1395. }))
  1396. return zstdframe.AppendEncode(nil, msg, zstdframe.FastestCompression)
  1397. })()
  1398. sess := newTestMapSession(t, nil)
  1399. // The first time we see a zstd keep-alive message, we learn how
  1400. // the server encodes that.
  1401. var mr tailcfg.MapResponse
  1402. must.Do(sess.decodeMsg(keepAliveMsgZstd, &mr))
  1403. if !mr.KeepAlive {
  1404. t.Fatal("mr.KeepAlive false; want true")
  1405. }
  1406. if !bytes.Equal(sess.keepAliveZ, keepAliveMsgZstd) {
  1407. t.Fatalf("sess.keepAlive = %q; want %q", sess.keepAliveZ, keepAliveMsgZstd)
  1408. }
  1409. if got, want := sess.ztdDecodesForTest, 1; got != want {
  1410. t.Fatalf("got %d zstd decodes; want %d", got, want)
  1411. }
  1412. // The second time on the session where we see that message, we
  1413. // decode it without needing to decompress.
  1414. var mr2 tailcfg.MapResponse
  1415. must.Do(sess.decodeMsg(keepAliveMsgZstd, &mr2))
  1416. if !mr2.KeepAlive {
  1417. t.Fatal("mr2.KeepAlive false; want true")
  1418. }
  1419. if got, want := sess.ztdDecodesForTest, 1; got != want {
  1420. t.Fatalf("got %d zstd decodes; want %d", got, want)
  1421. }
  1422. }