Просмотр исходного кода

refactor: move from io/ioutil to io and os packages

The io/ioutil package has been deprecated as of Go 1.16 [1]. This commit
replaces the existing io/ioutil functions with their new definitions in
io and os packages.

Reference: https://golang.org/doc/go1.16#ioutil
Signed-off-by: Eng Zer Jun <[email protected]>
Eng Zer Jun 3 лет назад
Родитель
Сommit
f0347e841f
60 измененных файлов с 112 добавлено и 156 удалено
  1. 1 2
      atomicfile/atomicfile.go
  2. 3 4
      client/tailscale/localclient.go
  3. 1 2
      client/tailscale/tailscale.go
  4. 2 3
      cmd/derper/derper.go
  5. 1 2
      cmd/hello/hello.go
  6. 1 2
      cmd/tailscale/cli/netcheck.go
  7. 1 2
      cmd/tailscale/cli/web.go
  8. 1 2
      cmd/tailscaled/debug.go
  9. 1 2
      cmd/tailscaled/install_darwin.go
  10. 1 2
      cmd/tsconnect/build.go
  11. 1 2
      cmd/tsconnect/common.go
  12. 1 2
      cmd/tsconnect/serve.go
  13. 7 8
      control/controlclient/direct.go
  14. 1 2
      derp/derp.go
  15. 4 5
      derp/derp_server.go
  16. 4 4
      derp/derp_test.go
  17. 1 2
      derp/derphttp/derphttp_client.go
  18. 4 5
      hostinfo/hostinfo_linux.go
  19. 2 3
      ipn/ipnlocal/peerapi_test.go
  20. 2 3
      ipn/ipnlocal/ssh.go
  21. 2 3
      ipn/ipnserver/server.go
  22. 4 5
      ipn/localapi/cert.go
  23. 1 2
      ipn/prefs.go
  24. 1 2
      ipn/prefs_test.go
  25. 1 2
      ipn/store/stores.go
  26. 8 3
      log/filelogger/log.go
  27. 2 3
      logpolicy/logpolicy.go
  28. 2 2
      logtail/example/logadopt/logadopt.go
  29. 2 2
      logtail/example/logreprocess/logreprocess.go
  30. 2 2
      logtail/filch/filch_test.go
  31. 2 3
      logtail/logtail.go
  32. 1 2
      logtail/logtail_test.go
  33. 1 2
      net/dns/debian_resolvconf.go
  34. 2 3
      net/dns/direct.go
  35. 1 2
      net/dns/manager_freebsd.go
  36. 1 2
      net/dns/resolver/forwarder.go
  37. 1 2
      net/dnsfallback/update-dns-fallbacks.go
  38. 2 3
      net/interfaces/interfaces_linux_test.go
  39. 1 2
      net/netcheck/netcheck.go
  40. 2 3
      net/tshttpproxy/tshttpproxy_synology_test.go
  41. 3 4
      packages/deb/deb.go
  42. 1 2
      portlist/portlist_linux.go
  43. 1 2
      safesocket/safesocket_darwin.go
  44. 2 2
      smallzstd/zstd_test.go
  45. 1 2
      ssh/tailssh/tailssh.go
  46. 2 2
      tempfork/gliderlabs/ssh/agent.go
  47. 2 2
      tempfork/gliderlabs/ssh/example_test.go
  48. 2 2
      tempfork/gliderlabs/ssh/options.go
  49. 1 2
      tempfork/gliderlabs/ssh/tcpip_test.go
  50. 2 2
      tempfork/pprof/pprof_test.go
  51. 1 2
      tka/tailchonk.go
  52. 1 2
      tsnet/tsnet.go
  53. 1 2
      tstest/integration/gen_deps.go
  54. 2 3
      tstest/integration/integration.go
  55. 2 3
      tstest/integration/integration_test.go
  56. 2 3
      tstest/integration/testcontrol/testcontrol.go
  57. 1 2
      tsweb/tsweb.go
  58. 1 2
      types/logger/logger.go
  59. 2 2
      wgengine/magicsock/magicsock_test.go
  60. 3 4
      wgengine/router/router_linux.go

+ 1 - 2
atomicfile/atomicfile.go

@@ -9,7 +9,6 @@
 package atomicfile // import "tailscale.com/atomicfile"
 
 import (
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -18,7 +17,7 @@ import (
 // WriteFile writes data to filename+some suffix, then renames it
 // into filename. The perm argument is ignored on Windows.
 func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
-	f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".tmp")
+	f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename)+".tmp")
 	if err != nil {
 		return err
 	}

+ 3 - 4
client/tailscale/localclient.go

@@ -15,7 +15,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptrace"
@@ -137,7 +136,7 @@ func (lc *LocalClient) doLocalRequestNiceError(req *http.Request) (*http.Respons
 			onVersionMismatch(ipn.IPCVersion(), server)
 		}
 		if res.StatusCode == 403 {
-			all, _ := ioutil.ReadAll(res.Body)
+			all, _ := io.ReadAll(res.Body)
 			return nil, &AccessDeniedError{errors.New(errorMessageFromBody(all))}
 		}
 		return res, nil
@@ -207,7 +206,7 @@ func (lc *LocalClient) send(ctx context.Context, method, path string, wantStatus
 		return nil, err
 	}
 	defer res.Body.Close()
-	slurp, err := ioutil.ReadAll(res.Body)
+	slurp, err := io.ReadAll(res.Body)
 	if err != nil {
 		return nil, err
 	}
@@ -365,7 +364,7 @@ func (lc *LocalClient) GetWaitingFile(ctx context.Context, baseName string) (rc
 		return nil, 0, fmt.Errorf("unexpected chunking")
 	}
 	if res.StatusCode != 200 {
-		body, _ := ioutil.ReadAll(res.Body)
+		body, _ := io.ReadAll(res.Body)
 		res.Body.Close()
 		return nil, 0, fmt.Errorf("HTTP %s: %s", res.Status, body)
 	}

+ 1 - 2
client/tailscale/tailscale.go

@@ -17,7 +17,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 )
 
@@ -131,7 +130,7 @@ func (c *Client) sendRequest(req *http.Request) ([]byte, *http.Response, error)
 
 	// Read response. Limit the response to 10MB.
 	body := io.LimitReader(resp.Body, maxReadSize+1)
-	b, err := ioutil.ReadAll(body)
+	b, err := io.ReadAll(body)
 	if len(b) > maxReadSize {
 		err = errors.New("API response too large")
 	}

