Fix lint errors

This commit is contained in:
世界
2026-05-13 22:37:05 +08:00
parent 429182a349
commit a47f39fa2b
79 changed files with 219 additions and 667 deletions

View File

@@ -46,7 +46,7 @@ lint:
GOOS=android golangci-lint run ./... GOOS=android golangci-lint run ./...
GOOS=windows golangci-lint run ./... GOOS=windows golangci-lint run ./...
GOOS=darwin golangci-lint run ./... GOOS=darwin golangci-lint run ./...
GOOS=freebsd golangci-lint run ./... # GOOS=freebsd golangci-lint run ./...
lint_install: lint_install:
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest

View File

@@ -48,8 +48,8 @@ func GetRuntimeEnv(key string) (string, error) {
if readErr != nil { if readErr != nil {
return "", readErr return "", readErr
} }
envStrings := strings.Split(string(data), "\n") envStrings := strings.SplitSeq(string(data), "\n")
for _, envItem := range envStrings { for envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r") envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=") envKeyValue := strings.Split(envItem, "=")
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) { if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {

View File

@@ -39,7 +39,7 @@ func main() {
common.Must(os.Chdir(androidPath)) common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("version.properties")) localProps := common.Must1(os.ReadFile("version.properties"))
var propsList [][]string var propsList [][]string
for _, propLine := range strings.Split(string(localProps), "\n") { for propLine := range strings.SplitSeq(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "=")) propsList = append(propsList, strings.Split(propLine, "="))
} }
var ( var (

View File

@@ -61,16 +61,17 @@ func geoipExport(countryCode string) error {
outputFile *os.File outputFile *os.File
outputWriter io.Writer outputWriter io.Writer
) )
if flagGeoipExportOutput == "stdout" { switch flagGeoipExportOutput {
case "stdout":
outputWriter = os.Stdout outputWriter = os.Stdout
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput { case flagGeoipExportDefaultOutput:
outputFile, err = os.Create("geoip-" + countryCode + ".json") outputFile, err = os.Create("geoip-" + countryCode + ".json")
if err != nil { if err != nil {
return err return err
} }
defer outputFile.Close() defer outputFile.Close()
outputWriter = outputFile outputWriter = outputFile
} else { default:
outputFile, err = os.Create(flagGeoipExportOutput) outputFile, err = os.Create(flagGeoipExportOutput)
if err != nil { if err != nil {
return err return err

View File

@@ -43,16 +43,17 @@ func geositeExport(category string) error {
outputFile *os.File outputFile *os.File
outputWriter io.Writer outputWriter io.Writer
) )
if commandGeositeExportOutput == "stdout" { switch commandGeositeExportOutput {
case "stdout":
outputWriter = os.Stdout outputWriter = os.Stdout
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput { case commandGeositeExportDefaultOutput:
outputFile, err = os.Create("geosite-" + category + ".json") outputFile, err = os.Create("geosite-" + category + ".json")
if err != nil { if err != nil {
return err return err
} }
defer outputFile.Close() defer outputFile.Close()
outputWriter = outputFile outputWriter = outputFile
} else { default:
outputFile, err = os.Create(commandGeositeExportOutput) outputFile, err = os.Create(commandGeositeExportOutput)
if err != nil { if err != nil {
return err return err

View File

@@ -112,9 +112,7 @@ func IsValid(versionName string) bool {
} }
func Parse(versionName string) (version Version) { func Parse(versionName string) (version Version) {
if strings.HasPrefix(versionName, "v") { versionName = strings.TrimPrefix(versionName, "v")
versionName = versionName[1:]
}
if strings.Contains(versionName, "-") { if strings.Contains(versionName, "-") {
parts := strings.Split(versionName, "-") parts := strings.Split(versionName, "-")
versionName = parts[0] versionName = parts[0]

View File

@@ -63,9 +63,7 @@ parseLine:
} }
continue continue
} }
if strings.HasSuffix(ruleLine, "|") { ruleLine = strings.TrimSuffix(ruleLine, "|")
ruleLine = ruleLine[:len(ruleLine)-1]
}
var ( var (
isExclude bool isExclude bool
isSuffix bool isSuffix bool
@@ -76,7 +74,7 @@ parseLine:
) )
if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") { if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") {
params := common.SubstringAfter(ruleLine, "$") params := common.SubstringAfter(ruleLine, "$")
for _, param := range strings.Split(params, ",") { for param := range strings.SplitSeq(params, ",") {
paramParts := strings.Split(param, "=") paramParts := strings.Split(param, "=")
var ignored bool var ignored bool
if len(paramParts) > 0 && len(paramParts) <= 2 { if len(paramParts) > 0 && len(paramParts) <= 2 {
@@ -106,9 +104,7 @@ parseLine:
ruleLine = ruleLine[2:] ruleLine = ruleLine[2:]
isExclude = true isExclude = true
} }
if strings.HasSuffix(ruleLine, "|") { ruleLine = strings.TrimSuffix(ruleLine, "|")
ruleLine = ruleLine[:len(ruleLine)-1]
}
if strings.HasPrefix(ruleLine, "||") { if strings.HasPrefix(ruleLine, "||") {
ruleLine = ruleLine[2:] ruleLine = ruleLine[2:]
isSuffix = true isSuffix = true
@@ -414,18 +410,18 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
} }
func parseAdGuardHostLine(ruleLine string) (string, error) { func parseAdGuardHostLine(ruleLine string) (string, error) {
idx := strings.Index(ruleLine, " ") before, after, ok := strings.Cut(ruleLine, " ")
if idx == -1 { if !ok {
return "", os.ErrInvalid return "", os.ErrInvalid
} }
address, err := netip.ParseAddr(ruleLine[:idx]) address, err := netip.ParseAddr(before)
if err != nil { if err != nil {
return "", err return "", err
} }
if !address.IsUnspecified() { if !address.IsUnspecified() {
return "", nil return "", nil
} }
domain := ruleLine[idx+1:] domain := after
if !M.IsDomainName(domain) { if !M.IsDomainName(domain) {
return "", E.New("invalid domain name: ", domain) return "", E.New("invalid domain name: ", domain)
} }

View File

@@ -136,18 +136,16 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
go startRacer(fallbackCtx, false, iif) go startRacer(fallbackCtx, false, iif)
} }
var errors []error var errors []error
for { for res := range results {
select { if res.error == nil {
case res := <-results: return res.Conn, res.primary, nil
if res.error == nil { }
return res.Conn, res.primary, nil errors = append(errors, res.error)
} if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
errors = append(errors, res.error) return nil, false, E.Errors(errors...)
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
return nil, false, E.Errors(errors...)
}
} }
} }
return nil, false, E.Errors(errors...)
} }
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {

View File

@@ -19,11 +19,6 @@ func oldWriteString(writer varbin.Writer, value string) error {
return varbin.Write(writer, binary.BigEndian, value) return varbin.Write(writer, binary.BigEndian, value)
} }
func oldWriteItem(writer varbin.Writer, item Item) error {
//nolint:staticcheck
return varbin.Write(writer, binary.BigEndian, item)
}
func oldReadString(reader varbin.Reader) (string, error) { func oldReadString(reader varbin.Reader) (string, error) {
//nolint:staticcheck //nolint:staticcheck
return varbin.ReadValue[string](reader, binary.BigEndian) return varbin.ReadValue[string](reader, binary.BigEndian)
@@ -224,7 +219,7 @@ func TestGeositeWriteReadCompat(t *testing.T) {
func generateLargeItems(count int) map[string][]Item { func generateLargeItems(count int) map[string][]Item {
items := make([]Item, count) items := make([]Item, count)
for i := 0; i < count; i++ { for i := range count {
items[i] = Item{ items[i] = Item{
Type: ItemType(i % 4), Type: ItemType(i % 4),
Value: strings.Repeat("x", i%200) + ".com", Value: strings.Repeat("x", i%200) + ".com",

View File

@@ -48,12 +48,6 @@ func NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) {
return reader, codes, nil return reader, codes, nil
} }
type geositeMetadata struct {
Code string
Index uint64
Length uint64
}
func (r *Reader) readMetadata() error { func (r *Reader) readMetadata() error {
counter := &readCounter{Reader: r.reader} counter := &readCounter{Reader: r.reader}
reader := bufio.NewReader(counter) reader := bufio.NewReader(counter)
@@ -101,6 +95,9 @@ func (r *Reader) readMetadata() error {
} }
func (r *Reader) Read(code string) ([]Item, error) { func (r *Reader) Read(code string) ([]Item, error) {
r.access.Lock()
defer r.access.Unlock()
index, exists := r.domainIndex[code] index, exists := r.domainIndex[code]
if !exists { if !exists {
return nil, E.New("code ", code, " not exists!") return nil, E.New("code ", code, " not exists!")

View File

@@ -131,7 +131,7 @@ func (j *ClientHello) parseHandshake(hs []byte) error {
return &ParseError{LengthErr, 7} return &ParseError{LengthErr, 7}
} }
for i := 0; i < numCiphers; i++ { for i := range numCiphers {
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1]) cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
cipherSuites = append(cipherSuites, cipherSuite) cipherSuites = append(cipherSuites, cipherSuite)
} }
@@ -234,7 +234,7 @@ func (j *ClientHello) parseExtensions(exs []byte) error {
return &ParseError{LengthErr, 16} return &ParseError{LengthErr, 16}
} }
for i := 0; i < numCurves; i++ { for i := range numCurves {
ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2]) ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2])
ellipticCurves = append(ellipticCurves, ecType) ellipticCurves = append(ellipticCurves, ecType)
} }
@@ -256,7 +256,7 @@ func (j *ClientHello) parseExtensions(exs []byte) error {
return &ParseError{LengthErr, 18} return &ParseError{LengthErr, 18}
} }
for i := 0; i < numPF; i++ { for i := range numPF {
ellipticCurvePF[i] = uint8(sex[i]) ellipticCurvePF[i] = uint8(sex[i])
} }
case versionExtensionType: case versionExtensionType:

View File

@@ -6,48 +6,7 @@
package ktls package ktls
import ( import "golang.org/x/crypto/cryptobyte"
"fmt"
"golang.org/x/crypto/cryptobyte"
)
// The marshalingFunction type is an adapter to allow the use of ordinary
// functions as cryptobyte.MarshalingValue.
type marshalingFunction func(b *cryptobyte.Builder) error
func (f marshalingFunction) Marshal(b *cryptobyte.Builder) error {
return f(b)
}
// addBytesWithLength appends a sequence of bytes to the cryptobyte.Builder. If
// the length of the sequence is not the value specified, it produces an error.
func addBytesWithLength(b *cryptobyte.Builder, v []byte, n int) {
b.AddValue(marshalingFunction(func(b *cryptobyte.Builder) error {
if len(v) != n {
return fmt.Errorf("invalid value length: expected %d, got %d", n, len(v))
}
b.AddBytes(v)
return nil
}))
}
// addUint64 appends a big-endian, 64-bit value to the cryptobyte.Builder.
func addUint64(b *cryptobyte.Builder, v uint64) {
b.AddUint32(uint32(v >> 32))
b.AddUint32(uint32(v))
}
// readUint64 decodes a big-endian, 64-bit value into out and advances over it.
// It reports whether the read was successful.
func readUint64(s *cryptobyte.String, out *uint64) bool {
var hi, lo uint32
if !s.ReadUint32(&hi) || !s.ReadUint32(&lo) {
return false
}
*out = uint64(hi)<<32 | uint64(lo)
return true
}
// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a // readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a
// []byte instead of a cryptobyte.String. // []byte instead of a cryptobyte.String.
@@ -61,12 +20,6 @@ func readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out)) return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out))
} }
// readUint24LengthPrefixed acts like s.ReadUint24LengthPrefixed, but targets a
// []byte instead of a cryptobyte.String.
func readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
return s.ReadUint24LengthPrefixed((*cryptobyte.String)(out))
}
type keyUpdateMsg struct { type keyUpdateMsg struct {
updateRequested bool updateRequested bool
} }
@@ -125,11 +78,6 @@ const (
typeMessageHash uint8 = 254 // synthetic message typeMessageHash uint8 = 254 // synthetic message
) )
// TLS compression types.
const (
compressionNone uint8 = 0
)
// TLS extension numbers // TLS extension numbers
const ( const (
extensionServerName uint16 = 0 extensionServerName uint16 = 0

View File

@@ -77,78 +77,5 @@ func (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) {
if !c.kernelTx { if !c.kernelTx {
return c.rawConn.WriteRecordLocked(typ, data) return c.rawConn.WriteRecordLocked(typ, data)
} }
/*for len(data) > 0 {
m := len(data)
if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload {
m = maxPayload
}
_, err = c.writeKernelRecord(typ, data[:m])
if err != nil {
return
}
n += m
data = data[m:]
}*/
return c.writeKernelRecord(typ, data) return c.writeKernelRecord(typ, data)
} }
const (
// tcpMSSEstimate is a conservative estimate of the TCP maximum segment
// size (MSS). A constant is used, rather than querying the kernel for
// the actual MSS, to avoid complexity. The value here is the IPv6
// minimum MTU (1280 bytes) minus the overhead of an IPv6 header (40
// bytes) and a TCP header with timestamps (32 bytes).
tcpMSSEstimate = 1208
// recordSizeBoostThreshold is the number of bytes of application data
// sent after which the TLS record size will be increased to the
// maximum.
recordSizeBoostThreshold = 128 * 1024
)
func (c *Conn) maxPayloadSizeForWrite(typ uint16) int {
if /*c.config.DynamicRecordSizingDisabled ||*/ typ != recordTypeApplicationData {
return maxPlaintext
}
if *c.rawConn.PacketsSent >= recordSizeBoostThreshold {
return maxPlaintext
}
// Subtract TLS overheads to get the maximum payload size.
payloadBytes := tcpMSSEstimate - recordHeaderLen - c.rawConn.Out.ExplicitNonceLen()
if rawCipher := *c.rawConn.Out.Cipher; rawCipher != nil {
switch ciph := rawCipher.(type) {
case cipher.Stream:
payloadBytes -= (*c.rawConn.Out.Mac).Size()
case cipher.AEAD:
payloadBytes -= ciph.Overhead()
/*case cbcMode:
blockSize := ciph.BlockSize()
// The payload must fit in a multiple of blockSize, with
// room for at least one padding byte.
payloadBytes = (payloadBytes & ^(blockSize - 1)) - 1
// The RawMac is appended before padding so affects the
// payload size directly.
payloadBytes -= c.out.mac.Size()*/
default:
panic("unknown cipher type")
}
}
if *c.rawConn.Vers == tls.VersionTLS13 {
payloadBytes-- // encrypted ContentType
}
// Allow packet growth in arithmetic progression up to max.
pkt := *c.rawConn.PacketsSent
*c.rawConn.PacketsSent++
if pkt > 1000 {
return maxPlaintext // avoid overflow in multiply below
}
n := payloadBytes * int(pkt+1)
if n > maxPlaintext {
n = maxPlaintext
}
return n
}

View File

@@ -81,7 +81,7 @@ func (f *darwinConnectionFinder) find(network string, source netip.AddrPort, des
source = normalizeDarwinAddrPort(source) source = normalizeDarwinAddrPort(source)
destination = normalizeDarwinAddrPort(destination) destination = normalizeDarwinAddrPort(destination)
var lastOwner *adapter.ConnectionOwner var lastOwner *adapter.ConnectionOwner
for attempt := 0; attempt < 2; attempt++ { for attempt := range 2 {
snapshot, fromCache, err := f.loadSnapshot(networkName, attempt > 0) snapshot, fromCache, err := f.loadSnapshot(networkName, attempt > 0)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -1,5 +1,6 @@
//go:build linux //go:build linux
//nolint:unused
package process package process
import ( import (
@@ -117,7 +118,7 @@ func (c *socketDiagConn) query(source netip.AddrPort, destination netip.AddrPort
c.access.Lock() c.access.Lock()
defer c.access.Unlock() defer c.access.Unlock()
request := packSocketDiagRequest(c.family, c.protocol, source, destination, false) request := packSocketDiagRequest(c.family, c.protocol, source, destination, false)
for attempt := 0; attempt < 2; attempt++ { for range 2 {
err = c.ensureOpenLocked() err = c.ensureOpenLocked()
if err != nil { if err != nil {
return 0, 0, E.Cause(err, "dial netlink") return 0, 0, E.Cause(err, "dial netlink")

View File

@@ -109,7 +109,7 @@ func getInterfaceDisplayName(name string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
for _, deviceSpan := range strings.Split(string(content), "Ethernet Address") { for deviceSpan := range strings.SplitSeq(string(content), "Ethernet Address") {
if strings.Contains(deviceSpan, "Device: "+name) { if strings.Contains(deviceSpan, "Device: "+name) {
substr := "Hardware Port: " substr := "Hardware Port: "
deviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):] deviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):]

View File

@@ -40,14 +40,14 @@ func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState {
defer cancel() defer cancel()
cmObj := m.conn.Object("net.connman", "/") cmObj := m.conn.Object("net.connman", "/")
var services []interface{} var services []any
err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services) err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services)
if err != nil { if err != nil {
return adapter.WIFIState{} return adapter.WIFIState{}
} }
for _, service := range services { for _, service := range services {
servicePair, ok := service.([]interface{}) servicePair, ok := service.([]any)
if !ok || len(servicePair) != 2 { if !ok || len(servicePair) != 2 {
continue continue
} }

View File

@@ -1,3 +1,4 @@
//nolint:unused
package settings package settings
import ( import (
@@ -73,13 +74,13 @@ func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
scanner := bufio.NewScanner(strings.NewReader(status)) scanner := bufio.NewScanner(strings.NewReader(status))
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if strings.HasPrefix(line, "wpa_state=") { if after, ok := strings.CutPrefix(line, "wpa_state="); ok {
state := strings.TrimPrefix(line, "wpa_state=") state := after
connected = state == "COMPLETED" connected = state == "COMPLETED"
} else if strings.HasPrefix(line, "ssid=") { } else if after, ok := strings.CutPrefix(line, "ssid="); ok {
ssid = strings.TrimPrefix(line, "ssid=") ssid = after
} else if strings.HasPrefix(line, "bssid=") { } else if after, ok := strings.CutPrefix(line, "bssid="); ok {
bssid = strings.TrimPrefix(line, "bssid=") bssid = after
} }
} }

View File

@@ -1,5 +1,6 @@
//go:build !linux && !windows //go:build !linux && !windows
//nolint:unused
package settings package settings
import ( import (

View File

@@ -54,9 +54,8 @@ type xorNonceAEAD struct {
aead cipher.AEAD aead cipher.AEAD
} }
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() } func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte { func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
for i, b := range nonce { for i, b := range nonce {

View File

@@ -1,6 +1,8 @@
package sniff package sniff
import ( import (
"slices"
"github.com/sagernet/sing-box/common/ja3" "github.com/sagernet/sing-box/common/ja3"
) )
@@ -15,15 +17,8 @@ const (
// Note: uQUIC with Chromium mimicry cannot be reliably distinguished from real Chromium // Note: uQUIC with Chromium mimicry cannot be reliably distinguished from real Chromium
// since it uses the same TLS fingerprint, so it will be identified as Chromium. // since it uses the same TLS fingerprint, so it will be identified as Chromium.
func isQUICGo(fingerprint *ja3.ClientHello) bool { func isQUICGo(fingerprint *ja3.ClientHello) bool {
for _, curve := range fingerprint.EllipticCurves { if slices.Contains(fingerprint.EllipticCurves, x25519Kyber768Draft00) {
if curve == x25519Kyber768Draft00 { return true
return true
}
} }
for _, ext := range fingerprint.Extensions { return slices.Contains(fingerprint.Extensions, extensionRenegotiationInfo)
if ext == extensionRenegotiationInfo {
return true
}
}
return false
} }

View File

@@ -30,7 +30,7 @@ func TestSniffQUICQuicGoFingerprint(t *testing.T) {
go func() { go func() {
var packets [][]byte var packets [][]byte
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second)) udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
for i := 0; i < 10; i++ { for range 10 {
buf := make([]byte, 2048) buf := make([]byte, 2048)
n, _, err := udpConn.ReadFromUDP(buf) n, _, err := udpConn.ReadFromUDP(buf)
if err != nil { if err != nil {
@@ -104,7 +104,7 @@ func TestSniffQUICInitialFromQuicGo(t *testing.T) {
go func() { go func() {
var packets [][]byte var packets [][]byte
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second)) udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
for i := 0; i < 5; i++ { // Capture up to 5 packets for range 5 { // Capture up to 5 packets
buf := make([]byte, 2048) buf := make([]byte, 2048)
n, _, err := udpConn.ReadFromUDP(buf) n, _, err := udpConn.ReadFromUDP(buf)
if err != nil { if err != nil {

View File

@@ -78,7 +78,7 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
} }
ruleSetCompat.Version = version ruleSetCompat.Version = version
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length) ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ { for i := range length {
ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover) ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover)
if err != nil { if err != nil {
err = E.Cause(err, "read rule[", i, "]") err = E.Cause(err, "read rule[", i, "]")
@@ -644,7 +644,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
return return
} }
logicalRule.Rules = make([]option.HeadlessRule, length) logicalRule.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ { for i := range length {
logicalRule.Rules[i], err = readRule(reader, recovery) logicalRule.Rules[i], err = readRule(reader, recovery)
if err != nil { if err != nil {
err = E.Cause(err, "read logical rule [", i, "]") err = E.Cause(err, "read logical rule [", i, "]")

View File

@@ -450,7 +450,7 @@ func buildIPSet(cidrs ...string) *netipx.IPSet {
func buildLargeIPSet(count int) *netipx.IPSet { func buildLargeIPSet(count int) *netipx.IPSet {
var builder netipx.IPSetBuilder var builder netipx.IPSetBuilder
for i := 0; i < count; i++ { for i := range count {
prefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24) prefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24)
builder.AddPrefix(prefix) builder.AddPrefix(prefix)
} }

View File

@@ -267,8 +267,8 @@ type realityVerifier struct {
} }
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates") p, _ := reflect.TypeFor[utls.Conn]().FieldByName("peerCertificates")
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset)) certs := *(*([]*x509.Certificate))(unsafe.Add(unsafe.Pointer(c.Conn), p.Offset))
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok { if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
h := hmac.New(sha512.New, c.authKey) h := hmac.New(sha512.New, c.authKey)
h.Write(pub) h.Write(pub)

View File

@@ -141,13 +141,14 @@ func (c *STDServerConfig) startWatcher() error {
func (c *STDServerConfig) certificateUpdated(path string) error { func (c *STDServerConfig) certificateUpdated(path string) error {
if path == c.certificatePath || path == c.keyPath { if path == c.certificatePath || path == c.keyPath {
if path == c.certificatePath { switch path {
case c.certificatePath:
certificate, err := os.ReadFile(c.certificatePath) certificate, err := os.ReadFile(c.certificatePath)
if err != nil { if err != nil {
return E.Cause(err, "reload certificate from ", c.certificatePath) return E.Cause(err, "reload certificate from ", c.certificatePath)
} }
c.certificate = certificate c.certificate = certificate
} else if path == c.keyPath { case c.keyPath:
key, err := os.ReadFile(c.keyPath) key, err := os.ReadFile(c.keyPath)
if err != nil { if err != nil {
return E.Cause(err, "reload key from ", c.keyPath) return E.Cause(err, "reload key from ", c.keyPath)
@@ -338,9 +339,10 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
} }
tlsConfig.ClientCAs = clientCertificateCA tlsConfig.ClientCAs = clientCertificateCA
} else if len(options.ClientCertificatePublicKeySHA256) > 0 { } else if len(options.ClientCertificatePublicKeySHA256) > 0 {
if tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert { switch tlsConfig.ClientAuth {
case tls.RequireAndVerifyClientCert:
tlsConfig.ClientAuth = tls.RequireAnyClientCert tlsConfig.ClientAuth = tls.RequireAnyClientCert
} else if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven { case tls.VerifyClientCertIfGiven:
tlsConfig.ClientAuth = tls.RequestClientCert tlsConfig.ClientAuth = tls.RequestClientCert
} }
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {

View File

@@ -603,10 +603,7 @@ func (s *StartedService) URLTest(ctx context.Context, request *URLTestRequest) (
return false return false
} }
_, isGroup := it.(adapter.OutboundGroup) _, isGroup := it.(adapter.OutboundGroup)
if isGroup { return !isGroup
return false
}
return true
}) })
b, _ := batch.New(boxService.ctx, batch.WithConcurrencyNum[any](10)) b, _ := batch.New(boxService.ctx, batch.WithConcurrencyNum[any](10))
for _, detour := range outbounds { for _, detour := range outbounds {

View File

@@ -70,10 +70,7 @@ func NewClient(options ClientOptions) *Client {
if client.timeout == 0 { if client.timeout == 0 {
client.timeout = C.DNSTimeout client.timeout = C.DNSTimeout
} }
cacheCapacity := options.CacheCapacity cacheCapacity := max(options.CacheCapacity, 1024)
if cacheCapacity < 1024 {
cacheCapacity = 1024
}
if !client.disableCache { if !client.disableCache {
if !client.independentCache { if !client.independentCache {
client.cache = common.Must1(freelru.NewSharded[dns.Question, *dns.Msg](cacheCapacity, maphash.NewHasher[dns.Question]().Hash32)) client.cache = common.Must1(freelru.NewSharded[dns.Question, *dns.Msg](cacheCapacity, maphash.NewHasher[dns.Question]().Hash32))
@@ -334,9 +331,10 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
if options.LookupStrategy != C.DomainStrategyAsIS { if options.LookupStrategy != C.DomainStrategyAsIS {
lookupOptions.Strategy = strategy lookupOptions.Strategy = strategy
} }
if strategy == C.DomainStrategyIPv4Only { switch strategy {
case C.DomainStrategyIPv4Only:
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker) return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker)
} else if strategy == C.DomainStrategyIPv6Only { case C.DomainStrategyIPv6Only:
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker) return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker)
} }
var response4 []netip.Addr var response4 []netip.Addr
@@ -500,10 +498,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
} }
} }
} }
nowTTL := int(expireAt.Sub(timeNow).Seconds()) nowTTL := max(int(expireAt.Sub(timeNow).Seconds()), 0)
if nowTTL < 0 {
nowTTL = 0
}
response = response.Copy() response = response.Copy()
if originTTL > 0 { if originTTL > 0 {
duration := uint32(originTTL - nowTTL) duration := uint32(originTTL - nowTTL)
@@ -551,18 +546,6 @@ func MessageToAddresses(response *dns.Msg) []netip.Addr {
return addresses return addresses
} }
func wrapError(err error) error {
switch dnsErr := err.(type) {
case *net.DNSError:
if dnsErr.IsNotFound {
return RcodeNameError
}
case *net.AddrError:
return RcodeNameError
}
return err
}
type transportKey struct{} type transportKey struct{}
func contextWithTransportTag(ctx context.Context, transportTag string) context.Context { func contextWithTransportTag(ctx context.Context, transportTag string) context.Context {

View File

@@ -222,7 +222,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface)
packetConn net.PacketConn packetConn net.PacketConn
err error err error
) )
for i := 0; i < 5; i++ { for range 5 {
packetConn, err = listener.ListenPacket(t.ctx, "udp4", listenAddr) packetConn, err = listener.ListenPacket(t.ctx, "udp4", listenAddr)
if err == nil || !errors.Is(err, syscall.EADDRINUSE) { if err == nil || !errors.Is(err, syscall.EADDRINUSE) {
break break

View File

@@ -72,7 +72,7 @@ func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn
sLen := len(servers) sLen := len(servers)
var lastErr error var lastErr error
for i := 0; i < t.attempts; i++ { for i := 0; i < t.attempts; i++ {
for j := 0; j < sLen; j++ { for j := range sLen {
server := servers[j] server := servers[j]
question := message.Question[0] question := message.Question[0]
question.Name = fqdn question.Name = fqdn

View File

@@ -1,5 +1,6 @@
//go:build !linux //go:build !linux
//nolint:unused
package local package local
import ( import (

View File

@@ -82,7 +82,7 @@ func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn stri
sLen := uint32(len(config.servers)) sLen := uint32(len(config.servers))
var lastErr error var lastErr error
for i := 0; i < config.attempts; i++ { for i := 0; i < config.attempts; i++ {
for j := uint32(0); j < sLen; j++ { for j := range sLen {
server := config.servers[(serverOffset+j)%sLen] server := config.servers[(serverOffset+j)%sLen]
question := message.Question[0] question := message.Question[0]
question.Name = fqdn question.Name = fqdn

View File

@@ -1,3 +1,4 @@
//nolint:unused
package local package local
import ( import (

View File

@@ -1,3 +1,4 @@
//nolint:unused
package local package local
import ( import (

View File

@@ -100,7 +100,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
err error err error
response *mDNS.Msg response *mDNS.Msg
) )
for i := 0; i < 2; i++ { for range 2 {
conn, _, err = t.connection.Acquire(ctx, func(ctx context.Context) (*quic.Conn, error) { conn, _, err = t.connection.Acquire(ctx, func(ctx context.Context) (*quic.Conn, error) {
rawConn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr) rawConn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr)
if err != nil { if err != nil {

View File

@@ -102,7 +102,7 @@ func (t *TLSTransport) Reset() {
func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
var lastErr error var lastErr error
for attempt := 0; attempt < 2; attempt++ { for range 2 {
conn, created, err := t.connections.Acquire(ctx, func(ctx context.Context) (*tlsDNSConn, error) { conn, created, err := t.connections.Acquire(ctx, func(ctx context.Context) (*tlsDNSConn, error) {
tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr) tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr)
if err != nil { if err != nil {

View File

@@ -112,7 +112,7 @@ func (c *CacheFile) Start(stage adapter.StartStage) error {
db *bbolt.DB db *bbolt.DB
err error err error
) )
for i := 0; i < 10; i++ { for range 10 {
db, err = bbolt.Open(c.path, fileMode, &options) db, err = bbolt.Open(c.path, fileMode, &options)
if err == nil { if err == nil {
break break

View File

@@ -164,7 +164,7 @@ func (s *Server) Start(stage adapter.StartStage) error {
listener net.Listener listener net.Listener
err error err error
) )
for i := 0; i < 3; i++ { for range 3 {
listener, err = net.Listen("tcp", s.httpServer.Addr) listener, err = net.Listen("tcp", s.httpServer.Addr)
if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)

View File

@@ -147,7 +147,7 @@ func (c *CommandClient) dialWithRetry(target string, contextDialer func(context.
var client daemon.StartedServiceClient var client daemon.StartedServiceClient
var lastError error var lastError error
for attempt := 0; attempt < commandClientDialAttempts; attempt++ { for attempt := range commandClientDialAttempts {
if connection == nil { if connection == nil {
options := []grpc.DialOption{ options := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithTransportCredentials(insecure.NewCredentials()),

View File

@@ -114,7 +114,7 @@ func (s *CommandServer) Start() error {
if sCommandServerListenPort == 0 { if sCommandServerListenPort == 0 {
sockPath := filepath.Join(sBasePath, "command.sock") sockPath := filepath.Join(sBasePath, "command.sock")
os.Remove(sockPath) os.Remove(sockPath)
for i := 0; i < 30; i++ { for range 30 {
listener, err = net.ListenUnix("unix", &net.UnixAddr{ listener, err = net.ListenUnix("unix", &net.UnixAddr{
Name: sockPath, Name: sockPath,
Net: "unix", Net: "unix",

View File

@@ -418,13 +418,3 @@ func systemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxySta
Enabled: status.Enabled, Enabled: status.Enabled,
} }
} }
func systemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus {
if status == nil {
return nil
}
return &daemon.SystemProxyStatus{
Available: status.Available,
Enabled: status.Enabled,
}
}

View File

@@ -8,8 +8,6 @@ import (
"runtime/debug" "runtime/debug"
) )
var crashOutputFile *os.File
func RedirectStderr(path string) error { func RedirectStderr(path string) error {
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 { if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
_ = os.Rename(path, path+".old") _ = os.Rename(path, path+".old")
@@ -32,6 +30,5 @@ func RedirectStderr(path string) error {
os.Remove(outputFile.Name()) os.Remove(outputFile.Name())
return err return err
} }
crashOutputFile = outputFile return outputFile.Close()
return nil
} }

View File

@@ -16,7 +16,6 @@ var (
type platformDefaultInterfaceMonitor struct { type platformDefaultInterfaceMonitor struct {
*platformInterfaceWrapper *platformInterfaceWrapper
logger logger.Logger logger logger.Logger
element *list.Element[tun.NetworkUpdateCallback]
callbacks list.List[tun.DefaultInterfaceUpdateCallback] callbacks list.List[tun.DefaultInterfaceUpdateCallback]
myInterface string myInterface string
} }

View File

@@ -1,9 +1,6 @@
package libbox package libbox
import ( import C "github.com/sagernet/sing-box/constant"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
)
type PlatformInterface interface { type PlatformInterface interface {
LocalDNSTransport() LocalDNSTransport LocalDNSTransport() LocalDNSTransport
@@ -98,37 +95,3 @@ type OnDemandRuleIterator interface {
Next() OnDemandRule Next() OnDemandRule
HasNext() bool HasNext() bool
} }
type onDemandRule struct {
option.OnDemandRule
}
func (r *onDemandRule) Target() int32 {
if r.OnDemandRule.Action == nil {
return -1
}
return int32(*r.OnDemandRule.Action)
}
func (r *onDemandRule) DNSSearchDomainMatch() StringIterator {
return newIterator(r.OnDemandRule.DNSSearchDomainMatch)
}
func (r *onDemandRule) DNSServerAddressMatch() StringIterator {
return newIterator(r.OnDemandRule.DNSServerAddressMatch)
}
func (r *onDemandRule) InterfaceTypeMatch() int32 {
if r.OnDemandRule.InterfaceTypeMatch == nil {
return -1
}
return int32(*r.OnDemandRule.InterfaceTypeMatch)
}
func (r *onDemandRule) SSIDMatch() StringIterator {
return newIterator(r.OnDemandRule.SSIDMatch)
}
func (r *onDemandRule) ProbeURL() string {
return r.OnDemandRule.ProbeURL
}

View File

@@ -11,7 +11,7 @@ const utunControlName = "com.apple.net.utun_control"
func GetTunnelFileDescriptor() int32 { func GetTunnelFileDescriptor() int32 {
ctlInfo := &unix.CtlInfo{} ctlInfo := &unix.CtlInfo{}
copy(ctlInfo.Name[:], utunControlName) copy(ctlInfo.Name[:], utunControlName)
for fd := 0; fd < 1024; fd++ { for fd := range 1024 {
addr, err := unix.Getpeername(fd) addr, err := unix.Getpeername(fd)
if err != nil { if err != nil {
continue continue

View File

@@ -4,14 +4,8 @@ import (
"context" "context"
"math/rand" "math/rand"
"time" "time"
"github.com/sagernet/sing/common/random"
) )
func init() {
random.InitializeSeed()
}
type idKey struct{} type idKey struct{}
type ID struct { type ID struct {

View File

@@ -28,7 +28,6 @@ func (v *NetworkList) UnmarshalJSON(content []byte) error {
for _, networkName := range networkList { for _, networkName := range networkList {
switch networkName { switch networkName {
case N.NetworkTCP, N.NetworkUDP: case N.NetworkTCP, N.NetworkUDP:
break
default: default:
return E.New("unknown network: " + networkName) return E.New("unknown network: " + networkName)
} }

View File

@@ -1,186 +0,0 @@
package direct
import (
"net"
"net/netip"
"sync"
"github.com/sagernet/sing-box/adapter"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type loopBackDetector struct {
networkManager adapter.NetworkManager
connAccess sync.RWMutex
packetConnAccess sync.RWMutex
connMap map[netip.AddrPort]netip.AddrPort
packetConnMap map[uint16]uint16
}
func newLoopBackDetector(networkManager adapter.NetworkManager) *loopBackDetector {
return &loopBackDetector{
networkManager: networkManager,
connMap: make(map[netip.AddrPort]netip.AddrPort),
packetConnMap: make(map[uint16]uint16),
}
}
func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn {
source := M.AddrPortFromNet(conn.LocalAddr())
if !source.IsValid() {
return conn
}
if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn {
if !source.Addr().IsLoopback() {
_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())
if err != nil {
return conn
}
}
if !N.IsPublicAddr(source.Addr()) {
return conn
}
l.packetConnAccess.Lock()
l.packetConnMap[source.Port()] = M.AddrPortFromNet(conn.RemoteAddr()).Port()
l.packetConnAccess.Unlock()
return &loopBackDetectUDPWrapper{abstractUDPConn: udpConn, detector: l, connPort: source.Port()}
} else {
l.connAccess.Lock()
l.connMap[source] = M.AddrPortFromNet(conn.RemoteAddr())
l.connAccess.Unlock()
return &loopBackDetectWrapper{Conn: conn, detector: l, connAddr: source}
}
}
func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Socksaddr) N.NetPacketConn {
source := M.AddrPortFromNet(conn.LocalAddr())
if !source.IsValid() {
return conn
}
if !source.Addr().IsLoopback() {
_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())
if err != nil {
return conn
}
}
l.packetConnAccess.Lock()
l.packetConnMap[source.Port()] = destination.AddrPort().Port()
l.packetConnAccess.Unlock()
return &loopBackDetectPacketWrapper{NetPacketConn: conn, detector: l, connPort: source.Port()}
}
func (l *loopBackDetector) CheckConn(source netip.AddrPort, local netip.AddrPort) bool {
l.connAccess.RLock()
defer l.connAccess.RUnlock()
destination, loaded := l.connMap[source]
return loaded && destination != local
}
func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.AddrPort) bool {
if !source.IsValid() {
return false
}
if !source.Addr().IsLoopback() {
_, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr())
if err != nil {
return false
}
}
if N.IsPublicAddr(source.Addr()) {
return false
}
l.packetConnAccess.RLock()
defer l.packetConnAccess.RUnlock()
destinationPort, loaded := l.packetConnMap[source.Port()]
return loaded && destinationPort != local.Port()
}
type loopBackDetectWrapper struct {
net.Conn
detector *loopBackDetector
connAddr netip.AddrPort
closeOnce sync.Once
}
func (w *loopBackDetectWrapper) Close() error {
w.closeOnce.Do(func() {
w.detector.connAccess.Lock()
delete(w.detector.connMap, w.connAddr)
w.detector.connAccess.Unlock()
})
return w.Conn.Close()
}
func (w *loopBackDetectWrapper) ReaderReplaceable() bool {
return true
}
func (w *loopBackDetectWrapper) WriterReplaceable() bool {
return true
}
func (w *loopBackDetectWrapper) Upstream() any {
return w.Conn
}
type loopBackDetectPacketWrapper struct {
N.NetPacketConn
detector *loopBackDetector
connPort uint16
closeOnce sync.Once
}
func (w *loopBackDetectPacketWrapper) Close() error {
w.closeOnce.Do(func() {
w.detector.packetConnAccess.Lock()
delete(w.detector.packetConnMap, w.connPort)
w.detector.packetConnAccess.Unlock()
})
return w.NetPacketConn.Close()
}
func (w *loopBackDetectPacketWrapper) ReaderReplaceable() bool {
return true
}
func (w *loopBackDetectPacketWrapper) WriterReplaceable() bool {
return true
}
func (w *loopBackDetectPacketWrapper) Upstream() any {
return w.NetPacketConn
}
type abstractUDPConn interface {
net.Conn
net.PacketConn
}
type loopBackDetectUDPWrapper struct {
abstractUDPConn
detector *loopBackDetector
connPort uint16
closeOnce sync.Once
}
func (w *loopBackDetectUDPWrapper) Close() error {
w.closeOnce.Do(func() {
w.detector.packetConnAccess.Lock()
delete(w.detector.packetConnMap, w.connPort)
w.detector.packetConnAccess.Unlock()
})
return w.abstractUDPConn.Close()
}
func (w *loopBackDetectUDPWrapper) ReaderReplaceable() bool {
return true
}
func (w *loopBackDetectUDPWrapper) WriterReplaceable() bool {
return true
}
func (w *loopBackDetectUDPWrapper) Upstream() any {
return w.abstractUDPConn
}

View File

@@ -41,7 +41,6 @@ type Outbound struct {
domainStrategy C.DomainStrategy domainStrategy C.DomainStrategy
fallbackDelay time.Duration fallbackDelay time.Duration
isEmpty bool isEmpty bool
// loopBack *loopBackDetector
} }
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) {
@@ -67,7 +66,6 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
fallbackDelay: time.Duration(options.FallbackDelay), fallbackDelay: time.Duration(options.FallbackDelay),
dialer: outboundDialer.(dialer.ParallelInterfaceDialer), dialer: outboundDialer.(dialer.ParallelInterfaceDialer),
isEmpty: reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true}), isEmpty: reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true}),
// loopBack: newLoopBackDetector(router),
} }
//nolint:staticcheck //nolint:staticcheck
if options.ProxyProtocol != 0 { if options.ProxyProtocol != 0 {
@@ -87,11 +85,6 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
case N.NetworkUDP: case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
} }
/*conn, err := h.dialer.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
return h.loopBack.NewConn(conn), nil*/
return h.dialer.DialContext(ctx, network, destination) return h.dialer.DialContext(ctx, network, destination)
} }
@@ -104,7 +97,6 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
if err != nil { if err != nil {
return nil, err return nil, err
} }
// conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination)
return conn, nil return conn, nil
} }
@@ -161,18 +153,3 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.
func (h *Outbound) IsEmpty() bool { func (h *Outbound) IsEmpty() bool {
return h.isEmpty return h.isEmpty
} }
/*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) {
return E.New("reject loopback connection to ", metadata.Destination)
}
return NewConnection(ctx, h, conn, metadata)
}
func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
if h.loopBack.CheckPacketConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) {
return E.New("reject loopback packet connection to ", metadata.Destination)
}
return NewPacketConnection(ctx, h, conn, metadata)
}
*/

View File

@@ -82,7 +82,7 @@ func NewDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn
} }
break break
} }
fastClose, cancel := common.ContextWithCancelCause(ctx) fastClose, cancel := context.WithCancelCause(ctx)
timeout := canceler.New(fastClose, cancel, C.DNSTimeout) timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
var group task.Group var group task.Group
group.Append0(func(_ context.Context) error { group.Append0(func(_ context.Context) error {
@@ -150,7 +150,7 @@ func NewDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn
} }
func newDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error { func newDNSPacketConnection(ctx context.Context, router adapter.DNSRouter, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error {
fastClose, cancel := common.ContextWithCancelCause(ctx) fastClose, cancel := context.WithCancelCause(ctx)
timeout := canceler.New(fastClose, cancel, C.DNSTimeout) timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
var group task.Group var group task.Group
group.Append0(func(_ context.Context) error { group.Append0(func(_ context.Context) error {

View File

@@ -34,7 +34,6 @@ var _ adapter.OutboundGroup = (*URLTest)(nil)
type URLTest struct { type URLTest struct {
outbound.Adapter outbound.Adapter
ctx context.Context ctx context.Context
router adapter.Router
outbound adapter.OutboundManager outbound adapter.OutboundManager
connection adapter.ConnectionManager connection adapter.ConnectionManager
logger log.ContextLogger logger log.ContextLogger
@@ -51,7 +50,6 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
outbound := &URLTest{ outbound := &URLTest{
Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds), Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds),
ctx: ctx, ctx: ctx,
router: router,
outbound: service.FromContext[adapter.OutboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx), connection: service.FromContext[adapter.ConnectionManager](ctx),
logger: logger, logger: logger,
@@ -188,7 +186,6 @@ func (s *URLTest) NewDirectRouteConnection(metadata adapter.InboundContext, rout
type URLTestGroup struct { type URLTestGroup struct {
ctx context.Context ctx context.Context
router adapter.Router
outbound adapter.OutboundManager outbound adapter.OutboundManager
pause pause.Manager pause pause.Manager
pauseCallback *list.Element[pause.Callback] pauseCallback *list.Element[pause.Callback]
@@ -267,9 +264,10 @@ func (g *URLTestGroup) Touch() {
g.lastActive.Store(time.Now()) g.lastActive.Store(time.Now())
return return
} }
g.ticker = time.NewTicker(g.interval) ticker := time.NewTicker(g.interval)
go g.loopCheck() g.ticker = ticker
g.pauseCallback = pause.RegisterTicker(g.pause, g.ticker, g.interval, nil) g.pauseCallback = pause.RegisterTicker(g.pause, ticker, g.interval, nil)
go g.loopCheck(ticker, g.close)
} }
func (g *URLTestGroup) Close() error { func (g *URLTestGroup) Close() error {
@@ -279,7 +277,9 @@ func (g *URLTestGroup) Close() error {
return nil return nil
} }
g.ticker.Stop() g.ticker.Stop()
g.ticker = nil
g.pause.UnregisterCallback(g.pauseCallback) g.pause.UnregisterCallback(g.pauseCallback)
g.pauseCallback = nil
close(g.close) close(g.close)
return nil return nil
} }
@@ -328,23 +328,25 @@ func (g *URLTestGroup) Select(network string) (adapter.Outbound, bool) {
return minOutbound, true return minOutbound, true
} }
func (g *URLTestGroup) loopCheck() { func (g *URLTestGroup) loopCheck(ticker *time.Ticker, closeChan <-chan struct{}) {
if time.Since(g.lastActive.Load()) > g.interval { if time.Since(g.lastActive.Load()) > g.interval {
g.lastActive.Store(time.Now()) g.lastActive.Store(time.Now())
g.CheckOutbounds(false) g.CheckOutbounds(false)
} }
for { for {
select { select {
case <-g.close: case <-closeChan:
return return
case <-g.ticker.C: case <-ticker.C:
} }
if time.Since(g.lastActive.Load()) > g.idleTimeout { if time.Since(g.lastActive.Load()) > g.idleTimeout {
g.access.Lock() g.access.Lock()
g.ticker.Stop() if g.ticker == ticker {
g.ticker = nil g.ticker.Stop()
g.pause.UnregisterCallback(g.pauseCallback) g.ticker = nil
g.pauseCallback = nil g.pause.UnregisterCallback(g.pauseCallback)
g.pauseCallback = nil
}
g.access.Unlock() g.access.Unlock()
return return
} }

View File

@@ -22,7 +22,7 @@ func generatePaddingHeader() string {
paddingLen := rand.Intn(32) + 30 paddingLen := rand.Intn(32) + 30
padding := make([]byte, paddingLen) padding := make([]byte, paddingLen)
bits := rand.Uint64() bits := rand.Uint64()
for i := 0; i < 16; i++ { for i := range 16 {
padding[i] = "!#$()+<>?@[]^`{}"[bits&15] padding[i] = "!#$()+<>?@[]^`{}"[bits&15]
bits >>= 4 bits >>= 4
} }

View File

@@ -11,7 +11,6 @@ import (
"sync/atomic" "sync/atomic"
singTun "github.com/sagernet/sing-tun" singTun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
wgTun "github.com/sagernet/wireguard-go/tun" wgTun "github.com/sagernet/wireguard-go/tun"
) )
@@ -57,7 +56,7 @@ func (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count i
if a.linuxTUN != nil { if a.linuxTUN != nil {
n, err := a.linuxTUN.BatchRead(bufs, offset-singTun.PacketOffset, sizes) n, err := a.linuxTUN.BatchRead(bufs, offset-singTun.PacketOffset, sizes)
if err == nil { if err == nil {
for i := 0; i < n; i++ { for i := range n {
a.debugPacket("read", bufs[i][offset:offset+sizes[i]]) a.debugPacket("read", bufs[i][offset:offset+sizes[i]])
} }
} }
@@ -92,7 +91,7 @@ func (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err erro
for _, packet := range bufs { for _, packet := range bufs {
a.debugPacket("write", packet[offset:]) a.debugPacket("write", packet[offset:])
if singTun.PacketOffset > 0 { if singTun.PacketOffset > 0 {
common.ClearArray(packet[offset-singTun.PacketOffset : offset]) clear(packet[offset-singTun.PacketOffset : offset])
singTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:])) singTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:]))
} }
_, err = a.tun.Write(packet[offset-singTun.PacketOffset:]) _, err = a.tun.Write(packet[offset-singTun.PacketOffset:])

View File

@@ -3,6 +3,7 @@ package route
import ( import (
"context" "context"
"net/netip" "net/netip"
"slices"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -78,10 +79,8 @@ func (r *Router) isLocalSource(source netip.Addr) bool {
return true return true
} }
if r.platformInterface != nil { if r.platformInterface != nil {
for _, addr := range r.platformInterface.MyInterfaceAddress() { if slices.Contains(r.platformInterface.MyInterfaceAddress(), source) {
if addr == source { return true
return true
}
} }
} }
for _, netInterface := range r.network.InterfaceFinder().Interfaces() { for _, netInterface := range r.network.InterfaceFinder().Interfaces() {

View File

@@ -31,7 +31,7 @@ import (
// Deprecated: use RouteConnectionEx instead. // Deprecated: use RouteConnectionEx instead.
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
done := make(chan interface{}) done := make(chan any)
err := r.routeConnection(ctx, conn, metadata, N.OnceClose(func(it error) { err := r.routeConnection(ctx, conn, metadata, N.OnceClose(func(it error) {
close(done) close(done)
})) }))
@@ -161,7 +161,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
} }
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
done := make(chan interface{}) done := make(chan any)
err := r.routePacketConnection(ctx, conn, metadata, N.OnceClose(func(it error) { err := r.routePacketConnection(ctx, conn, metadata, N.OnceClose(func(it error) {
close(done) close(done)
})) }))

View File

@@ -42,11 +42,11 @@ func (s ruleMatchStateSet) combine(other ruleMatchStateSet) ruleMatchStateSet {
return 0 return 0
} }
var combined ruleMatchStateSet var combined ruleMatchStateSet
for left := ruleMatchState(0); left < 16; left++ { for left := range ruleMatchState(16) {
if !s.contains(left) { if !s.contains(left) {
continue continue
} }
for right := ruleMatchState(0); right < 16; right++ { for right := range ruleMatchState(16) {
if !other.contains(right) { if !other.contains(right) {
continue continue
} }
@@ -61,7 +61,7 @@ func (s ruleMatchStateSet) withBase(base ruleMatchState) ruleMatchStateSet {
return 0 return 0
} }
var withBase ruleMatchStateSet var withBase ruleMatchStateSet
for state := ruleMatchState(0); state < 16; state++ { for state := range ruleMatchState(16) {
if !s.contains(state) { if !s.contains(state) {
continue continue
} }
@@ -72,7 +72,7 @@ func (s ruleMatchStateSet) withBase(base ruleMatchState) ruleMatchStateSet {
func (s ruleMatchStateSet) filter(allowed func(ruleMatchState) bool) ruleMatchStateSet { func (s ruleMatchStateSet) filter(allowed func(ruleMatchState) bool) ruleMatchStateSet {
var filtered ruleMatchStateSet var filtered ruleMatchStateSet
for state := ruleMatchState(0); state < 16; state++ { for state := range ruleMatchState(16) {
if !s.contains(state) { if !s.contains(state) {
continue continue
} }
@@ -91,10 +91,6 @@ type ruleStateMatcherWithBase interface {
matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet
} }
func matchHeadlessRuleStates(rule adapter.HeadlessRule, metadata *adapter.InboundContext) ruleMatchStateSet {
return matchHeadlessRuleStatesWithBase(rule, metadata, 0)
}
func matchHeadlessRuleStatesWithBase(rule adapter.HeadlessRule, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet { func matchHeadlessRuleStatesWithBase(rule adapter.HeadlessRule, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
if matcher, isStateMatcher := rule.(ruleStateMatcherWithBase); isStateMatcher { if matcher, isStateMatcher := rule.(ruleStateMatcherWithBase); isStateMatcher {
return matcher.matchStatesWithBase(metadata, base) return matcher.matchStatesWithBase(metadata, base)
@@ -108,10 +104,6 @@ func matchHeadlessRuleStatesWithBase(rule adapter.HeadlessRule, metadata *adapte
return 0 return 0
} }
func matchRuleItemStates(item RuleItem, metadata *adapter.InboundContext) ruleMatchStateSet {
return matchRuleItemStatesWithBase(item, metadata, 0)
}
func matchRuleItemStatesWithBase(item RuleItem, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet { func matchRuleItemStatesWithBase(item RuleItem, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
if matcher, isStateMatcher := item.(ruleStateMatcherWithBase); isStateMatcher { if matcher, isStateMatcher := item.(ruleStateMatcherWithBase); isStateMatcher {
return matcher.matchStatesWithBase(metadata, base) return matcher.matchStatesWithBase(metadata, base)

View File

@@ -141,7 +141,6 @@ func TestAbstractLogicalRule_And_WithRuleSetInvert(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
t.Parallel() t.Parallel()
logicalRule := &abstractLogicalRule{ logicalRule := &abstractLogicalRule{

View File

@@ -2,6 +2,7 @@ package rule
import ( import (
"net/netip" "net/netip"
"slices"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -80,12 +81,7 @@ func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
return r.ipSet.Contains(metadata.Destination.Addr) return r.ipSet.Contains(metadata.Destination.Addr)
} }
if len(metadata.DestinationAddresses) > 0 { if len(metadata.DestinationAddresses) > 0 {
for _, address := range metadata.DestinationAddresses { return slices.ContainsFunc(metadata.DestinationAddresses, r.ipSet.Contains)
if r.ipSet.Contains(address) {
return true
}
}
return false
} }
return metadata.IPCIDRAcceptEmpty return metadata.IPCIDRAcceptEmpty
} }

View File

@@ -1,6 +1,7 @@
package rule package rule
import ( import (
"slices"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -16,15 +17,11 @@ type DomainItem struct {
} }
func NewDomainItem(domains []string, domainSuffixes []string) (*DomainItem, error) { func NewDomainItem(domains []string, domainSuffixes []string) (*DomainItem, error) {
for _, domainItem := range domains { if slices.Contains(domains, "") {
if domainItem == "" { return nil, E.New("domain: empty item is not allowed")
return nil, E.New("domain: empty item is not allowed")
}
} }
for _, domainSuffixItem := range domainSuffixes { if slices.Contains(domainSuffixes, "") {
if domainSuffixItem == "" { return nil, E.New("domain_suffix: empty item is not allowed")
return nil, E.New("domain_suffix: empty item is not allowed")
}
} }
var description string var description string
if dLen := len(domains); dLen > 0 { if dLen := len(domains); dLen > 0 {

View File

@@ -57,7 +57,6 @@ func TestRouteRuleSetMergeDestinationAddressGroup(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
t.Parallel() t.Parallel()
ruleSet := newLocalRuleSetForTest("merge-destination", testCase.inner) ruleSet := newLocalRuleSetForTest("merge-destination", testCase.inner)
@@ -223,7 +222,6 @@ func TestRouteRuleSetOuterGroupedStateMergesIntoSameGroup(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
t.Parallel() t.Parallel()
ruleSet := newLocalRuleSetForTest("outer-merge-"+testCase.name, headlessDefaultRule(t, func(rule *abstractDefaultRule) { ruleSet := newLocalRuleSetForTest("outer-merge-"+testCase.name, headlessDefaultRule(t, func(rule *abstractDefaultRule) {
@@ -652,7 +650,6 @@ func TestDNSInvertAddressLimitPreLookupRegression(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
t.Parallel() t.Parallel()
rule := dnsRuleForTest(func(rule *abstractDefaultRule) { rule := dnsRuleForTest(func(rule *abstractDefaultRule) {

View File

@@ -1,4 +1,4 @@
//go:build !darwin //go:build !darwin || !cgo
package ccm package ccm

View File

@@ -124,8 +124,6 @@ type Service struct {
userManager *UserManager userManager *UserManager
accessMutex sync.RWMutex accessMutex sync.RWMutex
usageTracker *AggregatedUsage usageTracker *AggregatedUsage
trackingGroup sync.WaitGroup
shuttingDown bool
} }
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) {
@@ -283,8 +281,8 @@ func (s *Service) getAccessToken() (string, error) {
func detectContextWindow(betaHeader string, totalInputTokens int64) int { func detectContextWindow(betaHeader string, totalInputTokens int64) int {
if totalInputTokens > premiumContextThreshold { if totalInputTokens > premiumContextThreshold {
features := strings.Split(betaHeader, ",") features := strings.SplitSeq(betaHeader, ",")
for _, feature := range features { for feature := range features {
if strings.HasPrefix(strings.TrimSpace(feature), "context-1m") { if strings.HasPrefix(strings.TrimSpace(feature), "context-1m") {
return contextWindowPremium return contextWindowPremium
} }
@@ -507,8 +505,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
continue continue
} }
if bytes.HasPrefix(line, []byte("data: ")) { if after, ok0 := bytes.CutPrefix(line, []byte("data: ")); ok0 {
eventData := bytes.TrimPrefix(line, []byte("data: ")) eventData := after
if bytes.Equal(eventData, []byte("[DONE]")) { if bytes.Equal(eventData, []byte("[DONE]")) {
continue continue
} }

View File

@@ -556,8 +556,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
continue continue
} }
if bytes.HasPrefix(line, []byte("data: ")) { if after, ok0 := bytes.CutPrefix(line, []byte("data: ")); ok0 {
eventData := bytes.TrimPrefix(line, []byte("data: ")) eventData := after
if bytes.Equal(eventData, []byte("[DONE]")) { if bytes.Equal(eventData, []byte("[DONE]")) {
continue continue
} }

View File

@@ -851,10 +851,7 @@ func normalizeGPT5Model(model string) string {
func calculateCost(stats UsageStats, model string, serviceTier string, contextWindow int) float64 { func calculateCost(stats UsageStats, model string, serviceTier string, contextWindow int) float64 {
pricing := getPricing(model, serviceTier, contextWindow) pricing := getPricing(model, serviceTier, contextWindow)
regularInputTokens := stats.InputTokens - stats.CachedTokens regularInputTokens := max(stats.InputTokens-stats.CachedTokens, 0)
if regularInputTokens < 0 {
regularInputTokens = 0
}
cost := (float64(regularInputTokens)*pricing.InputPrice + cost := (float64(regularInputTokens)*pricing.InputPrice +
float64(stats.OutputTokens)*pricing.OutputPrice + float64(stats.OutputTokens)*pricing.OutputPrice +

View File

@@ -96,6 +96,7 @@ func (s *Service) Start(stage adapter.StartStage) error {
if s.hasTimerMode { if s.hasTimerMode {
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig) s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
s.adaptiveTimer.start(false)
if s.memoryLimit > 0 { if s.memoryLimit > 0 {
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB") s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
} else { } else {
@@ -164,7 +165,7 @@ func goMemoryPressureCallback(status C.ulong) {
if isCritical { if isCritical {
s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB") s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
if s.adaptiveTimer != nil { if s.adaptiveTimer != nil {
s.adaptiveTimer.startNow() s.adaptiveTimer.start(true)
} }
} else if isWarning { } else if isWarning {
s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB") s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")

View File

@@ -64,7 +64,7 @@ func (s *Service) Start(stage adapter.StartStage) error {
return E.New("memory pressure monitoring is not available on this platform without memory_limit") return E.New("memory pressure monitoring is not available on this platform without memory_limit")
} }
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig) s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
s.adaptiveTimer.start(0) s.adaptiveTimer.start(false)
if s.useAvailable { if s.useAvailable {
s.logger.Info("started memory monitor with available memory detection") s.logger.Info("started memory monitor with available memory detection")
} else { } else {

View File

@@ -55,17 +55,13 @@ func newAdaptiveTimer(logger log.ContextLogger, router adapter.Router, config ti
} }
} }
func (t *adaptiveTimer) start(_ uint64) { func (t *adaptiveTimer) start(immediate bool) {
t.access.Lock()
defer t.access.Unlock()
t.startLocked()
}
func (t *adaptiveTimer) startNow() {
t.access.Lock() t.access.Lock()
t.startLocked() t.startLocked()
t.access.Unlock() t.access.Unlock()
t.poll() if immediate {
t.poll()
}
} }
func (t *adaptiveTimer) startLocked() { func (t *adaptiveTimer) startLocked() {
@@ -90,12 +86,6 @@ func (t *adaptiveTimer) stopLocked() {
} }
} }
func (t *adaptiveTimer) running() bool {
t.access.Lock()
defer t.access.Unlock()
return t.timer != nil
}
func (t *adaptiveTimer) poll() { func (t *adaptiveTimer) poll() {
t.access.Lock() t.access.Lock()
defer t.access.Unlock() defer t.access.Unlock()
@@ -144,13 +134,8 @@ func (t *adaptiveTimer) poll() {
interval = t.maxInterval interval = t.maxInterval
} else { } else {
timeToLimit := time.Duration(float64(remaining) / float64(delta) * float64(t.lastInterval)) timeToLimit := time.Duration(float64(remaining) / float64(delta) * float64(t.lastInterval))
interval = timeToLimit / time.Duration(t.checksBeforeLimit) interval = max(timeToLimit/time.Duration(t.checksBeforeLimit), t.minInterval)
if interval < t.minInterval { interval = min(interval, t.maxInterval)
interval = t.minInterval
}
if interval > t.maxInterval {
interval = t.maxInterval
}
} }
t.lastInterval = interval t.lastInterval = interval

View File

@@ -10,6 +10,7 @@ import (
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
"slices"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@@ -127,7 +128,7 @@ func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundCont
var uidFound bool var uidFound bool
statusContent, err := os.ReadFile(F.ToString("/proc/", senderPid, "/status")) statusContent, err := os.ReadFile(F.ToString("/proc/", senderPid, "/status"))
if err == nil { if err == nil {
for _, line := range strings.Split(string(statusContent), "\n") { for line := range strings.SplitSeq(string(statusContent), "\n") {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if strings.HasPrefix(line, "Uid:") { if strings.HasPrefix(line, "Uid:") {
fields := strings.Fields(line) fields := strings.Fields(line)
@@ -255,8 +256,8 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami
return return
} }
var nibbles []string var nibbles []string
for i := len(address) - 1; i >= 0; i-- { for _, v := range slices.Backward(address) {
b := address[i] b := v
nibbles = append(nibbles, fmt.Sprintf("%x", b&0x0F)) nibbles = append(nibbles, fmt.Sprintf("%x", b&0x0F))
nibbles = append(nibbles, fmt.Sprintf("%x", b>>4)) nibbles = append(nibbles, fmt.Sprintf("%x", b>>4))
} }

View File

@@ -248,7 +248,7 @@ func (t *Transport) tryOneName(ctx context.Context, servers *LinkServers, messag
sLen := uint32(len(servers.Servers)) sLen := uint32(len(servers.Servers))
var lastErr error var lastErr error
for i := 0; i < t.attempts; i++ { for i := 0; i < t.attempts; i++ {
for j := uint32(0); j < sLen; j++ { for j := range sLen {
server := servers.Servers[(serverOffset+j)%sLen] server := servers.Servers[(serverOffset+j)%sLen]
question := message.Question[0] question := message.Question[0]
question.Name = fqdn question.Name = fqdn

View File

@@ -105,15 +105,3 @@ func ParsePluginOptions(s string) (opts Args, err error) {
} }
return opts, nil return opts, nil
} }
// Escape backslashes and all the bytes that are in set.
func backslashEscape(s string, set []byte) string {
var buf bytes.Buffer
for _, b := range []byte(s) {
if b == '\\' || bytes.IndexByte(set, b) != -1 {
buf.WriteByte('\\')
}
buf.WriteByte(b)
}
return buf.String()
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@@ -100,7 +99,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
return nil, err return nil, err
} }
client := NewGunServiceClient(clientConn).(GunServiceCustomNameClient) client := NewGunServiceClient(clientConn).(GunServiceCustomNameClient)
ctx, cancel := common.ContextWithCancelCause(ctx) ctx, cancel := context.WithCancelCause(ctx)
stream, err := client.TunCustomName(ctx, c.serviceName) stream, err := client.TunCustomName(ctx, c.serviceName)
if err != nil { if err != nil {
cancel(err) cancel(err)

View File

@@ -25,12 +25,12 @@ import (
type requestInfoKey struct{} type requestInfoKey struct{}
// NewRequestInfoContext creates a context with ri. // NewRequestInfoContext creates a context with ri.
func NewRequestInfoContext(ctx context.Context, ri interface{}) context.Context { func NewRequestInfoContext(ctx context.Context, ri any) context.Context {
return context.WithValue(ctx, requestInfoKey{}, ri) return context.WithValue(ctx, requestInfoKey{}, ri)
} }
// RequestInfoFromContext extracts the RequestInfo from ctx. // RequestInfoFromContext extracts the RequestInfo from ctx.
func RequestInfoFromContext(ctx context.Context) interface{} { func RequestInfoFromContext(ctx context.Context) any {
return ctx.Value(requestInfoKey{}) return ctx.Value(requestInfoKey{})
} }
@@ -39,11 +39,11 @@ func RequestInfoFromContext(ctx context.Context) interface{} {
type clientHandshakeInfoKey struct{} type clientHandshakeInfoKey struct{}
// ClientHandshakeInfoFromContext extracts the ClientHandshakeInfo from ctx. // ClientHandshakeInfoFromContext extracts the ClientHandshakeInfo from ctx.
func ClientHandshakeInfoFromContext(ctx context.Context) interface{} { func ClientHandshakeInfoFromContext(ctx context.Context) any {
return ctx.Value(clientHandshakeInfoKey{}) return ctx.Value(clientHandshakeInfoKey{})
} }
// NewClientHandshakeInfoContext creates a context with chi. // NewClientHandshakeInfoContext creates a context with chi.
func NewClientHandshakeInfoContext(ctx context.Context, chi interface{}) context.Context { func NewClientHandshakeInfoContext(ctx context.Context, chi any) context.Context {
return context.WithValue(ctx, clientHandshakeInfoKey{}, chi) return context.WithValue(ctx, clientHandshakeInfoKey{}, chi)
} }

View File

@@ -20,16 +20,15 @@ package credentials
import ( import (
"crypto/tls" "crypto/tls"
"slices"
) )
const alpnProtoStrH2 = "h2" const alpnProtoStrH2 = "h2"
// AppendH2ToNextProtos appends h2 to next protos. // AppendH2ToNextProtos appends h2 to next protos.
func AppendH2ToNextProtos(ps []string) []string { func AppendH2ToNextProtos(ps []string) []string {
for _, p := range ps { if slices.Contains(ps, alpnProtoStrH2) {
if p == alpnProtoStrH2 { return ps
return ps
}
} }
ret := make([]string, 0, len(ps)+1) ret := make([]string, 0, len(ps)+1)
ret = append(ret, ps...) ret = append(ret, ps...)

View File

@@ -60,7 +60,7 @@ func (s *Server) Tun(server GunService_TunServer) error {
if grpcMetadata, loaded := gM.FromIncomingContext(server.Context()); loaded { if grpcMetadata, loaded := gM.FromIncomingContext(server.Context()); loaded {
forwardFrom := strings.Join(grpcMetadata.Get("X-Forwarded-For"), ",") forwardFrom := strings.Join(grpcMetadata.Get("X-Forwarded-For"), ",")
if forwardFrom != "" { if forwardFrom != "" {
for _, from := range strings.Split(forwardFrom, ",") { for from := range strings.SplitSeq(forwardFrom, ",") {
originAddr := M.ParseSocksaddr(from) originAddr := M.ParseSocksaddr(from)
if originAddr.IsValid() { if originAddr.IsValid() {
source = originAddr.Unwrap() source = originAddr.Unwrap()

View File

@@ -136,7 +136,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
sizes[0] = n sizes[0] = n
if n > 3 { if n > 3 {
b := packets[0] b := packets[0]
common.ClearArray(b[1:4]) clear(b[1:4])
} }
eps[0] = remoteEndpoint(M.SocksaddrFromNet(addr).Unwrap().AddrPort()) eps[0] = remoteEndpoint(M.SocksaddrFromNet(addr).Unwrap().AddrPort())
count = 1 count = 1

View File

@@ -7,6 +7,7 @@ import (
"net" "net"
"net/netip" "net/netip"
"os" "os"
"sync"
"time" "time"
"github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/buffer"
@@ -42,6 +43,7 @@ type stackDevice struct {
outbound chan *stack.PacketBuffer outbound chan *stack.PacketBuffer
packetOutbound chan *buf.Buffer packetOutbound chan *buf.Buffer
done chan struct{} done chan struct{}
closeOnce sync.Once
dispatcher stack.NetworkDispatcher dispatcher stack.NetworkDispatcher
inet4Address netip.Addr inet4Address netip.Addr
inet6Address netip.Addr inet6Address netip.Addr
@@ -146,11 +148,17 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
} }
var networkProtocol tcpip.NetworkProtocolNumber var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() { if destination.IsIPv4() {
if !w.inet4Address.IsValid() {
return nil, E.New("missing IPv4 local address")
}
networkProtocol = header.IPv4ProtocolNumber networkProtocol = header.IPv4ProtocolNumber
bind.Addr = tun.AddressFromAddr(w.inet4Address) bind.Addr = tun.AddressFromAddr(w.inet4Address)
} else { } else {
if !w.inet6Address.IsValid() {
return nil, E.New("missing IPv6 local address")
}
networkProtocol = header.IPv6ProtocolNumber networkProtocol = header.IPv6ProtocolNumber
bind.Addr = tun.AddressFromAddr(w.inet4Address) bind.Addr = tun.AddressFromAddr(w.inet6Address)
} }
udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol) udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol)
if err != nil { if err != nil {
@@ -244,13 +252,15 @@ func (w *stackDevice) Events() <-chan wgTun.Event {
} }
func (w *stackDevice) Close() error { func (w *stackDevice) Close() error {
close(w.done) w.closeOnce.Do(func() {
close(w.events) close(w.done)
w.stack.Close() close(w.events)
for _, endpoint := range w.stack.CleanupEndpoints() { w.stack.Close()
endpoint.Abort() for _, endpoint := range w.stack.CleanupEndpoints() {
} endpoint.Abort()
w.stack.Wait() }
w.stack.Wait()
})
return nil return nil
} }

View File

@@ -111,6 +111,7 @@ func (w *systemDevice) Start() error {
} }
err = tunInterface.Start() err = tunInterface.Start()
if err != nil { if err != nil {
tunInterface.Close()
return err return err
} }
w.options.Logger.Info("started at ", w.options.Name) w.options.Logger.Info("started at ", w.options.Name)
@@ -147,7 +148,7 @@ func (w *systemDevice) Write(bufs [][]byte, offset int) (count int, err error) {
} else { } else {
for _, packet := range bufs { for _, packet := range bufs {
if tun.PacketOffset > 0 { if tun.PacketOffset > 0 {
common.ClearArray(packet[offset-tun.PacketOffset : offset]) clear(packet[offset-tun.PacketOffset : offset])
tun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:])) tun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:]))
} }
_, err = w.device.Write(packet[offset-tun.PacketOffset:]) _, err = w.device.Write(packet[offset-tun.PacketOffset:])
@@ -177,8 +178,14 @@ func (w *systemDevice) Events() <-chan wgTun.Event {
} }
func (w *systemDevice) Close() error { func (w *systemDevice) Close() error {
close(w.events) var err error
return w.device.Close() w.closeOnce.Do(func() {
close(w.events)
if w.device != nil {
err = w.device.Close()
}
})
return err
} }
func (w *systemDevice) BatchSize() int { func (w *systemDevice) BatchSize() int {

View File

@@ -5,6 +5,7 @@ package wireguard
import ( import (
"context" "context"
"net/netip" "net/netip"
"sync"
"time" "time"
"github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/buffer"
@@ -20,7 +21,6 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
"github.com/sagernet/wireguard-go/device" "github.com/sagernet/wireguard-go/device"
@@ -35,6 +35,7 @@ type systemStackDevice struct {
stack *stack.Stack stack *stack.Stack
endpoint *deviceEndpoint endpoint *deviceEndpoint
writeBufs [][]byte writeBufs [][]byte
closeOnce sync.Once
} }
func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) { func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
@@ -104,13 +105,13 @@ func (w *systemStackDevice) Write(bufs [][]byte, offset int) (count int, err err
} }
} }
if len(w.writeBufs) > 0 { if len(w.writeBufs) > 0 {
return w.batchDevice.BatchWrite(bufs, offset) return w.batchDevice.BatchWrite(w.writeBufs, offset)
} }
} else { } else {
for _, packet := range bufs { for _, packet := range bufs {
if !w.writeStack(packet[offset:]) { if !w.writeStack(packet[offset:]) {
if tun.PacketOffset > 0 { if tun.PacketOffset > 0 {
common.ClearArray(packet[offset-tun.PacketOffset : offset]) clear(packet[offset-tun.PacketOffset : offset])
tun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:])) tun.PacketFillHeader(packet[offset-tun.PacketOffset:], tun.PacketIPVersion(packet[offset:]))
} }
_, err = w.device.Write(packet[offset-tun.PacketOffset:]) _, err = w.device.Write(packet[offset-tun.PacketOffset:])
@@ -125,13 +126,17 @@ func (w *systemStackDevice) Write(bufs [][]byte, offset int) (count int, err err
} }
func (w *systemStackDevice) Close() error { func (w *systemStackDevice) Close() error {
close(w.endpoint.done) var err error
w.stack.Close() w.closeOnce.Do(func() {
for _, endpoint := range w.stack.CleanupEndpoints() { close(w.endpoint.done)
endpoint.Abort() w.stack.Close()
} for _, endpoint := range w.stack.CleanupEndpoints() {
w.stack.Wait() endpoint.Abort()
return w.systemDevice.Close() }
w.stack.Wait()
err = w.systemDevice.Close()
})
return err
} }
func (w *systemStackDevice) writeStack(packet []byte) bool { func (w *systemStackDevice) writeStack(packet []byte) bool {

View File

@@ -182,10 +182,10 @@ func (e *Endpoint) Start(resolve bool) error {
return err return err
} }
logger := &device.Logger{ logger := &device.Logger{
Verbosef: func(format string, args ...interface{}) { Verbosef: func(format string, args ...any) {
e.options.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) e.options.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
}, },
Errorf: func(format string, args ...interface{}) { Errorf: func(format string, args ...any) {
e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
}, },
} }
@@ -197,13 +197,15 @@ func (e *Endpoint) Start(resolve bool) error {
} }
wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers) wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers)
e.tunDevice.SetDevice(wgDevice) e.tunDevice.SetDevice(wgDevice)
ipcConf := e.ipcConf var ipcConf strings.Builder
ipcConf.WriteString(e.ipcConf)
for _, peer := range e.peers { for _, peer := range e.peers {
ipcConf += peer.GenerateIpcLines() ipcConf.WriteString(peer.GenerateIpcLines())
} }
err = wgDevice.IpcSet(ipcConf) err = wgDevice.IpcSet(ipcConf.String())
if err != nil { if err != nil {
return E.Cause(err, "setup wireguard: \n", ipcConf) wgDevice.Close()
return E.Cause(err, "setup wireguard: \n", ipcConf.String())
} }
e.device = wgDevice e.device = wgDevice
e.pause = service.FromContext[pause.Manager](e.options.Context) e.pause = service.FromContext[pause.Manager](e.options.Context)
@@ -231,10 +233,12 @@ func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
func (e *Endpoint) Close() error { func (e *Endpoint) Close() error {
if e.pauseCallback != nil { if e.pauseCallback != nil {
e.pause.UnregisterCallback(e.pauseCallback) e.pause.UnregisterCallback(e.pauseCallback)
e.pauseCallback = nil
} }
if e.device != nil { if e.device != nil {
e.device.Down() e.device.Down()
e.device.Close() e.device.Close()
e.device = nil
} }
return nil return nil
} }
@@ -273,18 +277,19 @@ type peerConfig struct {
} }
func (c peerConfig) GenerateIpcLines() string { func (c peerConfig) GenerateIpcLines() string {
ipcLines := "\npublic_key=" + c.publicKeyHex var ipcLines strings.Builder
ipcLines.WriteString("\npublic_key=" + c.publicKeyHex)
if c.endpoint.IsValid() { if c.endpoint.IsValid() {
ipcLines += "\nendpoint=" + c.endpoint.String() ipcLines.WriteString("\nendpoint=" + c.endpoint.String())
} }
if c.preSharedKeyHex != "" { if c.preSharedKeyHex != "" {
ipcLines += "\npreshared_key=" + c.preSharedKeyHex ipcLines.WriteString("\npreshared_key=" + c.preSharedKeyHex)
} }
for _, allowedIP := range c.allowedIPs { for _, allowedIP := range c.allowedIPs {
ipcLines += "\nallowed_ip=" + allowedIP.String() ipcLines.WriteString("\nallowed_ip=" + allowedIP.String())
} }
if c.keepalive > 0 { if c.keepalive > 0 {
ipcLines += "\npersistent_keepalive_interval=" + F.ToString(c.keepalive) ipcLines.WriteString("\npersistent_keepalive_interval=" + F.ToString(c.keepalive))
} }
return ipcLines return ipcLines.String()
} }