diff --git a/client/tailscale/apitype/apitype.go b/client/tailscale/apitype/apitype.go index d7d1440be..eb4d6c13e 100644 --- a/client/tailscale/apitype/apitype.go +++ b/client/tailscale/apitype/apitype.go @@ -76,7 +76,7 @@ type ReloadConfigResponse struct { type ExitNodeSuggestionResponse struct { ID tailcfg.StableNodeID Name string - Location tailcfg.LocationView `json:",omitempty"` + Location tailcfg.LocationView `json:",omitzero"` } // DNSOSConfig mimics dns.OSConfig without forcing us to import the entire dns package diff --git a/client/tailscale/keys.go b/client/tailscale/keys.go index 6edbae034..483d12390 100644 --- a/client/tailscale/keys.go +++ b/client/tailscale/keys.go @@ -22,7 +22,7 @@ type Key struct { // KeyCapabilities are the capabilities of a Key. type KeyCapabilities struct { - Devices KeyDeviceCapabilities `json:"devices,omitempty"` + Devices KeyDeviceCapabilities `json:"devices"` } // KeyDeviceCapabilities are the device-related capabilities of a Key. diff --git a/derp/derp_client.go b/derp/derp_client.go index e85c20761..cc792a2f2 100644 --- a/derp/derp_client.go +++ b/derp/derp_client.go @@ -167,7 +167,7 @@ type ClientInfo struct { // trusted clients. It's required to subscribe to the // connection list & forward packets. It's empty for regular // users. - MeshKey key.DERPMesh `json:"meshKey,omitempty,omitzero"` + MeshKey key.DERPMesh `json:"meshKey,omitzero"` // Version is the DERP protocol version that the client was built with. // See the ProtocolVersion const. diff --git a/drive/driveimpl/remote_impl.go b/drive/driveimpl/remote_impl.go index 0ff27dc64..c9b42f1ab 100644 --- a/drive/driveimpl/remote_impl.go +++ b/drive/driveimpl/remote_impl.go @@ -315,9 +315,7 @@ func (s *userServer) runLoop() { consecutiveFailures = 1 } sleepTime := time.Duration(math.Pow(2, consecutiveFailures)) * time.Millisecond - if sleepTime > maxSleepTime { - sleepTime = maxSleepTime - } + sleepTime = min(sleepTime, maxSleepTime) s.logf("user server % v stopped with error %v, will try again in %v", s.executable, err, sleepTime) time.Sleep(sleepTime) } diff --git a/drive/driveimpl/shared/readonlydir.go b/drive/driveimpl/shared/readonlydir.go index b0f958231..e2d3a469b 100644 --- a/drive/driveimpl/shared/readonlydir.go +++ b/drive/driveimpl/shared/readonlydir.go @@ -42,10 +42,7 @@ func (d *DirFile) Readdir(count int) ([]fs.FileInfo, error) { return result, nil } - n := len(d.children) - if count < n { - n = count - } + n := min(count, len(d.children)) result := d.children[:n] d.children = d.children[n:] if len(d.children) == 0 { diff --git a/gokrazy/gokrazy_test.go b/gokrazy/gokrazy_test.go index 8831bada1..560a19456 100644 --- a/gokrazy/gokrazy_test.go +++ b/gokrazy/gokrazy_test.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "slices" "strings" "sync" "testing" @@ -181,12 +182,7 @@ func (sl *serialLog) lastN(n int) []string { func (sl *serialLog) findLine(pred func(string) bool) bool { sl.mu.Lock() defer sl.mu.Unlock() - for _, line := range sl.lines { - if pred(line) { - return true - } - } - return false + return slices.ContainsFunc(sl.lines, pred) } // TestBusyboxInTsapp boots the tsapp image in QEMU and verifies that diff --git a/gokrazy/mkfs/mkfs.go b/gokrazy/mkfs/mkfs.go index 8fac5418a..cd08b9e9a 100644 --- a/gokrazy/mkfs/mkfs.go +++ b/gokrazy/mkfs/mkfs.go @@ -144,10 +144,7 @@ func (m *memBackend) ReadAt(p []byte, off int64) (int, error) { absOff := off + int64(total) page := absOff / memPageSize within := int(absOff % memPageSize) - room := memPageSize - within - if room > len(p)-total { - room = len(p) - total - } + room := min(memPageSize-within, len(p)-total) if chunk, ok := m.pages[page]; ok { copy(p[total:total+room], chunk[within:within+room]) } @@ -183,10 +180,7 @@ func (m *memBackend) WriteAt(p []byte, off int64) (int, error) { absOff := off + int64(total) page := absOff / memPageSize within := int(absOff % memPageSize) - room := memPageSize - within - if room > len(p)-total { - room = len(p) - total - } + room := min(memPageSize-within, len(p)-total) chunk, ok := m.pages[page] if !ok && isAllZero(p[total:total+room]) { // Don't allocate a fresh zero page. diff --git a/ipn/ipnlocal/breaktcp_darwin.go b/ipn/ipnlocal/breaktcp_darwin.go index 732c375f7..a97f43eb1 100644 --- a/ipn/ipnlocal/breaktcp_darwin.go +++ b/ipn/ipnlocal/breaktcp_darwin.go @@ -15,7 +15,7 @@ func init() { func breakTCPConnsDarwin() error { var matched int - for fd := 0; fd < 1000; fd++ { + for fd := range 1000 { _, err := unix.GetsockoptTCPConnectionInfo(fd, unix.IPPROTO_TCP, unix.TCP_CONNECTION_INFO) if err == nil { matched++ diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index fc08c7c7b..97d8d1abc 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -2123,8 +2123,7 @@ func TestWatchNotificationsCallbacks(t *testing.T) { func TestWatchNotificationsClosesSlowConsumer(t *testing.T) { b := newTestLocalBackend(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() watchAdded := make(chan struct{}) firstNotify := make(chan struct{}, 1) diff --git a/k8s-operator/utils_test.go b/k8s-operator/utils_test.go index 7a30df6b4..d46f5e64f 100644 --- a/k8s-operator/utils_test.go +++ b/k8s-operator/utils_test.go @@ -60,7 +60,7 @@ func TestTruncateLabelValue(t *testing.T) { func TestTruncateLabelValueDeterministic(t *testing.T) { input := strings.Repeat("a", 100) first := TruncateLabelValue(input) - for i := 0; i < 10; i++ { + for range 10 { got := TruncateLabelValue(input) if got != first { t.Fatalf("non-deterministic: got %q, want %q", got, first) diff --git a/kube/kubeapi/api.go b/kube/kubeapi/api.go index c3ed1a3b7..f1d7d51a4 100644 --- a/kube/kubeapi/api.go +++ b/kube/kubeapi/api.go @@ -94,7 +94,7 @@ type ObjectMeta struct { // Null for lists. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional - CreationTimestamp time.Time `json:"creationTimestamp,omitempty"` + CreationTimestamp time.Time `json:"creationTimestamp"` // DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This // field is set by the server when a graceful deletion is requested by the user, and is not @@ -169,14 +169,14 @@ type Event struct { ObjectMeta `json:"metadata"` Message string `json:"message,omitempty"` Reason string `json:"reason,omitempty"` - Source EventSource `json:"source,omitempty"` // who is emitting this Event - Type string `json:"type,omitempty"` // Normal or Warning + Source EventSource `json:"source"` // who is emitting this Event + Type string `json:"type,omitempty"` // Normal or Warning // InvolvedObject is the subject of the Event. `kubectl describe` will, for most object types, display any // currently present cluster Events matching the object (but you probably want to set UID for this to work). InvolvedObject ObjectReference `json:"involvedObject"` Count int32 `json:"count,omitempty"` // how many times Event was observed - FirstTimestamp time.Time `json:"firstTimestamp,omitempty"` - LastTimestamp time.Time `json:"lastTimestamp,omitempty"` + FirstTimestamp time.Time `json:"firstTimestamp"` + LastTimestamp time.Time `json:"lastTimestamp"` } // EventSource includes a subset of fields from corev1.EventSource. diff --git a/misc/git_hook/githook/pre-push.go b/misc/git_hook/githook/pre-push.go index 9d5624523..dd187e336 100644 --- a/misc/git_hook/githook/pre-push.go +++ b/misc/git_hook/githook/pre-push.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "os/exec" + "slices" "strings" "golang.org/x/mod/modfile" @@ -26,13 +27,7 @@ func CheckGoModReplaces(args []string, watchedRemotes, allowedReplaceDirs []stri } remoteLoc := args[1] - watched := false - for _, r := range watchedRemotes { - if r == remoteLoc { - watched = true - break - } - } + watched := slices.Contains(watchedRemotes, remoteLoc) if !watched { return nil } @@ -69,13 +64,7 @@ func checkCommit(sha string, allowedReplaceDirs []string) error { if !modfile.IsDirectoryPath(r.New.Path) { continue } - allowed := false - for _, a := range allowedReplaceDirs { - if a == r.New.Path { - allowed = true - break - } - } + allowed := slices.Contains(allowedReplaceDirs, r.New.Path) if !allowed { return fmt.Errorf("go.mod contains replace from %v => %v", r.Old.Path, r.New.Path) } diff --git a/net/art/table.go b/net/art/table.go index 447a56b39..43862f7c6 100644 --- a/net/art/table.go +++ b/net/art/table.go @@ -553,10 +553,7 @@ func computePrefixSplit(a, b netip.Prefix) (lastCommon netip.Prefix, aStride, bS panic("computePrefixSplit called with mismatched address families") } - minPrefixLen := a.Bits() - if b.Bits() < minPrefixLen { - minPrefixLen = b.Bits() - } + minPrefixLen := min(b.Bits(), a.Bits()) commonBits := commonBits(a.Addr(), b.Addr(), minPrefixLen) // We want to know how many 8-bit strides are shared between a and diff --git a/net/dns/resolver/forwarder_test.go b/net/dns/resolver/forwarder_test.go index 69a2f2ce0..fd96b3989 100644 --- a/net/dns/resolver/forwarder_test.go +++ b/net/dns/resolver/forwarder_test.go @@ -671,10 +671,7 @@ func makeResponseOfSize(tb testing.TB, domain string, targetSize int, includeOPT if includeOPT { baseSize += 11 // OPT record adds ~11 bytes } - estimatedRecords := (targetSize - baseSize) / bytesPerRecord - if estimatedRecords < 1 { - estimatedRecords = 1 - } + estimatedRecords := max((targetSize-baseSize)/bytesPerRecord, 1) // Start with estimated records and adjust txtLen := 200 @@ -741,10 +738,7 @@ func makeResponseOfSize(tb testing.TB, domain string, targetSize int, includeOPT // If we need too many records, increase TXT length instead txtLen = 255 // Max single TXT string length bytesPerRecord = 280 // Adjusted estimate - estimatedRecords = (targetSize - baseSize) / bytesPerRecord - if estimatedRecords < 1 { - estimatedRecords = 1 - } + estimatedRecords = max((targetSize-baseSize)/bytesPerRecord, 1) } } diff --git a/net/dns/utf.go b/net/dns/utf.go index b18cdebb4..cd10d6fce 100644 --- a/net/dns/utf.go +++ b/net/dns/utf.go @@ -28,10 +28,7 @@ func maybeUnUTF16(bs []byte) []byte { // Can't be complete UTF-16. return bs } - checkLen := 20 - if len(bs) < checkLen { - checkLen = len(bs) - } + checkLen := min(len(bs), 20) zeroOff := bytes.IndexByte(bs[:checkLen], 0) if zeroOff == -1 { return bs diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index 123cbaca5..ea1e24040 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -512,7 +512,7 @@ func TestRecentReportsRetainFullNetcheck(t *testing.T) { const tick = time.Minute start := time.Unix(1700000000, 0) var lastFull time.Time // zero => first report is full, as in GetReport - for i := 0; i < 60; i++ { + for i := range 60 { now = start.Add(time.Duration(i) * tick) // Mirror GetReport's full-vs-incremental decision. diff --git a/portlist/netstat.go b/portlist/netstat.go index de625afb5..c6a944cd6 100644 --- a/portlist/netstat.go +++ b/portlist/netstat.go @@ -26,10 +26,7 @@ func parsePort(s mem.RO) int { // a.b.c.d.1234 or [a:b:c:d].1234 i2 := mem.LastIndexByte(s, '.') - i := i1 - if i2 > i { - i = i2 - } + i := max(i2, i1) if i < 0 { // no match; weird return -1 diff --git a/safesocket/safesocket_darwin.go b/safesocket/safesocket_darwin.go index aa67baaf8..98b879844 100644 --- a/safesocket/safesocket_darwin.go +++ b/safesocket/safesocket_darwin.go @@ -323,11 +323,11 @@ func readMacosSameUserProof() (port int, token string, err error) { subStr := []byte(".tailscale.ipn.macos/sameuserproof-") for bs.Scan() { line := bs.Bytes() - i := bytes.Index(line, subStr) - if i == -1 { + _, after, ok := bytes.Cut(line, subStr) + if !ok { continue } - f := strings.SplitN(string(line[i+len(subStr):]), "-", 2) + f := strings.SplitN(string(after), "-", 2) if len(f) != 2 { continue } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index ceb423536..b58fe133d 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -2469,7 +2469,7 @@ type Oauth2Token struct { // If zero, TokenSource implementations will reuse the same // token forever and RefreshToken or equivalent // mechanisms for that TokenSource will not be used. - Expiry time.Time `json:"expiry,omitempty"` + Expiry time.Time `json:"expiry,omitzero"` } // NodeCapability represents a capability granted to the self node as listed in diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 5f70648a4..4a3b9bcff 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -808,7 +808,7 @@ func FuzzNodeIsRouter(f *testing.F) { decodePrefixes := func(t *testing.T, prefixes string) []netip.Prefix { t.Helper() var out []netip.Prefix - for _, p := range strings.Fields(prefixes) { + for p := range strings.FieldsSeq(prefixes) { pfx, err := netip.ParsePrefix(p) if err != nil { log.Printf("skipping %q: %v", prefixes, err) @@ -1076,7 +1076,7 @@ func TestMarshalToRawMessageAndBack(t *testing.T) { Ports []int `json:"ports,omitempty"` ToggleOn bool `json:"toggleOn,omitempty"` Name string `json:"name,omitempty"` - Groups inner `json:"groups,omitempty"` + Groups inner `json:"groups"` Addrs []netip.AddrPort `json:"addrs"` } tests := []struct { diff --git a/tsnet/listenssh_test.go b/tsnet/listenssh_test.go index 989af1209..eb3384aa8 100644 --- a/tsnet/listenssh_test.go +++ b/tsnet/listenssh_test.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "net" + "slices" "strings" "testing" "time" @@ -71,10 +72,8 @@ func TestListenSSH(t *testing.T) { return err } for _, peer := range st.Peer { - for _, ip := range peer.TailscaleIPs { - if ip == clientIP { - return nil - } + if slices.Contains(peer.TailscaleIPs, clientIP) { + return nil } } return errors.New("clientNode not yet in srvNode's netmap") diff --git a/tstest/natlab/vmtest/tailmac.go b/tstest/natlab/vmtest/tailmac.go index 167feeb04..ad133a1fc 100644 --- a/tstest/natlab/vmtest/tailmac.go +++ b/tstest/natlab/vmtest/tailmac.go @@ -428,11 +428,11 @@ func waitForVMIP(t testing.TB, mac string, timeout time.Duration) (string, error var currentIP string for _, line := range lines { line = strings.TrimSpace(line) - if strings.HasPrefix(line, "ip_address=") { - currentIP = strings.TrimPrefix(line, "ip_address=") + if after, ok := strings.CutPrefix(line, "ip_address="); ok { + currentIP = after } - if strings.HasPrefix(line, "hw_address=") { - hw := strings.TrimPrefix(line, "hw_address=") + if after, ok := strings.CutPrefix(line, "hw_address="); ok { + hw := after if strings.ToLower(hw) == leaseMAC && currentIP != "" { return currentIP, nil } diff --git a/tstest/natlab/vmtest/vmtest.go b/tstest/natlab/vmtest/vmtest.go index d479c15ff..ba401cade 100644 --- a/tstest/natlab/vmtest/vmtest.go +++ b/tstest/natlab/vmtest/vmtest.go @@ -872,7 +872,7 @@ func (e *Env) BringUpMullvadWGServer(n *Node, gw netip.Prefix, listenPort uint16 e.t.Fatalf("BringUpMullvadWGServer(%s): %s: %s", n.name, res.Status, body) } var pubB64 string - for _, line := range strings.Split(string(body), "\n") { + for line := range strings.SplitSeq(string(body), "\n") { if s, ok := strings.CutPrefix(strings.TrimSpace(line), "PUBKEY="); ok { pubB64 = s break @@ -2046,7 +2046,7 @@ func findKernelPath(goMod string) (string, error) { goModCache := strings.TrimSpace(string(goModCacheB)) // Parse go.mod to find gokrazy-kernel version. - for _, line := range strings.Split(string(b), "\n") { + for line := range strings.SplitSeq(string(b), "\n") { line = strings.TrimSpace(line) if strings.HasPrefix(line, "github.com/tailscale/gokrazy-kernel") { parts := strings.Fields(line) diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index a391110ad..c635809b2 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -74,7 +74,7 @@ func parseTrustedCIDRs(raw string) []netip.Prefix { return nil } var prefixes []netip.Prefix - for _, s := range strings.Split(raw, ",") { + for s := range strings.SplitSeq(raw, ",") { s = strings.TrimSpace(s) if s == "" { continue diff --git a/types/logger/tokenbucket.go b/types/logger/tokenbucket.go index fdee56237..7f7d3f35a 100644 --- a/types/logger/tokenbucket.go +++ b/types/logger/tokenbucket.go @@ -43,12 +43,7 @@ func (tb *tokenBucket) Get() bool { } func (tb *tokenBucket) Refund(n int) { - b := tb.remaining + n - if b > tb.max { - tb.remaining = tb.max - } else { - tb.remaining = b - } + tb.remaining = min(tb.remaining+n, tb.max) } func (tb *tokenBucket) AdvanceTo(t time.Time) { diff --git a/util/backoff/backoff.go b/util/backoff/backoff.go index 2edb1e771..d49340b67 100644 --- a/util/backoff/backoff.go +++ b/util/backoff/backoff.go @@ -60,10 +60,7 @@ func (b *Backoff) BackOff(ctx context.Context, err error) { b.n++ // n^2 backoff timer is a little smoother than the // common choice of 2^n. - d := time.Duration(b.n*b.n) * 10 * time.Millisecond - if d > b.maxBackoff { - d = b.maxBackoff - } + d := min(time.Duration(b.n*b.n)*10*time.Millisecond, b.maxBackoff) // Randomize the delay between 0.5-1.5 x msec, in order // to prevent accidental "thundering herd" problems. d = time.Duration(float64(d) * (rand.Float64() + 0.5))