2
0

map_test.go 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package controlclient
  4. import (
  5. "context"
  6. "encoding/json"
  7. "fmt"
  8. "net/netip"
  9. "reflect"
  10. "strings"
  11. "sync/atomic"
  12. "testing"
  13. "time"
  14. "github.com/google/go-cmp/cmp"
  15. "go4.org/mem"
  16. "tailscale.com/control/controlknobs"
  17. "tailscale.com/tailcfg"
  18. "tailscale.com/tstest"
  19. "tailscale.com/tstime"
  20. "tailscale.com/types/dnstype"
  21. "tailscale.com/types/key"
  22. "tailscale.com/types/logger"
  23. "tailscale.com/types/netmap"
  24. "tailscale.com/types/ptr"
  25. "tailscale.com/util/mak"
  26. "tailscale.com/util/must"
  27. )
  28. func eps(s ...string) []netip.AddrPort {
  29. var eps []netip.AddrPort
  30. for _, ep := range s {
  31. eps = append(eps, netip.MustParseAddrPort(ep))
  32. }
  33. return eps
  34. }
  35. func TestUpdatePeersStateFromResponse(t *testing.T) {
  36. var curTime time.Time
  37. online := func(v bool) func(*tailcfg.Node) {
  38. return func(n *tailcfg.Node) {
  39. n.Online = &v
  40. }
  41. }
  42. seenAt := func(t time.Time) func(*tailcfg.Node) {
  43. return func(n *tailcfg.Node) {
  44. n.LastSeen = &t
  45. }
  46. }
  47. withDERP := func(d string) func(*tailcfg.Node) {
  48. return func(n *tailcfg.Node) {
  49. n.DERP = d
  50. }
  51. }
  52. withEP := func(ep string) func(*tailcfg.Node) {
  53. return func(n *tailcfg.Node) {
  54. n.Endpoints = []netip.AddrPort{netip.MustParseAddrPort(ep)}
  55. }
  56. }
  57. n := func(id tailcfg.NodeID, name string, mod ...func(*tailcfg.Node)) *tailcfg.Node {
  58. n := &tailcfg.Node{ID: id, Name: name}
  59. for _, f := range mod {
  60. f(n)
  61. }
  62. return n
  63. }
  64. peers := func(nv ...*tailcfg.Node) []*tailcfg.Node { return nv }
  65. tests := []struct {
  66. name string
  67. mapRes *tailcfg.MapResponse
  68. curTime time.Time
  69. prev []*tailcfg.Node
  70. want []*tailcfg.Node
  71. wantStats updateStats
  72. }{
  73. {
  74. name: "full_peers",
  75. mapRes: &tailcfg.MapResponse{
  76. Peers: peers(n(1, "foo"), n(2, "bar")),
  77. },
  78. want: peers(n(1, "foo"), n(2, "bar")),
  79. wantStats: updateStats{
  80. allNew: true,
  81. added: 2,
  82. },
  83. },
  84. {
  85. name: "full_peers_ignores_deltas",
  86. mapRes: &tailcfg.MapResponse{
  87. Peers: peers(n(1, "foo"), n(2, "bar")),
  88. PeersRemoved: []tailcfg.NodeID{2},
  89. },
  90. want: peers(n(1, "foo"), n(2, "bar")),
  91. wantStats: updateStats{
  92. allNew: true,
  93. added: 2,
  94. },
  95. },
  96. {
  97. name: "add_and_update",
  98. prev: peers(n(1, "foo"), n(2, "bar")),
  99. mapRes: &tailcfg.MapResponse{
  100. PeersChanged: peers(n(0, "zero"), n(2, "bar2"), n(3, "three")),
  101. },
  102. want: peers(n(0, "zero"), n(1, "foo"), n(2, "bar2"), n(3, "three")),
  103. wantStats: updateStats{
  104. added: 2, // added IDs 0 and 3
  105. changed: 1, // changed ID 2
  106. },
  107. },
  108. {
  109. name: "remove",
  110. prev: peers(n(1, "foo"), n(2, "bar")),
  111. mapRes: &tailcfg.MapResponse{
  112. PeersRemoved: []tailcfg.NodeID{1, 3, 4},
  113. },
  114. want: peers(n(2, "bar")),
  115. wantStats: updateStats{
  116. removed: 1, // ID 1
  117. },
  118. },
  119. {
  120. name: "add_and_remove",
  121. prev: peers(n(1, "foo"), n(2, "bar")),
  122. mapRes: &tailcfg.MapResponse{
  123. PeersChanged: peers(n(1, "foo2")),
  124. PeersRemoved: []tailcfg.NodeID{2},
  125. },
  126. want: peers(n(1, "foo2")),
  127. wantStats: updateStats{
  128. changed: 1,
  129. removed: 1,
  130. },
  131. },
  132. {
  133. name: "unchanged",
  134. prev: peers(n(1, "foo"), n(2, "bar")),
  135. mapRes: &tailcfg.MapResponse{},
  136. want: peers(n(1, "foo"), n(2, "bar")),
  137. },
  138. {
  139. name: "online_change",
  140. prev: peers(n(1, "foo"), n(2, "bar")),
  141. mapRes: &tailcfg.MapResponse{
  142. OnlineChange: map[tailcfg.NodeID]bool{
  143. 1: true,
  144. 404: true,
  145. },
  146. },
  147. want: peers(
  148. n(1, "foo", online(true)),
  149. n(2, "bar"),
  150. ),
  151. wantStats: updateStats{changed: 1},
  152. },
  153. {
  154. name: "online_change_offline",
  155. prev: peers(n(1, "foo"), n(2, "bar")),
  156. mapRes: &tailcfg.MapResponse{
  157. OnlineChange: map[tailcfg.NodeID]bool{
  158. 1: false,
  159. 2: true,
  160. },
  161. },
  162. want: peers(
  163. n(1, "foo", online(false)),
  164. n(2, "bar", online(true)),
  165. ),
  166. wantStats: updateStats{changed: 2},
  167. },
  168. {
  169. name: "peer_seen_at",
  170. prev: peers(n(1, "foo", seenAt(time.Unix(111, 0))), n(2, "bar")),
  171. curTime: time.Unix(123, 0),
  172. mapRes: &tailcfg.MapResponse{
  173. PeerSeenChange: map[tailcfg.NodeID]bool{
  174. 1: false,
  175. 2: true,
  176. },
  177. },
  178. want: peers(
  179. n(1, "foo"),
  180. n(2, "bar", seenAt(time.Unix(123, 0))),
  181. ),
  182. wantStats: updateStats{changed: 2},
  183. },
  184. {
  185. name: "ep_change_derp",
  186. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"))),
  187. mapRes: &tailcfg.MapResponse{
  188. PeersChangedPatch: []*tailcfg.PeerChange{{
  189. NodeID: 1,
  190. DERPRegion: 4,
  191. }},
  192. },
  193. want: peers(n(1, "foo", withDERP("127.3.3.40:4"))),
  194. wantStats: updateStats{changed: 1},
  195. },
  196. {
  197. name: "ep_change_udp",
  198. prev: peers(n(1, "foo", withEP("1.2.3.4:111"))),
  199. mapRes: &tailcfg.MapResponse{
  200. PeersChangedPatch: []*tailcfg.PeerChange{{
  201. NodeID: 1,
  202. Endpoints: eps("1.2.3.4:56"),
  203. }},
  204. },
  205. want: peers(n(1, "foo", withEP("1.2.3.4:56"))),
  206. wantStats: updateStats{changed: 1},
  207. },
  208. {
  209. name: "ep_change_udp_2",
  210. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
  211. mapRes: &tailcfg.MapResponse{
  212. PeersChangedPatch: []*tailcfg.PeerChange{{
  213. NodeID: 1,
  214. Endpoints: eps("1.2.3.4:56"),
  215. }},
  216. },
  217. want: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:56"))),
  218. wantStats: updateStats{changed: 1},
  219. },
  220. {
  221. name: "ep_change_both",
  222. prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
  223. mapRes: &tailcfg.MapResponse{
  224. PeersChangedPatch: []*tailcfg.PeerChange{{
  225. NodeID: 1,
  226. DERPRegion: 2,
  227. Endpoints: eps("1.2.3.4:56"),
  228. }},
  229. },
  230. want: peers(n(1, "foo", withDERP("127.3.3.40:2"), withEP("1.2.3.4:56"))),
  231. wantStats: updateStats{changed: 1},
  232. },
  233. {
  234. name: "change_key",
  235. prev: peers(n(1, "foo")),
  236. mapRes: &tailcfg.MapResponse{
  237. PeersChangedPatch: []*tailcfg.PeerChange{{
  238. NodeID: 1,
  239. Key: ptr.To(key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  240. }},
  241. }, want: peers(&tailcfg.Node{
  242. ID: 1,
  243. Name: "foo",
  244. Key: key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  245. }),
  246. wantStats: updateStats{changed: 1},
  247. },
  248. {
  249. name: "change_key_signature",
  250. prev: peers(n(1, "foo")),
  251. mapRes: &tailcfg.MapResponse{
  252. PeersChangedPatch: []*tailcfg.PeerChange{{
  253. NodeID: 1,
  254. KeySignature: []byte{3, 4},
  255. }},
  256. },
  257. want: peers(&tailcfg.Node{
  258. ID: 1,
  259. Name: "foo",
  260. KeySignature: []byte{3, 4},
  261. }),
  262. wantStats: updateStats{changed: 1},
  263. },
  264. {
  265. name: "change_disco_key",
  266. prev: peers(n(1, "foo")),
  267. mapRes: &tailcfg.MapResponse{
  268. PeersChangedPatch: []*tailcfg.PeerChange{{
  269. NodeID: 1,
  270. DiscoKey: ptr.To(key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
  271. }},
  272. },
  273. want: peers(&tailcfg.Node{
  274. ID: 1,
  275. Name: "foo",
  276. DiscoKey: key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
  277. }),
  278. wantStats: updateStats{changed: 1},
  279. },
  280. {
  281. name: "change_online",
  282. prev: peers(n(1, "foo")),
  283. mapRes: &tailcfg.MapResponse{
  284. PeersChangedPatch: []*tailcfg.PeerChange{{
  285. NodeID: 1,
  286. Online: ptr.To(true),
  287. }},
  288. },
  289. want: peers(&tailcfg.Node{
  290. ID: 1,
  291. Name: "foo",
  292. Online: ptr.To(true),
  293. }),
  294. wantStats: updateStats{changed: 1},
  295. },
  296. {
  297. name: "change_last_seen",
  298. prev: peers(n(1, "foo")),
  299. mapRes: &tailcfg.MapResponse{
  300. PeersChangedPatch: []*tailcfg.PeerChange{{
  301. NodeID: 1,
  302. LastSeen: ptr.To(time.Unix(123, 0).UTC()),
  303. }},
  304. },
  305. want: peers(&tailcfg.Node{
  306. ID: 1,
  307. Name: "foo",
  308. LastSeen: ptr.To(time.Unix(123, 0).UTC()),
  309. }),
  310. wantStats: updateStats{changed: 1},
  311. },
  312. {
  313. name: "change_key_expiry",
  314. prev: peers(n(1, "foo")),
  315. mapRes: &tailcfg.MapResponse{
  316. PeersChangedPatch: []*tailcfg.PeerChange{{
  317. NodeID: 1,
  318. KeyExpiry: ptr.To(time.Unix(123, 0).UTC()),
  319. }},
  320. },
  321. want: peers(&tailcfg.Node{
  322. ID: 1,
  323. Name: "foo",
  324. KeyExpiry: time.Unix(123, 0).UTC(),
  325. }),
  326. wantStats: updateStats{changed: 1},
  327. },
  328. }
  329. for _, tt := range tests {
  330. t.Run(tt.name, func(t *testing.T) {
  331. if !tt.curTime.IsZero() {
  332. curTime = tt.curTime
  333. tstest.Replace(t, &clock, tstime.Clock(tstest.NewClock(tstest.ClockOpts{Start: curTime})))
  334. }
  335. ms := newTestMapSession(t, nil)
  336. for _, n := range tt.prev {
  337. mak.Set(&ms.peers, n.ID, ptr.To(n.View()))
  338. }
  339. ms.rebuildSorted()
  340. gotStats := ms.updatePeersStateFromResponse(tt.mapRes)
  341. got := make([]*tailcfg.Node, len(ms.sortedPeers))
  342. for i, vp := range ms.sortedPeers {
  343. got[i] = vp.AsStruct()
  344. }
  345. if gotStats != tt.wantStats {
  346. t.Errorf("got stats = %+v; want %+v", gotStats, tt.wantStats)
  347. }
  348. if !reflect.DeepEqual(got, tt.want) {
  349. t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(got), formatNodes(tt.want))
  350. }
  351. })
  352. }
  353. }
  354. func formatNodes(nodes []*tailcfg.Node) string {
  355. var sb strings.Builder
  356. for i, n := range nodes {
  357. if i > 0 {
  358. sb.WriteString(", ")
  359. }
  360. fmt.Fprintf(&sb, "(%d, %q", n.ID, n.Name)
  361. if n.Online != nil {
  362. fmt.Fprintf(&sb, ", online=%v", *n.Online)
  363. }
  364. if n.LastSeen != nil {
  365. fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen.Unix())
  366. }
  367. if n.Key != (key.NodePublic{}) {
  368. fmt.Fprintf(&sb, ", key=%v", n.Key.String())
  369. }
  370. if n.Expired {
  371. fmt.Fprintf(&sb, ", expired=true")
  372. }
  373. sb.WriteString(")")
  374. }
  375. return sb.String()
  376. }
  377. func newTestMapSession(t testing.TB, nu NetmapUpdater) *mapSession {
  378. ms := newMapSession(key.NewNode(), nu, new(controlknobs.Knobs))
  379. t.Cleanup(ms.Close)
  380. ms.logf = t.Logf
  381. return ms
  382. }
  383. func (ms *mapSession) netmapForResponse(res *tailcfg.MapResponse) *netmap.NetworkMap {
  384. ms.updateStateFromResponse(res)
  385. return ms.netmap()
  386. }
  387. func TestNetmapForResponse(t *testing.T) {
  388. t.Run("implicit_packetfilter", func(t *testing.T) {
  389. somePacketFilter := []tailcfg.FilterRule{
  390. {
  391. SrcIPs: []string{"*"},
  392. DstPorts: []tailcfg.NetPortRange{
  393. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  394. },
  395. },
  396. }
  397. ms := newTestMapSession(t, nil)
  398. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  399. Node: new(tailcfg.Node),
  400. PacketFilter: somePacketFilter,
  401. })
  402. if len(nm1.PacketFilter) == 0 {
  403. t.Fatalf("zero length PacketFilter")
  404. }
  405. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  406. Node: new(tailcfg.Node),
  407. PacketFilter: nil, // testing that the server can omit this.
  408. })
  409. if len(nm1.PacketFilter) == 0 {
  410. t.Fatalf("zero length PacketFilter in 2nd netmap")
  411. }
  412. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  413. t.Error("packet filters differ")
  414. }
  415. })
  416. t.Run("implicit_dnsconfig", func(t *testing.T) {
  417. someDNSConfig := &tailcfg.DNSConfig{Domains: []string{"foo", "bar"}}
  418. ms := newTestMapSession(t, nil)
  419. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  420. Node: new(tailcfg.Node),
  421. DNSConfig: someDNSConfig,
  422. })
  423. if !reflect.DeepEqual(nm1.DNS, *someDNSConfig) {
  424. t.Fatalf("1st DNS wrong")
  425. }
  426. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  427. Node: new(tailcfg.Node),
  428. DNSConfig: nil, // implicit
  429. })
  430. if !reflect.DeepEqual(nm2.DNS, *someDNSConfig) {
  431. t.Fatalf("2nd DNS wrong")
  432. }
  433. })
  434. t.Run("collect_services", func(t *testing.T) {
  435. ms := newTestMapSession(t, nil)
  436. var nm *netmap.NetworkMap
  437. wantCollect := func(v bool) {
  438. t.Helper()
  439. if nm.CollectServices != v {
  440. t.Errorf("netmap.CollectServices = %v; want %v", nm.CollectServices, v)
  441. }
  442. }
  443. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  444. Node: new(tailcfg.Node),
  445. })
  446. wantCollect(false)
  447. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  448. Node: new(tailcfg.Node),
  449. CollectServices: "false",
  450. })
  451. wantCollect(false)
  452. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  453. Node: new(tailcfg.Node),
  454. CollectServices: "true",
  455. })
  456. wantCollect(true)
  457. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  458. Node: new(tailcfg.Node),
  459. CollectServices: "",
  460. })
  461. wantCollect(true)
  462. })
  463. t.Run("implicit_domain", func(t *testing.T) {
  464. ms := newTestMapSession(t, nil)
  465. var nm *netmap.NetworkMap
  466. want := func(v string) {
  467. t.Helper()
  468. if nm.Domain != v {
  469. t.Errorf("netmap.Domain = %q; want %q", nm.Domain, v)
  470. }
  471. }
  472. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  473. Node: new(tailcfg.Node),
  474. Domain: "foo.com",
  475. })
  476. want("foo.com")
  477. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  478. Node: new(tailcfg.Node),
  479. })
  480. want("foo.com")
  481. })
  482. t.Run("implicit_node", func(t *testing.T) {
  483. someNode := &tailcfg.Node{
  484. Name: "foo",
  485. }
  486. wantNode := (&tailcfg.Node{
  487. Name: "foo",
  488. ComputedName: "foo",
  489. ComputedNameWithHost: "foo",
  490. }).View()
  491. ms := newTestMapSession(t, nil)
  492. mapRes := &tailcfg.MapResponse{
  493. Node: someNode,
  494. }
  495. initDisplayNames(mapRes.Node.View(), mapRes)
  496. ms.updateStateFromResponse(mapRes)
  497. nm1 := ms.netmap()
  498. if !nm1.SelfNode.Valid() {
  499. t.Fatal("nil Node in 1st netmap")
  500. }
  501. if !reflect.DeepEqual(nm1.SelfNode, wantNode) {
  502. j, _ := json.Marshal(nm1.SelfNode)
  503. t.Errorf("Node mismatch in 1st netmap; got: %s", j)
  504. }
  505. ms.updateStateFromResponse(&tailcfg.MapResponse{})
  506. nm2 := ms.netmap()
  507. if !nm2.SelfNode.Valid() {
  508. t.Fatal("nil Node in 1st netmap")
  509. }
  510. if !reflect.DeepEqual(nm2.SelfNode, wantNode) {
  511. j, _ := json.Marshal(nm2.SelfNode)
  512. t.Errorf("Node mismatch in 2nd netmap; got: %s", j)
  513. }
  514. })
  515. t.Run("named_packetfilter", func(t *testing.T) {
  516. pfA := []tailcfg.FilterRule{
  517. {
  518. SrcIPs: []string{"10.0.0.1"},
  519. DstPorts: []tailcfg.NetPortRange{
  520. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  521. },
  522. },
  523. }
  524. pfB := []tailcfg.FilterRule{
  525. {
  526. SrcIPs: []string{"10.0.0.2"},
  527. DstPorts: []tailcfg.NetPortRange{
  528. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  529. },
  530. },
  531. }
  532. ms := newTestMapSession(t, nil)
  533. // Mix of old & new style (PacketFilter and PacketFilters).
  534. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  535. Node: new(tailcfg.Node),
  536. PacketFilter: pfA,
  537. PacketFilters: map[string][]tailcfg.FilterRule{
  538. "pf-b": pfB,
  539. },
  540. })
  541. if got, want := len(nm1.PacketFilter), 2; got != want {
  542. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  543. }
  544. if got, want := first(nm1.PacketFilter[0].Srcs).String(), "10.0.0.1/32"; got != want {
  545. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  546. }
  547. if got, want := first(nm1.PacketFilter[1].Srcs).String(), "10.0.0.2/32"; got != want {
  548. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  549. }
  550. // No-op change. Remember the old stuff.
  551. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  552. Node: new(tailcfg.Node),
  553. PacketFilter: nil,
  554. PacketFilters: nil,
  555. })
  556. if got, want := len(nm2.PacketFilter), 2; got != want {
  557. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  558. }
  559. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  560. t.Error("packet filters differ")
  561. }
  562. // New style only, with clear.
  563. nm3 := ms.netmapForResponse(&tailcfg.MapResponse{
  564. Node: new(tailcfg.Node),
  565. PacketFilter: nil,
  566. PacketFilters: map[string][]tailcfg.FilterRule{
  567. "*": nil,
  568. "pf-b": pfB,
  569. },
  570. })
  571. if got, want := len(nm3.PacketFilter), 1; got != want {
  572. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  573. }
  574. if got, want := first(nm3.PacketFilter[0].Srcs).String(), "10.0.0.2/32"; got != want {
  575. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  576. }
  577. // New style only, adding pfA back, not as the legacy "base" layer:.
  578. nm4 := ms.netmapForResponse(&tailcfg.MapResponse{
  579. Node: new(tailcfg.Node),
  580. PacketFilter: nil,
  581. PacketFilters: map[string][]tailcfg.FilterRule{
  582. "pf-a": pfA,
  583. },
  584. })
  585. if got, want := len(nm4.PacketFilter), 2; got != want {
  586. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  587. }
  588. if got, want := first(nm4.PacketFilter[0].Srcs).String(), "10.0.0.1/32"; got != want {
  589. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  590. }
  591. if got, want := first(nm4.PacketFilter[1].Srcs).String(), "10.0.0.2/32"; got != want {
  592. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  593. }
  594. })
  595. }
  596. func first[T any](s []T) T {
  597. if len(s) == 0 {
  598. var zero T
  599. return zero
  600. }
  601. return s[0]
  602. }
  603. func TestDeltaDERPMap(t *testing.T) {
  604. regions1 := map[int]*tailcfg.DERPRegion{
  605. 1: {
  606. RegionID: 1,
  607. Nodes: []*tailcfg.DERPNode{{
  608. Name: "derp1a",
  609. RegionID: 1,
  610. HostName: "derp1a" + tailcfg.DotInvalid,
  611. IPv4: "169.254.169.254",
  612. IPv6: "none",
  613. }},
  614. },
  615. }
  616. // As above, but with a changed IPv4 addr
  617. regions2 := map[int]*tailcfg.DERPRegion{1: regions1[1].Clone()}
  618. regions2[1].Nodes[0].IPv4 = "127.0.0.1"
  619. type step struct {
  620. got *tailcfg.DERPMap
  621. want *tailcfg.DERPMap
  622. }
  623. tests := []struct {
  624. name string
  625. steps []step
  626. }{
  627. {
  628. name: "nothing-to-nothing",
  629. steps: []step{
  630. {nil, nil},
  631. {nil, nil},
  632. },
  633. },
  634. {
  635. name: "regions-sticky",
  636. steps: []step{
  637. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  638. {&tailcfg.DERPMap{}, &tailcfg.DERPMap{Regions: regions1}},
  639. },
  640. },
  641. {
  642. name: "regions-change",
  643. steps: []step{
  644. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  645. {&tailcfg.DERPMap{Regions: regions2}, &tailcfg.DERPMap{Regions: regions2}},
  646. },
  647. },
  648. {
  649. name: "home-params",
  650. steps: []step{
  651. // Send a DERP map
  652. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  653. // Send home params, want to still have the same regions
  654. {
  655. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{
  656. RegionScore: map[int]float64{1: 0.5},
  657. }},
  658. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  659. RegionScore: map[int]float64{1: 0.5},
  660. }},
  661. },
  662. },
  663. },
  664. {
  665. name: "home-params-sub-fields",
  666. steps: []step{
  667. // Send a DERP map with home params
  668. {
  669. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  670. RegionScore: map[int]float64{1: 0.5},
  671. }},
  672. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  673. RegionScore: map[int]float64{1: 0.5},
  674. }},
  675. },
  676. // Sending a struct with a 'HomeParams' field but nil RegionScore doesn't change home params...
  677. {
  678. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{RegionScore: nil}},
  679. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  680. RegionScore: map[int]float64{1: 0.5},
  681. }},
  682. },
  683. // ... but sending one with a non-nil and empty RegionScore field zeroes that out.
  684. {
  685. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{RegionScore: map[int]float64{}}},
  686. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  687. RegionScore: map[int]float64{},
  688. }},
  689. },
  690. },
  691. },
  692. }
  693. for _, tt := range tests {
  694. t.Run(tt.name, func(t *testing.T) {
  695. ms := newTestMapSession(t, nil)
  696. for stepi, s := range tt.steps {
  697. nm := ms.netmapForResponse(&tailcfg.MapResponse{DERPMap: s.got})
  698. if !reflect.DeepEqual(nm.DERPMap, s.want) {
  699. t.Errorf("unexpected result at step index %v; got: %s", stepi, logger.AsJSON(nm.DERPMap))
  700. }
  701. }
  702. })
  703. }
  704. }
  705. func TestPeerChangeDiff(t *testing.T) {
  706. tests := []struct {
  707. name string
  708. a, b *tailcfg.Node
  709. want *tailcfg.PeerChange // nil means want ok=false, unless wantEqual is set
  710. wantEqual bool // means test wants (nil, true)
  711. }{
  712. {
  713. name: "eq",
  714. a: &tailcfg.Node{ID: 1},
  715. b: &tailcfg.Node{ID: 1},
  716. wantEqual: true,
  717. },
  718. {
  719. name: "patch-derp",
  720. a: &tailcfg.Node{ID: 1, DERP: "127.3.3.40:1"},
  721. b: &tailcfg.Node{ID: 1, DERP: "127.3.3.40:2"},
  722. want: &tailcfg.PeerChange{NodeID: 1, DERPRegion: 2},
  723. },
  724. {
  725. name: "patch-endpoints",
  726. a: &tailcfg.Node{ID: 1, Endpoints: eps("10.0.0.1:1")},
  727. b: &tailcfg.Node{ID: 1, Endpoints: eps("10.0.0.2:2")},
  728. want: &tailcfg.PeerChange{NodeID: 1, Endpoints: eps("10.0.0.2:2")},
  729. },
  730. {
  731. name: "patch-cap",
  732. a: &tailcfg.Node{ID: 1, Cap: 1},
  733. b: &tailcfg.Node{ID: 1, Cap: 2},
  734. want: &tailcfg.PeerChange{NodeID: 1, Cap: 2},
  735. },
  736. {
  737. name: "patch-lastseen",
  738. a: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(1, 0))},
  739. b: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
  740. want: &tailcfg.PeerChange{NodeID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
  741. },
  742. {
  743. name: "patch-online-to-true",
  744. a: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
  745. b: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
  746. want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(true)},
  747. },
  748. {
  749. name: "patch-online-to-false",
  750. a: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
  751. b: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
  752. want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(false)},
  753. },
  754. {
  755. name: "mix-patchable-and-not",
  756. a: &tailcfg.Node{ID: 1, Cap: 1},
  757. b: &tailcfg.Node{ID: 1, Cap: 2, StableID: "foo"},
  758. want: nil,
  759. },
  760. {
  761. name: "miss-change-stableid",
  762. a: &tailcfg.Node{ID: 1},
  763. b: &tailcfg.Node{ID: 1, StableID: "diff"},
  764. want: nil,
  765. },
  766. {
  767. name: "miss-change-id",
  768. a: &tailcfg.Node{ID: 1},
  769. b: &tailcfg.Node{ID: 2},
  770. want: nil,
  771. },
  772. {
  773. name: "miss-change-name",
  774. a: &tailcfg.Node{ID: 1, Name: "foo"},
  775. b: &tailcfg.Node{ID: 1, Name: "bar"},
  776. want: nil,
  777. },
  778. {
  779. name: "miss-change-user",
  780. a: &tailcfg.Node{ID: 1, User: 1},
  781. b: &tailcfg.Node{ID: 1, User: 2},
  782. want: nil,
  783. },
  784. {
  785. name: "miss-change-masq-v4",
  786. a: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  787. b: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.2"))},
  788. want: nil,
  789. },
  790. {
  791. name: "miss-change-masq-v6",
  792. a: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  793. b: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3006"))},
  794. want: nil,
  795. },
  796. {
  797. name: "patch-capmap-add-value-to-existing-key",
  798. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  799. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: []tailcfg.RawMessage{"true"}}},
  800. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: []tailcfg.RawMessage{"true"}}},
  801. },
  802. {
  803. name: "patch-capmap-add-new-key",
  804. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  805. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}},
  806. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}},
  807. }, {
  808. name: "patch-capmap-remove-key",
  809. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  810. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{}},
  811. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}},
  812. }, {
  813. name: "patch-capmap-remove-as-nil",
  814. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  815. b: &tailcfg.Node{ID: 1},
  816. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}},
  817. }, {
  818. name: "patch-capmap-add-key-to-empty-map",
  819. a: &tailcfg.Node{ID: 1},
  820. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  821. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  822. },
  823. {
  824. name: "patch-capmap-no-change",
  825. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  826. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  827. wantEqual: true,
  828. },
  829. }
  830. for _, tt := range tests {
  831. t.Run(tt.name, func(t *testing.T) {
  832. pc, ok := peerChangeDiff(tt.a.View(), tt.b)
  833. if tt.wantEqual {
  834. if !ok || pc != nil {
  835. t.Errorf("got (%p, %v); want (nil, true); pc=%v", pc, ok, logger.AsJSON(pc))
  836. }
  837. return
  838. }
  839. if (pc != nil) != ok {
  840. t.Fatalf("inconsistent ok=%v, pc=%p", ok, pc)
  841. }
  842. if !reflect.DeepEqual(pc, tt.want) {
  843. t.Errorf("mismatch\n got: %v\nwant: %v\n", logger.AsJSON(pc), logger.AsJSON(tt.want))
  844. }
  845. })
  846. }
  847. }
  848. func TestPeerChangeDiffAllocs(t *testing.T) {
  849. a := &tailcfg.Node{ID: 1}
  850. b := &tailcfg.Node{ID: 1}
  851. n := testing.AllocsPerRun(10000, func() {
  852. diff, ok := peerChangeDiff(a.View(), b)
  853. if !ok || diff != nil {
  854. t.Fatalf("unexpected result: (%s, %v)", logger.AsJSON(diff), ok)
  855. }
  856. })
  857. if n != 0 {
  858. t.Errorf("allocs = %v; want 0", int(n))
  859. }
  860. }
  861. type countingNetmapUpdater struct {
  862. full atomic.Int64
  863. }
  864. func (nu *countingNetmapUpdater) UpdateFullNetmap(nm *netmap.NetworkMap) {
  865. nu.full.Add(1)
  866. }
  867. // tests (*mapSession).patchifyPeersChanged; smaller tests are in TestPeerChangeDiff
  868. func TestPatchifyPeersChanged(t *testing.T) {
  869. hi := (&tailcfg.Hostinfo{}).View()
  870. tests := []struct {
  871. name string
  872. mr0 *tailcfg.MapResponse // initial
  873. mr1 *tailcfg.MapResponse // incremental
  874. want *tailcfg.MapResponse // what the incremental one should've been mutated to
  875. }{
  876. {
  877. name: "change_one_endpoint",
  878. mr0: &tailcfg.MapResponse{
  879. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  880. Peers: []*tailcfg.Node{
  881. {ID: 1, Hostinfo: hi},
  882. },
  883. },
  884. mr1: &tailcfg.MapResponse{
  885. PeersChanged: []*tailcfg.Node{
  886. {ID: 1, Endpoints: eps("10.0.0.1:1111"), Hostinfo: hi},
  887. },
  888. },
  889. want: &tailcfg.MapResponse{
  890. PeersChanged: nil,
  891. PeersChangedPatch: []*tailcfg.PeerChange{
  892. {NodeID: 1, Endpoints: eps("10.0.0.1:1111")},
  893. },
  894. },
  895. },
  896. {
  897. name: "change_some",
  898. mr0: &tailcfg.MapResponse{
  899. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  900. Peers: []*tailcfg.Node{
  901. {ID: 1, DERP: "127.3.3.40:1", Hostinfo: hi},
  902. {ID: 2, DERP: "127.3.3.40:2", Hostinfo: hi},
  903. {ID: 3, DERP: "127.3.3.40:3", Hostinfo: hi},
  904. },
  905. },
  906. mr1: &tailcfg.MapResponse{
  907. PeersChanged: []*tailcfg.Node{
  908. {ID: 1, DERP: "127.3.3.40:11", Hostinfo: hi},
  909. {ID: 2, StableID: "other-change", Hostinfo: hi},
  910. {ID: 3, DERP: "127.3.3.40:33", Hostinfo: hi},
  911. {ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
  912. },
  913. },
  914. want: &tailcfg.MapResponse{
  915. PeersChanged: []*tailcfg.Node{
  916. {ID: 2, StableID: "other-change", Hostinfo: hi},
  917. {ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
  918. },
  919. PeersChangedPatch: []*tailcfg.PeerChange{
  920. {NodeID: 1, DERPRegion: 11},
  921. {NodeID: 3, DERPRegion: 33},
  922. },
  923. },
  924. },
  925. {
  926. name: "change_exitnodednsresolvers",
  927. mr0: &tailcfg.MapResponse{
  928. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  929. Peers: []*tailcfg.Node{
  930. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  931. },
  932. },
  933. mr1: &tailcfg.MapResponse{
  934. PeersChanged: []*tailcfg.Node{
  935. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  936. },
  937. },
  938. want: &tailcfg.MapResponse{
  939. PeersChanged: []*tailcfg.Node{
  940. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  941. },
  942. },
  943. },
  944. {
  945. name: "same_exitnoderesolvers",
  946. mr0: &tailcfg.MapResponse{
  947. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  948. Peers: []*tailcfg.Node{
  949. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  950. },
  951. },
  952. mr1: &tailcfg.MapResponse{
  953. PeersChanged: []*tailcfg.Node{
  954. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  955. },
  956. },
  957. want: &tailcfg.MapResponse{},
  958. },
  959. }
  960. for _, tt := range tests {
  961. t.Run(tt.name, func(t *testing.T) {
  962. nu := &countingNetmapUpdater{}
  963. ms := newTestMapSession(t, nu)
  964. ms.updateStateFromResponse(tt.mr0)
  965. mr1 := new(tailcfg.MapResponse)
  966. must.Do(json.Unmarshal(must.Get(json.Marshal(tt.mr1)), mr1))
  967. ms.patchifyPeersChanged(mr1)
  968. opts := []cmp.Option{
  969. cmp.Comparer(func(a, b netip.AddrPort) bool { return a == b }),
  970. }
  971. if diff := cmp.Diff(tt.want, mr1, opts...); diff != "" {
  972. t.Errorf("wrong result (-want +got):\n%s", diff)
  973. }
  974. })
  975. }
  976. }
  977. func BenchmarkMapSessionDelta(b *testing.B) {
  978. for _, size := range []int{10, 100, 1_000, 10_000} {
  979. b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
  980. ctx := context.Background()
  981. nu := &countingNetmapUpdater{}
  982. ms := newTestMapSession(b, nu)
  983. res := &tailcfg.MapResponse{
  984. Node: &tailcfg.Node{
  985. ID: 1,
  986. Name: "foo.bar.ts.net.",
  987. },
  988. }
  989. for i := range size {
  990. res.Peers = append(res.Peers, &tailcfg.Node{
  991. ID: tailcfg.NodeID(i + 2),
  992. Name: fmt.Sprintf("peer%d.bar.ts.net.", i),
  993. DERP: "127.3.3.40:10",
  994. Addresses: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  995. AllowedIPs: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  996. Endpoints: eps("192.168.1.2:345", "192.168.1.3:678"),
  997. Hostinfo: (&tailcfg.Hostinfo{
  998. OS: "fooOS",
  999. Hostname: "MyHostname",
  1000. Services: []tailcfg.Service{
  1001. {Proto: "peerapi4", Port: 1234},
  1002. {Proto: "peerapi6", Port: 1234},
  1003. {Proto: "peerapi-dns-proxy", Port: 1},
  1004. },
  1005. }).View(),
  1006. LastSeen: ptr.To(time.Unix(int64(i), 0)),
  1007. })
  1008. }
  1009. ms.HandleNonKeepAliveMapResponse(ctx, res)
  1010. b.ResetTimer()
  1011. b.ReportAllocs()
  1012. // Now for the core of the benchmark loop, just toggle
  1013. // a single node's online status.
  1014. for i := range b.N {
  1015. if err := ms.HandleNonKeepAliveMapResponse(ctx, &tailcfg.MapResponse{
  1016. OnlineChange: map[tailcfg.NodeID]bool{
  1017. 2: i%2 == 0,
  1018. },
  1019. }); err != nil {
  1020. b.Fatal(err)
  1021. }
  1022. }
  1023. })
  1024. }
  1025. }