From bd2a2d53d3a4d632c9fae7c1b6426c3b5ef34010 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 5 Mar 2026 21:13:57 +0000 Subject: [PATCH] all: use Go 1.26 things, run most gofix modernizers I omitted a lot of the min/max modernizers because they didn't result in more clear code. Some of it's older "for x := range 123". Also: errors.AsType, any, fmt.Appendf, etc. Updates #18682 Change-Id: I83a451577f33877f962766a5b65ce86f7696471c Signed-off-by: Brad Fitzpatrick --- appc/appconnector_test.go | 4 +- client/local/local.go | 10 ++--- client/systray/logo.go | 4 +- clientupdate/clientupdate.go | 4 +- clientupdate/clientupdate_test.go | 2 +- cmd/containerboot/forwarding.go | 2 +- cmd/containerboot/main_test.go | 7 ++-- cmd/derper/mesh.go | 2 +- cmd/k8s-operator/egress-eps_test.go | 9 ++--- .../egress-services-readiness_test.go | 7 ++-- cmd/k8s-operator/egress-services_test.go | 2 +- cmd/k8s-operator/ingress-for-pg.go | 10 ++--- cmd/k8s-operator/ingress-for-pg_test.go | 2 +- cmd/k8s-operator/metrics_resources.go | 9 ++--- cmd/k8s-operator/proxygroup.go | 9 ++--- cmd/k8s-operator/proxygroup_specs.go | 5 +-- cmd/k8s-operator/proxygroup_test.go | 4 +- cmd/k8s-operator/sts.go | 16 +++----- cmd/k8s-operator/sts_test.go | 5 +-- cmd/k8s-operator/svc-for-pg.go | 3 +- cmd/k8s-operator/testutils_test.go | 12 +++--- cmd/k8s-operator/tsrecorder.go | 10 ++--- cmd/k8s-operator/tsrecorder_specs.go | 5 +-- cmd/mkpkg/main.go | 4 +- cmd/natc/ippool/ippool_test.go | 2 +- cmd/natc/natc.go | 5 +-- cmd/sniproxy/sniproxy.go | 4 +- cmd/speedtest/speedtest.go | 3 +- cmd/stunstamp/stunstamp.go | 3 +- cmd/tailscale/cli/appcroutes.go | 6 +-- cmd/tailscale/cli/cli.go | 2 +- cmd/tailscale/cli/cli_test.go | 4 +- cmd/tailscale/cli/configure-synology-cert.go | 8 ++-- cmd/tailscale/cli/file.go | 7 ++-- cmd/tailscale/cli/ip.go | 13 +++---- cmd/tailscale/cli/risks.go | 2 +- cmd/tailscale/cli/serve_v2.go | 4 +- cmd/tailscale/cli/set.go | 7 ++-- cmd/tailscale/cli/ssh.go | 13 +++---- cmd/tailscale/cli/ssh_exec_windows.go | 3 +- cmd/tailscale/cli/ssh_unix.go | 2 +- cmd/tailscale/cli/up.go | 5 +-- cmd/testwrapper/testwrapper.go | 3 +- cmd/testwrapper/testwrapper_test.go | 3 +- cmd/tta/tta.go | 2 +- cmd/viewer/viewer.go | 5 +-- control/controlclient/controlclient_test.go | 8 ++-- control/controlclient/direct.go | 2 +- control/controlclient/map.go | 8 ++-- control/controlhttp/http_test.go | 4 +- control/controlknobs/controlknobs.go | 12 +++--- derp/derp_test.go | 12 ++---- derp/derphttp/derphttp_test.go | 12 ++---- derp/derpserver/derpserver.go | 22 +++++------ derp/derpserver/derpserver_test.go | 24 ++++++------ derp/xdp/xdp_linux.go | 3 +- disco/disco.go | 8 ++-- docs/webhooks/example.go | 6 +-- drive/driveimpl/compositedav/rewriting.go | 2 +- feature/conn25/conn25.go | 9 +---- feature/conn25/conn25_test.go | 2 +- .../identityfederation/identityfederation.go | 3 +- feature/linuxdnsfight/linuxdnsfight_test.go | 2 +- feature/taildrop/fileops_fs.go | 2 +- health/health_test.go | 5 +-- hostinfo/hostinfo_linux.go | 8 ++-- ipn/auditlog/auditlog.go | 7 +++- ipn/ipnlocal/breaktcp_linux.go | 2 +- ipn/ipnlocal/bus_test.go | 7 ++-- ipn/ipnlocal/extension_host.go | 2 +- ipn/ipnlocal/extension_host_test.go | 5 +-- ipn/ipnlocal/local.go | 13 ++----- ipn/ipnlocal/netmapcache/netmapcache_test.go | 2 +- ipn/ipnlocal/peerapi.go | 2 +- ipn/ipnlocal/serve.go | 4 +- ipn/ipnlocal/ssh.go | 6 +-- ipn/localapi/debug.go | 7 +--- ipn/prefs.go | 20 +++------- ipn/prefs_test.go | 4 +- ipn/serve.go | 2 +- ipn/store/awsstore/store_aws.go | 3 +- k8s-operator/sessionrecording/spdy/frame.go | 4 +- logtail/logtail.go | 4 +- logtail/logtail_test.go | 6 +-- metrics/multilabelmap.go | 8 ++-- net/art/stride_table_test.go | 2 +- net/captivedetection/captivedetection_test.go | 6 +-- net/dns/manager_linux.go | 2 +- net/dns/openresolv.go | 2 +- net/dns/resolver/forwarder.go | 3 +- net/dns/resolver/forwarder_test.go | 16 +++----- net/dns/resolver/tsdns.go | 4 +- net/dns/wsl_windows.go | 3 +- net/netcheck/netcheck.go | 14 +++---- net/netcheck/netcheck_test.go | 9 ++--- net/neterror/neterror_linux.go | 3 +- net/netmon/state.go | 7 +--- net/netutil/routes.go | 4 +- net/socks5/socks5.go | 7 ++-- net/socks5/socks5_test.go | 8 ++-- net/stunserver/stunserver_test.go | 3 +- net/tstun/wrap.go | 4 +- net/tstun/wrap_test.go | 10 ++--- net/udprelay/server.go | 2 +- net/udprelay/server_test.go | 4 +- prober/prober.go | 8 +--- ssh/tailssh/incubator.go | 5 +-- ssh/tailssh/privs_test.go | 12 ++---- ssh/tailssh/tailssh.go | 19 +++------ ssh/tailssh/tailssh_test.go | 28 +++++-------- syncs/shardedint_test.go | 4 +- syncs/shardvalue_test.go | 4 +- syncs/syncs_test.go | 9 ++--- tailcfg/tailcfg_test.go | 4 +- tka/chaintest_test.go | 5 +-- tka/key.go | 5 +-- tka/scenario_test.go | 5 +-- tka/sync.go | 2 +- tool/gocross/exec_other.go | 3 +- tsconsensus/monitor.go | 4 +- tsconsensus/tsconsensus_test.go | 8 ++-- tsd/tsd.go | 6 +-- tsnet/tsnet.go | 12 ++---- tsnet/tsnet_test.go | 2 +- tstest/deptest/deptest.go | 2 +- tstest/integration/integration_test.go | 2 +- tstest/integration/nat/nat_test.go | 7 +--- tstest/integration/vms/vms_test.go | 2 +- tstest/natlab/natlab.go | 16 +++----- tstest/natlab/vnet/vnet.go | 2 +- tstest/resource_test.go | 2 +- tstest/typewalk/typewalk.go | 5 +-- tsweb/tsweb.go | 9 ++--- tsweb/varz/varz.go | 13 +++---- types/ipproto/ipproto_test.go | 6 +-- types/lazy/deferred_test.go | 18 +++------ types/netmap/nodemut_test.go | 5 +-- types/persist/persist_test.go | 4 +- types/views/views.go | 4 +- util/dnsname/dnsname.go | 2 +- util/goroutines/goroutines.go | 2 +- util/hashx/block512_test.go | 2 +- util/httphdr/httphdr.go | 2 +- util/httpm/httpm_test.go | 2 +- util/linuxfw/fake.go | 7 ++-- util/linuxfw/iptables.go | 4 +- util/linuxfw/nftables_for_svcs.go | 4 +- util/linuxfw/nftables_runner_test.go | 2 +- util/pool/pool_test.go | 6 +-- util/set/intset.go | 2 +- util/singleflight/singleflight.go | 4 +- util/singleflight/singleflight_test.go | 39 ++++++++----------- util/slicesx/slicesx_test.go | 2 +- util/syspolicy/policytest/policytest.go | 7 +--- util/topk/topk_test.go | 2 +- util/vizerror/vizerror.go | 3 +- util/zstdframe/zstd_test.go | 32 +++++++-------- version/cmdname.go | 2 +- version/version_test.go | 2 +- wgengine/filter/filter_test.go | 8 ++-- wgengine/magicsock/magicsock.go | 3 +- wgengine/magicsock/magicsock_test.go | 6 +-- wgengine/pendopen.go | 2 +- wgengine/router/osrouter/router_linux_test.go | 8 ++-- wgengine/router/osrouter/runner.go | 11 ++---- wgengine/router/router_test.go | 4 +- wgengine/wgcfg/config_test.go | 6 +-- wif/wif.go | 3 +- 168 files changed, 431 insertions(+), 618 deletions(-) diff --git a/appc/appconnector_test.go b/appc/appconnector_test.go index a860da6a7..d14ef68fc 100644 --- a/appc/appconnector_test.go +++ b/appc/appconnector_test.go @@ -698,7 +698,7 @@ func TestRateLogger(t *testing.T) { wasCalled = true }) - for i := 0; i < 3; i++ { + for range 3 { clock.Advance(1 * time.Millisecond) rl.update(0) if wasCalled { @@ -720,7 +720,7 @@ func TestRateLogger(t *testing.T) { wasCalled = true }) - for i := 0; i < 3; i++ { + for range 3 { clock.Advance(1 * time.Minute) rl.update(0) if wasCalled { diff --git a/client/local/local.go b/client/local/local.go index a7b8b83b1..e72589306 100644 --- a/client/local/local.go +++ b/client/local/local.go @@ -192,8 +192,8 @@ func (e *AccessDeniedError) Unwrap() error { return e.err } // IsAccessDeniedError reports whether err is or wraps an AccessDeniedError. func IsAccessDeniedError(err error) bool { - var ae *AccessDeniedError - return errors.As(err, &ae) + _, ok := errors.AsType[*AccessDeniedError](err) + return ok } // PreconditionsFailedError is returned when the server responds @@ -210,8 +210,8 @@ func (e *PreconditionsFailedError) Unwrap() error { return e.err } // IsPreconditionsFailedError reports whether err is or wraps an PreconditionsFailedError. func IsPreconditionsFailedError(err error) bool { - var ae *PreconditionsFailedError - return errors.As(err, &ae) + _, ok := errors.AsType[*PreconditionsFailedError](err) + return ok } // bestError returns either err, or if body contains a valid JSON @@ -1071,7 +1071,7 @@ func tailscaledConnectHint() string { // ActiveState=inactive // SubState=dead st := map[string]string{} - for _, line := range strings.Split(string(out), "\n") { + for line := range strings.SplitSeq(string(out), "\n") { if k, v, ok := strings.Cut(line, "="); ok { st[k] = strings.TrimSpace(v) } diff --git a/client/systray/logo.go b/client/systray/logo.go index 4cd19778d..a0f8bf7d0 100644 --- a/client/systray/logo.go +++ b/client/systray/logo.go @@ -233,8 +233,8 @@ func (logo tsLogo) renderWithBorder(borderUnits int) *bytes.Buffer { dc.InvertMask() } - for y := 0; y < 3; y++ { - for x := 0; x < 3; x++ { + for y := range 3 { + for x := range 3 { px := (borderUnits + 1 + 3*x) * radius py := (borderUnits + 1 + 3*y) * radius col := fg diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index d52241483..6d034b342 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -1292,6 +1292,6 @@ func requireRoot() error { } func isExitError(err error) bool { - var exitErr *exec.ExitError - return errors.As(err, &exitErr) + _, ok := errors.AsType[*exec.ExitError](err) + return ok } diff --git a/clientupdate/clientupdate_test.go b/clientupdate/clientupdate_test.go index 13fc8f08a..100339ce7 100644 --- a/clientupdate/clientupdate_test.go +++ b/clientupdate/clientupdate_test.go @@ -451,7 +451,7 @@ func TestSynoArch(t *testing.T) { synoinfoConfPath := filepath.Join(t.TempDir(), "synoinfo.conf") if err := os.WriteFile( synoinfoConfPath, - []byte(fmt.Sprintf("unique=%q\n", tt.synoinfoUnique)), + fmt.Appendf(nil, "unique=%q\n", tt.synoinfoUnique), 0600, ); err != nil { t.Fatal(err) diff --git a/cmd/containerboot/forwarding.go b/cmd/containerboot/forwarding.go index 0ec9c36c0..6d90fbaaa 100644 --- a/cmd/containerboot/forwarding.go +++ b/cmd/containerboot/forwarding.go @@ -51,7 +51,7 @@ func ensureIPForwarding(root, clusterProxyTargetIP, tailnetTargetIP, tailnetTarg v4Forwarding = true } if routes != nil && *routes != "" { - for _, route := range strings.Split(*routes, ",") { + for route := range strings.SplitSeq(*routes, ",") { cidr, err := netip.ParsePrefix(route) if err != nil { return fmt.Errorf("invalid subnet route: %v", err) diff --git a/cmd/containerboot/main_test.go b/cmd/containerboot/main_test.go index cc5629f99..365cf2184 100644 --- a/cmd/containerboot/main_test.go +++ b/cmd/containerboot/main_test.go @@ -15,6 +15,7 @@ "fmt" "io" "io/fs" + "maps" "net" "net/http" "net/http/httptest" @@ -1249,7 +1250,7 @@ func (b *lockingBuffer) String() string { func waitLogLine(t *testing.T, timeout time.Duration, b *lockingBuffer, want string) { deadline := time.Now().Add(timeout) for time.Now().Before(deadline) { - for _, line := range strings.Split(b.String(), "\n") { + for line := range strings.SplitSeq(b.String(), "\n") { if !strings.HasPrefix(line, "boot: ") { continue } @@ -1438,9 +1439,7 @@ func (k *kubeServer) Secret() map[string]string { k.Lock() defer k.Unlock() ret := map[string]string{} - for k, v := range k.secret { - ret[k] = v - } + maps.Copy(ret, k.secret) return ret } diff --git a/cmd/derper/mesh.go b/cmd/derper/mesh.go index 34ea7da85..c07cfe969 100644 --- a/cmd/derper/mesh.go +++ b/cmd/derper/mesh.go @@ -25,7 +25,7 @@ func startMesh(s *derpserver.Server) error { if !s.HasMeshKey() { return errors.New("--mesh-with requires --mesh-psk-file") } - for _, hostTuple := range strings.Split(*meshWith, ",") { + for hostTuple := range strings.SplitSeq(*meshWith, ",") { if err := startMeshWithHost(s, hostTuple); err != nil { return err } diff --git a/cmd/k8s-operator/egress-eps_test.go b/cmd/k8s-operator/egress-eps_test.go index 47acb64f2..6335b4eb8 100644 --- a/cmd/k8s-operator/egress-eps_test.go +++ b/cmd/k8s-operator/egress-eps_test.go @@ -11,7 +11,6 @@ "math/rand/v2" "testing" - "github.com/AlekSi/pointer" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -106,11 +105,11 @@ func TestTailscaleEgressEndpointSlices(t *testing.T) { expectReconciled(t, er, "operator-ns", "foo") eps.Endpoints = append(eps.Endpoints, discoveryv1.Endpoint{ Addresses: []string{"10.0.0.1"}, - Hostname: pointer.To("foo"), + Hostname: new("foo"), Conditions: discoveryv1.EndpointConditions{ - Serving: pointer.ToBool(true), - Ready: pointer.ToBool(true), - Terminating: pointer.ToBool(false), + Serving: new(true), + Ready: new(true), + Terminating: new(false), }, }) expectEqual(t, fc, eps) diff --git a/cmd/k8s-operator/egress-services-readiness_test.go b/cmd/k8s-operator/egress-services-readiness_test.go index ba89903df..96d76cc4e 100644 --- a/cmd/k8s-operator/egress-services-readiness_test.go +++ b/cmd/k8s-operator/egress-services-readiness_test.go @@ -9,7 +9,6 @@ "fmt" "testing" - "github.com/AlekSi/pointer" "go.uber.org/zap" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -145,9 +144,9 @@ func setEndpointForReplica(pg *tsapi.ProxyGroup, ordinal int32, eps *discoveryv1 eps.Endpoints = append(eps.Endpoints, discoveryv1.Endpoint{ Addresses: []string{p.Status.PodIPs[0].IP}, Conditions: discoveryv1.EndpointConditions{ - Ready: pointer.ToBool(true), - Serving: pointer.ToBool(true), - Terminating: pointer.ToBool(false), + Ready: new(true), + Serving: new(true), + Terminating: new(false), }, }) } diff --git a/cmd/k8s-operator/egress-services_test.go b/cmd/k8s-operator/egress-services_test.go index 458614491..d38284690 100644 --- a/cmd/k8s-operator/egress-services_test.go +++ b/cmd/k8s-operator/egress-services_test.go @@ -243,7 +243,7 @@ func portsForEndpointSlice(svc *corev1.Service) []discoveryv1.EndpointPort { ports = append(ports, discoveryv1.EndpointPort{ Name: &p.Name, Protocol: &p.Protocol, - Port: pointer.ToInt32(p.TargetPort.IntVal), + Port: new(p.TargetPort.IntVal), }) } return ports diff --git a/cmd/k8s-operator/ingress-for-pg.go b/cmd/k8s-operator/ingress-for-pg.go index 5966ace3c..4b140a8ae 100644 --- a/cmd/k8s-operator/ingress-for-pg.go +++ b/cmd/k8s-operator/ingress-for-pg.go @@ -10,6 +10,7 @@ "encoding/json" "errors" "fmt" + "maps" "math/rand/v2" "net/http" "reflect" @@ -914,9 +915,7 @@ func ownerAnnotations(operatorID string, svc *tailscale.VIPService) (map[string] } newAnnots := make(map[string]string, len(svc.Annotations)+1) - for k, v := range svc.Annotations { - newAnnots[k] = v - } + maps.Copy(newAnnots, svc.Annotations) newAnnots[ownerAnnotation] = string(json) return newAnnots, nil } @@ -1129,8 +1128,7 @@ func hasCerts(ctx context.Context, cl client.Client, lc localClient, ns string, } func isErrorTailscaleServiceNotFound(err error) bool { - var errResp tailscale.ErrResponse - ok := errors.As(err, &errResp) + errResp, ok := errors.AsType[tailscale.ErrResponse](err) return ok && errResp.Status == http.StatusNotFound } @@ -1144,7 +1142,7 @@ func tagViolations(obj client.Object) []string { return nil } - for _, tag := range strings.Split(tags, ",") { + for tag := range strings.SplitSeq(tags, ",") { tag = strings.TrimSpace(tag) if err := tailcfg.CheckTag(tag); err != nil { violations = append(violations, fmt.Sprintf("invalid tag %q: %v", tag, err)) diff --git a/cmd/k8s-operator/ingress-for-pg_test.go b/cmd/k8s-operator/ingress-for-pg_test.go index e93d0184e..480e6a26e 100644 --- a/cmd/k8s-operator/ingress-for-pg_test.go +++ b/cmd/k8s-operator/ingress-for-pg_test.go @@ -1102,7 +1102,7 @@ func verifyTailscaledConfig(t *testing.T, fc client.Client, pgName string, expec Labels: pgSecretLabels(pgName, kubetypes.LabelSecretTypeConfig), }, Data: map[string][]byte{ - tsoperator.TailscaledConfigFileName(pgMinCapabilityVersion): []byte(fmt.Sprintf(`{"Version":""%s}`, expected)), + tsoperator.TailscaledConfigFileName(pgMinCapabilityVersion): fmt.Appendf(nil, `{"Version":""%s}`, expected), }, }) } diff --git a/cmd/k8s-operator/metrics_resources.go b/cmd/k8s-operator/metrics_resources.go index afb055018..c7c329a7e 100644 --- a/cmd/k8s-operator/metrics_resources.go +++ b/cmd/k8s-operator/metrics_resources.go @@ -8,6 +8,7 @@ import ( "context" "fmt" + "maps" "reflect" "go.uber.org/zap" @@ -286,11 +287,7 @@ func isNamespacedProxyType(typ string) bool { func mergeMapKeys(a, b map[string]string) map[string]string { m := make(map[string]string, len(a)+len(b)) - for key, val := range b { - m[key] = val - } - for key, val := range a { - m[key] = val - } + maps.Copy(m, b) + maps.Copy(m, a) return m } diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index db8733f90..200782498 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -308,8 +308,7 @@ func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, tailscaleClie var err error svcToNodePorts, tailscaledPort, err = r.ensureNodePortServiceCreated(ctx, pg, proxyClass) if err != nil { - var allocatePortErr *allocatePortsErr - if errors.As(err, &allocatePortErr) { + if _, ok := errors.AsType[*allocatePortsErr](err); ok { reason := reasonProxyGroupCreationFailed msg := fmt.Sprintf("error provisioning NodePort Services for static endpoints: %v", err) r.recorder.Event(pg, corev1.EventTypeWarning, reason, msg) @@ -321,8 +320,7 @@ func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, tailscaleClie staticEndpoints, err := r.ensureConfigSecretsCreated(ctx, tailscaleClient, pg, proxyClass, svcToNodePorts) if err != nil { - var selectorErr *FindStaticEndpointErr - if errors.As(err, &selectorErr) { + if _, ok := errors.AsType[*FindStaticEndpointErr](err); ok { reason := reasonProxyGroupCreationFailed msg := fmt.Sprintf("error provisioning config Secrets: %v", err) r.recorder.Event(pg, corev1.EventTypeWarning, reason, msg) @@ -718,8 +716,7 @@ func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, tailscaleClient func (r *ProxyGroupReconciler) deleteTailnetDevice(ctx context.Context, tailscaleClient tsClient, id tailcfg.StableNodeID, logger *zap.SugaredLogger) error { logger.Debugf("deleting device %s from control", string(id)) if err := tailscaleClient.DeleteDevice(ctx, string(id)); err != nil { - errResp := &tailscale.ErrResponse{} - if ok := errors.As(err, errResp); ok && errResp.Status == http.StatusNotFound { + if errResp, ok := errors.AsType[tailscale.ErrResponse](err); ok && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", string(id)) } else { return fmt.Errorf("error deleting device: %w", err) diff --git a/cmd/k8s-operator/proxygroup_specs.go b/cmd/k8s-operator/proxygroup_specs.go index 69b5b109a..60b4bddd5 100644 --- a/cmd/k8s-operator/proxygroup_specs.go +++ b/cmd/k8s-operator/proxygroup_specs.go @@ -7,6 +7,7 @@ import ( "fmt" + "maps" "slices" "strconv" "strings" @@ -544,9 +545,7 @@ func pgSecretLabels(pgName, secretType string) map[string]string { func pgLabels(pgName string, customLabels map[string]string) map[string]string { labels := make(map[string]string, len(customLabels)+3) - for k, v := range customLabels { - labels[k] = v - } + maps.Copy(labels, customLabels) labels[kubetypes.LabelManaged] = "true" labels[LabelParentType] = "proxygroup" diff --git a/cmd/k8s-operator/proxygroup_test.go b/cmd/k8s-operator/proxygroup_test.go index 2d46e3d5b..9b3ee0e0f 100644 --- a/cmd/k8s-operator/proxygroup_test.go +++ b/cmd/k8s-operator/proxygroup_test.go @@ -1826,10 +1826,10 @@ func addNodeIDToStateSecrets(t *testing.T, fc client.WithWatch, pg *tsapi.ProxyG currentProfileKey: []byte(key), key: bytes, kubetypes.KeyDeviceIPs: []byte(`["1.2.3.4", "::1"]`), - kubetypes.KeyDeviceFQDN: []byte(fmt.Sprintf("hostname-nodeid-%d.tails-scales.ts.net", i)), + kubetypes.KeyDeviceFQDN: fmt.Appendf(nil, "hostname-nodeid-%d.tails-scales.ts.net", i), // TODO(tomhjp): We have two different mechanisms to retrieve device IDs. // Consolidate on this one. - kubetypes.KeyDeviceID: []byte(fmt.Sprintf("nodeid-%d", i)), + kubetypes.KeyDeviceID: fmt.Appendf(nil, "nodeid-%d", i), kubetypes.KeyPodUID: []byte(podUID), } }) diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index 2a63ede4e..c88a6df17 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -11,6 +11,7 @@ "encoding/json" "errors" "fmt" + "maps" "net/http" "os" "path" @@ -304,8 +305,7 @@ func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, tailnet string, lo if dev.id != "" { logger.Debugf("deleting device %s from control", string(dev.id)) if err = tailscaleClient.DeleteDevice(ctx, string(dev.id)); err != nil { - errResp := &tailscale.ErrResponse{} - if ok := errors.As(err, errResp); ok && errResp.Status == http.StatusNotFound { + if errResp, ok := errors.AsType[tailscale.ErrResponse](err); ok && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", string(dev.id)) } else { return false, fmt.Errorf("deleting device: %w", err) @@ -499,14 +499,11 @@ func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, tailscale } if dev != nil && dev.id != "" { - var errResp *tailscale.ErrResponse - err = tailscaleClient.DeleteDevice(ctx, string(dev.id)) - switch { - case errors.As(err, &errResp) && errResp.Status == http.StatusNotFound: + if errResp, ok := errors.AsType[*tailscale.ErrResponse](err); ok && errResp.Status == http.StatusNotFound { // This device has possibly already been deleted in the admin console. So we can ignore this // and move on to removing the secret. - case err != nil: + } else if err != nil { return nil, err } } @@ -677,9 +674,8 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S }, } mak.Set(&pod.Labels, "app", sts.ParentResourceUID) - for key, val := range sts.ChildResourceLabels { - pod.Labels[key] = val // sync StatefulSet labels to Pod to make it easier for users to select the Pod - } + // sync StatefulSet labels to Pod to make it easier for users to select the Pod + maps.Copy(pod.Labels, sts.ChildResourceLabels) if sts.Replicas > 0 { ss.Spec.Replicas = new(sts.Replicas) diff --git a/cmd/k8s-operator/sts_test.go b/cmd/k8s-operator/sts_test.go index f44de8481..0ceec5791 100644 --- a/cmd/k8s-operator/sts_test.go +++ b/cmd/k8s-operator/sts_test.go @@ -8,6 +8,7 @@ import ( _ "embed" "fmt" + "maps" "reflect" "regexp" "strings" @@ -408,7 +409,5 @@ func Test_mergeStatefulSetLabelsOrAnnots(t *testing.T) { // updateMap updates map a with the values from map b. func updateMap(a, b map[string]string) { - for key, val := range b { - a[key] = val - } + maps.Copy(a, b) } diff --git a/cmd/k8s-operator/svc-for-pg.go b/cmd/k8s-operator/svc-for-pg.go index e0383824a..7cbbaebaa 100644 --- a/cmd/k8s-operator/svc-for-pg.go +++ b/cmd/k8s-operator/svc-for-pg.go @@ -526,8 +526,7 @@ func (r *HAServiceReconciler) tailnetCertDomain(ctx context.Context) (string, er func cleanupTailscaleService(ctx context.Context, tsClient tsClient, name tailcfg.ServiceName, operatorID string, logger *zap.SugaredLogger) (updated bool, err error) { svc, err := tsClient.GetVIPService(ctx, name) if err != nil { - errResp := &tailscale.ErrResponse{} - ok := errors.As(err, errResp) + errResp, ok := errors.AsType[tailscale.ErrResponse](err) if ok && errResp.Status == http.StatusNotFound { return false, nil } diff --git a/cmd/k8s-operator/testutils_test.go b/cmd/k8s-operator/testutils_test.go index e13478d71..191a31723 100644 --- a/cmd/k8s-operator/testutils_test.go +++ b/cmd/k8s-operator/testutils_test.go @@ -13,6 +13,7 @@ "net/netip" "path" "reflect" + "slices" "strings" "sync" "testing" @@ -555,7 +556,7 @@ func expectedSecret(t *testing.T, cl client.Client, opts configOpts) *corev1.Sec if opts.isExitNode { r = "0.0.0.0/0,::/0," + r } - for _, rr := range strings.Split(r, ",") { + for rr := range strings.SplitSeq(r, ",") { prefix, err := netip.ParsePrefix(rr) if err != nil { t.Fatal(err) @@ -822,12 +823,9 @@ func expectEvents(t *testing.T, rec *record.FakeRecorder, wantsEvents []string) select { case gotEvent := <-rec.Events: found := false - for _, wantEvent := range wantsEvents { - if wantEvent == gotEvent { - found = true - seenEvents = append(seenEvents, gotEvent) - break - } + if slices.Contains(wantsEvents, gotEvent) { + found = true + seenEvents = append(seenEvents, gotEvent) } if !found { t.Errorf("got unexpected event %q, expected events: %+#v", gotEvent, wantsEvents) diff --git a/cmd/k8s-operator/tsrecorder.go b/cmd/k8s-operator/tsrecorder.go index 60ed24a70..0a497a46e 100644 --- a/cmd/k8s-operator/tsrecorder.go +++ b/cmd/k8s-operator/tsrecorder.go @@ -363,15 +363,12 @@ func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tailscaleC } if ok { - var errResp *tailscale.ErrResponse - r.log.Debugf("deleting device %s", devicePrefs.Config.NodeID) err = tailscaleClient.DeleteDevice(ctx, string(devicePrefs.Config.NodeID)) - switch { - case errors.As(err, &errResp) && errResp.Status == http.StatusNotFound: + if errResp, ok := errors.AsType[*tailscale.ErrResponse](err); ok && errResp.Status == http.StatusNotFound { // This device has possibly already been deleted in the admin console. So we can ignore this // and move on to removing the secret. - case err != nil: + } else if err != nil { return err } } @@ -412,8 +409,7 @@ func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Record nodeID := string(devicePrefs.Config.NodeID) logger.Debugf("deleting device %s from control", nodeID) if err = tailscaleClient.DeleteDevice(ctx, nodeID); err != nil { - errResp := &tailscale.ErrResponse{} - if errors.As(err, errResp) && errResp.Status == http.StatusNotFound { + if errResp, ok := errors.AsType[tailscale.ErrResponse](err); ok && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", nodeID) continue } diff --git a/cmd/k8s-operator/tsrecorder_specs.go b/cmd/k8s-operator/tsrecorder_specs.go index 101f68405..5a93bc22b 100644 --- a/cmd/k8s-operator/tsrecorder_specs.go +++ b/cmd/k8s-operator/tsrecorder_specs.go @@ -7,6 +7,7 @@ import ( "fmt" + "maps" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -312,9 +313,7 @@ func tsrEnv(tsr *tsapi.Recorder, loginServer string) []corev1.EnvVar { func tsrLabels(app, instance string, customLabels map[string]string) map[string]string { labels := make(map[string]string, len(customLabels)+3) - for k, v := range customLabels { - labels[k] = v - } + maps.Copy(labels, customLabels) // ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ labels["app.kubernetes.io/name"] = app diff --git a/cmd/mkpkg/main.go b/cmd/mkpkg/main.go index 6f4de7e29..ecf108c2e 100644 --- a/cmd/mkpkg/main.go +++ b/cmd/mkpkg/main.go @@ -24,7 +24,7 @@ func parseFiles(s string, typ string) (files.Contents, error) { return nil, nil } var contents files.Contents - for _, f := range strings.Split(s, ",") { + for f := range strings.SplitSeq(s, ",") { fs := strings.Split(f, ":") if len(fs) != 2 { return nil, fmt.Errorf("unparseable file field %q", f) @@ -41,7 +41,7 @@ func parseEmptyDirs(s string) files.Contents { return nil } var contents files.Contents - for _, d := range strings.Split(s, ",") { + for d := range strings.SplitSeq(s, ",") { contents = append(contents, &files.Content{Type: files.TypeDir, Destination: d}) } return contents diff --git a/cmd/natc/ippool/ippool_test.go b/cmd/natc/ippool/ippool_test.go index 405ec6156..af0053c2f 100644 --- a/cmd/natc/ippool/ippool_test.go +++ b/cmd/natc/ippool/ippool_test.go @@ -30,7 +30,7 @@ func TestIPPoolExhaustion(t *testing.T) { from := tailcfg.NodeID(12345) - for i := 0; i < 5; i++ { + for range 5 { for _, domain := range domains { addr, err := pool.IPForDomain(from, domain) if err != nil { diff --git a/cmd/natc/natc.go b/cmd/natc/natc.go index 11975b7d2..339c42ccd 100644 --- a/cmd/natc/natc.go +++ b/cmd/natc/natc.go @@ -149,7 +149,7 @@ func main() { } var prefixes []netip.Prefix - for _, s := range strings.Split(*v4PfxStr, ",") { + for s := range strings.SplitSeq(*v4PfxStr, ",") { p := netip.MustParsePrefix(strings.TrimSpace(s)) if p.Masked() != p { log.Fatalf("v4 prefix %v is not a masked prefix", p) @@ -372,8 +372,7 @@ func (c *connector) handleDNS(pc net.PacketConn, buf []byte, remoteAddr *net.UDP addrQCount++ if _, ok := resolves[q.Name.String()]; !ok { addrs, err := c.resolver.LookupNetIP(ctx, "ip", q.Name.String()) - var dnsErr *net.DNSError - if errors.As(err, &dnsErr) && dnsErr.IsNotFound { + if dnsErr, ok := errors.AsType[*net.DNSError](err); ok && dnsErr.IsNotFound { continue } if err != nil { diff --git a/cmd/sniproxy/sniproxy.go b/cmd/sniproxy/sniproxy.go index 45503feca..bd95cc113 100644 --- a/cmd/sniproxy/sniproxy.go +++ b/cmd/sniproxy/sniproxy.go @@ -225,7 +225,7 @@ func (s *sniproxy) mergeConfigFromFlags(out *appctype.AppConnectorConfig, ports, Addrs: []netip.Addr{ip4, ip6}, } if ports != "" { - for _, portStr := range strings.Split(ports, ",") { + for portStr := range strings.SplitSeq(ports, ",") { port, err := strconv.ParseUint(portStr, 10, 16) if err != nil { log.Fatalf("invalid port: %s", portStr) @@ -238,7 +238,7 @@ func (s *sniproxy) mergeConfigFromFlags(out *appctype.AppConnectorConfig, ports, } var forwardConfigFromFlags []appctype.DNATConfig - for _, forwStr := range strings.Split(forwards, ",") { + for forwStr := range strings.SplitSeq(forwards, ",") { if forwStr == "" { continue } diff --git a/cmd/speedtest/speedtest.go b/cmd/speedtest/speedtest.go index 2cea97b1e..e11c4ad1d 100644 --- a/cmd/speedtest/speedtest.go +++ b/cmd/speedtest/speedtest.go @@ -72,8 +72,7 @@ func main() { func runSpeedtest(ctx context.Context, args []string) error { if _, _, err := net.SplitHostPort(speedtestArgs.host); err != nil { - var addrErr *net.AddrError - if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" { + if addrErr, ok := errors.AsType[*net.AddrError](err); ok && addrErr.Err == "missing port in address" { // if no port is provided, append the default port speedtestArgs.host = net.JoinHostPort(speedtestArgs.host, strconv.Itoa(speedtest.DefaultPort)) } diff --git a/cmd/stunstamp/stunstamp.go b/cmd/stunstamp/stunstamp.go index cfedd82bd..743d6aec3 100644 --- a/cmd/stunstamp/stunstamp.go +++ b/cmd/stunstamp/stunstamp.go @@ -889,8 +889,7 @@ func remoteWriteTimeSeries(client *remoteWriteClient, tsCh chan []prompb.TimeSer reqCtx, cancel := context.WithTimeout(context.Background(), time.Second*30) writeErr = client.write(reqCtx, ts) cancel() - var re recoverableErr - recoverable := errors.As(writeErr, &re) + _, recoverable := errors.AsType[recoverableErr](writeErr) if writeErr != nil { log.Printf("remote write error(recoverable=%v): %v", recoverable, writeErr) } diff --git a/cmd/tailscale/cli/appcroutes.go b/cmd/tailscale/cli/appcroutes.go index 2ea001aec..04cbcdd83 100644 --- a/cmd/tailscale/cli/appcroutes.go +++ b/cmd/tailscale/cli/appcroutes.go @@ -102,12 +102,12 @@ func getSummarizeLearnedOutput(ri *appctype.RouteInfo) string { } return 0 }) - s := "" + var s strings.Builder fmtString := fmt.Sprintf("%%-%ds %%d\n", maxDomainWidth) // eg "%-10s %d\n" for _, dc := range x { - s += fmt.Sprintf(fmtString, dc.domain, dc.count) + s.WriteString(fmt.Sprintf(fmtString, dc.domain, dc.count)) } - return s + return s.String() } func runAppcRoutesInfo(ctx context.Context, args []string) error { diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index fda6b4546..8a2c2b9ef 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -124,7 +124,7 @@ func Run(args []string) (err error) { if errors.Is(err, flag.ErrHelp) { return nil } - if noexec := (ffcli.NoExecError{}); errors.As(err, &noexec) { + if noexec, ok := errors.AsType[ffcli.NoExecError](err); ok { // When the user enters an unknown subcommand, ffcli tries to run // the closest valid parent subcommand with everything else as args, // returning NoExecError if it doesn't have an Exec function. diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go index 537e641fc..bdf9116a0 100644 --- a/cmd/tailscale/cli/cli_test.go +++ b/cmd/tailscale/cli/cli_test.go @@ -962,8 +962,8 @@ func TestPrefFlagMapping(t *testing.T) { } prefType := reflect.TypeFor[ipn.Prefs]() - for i := range prefType.NumField() { - prefName := prefType.Field(i).Name + for field := range prefType.Fields() { + prefName := field.Name if prefHasFlag[prefName] { continue } diff --git a/cmd/tailscale/cli/configure-synology-cert.go b/cmd/tailscale/cli/configure-synology-cert.go index 0f38f2df2..32f5bbd70 100644 --- a/cmd/tailscale/cli/configure-synology-cert.go +++ b/cmd/tailscale/cli/configure-synology-cert.go @@ -16,6 +16,7 @@ "os/exec" "path" "runtime" + "slices" "strings" "github.com/peterbourgon/ff/v3/ffcli" @@ -85,11 +86,8 @@ func runConfigureSynologyCert(ctx context.Context, args []string) error { domain = st.CertDomains[0] } else { var found bool - for _, d := range st.CertDomains { - if d == domain { - found = true - break - } + if slices.Contains(st.CertDomains, domain) { + found = true } if !found { return fmt.Errorf("Domain %q was not one of the valid domain options: %q.", domain, st.CertDomains) diff --git a/cmd/tailscale/cli/file.go b/cmd/tailscale/cli/file.go index 94b36f535..e7406bee3 100644 --- a/cmd/tailscale/cli/file.go +++ b/cmd/tailscale/cli/file.go @@ -19,6 +19,7 @@ "os" "path" "path/filepath" + "slices" "strings" "sync" "sync/atomic" @@ -126,10 +127,8 @@ func runCp(ctx context.Context, args []string) error { if cpArgs.name != "" { return errors.New("can't use --name= with multiple files") } - for _, fileArg := range files { - if fileArg == "-" { - return errors.New("can't use '-' as STDIN file when providing filename arguments") - } + if slices.Contains(files, "-") { + return errors.New("can't use '-' as STDIN file when providing filename arguments") } } diff --git a/cmd/tailscale/cli/ip.go b/cmd/tailscale/cli/ip.go index 7159904c7..b76ef0a70 100644 --- a/cmd/tailscale/cli/ip.go +++ b/cmd/tailscale/cli/ip.go @@ -9,6 +9,7 @@ "flag" "fmt" "net/netip" + "slices" "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/ipn/ipnstate" @@ -114,17 +115,13 @@ func peerMatchingIP(st *ipnstate.Status, ipStr string) (ps *ipnstate.PeerStatus, return } for _, ps = range st.Peer { - for _, pip := range ps.TailscaleIPs { - if ip == pip { - return ps, true - } + if slices.Contains(ps.TailscaleIPs, ip) { + return ps, true } } if ps := st.Self; ps != nil { - for _, pip := range ps.TailscaleIPs { - if ip == pip { - return ps, true - } + if slices.Contains(ps.TailscaleIPs, ip) { + return ps, true } } return nil, false diff --git a/cmd/tailscale/cli/risks.go b/cmd/tailscale/cli/risks.go index 1bd128d56..6f3ebf37b 100644 --- a/cmd/tailscale/cli/risks.go +++ b/cmd/tailscale/cli/risks.go @@ -39,7 +39,7 @@ func registerAcceptRiskFlag(f *flag.FlagSet, acceptedRisks *string) { // isRiskAccepted reports whether riskType is in the comma-separated list of // risks in acceptedRisks. func isRiskAccepted(riskType, acceptedRisks string) bool { - for _, r := range strings.Split(acceptedRisks, ",") { + for r := range strings.SplitSeq(acceptedRisks, ",") { if r == riskType || r == riskAll { return true } diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 9ac303c79..13f5c09b8 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -114,8 +114,8 @@ func (u *acceptAppCapsFlag) Set(s string) error { if s == "" { return nil } - appCaps := strings.Split(s, ",") - for _, appCap := range appCaps { + appCaps := strings.SplitSeq(s, ",") + for appCap := range appCaps { appCap = strings.TrimSpace(appCap) if !validAppCap.MatchString(appCap) { return fmt.Errorf("%q does not match the form {domain}/{name}, where domain must be a fully qualified domain name", appCap) diff --git a/cmd/tailscale/cli/set.go b/cmd/tailscale/cli/set.go index feccf6d12..b12b7de49 100644 --- a/cmd/tailscale/cli/set.go +++ b/cmd/tailscale/cli/set.go @@ -183,8 +183,7 @@ func runSet(ctx context.Context, args []string) (retErr error) { maskedPrefs.AutoExitNode = expr maskedPrefs.AutoExitNodeSet = true } else if err := maskedPrefs.Prefs.SetExitNodeIP(setArgs.exitNodeIP, st); err != nil { - var e ipn.ExitNodeLocalIPError - if errors.As(err, &e) { + if _, ok := errors.AsType[ipn.ExitNodeLocalIPError](err); ok { return fmt.Errorf("%w; did you mean --advertise-exit-node?", err) } return err @@ -251,8 +250,8 @@ func runSet(ctx context.Context, args []string) (retErr error) { if setArgs.relayServerStaticEndpoints != "" { endpointsSet := make(set.Set[netip.AddrPort]) - endpointsSplit := strings.Split(setArgs.relayServerStaticEndpoints, ",") - for _, s := range endpointsSplit { + endpointsSplit := strings.SplitSeq(setArgs.relayServerStaticEndpoints, ",") + for s := range endpointsSplit { ap, err := netip.ParseAddrPort(s) if err != nil { return fmt.Errorf("failed to set relay server static endpoints: %q is not a valid IP:port", s) diff --git a/cmd/tailscale/cli/ssh.go b/cmd/tailscale/cli/ssh.go index bea18f7ab..9efab8cf7 100644 --- a/cmd/tailscale/cli/ssh.go +++ b/cmd/tailscale/cli/ssh.go @@ -14,6 +14,7 @@ "os/user" "path/filepath" "runtime" + "slices" "strings" "github.com/peterbourgon/ff/v3/ffcli" @@ -202,10 +203,8 @@ func peerStatusFromArg(st *ipnstate.Status, arg string) (*ipnstate.PeerStatus, b argIP, _ := netip.ParseAddr(arg) for _, ps := range st.Peer { if argIP.IsValid() { - for _, ip := range ps.TailscaleIPs { - if ip == argIP { - return ps, true - } + if slices.Contains(ps.TailscaleIPs, argIP) { + return ps, true } continue } @@ -230,10 +229,8 @@ func nodeDNSNameFromArg(st *ipnstate.Status, arg string) (dnsName string, ok boo for _, ps := range st.Peer { dnsName = ps.DNSName if argIP.IsValid() { - for _, ip := range ps.TailscaleIPs { - if ip == argIP { - return dnsName, true - } + if slices.Contains(ps.TailscaleIPs, argIP) { + return dnsName, true } continue } diff --git a/cmd/tailscale/cli/ssh_exec_windows.go b/cmd/tailscale/cli/ssh_exec_windows.go index 85e151817..f9d306463 100644 --- a/cmd/tailscale/cli/ssh_exec_windows.go +++ b/cmd/tailscale/cli/ssh_exec_windows.go @@ -28,9 +28,8 @@ func execSSH(ssh string, argv []string) error { cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - var ee *exec.ExitError err := cmd.Run() - if errors.As(err, &ee) { + if ee, ok := errors.AsType[*exec.ExitError](err); ok { os.Exit(ee.ExitCode()) } return err diff --git a/cmd/tailscale/cli/ssh_unix.go b/cmd/tailscale/cli/ssh_unix.go index 768d71116..1cc3ccbe8 100644 --- a/cmd/tailscale/cli/ssh_unix.go +++ b/cmd/tailscale/cli/ssh_unix.go @@ -39,7 +39,7 @@ func init() { return "" } prefix := []byte("SSH_CLIENT=") - for _, env := range bytes.Split(b, []byte{0}) { + for env := range bytes.SplitSeq(b, []byte{0}) { if bytes.HasPrefix(env, prefix) { return string(env[len(prefix):]) } diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index 79cc60ca2..81c67d662 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -334,8 +334,7 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo if expr, useAutoExitNode := ipn.ParseAutoExitNodeString(upArgs.exitNodeIP); useAutoExitNode { prefs.AutoExitNode = expr } else if err := prefs.SetExitNodeIP(upArgs.exitNodeIP, st); err != nil { - var e ipn.ExitNodeLocalIPError - if errors.As(err, &e) { + if _, ok := errors.AsType[ipn.ExitNodeLocalIPError](err); ok { return nil, fmt.Errorf("%w; did you mean --advertise-exit-node?", err) } return nil, err @@ -912,7 +911,7 @@ func addPrefFlagMapping(flagName string, prefNames ...string) { prefType := reflect.TypeFor[ipn.Prefs]() for _, pref := range prefNames { t := prefType - for _, name := range strings.Split(pref, ".") { + for name := range strings.SplitSeq(pref, ".") { // Crash at runtime if there's a typo in the prefName. f, ok := t.FieldByName(name) if !ok { diff --git a/cmd/testwrapper/testwrapper.go b/cmd/testwrapper/testwrapper.go index e35b83407..204409a63 100644 --- a/cmd/testwrapper/testwrapper.go +++ b/cmd/testwrapper/testwrapper.go @@ -352,8 +352,7 @@ type nextRun struct { // If there's nothing to retry and no non-retryable tests have // failed then we've probably hit a build error. if err := <-runErr; len(toRetry) == 0 && err != nil { - var exit *exec.ExitError - if errors.As(err, &exit) { + if exit, ok := errors.AsType[*exec.ExitError](err); ok { if code := exit.ExitCode(); code > -1 { os.Exit(exit.ExitCode()) } diff --git a/cmd/testwrapper/testwrapper_test.go b/cmd/testwrapper/testwrapper_test.go index cf023f436..7ad78a3d0 100644 --- a/cmd/testwrapper/testwrapper_test.go +++ b/cmd/testwrapper/testwrapper_test.go @@ -273,8 +273,7 @@ func TestCached(t *testing.T) {} } func errExitCode(err error) (int, bool) { - var exit *exec.ExitError - if errors.As(err, &exit) { + if exit, ok := errors.AsType[*exec.ExitError](err); ok { return exit.ExitCode(), true } return 0, false diff --git a/cmd/tta/tta.go b/cmd/tta/tta.go index 377d01c94..dbdbf5ddf 100644 --- a/cmd/tta/tta.go +++ b/cmd/tta/tta.go @@ -91,7 +91,7 @@ func main() { if distro.Get() == distro.Gokrazy { cmdLine, _ := os.ReadFile("/proc/cmdline") explicitNS := false - for _, s := range strings.Fields(string(cmdLine)) { + for s := range strings.FieldsSeq(string(cmdLine)) { if ns, ok := strings.CutPrefix(s, "tta.nameserver="); ok { err := atomicfile.WriteFile("/tmp/resolv.conf", []byte("nameserver "+ns+"\n"), 0644) log.Printf("Wrote /tmp/resolv.conf: %v", err) diff --git a/cmd/viewer/viewer.go b/cmd/viewer/viewer.go index 56b999f5f..1c04dbb2b 100644 --- a/cmd/viewer/viewer.go +++ b/cmd/viewer/viewer.go @@ -500,8 +500,7 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, fie } writeTemplateWithComment("unsupportedField", fname) } - for i := range typ.NumMethods() { - f := typ.Method(i) + for f := range typ.Methods() { if !f.Exported() { continue } @@ -720,7 +719,7 @@ func main() { fieldComments := getFieldComments(pkg.Syntax) cloneOnlyType := map[string]bool{} - for _, t := range strings.Split(*flagCloneOnlyTypes, ",") { + for t := range strings.SplitSeq(*flagCloneOnlyTypes, ",") { cloneOnlyType[t] = true } diff --git a/control/controlclient/controlclient_test.go b/control/controlclient/controlclient_test.go index dca1d8ddf..2205a0eb3 100644 --- a/control/controlclient/controlclient_test.go +++ b/control/controlclient/controlclient_test.go @@ -38,8 +38,8 @@ ) func fieldsOf(t reflect.Type) (fields []string) { - for i := range t.NumField() { - if name := t.Field(i).Name; name != "_" { + for field := range t.Fields() { + if name := field.Name; name != "_" { fields = append(fields, name) } } @@ -214,12 +214,12 @@ func TestRetryableErrors(t *testing.T) { } type retryableForTest interface { + error Retryable() bool } func isRetryableErrorForTest(err error) bool { - var ae retryableForTest - if errors.As(err, &ae) { + if ae, ok := errors.AsType[retryableForTest](err); ok { return ae.Retryable() } return false diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 965523f95..db46a956f 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -1484,7 +1484,7 @@ func (c *Direct) answerPing(pr *tailcfg.PingRequest) { } return } - for _, t := range strings.Split(pr.Types, ",") { + for t := range strings.SplitSeq(pr.Types, ",") { switch pt := tailcfg.PingType(t); pt { case tailcfg.PingTSMP, tailcfg.PingDisco, tailcfg.PingICMP, tailcfg.PingPeerAPI: go doPingerPing(c.logf, httpc, pr, c.pinger, pt) diff --git a/control/controlclient/map.go b/control/controlclient/map.go index 29b0a0348..f33620edd 100644 --- a/control/controlclient/map.go +++ b/control/controlclient/map.go @@ -617,12 +617,12 @@ func (ms *mapSession) patchifyPeersChanged(resp *tailcfg.MapResponse) { var nodeFields = sync.OnceValue(getNodeFields) -// getNodeFields returns the fails of tailcfg.Node. +// getNodeFields returns the fields of tailcfg.Node. func getNodeFields() []string { rt := reflect.TypeFor[tailcfg.Node]() - ret := make([]string, rt.NumField()) - for i := range rt.NumField() { - ret[i] = rt.Field(i).Name + ret := make([]string, 0, rt.NumField()) + for f := range rt.Fields() { + ret = append(ret, f.Name) } return ret } diff --git a/control/controlhttp/http_test.go b/control/controlhttp/http_test.go index c02ac758e..7f0203cd0 100644 --- a/control/controlhttp/http_test.go +++ b/control/controlhttp/http_test.go @@ -814,8 +814,8 @@ func runDialPlanTest(t *testing.T, plan *tailcfg.ControlDialPlan, want []netip.A // split on "|" first to remove memnet pipe suffix addrPart := raddrStr - if idx := strings.Index(raddrStr, "|"); idx >= 0 { - addrPart = raddrStr[:idx] + if before, _, ok := strings.Cut(raddrStr, "|"); ok { + addrPart = before } host, _, err2 := net.SplitHostPort(addrPart) diff --git a/control/controlknobs/controlknobs.go b/control/controlknobs/controlknobs.go index 1861a122e..14f30d9ce 100644 --- a/control/controlknobs/controlknobs.go +++ b/control/controlknobs/controlknobs.go @@ -205,17 +205,15 @@ func (k *Knobs) AsDebugJSON() map[string]any { return nil } ret := map[string]any{} - rt := reflect.TypeFor[Knobs]() rv := reflect.ValueOf(k).Elem() // of *k - for i := 0; i < rt.NumField(); i++ { - name := rt.Field(i).Name - switch v := rv.Field(i).Addr().Interface().(type) { + for sf, fv := range rv.Fields() { + switch v := fv.Addr().Interface().(type) { case *atomic.Bool: - ret[name] = v.Load() + ret[sf.Name] = v.Load() case *syncs.AtomicValue[opt.Bool]: - ret[name] = v.Load() + ret[sf.Name] = v.Load() default: - panic(fmt.Sprintf("unknown field type %T for %v", v, name)) + panic(fmt.Sprintf("unknown field type %T for %v", v, sf.Name)) } } return ret diff --git a/derp/derp_test.go b/derp/derp_test.go index cff069dd4..f2ccefc9f 100644 --- a/derp/derp_test.go +++ b/derp/derp_test.go @@ -121,8 +121,7 @@ func TestSendRecv(t *testing.T) { } defer cin.Close() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() brwServer := bufio.NewReadWriter(bufio.NewReader(cin), bufio.NewWriter(cin)) go s.Accept(ctx, cin, brwServer, fmt.Sprintf("[abc::def]:%v", i)) @@ -331,8 +330,7 @@ func TestSendFreeze(t *testing.T) { return c, c2 } - ctx, clientCtxCancel := context.WithCancel(context.Background()) - defer clientCtxCancel() + ctx := t.Context() aliceKey := key.NewNode() aliceClient, aliceConn := newClient(ctx, "alice", aliceKey) @@ -716,8 +714,7 @@ func (c *testClient) close(t *testing.T) { // TestWatch tests the connection watcher mechanism used by regional // DERP nodes to mesh up with each other. func TestWatch(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() ts := newTestServer(t, ctx) defer ts.close(t) @@ -764,8 +761,7 @@ func waitConnect(t testing.TB, c *Client) { } func TestServerRepliesToPing(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() ts := newTestServer(t, ctx) defer ts.close(t) diff --git a/derp/derphttp/derphttp_test.go b/derp/derphttp/derphttp_test.go index ae530c93a..304906b74 100644 --- a/derp/derphttp/derphttp_test.go +++ b/derp/derphttp/derphttp_test.go @@ -299,9 +299,7 @@ func TestBreakWatcherConnRecv(t *testing.T) { errChan := make(chan error, 1) // Start the watcher thread (which connects to the watched server) - wg.Add(1) // To avoid using t.Logf after the test ends. See https://golang.org/issue/40343 - go func() { - defer wg.Done() + wg.Go(func() { var peers int add := func(m derp.PeerPresentMessage) { t.Logf("add: %v", m.Key.ShortString()) @@ -318,7 +316,7 @@ func TestBreakWatcherConnRecv(t *testing.T) { } watcher.RunWatchConnectionLoop(ctx, serverPrivateKey1.Public(), t.Logf, add, remove, notifyErr) - }() + }) synctest.Wait() @@ -381,9 +379,7 @@ func TestBreakWatcherConn(t *testing.T) { errorChan := make(chan error, 1) // Start the watcher thread (which connects to the watched server) - wg.Add(1) // To avoid using t.Logf after the test ends. See https://golang.org/issue/40343 - go func() { - defer wg.Done() + wg.Go(func() { var peers int add := func(m derp.PeerPresentMessage) { t.Logf("add: %v", m.Key.ShortString()) @@ -403,7 +399,7 @@ func TestBreakWatcherConn(t *testing.T) { } watcher1.RunWatchConnectionLoop(ctx, serverPrivateKey1.Public(), t.Logf, add, remove, notifyError) - }() + }) synctest.Wait() diff --git a/derp/derpserver/derpserver.go b/derp/derpserver/derpserver.go index f311eb25d..343543c55 100644 --- a/derp/derpserver/derpserver.go +++ b/derp/derpserver/derpserver.go @@ -30,6 +30,7 @@ "os" "os/exec" "runtime" + "slices" "strconv" "strings" "sync" @@ -71,7 +72,7 @@ func init() { if keys == "" { return } - for _, keyStr := range strings.Split(keys, ",") { + for keyStr := range strings.SplitSeq(keys, ",") { k, err := key.ParseNodePublicUntyped(mem.S(keyStr)) if err != nil { log.Printf("ignoring invalid debug key %q: %v", keyStr, err) @@ -1287,7 +1288,7 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error { if disco.LooksLikeDiscoWrapper(p.bs) { sendQueue = dst.discoSendQueue } - for attempt := 0; attempt < 3; attempt++ { + for attempt := range 3 { select { case <-dst.done: s.recordDrop(p.bs, c.key, dstKey, dropReasonGoneDisconnected) @@ -1484,16 +1485,13 @@ func (s *Server) noteClientActivity(c *sclient) { // If we saw this connection send previously, then consider // the group fighting and disable them all. if s.dupPolicy == disableFighters { - for _, prior := range dup.sendHistory { - if prior == c { - cs.ForeachClient(func(c *sclient) { - c.isDisabled.Store(true) - if cs.activeClient.Load() == c { - cs.activeClient.Store(nil) - } - }) - break - } + if slices.Contains(dup.sendHistory, c) { + cs.ForeachClient(func(c *sclient) { + c.isDisabled.Store(true) + if cs.activeClient.Load() == c { + cs.activeClient.Store(nil) + } + }) } } diff --git a/derp/derpserver/derpserver_test.go b/derp/derpserver/derpserver_test.go index 3a778d59f..7f956ba78 100644 --- a/derp/derpserver/derpserver_test.go +++ b/derp/derpserver/derpserver_test.go @@ -627,22 +627,17 @@ func BenchmarkConcurrentStreams(b *testing.B) { if err != nil { b.Fatal(err) } - defer ln.Close() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := b.Context() + acceptDone := make(chan struct{}) go func() { - for ctx.Err() == nil { + defer close(acceptDone) + for { connIn, err := ln.Accept() if err != nil { - if ctx.Err() != nil { - return - } - b.Error(err) return } - brwServer := bufio.NewReadWriter(bufio.NewReader(connIn), bufio.NewWriter(connIn)) go s.Accept(ctx, connIn, brwServer, "test-client") } @@ -680,6 +675,9 @@ func BenchmarkConcurrentStreams(b *testing.B) { } } }) + + ln.Close() + <-acceptDone } func BenchmarkSendRecv(b *testing.B) { @@ -769,7 +767,7 @@ func TestServeDebugTrafficUniqueSenders(t *testing.T) { senderCardinality: hyperloglog.New(), } - for i := 0; i < 5; i++ { + for range 5 { c.senderCardinality.Insert(key.NewNode().Public().AppendTo(nil)) } @@ -845,7 +843,7 @@ func TestSenderCardinality(t *testing.T) { t.Errorf("EstimatedUniqueSenders() = %d, want ~10 (8-12 range)", estimate) } - for i := 0; i < 5; i++ { + for i := range 5 { c.senderCardinality.Insert(senders[i].AppendTo(nil)) } @@ -869,7 +867,7 @@ func TestSenderCardinality100(t *testing.T) { } numSenders := 100 - for i := 0; i < numSenders; i++ { + for range numSenders { c.senderCardinality.Insert(key.NewNode().Public().AppendTo(nil)) } @@ -945,7 +943,7 @@ func BenchmarkHyperLogLogInsertUnique(b *testing.B) { func BenchmarkHyperLogLogEstimate(b *testing.B) { hll := hyperloglog.New() - for i := 0; i < 100; i++ { + for range 100 { hll.Insert(key.NewNode().Public().AppendTo(nil)) } diff --git a/derp/xdp/xdp_linux.go b/derp/xdp/xdp_linux.go index 5d22716be..7ab23bd2e 100644 --- a/derp/xdp/xdp_linux.go +++ b/derp/xdp/xdp_linux.go @@ -62,8 +62,7 @@ func NewSTUNServer(config *STUNServerConfig, opts ...STUNServerOption) (*STUNSer objs := new(bpfObjects) err = loadBpfObjects(objs, nil) if err != nil { - var ve *ebpf.VerifierError - if config.FullVerifierErr && errors.As(err, &ve) { + if ve, ok := errors.AsType[*ebpf.VerifierError](err); config.FullVerifierErr && ok { err = fmt.Errorf("verifier error: %+v", ve) } return nil, fmt.Errorf("error loading XDP program: %w", err) diff --git a/disco/disco.go b/disco/disco.go index 2147529d1..8f667b262 100644 --- a/disco/disco.go +++ b/disco/disco.go @@ -475,7 +475,7 @@ type AllocateUDPRelayEndpointRequest struct { func (m *AllocateUDPRelayEndpointRequest) AppendMarshal(b []byte) []byte { ret, p := appendMsgHeader(b, TypeAllocateUDPRelayEndpointRequest, v0, allocateUDPRelayEndpointRequestLen) - for i := 0; i < len(m.ClientDisco); i++ { + for i := range len(m.ClientDisco) { disco := m.ClientDisco[i].AppendTo(nil) copy(p, disco) p = p[key.DiscoPublicRawLen:] @@ -492,7 +492,7 @@ func parseAllocateUDPRelayEndpointRequest(ver uint8, p []byte) (m *AllocateUDPRe if len(p) < allocateUDPRelayEndpointRequestLen { return m, errShort } - for i := 0; i < len(m.ClientDisco); i++ { + for i := range len(m.ClientDisco) { m.ClientDisco[i] = key.DiscoPublicFromRaw32(mem.B(p[:key.DiscoPublicRawLen])) p = p[key.DiscoPublicRawLen:] } @@ -565,7 +565,7 @@ func (m *UDPRelayEndpoint) encode(b []byte) { disco := m.ServerDisco.AppendTo(nil) copy(b, disco) b = b[key.DiscoPublicRawLen:] - for i := 0; i < len(m.ClientDisco); i++ { + for i := range len(m.ClientDisco) { disco = m.ClientDisco[i].AppendTo(nil) copy(b, disco) b = b[key.DiscoPublicRawLen:] @@ -594,7 +594,7 @@ func (m *UDPRelayEndpoint) decode(b []byte) error { } m.ServerDisco = key.DiscoPublicFromRaw32(mem.B(b[:key.DiscoPublicRawLen])) b = b[key.DiscoPublicRawLen:] - for i := 0; i < len(m.ClientDisco); i++ { + for i := range len(m.ClientDisco) { m.ClientDisco[i] = key.DiscoPublicFromRaw32(mem.B(b[:key.DiscoPublicRawLen])) b = b[key.DiscoPublicRawLen:] } diff --git a/docs/webhooks/example.go b/docs/webhooks/example.go index 53ec1c8b7..d93c425d2 100644 --- a/docs/webhooks/example.go +++ b/docs/webhooks/example.go @@ -87,7 +87,7 @@ func verifyWebhookSignature(req *http.Request, secret string) (events []event, e return nil, err } mac := hmac.New(sha256.New, []byte(secret)) - mac.Write([]byte(fmt.Sprint(timestamp.Unix()))) + mac.Write(fmt.Append(nil, timestamp.Unix())) mac.Write([]byte(".")) mac.Write(b) want := hex.EncodeToString(mac.Sum(nil)) @@ -120,8 +120,8 @@ func parseSignatureHeader(header string) (timestamp time.Time, signatures map[st } signatures = make(map[string][]string) - pairs := strings.Split(header, ",") - for _, pair := range pairs { + pairs := strings.SplitSeq(header, ",") + for pair := range pairs { parts := strings.Split(pair, "=") if len(parts) != 2 { return time.Time{}, nil, errNotSigned diff --git a/drive/driveimpl/compositedav/rewriting.go b/drive/driveimpl/compositedav/rewriting.go index 47f020461..1f0a69d75 100644 --- a/drive/driveimpl/compositedav/rewriting.go +++ b/drive/driveimpl/compositedav/rewriting.go @@ -63,7 +63,7 @@ func (h *Handler) delegateRewriting(w http.ResponseWriter, r *http.Request, path // Fixup paths to add the requested path as a prefix, escaped for inclusion in XML. pp := shared.EscapeForXML(shared.Join(pathComponents[0:mpl]...)) - b := responseHrefRegex.ReplaceAll(bw.buf.Bytes(), []byte(fmt.Sprintf("$1%s/$3", pp))) + b := responseHrefRegex.ReplaceAll(bw.buf.Bytes(), fmt.Appendf(nil, "$1%s/$3", pp)) return bw.status, b } diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index 05f087e21..64fa93394 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -12,6 +12,7 @@ "errors" "net/http" "net/netip" + "slices" "sync" "go4.org/netipx" @@ -329,13 +330,7 @@ func configFromNodeView(n tailcfg.NodeView) (config, error) { selfRoutedDomains: set.Set[dnsname.FQDN]{}, } for _, app := range apps { - selfMatchesTags := false - for _, tag := range app.Connectors { - if selfTags.Contains(tag) { - selfMatchesTags = true - break - } - } + selfMatchesTags := slices.ContainsFunc(app.Connectors, selfTags.Contains) for _, d := range app.Domains { fqdn, err := dnsname.ToFQDN(d) if err != nil { diff --git a/feature/conn25/conn25_test.go b/feature/conn25/conn25_test.go index d63e84e02..7ed5c13b2 100644 --- a/feature/conn25/conn25_test.go +++ b/feature/conn25/conn25_test.go @@ -115,7 +115,7 @@ func TestHandleConnectorTransitIPRequestMultipleTIP(t *testing.T) { t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) } - for i := 0; i < 3; i++ { + for i := range 3 { got := resp.TransitIPs[i].Code if got != TransitIPResponseCode(0) { t.Fatalf("i=%d TransitIP Code: %d, want 0", i, got) diff --git a/feature/identityfederation/identityfederation.go b/feature/identityfederation/identityfederation.go index 4b96fd6a2..51a8018d8 100644 --- a/feature/identityfederation/identityfederation.go +++ b/feature/identityfederation/identityfederation.go @@ -128,8 +128,7 @@ func exchangeJWTForToken(ctx context.Context, baseURL, clientID, idToken string) }).Exchange(ctx, "", oauth2.SetAuthURLParam("client_id", clientID), oauth2.SetAuthURLParam("jwt", idToken)) if err != nil { // Try to extract more detailed error message - var retrieveErr *oauth2.RetrieveError - if errors.As(err, &retrieveErr) { + if retrieveErr, ok := errors.AsType[*oauth2.RetrieveError](err); ok { return "", fmt.Errorf("token exchange failed with status %d: %s", retrieveErr.Response.StatusCode, string(retrieveErr.Body)) } return "", fmt.Errorf("unexpected token exchange request error: %w", err) diff --git a/feature/linuxdnsfight/linuxdnsfight_test.go b/feature/linuxdnsfight/linuxdnsfight_test.go index 661ba7f6f..ce67353db 100644 --- a/feature/linuxdnsfight/linuxdnsfight_test.go +++ b/feature/linuxdnsfight/linuxdnsfight_test.go @@ -42,7 +42,7 @@ func TestWatchFile(t *testing.T) { // Keep writing until we get a callback. func() { for i := range 10000 { - if err := os.WriteFile(filepath, []byte(fmt.Sprintf("write%d", i)), 0644); err != nil { + if err := os.WriteFile(filepath, fmt.Appendf(nil, "write%d", i), 0644); err != nil { t.Fatal(err) } select { diff --git a/feature/taildrop/fileops_fs.go b/feature/taildrop/fileops_fs.go index 4a5b3e71a..3ddf95d03 100644 --- a/feature/taildrop/fileops_fs.go +++ b/feature/taildrop/fileops_fs.go @@ -101,7 +101,7 @@ func (f fsFileOps) Rename(oldPath, newName string) (newPath string, err error) { wantSize := st.Size() const maxRetries = 10 - for i := 0; i < maxRetries; i++ { + for range maxRetries { renameMu.Lock() fi, statErr := os.Stat(dst) // Atomically rename the partial file as the destination file if it doesn't exist. diff --git a/health/health_test.go b/health/health_test.go index 953c4dca2..824d53f7a 100644 --- a/health/health_test.go +++ b/health/health_test.go @@ -82,8 +82,7 @@ func TestAppendWarnableDebugFlags(t *testing.T) { func TestNilMethodsDontCrash(t *testing.T) { var nilt *Tracker rv := reflect.ValueOf(nilt) - for i := 0; i < rv.NumMethod(); i++ { - mt := rv.Type().Method(i) + for mt, method := range rv.Methods() { t.Logf("calling Tracker.%s ...", mt.Name) var args []reflect.Value for j := 0; j < mt.Type.NumIn(); j++ { @@ -92,7 +91,7 @@ func TestNilMethodsDontCrash(t *testing.T) { } args = append(args, reflect.Zero(mt.Type.In(j))) } - rv.Method(i).Call(args) + method.Call(args) } } diff --git a/hostinfo/hostinfo_linux.go b/hostinfo/hostinfo_linux.go index 77f47ffe2..6b21d8152 100644 --- a/hostinfo/hostinfo_linux.go +++ b/hostinfo/hostinfo_linux.go @@ -68,7 +68,7 @@ func deviceModelLinux() string { } func getQnapQtsVersion(versionInfo string) string { - for _, field := range strings.Fields(versionInfo) { + for field := range strings.FieldsSeq(versionInfo) { if suffix, ok := strings.CutPrefix(field, "QTSFW_"); ok { return suffix } @@ -110,11 +110,11 @@ func linuxVersionMeta() (meta versionMeta) { if err != nil { break } - eq := bytes.IndexByte(line, '=') - if eq == -1 { + before, after, ok := bytes.Cut(line, []byte{'='}) + if !ok { continue } - k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"'`) + k, v := string(before), strings.Trim(string(after), `"'`) m[k] = v } diff --git a/ipn/auditlog/auditlog.go b/ipn/auditlog/auditlog.go index cc6b43cbd..0d6bd278d 100644 --- a/ipn/auditlog/auditlog.go +++ b/ipn/auditlog/auditlog.go @@ -69,8 +69,11 @@ type Opts struct { // IsRetryableError returns true if the given error is retryable // See [controlclient.apiResponseError]. Potentially retryable errors implement the Retryable() method. func IsRetryableError(err error) bool { - var retryable interface{ Retryable() bool } - return errors.As(err, &retryable) && retryable.Retryable() + retryable, ok := errors.AsType[interface { + error + Retryable() bool + }](err) + return ok && retryable.Retryable() } type backoffOpts struct { diff --git a/ipn/ipnlocal/breaktcp_linux.go b/ipn/ipnlocal/breaktcp_linux.go index 0ba9ed6d7..1d7ea0f31 100644 --- a/ipn/ipnlocal/breaktcp_linux.go +++ b/ipn/ipnlocal/breaktcp_linux.go @@ -15,7 +15,7 @@ func init() { func breakTCPConnsLinux() error { var matched int - for fd := 0; fd < 1000; fd++ { + for fd := range 1000 { _, err := unix.GetsockoptTCPInfo(fd, unix.IPPROTO_TCP, unix.TCP_INFO) if err == nil { matched++ diff --git a/ipn/ipnlocal/bus_test.go b/ipn/ipnlocal/bus_test.go index 27ffebcdd..47d13f305 100644 --- a/ipn/ipnlocal/bus_test.go +++ b/ipn/ipnlocal/bus_test.go @@ -36,9 +36,8 @@ func TestIsNotableNotify(t *testing.T) { // We use reflect to catch fields that might be added in the future without // remembering to update the [isNotableNotify] function. rt := reflect.TypeFor[ipn.Notify]() - for i := range rt.NumField() { + for sf := range rt.Fields() { n := &ipn.Notify{} - sf := rt.Field(i) switch sf.Name { case "_", "NetMap", "Engine", "Version": // Already covered above or not applicable. @@ -46,7 +45,7 @@ func TestIsNotableNotify(t *testing.T) { case "DriveShares": n.DriveShares = views.SliceOfViews[*drive.Share, drive.ShareView](make([]*drive.Share, 1)) default: - rf := reflect.ValueOf(n).Elem().Field(i) + rf := reflect.ValueOf(n).Elem().FieldByIndex(sf.Index) switch rf.Kind() { case reflect.Pointer: rf.Set(reflect.New(rf.Type().Elem())) @@ -64,7 +63,7 @@ func TestIsNotableNotify(t *testing.T) { notify *ipn.Notify want bool }{ - name: "field-" + rt.Field(i).Name, + name: "field-" + sf.Name, notify: n, want: true, }) diff --git a/ipn/ipnlocal/extension_host.go b/ipn/ipnlocal/extension_host.go index 125a23294..7264d7407 100644 --- a/ipn/ipnlocal/extension_host.go +++ b/ipn/ipnlocal/extension_host.go @@ -339,7 +339,7 @@ func (h *ExtensionHost) FindMatchingExtension(target any) bool { val := reflect.ValueOf(target) typ := val.Type() - if typ.Kind() != reflect.Ptr || val.IsNil() { + if typ.Kind() != reflect.Pointer || val.IsNil() { panic("ipnext: target must be a non-nil pointer") } targetType := typ.Elem() diff --git a/ipn/ipnlocal/extension_host_test.go b/ipn/ipnlocal/extension_host_test.go index 3bd302aea..a22c5156c 100644 --- a/ipn/ipnlocal/extension_host_test.go +++ b/ipn/ipnlocal/extension_host_test.go @@ -1010,9 +1010,8 @@ func TestNilExtensionHostMethodCall(t *testing.T) { t.Parallel() var h *ExtensionHost - typ := reflect.TypeOf(h) - for i := range typ.NumMethod() { - m := typ.Method(i) + typ := reflect.TypeFor[*ExtensionHost]() + for m := range typ.Methods() { if strings.HasSuffix(m.Name, "ForTest") { // Skip methods that are only for testing. continue diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index b8f355039..5f694e915 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -3687,12 +3687,7 @@ func generateInterceptTCPPortFunc(ports []uint16) func(uint16) bool { f = func(p uint16) bool { return m[p] } } else { f = func(p uint16) bool { - for _, x := range ports { - if p == x { - return true - } - } - return false + return slices.Contains(ports, p) } } } @@ -7387,10 +7382,8 @@ func (b *LocalBackend) seamlessRenewalEnabled() bool { // allowedAutoRoute determines if the route being added via AdvertiseRoute (the app connector featuge) should be allowed. func allowedAutoRoute(ipp netip.Prefix) bool { // Note: blocking the addrs for globals, not solely the prefixes. - for _, addr := range disallowedAddrs { - if ipp.Addr() == addr { - return false - } + if slices.Contains(disallowedAddrs, ipp.Addr()) { + return false } for _, pfx := range disallowedRanges { if pfx.Overlaps(ipp) { diff --git a/ipn/ipnlocal/netmapcache/netmapcache_test.go b/ipn/ipnlocal/netmapcache/netmapcache_test.go index b5a46d298..ca66a1713 100644 --- a/ipn/ipnlocal/netmapcache/netmapcache_test.go +++ b/ipn/ipnlocal/netmapcache/netmapcache_test.go @@ -275,7 +275,7 @@ func TestInvalidCache(t *testing.T) { func checkFieldCoverage(t *testing.T, nm *netmap.NetworkMap) { t.Helper() - mt := reflect.TypeOf(nm).Elem() + mt := reflect.TypeFor[netmap.NetworkMap]() mv := reflect.ValueOf(nm).Elem() for i := 0; i < mt.NumField(); i++ { f := mt.Field(i) diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index aa4c1ef52..322884fc7 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -103,7 +103,7 @@ func (s *peerAPIServer) listen(ip netip.Addr, tunIfIndex int) (ln net.Listener, // deterministic that people will bake this into clients. // We try a few times just in case something's already // listening on that port (on all interfaces, probably). - for try := uint8(0); try < 5; try++ { + for try := range uint8(5) { a16 := ip.As16() hashData := a16[len(a16)-3:] hashData[0] += try diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index d25251acc..9460896ad 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -835,8 +835,8 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (http.Handler, err targetURL, insecure := expandProxyArg(backend) // Handle unix: scheme specially - if strings.HasPrefix(targetURL, "unix:") { - socketPath := strings.TrimPrefix(targetURL, "unix:") + if after, ok := strings.CutPrefix(targetURL, "unix:"); ok { + socketPath := after if socketPath == "" { return nil, fmt.Errorf("empty unix socket path") } diff --git a/ipn/ipnlocal/ssh.go b/ipn/ipnlocal/ssh.go index 52b306658..56a6d60cc 100644 --- a/ipn/ipnlocal/ssh.go +++ b/ipn/ipnlocal/ssh.go @@ -101,9 +101,9 @@ func (b *LocalBackend) getSSHUsernames(req *tailcfg.C2NSSHUsernamesRequest) (*ta mem.HasSuffix(mem.B(line), mem.S("/false")) { continue } - colon := bytes.IndexByte(line, ':') - if colon != -1 { - add(string(line[:colon])) + before, _, ok := bytes.Cut(line, []byte{':'}) + if ok { + add(string(before)) } } } diff --git a/ipn/localapi/debug.go b/ipn/localapi/debug.go index d1348abaa..36fce16ac 100644 --- a/ipn/localapi/debug.go +++ b/ipn/localapi/debug.go @@ -142,14 +142,11 @@ type result struct { var wg sync.WaitGroup for _, dialer := range dialers { - dialer := dialer // loop capture - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { conn, err := dialer.dial(ctx, network, addr) results <- result{dialer.name, conn, err} - }() + }) } wg.Wait() diff --git a/ipn/prefs.go b/ipn/prefs.go index 72e0cf8b7..1492bae38 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -439,12 +439,11 @@ func applyPrefsEdits(src, dst reflect.Value, mask map[string]reflect.Value) { func maskFields(v reflect.Value) map[string]reflect.Value { mask := make(map[string]reflect.Value) - for i := range v.NumField() { - f := v.Type().Field(i).Name - if !strings.HasSuffix(f, "Set") { + for sf, fv := range v.Fields() { + if !strings.HasSuffix(sf.Name, "Set") { continue } - mask[strings.TrimSuffix(f, "Set")] = v.Field(i) + mask[strings.TrimSuffix(sf.Name, "Set")] = fv } return mask } @@ -845,22 +844,15 @@ func (p *Prefs) SetAdvertiseExitNode(runExit bool) { // Tailscale IP. func peerWithTailscaleIP(st *ipnstate.Status, ip netip.Addr) (ps *ipnstate.PeerStatus, ok bool) { for _, ps := range st.Peer { - for _, ip2 := range ps.TailscaleIPs { - if ip == ip2 { - return ps, true - } + if slices.Contains(ps.TailscaleIPs, ip) { + return ps, true } } return nil, false } func isRemoteIP(st *ipnstate.Status, ip netip.Addr) bool { - for _, selfIP := range st.TailscaleIPs { - if ip == selfIP { - return false - } - } - return true + return !slices.Contains(st.TailscaleIPs, ip) } // ClearExitNode sets the ExitNodeID and ExitNodeIP to their zero values. diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 347a91e50..24c8f194e 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -27,8 +27,8 @@ ) func fieldsOf(t reflect.Type) (fields []string) { - for i := range t.NumField() { - fields = append(fields, t.Field(i).Name) + for field := range t.Fields() { + fields = append(fields, field.Name) } return } diff --git a/ipn/serve.go b/ipn/serve.go index 911b408b6..21d15ab81 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -673,7 +673,7 @@ func CheckFunnelPort(wantedPort uint16, node *ipnstate.PeerStatus) error { return deny("") } wantedPortString := strconv.Itoa(int(wantedPort)) - for _, ps := range strings.Split(portsStr, ",") { + for ps := range strings.SplitSeq(portsStr, ",") { if ps == "" { continue } diff --git a/ipn/store/awsstore/store_aws.go b/ipn/store/awsstore/store_aws.go index e06e00eb3..feb86e457 100644 --- a/ipn/store/awsstore/store_aws.go +++ b/ipn/store/awsstore/store_aws.go @@ -189,8 +189,7 @@ func (s *awsStore) LoadState() error { ) if err != nil { - var pnf *ssmTypes.ParameterNotFound - if errors.As(err, &pnf) { + if _, ok := errors.AsType[*ssmTypes.ParameterNotFound](err); ok { // Create the parameter as it does not exist yet // and return directly as it is defacto empty return s.persistState() diff --git a/k8s-operator/sessionrecording/spdy/frame.go b/k8s-operator/sessionrecording/spdy/frame.go index 7087db3c3..3ca661e0b 100644 --- a/k8s-operator/sessionrecording/spdy/frame.go +++ b/k8s-operator/sessionrecording/spdy/frame.go @@ -211,7 +211,7 @@ func parseHeaders(decompressor io.Reader, log *zap.SugaredLogger) (http.Header, return nil, fmt.Errorf("error determining num headers: %v", err) } h := make(http.Header, numHeaders) - for i := uint32(0); i < numHeaders; i++ { + for range numHeaders { name, err := readLenBytes() if err != nil { return nil, err @@ -224,7 +224,7 @@ func parseHeaders(decompressor io.Reader, log *zap.SugaredLogger) (http.Header, if err != nil { return nil, fmt.Errorf("error reading header data: %w", err) } - for _, v := range bytes.Split(val, headerSep) { + for v := range bytes.SplitSeq(val, headerSep) { h.Add(ns, string(v)) } } diff --git a/logtail/logtail.go b/logtail/logtail.go index ef296568d..ed3872e79 100644 --- a/logtail/logtail.go +++ b/logtail/logtail.go @@ -902,8 +902,8 @@ func parseAndRemoveLogLevel(buf []byte) (level int, cleanBuf []byte) { if bytes.Contains(buf, v2) { return 2, bytes.ReplaceAll(buf, v2, nil) } - if i := bytes.Index(buf, vJSON); i != -1 { - rest := buf[i+len(vJSON):] + if _, after, ok := bytes.Cut(buf, vJSON); ok { + rest := after if len(rest) >= 2 { v := rest[0] if v >= '0' && v <= '9' { diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index 67250ae0d..19e1eeb7a 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -86,10 +86,10 @@ func TestDrainPendingMessages(t *testing.T) { } // all of the "log line" messages usually arrive at once, but poll if needed. - body := "" + var body strings.Builder for i := 0; i <= logLines; i++ { - body += string(<-ts.uploaded) - count := strings.Count(body, "log line") + body.WriteString(string(<-ts.uploaded)) + count := strings.Count(body.String(), "log line") if count == logLines { break } diff --git a/metrics/multilabelmap.go b/metrics/multilabelmap.go index 54d41bbae..fa31819d9 100644 --- a/metrics/multilabelmap.go +++ b/metrics/multilabelmap.go @@ -63,16 +63,16 @@ func LabelString(k any) string { var sb strings.Builder sb.WriteString("{") - for i := range t.NumField() { - if i > 0 { + first := true + for ft, fv := range rv.Fields() { + if !first { sb.WriteString(",") } - ft := t.Field(i) + first = false label := ft.Tag.Get("prom") if label == "" { label = strings.ToLower(ft.Name) } - fv := rv.Field(i) switch fv.Kind() { case reflect.String: fmt.Fprintf(&sb, "%s=%q", label, fv.String()) diff --git a/net/art/stride_table_test.go b/net/art/stride_table_test.go index e797f40ee..8279a545e 100644 --- a/net/art/stride_table_test.go +++ b/net/art/stride_table_test.go @@ -19,7 +19,7 @@ func TestInversePrefix(t *testing.T) { t.Parallel() for i := range 256 { - for len := 0; len < 9; len++ { + for len := range 9 { addr := i & (0xFF << (8 - len)) idx := prefixIndex(uint8(addr), len) addr2, len2 := inversePrefixIndex(idx) diff --git a/net/captivedetection/captivedetection_test.go b/net/captivedetection/captivedetection_test.go index 2aa660d88..6b09ca0cc 100644 --- a/net/captivedetection/captivedetection_test.go +++ b/net/captivedetection/captivedetection_test.go @@ -94,8 +94,7 @@ func TestCaptivePortalRequest(t *testing.T) { now := time.Now() d.clock = func() time.Time { return now } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { @@ -133,8 +132,7 @@ func TestCaptivePortalRequest(t *testing.T) { func TestAgainstDERPHandler(t *testing.T) { d := NewDetector(t.Logf) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() s := httptest.NewServer(http.HandlerFunc(derpserver.ServeNoContent)) defer s.Close() diff --git a/net/dns/manager_linux.go b/net/dns/manager_linux.go index e68b2e7f9..392b64ba9 100644 --- a/net/dns/manager_linux.go +++ b/net/dns/manager_linux.go @@ -380,7 +380,7 @@ func isLibnssResolveUsed(env newOSConfigEnv) error { if err != nil { return fmt.Errorf("reading /etc/resolv.conf: %w", err) } - for _, line := range strings.Split(string(bs), "\n") { + for line := range strings.SplitSeq(string(bs), "\n") { fields := strings.Fields(line) if len(fields) < 2 || fields[0] != "hosts:" { continue diff --git a/net/dns/openresolv.go b/net/dns/openresolv.go index c3aaf3a69..2a4ed174e 100644 --- a/net/dns/openresolv.go +++ b/net/dns/openresolv.go @@ -82,7 +82,7 @@ func (m openresolvManager) GetBaseConfig() (OSConfig, error) { // Remove the "tailscale" snippet from the list. args := []string{"-l"} - for _, f := range strings.Split(strings.TrimSpace(string(bs)), " ") { + for f := range strings.SplitSeq(strings.TrimSpace(string(bs)), " ") { if f == "tailscale" { continue } diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 6fec32d6a..ca1599589 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -727,8 +727,7 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDe } // If we got a truncated UDP response, return that instead of an error. - var trErr truncatedResponseError - if errors.As(err, &trErr) { + if trErr, ok := errors.AsType[truncatedResponseError](err); ok { return trErr.res, nil } return nil, err diff --git a/net/dns/resolver/forwarder_test.go b/net/dns/resolver/forwarder_test.go index 6fd186c25..3ddb47433 100644 --- a/net/dns/resolver/forwarder_test.go +++ b/net/dns/resolver/forwarder_test.go @@ -328,7 +328,7 @@ func runDNSServer(tb testing.TB, opts *testDNSServerOptions, response []byte, on udpLn *net.UDPConn err error ) - for try := 0; try < tries; try++ { + for range tries { if tcpLn != nil { tcpLn.Close() tcpLn = nil @@ -392,9 +392,7 @@ func runDNSServer(tb testing.TB, opts *testDNSServerOptions, response []byte, on var wg sync.WaitGroup if opts == nil || !opts.SkipTCP { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for { conn, err := tcpLn.Accept() if err != nil { @@ -402,7 +400,7 @@ func runDNSServer(tb testing.TB, opts *testDNSServerOptions, response []byte, on } go handleConn(conn) } - }() + }) } handleUDP := func(addr netip.AddrPort, req []byte) { @@ -413,9 +411,7 @@ func runDNSServer(tb testing.TB, opts *testDNSServerOptions, response []byte, on } if opts == nil || !opts.SkipUDP { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for { buf := make([]byte, 65535) n, addr, err := udpLn.ReadFromUDPAddrPort(buf) @@ -425,7 +421,7 @@ func runDNSServer(tb testing.TB, opts *testDNSServerOptions, response []byte, on buf = buf[:n] go handleUDP(addr, buf) } - }() + }) } tb.Cleanup(func() { @@ -684,7 +680,7 @@ func makeResponseOfSize(tb testing.TB, domain string, targetSize int, includeOPT var response []byte var err error - for attempt := 0; attempt < 10; attempt++ { + for range 10 { testBuilder := dns.NewBuilder(nil, dns.Header{ Response: true, Authoritative: true, diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index d0601de7b..53f130a8a 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -16,7 +16,7 @@ "net/netip" "os" "runtime" - "sort" + "slices" "strconv" "strings" "sync" @@ -172,7 +172,7 @@ func WriteRoutes(w *bufio.Writer, routes map[dnsname.FQDN][]*dnstype.Resolver) { } kk = append(kk, k) } - sort.Slice(kk, func(i, j int) bool { return kk[i] < kk[j] }) + slices.Sort(kk) w.WriteByte('{') for i, k := range kk { if i > 0 { diff --git a/net/dns/wsl_windows.go b/net/dns/wsl_windows.go index c2400746b..1b93142f5 100644 --- a/net/dns/wsl_windows.go +++ b/net/dns/wsl_windows.go @@ -172,8 +172,7 @@ func (fs wslFS) Truncate(name string) error { return fs.WriteFile(name, nil, 064 func (fs wslFS) ReadFile(name string) ([]byte, error) { b, err := wslCombinedOutput(fs.cmd("cat", "--", name)) - var ee *exec.ExitError - if errors.As(err, &ee) && ee.ExitCode() == 1 { + if ee, ok := errors.AsType[*exec.ExitError](err); ok && ee.ExitCode() == 1 { return nil, os.ErrNotExist } return b, err diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index ebcdc4eac..a64c358c5 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -545,7 +545,7 @@ func makeProbePlanInitial(dm *tailcfg.DERPMap, ifState *netmon.State) (plan prob var p4 []probe var p6 []probe - for try := 0; try < 3; try++ { + for try := range 3 { n := reg.Nodes[try%len(reg.Nodes)] delay := time.Duration(try) * defaultInitialRetransmitTime if n.IPv4 != "none" && ((ifState.HaveV4 && nodeMight4(n)) || n.IsTestNode()) { @@ -975,13 +975,11 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe // need to close the underlying Pinger after a timeout // or when all ICMP probes are done, regardless of // whether the HTTPS probes have finished. - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { if err := c.measureAllICMPLatency(ctx, rs, need); err != nil { c.logf("[v1] measureAllICMPLatency: %v", err) } - }() + }) } wg.Add(len(need)) c.logf("netcheck: UDP is blocked, trying HTTPS") @@ -1072,9 +1070,7 @@ func (c *Client) runHTTPOnlyChecks(ctx context.Context, last *Report, rs *report if len(rg.Nodes) == 0 { continue } - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { node := rg.Nodes[0] req, _ := http.NewRequestWithContext(ctx, "HEAD", "https://"+node.HostName+"/derp/probe", nil) // One warm-up one to get HTTP connection set @@ -1099,7 +1095,7 @@ func (c *Client) runHTTPOnlyChecks(ctx context.Context, last *Report, rs *report } d := c.timeNow().Sub(t0) rs.addNodeLatency(node, netip.AddrPort{}, d) - }() + }) } wg.Wait() return nil diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index ab7f58feb..bc8f4a744 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -42,8 +42,7 @@ func TestBasic(t *testing.T) { c := newTestClient(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() if err := c.Standalone(ctx, "127.0.0.1:0"); err != nil { t.Fatal(err) @@ -124,8 +123,7 @@ func TestWorksWhenUDPBlocked(t *testing.T) { c := newTestClient(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() r, err := c.GetReport(ctx, dm, nil) if err != nil { @@ -1038,8 +1036,7 @@ func TestNoUDPNilGetReportOpts(t *testing.T) { } c := newTestClient(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() r, err := c.GetReport(ctx, dm, nil) if err != nil { diff --git a/net/neterror/neterror_linux.go b/net/neterror/neterror_linux.go index 9add4fd1d..a99452de5 100644 --- a/net/neterror/neterror_linux.go +++ b/net/neterror/neterror_linux.go @@ -12,8 +12,7 @@ func init() { shouldDisableUDPGSO = func(err error) bool { - var serr *os.SyscallError - if errors.As(err, &serr) { + if serr, ok := errors.AsType[*os.SyscallError](err); ok { // EIO is returned by udp_send_skb() if the device driver does not // have tx checksumming enabled, which is a hard requirement of // UDP_SEGMENT. See: diff --git a/net/netmon/state.go b/net/netmon/state.go index cdfa1d0fb..98ed52e5e 100644 --- a/net/netmon/state.go +++ b/net/netmon/state.go @@ -812,11 +812,8 @@ func (m *Monitor) HasCGNATInterface() (bool, error) { if hasCGNATInterface || !i.IsUp() || isTailscaleInterface(i.Name, pfxs) { return } - for _, pfx := range pfxs { - if cgnatRange.Overlaps(pfx) { - hasCGNATInterface = true - break - } + if slices.ContainsFunc(pfxs, cgnatRange.Overlaps) { + hasCGNATInterface = true } }) if err != nil { diff --git a/net/netutil/routes.go b/net/netutil/routes.go index c8212b9af..26f2de97c 100644 --- a/net/netutil/routes.go +++ b/net/netutil/routes.go @@ -41,8 +41,8 @@ func CalcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([] routeMap := map[netip.Prefix]bool{} if advertiseRoutes != "" { var default4, default6 bool - advroutes := strings.Split(advertiseRoutes, ",") - for _, s := range advroutes { + advroutes := strings.SplitSeq(advertiseRoutes, ",") + for s := range advroutes { ipp, err := netip.ParsePrefix(s) if err != nil { return nil, fmt.Errorf("%q is not a valid IP address or CIDR prefix", s) diff --git a/net/socks5/socks5.go b/net/socks5/socks5.go index 729fc8e88..f67dc1ecc 100644 --- a/net/socks5/socks5.go +++ b/net/socks5/socks5.go @@ -21,6 +21,7 @@ "io" "log" "net" + "slices" "strconv" "time" @@ -488,10 +489,8 @@ func parseClientGreeting(r io.Reader, authMethod byte) error { if err != nil { return fmt.Errorf("could not read methods") } - for _, m := range methods { - if m == authMethod { - return nil - } + if slices.Contains(methods, authMethod) { + return nil } return fmt.Errorf("no acceptable auth methods") } diff --git a/net/socks5/socks5_test.go b/net/socks5/socks5_test.go index e6ca4b68e..84ef4be7b 100644 --- a/net/socks5/socks5_test.go +++ b/net/socks5/socks5_test.go @@ -180,11 +180,11 @@ func TestUDP(t *testing.T) { const echoServerNumber = 3 echoServerListener := make([]net.PacketConn, echoServerNumber) - for i := 0; i < echoServerNumber; i++ { + for i := range echoServerNumber { echoServerListener[i] = newUDPEchoServer() } defer func() { - for i := 0; i < echoServerNumber; i++ { + for i := range echoServerNumber { _ = echoServerListener[i].Close() } }() @@ -277,10 +277,10 @@ func TestUDP(t *testing.T) { } defer socks5UDPConn.Close() - for i := 0; i < echoServerNumber; i++ { + for i := range echoServerNumber { port := echoServerListener[i].LocalAddr().(*net.UDPAddr).Port addr := socksAddr{addrType: ipv4, addr: "127.0.0.1", port: uint16(port)} - requestBody := []byte(fmt.Sprintf("Test %d", i)) + requestBody := fmt.Appendf(nil, "Test %d", i) responseBody := sendUDPAndWaitResponse(socks5UDPConn, addr, requestBody) if !bytes.Equal(requestBody, responseBody) { t.Fatalf("got: %q want: %q", responseBody, requestBody) diff --git a/net/stunserver/stunserver_test.go b/net/stunserver/stunserver_test.go index c96aea4d1..f9efe21f3 100644 --- a/net/stunserver/stunserver_test.go +++ b/net/stunserver/stunserver_test.go @@ -60,8 +60,7 @@ func TestSTUNServer(t *testing.T) { func BenchmarkServerSTUN(b *testing.B) { b.ReportAllocs() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := b.Context() s := New(ctx) s.Listen("localhost:0") diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index 2f5d8c1d1..6fe992575 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -1406,11 +1406,11 @@ func (t *Wrapper) InjectInboundPacketBuffer(pkt *netstack_PacketBuffer, buffs [] return err } } - for i := 0; i < n; i++ { + for i := range n { buffs[i] = buffs[i][:PacketStartOffset+sizes[i]] } defer func() { - for i := 0; i < n; i++ { + for i := range n { buffs[i] = buffs[i][:cap(buffs[i])] } }() diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index bd29489a8..57b300513 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -95,7 +95,7 @@ func tcp4syn(src, dst string, sport, dport uint16) []byte { func nets(nets ...string) (ret []netip.Prefix) { for _, s := range nets { - if i := strings.IndexByte(s, '/'); i == -1 { + if found := strings.Contains(s, "/"); !found { ip, err := netip.ParseAddr(s) if err != nil { panic(err) @@ -122,13 +122,13 @@ func ports(s string) filter.PortRange { } var fs, ls string - i := strings.IndexByte(s, '-') - if i == -1 { + before, after, ok := strings.Cut(s, "-") + if !ok { fs = s ls = fs } else { - fs = s[:i] - ls = s[i+1:] + fs = before + ls = after } first, err := strconv.ParseInt(fs, 10, 16) if err != nil { diff --git a/net/udprelay/server.go b/net/udprelay/server.go index 03d8e3dc3..7dd89920e 100644 --- a/net/udprelay/server.go +++ b/net/udprelay/server.go @@ -977,7 +977,7 @@ func (e ErrServerNotReady) Error() string { // For now, we favor simplicity and reducing VNI re-use over more complex // ephemeral port (VNI) selection algorithms. func (s *Server) getNextVNILocked() (uint32, error) { - for i := uint32(0); i < totalPossibleVNI; i++ { + for range totalPossibleVNI { vni := s.nextVNI if vni == maxVNI { s.nextVNI = minVNI diff --git a/net/udprelay/server_test.go b/net/udprelay/server_test.go index 66de0d88a..204e365bc 100644 --- a/net/udprelay/server_test.go +++ b/net/udprelay/server_test.go @@ -265,7 +265,7 @@ func TestServer(t *testing.T) { tcB := newTestClient(t, endpoint.VNI, tcBServerEndpointAddr, discoB, discoA.Public(), endpoint.ServerDisco) defer tcB.close() - for i := 0; i < 2; i++ { + for range 2 { // We handshake both clients twice to guarantee server-side // packet reading goroutines, which are independent across // address families, have seen an answer from both clients @@ -345,7 +345,7 @@ func TestServer_getNextVNILocked(t *testing.T) { s := &Server{ nextVNI: minVNI, } - for i := uint64(0); i < uint64(totalPossibleVNI); i++ { + for range uint64(totalPossibleVNI) { vni, err := s.getNextVNILocked() if err != nil { // using quicktest here triples test time t.Fatal(err) diff --git a/prober/prober.go b/prober/prober.go index 3a43401a1..40eef2faf 100644 --- a/prober/prober.go +++ b/prober/prober.go @@ -122,12 +122,8 @@ func (p *Prober) Run(name string, interval time.Duration, labels Labels, pc Prob "name": name, "class": pc.Class, } - for k, v := range pc.Labels { - lb[k] = v - } - for k, v := range labels { - lb[k] = v - } + maps.Copy(lb, pc.Labels) + maps.Copy(lb, labels) probe := newProbe(p, name, interval, lb, pc) p.probes[name] = probe diff --git a/ssh/tailssh/incubator.go b/ssh/tailssh/incubator.go index b414ce3fb..28316b04d 100644 --- a/ssh/tailssh/incubator.go +++ b/ssh/tailssh/incubator.go @@ -158,8 +158,7 @@ func (ss *sshSession) newIncubatorCommand(logf logger.Logf) (cmd *exec.Cmd, err cmd.Dir = "/" case errors.Is(err, fs.ErrPermission) || errors.Is(err, fs.ErrNotExist): // Ensure that cmd.Dir is the source of the error. - var pathErr *fs.PathError - if errors.As(err, &pathErr) && pathErr.Path == cmd.Dir { + if pathErr, ok := errors.AsType[*fs.PathError](err); ok && pathErr.Path == cmd.Dir { // If we cannot run loginShell in localUser.HomeDir, // we will try to run this command in the root directory. cmd.Dir = "/" @@ -312,7 +311,7 @@ func parseIncubatorArgs(args []string) (incubatorArgs, error) { flags.StringVar(&ia.encodedEnv, "encoded-env", "", "JSON encoded array of environment variables in '['key=value']' format") flags.Parse(args) - for _, g := range strings.Split(groups, ",") { + for g := range strings.SplitSeq(groups, ",") { gid, err := strconv.Atoi(g) if err != nil { return ia, fmt.Errorf("unable to parse group id %q: %w", g, err) diff --git a/ssh/tailssh/privs_test.go b/ssh/tailssh/privs_test.go index f0ec66c64..7ddc9c861 100644 --- a/ssh/tailssh/privs_test.go +++ b/ssh/tailssh/privs_test.go @@ -262,12 +262,10 @@ func maybeValidUID(id int) bool { return true } - var u1 user.UnknownUserIdError - if errors.As(err, &u1) { + if _, ok := errors.AsType[user.UnknownUserIdError](err); ok { return false } - var u2 user.UnknownUserError - if errors.As(err, &u2) { + if _, ok := errors.AsType[user.UnknownUserError](err); ok { return false } @@ -281,12 +279,10 @@ func maybeValidGID(id int) bool { return true } - var u1 user.UnknownGroupIdError - if errors.As(err, &u1) { + if _, ok := errors.AsType[user.UnknownGroupIdError](err); ok { return false } - var u2 user.UnknownGroupError - if errors.As(err, &u2) { + if _, ok := errors.AsType[user.UnknownGroupError](err); ok { return false } diff --git a/ssh/tailssh/tailssh.go b/ssh/tailssh/tailssh.go index cb56f701b..debad2b5c 100644 --- a/ssh/tailssh/tailssh.go +++ b/ssh/tailssh/tailssh.go @@ -14,6 +14,7 @@ "errors" "fmt" "io" + "maps" "net" "net/http" "net/netip" @@ -500,15 +501,9 @@ func (srv *server) newConn() (*conn, error) { }, } ss := c.Server - for k, v := range ssh.DefaultRequestHandlers { - ss.RequestHandlers[k] = v - } - for k, v := range ssh.DefaultChannelHandlers { - ss.ChannelHandlers[k] = v - } - for k, v := range ssh.DefaultSubsystemHandlers { - ss.SubsystemHandlers[k] = v - } + maps.Copy(ss.RequestHandlers, ssh.DefaultRequestHandlers) + maps.Copy(ss.ChannelHandlers, ssh.DefaultChannelHandlers) + maps.Copy(ss.SubsystemHandlers, ssh.DefaultSubsystemHandlers) keys, err := srv.lb.GetSSH_HostKeys() if err != nil { return nil, err @@ -964,8 +959,7 @@ func (ss *sshSession) run() { var err error rec, err = ss.startNewRecording() if err != nil { - var uve userVisibleError - if errors.As(err, &uve) { + if uve, ok := errors.AsType[userVisibleError](err); ok { fmt.Fprintf(ss, "%s\r\n", uve.SSHTerminationMessage()) } else { fmt.Fprintf(ss, "can't start new recording\r\n") @@ -986,8 +980,7 @@ func (ss *sshSession) run() { logf("start failed: %v", err.Error()) if errors.Is(err, context.Canceled) { err := context.Cause(ss.ctx) - var uve userVisibleError - if errors.As(err, &uve) { + if uve, ok := errors.AsType[userVisibleError](err); ok { fmt.Fprintf(ss, "%s\r\n", uve) } } diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go index df8023500..4d6f2172d 100644 --- a/ssh/tailssh/tailssh_test.go +++ b/ssh/tailssh/tailssh_test.go @@ -571,9 +571,7 @@ func TestSSHRecordingCancelsSessionsOnUploadFailure(t *testing.T) { tstest.Replace(t, &handler, tt.handler) sc, dc := memnet.NewTCPConn(src, dst, 1024) var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { c, chans, reqs, err := testssh.NewClientConn(sc, sc.RemoteAddr().String(), cfg) if err != nil { t.Errorf("client: %v", err) @@ -603,7 +601,7 @@ func TestSSHRecordingCancelsSessionsOnUploadFailure(t *testing.T) { t.Errorf("client output must not contain %q", x) } } - }() + }) if err := s.HandleSSHConn(dc); err != nil { t.Errorf("unexpected error: %v", err) } @@ -666,9 +664,7 @@ func TestMultipleRecorders(t *testing.T) { } var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { c, chans, reqs, err := testssh.NewClientConn(sc, sc.RemoteAddr().String(), cfg) if err != nil { t.Errorf("client: %v", err) @@ -690,7 +686,7 @@ func TestMultipleRecorders(t *testing.T) { if string(out) != "Ran echo!\n" { t.Errorf("client: unexpected output: %q", out) } - }() + }) if err := s.HandleSSHConn(dc); err != nil { t.Errorf("unexpected error: %v", err) } @@ -757,9 +753,7 @@ func TestSSHRecordingNonInteractive(t *testing.T) { } var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { c, chans, reqs, err := testssh.NewClientConn(sc, sc.RemoteAddr().String(), cfg) if err != nil { t.Errorf("client: %v", err) @@ -778,7 +772,7 @@ func TestSSHRecordingNonInteractive(t *testing.T) { if err != nil { t.Errorf("client: %v", err) } - }() + }) if err := s.HandleSSHConn(dc); err != nil { t.Errorf("unexpected error: %v", err) } @@ -988,9 +982,7 @@ func TestSSHAuthFlow(t *testing.T) { } var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { c, chans, reqs, err := testssh.NewClientConn(sc, sc.RemoteAddr().String(), cfg) if err != nil { if !tc.authErr { @@ -1014,7 +1006,7 @@ func TestSSHAuthFlow(t *testing.T) { if err != nil { t.Errorf("client: %v", err) } - }() + }) if err := s.HandleSSHConn(dc); err != nil { t.Errorf("unexpected error: %v", err) } @@ -1228,8 +1220,8 @@ func TestSSH(t *testing.T) { func parseEnv(out []byte) map[string]string { e := map[string]string{} for line := range lineiter.Bytes(out) { - if i := bytes.IndexByte(line, '='); i != -1 { - e[string(line[:i])] = string(line[i+1:]) + if before, after, ok := bytes.Cut(line, []byte{'='}); ok { + e[string(before)] = string(after) } } return e diff --git a/syncs/shardedint_test.go b/syncs/shardedint_test.go index 8c3f7ef7b..ac298e626 100644 --- a/syncs/shardedint_test.go +++ b/syncs/shardedint_test.go @@ -66,10 +66,10 @@ func TestShardedInt(t *testing.T) { numWorkers := 1000 numIncrements := 1000 wg.Add(numWorkers) - for i := 0; i < numWorkers; i++ { + for range numWorkers { go func() { defer wg.Done() - for i := 0; i < numIncrements; i++ { + for range numIncrements { m.Add(1) } }() diff --git a/syncs/shardvalue_test.go b/syncs/shardvalue_test.go index 1dd0a542e..ab34527ab 100644 --- a/syncs/shardvalue_test.go +++ b/syncs/shardvalue_test.go @@ -66,10 +66,10 @@ type intVal struct { iterations := 10000 var wg sync.WaitGroup wg.Add(goroutines) - for i := 0; i < goroutines; i++ { + for range goroutines { go func() { defer wg.Done() - for i := 0; i < iterations; i++ { + for range iterations { sv.One(func(v *intVal) { v.Add(1) }) diff --git a/syncs/syncs_test.go b/syncs/syncs_test.go index 81fcccbf6..1e79448ad 100644 --- a/syncs/syncs_test.go +++ b/syncs/syncs_test.go @@ -6,6 +6,7 @@ import ( "context" "io" + "maps" "os" "sync" "testing" @@ -226,9 +227,7 @@ func TestMap(t *testing.T) { } got := map[string]int{} want := map[string]int{"one": 1, "two": 2, "three": 3} - for k, v := range m.All() { - got[k] = v - } + maps.Insert(got, m.All()) if d := cmp.Diff(got, want); d != "" { t.Errorf("Range mismatch (-got +want):\n%s", d) } @@ -243,9 +242,7 @@ func TestMap(t *testing.T) { m.Delete("noexist") got = map[string]int{} want = map[string]int{} - for k, v := range m.All() { - got[k] = v - } + maps.Insert(got, m.All()) if d := cmp.Diff(got, want); d != "" { t.Errorf("Range mismatch (-got +want):\n%s", d) } diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 9ed7c1e14..6d4d4e8e9 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -21,8 +21,8 @@ ) func fieldsOf(t reflect.Type) (fields []string) { - for i := range t.NumField() { - fields = append(fields, t.Field(i).Name) + for field := range t.Fields() { + fields = append(fields, field.Name) } return } diff --git a/tka/chaintest_test.go b/tka/chaintest_test.go index 5ca68afa8..467880e2c 100644 --- a/tka/chaintest_test.go +++ b/tka/chaintest_test.go @@ -7,6 +7,7 @@ "bytes" "crypto/ed25519" "fmt" + "maps" "strconv" "strings" "testing" @@ -198,9 +199,7 @@ func (c *testChain) recordParent(t *testing.T, child, parent string) { // This method populates c.AUMs and c.AUMHashes. func (c *testChain) buildChain() { pending := make(map[string]*testchainNode, len(c.Nodes)) - for k, v := range c.Nodes { - pending[k] = v - } + maps.Copy(pending, c.Nodes) // AUMs with a parent need to know their hash, so we // only compute AUMs whose parents have been computed diff --git a/tka/key.go b/tka/key.go index bc946156e..005a10433 100644 --- a/tka/key.go +++ b/tka/key.go @@ -7,6 +7,7 @@ "crypto/ed25519" "errors" "fmt" + "maps" "tailscale.com/types/tkatype" ) @@ -64,9 +65,7 @@ func (k Key) Clone() Key { if k.Meta != nil { out.Meta = make(map[string]string, len(k.Meta)) - for k, v := range k.Meta { - out.Meta[k] = v - } + maps.Copy(out.Meta, k.Meta) } return out diff --git a/tka/scenario_test.go b/tka/scenario_test.go index cf4ee2d5b..ad3742dbf 100644 --- a/tka/scenario_test.go +++ b/tka/scenario_test.go @@ -5,6 +5,7 @@ import ( "crypto/ed25519" + "maps" "sort" "testing" ) @@ -36,9 +37,7 @@ func (s *scenarioTest) mkNode(name string) *scenarioNode { } aums := make(map[string]AUM, len(s.initial.AUMs)) - for k, v := range s.initial.AUMs { - aums[k] = v - } + maps.Copy(aums, s.initial.AUMs) n := &scenarioNode{ A: authority, diff --git a/tka/sync.go b/tka/sync.go index 27e1c0e63..18a991384 100644 --- a/tka/sync.go +++ b/tka/sync.go @@ -107,7 +107,7 @@ func (a *Authority) SyncOffer(storage Chonk) (SyncOffer, error) { skipAmount uint64 = ancestorsSkipStart curs AUMHash = a.Head() ) - for i := uint64(0); i < maxSyncHeadIntersectionIter; i++ { + for i := range uint64(maxSyncHeadIntersectionIter) { if i > 0 && (i%skipAmount) == 0 { out.Ancestors = append(out.Ancestors, curs) skipAmount = skipAmount << ancestorsSkipShift diff --git a/tool/gocross/exec_other.go b/tool/gocross/exec_other.go index 20e52aa8f..b9004b8d5 100644 --- a/tool/gocross/exec_other.go +++ b/tool/gocross/exec_other.go @@ -21,8 +21,7 @@ func doExec(cmd string, args []string, env []string) error { // Propagate ExitErrors within this func to give us similar semantics to // the Unix variant. - var ee *exec.ExitError - if errors.As(err, &ee) { + if ee, ok := errors.AsType[*exec.ExitError](err); ok { os.Exit(ee.ExitCode()) } diff --git a/tsconsensus/monitor.go b/tsconsensus/monitor.go index cc5ac812c..b937926a6 100644 --- a/tsconsensus/monitor.go +++ b/tsconsensus/monitor.go @@ -85,7 +85,7 @@ func (m *monitor) handleSummaryStatus(w http.ResponseWriter, r *http.Request) { lines = append(lines, fmt.Sprintf("%s\t\t%d\t%d\t%t", name, p.RxBytes, p.TxBytes, p.Active)) } } - _, err = w.Write([]byte(fmt.Sprintf("RaftState: %s\n", s.RaftState))) + _, err = w.Write(fmt.Appendf(nil, "RaftState: %s\n", s.RaftState)) if err != nil { log.Printf("monitor: error writing status: %v", err) return @@ -93,7 +93,7 @@ func (m *monitor) handleSummaryStatus(w http.ResponseWriter, r *http.Request) { slices.Sort(lines) for _, ln := range lines { - _, err = w.Write([]byte(fmt.Sprintf("%s\n", ln))) + _, err = w.Write(fmt.Appendf(nil, "%s\n", ln)) if err != nil { log.Printf("monitor: error writing status: %v", err) return diff --git a/tsconsensus/tsconsensus_test.go b/tsconsensus/tsconsensus_test.go index 8897db119..3236ef680 100644 --- a/tsconsensus/tsconsensus_test.go +++ b/tsconsensus/tsconsensus_test.go @@ -296,7 +296,7 @@ func startNodesAndWaitForPeerStatus(t testing.TB, ctx context.Context, clusterTa keysToTag := make([]key.NodePublic, nNodes) localClients := make([]*tailscale.LocalClient, nNodes) control, controlURL := startControl(t) - for i := 0; i < nNodes; i++ { + for i := range nNodes { ts, key, _ := startNode(t, ctx, controlURL, fmt.Sprintf("node %d", i)) ps[i] = &participant{ts: ts, key: key} keysToTag[i] = key @@ -353,7 +353,7 @@ func createConsensusCluster(t testing.TB, ctx context.Context, clusterTag string } fxRaftConfigContainsAll := func() bool { - for i := 0; i < len(participants); i++ { + for i := range participants { fut := participants[i].c.raft.GetConfiguration() err = fut.Error() if err != nil { @@ -618,8 +618,8 @@ func TestOnlyTaggedPeersCanDialRaftPort(t *testing.T) { } isNetErr := func(err error) bool { - var netErr net.Error - return errors.As(err, &netErr) + _, ok := errors.AsType[net.Error](err) + return ok } err := getErrorFromTryingToSend(untaggedNode) diff --git a/tsd/tsd.go b/tsd/tsd.go index 9d79334d6..57437ddcc 100644 --- a/tsd/tsd.go +++ b/tsd/tsd.go @@ -226,8 +226,7 @@ func (p *SubSystem[T]) Set(v T) { return } - var z *T - panic(fmt.Sprintf("%v is already set", reflect.TypeOf(z).Elem().String())) + panic(fmt.Sprintf("%v is already set", reflect.TypeFor[T]().String())) } p.v = v p.set = true @@ -236,8 +235,7 @@ func (p *SubSystem[T]) Set(v T) { // Get returns the value of p, panicking if it hasn't been set. func (p *SubSystem[T]) Get() T { if !p.set { - var z *T - panic(fmt.Sprintf("%v is not set", reflect.TypeOf(z).Elem().String())) + panic(fmt.Sprintf("%v is not set", reflect.TypeFor[T]().String())) } return p.v } diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 776854e22..4a116cf34 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -466,9 +466,7 @@ func (s *Server) close() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { // Perform a best-effort final flush. if s.logtail != nil { s.logtail.Shutdown(ctx) @@ -476,14 +474,12 @@ func (s *Server) close() { if s.logbuffer != nil { s.logbuffer.Close() } - }() - wg.Add(1) - go func() { - defer wg.Done() + }) + wg.Go(func() { if s.localAPIServer != nil { s.localAPIServer.Shutdown(ctx) } - }() + }) if s.shutdownCancel != nil { s.shutdownCancel() diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index 1cf4bf48f..a2bf76e18 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -2598,7 +2598,7 @@ func buildDNSQuery(name string, srcIP netip.Addr) []byte { 0x00, 0x01, // QDCOUNT: 1 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ANCOUNT, NSCOUNT, ARCOUNT } - for _, label := range strings.Split(name, ".") { + for label := range strings.SplitSeq(name, ".") { dns = append(dns, byte(len(label))) dns = append(dns, label...) } diff --git a/tstest/deptest/deptest.go b/tstest/deptest/deptest.go index 3117af2ff..59672761e 100644 --- a/tstest/deptest/deptest.go +++ b/tstest/deptest/deptest.go @@ -124,7 +124,7 @@ func ImportAliasCheck(t testing.TB, relDir string) { } badRx := regexp.MustCompile(`^([^:]+:\d+):\s+"golang\.org/x/exp/(slices|maps)"`) if s := strings.TrimSpace(string(matches)); s != "" { - for _, line := range strings.Split(s, "\n") { + for line := range strings.SplitSeq(s, "\n") { if m := badRx.FindStringSubmatch(line); m != nil { t.Errorf("%s: the x/exp/%s package should be imported as x%s", m[1], m[2], m[2]) } diff --git a/tstest/integration/integration_test.go b/tstest/integration/integration_test.go index 2d2194278..0482e4b53 100644 --- a/tstest/integration/integration_test.go +++ b/tstest/integration/integration_test.go @@ -1673,7 +1673,7 @@ func TestNetstackTCPLoopback(t *testing.T) { defer lis.Close() writeFn := func(conn net.Conn) error { - for i := 0; i < writeBufIterations; i++ { + for range writeBufIterations { toWrite := make([]byte, writeBufSize) var wrote int for { diff --git a/tstest/integration/nat/nat_test.go b/tstest/integration/nat/nat_test.go index 1f62436ff..2ac16bf58 100644 --- a/tstest/integration/nat/nat_test.go +++ b/tstest/integration/nat/nat_test.go @@ -317,9 +317,7 @@ func (nt *natTest) runTest(addNode ...addNodeFunc) pingRoute { } defer srv.Close() - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for { c, err := srv.Accept() if err != nil { @@ -327,7 +325,7 @@ func (nt *natTest) runTest(addNode ...addNodeFunc) pingRoute { } go nt.vnet.ServeUnixConn(c.(*net.UnixConn), vnet.ProtocolQEMU) } - }() + }) for i, node := range nodes { disk := fmt.Sprintf("%s/node-%d.qcow2", nt.tempDir, i) @@ -391,7 +389,6 @@ func (nt *natTest) runTest(addNode ...addNodeFunc) pingRoute { var eg errgroup.Group for i, c := range clients { - i, c := i, c eg.Go(func() error { node := nodes[i] t.Logf("%v calling Status...", node) diff --git a/tstest/integration/vms/vms_test.go b/tstest/integration/vms/vms_test.go index 5ebb12b71..bdfba1e27 100644 --- a/tstest/integration/vms/vms_test.go +++ b/tstest/integration/vms/vms_test.go @@ -364,7 +364,7 @@ func (h *Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) { // starts with testcontrol sometimes there can be up to a few seconds where // tailscaled is in an unknown state on these virtual machines. This exponential // delay loop should delay long enough for tailscaled to be ready. - for count := 0; count < 10; count++ { + for range 10 { sess := getSession(t, cli) outp, err = sess.CombinedOutput("tailscale status") diff --git a/tstest/natlab/natlab.go b/tstest/natlab/natlab.go index add812d8f..b66779eeb 100644 --- a/tstest/natlab/natlab.go +++ b/tstest/natlab/natlab.go @@ -18,6 +18,7 @@ "net" "net/netip" "os" + "slices" "sort" "strconv" "sync" @@ -247,12 +248,7 @@ func (f *Interface) String() string { // Contains reports whether f contains ip as an IP. func (f *Interface) Contains(ip netip.Addr) bool { - for _, v := range f.ips { - if ip == v { - return true - } - } - return false + return slices.Contains(f.ips, ip) } type routeEntry struct { @@ -348,10 +344,8 @@ func (m *Machine) isLocalIP(ip netip.Addr) bool { m.mu.Lock() defer m.mu.Unlock() for _, intf := range m.interfaces { - for _, iip := range intf.ips { - if ip == iip { - return true - } + if slices.Contains(intf.ips, ip) { + return true } } return false @@ -565,7 +559,7 @@ func (m *Machine) interfaceForIP(ip netip.Addr) (*Interface, error) { func (m *Machine) pickEphemPort() (port uint16, err error) { m.mu.Lock() defer m.mu.Unlock() - for tries := 0; tries < 500; tries++ { + for range 500 { port := uint16(rand.IntN(32<<10) + 32<<10) if !m.portInUseLocked(port) { return port, nil diff --git a/tstest/natlab/vnet/vnet.go b/tstest/natlab/vnet/vnet.go index ea119bad7..9eb81520c 100644 --- a/tstest/natlab/vnet/vnet.go +++ b/tstest/natlab/vnet/vnet.go @@ -1917,7 +1917,7 @@ func (n *network) doPortMap(src netip.Addr, dstLANPort, wantExtPort uint16, sec } } - for try := 0; try < 20_000; try++ { + for range 20_000 { if wanAP.Port() > 0 && !n.natTable.IsPublicPortUsed(wanAP) { mak.Set(&n.portMap, wanAP, portMapping{ dst: dst, diff --git a/tstest/resource_test.go b/tstest/resource_test.go index ecef91cf6..fc868d5f5 100644 --- a/tstest/resource_test.go +++ b/tstest/resource_test.go @@ -245,7 +245,7 @@ func TestParseGoroutines(t *testing.T) { t.Errorf("sort field has different number of words: got %d, want %d", len(sorted), len(original)) continue } - for i := 0; i < len(original); i++ { + for i := range original { if original[i] != sorted[len(sorted)-1-i] { t.Errorf("sort field word mismatch at position %d: got %q, want %q", i, sorted[len(sorted)-1-i], original[i]) } diff --git a/tstest/typewalk/typewalk.go b/tstest/typewalk/typewalk.go index f989b4c18..dea87a8e9 100644 --- a/tstest/typewalk/typewalk.go +++ b/tstest/typewalk/typewalk.go @@ -54,14 +54,13 @@ func MatchingPaths(rt reflect.Type, match func(reflect.Type) bool) iter.Seq[Path return } switch t.Kind() { - case reflect.Ptr, reflect.Slice, reflect.Array: + case reflect.Pointer, reflect.Slice, reflect.Array: walk(t.Elem(), func(root reflect.Value) reflect.Value { v := getV(root) return v.Elem() }) case reflect.Struct: - for i := range t.NumField() { - sf := t.Field(i) + for sf := range t.Fields() { fieldName := sf.Name if fieldName == "_" { continue diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index f464e7af2..c73010783 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -13,6 +13,7 @@ "expvar" "fmt" "io" + "maps" "net" "net/http" "net/netip" @@ -734,8 +735,8 @@ func (h errorHandler) handleError(w http.ResponseWriter, r *http.Request, lw *lo // Extract a presentable, loggable error. var hOK bool - var hErr HTTPError - if errors.As(err, &hErr) { + hErr, hAsOK := errors.AsType[HTTPError](err) + if hAsOK { hOK = true if hErr.Code == 0 { lw.logf("[unexpected] HTTPError %v did not contain an HTTP status code, sending internal server error", hErr) @@ -854,9 +855,7 @@ func WriteHTTPError(w http.ResponseWriter, r *http.Request, e HTTPError) { h.Set("X-Content-Type-Options", "nosniff") // Custom headers from the error. - for k, vs := range e.Header { - h[k] = vs - } + maps.Copy(h, e.Header) // Write the msg back to the user. w.WriteHeader(e.Code) diff --git a/tsweb/varz/varz.go b/tsweb/varz/varz.go index a2286c760..0df6e5775 100644 --- a/tsweb/varz/varz.go +++ b/tsweb/varz/varz.go @@ -93,8 +93,8 @@ func prometheusMetric(prefix string, key string) (string, string, string) { typ = "histogram" key = strings.TrimPrefix(key, histogramPrefix) } - if strings.HasPrefix(key, labelMapPrefix) { - key = strings.TrimPrefix(key, labelMapPrefix) + if after, ok := strings.CutPrefix(key, labelMapPrefix); ok { + key = after if a, b, ok := strings.Cut(key, "_"); ok { label, key = a, b } @@ -154,7 +154,7 @@ func writePromExpVar(w io.Writer, prefix string, kv expvar.KeyValue) { case PrometheusMetricsReflectRooter: root := v.PrometheusMetricsReflectRoot() rv := reflect.ValueOf(root) - if rv.Type().Kind() == reflect.Ptr { + if rv.Type().Kind() == reflect.Pointer { if rv.IsNil() { return } @@ -419,8 +419,7 @@ func structTypeSortedFields(t reflect.Type) []sortedStructField { return v.([]sortedStructField) } fields := make([]sortedStructField, 0, t.NumField()) - for i, n := 0, t.NumField(); i < n; i++ { - sf := t.Field(i) + for sf := range t.Fields() { name := sf.Name if v := sf.Tag.Get("json"); v != "" { v, _, _ = strings.Cut(v, ",") @@ -433,7 +432,7 @@ func structTypeSortedFields(t reflect.Type) []sortedStructField { } } fields = append(fields, sortedStructField{ - Index: i, + Index: sf.Index[0], Name: name, SortName: removeTypePrefixes(name), MetricType: sf.Tag.Get("metrictype"), @@ -467,7 +466,7 @@ func foreachExportedStructField(rv reflect.Value, f func(fieldOrJSONName, metric sf := ssf.StructFieldType if ssf.MetricType != "" || sf.Type.Kind() == reflect.Struct { f(ssf.Name, ssf.MetricType, rv.Field(ssf.Index)) - } else if sf.Type.Kind() == reflect.Ptr && sf.Type.Elem().Kind() == reflect.Struct { + } else if sf.Type.Kind() == reflect.Pointer && sf.Type.Elem().Kind() == reflect.Struct { fv := rv.Field(ssf.Index) if !fv.IsNil() { f(ssf.Name, ssf.MetricType, fv.Elem()) diff --git a/types/ipproto/ipproto_test.go b/types/ipproto/ipproto_test.go index 8bfeb13fa..6d8be47a9 100644 --- a/types/ipproto/ipproto_test.go +++ b/types/ipproto/ipproto_test.go @@ -69,7 +69,7 @@ func TestProtoUnmarshalText(t *testing.T) { for i := range 256 { var p Proto - must.Do(p.UnmarshalText([]byte(fmt.Sprintf("%d", i)))) + must.Do(p.UnmarshalText(fmt.Appendf(nil, "%d", i))) if got, want := p, Proto(i); got != want { t.Errorf("Proto(%d) = %v, want %v", i, got, want) } @@ -122,7 +122,7 @@ func TestProtoUnmarshalJSON(t *testing.T) { var p Proto for i := range 256 { - j := []byte(fmt.Sprintf(`%d`, i)) + j := fmt.Appendf(nil, `%d`, i) must.Do(json.Unmarshal(j, &p)) if got, want := p, Proto(i); got != want { t.Errorf("Proto(%d) = %v, want %v", i, got, want) @@ -130,7 +130,7 @@ func TestProtoUnmarshalJSON(t *testing.T) { } for name, wantProto := range acceptedNames { - must.Do(json.Unmarshal([]byte(fmt.Sprintf(`"%s"`, name)), &p)) + must.Do(json.Unmarshal(fmt.Appendf(nil, `"%s"`, name), &p)) if got, want := p, wantProto; got != want { t.Errorf("Proto(%q) = %v, want %v", name, got, want) } diff --git a/types/lazy/deferred_test.go b/types/lazy/deferred_test.go index 61cc8f8ac..4b2bb07ee 100644 --- a/types/lazy/deferred_test.go +++ b/types/lazy/deferred_test.go @@ -145,13 +145,11 @@ func TestDeferredInit(t *testing.T) { // Call [DeferredInit.Do] concurrently. const N = 10000 for range N { - wg.Add(1) - go func() { + wg.Go(func() { gotErr := di.Do() checkError(t, gotErr, nil, false) checkCalls() - wg.Done() - }() + }) } wg.Wait() }) @@ -193,12 +191,10 @@ func() error { return errors.New("unreachable") }, var wg sync.WaitGroup N := 10000 for range N { - wg.Add(1) - go func() { + wg.Go(func() { gotErr := di.Do() checkError(t, gotErr, tt.wantErr, false) - wg.Done() - }() + }) } wg.Wait() }) @@ -254,11 +250,9 @@ func TestDeferAfterDo(t *testing.T) { const N = 10000 var wg sync.WaitGroup for range N { - wg.Add(1) - go func() { + wg.Go(func() { deferOnce() - wg.Done() - }() + }) } if err := di.Do(); err != nil { diff --git a/types/netmap/nodemut_test.go b/types/netmap/nodemut_test.go index a03dee49c..1ae2ab1f9 100644 --- a/types/netmap/nodemut_test.go +++ b/types/netmap/nodemut_test.go @@ -34,7 +34,7 @@ func TestMapResponseContainsNonPatchFields(t *testing.T) { return reflect.ValueOf(int64(1)).Convert(t) case reflect.Slice: return reflect.MakeSlice(t, 1, 1) - case reflect.Ptr: + case reflect.Pointer: return reflect.New(t.Elem()) case reflect.Map: return reflect.MakeMap(t) @@ -43,8 +43,7 @@ func TestMapResponseContainsNonPatchFields(t *testing.T) { } rt := reflect.TypeFor[tailcfg.MapResponse]() - for i := range rt.NumField() { - f := rt.Field(i) + for f := range rt.Fields() { var want bool switch f.Name { diff --git a/types/persist/persist_test.go b/types/persist/persist_test.go index b25af5a0b..33773013d 100644 --- a/types/persist/persist_test.go +++ b/types/persist/persist_test.go @@ -12,8 +12,8 @@ ) func fieldsOf(t reflect.Type) (fields []string) { - for i := range t.NumField() { - if name := t.Field(i).Name; name != "_" { + for field := range t.Fields() { + if name := field.Name; name != "_" { fields = append(fields, name) } } diff --git a/types/views/views.go b/types/views/views.go index 4e17ac952..fe70e227f 100644 --- a/types/views/views.go +++ b/types/views/views.go @@ -968,8 +968,8 @@ func containsPointers(typ reflect.Type) bool { if isWellKnownImmutableStruct(typ) { return false } - for i := range typ.NumField() { - if containsPointers(typ.Field(i).Type) { + for field := range typ.Fields() { + if containsPointers(field.Type) { return true } } diff --git a/util/dnsname/dnsname.go b/util/dnsname/dnsname.go index 263c376aa..cf1ae6200 100644 --- a/util/dnsname/dnsname.go +++ b/util/dnsname/dnsname.go @@ -234,7 +234,7 @@ func ValidHostname(hostname string) error { return err } - for _, label := range strings.Split(fqdn.WithoutTrailingDot(), ".") { + for label := range strings.SplitSeq(fqdn.WithoutTrailingDot(), ".") { if err := ValidLabel(label); err != nil { return err } diff --git a/util/goroutines/goroutines.go b/util/goroutines/goroutines.go index fd0a4dd7e..f184fcd6c 100644 --- a/util/goroutines/goroutines.go +++ b/util/goroutines/goroutines.go @@ -52,7 +52,7 @@ func scrubHex(buf []byte) []byte { in[0] = '?' return } - v := []byte(fmt.Sprintf("v%d%%%d", len(saw)+1, u64%8)) + v := fmt.Appendf(nil, "v%d%%%d", len(saw)+1, u64%8) saw[inStr] = v copy(in, v) }) diff --git a/util/hashx/block512_test.go b/util/hashx/block512_test.go index 91d5d9ee6..03c77eabb 100644 --- a/util/hashx/block512_test.go +++ b/util/hashx/block512_test.go @@ -47,7 +47,7 @@ type hasher interface { func hashSuite(h hasher) { for i := range 10 { - for j := 0; j < 10; j++ { + for range 10 { h.HashUint8(0x01) h.HashUint8(0x23) h.HashUint32(0x456789ab) diff --git a/util/httphdr/httphdr.go b/util/httphdr/httphdr.go index 01e8eddc6..852b3f5c7 100644 --- a/util/httphdr/httphdr.go +++ b/util/httphdr/httphdr.go @@ -44,7 +44,7 @@ func ParseRange(hdr string) (ranges []Range, ok bool) { hdr = strings.Trim(hdr, ows) // per RFC 7230, section 3.2 units, elems, hasUnits := strings.Cut(hdr, "=") elems = strings.TrimLeft(elems, ","+ows) - for _, elem := range strings.Split(elems, ",") { + for elem := range strings.SplitSeq(elems, ",") { elem = strings.Trim(elem, ows) // per RFC 7230, section 7 switch { case strings.HasPrefix(elem, "-"): // i.e., "-" suffix-length diff --git a/util/httpm/httpm_test.go b/util/httpm/httpm_test.go index 4e7f7b5ab..4a36a38e1 100644 --- a/util/httpm/httpm_test.go +++ b/util/httpm/httpm_test.go @@ -27,7 +27,7 @@ func TestUsedConsistently(t *testing.T) { cmd := exec.Command("git", "grep", "-l", "-F", "http.Method") cmd.Dir = rootDir matches, _ := cmd.Output() - for _, fn := range strings.Split(strings.TrimSpace(string(matches)), "\n") { + for fn := range strings.SplitSeq(strings.TrimSpace(string(matches)), "\n") { switch fn { case "util/httpm/httpm.go", "util/httpm/httpm_test.go": continue diff --git a/util/linuxfw/fake.go b/util/linuxfw/fake.go index 1886e2542..166d80401 100644 --- a/util/linuxfw/fake.go +++ b/util/linuxfw/fake.go @@ -9,6 +9,7 @@ "errors" "fmt" "os" + "slices" "strconv" "strings" ) @@ -60,10 +61,8 @@ func (n *fakeIPTables) Append(table, chain string, args ...string) error { func (n *fakeIPTables) Exists(table, chain string, args ...string) (bool, error) { k := table + "/" + chain if rules, ok := n.n[k]; ok { - for _, rule := range rules { - if rule == strings.Join(args, " ") { - return true, nil - } + if slices.Contains(rules, strings.Join(args, " ")) { + return true, nil } return false, nil } else { diff --git a/util/linuxfw/iptables.go b/util/linuxfw/iptables.go index f054e7abe..3bd2c2886 100644 --- a/util/linuxfw/iptables.go +++ b/util/linuxfw/iptables.go @@ -21,8 +21,8 @@ func init() { isNotExistError = func(err error) bool { - var e *iptables.Error - return errors.As(err, &e) && e.IsNotExist() + e, ok := errors.AsType[*iptables.Error](err) + return ok && e.IsNotExist() } } diff --git a/util/linuxfw/nftables_for_svcs.go b/util/linuxfw/nftables_for_svcs.go index c2425e2ff..35764a2bd 100644 --- a/util/linuxfw/nftables_for_svcs.go +++ b/util/linuxfw/nftables_for_svcs.go @@ -236,7 +236,7 @@ func portMapRule(t *nftables.Table, ch *nftables.Chain, tun string, targetIP net // This metadata can then be used to find the rule. // https://github.com/google/nftables/issues/48 func svcPortMapRuleMeta(svcName string, targetIP netip.Addr, pm PortMap) []byte { - return []byte(fmt.Sprintf("svc:%s,targetIP:%s:matchPort:%v,targetPort:%v,proto:%v", svcName, targetIP.String(), pm.MatchPort, pm.TargetPort, pm.Protocol)) + return fmt.Appendf(nil, "svc:%s,targetIP:%s:matchPort:%v,targetPort:%v,proto:%v", svcName, targetIP.String(), pm.MatchPort, pm.TargetPort, pm.Protocol) } func (n *nftablesRunner) findRuleByMetadata(t *nftables.Table, ch *nftables.Chain, meta []byte) (*nftables.Rule, error) { @@ -305,5 +305,5 @@ func protoFromString(s string) (uint8, error) { // This metadata can then be used to find the rule. // https://github.com/google/nftables/issues/48 func svcRuleMeta(svcName string, origDst, dst netip.Addr) []byte { - return []byte(fmt.Sprintf("svc:%s,VIP:%s,ClusterIP:%s", svcName, origDst.String(), dst.String())) + return fmt.Appendf(nil, "svc:%s,VIP:%s,ClusterIP:%s", svcName, origDst.String(), dst.String()) } diff --git a/util/linuxfw/nftables_runner_test.go b/util/linuxfw/nftables_runner_test.go index 8299a9cbd..19e869a04 100644 --- a/util/linuxfw/nftables_runner_test.go +++ b/util/linuxfw/nftables_runner_test.go @@ -1066,7 +1066,7 @@ func checkSNATRule_nft(t *testing.T, runner *nftablesRunner, fam nftables.TableF if chain == nil { t.Fatal("POSTROUTING chain does not exist") } - meta := []byte(fmt.Sprintf("dst:%s,src:%s", dst.String(), src.String())) + meta := fmt.Appendf(nil, "dst:%s,src:%s", dst.String(), src.String()) wantsRule := snatRule(chain.Table, chain, src, dst, meta) checkRule(t, wantsRule, runner.conn) } diff --git a/util/pool/pool_test.go b/util/pool/pool_test.go index ac7cf86be..ad509a563 100644 --- a/util/pool/pool_test.go +++ b/util/pool/pool_test.go @@ -94,12 +94,12 @@ func TestPool(t *testing.T) { func TestTakeRandom(t *testing.T) { p := Pool[int]{} - for i := 0; i < 10; i++ { + for i := range 10 { p.Add(i + 100) } seen := make(map[int]bool) - for i := 0; i < 10; i++ { + for range 10 { item, ok := p.TakeRandom() if !ok { t.Errorf("unexpected empty pool") @@ -116,7 +116,7 @@ func TestTakeRandom(t *testing.T) { t.Errorf("expected empty pool") } - for i := 0; i < 10; i++ { + for i := range 10 { want := 100 + i if !seen[want] { t.Errorf("item %v not seen", want) diff --git a/util/set/intset.go b/util/set/intset.go index 04f614742..29a634516 100644 --- a/util/set/intset.go +++ b/util/set/intset.go @@ -152,7 +152,7 @@ func (s bitSet) values() iter.Seq[uint64] { return func(yield func(uint64) bool) { // Hyrum-proofing: randomly iterate in forwards or reverse. if rand.Uint64()%2 == 0 { - for i := 0; i < bits.UintSize; i++ { + for i := range bits.UintSize { if s.contains(uint64(i)) && !yield(uint64(i)) { return } diff --git a/util/singleflight/singleflight.go b/util/singleflight/singleflight.go index 23cf7e21f..e6d859178 100644 --- a/util/singleflight/singleflight.go +++ b/util/singleflight/singleflight.go @@ -36,7 +36,7 @@ // A panicError is an arbitrary value recovered from a panic // with the stack trace during the execution of given function. type panicError struct { - value interface{} + value any stack []byte } @@ -45,7 +45,7 @@ func (p *panicError) Error() string { return fmt.Sprintf("%v\n\n%s", p.value, p.stack) } -func newPanicError(v interface{}) error { +func newPanicError(v any) error { stack := debug.Stack() // The first line of the stack trace is of the form "goroutine N [status]:" diff --git a/util/singleflight/singleflight_test.go b/util/singleflight/singleflight_test.go index 9f0ca7f1d..4e8500cc3 100644 --- a/util/singleflight/singleflight_test.go +++ b/util/singleflight/singleflight_test.go @@ -25,7 +25,7 @@ func TestDo(t *testing.T) { var g Group[string, any] - v, err, _ := g.Do("key", func() (interface{}, error) { + v, err, _ := g.Do("key", func() (any, error) { return "bar", nil }) if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { @@ -39,7 +39,7 @@ func TestDo(t *testing.T) { func TestDoErr(t *testing.T) { var g Group[string, any] someErr := errors.New("Some error") - v, err, _ := g.Do("key", func() (interface{}, error) { + v, err, _ := g.Do("key", func() (any, error) { return nil, someErr }) if err != someErr { @@ -55,7 +55,7 @@ func TestDoDupSuppress(t *testing.T) { var wg1, wg2 sync.WaitGroup c := make(chan string, 1) var calls int32 - fn := func() (interface{}, error) { + fn := func() (any, error) { if atomic.AddInt32(&calls, 1) == 1 { // First invocation. wg1.Done() @@ -72,9 +72,7 @@ func TestDoDupSuppress(t *testing.T) { wg1.Add(1) for range n { wg1.Add(1) - wg2.Add(1) - go func() { - defer wg2.Done() + wg2.Go(func() { wg1.Done() v, err, _ := g.Do("key", fn) if err != nil { @@ -84,7 +82,7 @@ func TestDoDupSuppress(t *testing.T) { if s, _ := v.(string); s != "bar" { t.Errorf("Do = %T %v; want %q", v, v, "bar") } - }() + }) } wg1.Wait() // At least one goroutine is in fn now and all of them have at @@ -108,7 +106,7 @@ func TestForget(t *testing.T) { ) go func() { - g.Do("key", func() (i interface{}, e error) { + g.Do("key", func() (i any, e error) { close(firstStarted) <-unblockFirst close(firstFinished) @@ -119,7 +117,7 @@ func TestForget(t *testing.T) { g.Forget("key") unblockSecond := make(chan struct{}) - secondResult := g.DoChan("key", func() (i interface{}, e error) { + secondResult := g.DoChan("key", func() (i any, e error) { <-unblockSecond return 2, nil }) @@ -127,7 +125,7 @@ func TestForget(t *testing.T) { close(unblockFirst) <-firstFinished - thirdResult := g.DoChan("key", func() (i interface{}, e error) { + thirdResult := g.DoChan("key", func() (i any, e error) { return 3, nil }) @@ -141,7 +139,7 @@ func TestForget(t *testing.T) { func TestDoChan(t *testing.T) { var g Group[string, any] - ch := g.DoChan("key", func() (interface{}, error) { + ch := g.DoChan("key", func() (any, error) { return "bar", nil }) @@ -160,7 +158,7 @@ func TestDoChan(t *testing.T) { // See https://github.com/golang/go/issues/41133 func TestPanicDo(t *testing.T) { var g Group[string, any] - fn := func() (interface{}, error) { + fn := func() (any, error) { panic("invalid memory address or nil pointer dereference") } @@ -197,7 +195,7 @@ func TestPanicDo(t *testing.T) { func TestGoexitDo(t *testing.T) { var g Group[string, any] - fn := func() (interface{}, error) { + fn := func() (any, error) { runtime.Goexit() return nil, nil } @@ -238,7 +236,7 @@ func TestPanicDoChan(t *testing.T) { }() g := new(Group[string, any]) - ch := g.DoChan("", func() (interface{}, error) { + ch := g.DoChan("", func() (any, error) { panic("Panicking in DoChan") }) <-ch @@ -283,7 +281,7 @@ func TestPanicDoSharedByDoChan(t *testing.T) { defer func() { recover() }() - g.Do("", func() (interface{}, error) { + g.Do("", func() (any, error) { close(blocked) <-unblock panic("Panicking in Do") @@ -291,7 +289,7 @@ func TestPanicDoSharedByDoChan(t *testing.T) { }() <-blocked - ch := g.DoChan("", func() (interface{}, error) { + ch := g.DoChan("", func() (any, error) { panic("DoChan unexpectedly executed callback") }) close(unblock) @@ -325,8 +323,7 @@ func TestPanicDoSharedByDoChan(t *testing.T) { func TestDoChanContext(t *testing.T) { t.Run("Basic", func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() var g Group[string, int] ch := g.DoChanContext(ctx, "key", func(_ context.Context) (int, error) { @@ -337,8 +334,7 @@ func TestDoChanContext(t *testing.T) { }) t.Run("DoesNotPropagateValues", func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() key := new(int) const value = "hello world" @@ -364,8 +360,7 @@ func TestDoChanContext(t *testing.T) { ctx1, cancel1 := context.WithCancel(context.Background()) defer cancel1() - ctx2, cancel2 := context.WithCancel(context.Background()) - defer cancel2() + ctx2 := t.Context() fn := func(ctx context.Context) (int, error) { select { diff --git a/util/slicesx/slicesx_test.go b/util/slicesx/slicesx_test.go index d5c87a372..6b28c29b4 100644 --- a/util/slicesx/slicesx_test.go +++ b/util/slicesx/slicesx_test.go @@ -53,7 +53,7 @@ func TestShuffle(t *testing.T) { } var wasShuffled bool - for try := 0; try < 10; try++ { + for range 10 { shuffled := slices.Clone(sl) Shuffle(shuffled) if !reflect.DeepEqual(shuffled, sl) { diff --git a/util/syspolicy/policytest/policytest.go b/util/syspolicy/policytest/policytest.go index ef5ce889d..9879a0fd3 100644 --- a/util/syspolicy/policytest/policytest.go +++ b/util/syspolicy/policytest/policytest.go @@ -89,12 +89,7 @@ func (pc policyChanges) HasChanged(v pkey.Key) bool { return ok } func (pc policyChanges) HasChangedAnyOf(keys ...pkey.Key) bool { - for _, k := range keys { - if pc.HasChanged(k) { - return true - } - } - return false + return slices.ContainsFunc(keys, pc.HasChanged) } const watchersKey = "_policytest_watchers" diff --git a/util/topk/topk_test.go b/util/topk/topk_test.go index 06656c420..7679f59a3 100644 --- a/util/topk/topk_test.go +++ b/util/topk/topk_test.go @@ -43,7 +43,7 @@ func TestTopK(t *testing.T) { got []int want = []int{5, 6, 7, 8, 9} ) - for try := 0; try < 10; try++ { + for range 10 { topk := NewWithParams[int](5, func(in []byte, val int) []byte { return binary.LittleEndian.AppendUint64(in, uint64(val)) }, 4, 1000) diff --git a/util/vizerror/vizerror.go b/util/vizerror/vizerror.go index 479bd2de9..e0abe8f97 100644 --- a/util/vizerror/vizerror.go +++ b/util/vizerror/vizerror.go @@ -77,6 +77,5 @@ func WrapWithMessage(wrapped error, publicMsg string) error { // As returns the first vizerror.Error in err's chain. func As(err error) (e Error, ok bool) { - ok = errors.As(err, &e) - return + return errors.AsType[Error](err) } diff --git a/util/zstdframe/zstd_test.go b/util/zstdframe/zstd_test.go index 302090b99..c006a06fd 100644 --- a/util/zstdframe/zstd_test.go +++ b/util/zstdframe/zstd_test.go @@ -128,7 +128,7 @@ func BenchmarkEncode(b *testing.B) { b.Run(bb.name, func(b *testing.B) { b.ReportAllocs() b.SetBytes(int64(len(src))) - for range b.N { + for b.Loop() { dst = AppendEncode(dst[:0], src, bb.opts...) } }) @@ -153,7 +153,7 @@ func BenchmarkDecode(b *testing.B) { b.Run(bb.name, func(b *testing.B) { b.ReportAllocs() b.SetBytes(int64(len(src))) - for range b.N { + for b.Loop() { dst = must.Get(AppendDecode(dst[:0], src, bb.opts...)) } }) @@ -169,16 +169,14 @@ func BenchmarkEncodeParallel(b *testing.B) { } b.Run(coder.name, func(b *testing.B) { b.ReportAllocs() - for range b.N { - var group sync.WaitGroup - for j := 0; j < numCPU; j++ { - group.Add(1) - go func(j int) { - defer group.Done() + for b.Loop() { + var wg sync.WaitGroup + for j := range numCPU { + wg.Go(func() { dsts[j] = coder.appendEncode(dsts[j][:0], src) - }(j) + }) } - group.Wait() + wg.Wait() } }) } @@ -194,16 +192,14 @@ func BenchmarkDecodeParallel(b *testing.B) { } b.Run(coder.name, func(b *testing.B) { b.ReportAllocs() - for range b.N { - var group sync.WaitGroup - for j := 0; j < numCPU; j++ { - group.Add(1) - go func(j int) { - defer group.Done() + for b.Loop() { + var wg sync.WaitGroup + for j := range numCPU { + wg.Go(func() { dsts[j] = must.Get(coder.appendDecode(dsts[j][:0], src)) - }(j) + }) } - group.Wait() + wg.Wait() } }) } diff --git a/version/cmdname.go b/version/cmdname.go index 8a4040f97..5a0b84875 100644 --- a/version/cmdname.go +++ b/version/cmdname.go @@ -39,7 +39,7 @@ func cmdName(exe string) string { } // v is like: // "path\ttailscale.com/cmd/tailscale\nmod\ttailscale.com\t(devel)\t\ndep\tgithub.com/apenwarr/fixconsole\tv0.0.0-20191012055117-5a9f6489cc29\th1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=\ndep\tgithub.... - for _, line := range strings.Split(info, "\n") { + for line := range strings.SplitSeq(info, "\n") { if goPkg, ok := strings.CutPrefix(line, "path\t"); ok { // like "tailscale.com/cmd/tailscale" ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath break diff --git a/version/version_test.go b/version/version_test.go index ebae7f177..42bcf2163 100644 --- a/version/version_test.go +++ b/version/version_test.go @@ -30,7 +30,7 @@ func readAlpineTag(t *testing.T, file string) string { if err != nil { t.Fatal(err) } - for _, line := range bytes.Split(f, []byte{'\n'}) { + for line := range bytes.SplitSeq(f, []byte{'\n'}) { line = bytes.TrimSpace(line) _, suf, ok := bytes.Cut(line, []byte("FROM alpine:")) if !ok { diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index c588a506e..a3b9a8e00 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -751,13 +751,13 @@ func ports(s string) PortRange { } var fs, ls string - i := strings.IndexByte(s, '-') - if i == -1 { + before, after, ok := strings.Cut(s, "-") + if !ok { fs = s ls = fs } else { - fs = s[:i] - ls = s[i+1:] + fs = before + ls = after } first, err := strconv.ParseInt(fs, 10, 16) if err != nil { diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 1f02d84c7..78ffd0cd0 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -1474,8 +1474,7 @@ func (c *Conn) sendUDPBatch(addr epAddr, buffs [][]byte, offset int) (sent bool, err = c.pconn4.WriteWireGuardBatchTo(buffs, addr, offset) } if err != nil { - var errGSO neterror.ErrUDPGSODisabled - if errors.As(err, &errGSO) { + if errGSO, ok := errors.AsType[neterror.ErrUDPGSODisabled](err); ok { c.logf("magicsock: %s", errGSO.Error()) err = errGSO.RetryErr } else { diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index dfd9d395d..7a8a6374c 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -2007,7 +2007,7 @@ func TestStressSetNetworkMap(t *testing.T) { const iters = 1000 // approx 0.5s on an m1 mac for range iters { - for j := 0; j < npeers; j++ { + for j := range npeers { // Randomize which peers are present. if prng.Int()&1 == 0 { present[j] = !present[j] @@ -2196,7 +2196,7 @@ func newWireguard(t *testing.T, uapi string, aips []netip.Prefix) (*device.Devic if err != nil { t.Fatal(err) } - for _, line := range strings.Split(s, "\n") { + for line := range strings.SplitSeq(s, "\n") { line = strings.TrimSpace(line) if len(line) == 0 { continue @@ -4311,7 +4311,7 @@ func TestRotateDiscoKeyMultipleTimes(t *testing.T) { keys := make([]key.DiscoPublic, 0, 5) keys = append(keys, c.discoAtomic.Public()) - for i := 0; i < 4; i++ { + for i := range 4 { c.RotateDiscoKey() newKey := c.discoAtomic.Public() diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index 77cb4a7b9..116deffa9 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -102,7 +102,7 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.Wrapp canonicalIPs = sync.OnceValue(func() (checkIPFunc func(netip.Addr) bool) { // https://bgp.he.net/AS41231#_prefixes t := &bart.Table[bool]{} - for _, s := range strings.Fields(` + for s := range strings.FieldsSeq(` 91.189.89.0/24 91.189.91.0/24 91.189.92.0/24 diff --git a/wgengine/router/osrouter/router_linux_test.go b/wgengine/router/osrouter/router_linux_test.go index bae997e33..8c2514eb5 100644 --- a/wgengine/router/osrouter/router_linux_test.go +++ b/wgengine/router/osrouter/router_linux_test.go @@ -1073,11 +1073,9 @@ func (o *fakeOS) run(args ...string) error { switch args[2] { case "add": - for _, el := range *ls { - if el == rest { - o.t.Errorf("can't add %q, already present", rest) - return errors.New("already exists") - } + if slices.Contains(*ls, rest) { + o.t.Errorf("can't add %q, already present", rest) + return errors.New("already exists") } *ls = append(*ls, rest) sort.Strings(*ls) diff --git a/wgengine/router/osrouter/runner.go b/wgengine/router/osrouter/runner.go index bdc710a8d..82b2680e6 100644 --- a/wgengine/router/osrouter/runner.go +++ b/wgengine/router/osrouter/runner.go @@ -10,6 +10,7 @@ "fmt" "os" "os/exec" + "slices" "strconv" "strings" "syscall" @@ -42,8 +43,7 @@ func errCode(err error) int { if err == nil { return 0 } - var e *exec.ExitError - if ok := errors.As(err, &e); ok { + if e, ok := errors.AsType[*exec.ExitError](err); ok { return e.ExitCode() } s := err.Error() @@ -96,12 +96,7 @@ func newRunGroup(okCode []int, runner commandRunner) *runGroup { func (rg *runGroup) okCode(err error) bool { got := errCode(err) - for _, want := range rg.OkCode { - if got == want { - return true - } - } - return false + return slices.Contains(rg.OkCode, got) } func (rg *runGroup) Output(args ...string) []byte { diff --git a/wgengine/router/router_test.go b/wgengine/router/router_test.go index 28750e115..f6176f1d0 100644 --- a/wgengine/router/router_test.go +++ b/wgengine/router/router_test.go @@ -19,8 +19,8 @@ func TestConfigEqual(t *testing.T) { } configType := reflect.TypeFor[Config]() configFields := []string{} - for i := range configType.NumField() { - configFields = append(configFields, configType.Field(i).Name) + for field := range configType.Fields() { + configFields = append(configFields, field.Name) } if !reflect.DeepEqual(configFields, testedFields) { t.Errorf("Config.Equal check might be out of sync\nfields: %q\nhandled: %q\n", diff --git a/wgengine/wgcfg/config_test.go b/wgengine/wgcfg/config_test.go index b15b8cbf5..7059b17b2 100644 --- a/wgengine/wgcfg/config_test.go +++ b/wgengine/wgcfg/config_test.go @@ -12,8 +12,7 @@ // that might get added in the future. func TestConfigEqual(t *testing.T) { rt := reflect.TypeFor[Config]() - for i := range rt.NumField() { - sf := rt.Field(i) + for sf := range rt.Fields() { switch sf.Name { case "Name", "NodeID", "PrivateKey", "MTU", "Addresses", "DNS", "Peers", "NetworkLogging": @@ -28,8 +27,7 @@ func TestConfigEqual(t *testing.T) { // that might get added in the future. func TestPeerEqual(t *testing.T) { rt := reflect.TypeFor[Peer]() - for i := range rt.NumField() { - sf := rt.Field(i) + for sf := range rt.Fields() { switch sf.Name { case "PublicKey", "DiscoKey", "AllowedIPs", "IsJailed", "PersistentKeepalive", "V4MasqAddr", "V6MasqAddr", "WGEndpoint": diff --git a/wif/wif.go b/wif/wif.go index bb2e760f2..bc479fad1 100644 --- a/wif/wif.go +++ b/wif/wif.go @@ -190,8 +190,7 @@ func acquireAWSWebIdentityToken(ctx context.Context, audience string) (string, e out, err := stsClient.GetWebIdentityToken(ctx, in) if err != nil { - var apiErr smithy.APIError - if errors.As(err, &apiErr) { + if apiErr, ok := errors.AsType[smithy.APIError](err); ok { return "", fmt.Errorf("aws sts:GetWebIdentityToken failed (%s): %w", apiErr.ErrorCode(), err) } return "", fmt.Errorf("aws sts:GetWebIdentityToken failed: %w", err)