| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package netstack
- import (
- "context"
- "fmt"
- "maps"
- "net"
- "net/netip"
- "runtime"
- "testing"
- "time"
- "gvisor.dev/gvisor/pkg/buffer"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "tailscale.com/envknob"
- "tailscale.com/ipn"
- "tailscale.com/ipn/ipnlocal"
- "tailscale.com/ipn/store/mem"
- "tailscale.com/metrics"
- "tailscale.com/net/netx"
- "tailscale.com/net/packet"
- "tailscale.com/net/tsaddr"
- "tailscale.com/net/tsdial"
- "tailscale.com/net/tstun"
- "tailscale.com/tsd"
- "tailscale.com/tstest"
- "tailscale.com/types/ipproto"
- "tailscale.com/types/logid"
- "tailscale.com/wgengine"
- "tailscale.com/wgengine/filter"
- )
- // TestInjectInboundLeak tests that injectInbound doesn't leak memory.
- // See https://github.com/tailscale/tailscale/issues/3762
- func TestInjectInboundLeak(t *testing.T) {
- tunDev := tstun.NewFake()
- dialer := new(tsdial.Dialer)
- logf := func(format string, args ...any) {
- if !t.Failed() {
- t.Logf(format, args...)
- }
- }
- sys := tsd.NewSystem()
- eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
- Tun: tunDev,
- Dialer: dialer,
- SetSubsystem: sys.Set,
- HealthTracker: sys.HealthTracker.Get(),
- Metrics: sys.UserMetricsRegistry(),
- EventBus: sys.Bus.Get(),
- })
- if err != nil {
- t.Fatal(err)
- }
- defer eng.Close()
- sys.Set(eng)
- sys.Set(new(mem.Store))
- tunWrap := sys.Tun.Get()
- lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0)
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(lb.Shutdown)
- ns, err := Create(logf, tunWrap, eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper())
- if err != nil {
- t.Fatal(err)
- }
- defer ns.Close()
- ns.ProcessLocalIPs = true
- if err := ns.Start(lb); err != nil {
- t.Fatalf("Start: %v", err)
- }
- ns.atomicIsLocalIPFunc.Store(func(netip.Addr) bool { return true })
- pkt := &packet.Parsed{}
- const N = 10_000
- ms0 := getMemStats()
- for range N {
- outcome, _ := ns.injectInbound(pkt, tunWrap, nil)
- if outcome != filter.DropSilently {
- t.Fatalf("got outcome %v; want DropSilently", outcome)
- }
- }
- ms1 := getMemStats()
- if grew := int64(ms1.HeapObjects) - int64(ms0.HeapObjects); grew >= N {
- t.Fatalf("grew by %v (which is too much and >= the %v packets we sent)", grew, N)
- }
- }
- func getMemStats() (ms runtime.MemStats) {
- runtime.GC()
- runtime.ReadMemStats(&ms)
- return
- }
- func makeNetstack(tb testing.TB, config func(*Impl)) *Impl {
- tunDev := tstun.NewFake()
- sys := tsd.NewSystem()
- sys.Set(new(mem.Store))
- dialer := new(tsdial.Dialer)
- logf := tstest.WhileTestRunningLogger(tb)
- eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
- Tun: tunDev,
- Dialer: dialer,
- SetSubsystem: sys.Set,
- HealthTracker: sys.HealthTracker.Get(),
- Metrics: sys.UserMetricsRegistry(),
- EventBus: sys.Bus.Get(),
- })
- if err != nil {
- tb.Fatal(err)
- }
- tb.Cleanup(func() { eng.Close() })
- sys.Set(eng)
- ns, err := Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper())
- if err != nil {
- tb.Fatal(err)
- }
- tb.Cleanup(func() { ns.Close() })
- lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0)
- if err != nil {
- tb.Fatalf("NewLocalBackend: %v", err)
- }
- tb.Cleanup(lb.Shutdown)
- ns.atomicIsLocalIPFunc.Store(func(netip.Addr) bool { return true })
- if config != nil {
- config(ns)
- }
- if err := ns.Start(lb); err != nil {
- tb.Fatalf("Start: %v", err)
- }
- return ns
- }
- func TestShouldHandlePing(t *testing.T) {
- srcIP := netip.AddrFrom4([4]byte{1, 2, 3, 4})
- t.Run("ICMP4", func(t *testing.T) {
- dst := netip.MustParseAddr("5.6.7.8")
- icmph := packet.ICMP4Header{
- IP4Header: packet.IP4Header{
- IPProto: ipproto.ICMPv4,
- Src: srcIP,
- Dst: dst,
- },
- Type: packet.ICMP4EchoRequest,
- Code: packet.ICMP4NoCode,
- }
- _, payload := packet.ICMPEchoPayload(nil)
- icmpPing := packet.Generate(icmph, payload)
- pkt := &packet.Parsed{}
- pkt.Decode(icmpPing)
- impl := makeNetstack(t, func(impl *Impl) {
- impl.ProcessSubnets = true
- })
- pingDst, ok := impl.shouldHandlePing(pkt)
- if !ok {
- t.Errorf("expected shouldHandlePing==true")
- }
- if pingDst != dst {
- t.Errorf("got dst %s; want %s", pingDst, dst)
- }
- })
- t.Run("ICMP6-no-via", func(t *testing.T) {
- dst := netip.MustParseAddr("2a09:8280:1::4169")
- icmph := packet.ICMP6Header{
- IP6Header: packet.IP6Header{
- IPProto: ipproto.ICMPv6,
- Src: srcIP,
- Dst: dst,
- },
- Type: packet.ICMP6EchoRequest,
- Code: packet.ICMP6NoCode,
- }
- _, payload := packet.ICMPEchoPayload(nil)
- icmpPing := packet.Generate(icmph, payload)
- pkt := &packet.Parsed{}
- pkt.Decode(icmpPing)
- impl := makeNetstack(t, func(impl *Impl) {
- impl.ProcessSubnets = true
- })
- pingDst, ok := impl.shouldHandlePing(pkt)
- // Expect that we handle this since it's going out onto the
- // network.
- if !ok {
- t.Errorf("expected shouldHandlePing==true")
- }
- if pingDst != dst {
- t.Errorf("got dst %s; want %s", pingDst, dst)
- }
- })
- t.Run("ICMP6-tailscale-addr", func(t *testing.T) {
- dst := netip.MustParseAddr("fd7a:115c:a1e0:ab12::1")
- icmph := packet.ICMP6Header{
- IP6Header: packet.IP6Header{
- IPProto: ipproto.ICMPv6,
- Src: srcIP,
- Dst: dst,
- },
- Type: packet.ICMP6EchoRequest,
- Code: packet.ICMP6NoCode,
- }
- _, payload := packet.ICMPEchoPayload(nil)
- icmpPing := packet.Generate(icmph, payload)
- pkt := &packet.Parsed{}
- pkt.Decode(icmpPing)
- impl := makeNetstack(t, func(impl *Impl) {
- impl.ProcessSubnets = true
- })
- _, ok := impl.shouldHandlePing(pkt)
- // We don't handle this because it's a Tailscale IP and not 4via6
- if ok {
- t.Errorf("expected shouldHandlePing==false")
- }
- })
- // Handle pings for 4via6 addresses regardless of ProcessSubnets
- for _, subnets := range []bool{true, false} {
- t.Run("ICMP6-4via6-ProcessSubnets-"+fmt.Sprint(subnets), func(t *testing.T) {
- // The 4via6 route 10.1.1.0/24 siteid 7, and then the IP
- // 10.1.1.9 within that route.
- dst := netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:7:a01:109")
- expectedPingDst := netip.MustParseAddr("10.1.1.9")
- icmph := packet.ICMP6Header{
- IP6Header: packet.IP6Header{
- IPProto: ipproto.ICMPv6,
- Src: srcIP,
- Dst: dst,
- },
- Type: packet.ICMP6EchoRequest,
- Code: packet.ICMP6NoCode,
- }
- _, payload := packet.ICMPEchoPayload(nil)
- icmpPing := packet.Generate(icmph, payload)
- pkt := &packet.Parsed{}
- pkt.Decode(icmpPing)
- impl := makeNetstack(t, func(impl *Impl) {
- impl.ProcessSubnets = subnets
- })
- pingDst, ok := impl.shouldHandlePing(pkt)
- // Handled due to being 4via6
- if !ok {
- t.Errorf("expected shouldHandlePing==true")
- } else if pingDst != expectedPingDst {
- t.Errorf("got dst %s; want %s", pingDst, expectedPingDst)
- }
- })
- }
- }
- // looksLikeATailscaleSelfAddress reports whether addr looks like
- // a Tailscale self address, for tests.
- func looksLikeATailscaleSelfAddress(addr netip.Addr) bool {
- return addr.Is4() && tsaddr.IsTailscaleIP(addr) ||
- addr.Is6() && tsaddr.Tailscale4To6Range().Contains(addr)
- }
- func TestShouldProcessInbound(t *testing.T) {
- testCases := []struct {
- name string
- pkt *packet.Parsed
- afterStart func(*Impl) // optional; after Impl.Start is called
- beforeStart func(*Impl) // optional; before Impl.Start is called
- want bool
- runOnGOOS string
- }{
- {
- name: "ipv6-via",
- pkt: &packet.Parsed{
- IPVersion: 6,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("100.101.102.103:1234"),
- // $ tailscale debug via 7 10.1.1.9/24
- // fd7a:115c:a1e0:b1a:0:7:a01:109/120
- Dst: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:7:a01:109]:5678"),
- TCPFlags: packet.TCPSyn,
- },
- afterStart: func(i *Impl) {
- prefs := ipn.NewPrefs()
- prefs.AdvertiseRoutes = []netip.Prefix{
- // $ tailscale debug via 7 10.1.1.0/24
- // fd7a:115c:a1e0:b1a:0:7:a01:100/120
- netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:7:a01:100/120"),
- }
- i.lb.Start(ipn.Options{
- UpdatePrefs: prefs,
- })
- i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress)
- },
- beforeStart: func(i *Impl) {
- // This should be handled even if we're
- // otherwise not processing local IPs or
- // subnets.
- i.ProcessLocalIPs = false
- i.ProcessSubnets = false
- },
- want: true,
- },
- {
- name: "ipv6-via-not-advertised",
- pkt: &packet.Parsed{
- IPVersion: 6,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("100.101.102.103:1234"),
- // $ tailscale debug via 7 10.1.1.9/24
- // fd7a:115c:a1e0:b1a:0:7:a01:109/120
- Dst: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:7:a01:109]:5678"),
- TCPFlags: packet.TCPSyn,
- },
- afterStart: func(i *Impl) {
- prefs := ipn.NewPrefs()
- prefs.AdvertiseRoutes = []netip.Prefix{
- // tailscale debug via 7 10.1.2.0/24
- // fd7a:115c:a1e0:b1a:0:7:a01:200/120
- netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:7:a01:200/120"),
- }
- i.lb.Start(ipn.Options{
- UpdatePrefs: prefs,
- })
- },
- want: false,
- },
- {
- name: "tailscale-ssh-enabled",
- pkt: &packet.Parsed{
- IPVersion: 4,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("100.101.102.103:1234"),
- Dst: netip.MustParseAddrPort("100.101.102.104:22"),
- TCPFlags: packet.TCPSyn,
- },
- afterStart: func(i *Impl) {
- prefs := ipn.NewPrefs()
- prefs.RunSSH = true
- i.lb.Start(ipn.Options{
- UpdatePrefs: prefs,
- })
- i.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
- return addr.String() == "100.101.102.104" // Dst, above
- })
- },
- want: true,
- runOnGOOS: "linux",
- },
- {
- name: "tailscale-ssh-disabled",
- pkt: &packet.Parsed{
- IPVersion: 4,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("100.101.102.103:1234"),
- Dst: netip.MustParseAddrPort("100.101.102.104:22"),
- TCPFlags: packet.TCPSyn,
- },
- afterStart: func(i *Impl) {
- prefs := ipn.NewPrefs()
- prefs.RunSSH = false // default, but to be explicit
- i.lb.Start(ipn.Options{
- UpdatePrefs: prefs,
- })
- i.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
- return addr.String() == "100.101.102.104" // Dst, above
- })
- },
- want: false,
- },
- {
- name: "process-local-ips",
- pkt: &packet.Parsed{
- IPVersion: 4,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("100.101.102.103:1234"),
- Dst: netip.MustParseAddrPort("100.101.102.104:4567"),
- TCPFlags: packet.TCPSyn,
- },
- afterStart: func(i *Impl) {
- i.ProcessLocalIPs = true
- i.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
- return addr.String() == "100.101.102.104" // Dst, above
- })
- },
- want: true,
- },
- {
- name: "process-subnets",
- pkt: &packet.Parsed{
- IPVersion: 4,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("100.101.102.103:1234"),
- Dst: netip.MustParseAddrPort("10.1.2.3:4567"),
- TCPFlags: packet.TCPSyn,
- },
- beforeStart: func(i *Impl) {
- i.ProcessSubnets = true
- },
- afterStart: func(i *Impl) {
- // For testing purposes, assume all Tailscale
- // IPs are local; the Dst above is something
- // not in that range.
- i.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress)
- },
- want: true,
- },
- {
- name: "peerapi-port-subnet-router", // see #6235
- pkt: &packet.Parsed{
- IPVersion: 4,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("100.101.102.103:1234"),
- Dst: netip.MustParseAddrPort("10.0.0.23:5555"),
- TCPFlags: packet.TCPSyn,
- },
- beforeStart: func(i *Impl) {
- // As if we were running on Linux where netstack isn't used.
- i.ProcessSubnets = false
- i.atomicIsLocalIPFunc.Store(func(netip.Addr) bool { return false })
- },
- afterStart: func(i *Impl) {
- prefs := ipn.NewPrefs()
- prefs.AdvertiseRoutes = []netip.Prefix{
- netip.MustParsePrefix("10.0.0.1/24"),
- }
- i.lb.Start(ipn.Options{
- UpdatePrefs: prefs,
- })
- // Set the PeerAPI port to the Dst port above.
- i.peerapiPort4Atomic.Store(5555)
- i.peerapiPort6Atomic.Store(5555)
- },
- want: false,
- },
- // TODO(andrew): test PeerAPI
- // TODO(andrew): test TCP packets without the SYN flag set
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.runOnGOOS != "" && runtime.GOOS != tc.runOnGOOS {
- t.Skipf("skipping on GOOS=%v", runtime.GOOS)
- }
- impl := makeNetstack(t, tc.beforeStart)
- if tc.afterStart != nil {
- tc.afterStart(impl)
- }
- got := impl.shouldProcessInbound(tc.pkt, nil)
- if got != tc.want {
- t.Errorf("got shouldProcessInbound()=%v; want %v", got, tc.want)
- } else {
- t.Logf("OK: shouldProcessInbound() = %v", got)
- }
- })
- }
- }
- func tcp4syn(tb testing.TB, src, dst netip.Addr, sport, dport uint16) []byte {
- ip := header.IPv4(make([]byte, header.IPv4MinimumSize+header.TCPMinimumSize))
- ip.Encode(&header.IPv4Fields{
- Protocol: uint8(header.TCPProtocolNumber),
- TotalLength: header.IPv4MinimumSize + header.TCPMinimumSize,
- TTL: 64,
- SrcAddr: tcpip.AddrFrom4Slice(src.AsSlice()),
- DstAddr: tcpip.AddrFrom4Slice(dst.AsSlice()),
- })
- ip.SetChecksum(^ip.CalculateChecksum())
- if !ip.IsChecksumValid() {
- tb.Fatal("test broken; packet has incorrect IP checksum")
- }
- tcp := header.TCP(ip[header.IPv4MinimumSize:])
- tcp.Encode(&header.TCPFields{
- SrcPort: sport,
- DstPort: dport,
- SeqNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 65535,
- Checksum: 0,
- })
- xsum := header.PseudoHeaderChecksum(
- header.TCPProtocolNumber,
- tcpip.AddrFrom4Slice(src.AsSlice()),
- tcpip.AddrFrom4Slice(dst.AsSlice()),
- uint16(header.TCPMinimumSize),
- )
- tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
- if !tcp.IsChecksumValid(tcpip.AddrFrom4Slice(src.AsSlice()), tcpip.AddrFrom4Slice(dst.AsSlice()), 0, 0) {
- tb.Fatal("test broken; packet has incorrect TCP checksum")
- }
- return ip
- }
- // makeHangDialer returns a dialer that notifies the returned channel when a
- // connection is dialed and then hangs until the test finishes.
- func makeHangDialer(tb testing.TB) (netx.DialFunc, chan struct{}) {
- done := make(chan struct{})
- tb.Cleanup(func() {
- close(done)
- })
- gotConn := make(chan struct{}, 1)
- fn := func(ctx context.Context, network, address string) (net.Conn, error) {
- // Signal that we have a new connection
- tb.Logf("hangDialer: called with network=%q address=%q", network, address)
- select {
- case gotConn <- struct{}{}:
- default:
- }
- // Hang until the test is done.
- select {
- case <-ctx.Done():
- tb.Logf("context done")
- case <-done:
- tb.Logf("function completed")
- }
- return nil, fmt.Errorf("canceled")
- }
- return fn, gotConn
- }
- // TestTCPForwardLimits verifies that the limits on the TCP forwarder work in a
- // success case (i.e. when we don't hit the limit).
- func TestTCPForwardLimits(t *testing.T) {
- envknob.Setenv("TS_DEBUG_NETSTACK", "true")
- impl := makeNetstack(t, func(impl *Impl) {
- impl.ProcessSubnets = true
- })
- dialFn, gotConn := makeHangDialer(t)
- impl.forwardDialFunc = dialFn
- prefs := ipn.NewPrefs()
- prefs.AdvertiseRoutes = []netip.Prefix{
- // This is the TEST-NET-1 IP block for use in documentation,
- // and should never actually be routable.
- netip.MustParsePrefix("192.0.2.0/24"),
- }
- impl.lb.Start(ipn.Options{
- UpdatePrefs: prefs,
- })
- impl.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress)
- // Inject an "outbound" packet that's going to an IP address that times
- // out. We need to re-parse from a byte slice so that the internal
- // buffer in the packet.Parsed type is filled out.
- client := netip.MustParseAddr("100.101.102.103")
- destAddr := netip.MustParseAddr("192.0.2.1")
- pkt := tcp4syn(t, client, destAddr, 1234, 4567)
- var parsed packet.Parsed
- parsed.Decode(pkt)
- // When injecting this packet, we want the outcome to be "drop
- // silently", which indicates that netstack is processing the
- // packet and not delivering it to the host system.
- if resp, _ := impl.injectInbound(&parsed, impl.tundev, nil); resp != filter.DropSilently {
- t.Errorf("got filter outcome %v, want filter.DropSilently", resp)
- }
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
- // Wait until we have an in-flight outgoing connection.
- select {
- case <-ctx.Done():
- t.Fatalf("timed out waiting for connection")
- case <-gotConn:
- t.Logf("got connection in progress")
- }
- // Inject another packet, which will be deduplicated and thus not
- // increment our counter.
- parsed.Decode(pkt)
- if resp, _ := impl.injectInbound(&parsed, impl.tundev, nil); resp != filter.DropSilently {
- t.Errorf("got filter outcome %v, want filter.DropSilently", resp)
- }
- // Verify that we now have a single in-flight address in our map.
- impl.mu.Lock()
- inFlight := maps.Clone(impl.connsInFlightByClient)
- impl.mu.Unlock()
- if got, ok := inFlight[client]; !ok || got != 1 {
- t.Errorf("expected 1 in-flight connection for %v, got: %v", client, inFlight)
- }
- // Get the expvar statistics and verify that we're exporting the
- // correct metric.
- metrics := impl.ExpVar().(*metrics.Set)
- const metricName = "gauge_tcp_forward_in_flight"
- if v := metrics.Get(metricName).String(); v != "1" {
- t.Errorf("got metric %q=%s, want 1", metricName, v)
- }
- }
- // TestTCPForwardLimits_PerClient verifies that the per-client limit for TCP
- // forwarding works.
- func TestTCPForwardLimits_PerClient(t *testing.T) {
- envknob.Setenv("TS_DEBUG_NETSTACK", "true")
- // Set our test override limits during this test.
- tstest.Replace(t, &maxInFlightConnectionAttemptsForTest, 2)
- tstest.Replace(t, &maxInFlightConnectionAttemptsPerClientForTest, 1)
- impl := makeNetstack(t, func(impl *Impl) {
- impl.ProcessSubnets = true
- })
- dialFn, gotConn := makeHangDialer(t)
- impl.forwardDialFunc = dialFn
- prefs := ipn.NewPrefs()
- prefs.AdvertiseRoutes = []netip.Prefix{
- // This is the TEST-NET-1 IP block for use in documentation,
- // and should never actually be routable.
- netip.MustParsePrefix("192.0.2.0/24"),
- }
- impl.lb.Start(ipn.Options{
- UpdatePrefs: prefs,
- })
- impl.atomicIsLocalIPFunc.Store(looksLikeATailscaleSelfAddress)
- // Inject an "outbound" packet that's going to an IP address that times
- // out. We need to re-parse from a byte slice so that the internal
- // buffer in the packet.Parsed type is filled out.
- client := netip.MustParseAddr("100.101.102.103")
- destAddr := netip.MustParseAddr("192.0.2.1")
- // Helpers
- var port uint16 = 1234
- mustInjectPacket := func() {
- pkt := tcp4syn(t, client, destAddr, port, 4567)
- port++ // to avoid deduplication based on endpoint
- var parsed packet.Parsed
- parsed.Decode(pkt)
- // When injecting this packet, we want the outcome to be "drop
- // silently", which indicates that netstack is processing the
- // packet and not delivering it to the host system.
- if resp, _ := impl.injectInbound(&parsed, impl.tundev, nil); resp != filter.DropSilently {
- t.Fatalf("got filter outcome %v, want filter.DropSilently", resp)
- }
- }
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
- waitPacket := func() {
- select {
- case <-ctx.Done():
- t.Fatalf("timed out waiting for connection")
- case <-gotConn:
- t.Logf("got connection in progress")
- }
- }
- // Inject the packet to start the TCP forward and wait until we have an
- // in-flight outgoing connection.
- mustInjectPacket()
- waitPacket()
- // Verify that we now have a single in-flight address in our map.
- impl.mu.Lock()
- inFlight := maps.Clone(impl.connsInFlightByClient)
- impl.mu.Unlock()
- if got, ok := inFlight[client]; !ok || got != 1 {
- t.Errorf("expected 1 in-flight connection for %v, got: %v", client, inFlight)
- }
- metrics := impl.ExpVar().(*metrics.Set)
- // One client should have reached the limit at this point.
- if v := metrics.Get("gauge_tcp_forward_in_flight_per_client_limit_reached").String(); v != "1" {
- t.Errorf("got limit reached expvar metric=%s, want 1", v)
- }
- // Inject another packet, and verify that we've incremented our
- // "dropped" metrics since this will have been dropped.
- mustInjectPacket()
- // expvar metric
- const metricName = "counter_tcp_forward_max_in_flight_per_client_drop"
- if v := metrics.Get(metricName).String(); v != "1" {
- t.Errorf("got expvar metric %q=%s, want 1", metricName, v)
- }
- // client metric
- if v := metricPerClientForwardLimit.Value(); v != 1 {
- t.Errorf("got clientmetric limit metric=%d, want 1", v)
- }
- }
- // TestHandleLocalPackets tests the handleLocalPackets function, ensuring that
- // we are properly deciding to handle packets that are destined for "local"
- // IPs–addresses that are either for this node, or that it is responsible for.
- //
- // See, e.g. #11304
- func TestHandleLocalPackets(t *testing.T) {
- var (
- selfIP4 = netip.MustParseAddr("100.64.1.2")
- selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123")
- )
- impl := makeNetstack(t, func(impl *Impl) {
- impl.ProcessSubnets = false
- impl.ProcessLocalIPs = false
- impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
- return addr == selfIP4 || addr == selfIP6
- })
- })
- prefs := ipn.NewPrefs()
- prefs.AdvertiseRoutes = []netip.Prefix{
- // $ tailscale debug via 7 10.1.1.0/24
- // fd7a:115c:a1e0:b1a:0:7:a01:100/120
- netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:7:a01:100/120"),
- }
- _, err := impl.lb.EditPrefs(&ipn.MaskedPrefs{
- Prefs: *prefs,
- AdvertiseRoutesSet: true,
- })
- if err != nil {
- t.Fatalf("EditPrefs: %v", err)
- }
- t.Run("ShouldHandleServiceIP", func(t *testing.T) {
- pkt := &packet.Parsed{
- IPVersion: 4,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("127.0.0.1:9999"),
- Dst: netip.MustParseAddrPort("100.100.100.100:53"),
- TCPFlags: packet.TCPSyn,
- }
- resp, _ := impl.handleLocalPackets(pkt, impl.tundev, nil)
- if resp != filter.DropSilently {
- t.Errorf("got filter outcome %v, want filter.DropSilently", resp)
- }
- })
- t.Run("ShouldHandle4via6", func(t *testing.T) {
- pkt := &packet.Parsed{
- IPVersion: 6,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("[::1]:1234"),
- // This is an IP in the above 4via6 subnet that this node handles.
- // $ tailscale debug via 7 10.1.1.9/24
- // fd7a:115c:a1e0:b1a:0:7:a01:109/120
- Dst: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:7:a01:109]:5678"),
- TCPFlags: packet.TCPSyn,
- }
- resp, _ := impl.handleLocalPackets(pkt, impl.tundev, nil)
- // DropSilently is the outcome we expected, since we actually
- // handled this packet by injecting it into netstack, which
- // will handle creating the TCP forwarder. We drop it so we
- // don't process the packet outside of netstack.
- if resp != filter.DropSilently {
- t.Errorf("got filter outcome %v, want filter.DropSilently", resp)
- }
- })
- t.Run("OtherNonHandled", func(t *testing.T) {
- pkt := &packet.Parsed{
- IPVersion: 6,
- IPProto: ipproto.TCP,
- Src: netip.MustParseAddrPort("[::1]:1234"),
- // This IP is *not* in the above 4via6 route
- // $ tailscale debug via 99 10.1.1.9/24
- // fd7a:115c:a1e0:b1a:0:63:a01:109/120
- Dst: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:63:a01:109]:5678"),
- TCPFlags: packet.TCPSyn,
- }
- resp, _ := impl.handleLocalPackets(pkt, impl.tundev, nil)
- // Accept means that handleLocalPackets does not handle this
- // packet, we "accept" it to continue further processing,
- // instead of dropping because it was already handled.
- if resp != filter.Accept {
- t.Errorf("got filter outcome %v, want filter.Accept", resp)
- }
- })
- }
- func TestShouldSendToHost(t *testing.T) {
- var (
- selfIP4 = netip.MustParseAddr("100.64.1.2")
- selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123")
- )
- makeTestNetstack := func(tb testing.TB) *Impl {
- impl := makeNetstack(tb, func(impl *Impl) {
- impl.ProcessSubnets = false
- impl.ProcessLocalIPs = false
- impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
- return addr == selfIP4 || addr == selfIP6
- })
- })
- prefs := ipn.NewPrefs()
- prefs.AdvertiseRoutes = []netip.Prefix{
- // $ tailscale debug via 7 10.1.1.0/24
- // fd7a:115c:a1e0:b1a:0:7:a01:100/120
- netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:7:a01:100/120"),
- }
- _, err := impl.lb.EditPrefs(&ipn.MaskedPrefs{
- Prefs: *prefs,
- AdvertiseRoutesSet: true,
- })
- if err != nil {
- tb.Fatalf("EditPrefs: %v", err)
- }
- return impl
- }
- testCases := []struct {
- name string
- src, dst netip.AddrPort
- want bool
- }{
- // Reply from service IP to localhost should be sent to host,
- // not over WireGuard.
- {
- name: "from_service_ip_to_localhost",
- src: netip.AddrPortFrom(serviceIP, 53),
- dst: netip.MustParseAddrPort("127.0.0.1:9999"),
- want: true,
- },
- {
- name: "from_service_ip_to_localhost_v6",
- src: netip.AddrPortFrom(serviceIPv6, 53),
- dst: netip.MustParseAddrPort("[::1]:9999"),
- want: true,
- },
- // A reply from the local IP to a remote host isn't sent to the
- // host, but rather over WireGuard.
- {
- name: "local_ip_to_remote",
- src: netip.AddrPortFrom(selfIP4, 12345),
- dst: netip.MustParseAddrPort("100.64.99.88:7777"),
- want: false,
- },
- {
- name: "local_ip_to_remote_v6",
- src: netip.AddrPortFrom(selfIP6, 12345),
- dst: netip.MustParseAddrPort("[fd7a:115:a1e0::99]:7777"),
- want: false,
- },
- // A reply from a 4via6 address to a remote host isn't sent to
- // the local host, but rather over WireGuard. See:
- // https://github.com/tailscale/tailscale/issues/12448
- {
- name: "4via6_to_remote",
- // $ tailscale debug via 7 10.1.1.99/24
- // fd7a:115c:a1e0:b1a:0:7:a01:163/120
- src: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:7:a01:163]:12345"),
- dst: netip.MustParseAddrPort("[fd7a:115:a1e0::99]:7777"),
- want: false,
- },
- // However, a reply from a 4via6 address to the local Tailscale
- // IP for this host *is* sent to the local host. See:
- // https://github.com/tailscale/tailscale/issues/11304
- {
- name: "4via6_to_local",
- // $ tailscale debug via 7 10.1.1.99/24
- // fd7a:115c:a1e0:b1a:0:7:a01:163/120
- src: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:7:a01:163]:12345"),
- dst: netip.AddrPortFrom(selfIP6, 7777),
- want: true,
- },
- // Traffic from a 4via6 address that we're not handling to
- // either the local Tailscale IP or a remote host is sent
- // outbound.
- //
- // In most cases, we won't see this type of traffic in the
- // shouldSendToHost function, but let's confirm.
- {
- name: "other_4via6_to_local",
- // $ tailscale debug via 4444 10.1.1.88/24
- // fd7a:115c:a1e0:b1a:0:7:a01:163/120
- src: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:115c:a01:158]:12345"),
- dst: netip.AddrPortFrom(selfIP6, 7777),
- want: false,
- },
- {
- name: "other_4via6_to_remote",
- // $ tailscale debug via 4444 10.1.1.88/24
- // fd7a:115c:a1e0:b1a:0:7:a01:163/120
- src: netip.MustParseAddrPort("[fd7a:115c:a1e0:b1a:0:115c:a01:158]:12345"),
- dst: netip.MustParseAddrPort("[fd7a:115:a1e0::99]:7777"),
- want: false,
- },
- }
- for _, tt := range testCases {
- t.Run(tt.name, func(t *testing.T) {
- var pkt *stack.PacketBuffer
- if tt.src.Addr().Is4() {
- pkt = makeUDP4PacketBuffer(tt.src, tt.dst)
- } else {
- pkt = makeUDP6PacketBuffer(tt.src, tt.dst)
- }
- ns := makeTestNetstack(t)
- if got := ns.shouldSendToHost(pkt); got != tt.want {
- t.Errorf("shouldSendToHost returned %v, want %v", got, tt.want)
- }
- })
- }
- }
- func makeUDP4PacketBuffer(src, dst netip.AddrPort) *stack.PacketBuffer {
- if !src.Addr().Is4() || !dst.Addr().Is4() {
- panic("src and dst must be IPv4")
- }
- data := []byte("hello world\n")
- packetLen := header.IPv4MinimumSize + header.UDPMinimumSize
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: packetLen,
- Payload: buffer.MakeWithData(data),
- })
- // Initialize the UDP header.
- udp := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
- pkt.TransportProtocolNumber = header.UDPProtocolNumber
- length := uint16(pkt.Size())
- udp.Encode(&header.UDPFields{
- SrcPort: src.Port(),
- DstPort: dst.Port(),
- Length: length,
- })
- // Add IP header
- ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
- pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber
- ipHdr.Encode(&header.IPv4Fields{
- TotalLength: uint16(packetLen),
- Protocol: uint8(header.UDPProtocolNumber),
- SrcAddr: tcpip.AddrFrom4(src.Addr().As4()),
- DstAddr: tcpip.AddrFrom4(dst.Addr().As4()),
- Checksum: 0,
- })
- return pkt
- }
- func makeUDP6PacketBuffer(src, dst netip.AddrPort) *stack.PacketBuffer {
- if !src.Addr().Is6() || !dst.Addr().Is6() {
- panic("src and dst must be IPv6")
- }
- data := []byte("hello world\n")
- packetLen := header.IPv6MinimumSize + header.UDPMinimumSize
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: packetLen,
- Payload: buffer.MakeWithData(data),
- })
- srcAddr := tcpip.AddrFrom16(src.Addr().As16())
- dstAddr := tcpip.AddrFrom16(dst.Addr().As16())
- // Add IP header
- ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
- pkt.NetworkProtocolNumber = header.IPv6ProtocolNumber
- ipHdr.Encode(&header.IPv6Fields{
- SrcAddr: srcAddr,
- DstAddr: dstAddr,
- PayloadLength: uint16(header.UDPMinimumSize + len(data)),
- TransportProtocol: header.UDPProtocolNumber,
- HopLimit: 64,
- })
- // Initialize the UDP header.
- udp := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
- pkt.TransportProtocolNumber = header.UDPProtocolNumber
- length := uint16(pkt.Size())
- udp.Encode(&header.UDPFields{
- SrcPort: src.Port(),
- DstPort: dst.Port(),
- Length: length,
- })
- // Calculate the UDP pseudo-header checksum.
- xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcAddr, dstAddr, uint16(len(udp)))
- udp.SetChecksum(^udp.CalculateChecksum(xsum))
- return pkt
- }
|