map_test.go 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  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. name: "change_capabilities",
  330. prev: peers(n(1, "foo")),
  331. mapRes: &tailcfg.MapResponse{
  332. PeersChangedPatch: []*tailcfg.PeerChange{{
  333. NodeID: 1,
  334. Capabilities: ptr.To([]tailcfg.NodeCapability{"foo"}),
  335. }},
  336. },
  337. want: peers(&tailcfg.Node{
  338. ID: 1,
  339. Name: "foo",
  340. Capabilities: []tailcfg.NodeCapability{"foo"},
  341. }),
  342. wantStats: updateStats{changed: 1},
  343. }}
  344. for _, tt := range tests {
  345. t.Run(tt.name, func(t *testing.T) {
  346. if !tt.curTime.IsZero() {
  347. curTime = tt.curTime
  348. tstest.Replace(t, &clock, tstime.Clock(tstest.NewClock(tstest.ClockOpts{Start: curTime})))
  349. }
  350. ms := newTestMapSession(t, nil)
  351. for _, n := range tt.prev {
  352. mak.Set(&ms.peers, n.ID, ptr.To(n.View()))
  353. }
  354. ms.rebuildSorted()
  355. gotStats := ms.updatePeersStateFromResponse(tt.mapRes)
  356. got := make([]*tailcfg.Node, len(ms.sortedPeers))
  357. for i, vp := range ms.sortedPeers {
  358. got[i] = vp.AsStruct()
  359. }
  360. if gotStats != tt.wantStats {
  361. t.Errorf("got stats = %+v; want %+v", gotStats, tt.wantStats)
  362. }
  363. if !reflect.DeepEqual(got, tt.want) {
  364. t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(got), formatNodes(tt.want))
  365. }
  366. })
  367. }
  368. }
  369. func formatNodes(nodes []*tailcfg.Node) string {
  370. var sb strings.Builder
  371. for i, n := range nodes {
  372. if i > 0 {
  373. sb.WriteString(", ")
  374. }
  375. fmt.Fprintf(&sb, "(%d, %q", n.ID, n.Name)
  376. if n.Online != nil {
  377. fmt.Fprintf(&sb, ", online=%v", *n.Online)
  378. }
  379. if n.LastSeen != nil {
  380. fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen.Unix())
  381. }
  382. if n.Key != (key.NodePublic{}) {
  383. fmt.Fprintf(&sb, ", key=%v", n.Key.String())
  384. }
  385. if n.Expired {
  386. fmt.Fprintf(&sb, ", expired=true")
  387. }
  388. sb.WriteString(")")
  389. }
  390. return sb.String()
  391. }
  392. func newTestMapSession(t testing.TB, nu NetmapUpdater) *mapSession {
  393. ms := newMapSession(key.NewNode(), nu, new(controlknobs.Knobs))
  394. t.Cleanup(ms.Close)
  395. ms.logf = t.Logf
  396. return ms
  397. }
  398. func (ms *mapSession) netmapForResponse(res *tailcfg.MapResponse) *netmap.NetworkMap {
  399. ms.updateStateFromResponse(res)
  400. return ms.netmap()
  401. }
  402. func TestNetmapForResponse(t *testing.T) {
  403. t.Run("implicit_packetfilter", func(t *testing.T) {
  404. somePacketFilter := []tailcfg.FilterRule{
  405. {
  406. SrcIPs: []string{"*"},
  407. DstPorts: []tailcfg.NetPortRange{
  408. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  409. },
  410. },
  411. }
  412. ms := newTestMapSession(t, nil)
  413. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  414. Node: new(tailcfg.Node),
  415. PacketFilter: somePacketFilter,
  416. })
  417. if len(nm1.PacketFilter) == 0 {
  418. t.Fatalf("zero length PacketFilter")
  419. }
  420. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  421. Node: new(tailcfg.Node),
  422. PacketFilter: nil, // testing that the server can omit this.
  423. })
  424. if len(nm1.PacketFilter) == 0 {
  425. t.Fatalf("zero length PacketFilter in 2nd netmap")
  426. }
  427. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  428. t.Error("packet filters differ")
  429. }
  430. })
  431. t.Run("implicit_dnsconfig", func(t *testing.T) {
  432. someDNSConfig := &tailcfg.DNSConfig{Domains: []string{"foo", "bar"}}
  433. ms := newTestMapSession(t, nil)
  434. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  435. Node: new(tailcfg.Node),
  436. DNSConfig: someDNSConfig,
  437. })
  438. if !reflect.DeepEqual(nm1.DNS, *someDNSConfig) {
  439. t.Fatalf("1st DNS wrong")
  440. }
  441. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  442. Node: new(tailcfg.Node),
  443. DNSConfig: nil, // implicit
  444. })
  445. if !reflect.DeepEqual(nm2.DNS, *someDNSConfig) {
  446. t.Fatalf("2nd DNS wrong")
  447. }
  448. })
  449. t.Run("collect_services", func(t *testing.T) {
  450. ms := newTestMapSession(t, nil)
  451. var nm *netmap.NetworkMap
  452. wantCollect := func(v bool) {
  453. t.Helper()
  454. if nm.CollectServices != v {
  455. t.Errorf("netmap.CollectServices = %v; want %v", nm.CollectServices, v)
  456. }
  457. }
  458. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  459. Node: new(tailcfg.Node),
  460. })
  461. wantCollect(false)
  462. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  463. Node: new(tailcfg.Node),
  464. CollectServices: "false",
  465. })
  466. wantCollect(false)
  467. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  468. Node: new(tailcfg.Node),
  469. CollectServices: "true",
  470. })
  471. wantCollect(true)
  472. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  473. Node: new(tailcfg.Node),
  474. CollectServices: "",
  475. })
  476. wantCollect(true)
  477. })
  478. t.Run("implicit_domain", func(t *testing.T) {
  479. ms := newTestMapSession(t, nil)
  480. var nm *netmap.NetworkMap
  481. want := func(v string) {
  482. t.Helper()
  483. if nm.Domain != v {
  484. t.Errorf("netmap.Domain = %q; want %q", nm.Domain, v)
  485. }
  486. }
  487. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  488. Node: new(tailcfg.Node),
  489. Domain: "foo.com",
  490. })
  491. want("foo.com")
  492. nm = ms.netmapForResponse(&tailcfg.MapResponse{
  493. Node: new(tailcfg.Node),
  494. })
  495. want("foo.com")
  496. })
  497. t.Run("implicit_node", func(t *testing.T) {
  498. someNode := &tailcfg.Node{
  499. Name: "foo",
  500. }
  501. wantNode := (&tailcfg.Node{
  502. Name: "foo",
  503. ComputedName: "foo",
  504. ComputedNameWithHost: "foo",
  505. }).View()
  506. ms := newTestMapSession(t, nil)
  507. mapRes := &tailcfg.MapResponse{
  508. Node: someNode,
  509. }
  510. initDisplayNames(mapRes.Node.View(), mapRes)
  511. ms.updateStateFromResponse(mapRes)
  512. nm1 := ms.netmap()
  513. if !nm1.SelfNode.Valid() {
  514. t.Fatal("nil Node in 1st netmap")
  515. }
  516. if !reflect.DeepEqual(nm1.SelfNode, wantNode) {
  517. j, _ := json.Marshal(nm1.SelfNode)
  518. t.Errorf("Node mismatch in 1st netmap; got: %s", j)
  519. }
  520. ms.updateStateFromResponse(&tailcfg.MapResponse{})
  521. nm2 := ms.netmap()
  522. if !nm2.SelfNode.Valid() {
  523. t.Fatal("nil Node in 1st netmap")
  524. }
  525. if !reflect.DeepEqual(nm2.SelfNode, wantNode) {
  526. j, _ := json.Marshal(nm2.SelfNode)
  527. t.Errorf("Node mismatch in 2nd netmap; got: %s", j)
  528. }
  529. })
  530. t.Run("named_packetfilter", func(t *testing.T) {
  531. pfA := []tailcfg.FilterRule{
  532. {
  533. SrcIPs: []string{"10.0.0.1"},
  534. DstPorts: []tailcfg.NetPortRange{
  535. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  536. },
  537. },
  538. }
  539. pfB := []tailcfg.FilterRule{
  540. {
  541. SrcIPs: []string{"10.0.0.2"},
  542. DstPorts: []tailcfg.NetPortRange{
  543. {IP: "10.2.3.4", Ports: tailcfg.PortRange{First: 22, Last: 22}},
  544. },
  545. },
  546. }
  547. ms := newTestMapSession(t, nil)
  548. // Mix of old & new style (PacketFilter and PacketFilters).
  549. nm1 := ms.netmapForResponse(&tailcfg.MapResponse{
  550. Node: new(tailcfg.Node),
  551. PacketFilter: pfA,
  552. PacketFilters: map[string][]tailcfg.FilterRule{
  553. "pf-b": pfB,
  554. },
  555. })
  556. if got, want := len(nm1.PacketFilter), 2; got != want {
  557. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  558. }
  559. if got, want := first(nm1.PacketFilter[0].Srcs).String(), "10.0.0.1/32"; got != want {
  560. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  561. }
  562. if got, want := first(nm1.PacketFilter[1].Srcs).String(), "10.0.0.2/32"; got != want {
  563. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  564. }
  565. // No-op change. Remember the old stuff.
  566. nm2 := ms.netmapForResponse(&tailcfg.MapResponse{
  567. Node: new(tailcfg.Node),
  568. PacketFilter: nil,
  569. PacketFilters: nil,
  570. })
  571. if got, want := len(nm2.PacketFilter), 2; got != want {
  572. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  573. }
  574. if !reflect.DeepEqual(nm1.PacketFilter, nm2.PacketFilter) {
  575. t.Error("packet filters differ")
  576. }
  577. // New style only, with clear.
  578. nm3 := ms.netmapForResponse(&tailcfg.MapResponse{
  579. Node: new(tailcfg.Node),
  580. PacketFilter: nil,
  581. PacketFilters: map[string][]tailcfg.FilterRule{
  582. "*": nil,
  583. "pf-b": pfB,
  584. },
  585. })
  586. if got, want := len(nm3.PacketFilter), 1; got != want {
  587. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  588. }
  589. if got, want := first(nm3.PacketFilter[0].Srcs).String(), "10.0.0.2/32"; got != want {
  590. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  591. }
  592. // New style only, adding pfA back, not as the legacy "base" layer:.
  593. nm4 := ms.netmapForResponse(&tailcfg.MapResponse{
  594. Node: new(tailcfg.Node),
  595. PacketFilter: nil,
  596. PacketFilters: map[string][]tailcfg.FilterRule{
  597. "pf-a": pfA,
  598. },
  599. })
  600. if got, want := len(nm4.PacketFilter), 2; got != want {
  601. t.Fatalf("PacketFilter length = %v; want %v", got, want)
  602. }
  603. if got, want := first(nm4.PacketFilter[0].Srcs).String(), "10.0.0.1/32"; got != want {
  604. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  605. }
  606. if got, want := first(nm4.PacketFilter[1].Srcs).String(), "10.0.0.2/32"; got != want {
  607. t.Fatalf("PacketFilter[0].Srcs = %v; want %v", got, want)
  608. }
  609. })
  610. }
  611. func first[T any](s []T) T {
  612. if len(s) == 0 {
  613. var zero T
  614. return zero
  615. }
  616. return s[0]
  617. }
  618. func TestDeltaDERPMap(t *testing.T) {
  619. regions1 := map[int]*tailcfg.DERPRegion{
  620. 1: {
  621. RegionID: 1,
  622. Nodes: []*tailcfg.DERPNode{{
  623. Name: "derp1a",
  624. RegionID: 1,
  625. HostName: "derp1a" + tailcfg.DotInvalid,
  626. IPv4: "169.254.169.254",
  627. IPv6: "none",
  628. }},
  629. },
  630. }
  631. // As above, but with a changed IPv4 addr
  632. regions2 := map[int]*tailcfg.DERPRegion{1: regions1[1].Clone()}
  633. regions2[1].Nodes[0].IPv4 = "127.0.0.1"
  634. type step struct {
  635. got *tailcfg.DERPMap
  636. want *tailcfg.DERPMap
  637. }
  638. tests := []struct {
  639. name string
  640. steps []step
  641. }{
  642. {
  643. name: "nothing-to-nothing",
  644. steps: []step{
  645. {nil, nil},
  646. {nil, nil},
  647. },
  648. },
  649. {
  650. name: "regions-sticky",
  651. steps: []step{
  652. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  653. {&tailcfg.DERPMap{}, &tailcfg.DERPMap{Regions: regions1}},
  654. },
  655. },
  656. {
  657. name: "regions-change",
  658. steps: []step{
  659. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  660. {&tailcfg.DERPMap{Regions: regions2}, &tailcfg.DERPMap{Regions: regions2}},
  661. },
  662. },
  663. {
  664. name: "home-params",
  665. steps: []step{
  666. // Send a DERP map
  667. {&tailcfg.DERPMap{Regions: regions1}, &tailcfg.DERPMap{Regions: regions1}},
  668. // Send home params, want to still have the same regions
  669. {
  670. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{
  671. RegionScore: map[int]float64{1: 0.5},
  672. }},
  673. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  674. RegionScore: map[int]float64{1: 0.5},
  675. }},
  676. },
  677. },
  678. },
  679. {
  680. name: "home-params-sub-fields",
  681. steps: []step{
  682. // Send a DERP map with home params
  683. {
  684. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  685. RegionScore: map[int]float64{1: 0.5},
  686. }},
  687. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  688. RegionScore: map[int]float64{1: 0.5},
  689. }},
  690. },
  691. // Sending a struct with a 'HomeParams' field but nil RegionScore doesn't change home params...
  692. {
  693. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{RegionScore: nil}},
  694. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  695. RegionScore: map[int]float64{1: 0.5},
  696. }},
  697. },
  698. // ... but sending one with a non-nil and empty RegionScore field zeroes that out.
  699. {
  700. &tailcfg.DERPMap{HomeParams: &tailcfg.DERPHomeParams{RegionScore: map[int]float64{}}},
  701. &tailcfg.DERPMap{Regions: regions1, HomeParams: &tailcfg.DERPHomeParams{
  702. RegionScore: map[int]float64{},
  703. }},
  704. },
  705. },
  706. },
  707. }
  708. for _, tt := range tests {
  709. t.Run(tt.name, func(t *testing.T) {
  710. ms := newTestMapSession(t, nil)
  711. for stepi, s := range tt.steps {
  712. nm := ms.netmapForResponse(&tailcfg.MapResponse{DERPMap: s.got})
  713. if !reflect.DeepEqual(nm.DERPMap, s.want) {
  714. t.Errorf("unexpected result at step index %v; got: %s", stepi, logger.AsJSON(nm.DERPMap))
  715. }
  716. }
  717. })
  718. }
  719. }
  720. func TestPeerChangeDiff(t *testing.T) {
  721. tests := []struct {
  722. name string
  723. a, b *tailcfg.Node
  724. want *tailcfg.PeerChange // nil means want ok=false, unless wantEqual is set
  725. wantEqual bool // means test wants (nil, true)
  726. }{
  727. {
  728. name: "eq",
  729. a: &tailcfg.Node{ID: 1},
  730. b: &tailcfg.Node{ID: 1},
  731. wantEqual: true,
  732. },
  733. {
  734. name: "patch-derp",
  735. a: &tailcfg.Node{ID: 1, DERP: "127.3.3.40:1"},
  736. b: &tailcfg.Node{ID: 1, DERP: "127.3.3.40:2"},
  737. want: &tailcfg.PeerChange{NodeID: 1, DERPRegion: 2},
  738. },
  739. {
  740. name: "patch-endpoints",
  741. a: &tailcfg.Node{ID: 1, Endpoints: eps("10.0.0.1:1")},
  742. b: &tailcfg.Node{ID: 1, Endpoints: eps("10.0.0.2:2")},
  743. want: &tailcfg.PeerChange{NodeID: 1, Endpoints: eps("10.0.0.2:2")},
  744. },
  745. {
  746. name: "patch-cap",
  747. a: &tailcfg.Node{ID: 1, Cap: 1},
  748. b: &tailcfg.Node{ID: 1, Cap: 2},
  749. want: &tailcfg.PeerChange{NodeID: 1, Cap: 2},
  750. },
  751. {
  752. name: "patch-lastseen",
  753. a: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(1, 0))},
  754. b: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
  755. want: &tailcfg.PeerChange{NodeID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
  756. },
  757. {
  758. name: "patch-capabilities-to-nonempty",
  759. a: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"foo"}},
  760. b: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"bar"}},
  761. want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]tailcfg.NodeCapability{"bar"})},
  762. },
  763. {
  764. name: "patch-capabilities-to-empty",
  765. a: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"foo"}},
  766. b: &tailcfg.Node{ID: 1},
  767. want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]tailcfg.NodeCapability(nil))},
  768. },
  769. {
  770. name: "patch-online-to-true",
  771. a: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
  772. b: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
  773. want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(true)},
  774. },
  775. {
  776. name: "patch-online-to-false",
  777. a: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
  778. b: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
  779. want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(false)},
  780. },
  781. {
  782. name: "mix-patchable-and-not",
  783. a: &tailcfg.Node{ID: 1, Cap: 1},
  784. b: &tailcfg.Node{ID: 1, Cap: 2, StableID: "foo"},
  785. want: nil,
  786. },
  787. {
  788. name: "miss-change-stableid",
  789. a: &tailcfg.Node{ID: 1},
  790. b: &tailcfg.Node{ID: 1, StableID: "diff"},
  791. want: nil,
  792. },
  793. {
  794. name: "miss-change-id",
  795. a: &tailcfg.Node{ID: 1},
  796. b: &tailcfg.Node{ID: 2},
  797. want: nil,
  798. },
  799. {
  800. name: "miss-change-name",
  801. a: &tailcfg.Node{ID: 1, Name: "foo"},
  802. b: &tailcfg.Node{ID: 1, Name: "bar"},
  803. want: nil,
  804. },
  805. {
  806. name: "miss-change-user",
  807. a: &tailcfg.Node{ID: 1, User: 1},
  808. b: &tailcfg.Node{ID: 1, User: 2},
  809. want: nil,
  810. },
  811. {
  812. name: "miss-change-masq-v4",
  813. a: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  814. b: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.2"))},
  815. want: nil,
  816. },
  817. {
  818. name: "miss-change-masq-v6",
  819. a: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  820. b: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3006"))},
  821. want: nil,
  822. }}
  823. for _, tt := range tests {
  824. t.Run(tt.name, func(t *testing.T) {
  825. pc, ok := peerChangeDiff(tt.a.View(), tt.b)
  826. if tt.wantEqual {
  827. if !ok || pc != nil {
  828. t.Errorf("got (%p, %v); want (nil, true); pc=%v", pc, ok, logger.AsJSON(pc))
  829. }
  830. return
  831. }
  832. if (pc != nil) != ok {
  833. t.Fatalf("inconsistent ok=%v, pc=%p", ok, pc)
  834. }
  835. if !reflect.DeepEqual(pc, tt.want) {
  836. t.Errorf("mismatch\n got: %v\nwant: %v\n", logger.AsJSON(pc), logger.AsJSON(tt.want))
  837. }
  838. })
  839. }
  840. }
  841. func TestPeerChangeDiffAllocs(t *testing.T) {
  842. a := &tailcfg.Node{ID: 1}
  843. b := &tailcfg.Node{ID: 1}
  844. n := testing.AllocsPerRun(10000, func() {
  845. diff, ok := peerChangeDiff(a.View(), b)
  846. if !ok || diff != nil {
  847. t.Fatalf("unexpected result: (%s, %v)", logger.AsJSON(diff), ok)
  848. }
  849. })
  850. if n != 0 {
  851. t.Errorf("allocs = %v; want 0", int(n))
  852. }
  853. }
  854. type countingNetmapUpdater struct {
  855. full atomic.Int64
  856. }
  857. func (nu *countingNetmapUpdater) UpdateFullNetmap(nm *netmap.NetworkMap) {
  858. nu.full.Add(1)
  859. }
  860. // tests (*mapSession).patchifyPeersChanged; smaller tests are in TestPeerChangeDiff
  861. func TestPatchifyPeersChanged(t *testing.T) {
  862. hi := (&tailcfg.Hostinfo{}).View()
  863. tests := []struct {
  864. name string
  865. mr0 *tailcfg.MapResponse // initial
  866. mr1 *tailcfg.MapResponse // incremental
  867. want *tailcfg.MapResponse // what the incremental one should've been mutated to
  868. }{
  869. {
  870. name: "change_one_endpoint",
  871. mr0: &tailcfg.MapResponse{
  872. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  873. Peers: []*tailcfg.Node{
  874. {ID: 1, Hostinfo: hi},
  875. },
  876. },
  877. mr1: &tailcfg.MapResponse{
  878. PeersChanged: []*tailcfg.Node{
  879. {ID: 1, Endpoints: eps("10.0.0.1:1111"), Hostinfo: hi},
  880. },
  881. },
  882. want: &tailcfg.MapResponse{
  883. PeersChanged: nil,
  884. PeersChangedPatch: []*tailcfg.PeerChange{
  885. {NodeID: 1, Endpoints: eps("10.0.0.1:1111")},
  886. },
  887. },
  888. },
  889. {
  890. name: "change_some",
  891. mr0: &tailcfg.MapResponse{
  892. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  893. Peers: []*tailcfg.Node{
  894. {ID: 1, DERP: "127.3.3.40:1", Hostinfo: hi},
  895. {ID: 2, DERP: "127.3.3.40:2", Hostinfo: hi},
  896. {ID: 3, DERP: "127.3.3.40:3", Hostinfo: hi},
  897. },
  898. },
  899. mr1: &tailcfg.MapResponse{
  900. PeersChanged: []*tailcfg.Node{
  901. {ID: 1, DERP: "127.3.3.40:11", Hostinfo: hi},
  902. {ID: 2, StableID: "other-change", Hostinfo: hi},
  903. {ID: 3, DERP: "127.3.3.40:33", Hostinfo: hi},
  904. {ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
  905. },
  906. },
  907. want: &tailcfg.MapResponse{
  908. PeersChanged: []*tailcfg.Node{
  909. {ID: 2, StableID: "other-change", Hostinfo: hi},
  910. {ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
  911. },
  912. PeersChangedPatch: []*tailcfg.PeerChange{
  913. {NodeID: 1, DERPRegion: 11},
  914. {NodeID: 3, DERPRegion: 33},
  915. },
  916. },
  917. },
  918. {
  919. name: "change_exitnodednsresolvers",
  920. mr0: &tailcfg.MapResponse{
  921. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  922. Peers: []*tailcfg.Node{
  923. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  924. },
  925. },
  926. mr1: &tailcfg.MapResponse{
  927. PeersChanged: []*tailcfg.Node{
  928. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  929. },
  930. },
  931. want: &tailcfg.MapResponse{
  932. PeersChanged: []*tailcfg.Node{
  933. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  934. },
  935. },
  936. },
  937. {
  938. name: "same_exitnoderesolvers",
  939. mr0: &tailcfg.MapResponse{
  940. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  941. Peers: []*tailcfg.Node{
  942. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  943. },
  944. },
  945. mr1: &tailcfg.MapResponse{
  946. PeersChanged: []*tailcfg.Node{
  947. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  948. },
  949. },
  950. want: &tailcfg.MapResponse{},
  951. },
  952. }
  953. for _, tt := range tests {
  954. t.Run(tt.name, func(t *testing.T) {
  955. nu := &countingNetmapUpdater{}
  956. ms := newTestMapSession(t, nu)
  957. ms.updateStateFromResponse(tt.mr0)
  958. mr1 := new(tailcfg.MapResponse)
  959. must.Do(json.Unmarshal(must.Get(json.Marshal(tt.mr1)), mr1))
  960. ms.patchifyPeersChanged(mr1)
  961. opts := []cmp.Option{
  962. cmp.Comparer(func(a, b netip.AddrPort) bool { return a == b }),
  963. }
  964. if diff := cmp.Diff(tt.want, mr1, opts...); diff != "" {
  965. t.Errorf("wrong result (-want +got):\n%s", diff)
  966. }
  967. })
  968. }
  969. }
  970. func BenchmarkMapSessionDelta(b *testing.B) {
  971. for _, size := range []int{10, 100, 1_000, 10_000} {
  972. b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
  973. ctx := context.Background()
  974. nu := &countingNetmapUpdater{}
  975. ms := newTestMapSession(b, nu)
  976. res := &tailcfg.MapResponse{
  977. Node: &tailcfg.Node{
  978. ID: 1,
  979. Name: "foo.bar.ts.net.",
  980. },
  981. }
  982. for i := 0; i < size; i++ {
  983. res.Peers = append(res.Peers, &tailcfg.Node{
  984. ID: tailcfg.NodeID(i + 2),
  985. Name: fmt.Sprintf("peer%d.bar.ts.net.", i),
  986. DERP: "127.3.3.40:10",
  987. Addresses: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  988. AllowedIPs: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  989. Endpoints: eps("192.168.1.2:345", "192.168.1.3:678"),
  990. Hostinfo: (&tailcfg.Hostinfo{
  991. OS: "fooOS",
  992. Hostname: "MyHostname",
  993. Services: []tailcfg.Service{
  994. {Proto: "peerapi4", Port: 1234},
  995. {Proto: "peerapi6", Port: 1234},
  996. {Proto: "peerapi-dns-proxy", Port: 1},
  997. },
  998. }).View(),
  999. LastSeen: ptr.To(time.Unix(int64(i), 0)),
  1000. })
  1001. }
  1002. ms.HandleNonKeepAliveMapResponse(ctx, res)
  1003. b.ResetTimer()
  1004. b.ReportAllocs()
  1005. // Now for the core of the benchmark loop, just toggle
  1006. // a single node's online status.
  1007. for i := 0; i < b.N; i++ {
  1008. if err := ms.HandleNonKeepAliveMapResponse(ctx, &tailcfg.MapResponse{
  1009. OnlineChange: map[tailcfg.NodeID]bool{
  1010. 2: i%2 == 0,
  1011. },
  1012. }); err != nil {
  1013. b.Fatal(err)
  1014. }
  1015. }
  1016. })
  1017. }
  1018. }