+ 2 - 3
cmd/derper/derper.go

@@ -14,7 +14,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"math"
 	"net"
@@ -99,7 +98,7 @@ func loadConfig() config {
 		}
 		log.Printf("no config path specified; using %s", *configPath)
 	}
-	b, err := ioutil.ReadFile(*configPath)
+	b, err := os.ReadFile(*configPath)
 	switch {
 	case errors.Is(err, os.ErrNotExist):
 		return writeNewConfig()
@@ -155,7 +154,7 @@ func main() {
 	s.SetVerifyClient(*verifyClients)
 
 	if *meshPSKFile != "" {
-		b, err := ioutil.ReadFile(*meshPSKFile)
+		b, err := os.ReadFile(*meshPSKFile)
 		if err != nil {
 			log.Fatal(err)
 		}

+ 1 - 2
cmd/hello/hello.go

@@ -13,7 +13,6 @@ import (
 	"errors"
 	"flag"
 	"html/template"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"os"
@@ -106,7 +105,7 @@ func devMode() bool { return *httpsAddr == "" && *httpAddr != "" }
 
 func getTmpl() (*template.Template, error) {
 	if devMode() {
-		tmplData, err := ioutil.ReadFile("hello.tmpl.html")
+		tmplData, err := os.ReadFile("hello.tmpl.html")
 		if os.IsNotExist(err) {
 			log.Printf("using baked-in template in dev mode; can't find hello.tmpl.html in current directory")
 			return tmpl, nil

+ 1 - 2
cmd/tailscale/cli/netcheck.go

@@ -10,7 +10,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"sort"
@@ -202,7 +201,7 @@ func prodDERPMap(ctx context.Context, httpc *http.Client) (*tailcfg.DERPMap, err
 		return nil, fmt.Errorf("fetch prodDERPMap failed: %w", err)
 	}
 	defer res.Body.Close()
-	b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
+	b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
 	if err != nil {
 		return nil, fmt.Errorf("fetch prodDERPMap failed: %w", err)
 	}

+ 1 - 2
cmd/tailscale/cli/web.go

@@ -15,7 +15,6 @@ import (
 	"fmt"
 	"html/template"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -254,7 +253,7 @@ func qnapAuthnFinish(user, url string) (string, *qnapAuthResponse, error) {
 		return "", nil, err
 	}
 	defer resp.Body.Close()
-	out, err := ioutil.ReadAll(resp.Body)
+	out, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return "", nil, err
 	}

+ 1 - 2
cmd/tailscaled/debug.go

@@ -15,7 +15,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -173,7 +172,7 @@ func checkDerp(ctx context.Context, derpRegion string) error {
 		return fmt.Errorf("fetch derp map failed: %w", err)
 	}
 	defer res.Body.Close()
-	b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
+	b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
 	if err != nil {
 		return fmt.Errorf("fetch derp map failed: %w", err)
 	}

+ 1 - 2
cmd/tailscaled/install_darwin.go

@@ -11,7 +11,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -142,7 +141,7 @@ func installSystemDaemonDarwin(args []string) (err error) {
 		return err
 	}
 
-	if err := ioutil.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil {
+	if err := os.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil {
 		return err
 	}
 

+ 1 - 2
cmd/tsconnect/build.go

@@ -7,7 +7,6 @@ package main
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"path"
@@ -47,7 +46,7 @@ func runBuild() {
 	if err != nil {
 		log.Fatalf("Cannot fix esbuild metadata paths: %v", err)
 	}
-	if err := ioutil.WriteFile(path.Join(*distDir, "/esbuild-metadata.json"), metadataBytes, 0666); err != nil {
+	if err := os.WriteFile(path.Join(*distDir, "/esbuild-metadata.json"), metadataBytes, 0666); err != nil {
 		log.Fatalf("Cannot write metadata: %v", err)
 	}
 

+ 1 - 2
cmd/tsconnect/common.go

@@ -6,7 +6,6 @@ package main
 
 import (
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net"
 	"os"
@@ -183,7 +182,7 @@ func setupEsbuildWasm(build esbuild.PluginBuild, dev bool) {
 
 func buildWasm(dev bool) ([]byte, error) {
 	start := time.Now()
-	outputFile, err := ioutil.TempFile("", "main.*.wasm")
+	outputFile, err := os.CreateTemp("", "main.*.wasm")
 	if err != nil {
 		return nil, fmt.Errorf("Cannot create main.wasm output file: %w", err)
 	}

+ 1 - 2
cmd/tsconnect/serve.go

@@ -11,7 +11,6 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"os"
@@ -75,7 +74,7 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
 		return nil, fmt.Errorf("Could not open esbuild-metadata.json: %w", err)
 	}
 	defer esbuildMetadataFile.Close()
-	esbuildMetadataBytes, err := ioutil.ReadAll(esbuildMetadataFile)
+	esbuildMetadataBytes, err := io.ReadAll(esbuildMetadataFile)
 	if err != nil {
 		return nil, fmt.Errorf("Could not read esbuild-metadata.json: %w", err)
 	}

+ 7 - 8
control/controlclient/direct.go

@@ -14,7 +14,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"net/http/httptest"
@@ -523,7 +522,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
 		return regen, opt.URL, fmt.Errorf("register request: %w", err)
 	}
 	if res.StatusCode != 200 {
-		msg, _ := ioutil.ReadAll(res.Body)
+		msg, _ := io.ReadAll(res.Body)
 		res.Body.Close()
 		return regen, opt.URL, fmt.Errorf("register request: http %d: %.200s",
 			res.StatusCode, strings.TrimSpace(string(msg)))
@@ -804,7 +803,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
 	}
 	vlogf("netmap: Do = %v after %v", res.StatusCode, time.Since(t0).Round(time.Millisecond))
 	if res.StatusCode != 200 {
-		msg, _ := ioutil.ReadAll(res.Body)
+		msg, _ := io.ReadAll(res.Body)
 		res.Body.Close()
 		return fmt.Errorf("initial fetch failed %d: %.200s",
 			res.StatusCode, strings.TrimSpace(string(msg)))
@@ -814,7 +813,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
 	health.NoteMapRequestHeard(request)
 
 	if cb == nil {
-		io.Copy(ioutil.Discard, res.Body)
+		io.Copy(io.Discard, res.Body)
 		return nil
 	}
 
@@ -998,7 +997,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
 // it uses the serverKey and mkey to decode the message from the NaCl-crypto-box.
 func decode(res *http.Response, v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.MachinePrivate) error {
 	defer res.Body.Close()
-	msg, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
+	msg, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
 	if err != nil {
 		return err
 	}
@@ -1110,7 +1109,7 @@ func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string
 		return nil, fmt.Errorf("fetch control key: %v", err)
 	}
 	defer res.Body.Close()
-	b, err := ioutil.ReadAll(io.LimitReader(res.Body, 64<<10))
+	b, err := io.ReadAll(io.LimitReader(res.Body, 64<<10))
 	if err != nil {
 		return nil, fmt.Errorf("fetch control key response: %v", err)
 	}
@@ -1398,7 +1397,7 @@ func (c *Direct) setDNSNoise(ctx context.Context, req *tailcfg.SetDNSRequest) er
 	}
 	defer res.Body.Close()
 	if res.StatusCode != 200 {
-		msg, _ := ioutil.ReadAll(res.Body)
+		msg, _ := io.ReadAll(res.Body)
 		return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
 	}
 	var setDNSRes tailcfg.SetDNSResponse
@@ -1464,7 +1463,7 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err er
 	}
 	defer res.Body.Close()
 	if res.StatusCode != 200 {
-		msg, _ := ioutil.ReadAll(res.Body)
+		msg, _ := io.ReadAll(res.Body)
 		return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
 	}
 	var setDNSRes tailcfg.SetDNSResponse

+ 1 - 2
derp/derp.go

@@ -19,7 +19,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"time"
 )
 
@@ -195,7 +194,7 @@ func readFrame(br *bufio.Reader, maxSize uint32, b []byte) (t frameType, frameLe
 	}
 	remain := frameLen - uint32(n)
 	if remain > 0 {
-		if _, err := io.CopyN(ioutil.Discard, br, int64(remain)); err != nil {
+		if _, err := io.CopyN(io.Discard, br, int64(remain)); err != nil {
 			return 0, 0, err
 		}
 		err = io.ErrShortBuffer

+ 4 - 5
derp/derp_server.go

@@ -18,7 +18,6 @@ import (
 	"expvar"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"math"
 	"math/big"
@@ -758,7 +757,7 @@ func (c *sclient) run(ctx context.Context) error {
 }
 
 func (c *sclient) handleUnknownFrame(ft frameType, fl uint32) error {
-	_, err := io.CopyN(ioutil.Discard, c.br, int64(fl))
+	_, err := io.CopyN(io.Discard, c.br, int64(fl))
 	return err
 }
 
@@ -801,7 +800,7 @@ func (c *sclient) handleFramePing(ft frameType, fl uint32) error {
 		return err
 	}
 	if extra := int64(fl) - int64(len(m)); extra > 0 {
-		_, err = io.CopyN(ioutil.Discard, c.br, extra)
+		_, err = io.CopyN(io.Discard, c.br, extra)
 	}
 	select {
 	case c.sendPongCh <- [8]byte(m):
@@ -1828,7 +1827,7 @@ func (s *Server) ServeDebugTraffic(w http.ResponseWriter, r *http.Request) {
 
 var bufioWriterPool = &sync.Pool{
 	New: func() any {
-		return bufio.NewWriterSize(ioutil.Discard, 2<<10)
+		return bufio.NewWriterSize(io.Discard, 2<<10)
 	},
 }
 
@@ -1861,7 +1860,7 @@ func (w *lazyBufioWriter) Flush() error {
 	}
 	err := w.lbw.Flush()
 
-	w.lbw.Reset(ioutil.Discard)
+	w.lbw.Reset(io.Discard)
 	bufioWriterPool.Put(w.lbw)
 	w.lbw = nil
 

+ 4 - 4
derp/derp_test.go

@@ -15,9 +15,9 @@ import (
 	"expvar"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
+	"os"
 	"reflect"
 	"sync"
 	"testing"
@@ -1240,7 +1240,7 @@ func benchmarkSendRecvSize(b *testing.B, packetSize int) {
 }
 
 func BenchmarkWriteUint32(b *testing.B) {
-	w := bufio.NewWriter(ioutil.Discard)
+	w := bufio.NewWriter(io.Discard)
 	b.ReportAllocs()
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
@@ -1279,9 +1279,9 @@ func waitConnect(t testing.TB, c *Client) {
 }
 
 func TestParseSSOutput(t *testing.T) {
-	contents, err := ioutil.ReadFile("testdata/example_ss.txt")
+	contents, err := os.ReadFile("testdata/example_ss.txt")
 	if err != nil {
-		t.Errorf("ioutil.Readfile(example_ss.txt) failed: %v", err)
+		t.Errorf("os.ReadFile(example_ss.txt) failed: %v", err)
 	}
 	seen := parseSSOutput(string(contents))
 	if len(seen) == 0 {

+ 1 - 2
derp/derphttp/derphttp_client.go

@@ -19,7 +19,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/netip"
@@ -432,7 +431,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
 			return nil, 0, err
 		}
 		if resp.StatusCode != http.StatusSwitchingProtocols {
-			b, _ := ioutil.ReadAll(resp.Body)
+			b, _ := io.ReadAll(resp.Body)
 			resp.Body.Close()
 			return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
 		}

+ 4 - 5
hostinfo/hostinfo_linux.go

@@ -9,7 +9,6 @@ package hostinfo
 
 import (
 	"bytes"
-	"io/ioutil"
 	"os"
 	"strings"
 
@@ -99,11 +98,11 @@ func linuxVersionMeta() (meta versionMeta) {
 	case distro.OpenWrt:
 		propFile = "/etc/openwrt_release"
 	case distro.WDMyCloud:
-		slurp, _ := ioutil.ReadFile("/etc/version")
+		slurp, _ := os.ReadFile("/etc/version")
 		meta.DistroVersion = string(bytes.TrimSpace(slurp))
 		return
 	case distro.QNAP:
-		slurp, _ := ioutil.ReadFile("/etc/version_info")
+		slurp, _ := os.ReadFile("/etc/version_info")
 		meta.DistroVersion = getQnapQtsVersion(string(slurp))
 		return
 	}
@@ -133,7 +132,7 @@ func linuxVersionMeta() (meta versionMeta) {
 	case "debian":
 		// Debian's VERSION_ID is just like "11". But /etc/debian_version has "11.5" normally.
 		// Or "bookworm/sid" on sid/testing.
-		slurp, _ := ioutil.ReadFile("/etc/debian_version")
+		slurp, _ := os.ReadFile("/etc/debian_version")
 		if v := string(bytes.TrimSpace(slurp)); v != "" {
 			if '0' <= v[0] && v[0] <= '9' {
 				meta.DistroVersion = v
@@ -143,7 +142,7 @@ func linuxVersionMeta() (meta versionMeta) {
 		}
 	case "", "centos": // CentOS 6 has no /etc/os-release, so its id is ""
 		if meta.DistroVersion == "" {
-			if cr, _ := ioutil.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final)
+			if cr, _ := os.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final)
 				meta.DistroVersion = string(bytes.TrimSpace(cr))
 			}
 		}

+ 2 - 3
ipn/ipnlocal/peerapi_test.go

@@ -9,7 +9,6 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"math/rand"
 	"net/http"
 	"net/http/httptest"
@@ -87,7 +86,7 @@ func fileHasContents(name string, want string) check {
 			return
 		}
 		path := filepath.Join(root, name)
-		got, err := ioutil.ReadFile(path)
+		got, err := os.ReadFile(path)
 		if err != nil {
 			t.Errorf("fileHasContents: %v", err)
 			return
@@ -517,7 +516,7 @@ func TestDeletedMarkers(t *testing.T) {
 	}
 	wantEmptyTempDir := func() {
 		t.Helper()
-		if fis, err := ioutil.ReadDir(dir); err != nil {
+		if fis, err := os.ReadDir(dir); err != nil {
 			t.Fatal(err)
 		} else if len(fis) > 0 && runtime.GOOS != "windows" {
 			for _, fi := range fis {

+ 2 - 3
ipn/ipnlocal/ssh.go

@@ -18,7 +18,6 @@ import (
 	"encoding/pem"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -80,7 +79,7 @@ func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
 	defer keyGenMu.Unlock()
 
 	path := filepath.Join(keyDir, "ssh_host_"+typ+"_key")
-	v, err := ioutil.ReadFile(path)
+	v, err := os.ReadFile(path)
 	if err == nil {
 		return v, nil
 	}
@@ -121,7 +120,7 @@ func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
 func (b *LocalBackend) getSystemSSH_HostKeys() (ret map[string]ssh.Signer) {
 	for _, typ := range keyTypes {
 		filename := "/etc/ssh/ssh_host_" + typ + "_key"
-		hostKey, err := ioutil.ReadFile(filename)
+		hostKey, err := os.ReadFile(filename)
 		if err != nil || len(bytes.TrimSpace(hostKey)) == 0 {
 			continue
 		}

+ 2 - 3
ipn/ipnserver/server.go

@@ -12,7 +12,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -215,7 +214,7 @@ func (s *Server) blockWhileInUse(conn io.Reader, ci connIdentity) {
 	s.logf("blocking client while server in use; connIdentity=%v", ci)
 	connDone := make(chan struct{})
 	go func() {
-		io.Copy(ioutil.Discard, conn)
+		io.Copy(io.Discard, conn)
 		close(connDone)
 	}()
 	ch := make(chan struct{}, 1)
@@ -1175,7 +1174,7 @@ func findTrueNASTaildropDir(name string) (dir string, err error) {
 	}
 
 	// but if running on the host, it may be something like /mnt/Primary/Taildrop
-	fis, err := ioutil.ReadDir("/mnt")
+	fis, err := os.ReadDir("/mnt")
 	if err != nil {
 		return "", fmt.Errorf("error reading /mnt: %w", err)
 	}

+ 4 - 5
ipn/localapi/cert.go

@@ -23,7 +23,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -293,7 +292,7 @@ func (h *Handler) getCertPEM(ctx context.Context, logf logger.Logf, traceACME fu
 	if err := encodeECDSAKey(&privPEM, certPrivKey); err != nil {
 		return nil, err
 	}
-	if err := ioutil.WriteFile(keyFile(dir, domain), privPEM.Bytes(), 0600); err != nil {
+	if err := os.WriteFile(keyFile(dir, domain), privPEM.Bytes(), 0600); err != nil {
 		return nil, err
 	}
 
@@ -316,7 +315,7 @@ func (h *Handler) getCertPEM(ctx context.Context, logf logger.Logf, traceACME fu
 			return nil, err
 		}
 	}
-	if err := ioutil.WriteFile(certFile(dir, domain), certPEM.Bytes(), 0644); err != nil {
+	if err := os.WriteFile(certFile(dir, domain), certPEM.Bytes(), 0644); err != nil {
 		return nil, err
 	}
 
@@ -372,7 +371,7 @@ func parsePrivateKey(der []byte) (crypto.Signer, error) {
 
 func acmeKey(dir string) (crypto.Signer, error) {
 	pemName := filepath.Join(dir, "acme-account.key.pem")
-	if v, err := ioutil.ReadFile(pemName); err == nil {
+	if v, err := os.ReadFile(pemName); err == nil {
 		priv, _ := pem.Decode(v)
 		if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
 			return nil, errors.New("acme/autocert: invalid account key found in cache")
@@ -388,7 +387,7 @@ func acmeKey(dir string) (crypto.Signer, error) {
 	if err := encodeECDSAKey(&pemBuf, privKey); err != nil {
 		return nil, err
 	}
-	if err := ioutil.WriteFile(pemName, pemBuf.Bytes(), 0600); err != nil {
+	if err := os.WriteFile(pemName, pemBuf.Bytes(), 0600); err != nil {
 		return nil, err
 	}
 	return privKey, nil

+ 1 - 2
ipn/prefs.go

@@ -9,7 +9,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net/netip"
 	"os"
@@ -618,7 +617,7 @@ func PrefsFromBytes(b []byte) (*Prefs, error) {
 // LoadPrefs loads a legacy relaynode config file into Prefs
 // with sensible migration defaults set.
 func LoadPrefs(filename string) (*Prefs, error) {
-	data, err := ioutil.ReadFile(filename)
+	data, err := os.ReadFile(filename)
 	if err != nil {
 		return nil, fmt.Errorf("LoadPrefs open: %w", err) // err includes path
 	}

+ 1 - 2
ipn/prefs_test.go

@@ -8,7 +8,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net/netip"
 	"os"
 	"reflect"
@@ -474,7 +473,7 @@ func TestLoadPrefsNotExist(t *testing.T) {
 // TestLoadPrefsFileWithZeroInIt verifies that LoadPrefs hanldes corrupted input files.
 // See issue #954 for details.
 func TestLoadPrefsFileWithZeroInIt(t *testing.T) {
-	f, err := ioutil.TempFile("", "TestLoadPrefsFileWithZeroInIt")
+	f, err := os.CreateTemp("", "TestLoadPrefsFileWithZeroInIt")
 	if err != nil {
 		t.Fatal(err)
 	}

+ 1 - 2
ipn/store/stores.go

@@ -9,7 +9,6 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -128,7 +127,7 @@ func NewFileStore(logf logger.Logf, path string) (ipn.StateStore, error) {
 		return nil, fmt.Errorf("creating state directory: %w", err)
 	}
 
-	bs, err := ioutil.ReadFile(path)
+	bs, err := os.ReadFile(path)
 
 	// Treat an empty file as a missing file.
 	// (https://github.com/tailscale/tailscale/issues/895#issuecomment-723255589)

+ 8 - 3
log/filelogger/log.go

@@ -9,7 +9,6 @@ package filelogger
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -186,12 +185,18 @@ func (w *logFileWriter) startNewFileLocked() {
 //
 // w.mu must be held.
 func (w *logFileWriter) cleanLocked() {
-	fis, _ := ioutil.ReadDir(w.dir)
+	entries, _ := os.ReadDir(w.dir)
 	prefix := w.fileBasePrefix + "-"
 	fileSize := map[string]int64{}
 	var files []string
 	var sumSize int64
-	for _, fi := range fis {
+	for _, entry := range entries {
+		fi, err := entry.Info()
+		if err != nil {
+			w.wrappedLogf("error getting log file info: %v", err)
+			continue
+		}
+
 		baseName := filepath.Base(fi.Name())
 		if !strings.HasPrefix(baseName, prefix) {
 			continue

+ 2 - 3
logpolicy/logpolicy.go

@@ -16,7 +16,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -248,7 +247,7 @@ func logsDir(logf logger.Logf) string {
 	// No idea where to put stuff. Try to create a temp dir. It'll
 	// mean we might lose some logs and rotate through log IDs, but
 	// it's something.
-	tmp, err := ioutil.TempDir("", "tailscaled-log-*")
+	tmp, err := os.MkdirTemp("", "tailscaled-log-*")
 	if err != nil {
 		panic("no safe place found to store log state")
 	}
@@ -259,7 +258,7 @@ func logsDir(logf logger.Logf) string {
 // runningUnderSystemd reports whether we're running under systemd.
 func runningUnderSystemd() bool {
 	if runtime.GOOS == "linux" && os.Getppid() == 1 {
-		slurp, _ := ioutil.ReadFile("/proc/1/stat")
+		slurp, _ := os.ReadFile("/proc/1/stat")
 		return bytes.HasPrefix(slurp, []byte("1 (systemd) "))
 	}
 	return false

+ 2 - 2
logtail/example/logadopt/logadopt.go

@@ -6,7 +6,7 @@ package main
 
 import (
 	"flag"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"net/url"
@@ -39,7 +39,7 @@ func main() {
 	if err != nil {
 		log.Fatal(err)
 	}
-	b, err := ioutil.ReadAll(resp.Body)
+	b, err := io.ReadAll(resp.Body)
 	resp.Body.Close()
 	if err != nil {
 		log.Fatalf("logadopt: response read failed %d: %v", resp.StatusCode, err)

+ 2 - 2
logtail/example/logreprocess/logreprocess.go

@@ -9,7 +9,7 @@ import (
 	"bufio"
 	"encoding/json"
 	"flag"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -50,7 +50,7 @@ func main() {
 	defer resp.Body.Close()
 
 	if resp.StatusCode != 200 {
-		b, err := ioutil.ReadAll(resp.Body)
+		b, err := io.ReadAll(resp.Body)
 		if err != nil {
 			log.Fatalf("logreprocess: read error %d: %v", resp.StatusCode, err)
 		}

+ 2 - 2
logtail/filch/filch_test.go

@@ -6,7 +6,7 @@ package filch
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 	"runtime"
 	"strings"
@@ -195,7 +195,7 @@ func TestFilchStderr(t *testing.T) {
 	f.close(t)
 
 	pipeW.Close()
-	b, err := ioutil.ReadAll(pipeR)
+	b, err := io.ReadAll(pipeR)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 2 - 3
logtail/logtail.go

@@ -13,7 +13,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"strconv"
@@ -430,7 +429,7 @@ func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded
 
 	if resp.StatusCode != 200 {
 		uploaded = resp.StatusCode == 400 // the server saved the logs anyway
-		b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
+		b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
 		return uploaded, fmt.Errorf("log upload of %d bytes %s failed %d: %q", len(body), compressedNote, resp.StatusCode, b)
 	}
 
@@ -654,7 +653,7 @@ func (l *Logger) Write(buf []byte) (int, error) {
 		return 0, nil
 	}
 	level, buf := parseAndRemoveLogLevel(buf)
-	if l.stderr != nil && l.stderr != ioutil.Discard && int64(level) <= atomic.LoadInt64(&l.stderrLevel) {
+	if l.stderr != nil && l.stderr != io.Discard && int64(level) <= atomic.LoadInt64(&l.stderrLevel) {
 		if buf[len(buf)-1] == '\n' {
 			l.stderr.Write(buf)
 		} else {

+ 1 - 2
logtail/logtail_test.go

@@ -9,7 +9,6 @@ import (
 	"context"
 	"encoding/json"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -52,7 +51,7 @@ func NewLogtailTestHarness(t *testing.T) (*LogtailTestServer, *Logger) {
 
 	ts.srv = httptest.NewServer(http.HandlerFunc(
 		func(w http.ResponseWriter, r *http.Request) {
-			body, err := ioutil.ReadAll(r.Body)
+			body, err := io.ReadAll(r.Body)
 			if err != nil {
 				t.Error("failed to read HTTP request")
 			}

+ 1 - 2
net/dns/debian_resolvconf.go

@@ -12,7 +12,6 @@ import (
 	"bytes"
 	_ "embed"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -158,7 +157,7 @@ func (m *resolvconfManager) GetBaseConfig() (OSConfig, error) {
 		if sc.Text() == resolvconfConfigName {
 			continue
 		}
-		bs, err := ioutil.ReadFile(filepath.Join(m.interfacesDir, sc.Text()))
+		bs, err := os.ReadFile(filepath.Join(m.interfacesDir, sc.Text()))
 		if err != nil {
 			if os.IsNotExist(err) {
 				// Probably raced with a deletion, that's okay.

+ 2 - 3
net/dns/direct.go

@@ -12,7 +12,6 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"net/netip"
 	"os"
 	"os/exec"
@@ -452,7 +451,7 @@ func (fs directFS) Rename(oldName, newName string) error {
 func (fs directFS) Remove(name string) error { return os.Remove(fs.path(name)) }
 
 func (fs directFS) ReadFile(name string) ([]byte, error) {
-	return ioutil.ReadFile(fs.path(name))
+	return os.ReadFile(fs.path(name))
 }
 
 func (fs directFS) Truncate(name string) error {
@@ -460,7 +459,7 @@ func (fs directFS) Truncate(name string) error {
 }
 
 func (fs directFS) WriteFile(name string, contents []byte, perm os.FileMode) error {
-	return ioutil.WriteFile(fs.path(name), contents, perm)
+	return os.WriteFile(fs.path(name), contents, perm)
 }
 
 // runningAsGUIDesktopUser reports whether it seems that this code is

+ 1 - 2
net/dns/manager_freebsd.go

@@ -6,14 +6,13 @@ package dns
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 
 	"tailscale.com/types/logger"
 )
 
 func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) {
-	bs, err := ioutil.ReadFile("/etc/resolv.conf")
+	bs, err := os.ReadFile("/etc/resolv.conf")
 	if os.IsNotExist(err) {
 		return newDirectManager(logf), nil
 	}

+ 1 - 2
net/dns/resolver/forwarder.go

@@ -11,7 +11,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"net"
 	"net/http"
@@ -474,7 +473,7 @@ func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client,
 		metricDNSFwdDoHErrorCT.Add(1)
 		return nil, fmt.Errorf("unexpected response Content-Type %q", ct)
 	}
-	res, err := ioutil.ReadAll(hres.Body)
+	res, err := io.ReadAll(hres.Body)
 	if err != nil {
 		metricDNSFwdDoHErrorBody.Add(1)
 	}

+ 1 - 2
net/dnsfallback/update-dns-fallbacks.go

@@ -10,7 +10,6 @@ package main
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"os"
@@ -42,7 +41,7 @@ func main() {
 	if err != nil {
 		log.Fatal(err)
 	}
-	if err := ioutil.WriteFile("dns-fallback-servers.json", out, 0644); err != nil {
+	if err := os.WriteFile("dns-fallback-servers.json", out, 0644); err != nil {
 		log.Fatal(err)
 	}
 }

+ 2 - 3
net/interfaces/interfaces_linux_test.go

@@ -8,7 +8,6 @@ import (
 	"errors"
 	"fmt"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
@@ -23,7 +22,7 @@ func TestGoogleCloudRunDefaultRouteInterface(t *testing.T) {
 	buf := []byte("Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT\n" +
 		"eth0\t8008FEA9\t00000000\t0001\t0\t0\t0\t01FFFFFF\t0\t0\t0\n" +
 		"eth1\t00000000\t00000000\t0001\t0\t0\t0\t00000000\t0\t0\t0\n")
-	err := ioutil.WriteFile(procNetRoutePath, buf, 0644)
+	err := os.WriteFile(procNetRoutePath, buf, 0644)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -87,7 +86,7 @@ func TestAwsAppRunnerDefaultRouteInterface(t *testing.T) {
 		"ecs-eth0\t02AAFEA9\t01ACFEA9\t0007\t0\t0\t0\tFFFFFFFF\t0\t0\t0\n" +
 		"ecs-eth0\t00ACFEA9\t00000000\t0001\t0\t0\t0\t00FFFFFF\t0\t0\t0\n" +
 		"eth0\t00AFFEA9\t00000000\t0001\t0\t0\t0\t00FFFFFF\t0\t0\t0\n")
-	err := ioutil.WriteFile(procNetRoutePath, buf, 0644)
+	err := os.WriteFile(procNetRoutePath, buf, 0644)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 1 - 2
net/netcheck/netcheck.go

@@ -12,7 +12,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -1096,7 +1095,7 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
 		return 0, ip, fmt.Errorf("unexpected status code: %d (%s)", resp.StatusCode, resp.Status)
 	}
 
-	_, err = io.Copy(ioutil.Discard, io.LimitReader(resp.Body, 8<<10))
+	_, err = io.Copy(io.Discard, io.LimitReader(resp.Body, 8<<10))
 	if err != nil {
 		return 0, ip, err
 	}

+ 2 - 3
net/tshttpproxy/tshttpproxy_synology_test.go

@@ -11,7 +11,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"os"
@@ -60,7 +59,7 @@ func TestSynologyProxyFromConfigCached(t *testing.T) {
 		cache.httpProxy = nil
 		cache.httpsProxy = nil
 
-		if err := ioutil.WriteFile(synologyProxyConfigPath, []byte(`
+		if err := os.WriteFile(synologyProxyConfigPath, []byte(`
 proxy_enabled=yes
 http_host=10.0.0.55
 http_port=80
@@ -116,7 +115,7 @@ https_port=443
 		cache.httpProxy = nil
 		cache.httpsProxy = nil
 
-		if err := ioutil.WriteFile(synologyProxyConfigPath, []byte(`
+		if err := os.WriteFile(synologyProxyConfigPath, []byte(`
 proxy_enabled=yes
 http_host=10.0.0.55
 http_port=80

+ 3 - 4
packages/deb/deb.go

@@ -16,7 +16,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -74,7 +73,7 @@ func Read(r io.Reader) (*Info, error) {
 	}
 
 	// Exhaust the remainder of r, so that the summers see the entire file.
-	if _, err := io.Copy(ioutil.Discard, r); err != nil {
+	if _, err := io.Copy(io.Discard, r); err != nil {
 		return nil, fmt.Errorf("hashing file: %w", err)
 	}
 
@@ -117,7 +116,7 @@ func findControlTar(r io.Reader) (tarReader io.Reader, err error) {
 		if size%2 == 1 {
 			size++
 		}
-		if _, err := io.CopyN(ioutil.Discard, r, size); err != nil {
+		if _, err := io.CopyN(io.Discard, r, size); err != nil {
 			return nil, fmt.Errorf("seeking past file %q: %w", filename, err)
 		}
 	}
@@ -150,7 +149,7 @@ func findControlFile(r io.Reader) (control []byte, err error) {
 		break
 	}
 
-	bs, err := ioutil.ReadAll(tr)
+	bs, err := io.ReadAll(tr)
 	if err != nil {
 		return nil, fmt.Errorf("reading control file: %w", err)
 	}

+ 1 - 2
portlist/portlist_linux.go

@@ -8,7 +8,6 @@ import (
 	"bufio"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -230,7 +229,7 @@ func addProcesses(pl []Port) ([]Port, error) {
 
 				pe := pm[string(targetBuf[:n])] // m[string([]byte)] avoids alloc
 				if pe != nil {
-					bs, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid))
+					bs, err := os.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid))
 					if err != nil {
 						// Usually shouldn't happen. One possibility is
 						// the process has gone away, so let's skip it.

+ 1 - 2
safesocket/safesocket_darwin.go

@@ -9,7 +9,6 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -70,7 +69,7 @@ func localTCPPortAndTokenDarwin() (port int, token string, err error) {
 		// The current binary (this process) is sandboxed. The user is
 		// running the CLI via /Applications/Tailscale.app/Contents/MacOS/Tailscale
 		// which sets the TS_MACOS_CLI_SHARED_DIR environment variable.
-		fis, err := ioutil.ReadDir(dir)
+		fis, err := os.ReadDir(dir)
 		if err != nil {
 			return 0, "", err
 		}

+ 2 - 2
smallzstd/zstd_test.go

@@ -5,7 +5,7 @@
 package smallzstd
 
 import (
-	"io/ioutil"
+	"os"
 	"testing"
 
 	"github.com/klauspost/compress/zstd"
@@ -113,7 +113,7 @@ func benchDecoderWithConstruction(b *testing.B, mk func() (*zstd.Decoder, error)
 
 func testdata(b *testing.B) []byte {
 	b.Helper()
-	in, err := ioutil.ReadFile("testdata")
+	in, err := os.ReadFile("testdata")
 	if err != nil {
 		b.Fatalf("reading testdata: %v", err)
 	}

+ 1 - 2
ssh/tailssh/tailssh.go

@@ -17,7 +17,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/netip"
@@ -1306,7 +1305,7 @@ func (ss *sshSession) startNewRecording() (*recording, error) {
 	if err := os.MkdirAll(dir, 0700); err != nil {
 		return nil, err
 	}
-	f, err := ioutil.TempFile(dir, fmt.Sprintf("ssh-session-%v-*.cast", now.UnixNano()))
+	f, err := os.CreateTemp(dir, fmt.Sprintf("ssh-session-%v-*.cast", now.UnixNano()))
 	if err != nil {
 		return nil, err
 	}

+ 2 - 2
tempfork/gliderlabs/ssh/agent.go

@@ -2,8 +2,8 @@ package ssh
 
 import (
 	"io"
-	"io/ioutil"
 	"net"
+	"os"
 	"path"
 	"sync"
 
@@ -36,7 +36,7 @@ func AgentRequested(sess Session) bool {
 // NewAgentListener sets up a temporary Unix socket that can be communicated
 // to the session environment and used for forwarding connections.
 func NewAgentListener() (net.Listener, error) {
-	dir, err := ioutil.TempDir("", agentTempDir)
+	dir, err := os.MkdirTemp("", agentTempDir)
 	if err != nil {
 		return nil, err
 	}

+ 2 - 2
tempfork/gliderlabs/ssh/example_test.go

@@ -3,7 +3,7 @@ package ssh_test
 import (
 	"errors"
 	"io"
-	"io/ioutil"
+	"os"
 
 	"tailscale.com/tempfork/gliderlabs/ssh"
 )
@@ -29,7 +29,7 @@ func ExampleNoPty() {
 func ExamplePublicKeyAuth() {
 	ssh.ListenAndServe(":2222", nil,
 		ssh.PublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) error {
-			data, err := ioutil.ReadFile("/path/to/allowed/key.pub")
+			data, err := os.ReadFile("/path/to/allowed/key.pub")
 			if err != nil {
 				return err
 			}

+ 2 - 2
tempfork/gliderlabs/ssh/options.go

@@ -1,7 +1,7 @@
 package ssh
 
 import (
-	"io/ioutil"
+	"os"
 
 	gossh "github.com/tailscale/golang-x-crypto/ssh"
 )
@@ -26,7 +26,7 @@ func PublicKeyAuth(fn PublicKeyHandler) Option {
 // from a PEM file at filepath.
 func HostKeyFile(filepath string) Option {
 	return func(srv *Server) error {
-		pemBytes, err := ioutil.ReadFile(filepath)
+		pemBytes, err := os.ReadFile(filepath)
 		if err != nil {
 			return err
 		}

+ 1 - 2
tempfork/gliderlabs/ssh/tcpip_test.go

@@ -5,7 +5,6 @@ package ssh
 
 import (
 	"bytes"
-	"io/ioutil"
 	"net"
 	"strconv"
 	"strings"
@@ -61,7 +60,7 @@ func TestLocalPortForwardingWorks(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Error connecting to %v: %v", l.Addr().String(), err)
 	}
-	result, err := ioutil.ReadAll(conn)
+	result, err := io.ReadAll(conn)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 2 - 2
tempfork/pprof/pprof_test.go

@@ -6,7 +6,7 @@ package pprof
 
 import (
 	"bytes"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"runtime/pprof"
@@ -52,7 +52,7 @@ func TestHandlers(t *testing.T) {
 				t.Errorf("status code: got %d; want %d", got, want)
 			}
 
-			body, err := ioutil.ReadAll(resp.Body)
+			body, err := io.ReadAll(resp.Body)
 			if err != nil {
 				t.Errorf("when reading response body, expected non-nil err; got %v", err)
 			}

+ 1 - 2
tka/tailchonk.go

@@ -7,7 +7,6 @@ package tka
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"sync"
@@ -345,7 +344,7 @@ func (c *FS) LastActiveAncestor() (*AUMHash, error) {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 
-	hash, err := ioutil.ReadFile(filepath.Join(c.base, "last_active_ancestor"))
+	hash, err := os.ReadFile(filepath.Join(c.base, "last_active_ancestor"))
 	if err != nil {
 		if os.IsNotExist(err) {
 			return nil, nil // Not exist == none set.

+ 1 - 2
tsnet/tsnet.go

@@ -11,7 +11,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -252,7 +251,7 @@ func (s *Server) start() (reterr error) {
 	c := logtail.Config{
 		Collection: lpc.Collection,
 		PrivateID:  lpc.PrivateID,
-		Stderr:     ioutil.Discard, // log everything to Buffer
+		Stderr:     io.Discard, // log everything to Buffer
 		Buffer:     s.logbuffer,
 		NewZstdEncoder: func() logtail.Encoder {
 			w, err := smallzstd.NewEncoder(nil)

+ 1 - 2
tstest/integration/gen_deps.go

@@ -11,7 +11,6 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -62,7 +61,7 @@ import (
 	fmt.Fprintf(&out, ")\n")
 
 	filename := fmt.Sprintf("tailscaled_deps_test_%s.go", goos)
-	err = ioutil.WriteFile(filename, out.Bytes(), 0644)
+	err = os.WriteFile(filename, out.Bytes(), 0644)
 	if err != nil {
 		log.Fatal(err)
 	}

+ 2 - 3
tstest/integration/integration.go

@@ -14,7 +14,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -88,7 +87,7 @@ var (
 // buildTestBinaries builds tailscale and tailscaled.
 // It returns the dir containing the binaries.
 func buildTestBinaries() (string, error) {
-	bindir, err := ioutil.TempDir("", "")
+	bindir, err := os.MkdirTemp("", "")
 	if err != nil {
 		return "", err
 	}
@@ -288,7 +287,7 @@ func (lc *LogCatcher) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		defer dec.Close()
 		body = dec
 	}
-	bodyBytes, _ := ioutil.ReadAll(body)
+	bodyBytes, _ := io.ReadAll(body)
 
 	type Entry struct {
 		Logtail struct {

+ 2 - 3
tstest/integration/integration_test.go

@@ -14,7 +14,6 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"net/http/httptest"
@@ -653,7 +652,7 @@ func newTestNode(t *testing.T, env *testEnv) *testNode {
 func (n *testNode) diskPrefs() *ipn.Prefs {
 	t := n.env.t
 	t.Helper()
-	if _, err := ioutil.ReadFile(n.stateFile); err != nil {
+	if _, err := os.ReadFile(n.stateFile); err != nil {
 		t.Fatalf("reading prefs: %v", err)
 	}
 	fs, err := store.NewFileStore(nil, n.stateFile)
@@ -768,7 +767,7 @@ func (op *nodeOutputParser) parseLines() {
 	if len(buf) == 0 {
 		op.buf.Reset()
 	} else {
-		io.CopyN(ioutil.Discard, &op.buf, int64(op.buf.Len()-len(buf)))
+		io.CopyN(io.Discard, &op.buf, int64(op.buf.Len()-len(buf)))
 	}
 }
 

+ 2 - 3
tstest/integration/testcontrol/testcontrol.go

@@ -14,7 +14,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"math/rand"
 	"net/http"
@@ -426,7 +425,7 @@ func (s *Server) CompleteAuth(authPathOrURL string) bool {
 }
 
 func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.MachinePublic) {
-	msg, err := ioutil.ReadAll(io.LimitReader(r.Body, msgLimit))
+	msg, err := io.ReadAll(io.LimitReader(r.Body, msgLimit))
 	r.Body.Close()
 	if err != nil {
 		http.Error(w, fmt.Sprintf("bad map request read: %v", err), 400)
@@ -597,7 +596,7 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi
 	defer s.incrInServeMap(-1)
 	ctx := r.Context()
 
-	msg, err := ioutil.ReadAll(io.LimitReader(r.Body, msgLimit))
+	msg, err := io.ReadAll(io.LimitReader(r.Body, msgLimit))
 	if err != nil {
 		r.Body.Close()
 		http.Error(w, fmt.Sprintf("bad map request read: %v", err), 400)

+ 1 - 2
tsweb/tsweb.go

@@ -13,7 +13,6 @@ import (
 	"expvar"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	_ "net/http/pprof"
@@ -81,7 +80,7 @@ func AllowDebugAccess(r *http.Request) bool {
 		urlKey := r.FormValue("debugkey")
 		keyPath := envknob.String("TS_DEBUG_KEY_PATH")
 		if urlKey != "" && keyPath != "" {
-			slurp, err := ioutil.ReadFile(keyPath)
+			slurp, err := os.ReadFile(keyPath)
 			if err == nil && string(bytes.TrimSpace(slurp)) == urlKey {
 				return true
 			}

+ 1 - 2
types/logger/logger.go

@@ -14,7 +14,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"strings"
 	"sync"
@@ -268,7 +267,7 @@ func (fn ArgWriter) Format(f fmt.State, _ rune) {
 	argBufioPool.Put(bw)
 }
 
-var argBufioPool = &sync.Pool{New: func() any { return bufio.NewWriterSize(ioutil.Discard, 1024) }}
+var argBufioPool = &sync.Pool{New: func() any { return bufio.NewWriterSize(io.Discard, 1024) }}
 
 // Filtered returns a Logf that silently swallows some log lines.
 // Each inbound format and args is evaluated and printed to a string s.

+ 2 - 2
wgengine/magicsock/magicsock_test.go

@@ -12,7 +12,7 @@ import (
 	"encoding/binary"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"math/rand"
 	"net"
 	"net/http"
@@ -1174,7 +1174,7 @@ func TestDiscoStringLogRace(t *testing.T) {
 	wg.Add(2)
 	go func() {
 		defer wg.Done()
-		fmt.Fprintf(ioutil.Discard, "%v", de)
+		fmt.Fprintf(io.Discard, "%v", de)
 	}()
 	go func() {
 		defer wg.Done()

+ 3 - 4
wgengine/router/router_linux.go

@@ -8,7 +8,6 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net/netip"
 	"os"
 	"os/exec"
@@ -1601,7 +1600,7 @@ func checkIPv6(logf logger.Logf) error {
 	if os.IsNotExist(err) {
 		return err
 	}
-	bs, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6")
+	bs, err := os.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6")
 	if err != nil {
 		// Be conservative if we can't find the ipv6 configuration knob.
 		return err
@@ -1617,7 +1616,7 @@ func checkIPv6(logf logger.Logf) error {
 	// Older kernels don't support IPv6 policy routing. Some kernels
 	// support policy routing but don't have this knob, so absence of
 	// the knob is not fatal.
-	bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
+	bs, err = os.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
 	if err == nil {
 		disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
 		if err != nil {
@@ -1647,7 +1646,7 @@ func checkIPv6(logf logger.Logf) error {
 // netfilter, so some older distros ship a kernel that can't NAT IPv6
 // traffic.
 func supportsV6NAT() bool {
-	bs, err := ioutil.ReadFile("/proc/net/ip6_tables_names")
+	bs, err := os.ReadFile("/proc/net/ip6_tables_names")
 	if err != nil {
 		// Can't read the file. Assume SNAT works.
 		return true