map_test.go 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088
  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. {
  824. name: "patch-capmap-add-value-to-existing-key",
  825. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  826. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: []tailcfg.RawMessage{"true"}}},
  827. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: []tailcfg.RawMessage{"true"}}},
  828. },
  829. {
  830. name: "patch-capmap-add-new-key",
  831. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  832. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}},
  833. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil, tailcfg.CapabilityDebug: nil}},
  834. }, {
  835. name: "patch-capmap-remove-key",
  836. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  837. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{}},
  838. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}},
  839. }, {
  840. name: "patch-capmap-remove-as-nil",
  841. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  842. b: &tailcfg.Node{ID: 1},
  843. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{}},
  844. }, {
  845. name: "patch-capmap-add-key-to-empty-map",
  846. a: &tailcfg.Node{ID: 1},
  847. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  848. want: &tailcfg.PeerChange{NodeID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  849. },
  850. {
  851. name: "patch-capmap-no-change",
  852. a: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  853. b: &tailcfg.Node{ID: 1, CapMap: tailcfg.NodeCapMap{tailcfg.CapabilityAdmin: nil}},
  854. wantEqual: true,
  855. },
  856. }
  857. for _, tt := range tests {
  858. t.Run(tt.name, func(t *testing.T) {
  859. pc, ok := peerChangeDiff(tt.a.View(), tt.b)
  860. if tt.wantEqual {
  861. if !ok || pc != nil {
  862. t.Errorf("got (%p, %v); want (nil, true); pc=%v", pc, ok, logger.AsJSON(pc))
  863. }
  864. return
  865. }
  866. if (pc != nil) != ok {
  867. t.Fatalf("inconsistent ok=%v, pc=%p", ok, pc)
  868. }
  869. if !reflect.DeepEqual(pc, tt.want) {
  870. t.Errorf("mismatch\n got: %v\nwant: %v\n", logger.AsJSON(pc), logger.AsJSON(tt.want))
  871. }
  872. })
  873. }
  874. }
  875. func TestPeerChangeDiffAllocs(t *testing.T) {
  876. a := &tailcfg.Node{ID: 1}
  877. b := &tailcfg.Node{ID: 1}
  878. n := testing.AllocsPerRun(10000, func() {
  879. diff, ok := peerChangeDiff(a.View(), b)
  880. if !ok || diff != nil {
  881. t.Fatalf("unexpected result: (%s, %v)", logger.AsJSON(diff), ok)
  882. }
  883. })
  884. if n != 0 {
  885. t.Errorf("allocs = %v; want 0", int(n))
  886. }
  887. }
  888. type countingNetmapUpdater struct {
  889. full atomic.Int64
  890. }
  891. func (nu *countingNetmapUpdater) UpdateFullNetmap(nm *netmap.NetworkMap) {
  892. nu.full.Add(1)
  893. }
  894. // tests (*mapSession).patchifyPeersChanged; smaller tests are in TestPeerChangeDiff
  895. func TestPatchifyPeersChanged(t *testing.T) {
  896. hi := (&tailcfg.Hostinfo{}).View()
  897. tests := []struct {
  898. name string
  899. mr0 *tailcfg.MapResponse // initial
  900. mr1 *tailcfg.MapResponse // incremental
  901. want *tailcfg.MapResponse // what the incremental one should've been mutated to
  902. }{
  903. {
  904. name: "change_one_endpoint",
  905. mr0: &tailcfg.MapResponse{
  906. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  907. Peers: []*tailcfg.Node{
  908. {ID: 1, Hostinfo: hi},
  909. },
  910. },
  911. mr1: &tailcfg.MapResponse{
  912. PeersChanged: []*tailcfg.Node{
  913. {ID: 1, Endpoints: eps("10.0.0.1:1111"), Hostinfo: hi},
  914. },
  915. },
  916. want: &tailcfg.MapResponse{
  917. PeersChanged: nil,
  918. PeersChangedPatch: []*tailcfg.PeerChange{
  919. {NodeID: 1, Endpoints: eps("10.0.0.1:1111")},
  920. },
  921. },
  922. },
  923. {
  924. name: "change_some",
  925. mr0: &tailcfg.MapResponse{
  926. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  927. Peers: []*tailcfg.Node{
  928. {ID: 1, DERP: "127.3.3.40:1", Hostinfo: hi},
  929. {ID: 2, DERP: "127.3.3.40:2", Hostinfo: hi},
  930. {ID: 3, DERP: "127.3.3.40:3", Hostinfo: hi},
  931. },
  932. },
  933. mr1: &tailcfg.MapResponse{
  934. PeersChanged: []*tailcfg.Node{
  935. {ID: 1, DERP: "127.3.3.40:11", Hostinfo: hi},
  936. {ID: 2, StableID: "other-change", Hostinfo: hi},
  937. {ID: 3, DERP: "127.3.3.40:33", Hostinfo: hi},
  938. {ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
  939. },
  940. },
  941. want: &tailcfg.MapResponse{
  942. PeersChanged: []*tailcfg.Node{
  943. {ID: 2, StableID: "other-change", Hostinfo: hi},
  944. {ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
  945. },
  946. PeersChangedPatch: []*tailcfg.PeerChange{
  947. {NodeID: 1, DERPRegion: 11},
  948. {NodeID: 3, DERPRegion: 33},
  949. },
  950. },
  951. },
  952. {
  953. name: "change_exitnodednsresolvers",
  954. mr0: &tailcfg.MapResponse{
  955. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  956. Peers: []*tailcfg.Node{
  957. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  958. },
  959. },
  960. mr1: &tailcfg.MapResponse{
  961. PeersChanged: []*tailcfg.Node{
  962. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  963. },
  964. },
  965. want: &tailcfg.MapResponse{
  966. PeersChanged: []*tailcfg.Node{
  967. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns2.exmaple.com"}}, Hostinfo: hi},
  968. },
  969. },
  970. },
  971. {
  972. name: "same_exitnoderesolvers",
  973. mr0: &tailcfg.MapResponse{
  974. Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
  975. Peers: []*tailcfg.Node{
  976. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  977. },
  978. },
  979. mr1: &tailcfg.MapResponse{
  980. PeersChanged: []*tailcfg.Node{
  981. {ID: 1, ExitNodeDNSResolvers: []*dnstype.Resolver{{Addr: "dns.exmaple.com"}}, Hostinfo: hi},
  982. },
  983. },
  984. want: &tailcfg.MapResponse{},
  985. },
  986. }
  987. for _, tt := range tests {
  988. t.Run(tt.name, func(t *testing.T) {
  989. nu := &countingNetmapUpdater{}
  990. ms := newTestMapSession(t, nu)
  991. ms.updateStateFromResponse(tt.mr0)
  992. mr1 := new(tailcfg.MapResponse)
  993. must.Do(json.Unmarshal(must.Get(json.Marshal(tt.mr1)), mr1))
  994. ms.patchifyPeersChanged(mr1)
  995. opts := []cmp.Option{
  996. cmp.Comparer(func(a, b netip.AddrPort) bool { return a == b }),
  997. }
  998. if diff := cmp.Diff(tt.want, mr1, opts...); diff != "" {
  999. t.Errorf("wrong result (-want +got):\n%s", diff)
  1000. }
  1001. })
  1002. }
  1003. }
  1004. func BenchmarkMapSessionDelta(b *testing.B) {
  1005. for _, size := range []int{10, 100, 1_000, 10_000} {
  1006. b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
  1007. ctx := context.Background()
  1008. nu := &countingNetmapUpdater{}
  1009. ms := newTestMapSession(b, nu)
  1010. res := &tailcfg.MapResponse{
  1011. Node: &tailcfg.Node{
  1012. ID: 1,
  1013. Name: "foo.bar.ts.net.",
  1014. },
  1015. }
  1016. for i := 0; i < size; i++ {
  1017. res.Peers = append(res.Peers, &tailcfg.Node{
  1018. ID: tailcfg.NodeID(i + 2),
  1019. Name: fmt.Sprintf("peer%d.bar.ts.net.", i),
  1020. DERP: "127.3.3.40:10",
  1021. Addresses: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  1022. AllowedIPs: []netip.Prefix{netip.MustParsePrefix("100.100.2.3/32"), netip.MustParsePrefix("fd7a:115c:a1e0::123/128")},
  1023. Endpoints: eps("192.168.1.2:345", "192.168.1.3:678"),
  1024. Hostinfo: (&tailcfg.Hostinfo{
  1025. OS: "fooOS",
  1026. Hostname: "MyHostname",
  1027. Services: []tailcfg.Service{
  1028. {Proto: "peerapi4", Port: 1234},
  1029. {Proto: "peerapi6", Port: 1234},
  1030. {Proto: "peerapi-dns-proxy", Port: 1},
  1031. },
  1032. }).View(),
  1033. LastSeen: ptr.To(time.Unix(int64(i), 0)),
  1034. })
  1035. }
  1036. ms.HandleNonKeepAliveMapResponse(ctx, res)
  1037. b.ResetTimer()
  1038. b.ReportAllocs()
  1039. // Now for the core of the benchmark loop, just toggle
  1040. // a single node's online status.
  1041. for i := 0; i < b.N; i++ {
  1042. if err := ms.HandleNonKeepAliveMapResponse(ctx, &tailcfg.MapResponse{
  1043. OnlineChange: map[tailcfg.NodeID]bool{
  1044. 2: i%2 == 0,
  1045. },
  1046. }); err != nil {
  1047. b.Fatal(err)
  1048. }
  1049. }
  1050. })
  1051. }
  1052. }