netstack_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. // Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package netstack
  5. import (
  6. "fmt"
  7. "net/netip"
  8. "runtime"
  9. "testing"
  10. "tailscale.com/ipn"
  11. "tailscale.com/ipn/ipnlocal"
  12. "tailscale.com/ipn/store/mem"
  13. "tailscale.com/net/packet"
  14. "tailscale.com/net/tsaddr"
  15. "tailscale.com/net/tsdial"
  16. "tailscale.com/net/tstun"
  17. "tailscale.com/types/ipproto"
  18. "tailscale.com/wgengine"
  19. "tailscale.com/wgengine/filter"
  20. )
  21. // TestInjectInboundLeak tests that injectInbound doesn't leak memory.
  22. // See https://github.com/tailscale/tailscale/issues/3762
  23. func TestInjectInboundLeak(t *testing.T) {
  24. tunDev := tstun.NewFake()
  25. dialer := new(tsdial.Dialer)
  26. logf := func(format string, args ...any) {
  27. if !t.Failed() {
  28. t.Logf(format, args...)
  29. }
  30. }
  31. eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
  32. Tun: tunDev,
  33. Dialer: dialer,
  34. })
  35. if err != nil {
  36. t.Fatal(err)
  37. }
  38. defer eng.Close()
  39. ig, ok := eng.(wgengine.InternalsGetter)
  40. if !ok {
  41. t.Fatal("not an InternalsGetter")
  42. }
  43. tunWrap, magicSock, dns, ok := ig.GetInternals()
  44. if !ok {
  45. t.Fatal("failed to get internals")
  46. }
  47. lb, err := ipnlocal.NewLocalBackend(logf, "logid", new(mem.Store), "", dialer, eng, 0)
  48. if err != nil {
  49. t.Fatal(err)
  50. }
  51. ns, err := Create(logf, tunWrap, eng, magicSock, dialer, dns)
  52. if err != nil {
  53. t.Fatal(err)
  54. }
  55. defer ns.Close()
  56. ns.ProcessLocalIPs = true
  57. if err := ns.Start(lb); err != nil {
  58. t.Fatalf("Start: %v", err)
  59. }
  60. ns.atomicIsLocalIPFunc.Store(func(netip.Addr) bool { return true })
  61. pkt := &packet.Parsed{}
  62. const N = 10_000
  63. ms0 := getMemStats()
  64. for i := 0; i < N; i++ {
  65. outcome := ns.injectInbound(pkt, tunWrap)
  66. if outcome != filter.DropSilently {
  67. t.Fatalf("got outcome %v; want DropSilently", outcome)
  68. }
  69. }
  70. ms1 := getMemStats()
  71. if grew := int64(ms1.HeapObjects) - int64(ms0.HeapObjects); grew >= N {
  72. t.Fatalf("grew by %v (which is too much and >= the %v packets we sent)", grew, N)
  73. }
  74. }
  75. func getMemStats() (ms runtime.MemStats) {
  76. runtime.GC()
  77. runtime.ReadMemStats(&ms)
  78. return
  79. }
  80. func makeNetstack(t *testing.T, config func(*Impl)) *Impl {
  81. tunDev := tstun.NewFake()
  82. dialer := new(tsdial.Dialer)
  83. logf := func(format string, args ...any) {
  84. if !t.Failed() {
  85. t.Helper()
  86. t.Logf(format, args...)
  87. }
  88. }
  89. eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
  90. Tun: tunDev,
  91. Dialer: dialer,
  92. })
  93. if err != nil {
  94. t.Fatal(err)
  95. }
  96. t.Cleanup(func() { eng.Close() })
  97. ig, ok := eng.(wgengine.InternalsGetter)
  98. if !ok {
  99. t.Fatal("not an InternalsGetter")
  100. }
  101. tunWrap, magicSock, dns, ok := ig.GetInternals()
  102. if !ok {
  103. t.Fatal("failed to get internals")
  104. }
  105. ns, err := Create(logf, tunWrap, eng, magicSock, dialer, dns)
  106. if err != nil {
  107. t.Fatal(err)
  108. }
  109. t.Cleanup(func() { ns.Close() })
  110. lb, err := ipnlocal.NewLocalBackend(logf, "logid", new(mem.Store), "", dialer, eng, 0)
  111. if err != nil {
  112. t.Fatalf("NewLocalBackend: %v", err)
  113. }
  114. ns.atomicIsLocalIPFunc.Store(func(netip.Addr) bool { return true })
  115. if config != nil {
  116. config(ns)
  117. }
  118. if err := ns.Start(lb); err != nil {
  119. t.Fatalf("Start: %v", err)
  120. }
  121. return ns
  122. }
  123. func TestShouldHandlePing(t *testing.T) {
  124. srcIP := netip.AddrFrom4([4]byte{1, 2, 3, 4})
  125. t.Run("ICMP4", func(t *testing.T) {
  126. dst := netip.MustParseAddr("5.6.7.8")
  127. icmph := packet.ICMP4Header{
  128. IP4Header: packet.IP4Header{
  129. IPProto: ipproto.ICMPv4,
  130. Src: srcIP,
  131. Dst: dst,
  132. },
  133. Type: packet.ICMP4EchoRequest,
  134. Code: packet.ICMP4NoCode,
  135. }
  136. _, payload := packet.ICMPEchoPayload(nil)
  137. icmpPing := packet.Generate(icmph, payload)
  138. pkt := &packet.Parsed{}
  139. pkt.Decode(icmpPing)
  140. impl := makeNetstack(t, func(impl *Impl) {
  141. impl.ProcessSubnets = true
  142. })
  143. pingDst, ok := impl.shouldHandlePing(pkt)
  144. if !ok {
  145. t.Errorf("expected shouldHandlePing==true")
  146. }
  147. if pingDst != dst {
  148. t.Errorf("got dst %s; want %s", pingDst, dst)
  149. }
  150. })
  151. t.Run("ICMP6-no-via", func(t *testing.T) {
  152. dst := netip.MustParseAddr("2a09:8280:1::4169")
  153. icmph := packet.ICMP6Header{
  154. IP6Header: packet.IP6Header{
  155. IPProto: ipproto.ICMPv6,
  156. Src: srcIP,
  157. Dst: dst,
  158. },
  159. Type: packet.ICMP6EchoRequest,
  160. Code: packet.ICMP6NoCode,
  161. }
  162. _, payload := packet.ICMPEchoPayload(nil)
  163. icmpPing := packet.Generate(icmph, payload)
  164. pkt := &packet.Parsed{}
  165. pkt.Decode(icmpPing)
  166. impl := makeNetstack(t, func(impl *Impl) {
  167. impl.ProcessSubnets = true
  168. })
  169. pingDst, ok := impl.shouldHandlePing(pkt)
  170. // Expect that we handle this since it's going out onto the
  171. // network.
  172. if !ok {
  173. t.Errorf("expected shouldHandlePing==true")
  174. }
  175. if pingDst != dst {
  176. t.Errorf("got dst %s; want %s", pingDst, dst)
  177. }
  178. })
  179. t.Run("ICMP6-tailscale-addr", func(t *testing.T) {
  180. dst := netip.MustParseAddr("fd7a:115c:a1e0:ab12::1")
  181. icmph := packet.ICMP6Header{
  182. IP6Header: packet.IP6Header{
  183. IPProto: ipproto.ICMPv6,
  184. Src: srcIP,
  185. Dst: dst,
  186. },
  187. Type: packet.ICMP6EchoRequest,
  188. Code: packet.ICMP6NoCode,
  189. }
  190. _, payload := packet.ICMPEchoPayload(nil)
  191. icmpPing := packet.Generate(icmph, payload)
  192. pkt := &packet.Parsed{}
  193. pkt.Decode(icmpPing)
  194. impl := makeNetstack(t, func(impl *Impl) {
  195. impl.ProcessSubnets = true
  196. })
  197. _, ok := impl.shouldHandlePing(pkt)
  198. // We don't handle this because it's a Tailscale IP and not 4via6
  199. if ok {
  200. t.Errorf("expected shouldHandlePing==false")
  201. }
  202. })
  203. // Handle pings for 4via6 addresses regardless of ProcessSubnets
  204. for _, subnets := range []bool{true, false} {
  205. t.Run("ICMP6-4via6-ProcessSubnets-"+fmt.Sprint(subnets), func(t *testing.T) {
  206. // The 4via6 route 10.1.1.0/24 siteid 7, and then the IP
  207. // 10.1.1.9 within that route.
  208. dst := netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:7:a01:109")
  209. expectedPingDst := netip.MustParseAddr("10.1.1.9")
  210. icmph := packet.ICMP6Header{
  211. IP6Header: packet.IP6Header{
  212. IPProto: ipproto.ICMPv6,
  213. Src: srcIP,
  214. Dst: dst,
  215. },
  216. Type: packet.ICMP6EchoRequest,
  217. Code: packet.ICMP6NoCode,
  218. }
  219. _, payload := packet.ICMPEchoPayload(nil)
  220. icmpPing := packet.Generate(icmph, payload)
  221. pkt := &packet.Parsed{}
  222. pkt.Decode(icmpPing)
  223. impl := makeNetstack(t, func(impl *Impl) {
  224. impl.ProcessSubnets = subnets
  225. })
  226. pingDst, ok := impl.shouldHandlePing(pkt)
  227. // Handled due to being 4via6
  228. if !ok {
  229. t.Errorf("expected shouldHandlePing==true")
  230. } else if pingDst != expectedPingDst {
  231. t.Errorf("got dst %s; want %s", pingDst, expectedPingDst)
  232. }
  233. })
  234. }
  235. }
  236. // looksLikeATailscaleSelfAddress reports whether addr looks like
  237. // a Tailscale self address, for tests.
  238. func looksLikeATailscaleSelfAddress(addr netip.Addr) bool {
  239. return addr.Is4() && tsaddr.IsTailscaleIP(addr) ||
  240. addr.Is6() && tsaddr.Tailscale4To6Range().Contains(addr)
  241. }
  242. func TestShouldProcessInbound(t *testing.T) {
  243. testCases := []struct {
  244. name string
  245. pkt *packet.Parsed
  246. afterStart func(*Impl) // optional; after Impl.Start is called
  247. beforeStart func(*Impl) // optional; before Impl.Start is called
  248. want bool
  249. runOnGOOS string
  250. }{
  251. {
  252. name: "ipv6-via",
  253. pkt: &packet.Parsed{
  254. IPVersion: 6,
  255. IPProto: ipproto.TCP,
  256. Src: netip.MustParseAddrPort("100.101.102.103:1234"),
  257. // $ tailscale debug via 7 10.1.1.9/24
  258. // fd7a:115c:a1e0:b1a:0:7:a01:109/120
  259. Dst: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:7:a01:109]:5678"),
  260. TCPFlags: packet.TCPSyn,
  261. },
  262. afterStart: func(i *Impl) {
  263. prefs := ipn.NewPrefs()
  264. prefs.AdvertiseRoutes = []netip.Prefix{
  265. // $ tailscale debug via 7 10.1.1.0/24
  266. // fd7a:115c:a1e0:b1a:0:7:a01:100/120
  267. netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:7:a01:100/120"),
  268. }
  269. i.lb.Start(ipn.Options{
  270. LegacyMigrationPrefs: prefs,
  271. })
  272. i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress)
  273. },
  274. beforeStart: func(i *Impl) {
  275. // This should be handled even if we're
  276. // otherwise not processing local IPs or
  277. // subnets.
  278. i.ProcessLocalIPs = false
  279. i.ProcessSubnets = false
  280. },
  281. want: true,
  282. },
  283. {
  284. name: "ipv6-via-not-advertised",
  285. pkt: &packet.Parsed{
  286. IPVersion: 6,
  287. IPProto: ipproto.TCP,
  288. Src: netip.MustParseAddrPort("100.101.102.103:1234"),
  289. // $ tailscale debug via 7 10.1.1.9/24
  290. // fd7a:115c:a1e0:b1a:0:7:a01:109/120
  291. Dst: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:7:a01:109]:5678"),
  292. TCPFlags: packet.TCPSyn,
  293. },
  294. afterStart: func(i *Impl) {
  295. prefs := ipn.NewPrefs()
  296. prefs.AdvertiseRoutes = []netip.Prefix{
  297. // tailscale debug via 7 10.1.2.0/24
  298. // fd7a:115c:a1e0:b1a:0:7:a01:200/120
  299. netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:7:a01:200/120"),
  300. }
  301. i.lb.Start(ipn.Options{
  302. LegacyMigrationPrefs: prefs,
  303. })
  304. },
  305. want: false,
  306. },
  307. {
  308. name: "tailscale-ssh-enabled",
  309. pkt: &packet.Parsed{
  310. IPVersion: 4,
  311. IPProto: ipproto.TCP,
  312. Src: netip.MustParseAddrPort("100.101.102.103:1234"),
  313. Dst: netip.MustParseAddrPort("100.101.102.104:22"),
  314. TCPFlags: packet.TCPSyn,
  315. },
  316. afterStart: func(i *Impl) {
  317. prefs := ipn.NewPrefs()
  318. prefs.RunSSH = true
  319. i.lb.Start(ipn.Options{
  320. LegacyMigrationPrefs: prefs,
  321. })
  322. i.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
  323. return addr.String() == "100.101.102.104" // Dst, above
  324. })
  325. },
  326. want: true,
  327. runOnGOOS: "linux",
  328. },
  329. {
  330. name: "tailscale-ssh-disabled",
  331. pkt: &packet.Parsed{
  332. IPVersion: 4,
  333. IPProto: ipproto.TCP,
  334. Src: netip.MustParseAddrPort("100.101.102.103:1234"),
  335. Dst: netip.MustParseAddrPort("100.101.102.104:22"),
  336. TCPFlags: packet.TCPSyn,
  337. },
  338. afterStart: func(i *Impl) {
  339. prefs := ipn.NewPrefs()
  340. prefs.RunSSH = false // default, but to be explicit
  341. i.lb.Start(ipn.Options{
  342. LegacyMigrationPrefs: prefs,
  343. })
  344. i.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
  345. return addr.String() == "100.101.102.104" // Dst, above
  346. })
  347. },
  348. want: false,
  349. },
  350. {
  351. name: "process-local-ips",
  352. pkt: &packet.Parsed{
  353. IPVersion: 4,
  354. IPProto: ipproto.TCP,
  355. Src: netip.MustParseAddrPort("100.101.102.103:1234"),
  356. Dst: netip.MustParseAddrPort("100.101.102.104:4567"),
  357. TCPFlags: packet.TCPSyn,
  358. },
  359. afterStart: func(i *Impl) {
  360. i.ProcessLocalIPs = true
  361. i.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
  362. return addr.String() == "100.101.102.104" // Dst, above
  363. })
  364. },
  365. want: true,
  366. },
  367. {
  368. name: "process-subnets",
  369. pkt: &packet.Parsed{
  370. IPVersion: 4,
  371. IPProto: ipproto.TCP,
  372. Src: netip.MustParseAddrPort("100.101.102.103:1234"),
  373. Dst: netip.MustParseAddrPort("10.1.2.3:4567"),
  374. TCPFlags: packet.TCPSyn,
  375. },
  376. beforeStart: func(i *Impl) {
  377. i.ProcessSubnets = true
  378. },
  379. afterStart: func(i *Impl) {
  380. // For testing purposes, assume all Tailscale
  381. // IPs are local; the Dst above is something
  382. // not in that range.
  383. i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress)
  384. },
  385. want: true,
  386. },
  387. {
  388. name: "peerapi-port-subnet-router", // see #6235
  389. pkt: &packet.Parsed{
  390. IPVersion: 4,
  391. IPProto: ipproto.TCP,
  392. Src: netip.MustParseAddrPort("100.101.102.103:1234"),
  393. Dst: netip.MustParseAddrPort("10.0.0.23:5555"),
  394. TCPFlags: packet.TCPSyn,
  395. },
  396. beforeStart: func(i *Impl) {
  397. // As if we were running on Linux where netstack isn't used.
  398. i.ProcessSubnets = false
  399. i.atomicIsLocalIPFunc.Store(func(netip.Addr) bool { return false })
  400. },
  401. afterStart: func(i *Impl) {
  402. prefs := ipn.NewPrefs()
  403. prefs.AdvertiseRoutes = []netip.Prefix{
  404. netip.MustParsePrefix("10.0.0.1/24"),
  405. }
  406. i.lb.Start(ipn.Options{
  407. LegacyMigrationPrefs: prefs,
  408. })
  409. // Set the PeerAPI port to the Dst port above.
  410. i.peerapiPort4Atomic.Store(5555)
  411. i.peerapiPort6Atomic.Store(5555)
  412. },
  413. want: false,
  414. },
  415. // TODO(andrew): test PeerAPI
  416. // TODO(andrew): test TCP packets without the SYN flag set
  417. }
  418. for _, tc := range testCases {
  419. t.Run(tc.name, func(t *testing.T) {
  420. if tc.runOnGOOS != "" && runtime.GOOS != tc.runOnGOOS {
  421. t.Skipf("skipping on GOOS=%v", runtime.GOOS)
  422. }
  423. impl := makeNetstack(t, tc.beforeStart)
  424. if tc.afterStart != nil {
  425. tc.afterStart(impl)
  426. }
  427. got := impl.shouldProcessInbound(tc.pkt, nil)
  428. if got != tc.want {
  429. t.Errorf("got shouldProcessInbound()=%v; want %v", got, tc.want)
  430. } else {
  431. t.Logf("OK: shouldProcessInbound() = %v", got)
  432. }
  433. })
  434. }
  435. }