map_test.go 30 KB

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