diff --git a/common/certificate/store.go b/common/certificate/store.go index 54b2251f5..f489267d3 100644 --- a/common/certificate/store.go +++ b/common/certificate/store.go @@ -30,7 +30,8 @@ type Store struct { certificatePaths []string certificateDirectoryPaths []string watcher *fswatch.Watcher - platform storePlatform + //nolint:unused // populated only on darwin && cgo via the storePlatform embed. + platform storePlatform } func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) { diff --git a/common/certificate/store_other.go b/common/certificate/store_other.go index c2d68ed21..746520d14 100644 --- a/common/certificate/store_other.go +++ b/common/certificate/store_other.go @@ -2,6 +2,7 @@ package certificate +//nolint:unused // referenced by Store.platform; populated only in store_darwin.go. type storePlatform struct{} func (s *Store) updatePlatformLocked(_ []byte) error { diff --git a/common/httpclient/apple_transport_darwin_test.go b/common/httpclient/apple_transport_darwin_test.go index 17485cc36..3055c25f2 100644 --- a/common/httpclient/apple_transport_darwin_test.go +++ b/common/httpclient/apple_transport_darwin_test.go @@ -532,7 +532,7 @@ func TestAppleTransportRoundTripHTTPS(t *testing.T) { } var normalizedValues []string for _, value := range observed.values { - for _, part := range strings.Split(value, ",") { + for part := range strings.SplitSeq(value, ",") { normalizedValues = append(normalizedValues, strings.TrimSpace(part)) } } @@ -687,7 +687,7 @@ func TestAppleTransportCancellationRecovery(t *testing.T) { }, }) - for index := 0; index < appleHTTPRecoveryLoops; index++ { + for index := range appleHTTPRecoveryLoops { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) request := newAppleHTTPRequestWithContext(t, ctx, http.MethodGet, server.URL("/block"), nil) response, err := transport.RoundTrip(request) diff --git a/common/networkquality/networkquality.go b/common/networkquality/networkquality.go index 8373035d8..3247705d9 100644 --- a/common/networkquality/networkquality.go +++ b/common/networkquality/networkquality.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httptrace" "net/url" + "slices" "sort" "strings" "sync" @@ -513,10 +514,7 @@ func (r *directionRunner) swapIntervalProbeValues() []float64 { } func (r *directionRunner) setResponsivenessWindow(currentInterval int) { - lower := currentInterval - settings.movingAvgDistance + 1 - if lower < 0 { - lower = 0 - } + lower := max(currentInterval-settings.movingAvgDistance+1, 0) r.probeMu.Lock() r.responsivenessWindow = &intervalWindow{lower: lower, upper: currentInterval} r.probeMu.Unlock() @@ -529,10 +527,7 @@ func (r *directionRunner) recordThroughput(interval int, bps float64) { } func (r *directionRunner) setThroughputWindow(currentInterval int) { - lower := currentInterval - settings.movingAvgDistance + 1 - if lower < 0 { - lower = 0 - } + lower := max(currentInterval-settings.movingAvgDistance+1, 0) r.probeMu.Lock() r.throughputWindow = &intervalWindow{lower: lower, upper: currentInterval} r.probeMu.Unlock() @@ -956,7 +951,7 @@ func measureIdleLatency(ctx context.Context, factory MeasurementClientFactory, c maxProbeBytes = measurement.bytes } } - sort.Slice(latencies, func(i, j int) bool { return latencies[i] < latencies[j] }) + slices.Sort(latencies) return int32(latencies[len(latencies)/2]), maxProbeBytes, nil } diff --git a/common/schannel/types_windows.go b/common/schannel/types_windows.go index 5d0bc134a..fd4f8b543 100644 --- a/common/schannel/types_windows.go +++ b/common/schannel/types_windows.go @@ -109,12 +109,12 @@ type schCredentials struct { } type tlsParameters struct { - cAlpnIds uint32 - rgstrAlpnIds uintptr + _ uint32 // cAlpnIds + _ uintptr // rgstrAlpnIds grbitDisabledProtocols uint32 - cDisabledCrypto uint32 - pDisabledCrypto uintptr - dwFlags uint32 + _ uint32 // cDisabledCrypto + _ uintptr // pDisabledCrypto + _ uint32 // dwFlags } type secPkgContextStreamSizes struct { diff --git a/common/tls/acme.go b/common/tls/acme.go index 7491255a1..93f293f6f 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -5,6 +5,7 @@ package tls import ( "context" "crypto/tls" + "slices" "strings" "github.com/sagernet/sing-box/adapter" @@ -70,13 +71,8 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound Logger: zapLogger, } profile := options.Profile - if profile == "" && acmeServer == certmagic.LetsEncryptProductionCA { - for _, domain := range options.Domain { - if certmagic.SubjectIsIP(domain) { - profile = "shortlived" - break - } - } + if profile == "" && acmeServer == certmagic.LetsEncryptProductionCA && slices.ContainsFunc(options.Domain, certmagic.SubjectIsIP) { + profile = "shortlived" } acmeConfig := certmagic.ACMEIssuer{ diff --git a/common/tls/apple_client_platform_test.go b/common/tls/apple_client_platform_test.go index 18d040fb8..c7b93a1ba 100644 --- a/common/tls/apple_client_platform_test.go +++ b/common/tls/apple_client_platform_test.go @@ -39,7 +39,7 @@ var ( func TestAppleClientHandshakeAppliesALPNAndVersion(t *testing.T) { serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost") - for index := 0; index < appleTLSSuccessHandshakeLoops; index++ { + for index := range appleTLSSuccessHandshakeLoops { serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{ Certificates: []stdtls.Certificate{serverCertificate}, MinVersion: stdtls.VersionTLS12, @@ -201,7 +201,7 @@ func TestAppleClientHandshakeRecoversAfterFailure(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - for index := 0; index < appleTLSFailureRecoveryLoops; index++ { + for index := range appleTLSFailureRecoveryLoops { failedResult, failedAddress := startAppleTLSTestServer(t, testCase.serverConfig) failedConn, err := newAppleTestClientConn(t, failedAddress, testCase.clientOptions) if err == nil { @@ -271,7 +271,7 @@ func TestAppleClientConfigCloneWithInlineCertificate(t *testing.T) { t.Fatalf("Clone shares ALPN slice with original: %v", nextProtos) } - for index := 0; index < appleTLSFailureRecoveryLoops; index++ { + for index := range appleTLSFailureRecoveryLoops { serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{ Certificates: []stdtls.Certificate{serverCertificate}, MinVersion: stdtls.VersionTLS12, diff --git a/common/tls/system_client.go b/common/tls/system_client.go index 356030ece..ade2bd363 100644 --- a/common/tls/system_client.go +++ b/common/tls/system_client.go @@ -3,79 +3,16 @@ package tls import ( "context" "crypto/x509" - "net" "os" "strings" "time" "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service" ) -type systemTLSConfig struct { - serverName string - nextProtos []string - handshakeTimeout time.Duration - minVersion uint16 - maxVersion uint16 - insecure bool - anchorOnly bool - certificatePublicKeySHA256 [][]byte - timeFunc func() time.Time - store adapter.CertificateStore -} - -func (c *systemTLSConfig) ServerName() string { - return c.serverName -} - -func (c *systemTLSConfig) SetServerName(serverName string) { - c.serverName = serverName -} - -func (c *systemTLSConfig) NextProtos() []string { - return c.nextProtos -} - -func (c *systemTLSConfig) SetNextProtos(nextProto []string) { - c.nextProtos = append([]string(nil), nextProto...) -} - -func (c *systemTLSConfig) HandshakeTimeout() time.Duration { - return c.handshakeTimeout -} - -func (c *systemTLSConfig) SetHandshakeTimeout(timeout time.Duration) { - c.handshakeTimeout = timeout -} - -func (c *systemTLSConfig) STDConfig() (*STDConfig, error) { - return nil, E.New("STDConfig is unsupported for the system TLS engine") -} - -func (c *systemTLSConfig) Client(conn net.Conn) (Conn, error) { - return nil, os.ErrInvalid -} - -func (c *systemTLSConfig) clone() systemTLSConfig { - return systemTLSConfig{ - serverName: c.serverName, - nextProtos: append([]string(nil), c.nextProtos...), - handshakeTimeout: c.handshakeTimeout, - minVersion: c.minVersion, - maxVersion: c.maxVersion, - insecure: c.insecure, - anchorOnly: c.anchorOnly, - certificatePublicKeySHA256: append([][]byte(nil), c.certificatePublicKeySHA256...), - timeFunc: c.timeFunc, - store: c.store, - } -} - type SystemTLSValidated struct { MinVersion uint16 MaxVersion uint16 @@ -165,38 +102,6 @@ func resolveSystemAnchors(ctx context.Context, options option.OutboundTLSOptions return nil, store.ExclusiveAnchors(), store, nil } -func newSystemTLSConfig(ctx context.Context, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool, engineName string) (systemTLSConfig, SystemTLSValidated, error) { - validated, err := ValidateSystemTLSOptions(ctx, options, engineName) - if err != nil { - return systemTLSConfig{}, SystemTLSValidated{}, err - } - var serverName string - if options.ServerName != "" { - serverName = options.ServerName - } else if serverAddress != "" { - serverName = serverAddress - } - if serverName == "" && !options.Insecure && !allowEmptyServerName { - return systemTLSConfig{}, SystemTLSValidated{}, errMissingServerName - } - handshakeTimeout := C.TCPTimeout - if options.HandshakeTimeout > 0 { - handshakeTimeout = options.HandshakeTimeout.Build() - } - return systemTLSConfig{ - serverName: serverName, - nextProtos: append([]string(nil), options.ALPN...), - handshakeTimeout: handshakeTimeout, - minVersion: validated.MinVersion, - maxVersion: validated.MaxVersion, - insecure: options.Insecure || len(options.CertificatePublicKeySHA256) > 0, - anchorOnly: validated.Exclusive, - certificatePublicKeySHA256: append([][]byte(nil), options.CertificatePublicKeySHA256...), - timeFunc: ntp.TimeFuncFromContext(ctx), - store: validated.Store, - }, validated, nil -} - func verifySystemTLSPeer(roots *x509.CertPool, serverName string, timeFunc func() time.Time, peerCertificates []*x509.Certificate) error { if len(peerCertificates) == 0 { return E.New("no peer certificates") diff --git a/common/tls/system_client_engine.go b/common/tls/system_client_engine.go new file mode 100644 index 000000000..5fc89959f --- /dev/null +++ b/common/tls/system_client_engine.go @@ -0,0 +1,108 @@ +//go:build (darwin && cgo) || windows + +package tls + +import ( + "context" + "net" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/ntp" +) + +type systemTLSConfig struct { + serverName string + nextProtos []string + handshakeTimeout time.Duration + minVersion uint16 + maxVersion uint16 + insecure bool + anchorOnly bool + certificatePublicKeySHA256 [][]byte + timeFunc func() time.Time + store adapter.CertificateStore +} + +func (c *systemTLSConfig) ServerName() string { + return c.serverName +} + +func (c *systemTLSConfig) SetServerName(serverName string) { + c.serverName = serverName +} + +func (c *systemTLSConfig) NextProtos() []string { + return c.nextProtos +} + +func (c *systemTLSConfig) SetNextProtos(nextProto []string) { + c.nextProtos = append([]string(nil), nextProto...) +} + +func (c *systemTLSConfig) HandshakeTimeout() time.Duration { + return c.handshakeTimeout +} + +func (c *systemTLSConfig) SetHandshakeTimeout(timeout time.Duration) { + c.handshakeTimeout = timeout +} + +func (c *systemTLSConfig) STDConfig() (*STDConfig, error) { + return nil, E.New("STDConfig is unsupported for the system TLS engine") +} + +func (c *systemTLSConfig) Client(conn net.Conn) (Conn, error) { + return nil, os.ErrInvalid +} + +func (c *systemTLSConfig) clone() systemTLSConfig { + return systemTLSConfig{ + serverName: c.serverName, + nextProtos: append([]string(nil), c.nextProtos...), + handshakeTimeout: c.handshakeTimeout, + minVersion: c.minVersion, + maxVersion: c.maxVersion, + insecure: c.insecure, + anchorOnly: c.anchorOnly, + certificatePublicKeySHA256: append([][]byte(nil), c.certificatePublicKeySHA256...), + timeFunc: c.timeFunc, + store: c.store, + } +} + +func newSystemTLSConfig(ctx context.Context, serverAddress string, options option.OutboundTLSOptions, allowEmptyServerName bool, engineName string) (systemTLSConfig, SystemTLSValidated, error) { + validated, err := ValidateSystemTLSOptions(ctx, options, engineName) + if err != nil { + return systemTLSConfig{}, SystemTLSValidated{}, err + } + var serverName string + if options.ServerName != "" { + serverName = options.ServerName + } else if serverAddress != "" { + serverName = serverAddress + } + if serverName == "" && !options.Insecure && !allowEmptyServerName { + return systemTLSConfig{}, SystemTLSValidated{}, errMissingServerName + } + handshakeTimeout := C.TCPTimeout + if options.HandshakeTimeout > 0 { + handshakeTimeout = options.HandshakeTimeout.Build() + } + return systemTLSConfig{ + serverName: serverName, + nextProtos: append([]string(nil), options.ALPN...), + handshakeTimeout: handshakeTimeout, + minVersion: validated.MinVersion, + maxVersion: validated.MaxVersion, + insecure: options.Insecure || len(options.CertificatePublicKeySHA256) > 0, + anchorOnly: validated.Exclusive, + certificatePublicKeySHA256: append([][]byte(nil), options.CertificatePublicKeySHA256...), + timeFunc: ntp.TimeFuncFromContext(ctx), + store: validated.Store, + }, validated, nil +} diff --git a/common/tls/windows_client_test.go b/common/tls/windows_client_test.go index 13c46522a..755aea59d 100644 --- a/common/tls/windows_client_test.go +++ b/common/tls/windows_client_test.go @@ -1110,7 +1110,7 @@ func TestWindowsClientMultipleRoundtrips(t *testing.T) { clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12) defer clientConn.Close() - for i := 0; i < 100; i++ { + for i := range 100 { payload := []byte("msg" + string(rune('A'+(i%26)))) _, err := clientConn.Write(payload) if err != nil { @@ -1148,7 +1148,7 @@ func TestWindowsClientConcurrentReadWrite(t *testing.T) { readErr := make(chan error, 1) readBack := make(chan []byte, messageCount) go func() { - for i := 0; i < messageCount; i++ { + for range messageCount { reply := make([]byte, messageSize) _, err := io.ReadFull(clientConn, reply) if err != nil { @@ -1171,7 +1171,7 @@ func TestWindowsClientConcurrentReadWrite(t *testing.T) { if readResult != nil { t.Fatal(readResult) } - for i := 0; i < messageCount; i++ { + for i := range messageCount { got := <-readBack if !bytes.Equal(payloads[i], got) { t.Fatalf("iteration %d: payload mismatch", i) diff --git a/common/tlsspoof/endpoints.go b/common/tlsspoof/endpoints.go index 6be458c85..2cc8df122 100644 --- a/common/tlsspoof/endpoints.go +++ b/common/tlsspoof/endpoints.go @@ -1,3 +1,5 @@ +//go:build linux || darwin || (windows && (amd64 || 386)) + package tlsspoof import ( diff --git a/common/tlsspoof/packet.go b/common/tlsspoof/packet.go index bee53f537..2b0210c07 100644 --- a/common/tlsspoof/packet.go +++ b/common/tlsspoof/packet.go @@ -1,3 +1,5 @@ +//go:build linux || darwin || (windows && (amd64 || 386)) + package tlsspoof import ( @@ -94,18 +96,6 @@ func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNe return buildTCPSegment(src, dst, packetInfo, payload), nil } -// buildSpoofTCPSegment returns a TCP segment without an IP header, for -// platforms where the kernel synthesises the IP header (darwin IPv6). -func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { - packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) - if err != nil { - return nil, err - } - segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) - encodeTCP(segment, 0, src, dst, packetInfo, payload) - return segment, nil -} - func resolveSpoofPacketInfo(method Method, sendNext, receiveNext, timestamp uint32, tcpOptions, payload []byte) (spoofPacketInfo, error) { packetInfo := spoofPacketInfo{seqNum: sendNext, ackNum: receiveNext} switch method { diff --git a/common/tlsspoof/packet_darwin.go b/common/tlsspoof/packet_darwin.go new file mode 100644 index 000000000..4d12bd37e --- /dev/null +++ b/common/tlsspoof/packet_darwin.go @@ -0,0 +1,15 @@ +package tlsspoof + +import "net/netip" + +// buildSpoofTCPSegment returns a TCP segment without an IP header, for +// platforms where the kernel synthesises the IP header (darwin IPv6). +func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext, timestamp uint32, payload []byte) ([]byte, error) { + packetInfo, err := resolveSpoofPacketInfo(method, sendNext, receiveNext, timestamp, nil, payload) + if err != nil { + return nil, err + } + segment := make([]byte, tcpHeaderLen+len(packetInfo.options)+len(payload)) + encodeTCP(segment, 0, src, dst, packetInfo, payload) + return segment, nil +} diff --git a/common/tlsspoof/testdata_test.go b/common/tlsspoof/testdata_test.go index 85e74c524..2596ebf61 100644 --- a/common/tlsspoof/testdata_test.go +++ b/common/tlsspoof/testdata_test.go @@ -1,3 +1,5 @@ +//go:build linux || darwin || (windows && (amd64 || 386)) + package tlsspoof // realClientHello is a captured Chrome ClientHello for github.com. diff --git a/common/windivert/integration_windows_test.go b/common/windivert/integration_windows_test.go index 00ab89709..1b4ce958c 100644 --- a/common/windivert/integration_windows_test.go +++ b/common/windivert/integration_windows_test.go @@ -72,14 +72,14 @@ func TestIntegrationRecvAbortsOnClose(t *testing.T) { func TestIntegrationConcurrentOpen(t *testing.T) { errCh := make(chan error, 2) handles := make(chan *Handle, 2) - for i := 0; i < 2; i++ { + for range 2 { go func() { h, err := Open(nil, LayerNetwork, 0, FlagSendOnly) handles <- h errCh <- err }() } - for i := 0; i < 2; i++ { + for range 2 { err := <-errCh h := <-handles require.NoError(t, err) diff --git a/common/windivert/windivert.go b/common/windivert/windivert.go index 9d309886c..619e41b61 100644 --- a/common/windivert/windivert.go +++ b/common/windivert/windivert.go @@ -48,7 +48,7 @@ type Address struct { Timestamp int64 bits uint32 Reserved2 uint32 - union [64]byte + _ [64]byte } var _ [80]byte = [unsafe.Sizeof(Address{})]byte{} diff --git a/dns/client.go b/dns/client.go index e8c2e5849..f01605109 100644 --- a/dns/client.go +++ b/dns/client.go @@ -61,10 +61,7 @@ type ClientOptions struct { } func NewClient(options ClientOptions) *Client { - cacheCapacity := options.CacheCapacity - if cacheCapacity < 1024 { - cacheCapacity = 1024 - } + cacheCapacity := max(options.CacheCapacity, 1024) client := &Client{ ctx: options.Context, timeout: options.Timeout, @@ -426,10 +423,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp c.cache.Remove(key) return nil, 0, false } - nowTTL := int(expireAt.Sub(timeNow).Seconds()) - if nowTTL < 0 { - nowTTL = 0 - } + nowTTL := max(int(expireAt.Sub(timeNow).Seconds()), 0) response = response.Copy() normalizeTTL(response, uint32(nowTTL)) return response, nowTTL, false diff --git a/dns/router.go b/dns/router.go index ceb9ea2cd..7c13c551d 100644 --- a/dns/router.go +++ b/dns/router.go @@ -1011,33 +1011,6 @@ func lookupDNSRuleSetMetadata(router adapter.Router, tag string, metadataOverrid return ruleSet.Metadata(), nil } -func referencedDNSRuleSetTags(rules []option.DNSRule) []string { - tagMap := make(map[string]bool) - var walkRule func(rule option.DNSRule) - walkRule = func(rule option.DNSRule) { - switch rule.Type { - case "", C.RuleTypeDefault: - for _, tag := range rule.DefaultOptions.RuleSet { - tagMap[tag] = true - } - case C.RuleTypeLogical: - for _, subRule := range rule.LogicalOptions.Rules { - walkRule(subRule) - } - } - } - for _, rule := range rules { - walkRule(rule) - } - tags := make([]string, 0, len(tagMap)) - for tag := range tagMap { - if tag != "" { - tags = append(tags, tag) - } - } - return tags -} - func validateLegacyDNSModeDisabledRules(router adapter.Router, rules []option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) error { var seenEvaluate bool for i, rule := range rules { diff --git a/dns/router_test.go b/dns/router_test.go index 206eae73b..b5a7ebcfb 100644 --- a/dns/router_test.go +++ b/dns/router_test.go @@ -4,7 +4,6 @@ import ( "context" "net" "net/netip" - "strings" "sync" "testing" "time" @@ -113,14 +112,6 @@ func (r *fakeRouter) RuleSet(tag string) (adapter.RuleSet, bool) { return ruleSet, loaded } -func (r *fakeRouter) setRuleSet(tag string, ruleSet adapter.RuleSet) { - r.access.Lock() - defer r.access.Unlock() - if r.ruleSets == nil { - r.ruleSets = make(map[string]adapter.RuleSet) - } - r.ruleSets[tag] = ruleSet -} func (r *fakeRouter) Rules() []adapter.Rule { return nil } func (r *fakeRouter) NeedFindProcess() bool { return false } func (r *fakeRouter) NeedFindNeighbor() bool { return false } @@ -210,12 +201,6 @@ func (s *fakeRuleSet) updateMetadata(metadata adapter.RuleSetMetadata) { } } -func (s *fakeRuleSet) snapshotCallbacks() []adapter.RuleSetUpdateCallback { - s.access.Lock() - defer s.access.Unlock() - return s.callbacks.Array() -} - func (s *fakeRuleSet) refCount() int { s.access.Lock() defer s.access.Unlock() @@ -322,26 +307,6 @@ func newTestRouterWithContextAndLogger(t *testing.T, ctx context.Context, rules return router } -func waitForLogMessageContaining(t *testing.T, entries <-chan log.Entry, done <-chan struct{}, substring string) log.Entry { - t.Helper() - timeout := time.After(time.Second) - for { - select { - case entry, ok := <-entries: - if !ok { - t.Fatal("log subscription closed") - } - if strings.Contains(entry.Message, substring) { - return entry - } - case <-done: - t.Fatal("log subscription closed") - case <-timeout: - t.Fatalf("timed out waiting for log message containing %q", substring) - } - } -} - func fixedQuestion(name string, qType uint16) mDNS.Question { return mDNS.Question{ Name: mDNS.Fqdn(name), diff --git a/dns/transport/local/local_darwin_stun.go b/dns/transport/local/local_darwin_stun.go new file mode 100644 index 000000000..b99833c26 --- /dev/null +++ b/dns/transport/local/local_darwin_stun.go @@ -0,0 +1,15 @@ +//go:build darwin && !cgo + +package local + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + + mDNS "github.com/miekg/dns" +) + +func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + return nil, E.New(`local DNS server requires CGO on darwin, rebuild with CGO_ENABLED=1`) +} diff --git a/experimental/libbox/internal/oomprofile/builder.go b/experimental/libbox/internal/oomprofile/builder.go index 1f59078a2..9f95456f2 100644 --- a/experimental/libbox/internal/oomprofile/builder.go +++ b/experimental/libbox/internal/oomprofile/builder.go @@ -374,10 +374,6 @@ func (b *profileBuilder) emitLocation() uint64 { return id } -func (b *profileBuilder) addMapping(lo uint64, hi uint64, offset uint64, file string, buildID string) { - b.addMappingEntry(lo, hi, offset, file, buildID, false) -} - func (b *profileBuilder) addMappingEntry(lo uint64, hi uint64, offset uint64, file string, buildID string, fake bool) { b.mem = append(b.mem, memMap{ start: uintptr(lo), diff --git a/experimental/libbox/internal/oomprofile/mapping_darwin.go b/experimental/libbox/internal/oomprofile/mapping_darwin.go index e27300056..a686a0485 100644 --- a/experimental/libbox/internal/oomprofile/mapping_darwin.go +++ b/experimental/libbox/internal/oomprofile/mapping_darwin.go @@ -14,7 +14,10 @@ func isExecutable(protection int32) bool { } func (b *profileBuilder) readMapping() { - if !machVMInfo(b.addMapping) { + added := machVMInfo(func(lo, hi, offset uint64, file, buildID string) { + b.addMappingEntry(lo, hi, offset, file, buildID, false) + }) + if !added { b.addMappingEntry(0, 0, 0, "", "", true) } } diff --git a/experimental/libbox/internal/oomprofile/mapping_linux.go b/experimental/libbox/internal/oomprofile/mapping_linux.go index cc9b03a6d..ddb2eaaab 100644 --- a/experimental/libbox/internal/oomprofile/mapping_linux.go +++ b/experimental/libbox/internal/oomprofile/mapping_linux.go @@ -6,7 +6,9 @@ import "os" func (b *profileBuilder) readMapping() { data, _ := os.ReadFile("/proc/self/maps") - stdParseProcSelfMaps(data, b.addMapping) + stdParseProcSelfMaps(data, func(lo, hi, offset uint64, file, buildID string) { + b.addMappingEntry(lo, hi, offset, file, buildID, false) + }) if len(b.mem) == 0 { b.addMappingEntry(0, 0, 0, "", "", true) } diff --git a/experimental/libbox/internal/oomprofile/oomprofile.go b/experimental/libbox/internal/oomprofile/oomprofile.go index cd0b9bec0..0728af8f4 100644 --- a/experimental/libbox/internal/oomprofile/oomprofile.go +++ b/experimental/libbox/internal/oomprofile/oomprofile.go @@ -320,7 +320,7 @@ func writeHeapProto(w io.Writer, profile []memProfileRecord, rate int64, default var locs []uint64 for _, record := range profile { hideRuntime := true - for tries := 0; tries < 2; tries++ { + for range 2 { stk := record.Stack if hideRuntime { for i, addr := range stk { diff --git a/experimental/libbox/neighbor.go b/experimental/libbox/neighbor.go index e38aa8023..b5cbe7796 100644 --- a/experimental/libbox/neighbor.go +++ b/experimental/libbox/neighbor.go @@ -1,10 +1,5 @@ package libbox -import ( - "net" - "net/netip" -) - type NeighborEntry struct { Address string MacAddress string @@ -23,31 +18,3 @@ type NeighborSubscription struct { func (s *NeighborSubscription) Close() { close(s.done) } - -func tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator { - entries := make([]*NeighborEntry, 0, len(table)) - for address, mac := range table { - entries = append(entries, &NeighborEntry{ - Address: address.String(), - MacAddress: mac.String(), - }) - } - return &neighborEntryIterator{entries} -} - -type neighborEntryIterator struct { - entries []*NeighborEntry -} - -func (i *neighborEntryIterator) HasNext() bool { - return len(i.entries) > 0 -} - -func (i *neighborEntryIterator) Next() *NeighborEntry { - if len(i.entries) == 0 { - return nil - } - entry := i.entries[0] - i.entries = i.entries[1:] - return entry -} diff --git a/experimental/libbox/neighbor_unix.go b/experimental/libbox/neighbor_unix.go new file mode 100644 index 000000000..c854d0f9a --- /dev/null +++ b/experimental/libbox/neighbor_unix.go @@ -0,0 +1,36 @@ +//go:build linux || darwin + +package libbox + +import ( + "net" + "net/netip" +) + +func tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator { + entries := make([]*NeighborEntry, 0, len(table)) + for address, mac := range table { + entries = append(entries, &NeighborEntry{ + Address: address.String(), + MacAddress: mac.String(), + }) + } + return &neighborEntryIterator{entries} +} + +type neighborEntryIterator struct { + entries []*NeighborEntry +} + +func (i *neighborEntryIterator) HasNext() bool { + return len(i.entries) > 0 +} + +func (i *neighborEntryIterator) Next() *NeighborEntry { + if len(i.entries) == 0 { + return nil + } + entry := i.entries[0] + i.entries = i.entries[1:] + return entry +} diff --git a/option/rule_nested.go b/option/rule_nested.go index 172165729..a48c17ea3 100644 --- a/option/rule_nested.go +++ b/option/rule_nested.go @@ -3,6 +3,7 @@ package option import ( "context" "reflect" + "slices" "strings" E "github.com/sagernet/sing/common/exceptions" @@ -59,12 +60,7 @@ func hasAnyJSONKey(ctx context.Context, content []byte, keys ...string) (bool, e if err != nil { return false, err } - for _, key := range keys { - if object.ContainsKey(key) { - return true, nil - } - } - return false, nil + return slices.ContainsFunc(keys, object.ContainsKey), nil } func inspectRouteRuleAction(ctx context.Context, content []byte) (string, RouteActionOptions, error) { diff --git a/route/neighbor_resolver_lease.go b/route/neighbor_resolver_lease.go index e3f9c0b46..38f1fbf06 100644 --- a/route/neighbor_resolver_lease.go +++ b/route/neighbor_resolver_lease.go @@ -220,20 +220,20 @@ func parseISCDhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToH if !inLease { continue } - if strings.HasPrefix(line, "hardware ethernet ") { - macString := strings.TrimSuffix(strings.TrimPrefix(line, "hardware ethernet "), ";") + if rest, ok := strings.CutPrefix(line, "hardware ethernet "); ok { + macString := strings.TrimSuffix(rest, ";") parsed, macErr := net.ParseMAC(macString) if macErr == nil { currentMAC = parsed } - } else if strings.HasPrefix(line, "client-hostname ") { - hostname := strings.TrimSuffix(strings.TrimPrefix(line, "client-hostname "), ";") + } else if rest, ok := strings.CutPrefix(line, "client-hostname "); ok { + hostname := strings.TrimSuffix(rest, ";") hostname = strings.Trim(hostname, "\"") if hostname != "" { currentHostname = hostname } - } else if strings.HasPrefix(line, "binding state ") { - state := strings.TrimSuffix(strings.TrimPrefix(line, "binding state "), ";") + } else if rest, ok := strings.CutPrefix(line, "binding state "); ok { + state := strings.TrimSuffix(rest, ";") currentActive = state == "active" } } diff --git a/route/rule/rule_item_cidr.go b/route/rule/rule_item_cidr.go index f92845db4..9780a8ec4 100644 --- a/route/rule/rule_item_cidr.go +++ b/route/rule/rule_item_cidr.go @@ -2,6 +2,7 @@ package rule import ( "net/netip" + "slices" "strings" "github.com/sagernet/sing-box/adapter" @@ -84,12 +85,7 @@ func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool { // does not expose any address answers for matching. return metadata.IPCIDRAcceptEmpty } - for _, address := range addresses { - if r.ipSet.Contains(address) { - return true - } - } - return false + return slices.ContainsFunc(addresses, r.ipSet.Contains) } if metadata.Destination.IsIP() { return r.ipSet.Contains(metadata.Destination.Addr) diff --git a/route/rule/rule_item_package_name_regex.go b/route/rule/rule_item_package_name_regex.go index 9db4504ac..e329b6278 100644 --- a/route/rule/rule_item_package_name_regex.go +++ b/route/rule/rule_item_package_name_regex.go @@ -2,6 +2,7 @@ package rule import ( "regexp" + "slices" "strings" "github.com/sagernet/sing-box/adapter" @@ -42,10 +43,8 @@ func (r *PackageNameRegexItem) Match(metadata *adapter.InboundContext) bool { return false } for _, matcher := range r.matchers { - for _, packageName := range metadata.ProcessInfo.AndroidPackageNames { - if matcher.MatchString(packageName) { - return true - } + if slices.ContainsFunc(metadata.ProcessInfo.AndroidPackageNames, matcher.MatchString) { + return true } } return false diff --git a/route/rule/rule_item_response_record.go b/route/rule/rule_item_response_record.go index 3a2c889be..1431fd9b0 100644 --- a/route/rule/rule_item_response_record.go +++ b/route/rule/rule_item_response_record.go @@ -1,6 +1,7 @@ package rule import ( + "slices" "strings" "github.com/sagernet/sing-box/adapter" @@ -31,10 +32,8 @@ func (r *DNSResponseRecordItem) Match(metadata *adapter.InboundContext) bool { } records := r.selector(metadata.DNSResponse) for _, expected := range r.records { - for _, record := range records { - if expected.Match(record) { - return true - } + if slices.ContainsFunc(records, expected.Match) { + return true } } return false diff --git a/route/rule/rule_set_semantics_test.go b/route/rule/rule_set_semantics_test.go index 4f6cf21b0..d93dd420d 100644 --- a/route/rule/rule_set_semantics_test.go +++ b/route/rule/rule_set_semantics_test.go @@ -769,7 +769,6 @@ func TestDNSAddressLimitIgnoresDestinationAddresses(t *testing.T) { }, } for _, testCase := range testCases { - testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() rule := dnsRuleForTest(func(rule *abstractDefaultRule) { @@ -825,7 +824,6 @@ func TestDNSLegacyAddressLimitPreLookupDefersDirectRules(t *testing.T) { }, } for _, testCase := range testCases { - testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -925,7 +923,6 @@ func TestDNSLegacyInvertAddressLimitPreLookupRegression(t *testing.T) { }, } for _, testCase := range testCases { - testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() diff --git a/service/acme/service.go b/service/acme/service.go index b73ffb9da..1b9677099 100644 --- a/service/acme/service.go +++ b/service/acme/service.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "reflect" + "slices" "strings" "time" "unsafe" @@ -113,13 +114,8 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s } profile := options.Profile - if profile == "" && acmeServer == certmagic.LetsEncryptProductionCA { - for _, domain := range options.Domain { - if certmagic.SubjectIsIP(domain) { - profile = "shortlived" - break - } - } + if profile == "" && acmeServer == certmagic.LetsEncryptProductionCA && slices.ContainsFunc(options.Domain, certmagic.SubjectIsIP) { + profile = "shortlived" } acmeIssuer := certmagic.ACMEIssuer{ diff --git a/service/oomkiller/service.go b/service/oomkiller/service.go index 03fbdb198..bbf032d0f 100644 --- a/service/oomkiller/service.go +++ b/service/oomkiller/service.go @@ -31,6 +31,7 @@ type Service struct { timerConfig timerConfig adaptiveTimer *adaptiveTimer lastReportTime atomic.Int64 + //nolint:unused // touched only on darwin && cgo via writeOOMDraft/discardOOMDraft. draftCancelled atomic.Bool } @@ -84,37 +85,3 @@ func (s *Service) writeOOMReport(memoryUsage uint64) { s.logger.Info("OOM report saved") } } - -func (s *Service) writeOOMDraft(memoryUsage uint64) { - if s.draftCancelled.Load() { - return - } - reporter := service.FromContext[OOMReporter](s.ctx) - if reporter == nil { - return - } - err := reporter.WriteDraft(memoryUsage) - if s.draftCancelled.Load() { - reporter.DiscardDraft() - return - } - if err != nil { - s.logger.Warn("failed to write OOM draft: ", err) - } else { - s.logger.Warn("OOM draft saved") - } -} - -func (s *Service) discardOOMDraft() { - s.draftCancelled.Store(true) - reporter := service.FromContext[OOMReporter](s.ctx) - if reporter == nil { - return - } - err := reporter.DiscardDraft() - if err != nil { - s.logger.Warn("failed to discard OOM draft: ", err) - } else { - s.logger.Info("OOM draft discarded") - } -} diff --git a/service/oomkiller/service_darwin.go b/service/oomkiller/service_darwin.go index ad3164b83..ff2d489d1 100644 --- a/service/oomkiller/service_darwin.go +++ b/service/oomkiller/service_darwin.go @@ -38,6 +38,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common/byteformats" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service" ) var ( @@ -103,3 +104,37 @@ func goMemoryPressureCallback(status C.ulong) { s.adaptiveTimer.notifyPressure() } } + +func (s *Service) writeOOMDraft(memoryUsage uint64) { + if s.draftCancelled.Load() { + return + } + reporter := service.FromContext[OOMReporter](s.ctx) + if reporter == nil { + return + } + err := reporter.WriteDraft(memoryUsage) + if s.draftCancelled.Load() { + reporter.DiscardDraft() + return + } + if err != nil { + s.logger.Warn("failed to write OOM draft: ", err) + } else { + s.logger.Warn("OOM draft saved") + } +} + +func (s *Service) discardOOMDraft() { + s.draftCancelled.Store(true) + reporter := service.FromContext[OOMReporter](s.ctx) + if reporter == nil { + return + } + err := reporter.DiscardDraft() + if err != nil { + s.logger.Warn("failed to discard OOM draft: ", err) + } else { + s.logger.Info("OOM draft discarded") + } +} diff --git a/service/oomkiller/timer.go b/service/oomkiller/timer.go index 47fb68563..6e9db4c1d 100644 --- a/service/oomkiller/timer.go +++ b/service/oomkiller/timer.go @@ -134,15 +134,6 @@ func (t *adaptiveTimer) start() { t.startLocked() } -func (t *adaptiveTimer) notifyPressure() { - t.access.Lock() - t.startLocked() - t.forceMinInterval = true - t.pendingPressureBaseline = true - t.access.Unlock() - t.poll() -} - func (t *adaptiveTimer) startLocked() { if t.timer != nil { return diff --git a/service/oomkiller/timer_darwin.go b/service/oomkiller/timer_darwin.go new file mode 100644 index 000000000..6c4f7efab --- /dev/null +++ b/service/oomkiller/timer_darwin.go @@ -0,0 +1,12 @@ +//go:build darwin && cgo + +package oomkiller + +func (t *adaptiveTimer) notifyPressure() { + t.access.Lock() + t.startLocked() + t.forceMinInterval = true + t.pendingPressureBaseline = true + t.access.Unlock() + t.poll() +} diff --git a/service/origin_ca/service.go b/service/origin_ca/service.go index d0a444212..e9fca42c6 100644 --- a/service/origin_ca/service.go +++ b/service/origin_ca/service.go @@ -210,10 +210,7 @@ func (s *Service) refreshLoop() { waitDuration = minimumRenewRetryDelay } else { refreshAt := leaf.NotAfter.Add(-s.effectiveRenewBefore(leaf)) - waitDuration = refreshAt.Sub(s.timeFunc()) - if waitDuration < minimumRenewRetryDelay { - waitDuration = minimumRenewRetryDelay - } + waitDuration = max(refreshAt.Sub(s.timeFunc()), minimumRenewRetryDelay) } } timer := time.NewTimer(waitDuration)