From 44981fd803f17b818f68aea1f0970ba9e100ad0e Mon Sep 17 00:00:00 2001 From: Kismet Date: Mon, 11 Aug 2025 21:28:47 +0800 Subject: [PATCH 01/63] documentation: Fix typo --- docs/configuration/endpoint/wireguard.zh.md | 2 +- docs/configuration/outbound/wireguard.zh.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/endpoint/wireguard.zh.md b/docs/configuration/endpoint/wireguard.zh.md index f80c82b4..cf820580 100644 --- a/docs/configuration/endpoint/wireguard.zh.md +++ b/docs/configuration/endpoint/wireguard.zh.md @@ -61,7 +61,7 @@ WireGuard MTU。 ==必填== -接口的 IPv4/IPv6 地址或地址段的列表您。 +接口的 IPv4/IPv6 地址或地址段的列表。 要分配给接口的 IP(v4 或 v6)地址段列表。 diff --git a/docs/configuration/outbound/wireguard.zh.md b/docs/configuration/outbound/wireguard.zh.md index 4597fd9d..46b49a94 100644 --- a/docs/configuration/outbound/wireguard.zh.md +++ b/docs/configuration/outbound/wireguard.zh.md @@ -88,7 +88,7 @@ icon: material/delete-clock ==必填== -接口的 IPv4/IPv6 地址或地址段的列表您。 +接口的 IPv4/IPv6 地址或地址段的列表。 要分配给接口的 IP(v4 或 v6)地址段列表。 From 531de7712402c0301c2f8109d46e3cee32119c77 Mon Sep 17 00:00:00 2001 From: yu Date: Mon, 11 Aug 2025 21:30:46 +0800 Subject: [PATCH 02/63] documentation: Fix tun address format --- docs/manual/proxy/client.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/manual/proxy/client.md b/docs/manual/proxy/client.md index 8583a6a2..e8c80523 100644 --- a/docs/manual/proxy/client.md +++ b/docs/manual/proxy/client.md @@ -108,7 +108,7 @@ flowchart TB "inbounds": [ { "type": "tun", - "inet4_address": "172.19.0.1/30", + "address": ["172.19.0.1/30"], "auto_route": true, // "auto_redirect": true, // On linux "strict_route": true @@ -162,8 +162,7 @@ flowchart TB "inbounds": [ { "type": "tun", - "inet4_address": "172.19.0.1/30", - "inet6_address": "fdfe:dcba:9876::1/126", + "address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"], "auto_route": true, // "auto_redirect": true, // On linux "strict_route": true @@ -233,8 +232,7 @@ flowchart TB "inbounds": [ { "type": "tun", - "inet4_address": "172.19.0.1/30", - "inet6_address": "fdfe:dcba:9876::1/126", + "address": ["172.19.0.1/30","fdfe:dcba:9876::1/126"], "auto_route": true, // "auto_redirect": true, // On linux "strict_route": true From 09d3b8f2c28386326148dbe16ce0e81e87e5c7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Aug 2025 11:08:40 +0800 Subject: [PATCH 03/63] release: Fix repo --- .github/workflows/linux.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c64398b0..ad0c8af5 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,6 +7,11 @@ on: description: "Version name" required: true type: string + forceBeta: + description: "Force beta" + required: false + type: boolean + default: false release: types: - published @@ -99,11 +104,11 @@ jobs: run: |- TZ=UTC touch -t '197001010000' dist/sing-box - name: Set name - if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }} + if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta run: |- echo "NAME=sing-box" >> "$GITHUB_ENV" - name: Set beta name - if: contains(needs.calculate_version.outputs.version, '-') + if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta run: |- echo "NAME=sing-box-beta" >> "$GITHUB_ENV" - name: Set version From 2358efe44a945279913271988ff86284a0c15e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Aug 2025 22:10:45 +0800 Subject: [PATCH 04/63] release: Fix android build --- cmd/internal/build_libbox/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index de470eb3..c7bdf6cf 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -107,13 +107,13 @@ func buildAndroid() { } if !debugEnabled { + sharedFlags[3] = sharedFlags[3] + " -checklinkname=0" args = append(args, sharedFlags...) } else { + debugFlags[1] = debugFlags[1] + " -checklinkname=0" args = append(args, debugFlags...) } - args = append(args, "-ldflags", "-checklinkname=0") - tags := append(sharedTags, memcTags...) if debugEnabled { tags = append(tags, debugTags...) From a65d3e040a11582c33f5255e5e05ead97f341b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Aug 2025 11:26:05 +0800 Subject: [PATCH 05/63] platform: Fix context --- experimental/libbox/config.go | 5 ++++- experimental/libbox/service.go | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 7dc84b83..89c51222 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -22,6 +22,7 @@ import ( "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/filemanager" ) func BaseContext(platformInterface PlatformInterface) context.Context { @@ -33,7 +34,9 @@ func BaseContext(platformInterface PlatformInterface) context.Context { }) } } - return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry()) + ctx := context.Background() + ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) + return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry()) } func parseConfig(ctx context.Context, configContent string) (option.Options, error) { diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 7f80e6fe..8c94b389 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -27,7 +27,6 @@ import ( "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" - "github.com/sagernet/sing/service/filemanager" "github.com/sagernet/sing/service/pause" ) @@ -44,7 +43,6 @@ type BoxService struct { func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { ctx := BaseContext(platformInterface) - ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) options, err := parseConfig(ctx, configContent) if err != nil { From c0ac3c748ceaf09f387fed246204a1535b03e5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Aug 2025 11:48:44 +0800 Subject: [PATCH 06/63] Reduce default MTU for android --- protocol/tun/inbound.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index e018b642..fc69309c 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -137,6 +137,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if platformInterface != nil && platformInterface.UnderNetworkExtension() { // In Network Extension, when MTU exceeds 4064 (4096-UTUN_IF_HEADROOM_SIZE), the performance of tun will drop significantly, which may be a system bug. tunMTU = 4064 + } else if C.IsAndroid { + // Some Android devices report ENOBUFS when using MTU 65535 + tunMTU = 9000 } else { tunMTU = 65535 } From 4a209f1afba2a165cd649d1679024cc976a05681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Aug 2025 21:04:01 +0800 Subject: [PATCH 07/63] Fix h2mux close check --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fef5f03b..45654aff 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/sing v0.7.5 - github.com/sagernet/sing-mux v0.3.2 + github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-quic v0.5.0-beta.3 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 diff --git a/go.sum b/go.sum index e83a0b05..2660d7c5 100644 --- a/go.sum +++ b/go.sum @@ -169,8 +169,8 @@ github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9Gy github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc= github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= -github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= +github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= +github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.5.0-beta.3 h1:X/acRNsqQNfDlmwE7SorHfaZiny5e67hqIzM/592ric= github.com/sagernet/sing-quic v0.5.0-beta.3/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= From 5eb318ba061a2c4d2f6e822862b763eb1a9c50dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Aug 2025 21:15:43 +0800 Subject: [PATCH 08/63] Update Go to 1.25 --- .github/setup_legacy_go.sh | 2 +- .github/workflows/build.yml | 42 +++++++++++++++++++++---------------- .github/workflows/lint.yml | 2 +- .github/workflows/linux.yml | 4 ++-- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/.github/setup_legacy_go.sh b/.github/setup_legacy_go.sh index 3f1a31ad..d4617e79 100755 --- a/.github/setup_legacy_go.sh +++ b/.github/setup_legacy_go.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -VERSION="1.23.6" +VERSION="1.23.12" mkdir -p $HOME/go cd $HOME/go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ccc81a8..6da08464 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.0 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -88,13 +88,14 @@ jobs: - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" } - { os: windows, arch: amd64 } - - { os: windows, arch: amd64, legacy_go: true } + - { os: windows, arch: amd64, legacy_go123: true, legacy_name: "windows-7" } - { os: windows, arch: "386" } - - { os: windows, arch: "386", legacy_go: true } + - { os: windows, arch: "386", legacy_go123: true, legacy_name: "windows-7" } - { os: windows, arch: arm64 } - { os: darwin, arch: amd64 } - { os: darwin, arch: arm64 } + - { os: darwin, arch: amd64, legacy_go124: true, legacy_name: "macos-11" } - { os: android, arch: arm64, ndk: "aarch64-linux-android21" } - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" } @@ -106,24 +107,29 @@ jobs: with: fetch-depth: 0 - name: Setup Go - if: ${{ ! matrix.legacy_go }} + if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }} uses: actions/setup-go@v5 with: - go-version: ^1.24.6 - - name: Cache Legacy Go - if: matrix.require_legacy_go + go-version: ^1.25.0 + - name: Setup Go 1.24 + if: matrix.legacy_go124 + uses: actions/setup-go@v5 + with: + go-version: ~1.24.6 + - name: Cache Go 1.23 + if: matrix.legacy_go123 id: cache-legacy-go uses: actions/cache@v4 with: path: | ~/go/go_legacy - key: go_legacy_1236 - - name: Setup Legacy Go - if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true' + key: go_legacy_12312 + - name: Setup Go 1.23 + if: matrix.legacy_go123 && steps.cache-legacy-go.outputs.cache-hit != 'true' run: |- .github/setup_legacy_go.sh - - name: Setup Legacy Go 2 - if: matrix.legacy_go + - name: Setup Go 1.23 + if: matrix.legacy_go123 run: |- echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV @@ -184,8 +190,8 @@ jobs: DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}" elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}" - elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then - DIR_NAME="${DIR_NAME}-legacy" + elif [[ -n "${{ matrix.legacy_name }}" ]]; then + DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}" fi echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" PKG_VERSION="${{ needs.calculate_version.outputs.version }}" @@ -277,7 +283,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }} + name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }} path: "dist" build_android: name: Build Android @@ -294,7 +300,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.0 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -374,7 +380,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.0 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -472,7 +478,7 @@ jobs: if: matrix.if uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.0 - name: Setup Xcode stable if: matrix.if && github.ref == 'refs/heads/main-next' run: |- diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 08d54402..b481fef0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.0 - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ad0c8af5..eea2e545 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.0 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -71,7 +71,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.0 - name: Setup Android NDK if: matrix.os == 'android' uses: nttld/setup-ndk@v1 From 7e190e92ca9e1d3a3ca4a2f0ebd89518f9769c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Aug 2025 21:16:38 +0800 Subject: [PATCH 09/63] Fix build with Go 1.25 --- Makefile | 4 ++-- experimental/libbox/iterator.go | 1 + go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 3c0c152d..00af283e 100644 --- a/Makefile +++ b/Makefile @@ -245,8 +245,8 @@ lib: go run ./cmd/internal/build_libbox -target ios lib_install: - go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.7 - go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.7 + go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8 + go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8 docs: venv/bin/mkdocs serve diff --git a/experimental/libbox/iterator.go b/experimental/libbox/iterator.go index 50d7385b..b71ab886 100644 --- a/experimental/libbox/iterator.go +++ b/experimental/libbox/iterator.go @@ -18,6 +18,7 @@ func newIterator[T any](values []T) *iterator[T] { return &iterator[T]{values} } +//go:noinline func newPtrIterator[T any](values []T) *iterator[*T] { return &iterator[*T]{common.Map(values, func(value T) *T { return &value })} } diff --git a/go.mod b/go.mod index 45654aff..67425190 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 - github.com/sagernet/gomobile v0.1.7 + github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/sing v0.7.5 @@ -44,11 +44,11 @@ require ( github.com/vishvananda/netns v0.0.5 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.40.0 + golang.org/x/crypto v0.41.0 golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 - golang.org/x/mod v0.26.0 - golang.org/x/net v0.42.0 - golang.org/x/sys v0.34.0 + golang.org/x/mod v0.27.0 + golang.org/x/net v0.43.0 + golang.org/x/sys v0.35.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 @@ -123,10 +123,10 @@ require ( go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.34.0 // indirect + golang.org/x/tools v0.36.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect diff --git a/go.sum b/go.sum index 2660d7c5..1f926629 100644 --- a/go.sum +++ b/go.sum @@ -156,8 +156,8 @@ github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= -github.com/sagernet/gomobile v0.1.7 h1:I9jCJZTH0weP5MsuydvYHX5QfN/r6Fe8ptAIj1+SJVg= -github.com/sagernet/gomobile v0.1.7/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= +github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo= +github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38= github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= @@ -262,18 +262,18 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= @@ -285,20 +285,20 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 043a2e7a07b111ed24b020da12980021a90f88a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:07:02 +0000 Subject: [PATCH 10/63] [dependencies] Update github-actions --- .github/workflows/build.yml | 14 +++++++------- .github/workflows/docker.yml | 4 ++-- .github/workflows/lint.yml | 4 ++-- .github/workflows/linux.yml | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6da08464..25114dff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: version: ${{ steps.outputs.outputs.version }} steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Setup Go @@ -103,7 +103,7 @@ jobs: - { os: android, arch: "386", ndk: "i686-linux-android21" } steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Setup Go @@ -293,7 +293,7 @@ jobs: - calculate_version steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 submodules: 'recursive' @@ -373,7 +373,7 @@ jobs: - calculate_version steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 submodules: 'recursive' @@ -470,7 +470,7 @@ jobs: steps: - name: Checkout if: matrix.if - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 submodules: 'recursive' @@ -630,7 +630,7 @@ jobs: - build_apple steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Cache ghr @@ -653,7 +653,7 @@ jobs: git tag v${{ needs.calculate_version.outputs.version }} -f echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: path: dist merge-multiple: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index bcf210ab..a4432a21 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ jobs: echo "ref=$ref" echo "ref=$ref" >> $GITHUB_OUTPUT - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: ref: ${{ steps.ref.outputs.ref }} fetch-depth: 0 @@ -107,7 +107,7 @@ jobs: echo "latest=$latest" echo "latest=$latest" >> $GITHUB_OUTPUT - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: path: /tmp/digests pattern: digests-* diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b481fef0..abeaa4bc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Setup Go @@ -30,7 +30,7 @@ jobs: with: go-version: ^1.25.0 - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: version: latest args: --timeout=30m diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index eea2e545..a705d7ff 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -24,7 +24,7 @@ jobs: version: ${{ steps.outputs.outputs.version }} steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Setup Go @@ -65,7 +65,7 @@ jobs: - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 } steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Setup Go @@ -171,7 +171,7 @@ jobs: - build steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Set tag @@ -180,7 +180,7 @@ jobs: git tag v${{ needs.calculate_version.outputs.version }} -f echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: path: dist merge-multiple: true From 378e39f70cea36033f716f85dbe4ec5cb57adbc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 Aug 2025 22:42:54 +0800 Subject: [PATCH 11/63] Update golangci-lint to v2 --- .github/workflows/lint.yml | 2 +- .golangci.yml | 77 +++++++++++++++++--------- Makefile | 2 +- adapter/inbound.go | 3 +- cmd/internal/tun_bench/main.go | 4 +- common/dialer/default.go | 2 +- common/sniff/quic_blacklist.go | 5 +- common/tls/ech.go | 2 +- dns/transport/local/resolv.go | 8 +-- experimental/libbox/command_urltest.go | 5 +- experimental/v2rayapi/stats.go | 2 +- option/dns.go | 1 - protocol/group/urltest.go | 6 +- protocol/shadowsocks/outbound.go | 1 - protocol/ssh/outbound.go | 1 - protocol/trojan/outbound.go | 1 - protocol/vless/outbound.go | 1 - protocol/vmess/outbound.go | 1 - service/ssmapi/traffic.go | 1 - transport/v2rayquic/client.go | 3 +- 20 files changed, 67 insertions(+), 61 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index abeaa4bc..0a8cbd1a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.0 + go-version: ~1.24.6 - name: golangci-lint uses: golangci/golangci-lint-action@v8 with: diff --git a/.golangci.yml b/.golangci.yml index 9cb20ee8..de2aa5a6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,27 +1,6 @@ -linters: - disable-all: true - enable: - - gofumpt - - govet - - gci - - staticcheck - - paralleltest - - ineffassign - -linters-settings: - gci: - custom-order: true - sections: - - standard - - prefix(github.com/sagernet/) - - default - staticcheck: - checks: - - all - - -SA1003 - +version: "2" run: - go: "1.23" + go: "1.24" build-tags: - with_gvisor - with_quic @@ -30,7 +9,51 @@ run: - with_utls - with_acme - with_clash_api - -issues: - exclude-dirs: - - transport/simple-obfs +linters: + default: none + enable: + - govet + - ineffassign + - paralleltest + - staticcheck + settings: + staticcheck: + checks: + - all + - -S1000 + - -S1008 + - -S1017 + - -ST1003 + - -QF1001 + - -QF1003 + - -QF1008 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - transport/simple-obfs + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofumpt + settings: + gci: + sections: + - standard + - prefix(github.com/sagernet/) + - default + custom-order: true + exclusions: + generated: lax + paths: + - transport/simple-obfs + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index 00af283e..43f93c8c 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ lint: GOOS=freebsd golangci-lint run ./... lint_install: - go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest + go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest proto: @go run ./cmd/internal/protogen diff --git a/adapter/inbound.go b/adapter/inbound.go index 0de4c799..edba8447 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -135,8 +135,7 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) { func OverrideContext(ctx context.Context) context.Context { if metadata := ContextFrom(ctx); metadata != nil { - var newMetadata InboundContext - newMetadata = *metadata + newMetadata := *metadata return WithContext(ctx, &newMetadata) } return ctx diff --git a/cmd/internal/tun_bench/main.go b/cmd/internal/tun_bench/main.go index abad8c34..e62841dc 100644 --- a/cmd/internal/tun_bench/main.go +++ b/cmd/internal/tun_bench/main.go @@ -151,9 +151,7 @@ func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags var sudoArgs []string if len(flags) > 0 { sudoArgs = append(sudoArgs, "env") - for _, flag := range flags { - sudoArgs = append(sudoArgs, flag) - } + sudoArgs = append(sudoArgs, flags...) } sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name()) boxProcess := shell.Exec("sudo", sudoArgs...) diff --git a/common/dialer/default.go b/common/dialer/default.go index 4b9cf71c..5337934a 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -271,7 +271,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin } else { dialer = d.udpDialer4 } - fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout + fastFallback := time.Since(d.networkLastFallback.Load()) < C.TCPTimeout var ( conn net.Conn isPrimary bool diff --git a/common/sniff/quic_blacklist.go b/common/sniff/quic_blacklist.go index 7d5f91e7..d5ff7bcf 100644 --- a/common/sniff/quic_blacklist.go +++ b/common/sniff/quic_blacklist.go @@ -17,8 +17,5 @@ var uQUICChrome115 = &ja3.ClientHello{ } func maybeUQUIC(fingerprint *ja3.ClientHello) bool { - if uQUICChrome115.Equals(fingerprint, true) { - return true - } - return false + return !uQUICChrome115.Equals(fingerprint, true) } diff --git a/common/tls/ech.go b/common/tls/ech.go index f4434604..830a8d08 100644 --- a/common/tls/ech.go +++ b/common/tls/ech.go @@ -125,7 +125,7 @@ func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (a func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { s.access.Lock() defer s.access.Unlock() - if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL { + if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL { message := &mDNS.Msg{ MsgHdr: mDNS.MsgHdr{ RecursionDesired: true, diff --git a/dns/transport/local/resolv.go b/dns/transport/local/resolv.go index 754d4539..afe679a2 100644 --- a/dns/transport/local/resolv.go +++ b/dns/transport/local/resolv.go @@ -104,7 +104,7 @@ func (c *dnsConfig) serverOffset() uint32 { return 0 } -func (conf *dnsConfig) nameList(name string) []string { +func (c *dnsConfig) nameList(name string) []string { l := len(name) rooted := l > 0 && name[l-1] == '.' if l > 254 || l == 254 && !rooted { @@ -118,15 +118,15 @@ func (conf *dnsConfig) nameList(name string) []string { return []string{name} } - hasNdots := strings.Count(name, ".") >= conf.ndots + hasNdots := strings.Count(name, ".") >= c.ndots name += "." // l++ - names := make([]string, 0, 1+len(conf.search)) + names := make([]string, 0, 1+len(c.search)) if hasNdots && !avoidDNS(name) { names = append(names, name) } - for _, suffix := range conf.search { + for _, suffix := range c.search { fqdn := name + suffix if !avoidDNS(fqdn) && len(fqdn) <= 254 { names = append(names, fqdn) diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go index 5dcb3d67..907d1699 100644 --- a/experimental/libbox/command_urltest.go +++ b/experimental/libbox/command_urltest.go @@ -62,10 +62,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { return false } _, isGroup := it.(adapter.OutboundGroup) - if isGroup { - return false - } - return true + return !isGroup }) b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10)) for _, detour := range outbounds { diff --git a/experimental/v2rayapi/stats.go b/experimental/v2rayapi/stats.go index 6c44518f..a85e190f 100644 --- a/experimental/v2rayapi/stats.go +++ b/experimental/v2rayapi/stats.go @@ -192,7 +192,7 @@ func (s *StatsService) GetSysStats(ctx context.Context, request *SysStatsRequest var rtm runtime.MemStats runtime.ReadMemStats(&rtm) response := &SysStatsResponse{ - Uptime: uint32(time.Now().Sub(s.createdAt).Seconds()), + Uptime: uint32(time.Since(s.createdAt).Seconds()), NumGoroutine: uint32(runtime.NumGoroutine()), Alloc: rtm.Alloc, TotalAlloc: rtm.TotalAlloc, diff --git a/option/dns.go b/option/dns.go index 0886dd64..422d7b3b 100644 --- a/option/dns.go +++ b/option/dns.go @@ -100,7 +100,6 @@ func rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) { } ruleAction.Action = C.RuleActionTypePredefined ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode)) - return } type DNSClientOptions struct { diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index f44698e1..719f4a2c 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -313,7 +313,7 @@ func (g *URLTestGroup) Select(network string) (adapter.Outbound, bool) { } func (g *URLTestGroup) loopCheck() { - if time.Now().Sub(g.lastActive.Load()) > g.interval { + if time.Since(g.lastActive.Load()) > g.interval { g.lastActive.Store(time.Now()) g.CheckOutbounds(false) } @@ -323,7 +323,7 @@ func (g *URLTestGroup) loopCheck() { return case <-g.ticker.C: } - if time.Now().Sub(g.lastActive.Load()) > g.idleTimeout { + if time.Since(g.lastActive.Load()) > g.idleTimeout { g.access.Lock() g.ticker.Stop() g.ticker = nil @@ -360,7 +360,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint continue } history := g.history.LoadURLTestHistory(realTag) - if !force && history != nil && time.Now().Sub(history.Time) < g.interval { + if !force && history != nil && time.Since(history.Time) < g.interval { continue } checked[realTag] = true diff --git a/protocol/shadowsocks/outbound.go b/protocol/shadowsocks/outbound.go index 875c9e69..9b9d9252 100644 --- a/protocol/shadowsocks/outbound.go +++ b/protocol/shadowsocks/outbound.go @@ -128,7 +128,6 @@ func (h *Outbound) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return } func (h *Outbound) Close() error { diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index aeb43d34..b2f96807 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -180,7 +180,6 @@ func (s *Outbound) connect() (*ssh.Client, error) { func (s *Outbound) InterfaceUpdated() { common.Close(s.clientConn) - return } func (s *Outbound) Close() error { diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index 37a6933c..cd290386 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -105,7 +105,6 @@ func (h *Outbound) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return } func (h *Outbound) Close() error { diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index e0208be9..b95a36f7 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -124,7 +124,6 @@ func (h *Outbound) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return } func (h *Outbound) Close() error { diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index be05990e..bf76ab3d 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -108,7 +108,6 @@ func (h *Outbound) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return } func (h *Outbound) Close() error { diff --git a/service/ssmapi/traffic.go b/service/ssmapi/traffic.go index 23a034c1..df1b04b1 100644 --- a/service/ssmapi/traffic.go +++ b/service/ssmapi/traffic.go @@ -178,7 +178,6 @@ func (s *TrafficManager) ReadUsers(users []*UserObject) { for _, user := range users { s.readUser(user) } - return } func (s *TrafficManager) ReadGlobal() ( diff --git a/transport/v2rayquic/client.go b/transport/v2rayquic/client.go index a1c3e3a6..f9556211 100644 --- a/transport/v2rayquic/client.go +++ b/transport/v2rayquic/client.go @@ -72,8 +72,7 @@ func (c *Client) offerNew() (quic.Connection, error) { if err != nil { return nil, err } - var packetConn net.PacketConn - packetConn = bufio.NewUnbindPacketConn(udpConn) + packetConn := bufio.NewUnbindPacketConn(udpConn) quicConn, err := qtls.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig) if err != nil { packetConn.Close() From 8752b631bdc40d49394cfe2eed54bfcd8b0e3be4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 17:38:06 +0800 Subject: [PATCH 12/63] [dependencies] Update golang Docker tag to v1.25 (#3276) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fecd98a6..359ae293 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder LABEL maintainer="nekohasekai " COPY . /go/src/github.com/sagernet/sing-box WORKDIR /go/src/github.com/sagernet/sing-box From fdc181106de63a324b7e0cb936dc7da1079e9dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 14 Aug 2025 01:48:42 +0800 Subject: [PATCH 13/63] Fix atomic pointer usages --- common/dialer/tfo.go | 80 ++++++++++++++++++-------------- experimental/v2rayapi/stats.go | 2 +- go.mod | 4 +- go.sum | 8 ++-- transport/v2raygrpc/client.go | 17 ++++--- transport/v2rayquic/client.go | 15 +++--- transport/v2raywebsocket/conn.go | 52 ++++++++++++--------- 7 files changed, 100 insertions(+), 78 deletions(-) diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index 8ea59ca6..cd1a2a22 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -10,6 +10,8 @@ import ( "sync" "time" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -22,7 +24,7 @@ type slowOpenConn struct { ctx context.Context network string destination M.Socksaddr - conn net.Conn + conn atomic.Pointer[net.TCPConn] create chan struct{} done chan struct{} access sync.Mutex @@ -50,22 +52,25 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des } func (c *slowOpenConn) Read(b []byte) (n int, err error) { - if c.conn == nil { - select { - case <-c.create: - if c.err != nil { - return 0, c.err - } - case <-c.done: - return 0, os.ErrClosed - } + conn := c.conn.Load() + if conn != nil { + return conn.Read(b) + } + select { + case <-c.create: + if c.err != nil { + return 0, c.err + } + return c.conn.Load().Read(b) + case <-c.done: + return 0, os.ErrClosed } - return c.conn.Read(b) } func (c *slowOpenConn) Write(b []byte) (n int, err error) { - if c.conn != nil { - return c.conn.Write(b) + tcpConn := c.conn.Load() + if tcpConn != nil { + return tcpConn.Write(b) } c.access.Lock() defer c.access.Unlock() @@ -74,7 +79,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) { if c.err != nil { return 0, c.err } - return c.conn.Write(b) + return c.conn.Load().Write(b) case <-c.done: return 0, os.ErrClosed default: @@ -83,7 +88,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) { if err != nil { c.err = err } else { - c.conn = conn + c.conn.Store(conn.(*net.TCPConn)) } n = len(b) close(c.create) @@ -93,70 +98,77 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) { func (c *slowOpenConn) Close() error { c.closeOnce.Do(func() { close(c.done) - if c.conn != nil { - c.conn.Close() + conn := c.conn.Load() + if conn != nil { + conn.Close() } }) return nil } func (c *slowOpenConn) LocalAddr() net.Addr { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return M.Socksaddr{} } - return c.conn.LocalAddr() + return conn.LocalAddr() } func (c *slowOpenConn) RemoteAddr() net.Addr { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return M.Socksaddr{} } - return c.conn.RemoteAddr() + return conn.RemoteAddr() } func (c *slowOpenConn) SetDeadline(t time.Time) error { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return os.ErrInvalid } - return c.conn.SetDeadline(t) + return conn.SetDeadline(t) } func (c *slowOpenConn) SetReadDeadline(t time.Time) error { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return os.ErrInvalid } - return c.conn.SetReadDeadline(t) + return conn.SetReadDeadline(t) } func (c *slowOpenConn) SetWriteDeadline(t time.Time) error { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return os.ErrInvalid } - return c.conn.SetWriteDeadline(t) + return conn.SetWriteDeadline(t) } func (c *slowOpenConn) Upstream() any { - return c.conn + return common.PtrOrNil(c.conn.Load()) } func (c *slowOpenConn) ReaderReplaceable() bool { - return c.conn != nil + return c.conn.Load() != nil } func (c *slowOpenConn) WriterReplaceable() bool { - return c.conn != nil + return c.conn.Load() != nil } func (c *slowOpenConn) LazyHeadroom() bool { - return c.conn == nil + return c.conn.Load() == nil } func (c *slowOpenConn) NeedHandshake() bool { - return c.conn == nil + return c.conn.Load() == nil } func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { select { case <-c.create: if c.err != nil { @@ -166,5 +178,5 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) { return 0, c.err } } - return bufio.Copy(w, c.conn) + return bufio.Copy(w, c.conn.Load()) } diff --git a/experimental/v2rayapi/stats.go b/experimental/v2rayapi/stats.go index a85e190f..16d44114 100644 --- a/experimental/v2rayapi/stats.go +++ b/experimental/v2rayapi/stats.go @@ -115,7 +115,7 @@ func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.Packet writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink")) } s.access.Unlock() - return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter) + return bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil) } func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) { diff --git a/go.mod b/go.mod index 67425190..36038a39 100644 --- a/go.mod +++ b/go.mod @@ -27,9 +27,9 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.7.5 + github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f github.com/sagernet/sing-mux v0.3.3 - github.com/sagernet/sing-quic v0.5.0-beta.3 + github.com/sagernet/sing-quic v0.5.0 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index 1f926629..db3cf464 100644 --- a/go.sum +++ b/go.sum @@ -167,12 +167,12 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc= -github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f h1:HIBo8l+tsS3wLwuI1E56uRTQw46QytXSUpZTP3vwG/U= +github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.5.0-beta.3 h1:X/acRNsqQNfDlmwE7SorHfaZiny5e67hqIzM/592ric= -github.com/sagernet/sing-quic v0.5.0-beta.3/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0= +github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak= +github.com/sagernet/sing-quic v0.5.0/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= diff --git a/transport/v2raygrpc/client.go b/transport/v2raygrpc/client.go index e649aa8f..2bbaa627 100644 --- a/transport/v2raygrpc/client.go +++ b/transport/v2raygrpc/client.go @@ -4,6 +4,7 @@ import ( "context" "net" "sync" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" @@ -29,7 +30,7 @@ type Client struct { serverAddr string serviceName string dialOptions []grpc.DialOption - conn *grpc.ClientConn + conn atomic.Pointer[grpc.ClientConn] connAccess sync.Mutex } @@ -74,13 +75,13 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt } func (c *Client) connect() (*grpc.ClientConn, error) { - conn := c.conn + conn := c.conn.Load() if conn != nil && conn.GetState() != connectivity.Shutdown { return conn, nil } c.connAccess.Lock() defer c.connAccess.Unlock() - conn = c.conn + conn = c.conn.Load() if conn != nil && conn.GetState() != connectivity.Shutdown { return conn, nil } @@ -89,7 +90,7 @@ func (c *Client) connect() (*grpc.ClientConn, error) { if err != nil { return nil, err } - c.conn = conn + c.conn.Store(conn) return conn, nil } @@ -109,11 +110,9 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { } func (c *Client) Close() error { - c.connAccess.Lock() - defer c.connAccess.Unlock() - if c.conn != nil { - c.conn.Close() - c.conn = nil + conn := c.conn.Swap(nil) + if conn != nil { + conn.Close() } return nil } diff --git a/transport/v2rayquic/client.go b/transport/v2rayquic/client.go index f9556211..3d1d916e 100644 --- a/transport/v2rayquic/client.go +++ b/transport/v2rayquic/client.go @@ -15,6 +15,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-quic" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -29,7 +30,7 @@ type Client struct { tlsConfig tls.Config quicConfig *quic.Config connAccess sync.Mutex - conn quic.Connection + conn atomic.TypedValue[quic.Connection] rawConn net.Conn } @@ -50,13 +51,13 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt } func (c *Client) offer() (quic.Connection, error) { - conn := c.conn + conn := c.conn.Load() if conn != nil && !common.Done(conn.Context()) { return conn, nil } c.connAccess.Lock() defer c.connAccess.Unlock() - conn = c.conn + conn = c.conn.Load() if conn != nil && !common.Done(conn.Context()) { return conn, nil } @@ -78,7 +79,7 @@ func (c *Client) offerNew() (quic.Connection, error) { packetConn.Close() return nil, err } - c.conn = quicConn + c.conn.Store(quicConn) c.rawConn = udpConn return quicConn, nil } @@ -98,13 +99,13 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { func (c *Client) Close() error { c.connAccess.Lock() defer c.connAccess.Unlock() - if c.conn != nil { - c.conn.CloseWithError(0, "") + conn := c.conn.Swap(nil) + if conn != nil { + conn.CloseWithError(0, "") } if c.rawConn != nil { c.rawConn.Close() } - c.conn = nil c.rawConn = nil return nil } diff --git a/transport/v2raywebsocket/conn.go b/transport/v2raywebsocket/conn.go index 7f347dc9..009cadd8 100644 --- a/transport/v2raywebsocket/conn.go +++ b/transport/v2raywebsocket/conn.go @@ -8,6 +8,7 @@ import ( "net" "os" "sync" + "sync/atomic" "time" C "github.com/sagernet/sing-box/constant" @@ -135,20 +136,22 @@ func (c *WebsocketConn) Upstream() any { type EarlyWebsocketConn struct { *Client ctx context.Context - conn *WebsocketConn + conn atomic.Pointer[WebsocketConn] access sync.Mutex create chan struct{} err error } func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { <-c.create if c.err != nil { return 0, c.err } + conn = c.conn.Load() } - return wrapWsError0(c.conn.Read(b)) + return wrapWsError0(conn.Read(b)) } func (c *EarlyWebsocketConn) writeRequest(content []byte) error { @@ -187,21 +190,23 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error { return err } } - c.conn = conn + c.conn.Store(conn) return nil } func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) { - if c.conn != nil { - return wrapWsError0(c.conn.Write(b)) + conn := c.conn.Load() + if conn != nil { + return wrapWsError0(conn.Write(b)) } c.access.Lock() defer c.access.Unlock() + conn = c.conn.Load() if c.err != nil { return 0, c.err } - if c.conn != nil { - return wrapWsError0(c.conn.Write(b)) + if conn != nil { + return wrapWsError0(conn.Write(b)) } err = c.writeRequest(b) c.err = err @@ -213,17 +218,19 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) { } func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error { - if c.conn != nil { - return wrapWsError(c.conn.WriteBuffer(buffer)) + conn := c.conn.Load() + if conn != nil { + return wrapWsError(conn.WriteBuffer(buffer)) } c.access.Lock() defer c.access.Unlock() - if c.conn != nil { - return wrapWsError(c.conn.WriteBuffer(buffer)) - } if c.err != nil { return c.err } + conn = c.conn.Load() + if conn != nil { + return wrapWsError(conn.WriteBuffer(buffer)) + } err := c.writeRequest(buffer.Bytes()) c.err = err close(c.create) @@ -231,24 +238,27 @@ func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error { } func (c *EarlyWebsocketConn) Close() error { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return nil } - return c.conn.Close() + return conn.Close() } func (c *EarlyWebsocketConn) LocalAddr() net.Addr { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return M.Socksaddr{} } - return c.conn.LocalAddr() + return conn.LocalAddr() } func (c *EarlyWebsocketConn) RemoteAddr() net.Addr { - if c.conn == nil { + conn := c.conn.Load() + if conn == nil { return M.Socksaddr{} } - return c.conn.RemoteAddr() + return conn.RemoteAddr() } func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error { @@ -268,11 +278,11 @@ func (c *EarlyWebsocketConn) NeedAdditionalReadDeadline() bool { } func (c *EarlyWebsocketConn) Upstream() any { - return common.PtrOrNil(c.conn) + return common.PtrOrNil(c.conn.Load()) } func (c *EarlyWebsocketConn) LazyHeadroom() bool { - return c.conn == nil + return c.conn.Load() == nil } func wrapWsError(err error) error { From de10bb00a9cd55e8ba41113ea1ea00364445deb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 14 Apr 2025 23:40:54 +0800 Subject: [PATCH 14/63] Fix ssm-api --- service/ssmapi/api.go | 10 ++-- service/ssmapi/traffic.go | 103 ++++++++++++++++++-------------------- 2 files changed, 53 insertions(+), 60 deletions(-) diff --git a/service/ssmapi/api.go b/service/ssmapi/api.go index b9b753a4..6a067c50 100644 --- a/service/ssmapi/api.go +++ b/service/ssmapi/api.go @@ -156,18 +156,14 @@ func (s *APIServer) deleteUser(writer http.ResponseWriter, request *http.Request } func (s *APIServer) getStats(writer http.ResponseWriter, request *http.Request) { - requireClear := chi.URLParam(request, "clear") == "true" + requireClear := request.URL.Query().Get("clear") == "true" users := s.user.List() - s.traffic.ReadUsers(users) + s.traffic.ReadUsers(users, requireClear) for i := range users { users[i].Password = "" } - uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal() - - if requireClear { - s.traffic.Clear() - } + uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal(requireClear) render.JSON(writer, request, render.M{ "uplinkBytes": uplinkBytes, diff --git a/service/ssmapi/traffic.go b/service/ssmapi/traffic.go index df1b04b1..f39d56d8 100644 --- a/service/ssmapi/traffic.go +++ b/service/ssmapi/traffic.go @@ -142,85 +142,82 @@ func (s *TrafficManager) TrackPacketConnection(conn N.PacketConn, metadata adapt readPacketCounter = append(readPacketCounter, upPacketsCounter) writePacketCounter = append(writePacketCounter, downPacketsCounter) udpSessionCounter.Add(1) - return bufio.NewInt64CounterPacketConn(conn, append(readCounter, readPacketCounter...), append(writeCounter, writePacketCounter...)) + return bufio.NewInt64CounterPacketConn(conn, readCounter, readPacketCounter, writeCounter, writePacketCounter) } func (s *TrafficManager) ReadUser(user *UserObject) { s.userAccess.Lock() defer s.userAccess.Unlock() - s.readUser(user) + s.readUser(user, false) } -func (s *TrafficManager) readUser(user *UserObject) { +func (s *TrafficManager) readUser(user *UserObject, swap bool) { if counter, loaded := s.userUplink[user.UserName]; loaded { - user.UplinkBytes = counter.Load() + if swap { + user.UplinkBytes = counter.Swap(0) + } else { + user.UplinkBytes = counter.Load() + } } if counter, loaded := s.userDownlink[user.UserName]; loaded { - user.DownlinkBytes = counter.Load() + if swap { + user.DownlinkBytes = counter.Swap(0) + } else { + user.DownlinkBytes = counter.Load() + } } if counter, loaded := s.userUplinkPackets[user.UserName]; loaded { - user.UplinkPackets = counter.Load() + if swap { + user.UplinkPackets = counter.Swap(0) + } else { + user.UplinkPackets = counter.Load() + } } if counter, loaded := s.userDownlinkPackets[user.UserName]; loaded { - user.DownlinkPackets = counter.Load() + if swap { + user.DownlinkPackets = counter.Swap(0) + } else { + user.DownlinkPackets = counter.Load() + } } if counter, loaded := s.userTCPSessions[user.UserName]; loaded { - user.TCPSessions = counter.Load() + if swap { + user.TCPSessions = counter.Swap(0) + } else { + user.TCPSessions = counter.Load() + } } if counter, loaded := s.userUDPSessions[user.UserName]; loaded { - user.UDPSessions = counter.Load() + if swap { + user.UDPSessions = counter.Swap(0) + } else { + user.UDPSessions = counter.Load() + } } } -func (s *TrafficManager) ReadUsers(users []*UserObject) { +func (s *TrafficManager) ReadUsers(users []*UserObject, swap bool) { s.userAccess.Lock() defer s.userAccess.Unlock() for _, user := range users { - s.readUser(user) + s.readUser(user, swap) } } -func (s *TrafficManager) ReadGlobal() ( - uplinkBytes int64, - downlinkBytes int64, - uplinkPackets int64, - downlinkPackets int64, - tcpSessions int64, - udpSessions int64, -) { - return s.globalUplink.Load(), - s.globalDownlink.Load(), - s.globalUplinkPackets.Load(), - s.globalDownlinkPackets.Load(), - s.globalTCPSessions.Load(), - s.globalUDPSessions.Load() -} - -func (s *TrafficManager) Clear() { - s.globalUplink.Store(0) - s.globalDownlink.Store(0) - s.globalUplinkPackets.Store(0) - s.globalDownlinkPackets.Store(0) - s.globalTCPSessions.Store(0) - s.globalUDPSessions.Store(0) - s.userAccess.Lock() - defer s.userAccess.Unlock() - for _, counter := range s.userUplink { - counter.Store(0) - } - for _, counter := range s.userDownlink { - counter.Store(0) - } - for _, counter := range s.userUplinkPackets { - counter.Store(0) - } - for _, counter := range s.userDownlinkPackets { - counter.Store(0) - } - for _, counter := range s.userTCPSessions { - counter.Store(0) - } - for _, counter := range s.userUDPSessions { - counter.Store(0) +func (s *TrafficManager) ReadGlobal(swap bool) (uplinkBytes int64, downlinkBytes int64, uplinkPackets int64, downlinkPackets int64, tcpSessions int64, udpSessions int64) { + if swap { + return s.globalUplink.Swap(0), + s.globalDownlink.Swap(0), + s.globalUplinkPackets.Swap(0), + s.globalDownlinkPackets.Swap(0), + s.globalTCPSessions.Swap(0), + s.globalUDPSessions.Swap(0) + } else { + return s.globalUplink.Load(), + s.globalDownlink.Load(), + s.globalUplinkPackets.Load(), + s.globalDownlinkPackets.Load(), + s.globalTCPSessions.Load(), + s.globalUDPSessions.Load() } } From 354ece2bdf0ad7df82386c81369463e185a18a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 Aug 2025 23:29:55 +0800 Subject: [PATCH 15/63] Fix resolved service --- adapter/outbound/manager.go | 27 ++++++++++++++++++--------- box.go | 8 ++++---- service/resolved/resolve1.go | 25 +++++++++++++++++-------- service/resolved/service.go | 12 +++++++----- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index 44ac8bc5..b58f5277 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -30,7 +30,7 @@ type Manager struct { outboundByTag map[string]adapter.Outbound dependByTag map[string][]string defaultOutbound adapter.Outbound - defaultOutboundFallback adapter.Outbound + defaultOutboundFallback func() (adapter.Outbound, error) } func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager { @@ -44,7 +44,7 @@ func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, } } -func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) { +func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) { m.defaultOutboundFallback = defaultOutboundFallback } @@ -55,18 +55,31 @@ func (m *Manager) Start(stage adapter.StartStage) error { } m.started = true m.stage = stage - outbounds := m.outbounds - m.access.Unlock() if stage == adapter.StartStateStart { + if m.defaultOutbound == nil { + directOutbound, err := m.defaultOutboundFallback() + if err != nil { + m.access.Unlock() + return E.Cause(err, "create direct outbound for fallback") + } + m.outbounds = append(m.outbounds, directOutbound) + m.outboundByTag[directOutbound.Tag()] = directOutbound + m.defaultOutbound = directOutbound + } if m.defaultTag != "" && m.defaultOutbound == nil { defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag) if !loaded { + m.access.Unlock() return E.New("default outbound not found: ", m.defaultTag) } m.defaultOutbound = defaultEndpoint } + outbounds := m.outbounds + m.access.Unlock() return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...)) } else { + outbounds := m.outbounds + m.access.Unlock() for _, outbound := range outbounds { err := adapter.LegacyStart(outbound, stage) if err != nil { @@ -187,11 +200,7 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) { func (m *Manager) Default() adapter.Outbound { m.access.RLock() defer m.access.RUnlock() - if m.defaultOutbound != nil { - return m.defaultOutbound - } else { - return m.defaultOutboundFallback - } + return m.defaultOutbound } func (m *Manager) Remove(tag string) error { diff --git a/box.go b/box.go index bfb0b47e..8a38f6ae 100644 --- a/box.go +++ b/box.go @@ -314,15 +314,15 @@ func New(options Options) (*Box, error) { return nil, E.Cause(err, "initialize service[", i, "]") } } - outboundManager.Initialize(common.Must1( - direct.NewOutbound( + outboundManager.Initialize(func() (adapter.Outbound, error) { + return direct.NewOutbound( ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{}, - ), - )) + ) + }) dnsTransportManager.Initialize(common.Must1( local.NewTransport( ctx, diff --git a/service/resolved/resolve1.go b/service/resolved/resolve1.go index d64619e6..8e6dd3fa 100644 --- a/service/resolved/resolve1.go +++ b/service/resolved/resolve1.go @@ -182,9 +182,9 @@ func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context } else if metadata.ProcessInfo.UserId != 0 { prefix = F.ToString("uid:", metadata.ProcessInfo.UserId) } - t.logger.InfoContext(ctx, "(", prefix, ") ", F.ToString(message...)) + t.logger.InfoContext(ctx, "(", prefix, ") ", strings.Join(F.MapToString(message), " ")) } else { - t.logger.InfoContext(ctx, F.ToString(message...)) + t.logger.InfoContext(ctx, strings.Join(F.MapToString(message), " ")) } return adapter.WithContext(ctx, &metadata) } @@ -280,7 +280,10 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami }, } ctx := t.logRequest(sender, "ResolveAddress ", link.iif.Name, familyToString(family), addr, flags) - response, lookupErr := t.dnsRouter.Exchange(ctx, request, adapter.DNSQueryOptions{}) + var metadata adapter.InboundContext + metadata.InboundType = t.Type() + metadata.Inbound = t.Tag() + response, lookupErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{}) if lookupErr != nil { err = wrapError(err) return @@ -301,7 +304,7 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami return } -func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, family int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) { +func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) { t.linkAccess.Lock() link, err := t.getLink(ifIndex) if err != nil { @@ -320,8 +323,11 @@ func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, famil }, }, } - ctx := t.logRequest(sender, "ResolveRecord ", link.iif.Name, familyToString(family), hostname, mDNS.Class(qClass), mDNS.Type(qType), flags) - response, exchangeErr := t.dnsRouter.Exchange(ctx, request, adapter.DNSQueryOptions{}) + ctx := t.logRequest(sender, "ResolveRecord", link.iif.Name, hostname, mDNS.Class(qClass), mDNS.Type(qType), flags) + var metadata adapter.InboundContext + metadata.InboundType = t.Type() + metadata.Inbound = t.Tag() + response, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{}) if exchangeErr != nil { err = wrapError(exchangeErr) return @@ -341,6 +347,7 @@ func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, famil err = wrapError(unpackErr) } record.Data = data + records = append(records, record) } return } @@ -380,8 +387,10 @@ func (t *resolve1Manager) ResolveService(sender dbus.Sender, ifIndex int32, host }, }, } - - srvResponse, exchangeErr := t.dnsRouter.Exchange(ctx, srvRequest, adapter.DNSQueryOptions{}) + var metadata adapter.InboundContext + metadata.InboundType = t.Type() + metadata.Inbound = t.Tag() + srvResponse, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), srvRequest, adapter.DNSQueryOptions{}) if exchangeErr != nil { err = wrapError(exchangeErr) return diff --git a/service/resolved/service.go b/service/resolved/service.go index 133cb1ab..eaedc09d 100644 --- a/service/resolved/service.go +++ b/service/resolved/service.go @@ -91,11 +91,6 @@ func (i *Service) Start(stage adapter.StartStage) error { return E.New("multiple resolved service are not supported") } } - case adapter.StartStateStart: - err := i.listener.Start() - if err != nil { - return err - } systemBus, err := dbus.SystemBus() if err != nil { return err @@ -117,6 +112,11 @@ func (i *Service) Start(stage adapter.StartStage) error { return E.New("unknown request name reply: ", reply) } i.networkUpdateCallback = i.network.NetworkMonitor().RegisterCallback(i.onNetworkUpdate) + case adapter.StartStateStart: + err := i.listener.Start() + if err != nil { + return err + } } return nil } @@ -167,6 +167,8 @@ func (i *Service) exchangePacket0(ctx context.Context, buffer *buf.Buffer, oob [ } var metadata adapter.InboundContext metadata.Source = source + metadata.InboundType = i.Type() + metadata.Inbound = i.Tag() response, err := i.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{}) if err != nil { return err From acda4ce985f27f87bd127c6a6ff2a66360aed649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 17 Aug 2025 14:48:01 +0800 Subject: [PATCH 16/63] Fix `bind_interface` not working with `auto_redirect` --- adapter/network.go | 1 + common/dialer/default.go | 13 +++++++++---- route/network.go | 21 ++++++++++----------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/adapter/network.go b/adapter/network.go index 50670831..1b26bed6 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -20,6 +20,7 @@ type NetworkManager interface { DefaultOptions() NetworkOptions RegisterAutoRedirectOutputMark(mark uint32) error AutoRedirectOutputMark() uint32 + AutoRedirectOutputMarkFunc() control.Func NetworkMonitor() tun.NetworkUpdateMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor PackageManager() tun.PackageManager diff --git a/common/dialer/default.go b/common/dialer/default.go index 5337934a..4afa0091 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -121,11 +121,16 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial listener.Control = control.Append(listener.Control, bindFunc) } } + if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + } } - if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { - dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) - listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) - } + } + if networkManager != nil { + markFunc := networkManager.AutoRedirectOutputMarkFunc() + dialer.Control = control.Append(dialer.Control, markFunc) + listener.Control = control.Append(listener.Control, markFunc) } if options.ReuseAddr { listener.Control = control.Append(listener.Control, control.ReuseAddr()) diff --git a/route/network.go b/route/network.go index 090e4c0d..6a45abc6 100644 --- a/route/network.go +++ b/route/network.go @@ -312,7 +312,7 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { if r.interfaceMonitor == nil { return nil } - bindFunc := control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { + return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { remoteAddr := M.ParseSocksaddr(address).Addr if remoteAddr.IsValid() { iif, err := r.interfaceFinder.ByAddr(remoteAddr) @@ -326,16 +326,6 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } return defaultInterface.Name, defaultInterface.Index, nil }) - return func(network, address string, conn syscall.RawConn) error { - err := bindFunc(network, address, conn) - if err != nil { - return err - } - if r.autoRedirectOutputMark > 0 { - return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn) - } - return nil - } } } @@ -366,6 +356,15 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 { return r.autoRedirectOutputMark } +func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func { + return func(network, address string, conn syscall.RawConn) error { + if r.autoRedirectOutputMark == 0 { + return nil + } + return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn) + } +} + func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor { return r.networkMonitor } From cef3e538baa3ffdb172c2c9466495a564961a032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 19 Aug 2025 11:06:22 +0800 Subject: [PATCH 17/63] Fix failed DNS responses being incorrectly rejected --- dns/router.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dns/router.go b/dns/router.go index 36b7bf05..e62f0ef5 100644 --- a/dns/router.go +++ b/dns/router.go @@ -293,12 +293,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte } else if errors.Is(err, ErrResponseRejected) { rejected = true r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String()))) - /*} else if responseCheck!= nil && errors.Is(err, RcodeError(mDNS.RcodeNameError)) { - rejected = true - r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String()))) - */ } else if len(message.Question) > 0 { - rejected = true r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String()))) } else { r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ")) From f462ce5615acf9fc9c7f7ba02d5835a9159bb405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 19 Aug 2025 21:55:35 +0800 Subject: [PATCH 18/63] Update tfo-go --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 36038a39..21e739f1 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/libdns/alidns v1.0.5-libdns.v1.beta1 github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 + github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 github.com/metacubex/utls v1.8.0 github.com/mholt/acmez/v3 v3.1.2 github.com/miekg/dns v1.1.67 diff --git a/go.sum b/go.sum index db3cf464..285909c4 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= -github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= +github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc= +github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac= github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ= github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= From 52fa5f20a3ba526bfbf721f7d2115088b939517b Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Tue, 19 Aug 2025 23:05:37 +0800 Subject: [PATCH 19/63] Make realityConnWrapper replaceable --- common/tls/reality_server.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index 27420248..3a3ae99e 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -206,3 +206,11 @@ func (c *realityConnWrapper) Upstream() any { func (c *realityConnWrapper) CloseWrite() error { return c.Close() } + +func (c *realityConnWrapper) ReaderReplaceable() bool { + return true +} + +func (c *realityConnWrapper) WriterReplaceable() bool { + return true +} From 83f02d0bfb16c932b7984dee870e1dcca0a582ff Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Tue, 19 Aug 2025 23:25:03 +0800 Subject: [PATCH 20/63] Make utlsConnWrapper replaceable --- common/tls/utls_client.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index f897e65c..fceb15b8 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -106,6 +106,14 @@ func (c *utlsConnWrapper) Upstream() any { return c.UConn } +func (c *utlsConnWrapper) ReaderReplaceable() bool { + return true +} + +func (c *utlsConnWrapper) WriterReplaceable() bool { + return true +} + type utlsALPNWrapper struct { utlsConnWrapper nextProtocols []string From f4ed684146a4a1b9ad864dbb5888d4407972c3b2 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Tue, 19 Aug 2025 23:11:38 +0800 Subject: [PATCH 21/63] Update cast using in sing-vmess --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 21e739f1..452694c6 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 github.com/sagernet/sing-tun v0.7.0-beta.1 - github.com/sagernet/sing-vmess v0.2.6 + github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-mod.5 github.com/sagernet/wireguard-go v0.0.1-beta.7 diff --git a/go.sum b/go.sum index 285909c4..d0f21c8c 100644 --- a/go.sum +++ b/go.sum @@ -181,8 +181,8 @@ github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY= github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ= -github.com/sagernet/sing-vmess v0.2.6 h1:1c4dGzeGy0kpBXXrT1sgiMZtHhdJylIT8eWrGhJYZec= -github.com/sagernet/sing-vmess v0.2.6/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= +github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= +github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A= From f1dd0dba7881780e11fa7d069bd9226146b34da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 20 Aug 2025 08:44:29 +0800 Subject: [PATCH 22/63] Make ReadWaitConn reader replaceable --- common/badtls/read_wait.go | 4 ++++ common/badtls/read_wait_utls.go | 24 ++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/common/badtls/read_wait.go b/common/badtls/read_wait.go index 334bcfa8..9508a7e3 100644 --- a/common/badtls/read_wait.go +++ b/common/badtls/read_wait.go @@ -128,6 +128,10 @@ func (c *ReadWaitConn) Upstream() any { return c.Conn } +func (c *ReadWaitConn) ReaderReplaceable() bool { + return true +} + var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) func init() { diff --git a/common/badtls/read_wait_utls.go b/common/badtls/read_wait_utls.go index bba016e4..1facd30b 100644 --- a/common/badtls/read_wait_utls.go +++ b/common/badtls/read_wait_utls.go @@ -6,22 +6,26 @@ import ( "net" _ "unsafe" - "github.com/sagernet/sing/common" - "github.com/metacubex/utls" ) func init() { tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { - tlsConn, loaded := common.Cast[*tls.UConn](conn) - if !loaded { - return + switch tlsConn := conn.(type) { + case *tls.UConn: + return true, func() error { + return utlsReadRecord(tlsConn.Conn) + }, func() error { + return utlsHandlePostHandshakeMessage(tlsConn.Conn) + } + case *tls.Conn: + return true, func() error { + return utlsReadRecord(tlsConn) + }, func() error { + return utlsHandlePostHandshakeMessage(tlsConn) + } } - return true, func() error { - return utlsReadRecord(tlsConn.Conn) - }, func() error { - return utlsHandlePostHandshakeMessage(tlsConn.Conn) - } + return }) } From ee02532ab59ec4581e23b8a5e9e4330335c7d1ca Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Wed, 20 Aug 2025 02:54:47 +0800 Subject: [PATCH 23/63] Fix tlsfragment fallback writeAndWaitAck --- common/tlsfragment/conn.go | 3 +++ common/tlsfragment/wait_stub.go | 4 ++++ common/tlsfragment/wait_windows.go | 3 +++ 3 files changed, 10 insertions(+) diff --git a/common/tlsfragment/conn.go b/common/tlsfragment/conn.go index cb29000e..040663bd 100644 --- a/common/tlsfragment/conn.go +++ b/common/tlsfragment/conn.go @@ -109,6 +109,9 @@ func (c *Conn) Write(b []byte) (n int, err error) { if err != nil { return } + if i != len(splitIndexes) { + time.Sleep(c.fallbackDelay) + } } } } diff --git a/common/tlsfragment/wait_stub.go b/common/tlsfragment/wait_stub.go index 7e451a04..6a1cd889 100644 --- a/common/tlsfragment/wait_stub.go +++ b/common/tlsfragment/wait_stub.go @@ -9,6 +9,10 @@ import ( ) func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error { + _, err := conn.Write(payload) + if err != nil { + return err + } time.Sleep(fallbackDelay) return nil } diff --git a/common/tlsfragment/wait_windows.go b/common/tlsfragment/wait_windows.go index 118a204d..49706ca5 100644 --- a/common/tlsfragment/wait_windows.go +++ b/common/tlsfragment/wait_windows.go @@ -16,6 +16,9 @@ func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fal err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload) if err != nil { if errors.Is(err, windows.ERROR_ACCESS_DENIED) { + if _, err := conn.Write(payload); err != nil { + return err + } time.Sleep(fallbackDelay) return nil } From 97f0dc8a6060f8993388cb36d357668731475f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 17 Aug 2025 14:49:48 +0800 Subject: [PATCH 24/63] Bump version --- clients/android | 2 +- docs/changelog.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 6db8e06e..597c1848 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 6db8e06e8d6c77648e79e3f93bdd41a4d48cc319 +Subproject commit 597c18482f6edc44e0c94b7cc849dd03b2121c45 diff --git a/docs/changelog.md b/docs/changelog.md index e54a9d17..8a90f446 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +#### 1.12.2 + +* Fixes and improvements + #### 1.12.1 * Fixes and improvements From 1468d83895502ac468b5dca99f188a007ad1a5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 20 Aug 2025 16:26:12 +0800 Subject: [PATCH 25/63] Make realityClientConnWrapper replaceable --- common/tls/reality_client.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/tls/reality_client.go b/common/tls/reality_client.go index ca0e1e04..d056966c 100644 --- a/common/tls/reality_client.go +++ b/common/tls/reality_client.go @@ -307,3 +307,11 @@ func (c *realityClientConnWrapper) Upstream() any { func (c *realityClientConnWrapper) CloseWrite() error { return c.Close() } + +func (c *realityClientConnWrapper) ReaderReplaceable() bool { + return true +} + +func (c *realityClientConnWrapper) WriterReplaceable() bool { + return true +} From 22782ca6fc26c647a4e7d09dc71a4e3bb328ec3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 21 Aug 2025 09:41:31 +0800 Subject: [PATCH 26/63] Fix outbound start --- adapter/outbound/manager.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index b58f5277..0fdeb390 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -56,6 +56,14 @@ func (m *Manager) Start(stage adapter.StartStage) error { m.started = true m.stage = stage if stage == adapter.StartStateStart { + if m.defaultTag != "" && m.defaultOutbound == nil { + defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag) + if !loaded { + m.access.Unlock() + return E.New("default outbound not found: ", m.defaultTag) + } + m.defaultOutbound = defaultEndpoint + } if m.defaultOutbound == nil { directOutbound, err := m.defaultOutboundFallback() if err != nil { @@ -66,14 +74,6 @@ func (m *Manager) Start(stage adapter.StartStage) error { m.outboundByTag[directOutbound.Tag()] = directOutbound m.defaultOutbound = directOutbound } - if m.defaultTag != "" && m.defaultOutbound == nil { - defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag) - if !loaded { - m.access.Unlock() - return E.New("default outbound not found: ", m.defaultTag) - } - m.defaultOutbound = defaultEndpoint - } outbounds := m.outbounds m.access.Unlock() return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...)) From b40f642fa4ae25633426f136aa2e5d25f985c991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 21 Aug 2025 09:42:12 +0800 Subject: [PATCH 27/63] Bump version --- clients/android | 2 +- docs/changelog.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 597c1848..24b26130 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 597c18482f6edc44e0c94b7cc849dd03b2121c45 +Subproject commit 24b26130071bd96c660deeecc4931a3e31339681 diff --git a/docs/changelog.md b/docs/changelog.md index 8a90f446..c9a686af 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +#### 1.12.3 + +* Fixes and improvements + #### 1.12.2 * Fixes and improvements From 031f25c1c108e68820fbf292a0d10d637ef7d202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 25 Aug 2025 19:49:12 +0800 Subject: [PATCH 28/63] Deprecate common/atomic --- common/dialer/default.go | 3 +-- common/dialer/tfo.go | 2 +- experimental/clashapi/trafficontrol/manager.go | 2 +- experimental/clashapi/trafficontrol/tracker.go | 2 +- experimental/v2rayapi/stats.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- protocol/group/selector.go | 4 ++-- protocol/group/urltest.go | 4 ++-- route/network.go | 3 +-- route/rule/rule_set_local.go | 2 +- route/rule/rule_set_remote.go | 2 +- service/ssmapi/cache.go | 2 +- service/ssmapi/traffic.go | 2 +- transport/v2rayquic/client.go | 3 +-- 15 files changed, 18 insertions(+), 21 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index 4afa0091..dd1e90e5 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -43,7 +42,7 @@ type DefaultDialer struct { networkType []C.InterfaceType fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration - networkLastFallback atomic.TypedValue[time.Time] + networkLastFallback common.TypedValue[time.Time] } func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index cd1a2a22..4832c12d 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -8,10 +8,10 @@ import ( "net" "os" "sync" + "sync/atomic" "time" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index 757ffdf9..7b69b93d 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -3,12 +3,12 @@ package trafficontrol import ( "runtime" "sync" + "sync/atomic" "time" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/clashapi/compatible" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/x/list" diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index e324be20..48d54b25 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -2,11 +2,11 @@ package trafficontrol import ( "net" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" diff --git a/experimental/v2rayapi/stats.go b/experimental/v2rayapi/stats.go index 16d44114..c7b2b49f 100644 --- a/experimental/v2rayapi/stats.go +++ b/experimental/v2rayapi/stats.go @@ -7,11 +7,11 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" diff --git a/go.mod b/go.mod index 452694c6..dccba18f 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f + github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28 github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-quic v0.5.0 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index d0f21c8c..86e4b287 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f h1:HIBo8l+tsS3wLwuI1E56uRTQw46QytXSUpZTP3vwG/U= -github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28 h1:C8Lnqd0Q+C15kwaMiDsfq5S45rhhaQMBG91TT+6oFVo= +github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak= diff --git a/protocol/group/selector.go b/protocol/group/selector.go index 9806e033..439283bd 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -10,7 +10,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/atomic" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" @@ -37,7 +37,7 @@ type Selector struct { tags []string defaultTag string outbounds map[string]adapter.Outbound - selected atomic.TypedValue[adapter.Outbound] + selected common.TypedValue[adapter.Outbound] interruptGroup *interrupt.Group interruptExternalConnections bool } diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index 719f4a2c..4bdef9fb 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -4,6 +4,7 @@ import ( "context" "net" "sync" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" @@ -14,7 +15,6 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/batch" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -192,7 +192,7 @@ type URLTestGroup struct { ticker *time.Ticker close chan struct{} started bool - lastActive atomic.TypedValue[time.Time] + lastActive common.TypedValue[time.Time] } func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) { diff --git a/route/network.go b/route/network.go index 6a45abc6..5009ec0b 100644 --- a/route/network.go +++ b/route/network.go @@ -19,7 +19,6 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -37,7 +36,7 @@ var _ adapter.NetworkManager = (*NetworkManager)(nil) type NetworkManager struct { logger logger.ContextLogger interfaceFinder *control.DefaultInterfaceFinder - networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface] + networkInterfaces common.TypedValue[[]adapter.NetworkInterface] autoDetectInterface bool defaultOptions adapter.NetworkOptions diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index 4f2fabcc..0ef15fc7 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" "sync" + "sync/atomic" "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" @@ -13,7 +14,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 16a95bb6..133e575a 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -10,6 +10,7 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" @@ -17,7 +18,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" diff --git a/service/ssmapi/cache.go b/service/ssmapi/cache.go index 4c82c9d0..03cc8c3f 100644 --- a/service/ssmapi/cache.go +++ b/service/ssmapi/cache.go @@ -5,8 +5,8 @@ import ( "os" "path/filepath" "sort" + "sync/atomic" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/service/filemanager" diff --git a/service/ssmapi/traffic.go b/service/ssmapi/traffic.go index f39d56d8..4a669adb 100644 --- a/service/ssmapi/traffic.go +++ b/service/ssmapi/traffic.go @@ -3,9 +3,9 @@ package ssmapi import ( "net" "sync" + "sync/atomic" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" N "github.com/sagernet/sing/common/network" ) diff --git a/transport/v2rayquic/client.go b/transport/v2rayquic/client.go index 3d1d916e..803d58c5 100644 --- a/transport/v2rayquic/client.go +++ b/transport/v2rayquic/client.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-quic" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -30,7 +29,7 @@ type Client struct { tlsConfig tls.Config quicConfig *quic.Config connAccess sync.Mutex - conn atomic.TypedValue[quic.Connection] + conn common.TypedValue[quic.Connection] rawConn net.Conn } From 963bc4b6475a62269c5d65a36f139fbab2268852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 28 Aug 2025 10:30:13 +0800 Subject: [PATCH 29/63] Enforce Tailscale NoLogsNoSupport --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dccba18f..ce6ebdc8 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/sagernet/sing-tun v0.7.0-beta.1 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 - github.com/sagernet/tailscale v1.80.3-mod.5 + github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.9.1 diff --git a/go.sum b/go.sum index 86e4b287..c359cc2e 100644 --- a/go.sum +++ b/go.sum @@ -185,8 +185,8 @@ github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiY github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= -github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A= -github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= +github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 h1:gMC0q+0VvZBotZMZ9G0R8ZMEIT/Q6KnXbw0/OgMjmdk= +github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= From 980e96250bf693bb8400183d6a05665da8ad9d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 28 Aug 2025 11:58:14 +0800 Subject: [PATCH 30/63] Bump version --- clients/android | 2 +- docs/changelog.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 24b26130..9d71ede0 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 24b26130071bd96c660deeecc4931a3e31339681 +Subproject commit 9d71ede01a1a51bb459847bb5d4444b2846c77de diff --git a/docs/changelog.md b/docs/changelog.md index c9a686af..c1a24824 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +#### 1.12.4 + +* Fixes and improvements + #### 1.12.3 * Fixes and improvements From 649163cb7bed04bbcb118388da069fc3e5479fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 2 Sep 2025 17:19:08 +0800 Subject: [PATCH 31/63] Fix domain strategy not taking effect --- protocol/direct/outbound.go | 43 ++----------------------------------- route/route.go | 5 ----- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 84838bc0..58cdb002 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -13,7 +13,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -164,26 +163,7 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - var domainStrategy C.DomainStrategy - if h.domainStrategy != C.DomainStrategyAsIS { - domainStrategy = h.domainStrategy - } else { - //nolint:staticcheck - domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy) - } - switch domainStrategy { - case C.DomainStrategyIPv4Only: - destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4) - if len(destinationAddresses) == 0 { - return nil, E.New("no IPv4 address available for ", destination) - } - case C.DomainStrategyIPv6Only: - destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6) - if len(destinationAddresses) == 0 { - return nil, E.New("no IPv6 address available for ", destination) - } - } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay) } func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { @@ -204,26 +184,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - var domainStrategy C.DomainStrategy - if h.domainStrategy != C.DomainStrategyAsIS { - domainStrategy = h.domainStrategy - } else { - //nolint:staticcheck - domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy) - } - switch domainStrategy { - case C.DomainStrategyIPv4Only: - destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4) - if len(destinationAddresses) == 0 { - return nil, E.New("no IPv4 address available for ", destination) - } - case C.DomainStrategyIPv6Only: - destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6) - if len(destinationAddresses) == 0 { - return nil, E.New("no IPv6 address available for ", destination) - } - } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), networkStrategy, networkType, fallbackNetworkType, fallbackDelay) } func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { diff --git a/route/route.go b/route/route.go index 20fbf4ec..250b8fee 100644 --- a/route/route.go +++ b/route/route.go @@ -675,11 +675,6 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon } metadata.DestinationAddresses = addresses r.logger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") - if metadata.Destination.IsIPv4() { - metadata.IPVersion = 4 - } else if metadata.Destination.IsIPv6() { - metadata.IPVersion = 6 - } } return nil } From 30c069f5b714a9a79049dbc488e6634ee726e810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 2 Sep 2025 17:34:38 +0800 Subject: [PATCH 32/63] Fix local DNS server on legacy windows --- dns/transport_dialer.go | 1 + go.mod | 2 +- go.sum | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dns/transport_dialer.go b/dns/transport_dialer.go index b3ee8082..2ed70ead 100644 --- a/dns/transport_dialer.go +++ b/dns/transport_dialer.go @@ -19,6 +19,7 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) ( if options.LegacyDefaultDialer { return dialer.NewDefaultOutbound(ctx), nil } else { + options.UDPFragmentDefault = true return dialer.NewWithOptions(dialer.Options{ Context: ctx, Options: options.DialerOptions, diff --git a/go.mod b/go.mod index ce6ebdc8..6f18a013 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28 + github.com/sagernet/sing v0.7.6 github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-quic v0.5.0 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index c359cc2e..750d2310 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,10 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28 h1:C8Lnqd0Q+C15kwaMiDsfq5S45rhhaQMBG91TT+6oFVo= -github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.6-0.20250902093152-bd5790e3b4db h1:D+56hVUg42b+nbXVYMwDNyFaN6vTviSXqF96dvBqRiM= +github.com/sagernet/sing v0.7.6-0.20250902093152-bd5790e3b4db/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.6 h1:6LBfDH+aI/26J3r9UHlaxTNjJeMhBpU/wrk0JKDZYI4= +github.com/sagernet/sing v0.7.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak= From 2edfed7d918bce9fd94777f9c50e48e0ded52a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 2 Sep 2025 17:37:44 +0800 Subject: [PATCH 33/63] Improve DHCP DNS server --- dns/transport/dhcp/dhcp.go | 132 ++++++++++--------- dns/transport/dhcp/dhcp_shared.go | 202 ++++++++++++++++++++++++++++++ 2 files changed, 278 insertions(+), 56 deletions(-) create mode 100644 dns/transport/dhcp/dhcp_shared.go diff --git a/dns/transport/dhcp/dhcp.go b/dns/transport/dhcp/dhcp.go index 92dd1f8b..b56a60e7 100644 --- a/dns/transport/dhcp/dhcp.go +++ b/dns/transport/dhcp/dhcp.go @@ -9,10 +9,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/dns/transport" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" @@ -29,6 +27,7 @@ import ( "github.com/insomniacslk/dhcp/dhcpv4" mDNS "github.com/miekg/dns" + "golang.org/x/exp/slices" ) func RegisterTransport(registry *dns.TransportRegistry) { @@ -45,9 +44,12 @@ type Transport struct { networkManager adapter.NetworkManager interfaceName string interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] - transports []adapter.DNSTransport - updateAccess sync.Mutex + transportLock sync.RWMutex updatedAt time.Time + servers []M.Socksaddr + search []string + ndots int + attempts int } func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) { @@ -62,27 +64,40 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt logger: logger, networkManager: service.FromContext[adapter.NetworkManager](ctx), interfaceName: options.Interface, + ndots: 1, + attempts: 2, }, nil } +func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) *Transport { + return &Transport{ + TransportAdapter: transportAdapter, + ctx: ctx, + dialer: dialer, + logger: logger, + networkManager: service.FromContext[adapter.NetworkManager](ctx), + ndots: 1, + attempts: 2, + } +} + func (t *Transport) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } - err := t.fetchServers() - if err != nil { - return err - } if t.interfaceName == "" { t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) } + go func() { + _, err := t.Fetch() + if err != nil { + t.logger.Error(E.Cause(err, "fetch DNS servers")) + } + }() return nil } func (t *Transport) Close() error { - for _, transport := range t.transports { - transport.Close() - } if t.interfaceCallback != nil { t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) } @@ -90,23 +105,44 @@ func (t *Transport) Close() error { } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - err := t.fetchServers() + servers, err := t.Fetch() if err != nil { return nil, err } - - if len(t.transports) == 0 { + if len(servers) == 0 { return nil, E.New("dhcp: empty DNS servers from response") } + return t.Exchange0(ctx, message, servers) +} - var response *mDNS.Msg - for _, transport := range t.transports { - response, err = transport.Exchange(ctx, message) - if err == nil { - return response, nil - } +func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) { + question := message.Question[0] + domain := dns.FqdnToDomain(question.Name) + if len(servers) == 1 || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) { + return t.exchangeSingleRequest(ctx, servers, message, domain) + } else { + return t.exchangeParallel(ctx, servers, message, domain) } - return nil, err +} + +func (t *Transport) Fetch() ([]M.Socksaddr, error) { + t.transportLock.RLock() + updatedAt := t.updatedAt + servers := t.servers + t.transportLock.RUnlock() + if time.Since(updatedAt) < C.DHCPTTL { + return servers, nil + } + t.transportLock.Lock() + defer t.transportLock.Unlock() + if time.Since(t.updatedAt) < C.DHCPTTL { + return t.servers, nil + } + err := t.updateServers() + if err != nil { + return nil, err + } + return t.servers, nil } func (t *Transport) fetchInterface() (*control.Interface, error) { @@ -124,18 +160,6 @@ func (t *Transport) fetchInterface() (*control.Interface, error) { } } -func (t *Transport) fetchServers() error { - if time.Since(t.updatedAt) < C.DHCPTTL { - return nil - } - t.updateAccess.Lock() - defer t.updateAccess.Unlock() - if time.Since(t.updatedAt) < C.DHCPTTL { - return nil - } - return t.updateServers() -} - func (t *Transport) updateServers() error { iface, err := t.fetchInterface() if err != nil { @@ -148,7 +172,7 @@ func (t *Transport) updateServers() error { cancel() if err != nil { return err - } else if len(t.transports) == 0 { + } else if len(t.servers) == 0 { return E.New("dhcp: empty DNS servers response") } else { t.updatedAt = time.Now() @@ -177,7 +201,11 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) } defer packetConn.Close() - discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer)) + discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions( + dhcpv4.OptionDomainName, + dhcpv4.OptionDomainNameServer, + dhcpv4.OptionDNSDomainSearchList, + )) if err != nil { return err } @@ -223,31 +251,23 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne continue } - dns := dhcpPacket.DNS() - if len(dns) == 0 { - return nil - } - return t.recreateServers(iface, common.Map(dns, func(it net.IP) M.Socksaddr { - return M.SocksaddrFrom(M.AddrFromIP(it), 53) - })) + return t.recreateServers(iface, dhcpPacket) } } -func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.Socksaddr) error { - if len(serverAddrs) > 0 { - t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "]") +func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error { + searchList := dhcpPacket.DomainSearch() + if searchList != nil && len(searchList.Labels) > 0 { + t.search = searchList.Labels + } else if dhcpPacket.DomainName() != "" { + t.search = []string{dhcpPacket.DomainName()} } - serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{ - BindInterface: iface.Name, - UDPFragmentDefault: true, - })) - var transports []adapter.DNSTransport - for _, serverAddr := range serverAddrs { - transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr)) + serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr { + return M.SocksaddrFrom(M.AddrFromIP(it), 53) + }) + if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) { + t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]") } - for _, transport := range t.transports { - transport.Close() - } - t.transports = transports + t.servers = serverAddrs return nil } diff --git a/dns/transport/dhcp/dhcp_shared.go b/dns/transport/dhcp/dhcp_shared.go new file mode 100644 index 00000000..31b92fb5 --- /dev/null +++ b/dns/transport/dhcp/dhcp_shared.go @@ -0,0 +1,202 @@ +package dhcp + +import ( + "context" + "math/rand" + "strings" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +const ( + // net.maxDNSPacketSize + maxDNSPacketSize = 1232 +) + +func (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + var lastErr error + for _, fqdn := range t.nameList(domain) { + response, err := t.tryOneName(ctx, servers, fqdn, message) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr +} + +func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + returned := make(chan struct{}) + defer close(returned) + type queryResult struct { + response *mDNS.Msg + err error + } + results := make(chan queryResult) + startRacer := func(ctx context.Context, fqdn string) { + response, err := t.tryOneName(ctx, servers, fqdn, message) + if err == nil { + if response.Rcode != mDNS.RcodeSuccess { + err = dns.RcodeError(response.Rcode) + } else if len(dns.MessageToAddresses(response)) == 0 { + err = E.New(fqdn, ": empty result") + } + } + select { + case results <- queryResult{response, err}: + case <-returned: + } + } + queryCtx, queryCancel := context.WithCancel(ctx) + defer queryCancel() + var nameCount int + for _, fqdn := range t.nameList(domain) { + nameCount++ + go startRacer(queryCtx, fqdn) + } + var errors []error + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case result := <-results: + if result.err == nil { + return result.response, nil + } + errors = append(errors, result.err) + if len(errors) == nameCount { + return nil, E.Errors(errors...) + } + } + } +} + +func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) { + sLen := len(servers) + var lastErr error + for i := 0; i < t.attempts; i++ { + for j := 0; j < sLen; j++ { + server := servers[j] + question := message.Question[0] + question.Name = fqdn + response, err := t.exchangeOne(ctx, server, question, C.DNSTimeout, false, true) + if err != nil { + lastErr = err + continue + } + return response, nil + } + } + return nil, E.Cause(lastErr, fqdn) +} + +func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) { + if server.Port == 0 { + server.Port = 53 + } + var networks []string + if useTCP { + networks = []string{N.NetworkTCP} + } else { + networks = []string{N.NetworkUDP, N.NetworkTCP} + } + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: uint16(rand.Uint32()), + RecursionDesired: true, + AuthenticatedData: ad, + }, + Question: []mDNS.Question{question}, + Compress: true, + } + request.SetEdns0(maxDNSPacketSize, false) + buffer := buf.Get(buf.UDPBufferSize) + defer buf.Put(buffer) + for _, network := range networks { + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout)) + defer cancel() + conn, err := t.dialer.DialContext(ctx, network, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + conn.SetDeadline(deadline) + } + rawMessage, err := request.PackBuffer(buffer) + if err != nil { + return nil, E.Cause(err, "pack request") + } + _, err = conn.Write(rawMessage) + if err != nil { + return nil, E.Cause(err, "write request") + } + n, err := conn.Read(buffer) + if err != nil { + return nil, E.Cause(err, "read response") + } + var response mDNS.Msg + err = response.Unpack(buffer[:n]) + if err != nil { + return nil, E.Cause(err, "unpack response") + } + if response.Truncated && network == N.NetworkUDP { + continue + } + return &response, nil + } + panic("unexpected") +} + +func (t *Transport) nameList(name string) []string { + l := len(name) + rooted := l > 0 && name[l-1] == '.' + if l > 254 || l == 254 && !rooted { + return nil + } + + if rooted { + if avoidDNS(name) { + return nil + } + return []string{name} + } + + hasNdots := strings.Count(name, ".") >= t.ndots + name += "." + // l++ + + names := make([]string, 0, 1+len(t.search)) + if hasNdots && !avoidDNS(name) { + names = append(names, name) + } + for _, suffix := range t.search { + fqdn := name + suffix + if !avoidDNS(fqdn) && len(fqdn) <= 254 { + names = append(names, fqdn) + } + } + if !hasNdots && !avoidDNS(name) { + names = append(names, name) + } + return names +} + +func avoidDNS(name string) bool { + if name == "" { + return true + } + if name[len(name)-1] == '.' { + name = name[:len(name)-1] + } + return strings.HasSuffix(name, ".onion") +} From 6849288d6d4dd28dffea6fbdc67e206589f59475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 2 Sep 2025 17:40:48 +0800 Subject: [PATCH 34/63] Fix typo in TestSniffUQUICChrome115 --- common/sniff/quic_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/sniff/quic_test.go b/common/sniff/quic_test.go index 1149f68e..27243dd3 100644 --- a/common/sniff/quic_test.go +++ b/common/sniff/quic_test.go @@ -56,7 +56,7 @@ func TestSniffUQUICChrome115(t *testing.T) { err = sniff.QUICClientHello(context.Background(), &metadata, pkt) require.NoError(t, err) require.Equal(t, metadata.Protocol, C.ProtocolQUIC) - require.Equal(t, metadata.Client, C.ClientQUICGo) + require.Equal(t, metadata.Client, C.ClientChromium) require.Equal(t, metadata.Domain, "www.google.com") } From 1a18e43a88a230ec636172338403e77a1b651302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 2 Sep 2025 17:55:48 +0800 Subject: [PATCH 35/63] Fix linux icmp routes --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 6f18a013..2bf6c638 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.7.0-beta.1 + github.com/sagernet/sing-tun v0.7.0 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 diff --git a/go.sum b/go.sum index 750d2310..f7008b61 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,6 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.6-0.20250902093152-bd5790e3b4db h1:D+56hVUg42b+nbXVYMwDNyFaN6vTviSXqF96dvBqRiM= -github.com/sagernet/sing v0.7.6-0.20250902093152-bd5790e3b4db/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.7.6 h1:6LBfDH+aI/26J3r9UHlaxTNjJeMhBpU/wrk0JKDZYI4= github.com/sagernet/sing v0.7.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= @@ -181,8 +179,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY= -github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ= +github.com/sagernet/sing-tun v0.7.0 h1:zda+KYbxVyeWKdE9k73Ax02jg7cmPZ7/4ZTVCloFBYo= +github.com/sagernet/sing-tun v0.7.0/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= From 0ef7e8eca2637e63ce7b732d89e00c93ac4ef9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 2 Sep 2025 18:00:02 +0800 Subject: [PATCH 36/63] Fix `route.default_interface` not taking effect --- common/dialer/default.go | 64 +++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index dd1e90e5..08111625 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -88,43 +88,41 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial if networkManager != nil { defaultOptions := networkManager.DefaultOptions() - if !disableDefaultBind { - if defaultOptions.BindInterface != "" { - bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) + if defaultOptions.BindInterface != "" { + bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else if networkManager.AutoDetectInterface() && !disableDefaultBind { + if platformInterface != nil { + networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy) + networkType = common.Map(options.NetworkType, option.InterfaceType.Build) + fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) + if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 { + networkStrategy = defaultOptions.NetworkStrategy + networkType = defaultOptions.NetworkType + fallbackNetworkType = defaultOptions.FallbackNetworkType + } + networkFallbackDelay = time.Duration(options.FallbackDelay) + if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 { + networkFallbackDelay = defaultOptions.FallbackDelay + } + if networkStrategy == nil { + networkStrategy = common.Ptr(C.NetworkStrategyDefault) + defaultNetworkStrategy = true + } + bindFunc := networkManager.ProtectFunc() + dialer.Control = control.Append(dialer.Control, bindFunc) + listener.Control = control.Append(listener.Control, bindFunc) + } else { + bindFunc := networkManager.AutoDetectInterfaceFunc() dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) - } else if networkManager.AutoDetectInterface() { - if platformInterface != nil { - networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy) - networkType = common.Map(options.NetworkType, option.InterfaceType.Build) - fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) - if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 { - networkStrategy = defaultOptions.NetworkStrategy - networkType = defaultOptions.NetworkType - fallbackNetworkType = defaultOptions.FallbackNetworkType - } - networkFallbackDelay = time.Duration(options.FallbackDelay) - if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 { - networkFallbackDelay = defaultOptions.FallbackDelay - } - if networkStrategy == nil { - networkStrategy = common.Ptr(C.NetworkStrategyDefault) - defaultNetworkStrategy = true - } - bindFunc := networkManager.ProtectFunc() - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } else { - bindFunc := networkManager.AutoDetectInterfaceFunc() - dialer.Control = control.Append(dialer.Control, bindFunc) - listener.Control = control.Append(listener.Control, bindFunc) - } - } - if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { - dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) - listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) } } + if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) + } } if networkManager != nil { markFunc := networkManager.AutoRedirectOutputMarkFunc() From cbf48e9b8c7c88330c939ed5a30a8e6842012721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 2 Sep 2025 18:09:48 +0800 Subject: [PATCH 37/63] Fix multiple sniff --- adapter/inbound.go | 1 + route/route.go | 99 ++++++++++++++++++++++++++------------- route/rule/rule_action.go | 16 +++---- 3 files changed, 75 insertions(+), 41 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index edba8447..de30149f 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -57,6 +57,7 @@ type InboundContext struct { Domain string Client string SniffContext any + SnifferNames []string SniffError error // cache diff --git a/route/route.go b/route/route.go index 250b8fee..ef30a0b1 100644 --- a/route/route.go +++ b/route/route.go @@ -27,6 +27,8 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" + + "golang.org/x/exp/slices" ) // Deprecated: use RouteConnectionEx instead. @@ -345,16 +347,16 @@ func (r *Router) matchRule( newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{ OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), - }, inputConn, inputPacketConn, nil) - if newErr != nil { - fatalErr = newErr - return - } + }, inputConn, inputPacketConn, nil, nil) if newBuffer != nil { buffers = []*buf.Buffer{newBuffer} } else if len(newPackerBuffers) > 0 { packetBuffers = newPackerBuffers } + if newErr != nil { + fatalErr = newErr + return + } } if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS { fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{ @@ -453,16 +455,16 @@ match: switch action := currentRule.Action().(type) { case *R.RuleActionSniff: if !preMatch { - newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers) - if newErr != nil { - fatalErr = newErr - return - } + newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers) if newBuffer != nil { buffers = append(buffers, newBuffer) } else if len(newPacketBuffers) > 0 { packetBuffers = append(packetBuffers, newPacketBuffers...) } + if newErr != nil { + fatalErr = newErr + return + } } else { selectedRule = currentRule selectedRuleIndex = currentRuleIndex @@ -489,7 +491,7 @@ match: func (r *Router) actionSniff( ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff, - inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, + inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputPacketBuffers []*N.PacketBuffer, ) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { if sniff.Skip(metadata) { r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first") @@ -501,7 +503,7 @@ func (r *Router) actionSniff( if inputConn != nil { if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 { return - } else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { + } else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError) return } @@ -528,6 +530,7 @@ func (r *Router) actionSniff( action.Timeout, streamSniffers..., ) + metadata.SnifferNames = action.SnifferNames metadata.SniffError = err if err == nil { //goland:noinspection GoDeprecation @@ -553,10 +556,13 @@ func (r *Router) actionSniff( } else if inputPacketConn != nil { if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 { return - } else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { + } else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError) return } + quicMoreData := func() bool { + return slices.Equal(metadata.SnifferNames, action.SnifferNames) && errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) + } var packetSniffers []sniff.PacketSniffer if len(action.PacketSniffers) > 0 { packetSniffers = action.PacketSniffers @@ -571,12 +577,37 @@ func (r *Router) actionSniff( sniff.NTP, } } + var err error + for _, packetBuffer := range inputPacketBuffers { + if quicMoreData() { + err = sniff.PeekPacket( + ctx, + metadata, + packetBuffer.Buffer.Bytes(), + sniff.QUICClientHello, + ) + } else { + err = sniff.PeekPacket( + ctx, metadata, + packetBuffer.Buffer.Bytes(), + packetSniffers..., + ) + } + metadata.SnifferNames = action.SnifferNames + metadata.SniffError = err + if errors.Is(err, sniff.ErrNeedMoreData) { + // TODO: replace with generic message when there are more multi-packet protocols + r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") + continue + } + goto finally + } + packetBuffers = inputPacketBuffers for { var ( sniffBuffer = buf.NewPacket() destination M.Socksaddr done = make(chan struct{}) - err error ) go func() { sniffTimeout := C.ReadPayloadTimeout @@ -602,7 +633,7 @@ func (r *Router) actionSniff( return } } else { - if len(packetBuffers) > 0 || metadata.SniffError != nil { + if quicMoreData() { err = sniff.PeekPacket( ctx, metadata, @@ -622,32 +653,34 @@ func (r *Router) actionSniff( Destination: destination, } packetBuffers = append(packetBuffers, packetBuffer) + metadata.SnifferNames = action.SnifferNames metadata.SniffError = err if errors.Is(err, sniff.ErrNeedMoreData) { // TODO: replace with generic message when there are more multi-packet protocols r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") continue } - if metadata.Protocol != "" { - //goland:noinspection GoDeprecation - if action.OverrideDestination && M.IsDomainName(metadata.Domain) { - metadata.Destination = M.Socksaddr{ - Fqdn: metadata.Domain, - Port: metadata.Destination.Port, - } - } - if metadata.Domain != "" && metadata.Client != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) - } else if metadata.Domain != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) - } else if metadata.Client != "" { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client) - } else { - r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) - } + } + goto finally + } + finally: + if err == nil { + //goland:noinspection GoDeprecation + if action.OverrideDestination && M.IsDomainName(metadata.Domain) { + metadata.Destination = M.Socksaddr{ + Fqdn: metadata.Domain, + Port: metadata.Destination.Port, } } - break + if metadata.Domain != "" && metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client) + } else if metadata.Domain != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) + } else if metadata.Client != "" { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client) + } else { + r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) + } } } return diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index ce50dad9..62d5410a 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -87,7 +87,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti return &RuleActionHijackDNS{}, nil case C.RuleActionTypeSniff: sniffAction := &RuleActionSniff{ - snifferNames: action.SniffOptions.Sniffer, + SnifferNames: action.SniffOptions.Sniffer, Timeout: time.Duration(action.SniffOptions.Timeout), } return sniffAction, sniffAction.build() @@ -361,7 +361,7 @@ func (r *RuleActionHijackDNS) String() string { } type RuleActionSniff struct { - snifferNames []string + SnifferNames []string StreamSniffers []sniff.StreamSniffer PacketSniffers []sniff.PacketSniffer Timeout time.Duration @@ -374,7 +374,7 @@ func (r *RuleActionSniff) Type() string { } func (r *RuleActionSniff) build() error { - for _, name := range r.snifferNames { + for _, name := range r.SnifferNames { switch name { case C.ProtocolTLS: r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello) @@ -407,14 +407,14 @@ func (r *RuleActionSniff) build() error { } func (r *RuleActionSniff) String() string { - if len(r.snifferNames) == 0 && r.Timeout == 0 { + if len(r.SnifferNames) == 0 && r.Timeout == 0 { return "sniff" - } else if len(r.snifferNames) > 0 && r.Timeout == 0 { - return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ")") - } else if len(r.snifferNames) == 0 && r.Timeout > 0 { + } else if len(r.SnifferNames) > 0 && r.Timeout == 0 { + return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ")") + } else if len(r.SnifferNames) == 0 && r.Timeout > 0 { return F.ToString("sniff(", r.Timeout.String(), ")") } else { - return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ",", r.Timeout.String(), ")") + return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ",", r.Timeout.String(), ")") } } From f352f8448372647d5795c6af484c50165c95da30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 5 Sep 2025 15:16:14 +0800 Subject: [PATCH 38/63] Fix read address --- adapter/upstream_legacy.go | 4 ++-- go.mod | 4 ++-- go.sum | 8 ++++---- protocol/anytls/inbound.go | 2 +- protocol/naive/inbound.go | 2 +- transport/trojan/protocol.go | 2 +- transport/wireguard/client_bind.go | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/adapter/upstream_legacy.go b/adapter/upstream_legacy.go index 60f5fdb4..65402563 100644 --- a/adapter/upstream_legacy.go +++ b/adapter/upstream_legacy.go @@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { // Deprecated: removed func UpstreamMetadata(metadata InboundContext) M.Metadata { return M.Metadata{ - Source: metadata.Source, - Destination: metadata.Destination, + Source: metadata.Source.Unwrap(), + Destination: metadata.Destination.Unwrap(), } } diff --git a/go.mod b/go.mod index 2bf6c638..7666f453 100644 --- a/go.mod +++ b/go.mod @@ -27,9 +27,9 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.7.6 + github.com/sagernet/sing v0.7.7 github.com/sagernet/sing-mux v0.3.3 - github.com/sagernet/sing-quic v0.5.0 + github.com/sagernet/sing-quic v0.5.1 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index f7008b61..3c49b388 100644 --- a/go.sum +++ b/go.sum @@ -167,12 +167,12 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.6 h1:6LBfDH+aI/26J3r9UHlaxTNjJeMhBpU/wrk0JKDZYI4= -github.com/sagernet/sing v0.7.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.7 h1:o46FzVZS+wKbBMEkMEdEHoVZxyM9jvfRpKXc7pEgS/c= +github.com/sagernet/sing v0.7.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak= -github.com/sagernet/sing-quic v0.5.0/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0= +github.com/sagernet/sing-quic v0.5.1 h1:o+mX/schfy6fbbU2rnb6ouUYOL+iUBjA4jOZqyIvDsU= +github.com/sagernet/sing-quic v0.5.1/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= diff --git a/protocol/anytls/inbound.go b/protocol/anytls/inbound.go index 89907f1d..662c7788 100644 --- a/protocol/anytls/inbound.go +++ b/protocol/anytls/inbound.go @@ -124,7 +124,7 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou //nolint:staticcheck metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Source = source - metadata.Destination = destination + metadata.Destination = destination.Unwrap() if userName, _ := auth.UserFromContext[string](ctx); userName != "" { metadata.User = userName h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index 5e77af43..f6e456f5 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -170,7 +170,7 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) { hostPort = request.Host } source := sHttp.SourceAddress(request) - destination := M.ParseSocksaddr(hostPort) + destination := M.ParseSocksaddr(hostPort).Unwrap() if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { conn, _, err := hijacker.Hijack() diff --git a/transport/trojan/protocol.go b/transport/trojan/protocol.go index 394ba291..e13dda67 100644 --- a/transport/trojan/protocol.go +++ b/transport/trojan/protocol.go @@ -292,7 +292,7 @@ func ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) { } _, err = buffer.ReadFullFrom(conn, int(length)) - return destination.Unwrap(), err + return destination, err } func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error { diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index e74e909d..f1081855 100644 --- a/transport/wireguard/client_bind.go +++ b/transport/wireguard/client_bind.go @@ -138,7 +138,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) b := packets[0] common.ClearArray(b[1:4]) } - eps[0] = remoteEndpoint(M.AddrPortFromNet(addr)) + eps[0] = remoteEndpoint(M.SocksaddrFromNet(addr).Unwrap().AddrPort()) count = 1 return } From cc3041322e273fee324b9027804fabce10cbd383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 5 Sep 2025 15:20:49 +0800 Subject: [PATCH 39/63] Fix DNS cache --- dns/client.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/dns/client.go b/dns/client.go index 1223759a..e80fec4b 100644 --- a/dns/client.go +++ b/dns/client.go @@ -320,36 +320,36 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip. } dnsName := dns.Fqdn(domain) if strategy == C.DomainStrategyIPv4Only { - response, err := c.questionCache(dns.Question{ + addresses, err := c.questionCache(dns.Question{ Name: dnsName, Qtype: dns.TypeA, Qclass: dns.ClassINET, }, nil) if err != ErrNotCached { - return response, true + return addresses, true } } else if strategy == C.DomainStrategyIPv6Only { - response, err := c.questionCache(dns.Question{ + addresses, err := c.questionCache(dns.Question{ Name: dnsName, Qtype: dns.TypeAAAA, Qclass: dns.ClassINET, }, nil) if err != ErrNotCached { - return response, true + return addresses, true } } else { - response4, _ := c.questionCache(dns.Question{ + response4, _ := c.loadResponse(dns.Question{ Name: dnsName, Qtype: dns.TypeA, Qclass: dns.ClassINET, }, nil) - response6, _ := c.questionCache(dns.Question{ + response6, _ := c.loadResponse(dns.Question{ Name: dnsName, Qtype: dns.TypeAAAA, Qclass: dns.ClassINET, }, nil) - if len(response4) > 0 || len(response6) > 0 { - return sortAddresses(response4, response6, strategy), true + if response4 != nil || response6 != nil { + return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true } } return nil, false @@ -517,6 +517,9 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp } func MessageToAddresses(response *dns.Msg) []netip.Addr { + if response == nil || response.Rcode != dns.RcodeSuccess { + return nil + } addresses := make([]netip.Addr, 0, len(response.Answer)) for _, rawAnswer := range response.Answer { switch answer := rawAnswer.(type) { From 2594745ef873052fafed12251fc09dd5bda529fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 5 Sep 2025 15:49:06 +0800 Subject: [PATCH 40/63] Fix DNS client --- .../clashapi => common}/compatible/map.go | 0 dns/client.go | 118 ++++++++++-------- .../clashapi/trafficontrol/manager.go | 2 +- 3 files changed, 69 insertions(+), 51 deletions(-) rename {experimental/clashapi => common}/compatible/map.go (100%) diff --git a/experimental/clashapi/compatible/map.go b/common/compatible/map.go similarity index 100% rename from experimental/clashapi/compatible/map.go rename to common/compatible/map.go diff --git a/dns/client.go b/dns/client.go index e80fec4b..d8f0fb0b 100644 --- a/dns/client.go +++ b/dns/client.go @@ -2,12 +2,14 @@ package dns import ( "context" + "errors" "net" "net/netip" "strings" "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/compatible" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -30,16 +32,18 @@ var ( var _ adapter.DNSClient = (*Client)(nil) type Client struct { - timeout time.Duration - disableCache bool - disableExpire bool - independentCache bool - clientSubnet netip.Prefix - rdrc adapter.RDRCStore - initRDRCFunc func() adapter.RDRCStore - logger logger.ContextLogger - cache freelru.Cache[dns.Question, *dns.Msg] - transportCache freelru.Cache[transportCacheKey, *dns.Msg] + timeout time.Duration + disableCache bool + disableExpire bool + independentCache bool + clientSubnet netip.Prefix + rdrc adapter.RDRCStore + initRDRCFunc func() adapter.RDRCStore + logger logger.ContextLogger + cache freelru.Cache[dns.Question, *dns.Msg] + cacheLock compatible.Map[dns.Question, chan struct{}] + transportCache freelru.Cache[transportCacheKey, *dns.Msg] + transportCacheLock compatible.Map[dns.Question, chan struct{}] } type ClientOptions struct { @@ -96,17 +100,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m if c.logger != nil { c.logger.WarnContext(ctx, "bad question size: ", len(message.Question)) } - responseMessage := dns.Msg{ - MsgHdr: dns.MsgHdr{ - Id: message.Id, - Response: true, - Rcode: dns.RcodeFormatError, - }, - Question: message.Question, - } - return &responseMessage, nil + return FixedResponseStatus(message, dns.RcodeFormatError), nil } question := message.Question[0] + if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only { + if c.logger != nil { + c.logger.DebugContext(ctx, "strategy rejected") + } + return FixedResponseStatus(message, dns.RcodeSuccess), nil + } clientSubnet := options.ClientSubnet if !clientSubnet.IsValid() { clientSubnet = c.clientSubnet @@ -120,6 +122,27 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m !options.ClientSubnet.IsValid() disableCache := !isSimpleRequest || c.disableCache || options.DisableCache if !disableCache { + if c.cache != nil { + cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{})) + if loaded { + <-cond + } else { + defer func() { + c.cacheLock.Delete(question) + close(cond) + }() + } + } else if c.transportCache != nil { + cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{})) + if loaded { + <-cond + } else { + defer func() { + c.transportCacheLock.Delete(question) + close(cond) + }() + } + } response, ttl := c.loadResponse(question, transport) if response != nil { logCachedResponse(c.logger, ctx, response, ttl) @@ -127,27 +150,14 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m return response, nil } } - if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only { - responseMessage := dns.Msg{ - MsgHdr: dns.MsgHdr{ - Id: message.Id, - Response: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{question}, - } - if c.logger != nil { - c.logger.DebugContext(ctx, "strategy rejected") - } - return &responseMessage, nil - } + messageId := message.Id contextTransport, clientSubnetLoaded := transportTagFromContext(ctx) if clientSubnetLoaded && transport.Tag() == contextTransport { return nil, E.New("DNS query loopback in transport[", contextTransport, "]") } ctx = contextWithTransportTag(ctx, transport.Tag()) - if responseChecker != nil && c.rdrc != nil { + if !disableCache && responseChecker != nil && c.rdrc != nil { rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype) if rejected { return nil, ErrResponseRejectedCached @@ -157,7 +167,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m response, err := transport.Exchange(ctx, message) cancel() if err != nil { - return nil, err + var rcodeError RcodeError + if errors.As(err, &rcodeError) { + response = FixedResponseStatus(message, int(rcodeError)) + } else { + return nil, err + } } /*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA { validResponse := response @@ -196,13 +211,14 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m }*/ if responseChecker != nil { var rejected bool - if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) { + // TODO: add accept_any rule and support to check response instead of addresses + if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 { rejected = true } else { rejected = !responseChecker(MessageToAddresses(response)) } if rejected { - if c.rdrc != nil { + if !disableCache && c.rdrc != nil { c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger) } logRejectedResponse(c.logger, ctx, response) @@ -305,8 +321,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom func (c *Client) ClearCache() { if c.cache != nil { c.cache.Purge() - } - if c.transportCache != nil { + } else if c.transportCache != nil { c.transportCache.Purge() } } @@ -390,15 +405,15 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio transportTag: transport.Tag(), }, message) } - return - } - if !c.independentCache { - c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive)) } else { - c.transportCache.AddWithLifetime(transportCacheKey{ - Question: question, - transportTag: transport.Tag(), - }, message, time.Second*time.Duration(timeToLive)) + if !c.independentCache { + c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive)) + } else { + c.transportCache.AddWithLifetime(transportCacheKey{ + Question: question, + transportTag: transport.Tag(), + }, message, time.Second*time.Duration(timeToLive)) + } } } @@ -564,9 +579,12 @@ func transportTagFromContext(ctx context.Context) (string, bool) { func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg { return &dns.Msg{ MsgHdr: dns.MsgHdr{ - Id: message.Id, - Rcode: rcode, - Response: true, + Id: message.Id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: rcode, }, Question: message.Question, } diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index 7b69b93d..bb4822df 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -6,8 +6,8 @@ import ( "sync/atomic" "time" + "github.com/sagernet/sing-box/common/compatible" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/clashapi/compatible" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/x/list" From b14cecaeb2c2d00be4670f2cdddae71ae0394e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 5 Sep 2025 16:17:49 +0800 Subject: [PATCH 41/63] Fix DNS packet size --- dns/transport/dhcp/dhcp_shared.go | 7 +------ dns/transport/local/local.go | 7 ++++++- dns/transport/local/resolv.go | 5 ----- dns/transport_dialer.go | 1 - 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/dns/transport/dhcp/dhcp_shared.go b/dns/transport/dhcp/dhcp_shared.go index 31b92fb5..3d8d5512 100644 --- a/dns/transport/dhcp/dhcp_shared.go +++ b/dns/transport/dhcp/dhcp_shared.go @@ -16,11 +16,6 @@ import ( mDNS "github.com/miekg/dns" ) -const ( - // net.maxDNSPacketSize - maxDNSPacketSize = 1232 -) - func (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { var lastErr error for _, fqdn := range t.nameList(domain) { @@ -118,7 +113,7 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio Question: []mDNS.Question{question}, Compress: true, } - request.SetEdns0(maxDNSPacketSize, false) + request.SetEdns0(buf.UDPBufferSize, false) buffer := buf.Get(buf.UDPBufferSize) defer buf.Put(buffer) for _, network := range networks { diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index 70b495ca..7c26b936 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -2,7 +2,9 @@ package local import ( "context" + "errors" "math/rand" + "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -164,7 +166,7 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio Question: []mDNS.Question{question}, Compress: true, } - request.SetEdns0(maxDNSPacketSize, false) + request.SetEdns0(buf.UDPBufferSize, false) buffer := buf.Get(buf.UDPBufferSize) defer buf.Put(buffer) for _, network := range networks { @@ -184,6 +186,9 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio } _, err = conn.Write(rawMessage) if err != nil { + if errors.Is(err, syscall.EMSGSIZE) && network == N.NetworkUDP { + continue + } return nil, E.Cause(err, "write request") } n, err := conn.Read(buffer) diff --git a/dns/transport/local/resolv.go b/dns/transport/local/resolv.go index afe679a2..3586cbbf 100644 --- a/dns/transport/local/resolv.go +++ b/dns/transport/local/resolv.go @@ -10,11 +10,6 @@ import ( "time" ) -const ( - // net.maxDNSPacketSize - maxDNSPacketSize = 1232 -) - type resolverConfig struct { initOnce sync.Once ch chan struct{} diff --git a/dns/transport_dialer.go b/dns/transport_dialer.go index 2ed70ead..b3ee8082 100644 --- a/dns/transport_dialer.go +++ b/dns/transport_dialer.go @@ -19,7 +19,6 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) ( if options.LegacyDefaultDialer { return dialer.NewDefaultOutbound(ctx), nil } else { - options.UDPFragmentDefault = true return dialer.NewWithOptions(dialer.Options{ Context: ctx, Options: options.DialerOptions, From f98a3a4f653b848516ec6344983b76a607430742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 5 Sep 2025 16:23:30 +0800 Subject: [PATCH 42/63] Treat requests with OPT extra but no options as simple requests --- dns/client.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dns/client.go b/dns/client.go index d8f0fb0b..0d0e712b 100644 --- a/dns/client.go +++ b/dns/client.go @@ -19,7 +19,7 @@ import ( "github.com/sagernet/sing/contrab/freelru" "github.com/sagernet/sing/contrab/maphash" - dns "github.com/miekg/dns" + "github.com/miekg/dns" ) var ( @@ -116,9 +116,14 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m if clientSubnet.IsValid() { message = SetClientSubnet(message, clientSubnet) } + isSimpleRequest := len(message.Question) == 1 && len(message.Ns) == 0 && - len(message.Extra) == 0 && + (len(message.Extra) == 0 || len(message.Extra) == 1 && + message.Extra[0].Header().Rrtype == dns.TypeOPT && + message.Extra[0].Header().Class > 0 && + message.Extra[0].Header().Ttl == 0 && + len(message.Extra[0].(*dns.OPT).Option) == 0) && !options.ClientSubnet.IsValid() disableCache := !isSimpleRequest || c.disableCache || options.DisableCache if !disableCache { From 79c0b9f51d3f425c566c6a85c94b3a0472e609e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 8 Sep 2025 19:37:58 +0800 Subject: [PATCH 43/63] Fix tls options ignored in mixed inbounds --- protocol/mixed/inbound.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index fe84aa01..5b531480 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -8,10 +8,12 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" @@ -33,6 +35,7 @@ type Inbound struct { logger log.ContextLogger listener *listener.Listener authenticator *auth.Authenticator + tlsConfig tls.ServerConfig } func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { @@ -42,6 +45,13 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo logger: logger, authenticator: auth.NewAuthenticator(options.Users), } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } inbound.listener = listener.New(listener.Options{ Context: ctx, Logger: logger, @@ -58,11 +68,20 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } return h.listener.Start() } func (h *Inbound) Close() error { - return h.listener.Close() + return common.Close( + h.listener, + h.tlsConfig, + ) } func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { @@ -78,6 +97,13 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a } func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + if h.tlsConfig != nil { + tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + return E.Cause(err, "TLS handshake") + } + conn = tlsConn + } reader := std_bufio.NewReader(conn) headerBytes, err := reader.Peek(1) if err != nil { From f16468e74f5bbb80773344b62beed13eef31189c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 9 Sep 2025 14:16:31 +0800 Subject: [PATCH 44/63] Fix ipv6 tproxy listener --- common/listener/listener_tcp.go | 3 ++- common/listener/listener_udp.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index 636d0cfb..53d7bc86 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -3,6 +3,7 @@ package listener import ( "net" "net/netip" + "strings" "syscall" "time" @@ -56,7 +57,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) { if l.tproxy { listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error { return control.Raw(conn, func(fd uintptr) error { - return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false) + return redir.TProxy(fd, !strings.HasSuffix(network, "4"), false) }) }) } diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go index 3304f165..e689c8bb 100644 --- a/common/listener/listener_udp.go +++ b/common/listener/listener_udp.go @@ -5,6 +5,7 @@ import ( "net" "net/netip" "os" + "strings" "syscall" "github.com/sagernet/sing-box/adapter" @@ -41,7 +42,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) { if l.tproxy { listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error { return control.Raw(conn, func(fd uintptr) error { - return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true) + return redir.TProxy(fd, !strings.HasSuffix(network, "4"), true) }) }) } From 8a200bf91307a88bb840e149d8d72ddceb4835ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 9 Sep 2025 14:29:40 +0800 Subject: [PATCH 45/63] Fix auto redirect output --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7666f453..f755f030 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.7.0 + github.com/sagernet/sing-tun v0.7.1-0.20250909062902-4c9139540047 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 diff --git a/go.sum b/go.sum index 3c49b388..d2ecb81f 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.7.0 h1:zda+KYbxVyeWKdE9k73Ax02jg7cmPZ7/4ZTVCloFBYo= -github.com/sagernet/sing-tun v0.7.0/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= +github.com/sagernet/sing-tun v0.7.1-0.20250909062902-4c9139540047 h1:FOCCZM4UU9EjSN8lpa6+r788z/FFtf29DqXlr4Z/6Z0= +github.com/sagernet/sing-tun v0.7.1-0.20250909062902-4c9139540047/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= From c5f2cea80200e2e997f80c77c3fff37e9e82852f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 9 Sep 2025 14:49:00 +0800 Subject: [PATCH 46/63] Prevent panic when wintun dll fails to load --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f755f030..14839e9e 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.7.1-0.20250909062902-4c9139540047 + github.com/sagernet/sing-tun v0.7.1-0.20250909064831-29d619807240 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 diff --git a/go.sum b/go.sum index d2ecb81f..772528cc 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.7.1-0.20250909062902-4c9139540047 h1:FOCCZM4UU9EjSN8lpa6+r788z/FFtf29DqXlr4Z/6Z0= -github.com/sagernet/sing-tun v0.7.1-0.20250909062902-4c9139540047/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= +github.com/sagernet/sing-tun v0.7.1-0.20250909064831-29d619807240 h1:QJrYOLJB4A0ONEl1dmZtcyY9NmY6EOKAx3CblLOb+Y8= +github.com/sagernet/sing-tun v0.7.1-0.20250909064831-29d619807240/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= From 36babe4bef39fa46a09d1918ede9426051210110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 9 Sep 2025 16:33:19 +0800 Subject: [PATCH 47/63] Fix hysteria2 handshake timeout --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 14839e9e..4dd02300 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/sing v0.7.7 github.com/sagernet/sing-mux v0.3.3 - github.com/sagernet/sing-quic v0.5.1 + github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index 772528cc..2876d18a 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/sagernet/sing v0.7.7 h1:o46FzVZS+wKbBMEkMEdEHoVZxyM9jvfRpKXc7pEgS/c= github.com/sagernet/sing v0.7.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.5.1 h1:o+mX/schfy6fbbU2rnb6ouUYOL+iUBjA4jOZqyIvDsU= -github.com/sagernet/sing-quic v0.5.1/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI= +github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM= +github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= From c331ee3d5cdfdba94415864e6418b1c1c0192f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 10 Sep 2025 17:40:01 +0800 Subject: [PATCH 48/63] Fix timeout check --- dns/transport/https.go | 3 +-- route/route.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dns/transport/https.go b/dns/transport/https.go index f02fd330..7d56f45e 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -8,7 +8,6 @@ import ( "net" "net/http" "net/url" - "os" "strconv" "sync" "time" @@ -178,7 +177,7 @@ func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS startAt := time.Now() response, err := t.exchange(ctx, message) if err != nil { - if errors.Is(err, os.ErrDeadlineExceeded) { + if errors.Is(err, context.DeadlineExceeded) { t.transportAccess.Lock() defer t.transportAccess.Unlock() if t.transportResetAt.After(startAt) { diff --git a/route/route.go b/route/route.go index ef30a0b1..0213f354 100644 --- a/route/route.go +++ b/route/route.go @@ -5,7 +5,6 @@ import ( "errors" "net" "net/netip" - "os" "strings" "time" @@ -628,7 +627,7 @@ func (r *Router) actionSniff( } if err != nil { sniffBuffer.Release() - if !errors.Is(err, os.ErrDeadlineExceeded) { + if !errors.Is(err, context.DeadlineExceeded) { fatalErr = err return } From 59ee7be72a5f54df799dd2564b2563ae134fd464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 10 Sep 2025 22:46:35 +0800 Subject: [PATCH 49/63] Fix SyscallVectorisedPacketWriter --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4dd02300..5ca3d4a8 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.7.7 + github.com/sagernet/sing v0.7.8 github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 2876d18a..3d0f8bc5 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.7 h1:o46FzVZS+wKbBMEkMEdEHoVZxyM9jvfRpKXc7pEgS/c= -github.com/sagernet/sing v0.7.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.8 h1:i3JBTzeEOqMRtYcyNV17LKvxkb3mr2Y/omM5ldvhCYo= +github.com/sagernet/sing v0.7.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM= From 1af83e997d500648f3c82fd3eae4e1b23ce1329c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 8 Sep 2025 15:53:29 +0800 Subject: [PATCH 50/63] Update Go to 1.25.1 --- .github/workflows/build.yml | 10 +++++----- .github/workflows/linux.yml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25114dff..c8bdb5a2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.0 + go-version: ^1.25.1 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -110,7 +110,7 @@ jobs: if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }} uses: actions/setup-go@v5 with: - go-version: ^1.25.0 + go-version: ^1.25.1 - name: Setup Go 1.24 if: matrix.legacy_go124 uses: actions/setup-go@v5 @@ -300,7 +300,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.0 + go-version: ^1.25.1 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -380,7 +380,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.0 + go-version: ^1.25.1 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -478,7 +478,7 @@ jobs: if: matrix.if uses: actions/setup-go@v5 with: - go-version: ^1.25.0 + go-version: ^1.25.1 - name: Setup Xcode stable if: matrix.if && github.ref == 'refs/heads/main-next' run: |- diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a705d7ff..b43cfb39 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.0 + go-version: ^1.25.1 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -71,7 +71,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.0 + go-version: ^1.25.1 - name: Setup Android NDK if: matrix.os == 'android' uses: nttld/setup-ndk@v1 From fcde0c94e01f990b5d67c16a2382032f784a6ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 10 Sep 2025 22:42:56 +0800 Subject: [PATCH 51/63] Bump version --- clients/android | 2 +- docs/changelog.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 9d71ede0..6295dde5 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 9d71ede01a1a51bb459847bb5d4444b2846c77de +Subproject commit 6295dde5b36801a24452ecb65b9c5e02e3de2305 diff --git a/docs/changelog.md b/docs/changelog.md index c1a24824..9f2b1cea 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +#### 1.12.5 + +* Fixes and improvements + #### 1.12.4 * Fixes and improvements From e42b818c2a5b8a27a453981f2c12073436b7da73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 11 Sep 2025 20:57:57 +0800 Subject: [PATCH 52/63] Fix dhcp fetch --- dns/transport/dhcp/dhcp.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/dns/transport/dhcp/dhcp.go b/dns/transport/dhcp/dhcp.go index b56a60e7..f55d547e 100644 --- a/dns/transport/dhcp/dhcp.go +++ b/dns/transport/dhcp/dhcp.go @@ -2,10 +2,13 @@ package dhcp import ( "context" + "errors" + "io" "net" "runtime" "strings" "sync" + "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -195,7 +198,17 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) if runtime.GOOS == "linux" || runtime.GOOS == "android" { listenAddr = "255.255.255.255:68" } - packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr) + var ( + packetConn net.PacketConn + err error + ) + for i := 0; i < 5; i++ { + packetConn, err = listener.ListenPacket(t.ctx, "udp4", listenAddr) + if err == nil || !errors.Is(err, syscall.EADDRINUSE) { + break + } + time.Sleep(time.Second) + } if err != nil { return err } @@ -232,6 +245,9 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne for { _, _, err := buffer.ReadPacketFrom(packetConn) if err != nil { + if errors.Is(err, io.ErrShortBuffer) { + continue + } return err } From de13137418a7a3217a7f6f37f6d3c9e261e74da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 12 Sep 2025 11:04:12 +0800 Subject: [PATCH 53/63] Fix auto redirect --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5ca3d4a8..de248fb2 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.7.1-0.20250909064831-29d619807240 + github.com/sagernet/sing-tun v0.7.1 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 diff --git a/go.sum b/go.sum index 3d0f8bc5..3e2a8d83 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.7.1-0.20250909064831-29d619807240 h1:QJrYOLJB4A0ONEl1dmZtcyY9NmY6EOKAx3CblLOb+Y8= -github.com/sagernet/sing-tun v0.7.1-0.20250909064831-29d619807240/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= +github.com/sagernet/sing-tun v0.7.1 h1:bgoe9ixY9G+y8FN5y2szD/0Kr1wQqYuuiCzsXefNDBE= +github.com/sagernet/sing-tun v0.7.1/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= From e81a76fdf9835bb653f26ae54bee66d6dcfd810b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 12 Sep 2025 17:32:27 +0800 Subject: [PATCH 54/63] Fix DNS exchange --- common/certificate/store.go | 6 ++++ common/tls/ech.go | 19 +++-------- common/tls/std_server.go | 53 +++++++++++++++++++++++++------ dns/client.go | 2 +- dns/rcode.go | 1 + dns/transport/dhcp/dhcp_shared.go | 2 +- dns/transport/local/local.go | 2 +- 7 files changed, 58 insertions(+), 27 deletions(-) diff --git a/common/certificate/store.go b/common/certificate/store.go index 34f20019..ee127278 100644 --- a/common/certificate/store.go +++ b/common/certificate/store.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "strings" + "sync" "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" @@ -21,6 +22,7 @@ import ( var _ adapter.CertificateStore = (*Store)(nil) type Store struct { + access sync.RWMutex systemPool *x509.CertPool currentPool *x509.CertPool certificate string @@ -115,10 +117,14 @@ func (s *Store) Close() error { } func (s *Store) Pool() *x509.CertPool { + s.access.RLock() + defer s.access.RUnlock() return s.currentPool } func (s *Store) update() error { + s.access.Lock() + defer s.access.Unlock() var currentPool *x509.CertPool if s.systemPool == nil { currentPool = x509.NewCertPool() diff --git a/common/tls/ech.go b/common/tls/ech.go index 830a8d08..d264de9b 100644 --- a/common/tls/ech.go +++ b/common/tls/ech.go @@ -69,11 +69,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, } else { return E.New("missing ECH keys") } - block, rest := pem.Decode(echKey) - if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { - return E.New("invalid ECH keys pem") - } - echKeys, err := UnmarshalECHKeys(block.Bytes) + echKeys, err := parseECHKeys(echKey) if err != nil { return E.Cause(err, "parse ECH keys") } @@ -85,21 +81,16 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, return nil } -func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error { - echKey, err := os.ReadFile(echKeyPath) - if err != nil { - return E.Cause(err, "reload ECH keys from ", echKeyPath) - } +func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) { block, _ := pem.Decode(echKey) if block == nil || block.Type != "ECH KEYS" { - return E.New("invalid ECH keys pem") + return nil, E.New("invalid ECH keys pem") } echKeys, err := UnmarshalECHKeys(block.Bytes) if err != nil { - return E.Cause(err, "parse ECH keys") + return nil, E.Cause(err, "parse ECH keys") } - tlsConfig.EncryptedClientHelloKeys = echKeys - return nil + return echKeys, nil } type ECHClientConfig struct { diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 82ba71ed..541818fa 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -6,6 +6,7 @@ import ( "net" "os" "strings" + "sync" "time" "github.com/sagernet/fswatch" @@ -20,6 +21,7 @@ import ( var errInsecureUnused = E.New("tls: insecure unused") type STDServerConfig struct { + access sync.RWMutex config *tls.Config logger log.Logger acmeService adapter.SimpleLifecycle @@ -32,14 +34,22 @@ type STDServerConfig struct { } func (c *STDServerConfig) ServerName() string { + c.access.RLock() + defer c.access.RUnlock() return c.config.ServerName } func (c *STDServerConfig) SetServerName(serverName string) { - c.config.ServerName = serverName + c.access.Lock() + defer c.access.Unlock() + config := c.config.Clone() + config.ServerName = serverName + c.config = config } func (c *STDServerConfig) NextProtos() []string { + c.access.RLock() + defer c.access.RUnlock() if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol { return c.config.NextProtos[1:] } else { @@ -48,11 +58,15 @@ func (c *STDServerConfig) NextProtos() []string { } func (c *STDServerConfig) SetNextProtos(nextProto []string) { + c.access.Lock() + defer c.access.Unlock() + config := c.config.Clone() if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol { - c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...) + config.NextProtos = append(c.config.NextProtos[:1], nextProto...) } else { - c.config.NextProtos = nextProto + config.NextProtos = nextProto } + c.config = config } func (c *STDServerConfig) Config() (*STDConfig, error) { @@ -77,9 +91,6 @@ func (c *STDServerConfig) Start() error { if c.acmeService != nil { return c.acmeService.Start() } else { - if c.certificatePath == "" && c.keyPath == "" { - return nil - } err := c.startWatcher() if err != nil { c.logger.Warn("create fsnotify watcher: ", err) @@ -99,6 +110,9 @@ func (c *STDServerConfig) startWatcher() error { if c.echKeyPath != "" { watchPath = append(watchPath, c.echKeyPath) } + if len(watchPath) == 0 { + return nil + } watcher, err := fswatch.NewWatcher(fswatch.Options{ Path: watchPath, Callback: func(path string) { @@ -138,13 +152,26 @@ func (c *STDServerConfig) certificateUpdated(path string) error { if err != nil { return E.Cause(err, "reload key pair") } - c.config.Certificates = []tls.Certificate{keyPair} + c.access.Lock() + config := c.config.Clone() + config.Certificates = []tls.Certificate{keyPair} + c.config = config + c.access.Unlock() c.logger.Info("reloaded TLS certificate") } else if path == c.echKeyPath { - err := reloadECHKeys(c.echKeyPath, c.config) + echKey, err := os.ReadFile(c.echKeyPath) + if err != nil { + return E.Cause(err, "reload ECH keys from ", c.echKeyPath) + } + echKeys, err := parseECHKeys(echKey) if err != nil { return err } + c.access.Lock() + config := c.config.Clone() + config.EncryptedClientHelloKeys = echKeys + c.config = config + c.access.Unlock() c.logger.Info("reloaded ECH keys") } return nil @@ -262,7 +289,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound return nil, err } } - return &STDServerConfig{ + serverConfig := &STDServerConfig{ config: tlsConfig, logger: logger, acmeService: acmeService, @@ -271,5 +298,11 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound certificatePath: options.CertificatePath, keyPath: options.KeyPath, echKeyPath: echKeyPath, - }, nil + } + serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) { + serverConfig.access.Lock() + defer serverConfig.access.Unlock() + return serverConfig.config, nil + } + return serverConfig, nil } diff --git a/dns/client.go b/dns/client.go index 0d0e712b..6063b1c6 100644 --- a/dns/client.go +++ b/dns/client.go @@ -280,7 +280,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m } } logExchangedResponse(c.logger, ctx, response, timeToLive) - return response, err + return response, nil } func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) { diff --git a/dns/rcode.go b/dns/rcode.go index 08545474..59c564b6 100644 --- a/dns/rcode.go +++ b/dns/rcode.go @@ -5,6 +5,7 @@ import ( ) const ( + RcodeSuccess RcodeError = mDNS.RcodeSuccess RcodeFormatError RcodeError = mDNS.RcodeFormatError RcodeNameError RcodeError = mDNS.RcodeNameError RcodeRefused RcodeError = mDNS.RcodeRefused diff --git a/dns/transport/dhcp/dhcp_shared.go b/dns/transport/dhcp/dhcp_shared.go index 3d8d5512..086d1d68 100644 --- a/dns/transport/dhcp/dhcp_shared.go +++ b/dns/transport/dhcp/dhcp_shared.go @@ -43,7 +43,7 @@ func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr, if response.Rcode != mDNS.RcodeSuccess { err = dns.RcodeError(response.Rcode) } else if len(dns.MessageToAddresses(response)) == 0 { - err = E.New(fqdn, ": empty result") + err = dns.RcodeSuccess } } select { diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index 7c26b936..8187681a 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -95,7 +95,7 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi if response.Rcode != mDNS.RcodeSuccess { err = dns.RcodeError(response.Rcode) } else if len(dns.MessageToAddresses(response)) == 0 { - err = E.New(fqdn, ": empty result") + err = dns.RcodeSuccess } } select { From 146383499e23d2e27b065f6577e08c634e19342a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 12 Sep 2025 18:04:44 +0800 Subject: [PATCH 55/63] Fix race codes --- Makefile | 4 ++++ common/tls/ech.go | 13 +++++++++++++ common/tls/ech_stub.go | 4 ++-- common/tls/std_server.go | 7 +------ common/urltest/urltest.go | 6 ++++-- go.mod | 4 ++-- go.sum | 8 ++++---- route/rule/rule_set_local.go | 36 +++++++++++++++++++---------------- route/rule/rule_set_remote.go | 20 +++++++++++-------- 9 files changed, 62 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index 43f93c8c..a98faeac 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,10 @@ build: export GOTOOLCHAIN=local && \ go build $(MAIN_PARAMS) $(MAIN) +race: + export GOTOOLCHAIN=local && \ + go build -race $(MAIN_PARAMS) $(MAIN) + ci_build: export GOTOOLCHAIN=local && \ go build $(PARAMS) $(MAIN) && \ diff --git a/common/tls/ech.go b/common/tls/ech.go index d264de9b..5e9fba6d 100644 --- a/common/tls/ech.go +++ b/common/tls/ech.go @@ -81,6 +81,19 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, return nil } +func (c *STDServerConfig) setECHServerConfig(echKey []byte) error { + echKeys, err := parseECHKeys(echKey) + if err != nil { + return err + } + c.access.Lock() + config := c.config.Clone() + config.EncryptedClientHelloKeys = echKeys + c.config = config + c.access.Unlock() + return nil +} + func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) { block, _ := pem.Decode(echKey) if block == nil || block.Type != "ECH KEYS" { diff --git a/common/tls/ech_stub.go b/common/tls/ech_stub.go index 3b6ffc23..357466c0 100644 --- a/common/tls/ech_stub.go +++ b/common/tls/ech_stub.go @@ -18,6 +18,6 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, return E.New("ECH requires go1.24, please recompile your binary.") } -func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error { - return E.New("ECH requires go1.24, please recompile your binary.") +func (c *STDServerConfig) setECHServerConfig(echKey []byte) error { + panic("unreachable") } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 541818fa..94774179 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -163,15 +163,10 @@ func (c *STDServerConfig) certificateUpdated(path string) error { if err != nil { return E.Cause(err, "reload ECH keys from ", c.echKeyPath) } - echKeys, err := parseECHKeys(echKey) + err = c.setECHServerConfig(echKey) if err != nil { return err } - c.access.Lock() - config := c.config.Clone() - config.EncryptedClientHelloKeys = echKeys - c.config = config - c.access.Unlock() c.logger.Info("reloaded ECH keys") } return nil diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index cfe1e532..ee8624fd 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -47,15 +47,15 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory func (s *HistoryStorage) DeleteURLTestHistory(tag string) { s.access.Lock() delete(s.delayHistory, tag) - s.access.Unlock() s.notifyUpdated() + s.access.Unlock() } func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) { s.access.Lock() s.delayHistory[tag] = history - s.access.Unlock() s.notifyUpdated() + s.access.Unlock() } func (s *HistoryStorage) notifyUpdated() { @@ -69,6 +69,8 @@ func (s *HistoryStorage) notifyUpdated() { } func (s *HistoryStorage) Close() error { + s.access.Lock() + defer s.access.Unlock() s.updateHook = nil return nil } diff --git a/go.mod b/go.mod index de248fb2..71aba29e 100644 --- a/go.mod +++ b/go.mod @@ -27,13 +27,13 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.7.8 + github.com/sagernet/sing v0.7.10 github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.7.1 + github.com/sagernet/sing-tun v0.7.2 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1 diff --git a/go.sum b/go.sum index 3e2a8d83..34aad7a1 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.8 h1:i3JBTzeEOqMRtYcyNV17LKvxkb3mr2Y/omM5ldvhCYo= -github.com/sagernet/sing v0.7.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.10 h1:2yPhZFx+EkyHPH8hXNezgyRSHyGY12CboId7CtwLROw= +github.com/sagernet/sing v0.7.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM= @@ -179,8 +179,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.7.1 h1:bgoe9ixY9G+y8FN5y2szD/0Kr1wQqYuuiCzsXefNDBE= -github.com/sagernet/sing-tun v0.7.1/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= +github.com/sagernet/sing-tun v0.7.2 h1:uJkAZM0KBqIYzrq077QGqdvj/+4i/pMOx6Pnx0jYqAs= +github.com/sagernet/sing-tun v0.7.2/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index 0ef15fc7..bda7c5ab 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -27,16 +27,16 @@ import ( var _ adapter.RuleSet = (*LocalRuleSet)(nil) type LocalRuleSet struct { - ctx context.Context - logger logger.Logger - tag string - rules []adapter.HeadlessRule - metadata adapter.RuleSetMetadata - fileFormat string - watcher *fswatch.Watcher - callbackAccess sync.Mutex - callbacks list.List[adapter.RuleSetUpdateCallback] - refs atomic.Int32 + ctx context.Context + logger logger.Logger + tag string + access sync.RWMutex + rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata + fileFormat string + watcher *fswatch.Watcher + callbacks list.List[adapter.RuleSetUpdateCallback] + refs atomic.Int32 } func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) { @@ -141,11 +141,11 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule) metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule) metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule) + s.access.Lock() s.rules = rules s.metadata = metadata - s.callbackAccess.Lock() callbacks := s.callbacks.Array() - s.callbackAccess.Unlock() + s.access.Unlock() for _, callback := range callbacks { callback(s) } @@ -157,10 +157,14 @@ func (s *LocalRuleSet) PostStart() error { } func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata { + s.access.RLock() + defer s.access.RUnlock() return s.metadata } func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet { + s.access.RLock() + defer s.access.RUnlock() return common.FlatMap(s.rules, extractIPSetFromRule) } @@ -181,14 +185,14 @@ func (s *LocalRuleSet) Cleanup() { } func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { - s.callbackAccess.Lock() - defer s.callbackAccess.Unlock() + s.access.Lock() + defer s.access.Unlock() return s.callbacks.PushBack(callback) } func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { - s.callbackAccess.Lock() - defer s.callbackAccess.Unlock() + s.access.Lock() + defer s.access.Unlock() s.callbacks.Remove(element) } diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 133e575a..73e46f4f 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -40,16 +40,16 @@ type RemoteRuleSet struct { logger logger.ContextLogger outbound adapter.OutboundManager options option.RuleSet - metadata adapter.RuleSetMetadata updateInterval time.Duration dialer N.Dialer + access sync.RWMutex rules []adapter.HeadlessRule + metadata adapter.RuleSetMetadata lastUpdated time.Time lastEtag string updateTicker *time.Ticker cacheFile adapter.CacheFile pauseManager pause.Manager - callbackAccess sync.Mutex callbacks list.List[adapter.RuleSetUpdateCallback] refs atomic.Int32 } @@ -120,10 +120,14 @@ func (s *RemoteRuleSet) PostStart() error { } func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata { + s.access.RLock() + defer s.access.RUnlock() return s.metadata } func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet { + s.access.RLock() + defer s.access.RUnlock() return common.FlatMap(s.rules, extractIPSetFromRule) } @@ -144,14 +148,14 @@ func (s *RemoteRuleSet) Cleanup() { } func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { - s.callbackAccess.Lock() - defer s.callbackAccess.Unlock() + s.access.Lock() + defer s.access.Unlock() return s.callbacks.PushBack(callback) } func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) { - s.callbackAccess.Lock() - defer s.callbackAccess.Unlock() + s.access.Lock() + defer s.access.Unlock() s.callbacks.Remove(element) } @@ -185,13 +189,13 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { return E.Cause(err, "parse rule_set.rules.[", i, "]") } } + s.access.Lock() s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) s.rules = rules - s.callbackAccess.Lock() callbacks := s.callbacks.Array() - s.callbackAccess.Unlock() + s.access.Unlock() for _, callback := range callbacks { callback(s) } From 5d1d1a14564748fdc92bea82717b4dd3f9d76fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 12 Sep 2025 18:49:41 +0800 Subject: [PATCH 56/63] Fix TCP exchange for local/dhcp DNS servers --- dns/transport/dhcp/dhcp_shared.go | 108 ++++++++++++++++------------ dns/transport/local/local.go | 113 ++++++++++++++++++------------ 2 files changed, 131 insertions(+), 90 deletions(-) diff --git a/dns/transport/dhcp/dhcp_shared.go b/dns/transport/dhcp/dhcp_shared.go index 086d1d68..2a6b5773 100644 --- a/dns/transport/dhcp/dhcp_shared.go +++ b/dns/transport/dhcp/dhcp_shared.go @@ -2,12 +2,13 @@ package dhcp import ( "context" + "errors" "math/rand" "strings" - "time" + "syscall" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -83,7 +84,7 @@ func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn server := servers[j] question := message.Question[0] question.Name = fqdn - response, err := t.exchangeOne(ctx, server, question, C.DNSTimeout, false, true) + response, err := t.exchangeOne(ctx, server, question) if err != nil { lastErr = err continue @@ -94,62 +95,77 @@ func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn return nil, E.Cause(lastErr, fqdn) } -func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) { +func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question) (*mDNS.Msg, error) { if server.Port == 0 { server.Port = 53 } - var networks []string - if useTCP { - networks = []string{N.NetworkTCP} - } else { - networks = []string{N.NetworkUDP, N.NetworkTCP} - } request := &mDNS.Msg{ MsgHdr: mDNS.MsgHdr{ Id: uint16(rand.Uint32()), RecursionDesired: true, - AuthenticatedData: ad, + AuthenticatedData: true, }, Question: []mDNS.Question{question}, Compress: true, } request.SetEdns0(buf.UDPBufferSize, false) - buffer := buf.Get(buf.UDPBufferSize) - defer buf.Put(buffer) - for _, network := range networks { - ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout)) - defer cancel() - conn, err := t.dialer.DialContext(ctx, network, server) - if err != nil { - return nil, err - } - defer conn.Close() - if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { - conn.SetDeadline(deadline) - } - rawMessage, err := request.PackBuffer(buffer) - if err != nil { - return nil, E.Cause(err, "pack request") - } - _, err = conn.Write(rawMessage) - if err != nil { - return nil, E.Cause(err, "write request") - } - n, err := conn.Read(buffer) - if err != nil { - return nil, E.Cause(err, "read response") - } - var response mDNS.Msg - err = response.Unpack(buffer[:n]) - if err != nil { - return nil, E.Cause(err, "unpack response") - } - if response.Truncated && network == N.NetworkUDP { - continue - } - return &response, nil + return t.exchangeUDP(ctx, server, request) +} + +func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server) + if err != nil { + return nil, err } - panic("unexpected") + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + conn.SetDeadline(deadline) + } + buffer := buf.Get(1 + request.Len()) + defer buf.Put(buffer) + rawMessage, err := request.PackBuffer(buffer) + if err != nil { + return nil, E.Cause(err, "pack request") + } + _, err = conn.Write(rawMessage) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request) + } + return nil, E.Cause(err, "write request") + } + n, err := conn.Read(buffer) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request) + } + return nil, E.Cause(err, "read response") + } + var response mDNS.Msg + err = response.Unpack(buffer[:n]) + if err != nil { + return nil, E.Cause(err, "unpack response") + } + if response.Truncated { + return t.exchangeTCP(ctx, server, request) + } + return &response, nil +} + +func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + conn.SetDeadline(deadline) + } + err = transport.WriteMessage(conn, 0, request) + if err != nil { + return nil, err + } + return transport.ReadMessage(conn) } func (t *Transport) nameList(name string) []string { diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index 8187681a..ec3baad1 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" "github.com/sagernet/sing-box/dns/transport/hosts" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -151,12 +152,6 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio if server.Port == 0 { server.Port = 53 } - var networks []string - if useTCP { - networks = []string{N.NetworkTCP} - } else { - networks = []string{N.NetworkUDP, N.NetworkTCP} - } request := &mDNS.Msg{ MsgHdr: mDNS.MsgHdr{ Id: uint16(rand.Uint32()), @@ -167,43 +162,73 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio Compress: true, } request.SetEdns0(buf.UDPBufferSize, false) - buffer := buf.Get(buf.UDPBufferSize) - defer buf.Put(buffer) - for _, network := range networks { - ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout)) - defer cancel() - conn, err := t.dialer.DialContext(ctx, network, server) - if err != nil { - return nil, err - } - defer conn.Close() - if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { - conn.SetDeadline(deadline) - } - rawMessage, err := request.PackBuffer(buffer) - if err != nil { - return nil, E.Cause(err, "pack request") - } - _, err = conn.Write(rawMessage) - if err != nil { - if errors.Is(err, syscall.EMSGSIZE) && network == N.NetworkUDP { - continue - } - return nil, E.Cause(err, "write request") - } - n, err := conn.Read(buffer) - if err != nil { - return nil, E.Cause(err, "read response") - } - var response mDNS.Msg - err = response.Unpack(buffer[:n]) - if err != nil { - return nil, E.Cause(err, "unpack response") - } - if response.Truncated && network == N.NetworkUDP { - continue - } - return &response, nil + if !useTCP { + return t.exchangeUDP(ctx, server, request, timeout) + } else { + return t.exchangeTCP(ctx, server, request, timeout) } - panic("unexpected") +} + +func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + newDeadline := time.Now().Add(timeout) + if deadline.After(newDeadline) { + deadline = newDeadline + } + conn.SetDeadline(deadline) + } + buffer := buf.Get(1 + request.Len()) + defer buf.Put(buffer) + rawMessage, err := request.PackBuffer(buffer) + if err != nil { + return nil, E.Cause(err, "pack request") + } + _, err = conn.Write(rawMessage) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request, timeout) + } + return nil, E.Cause(err, "write request") + } + n, err := conn.Read(buffer) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request, timeout) + } + return nil, E.Cause(err, "read response") + } + var response mDNS.Msg + err = response.Unpack(buffer[:n]) + if err != nil { + return nil, E.Cause(err, "unpack response") + } + if response.Truncated { + return t.exchangeTCP(ctx, server, request, timeout) + } + return &response, nil +} + +func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + newDeadline := time.Now().Add(timeout) + if deadline.After(newDeadline) { + deadline = newDeadline + } + conn.SetDeadline(deadline) + } + err = transport.WriteMessage(conn, 0, request) + if err != nil { + return nil, err + } + return transport.ReadMessage(conn) } From 07697bf931d53c2dd5cc5d0ce8785403b0199783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 12 Sep 2025 22:56:40 +0800 Subject: [PATCH 57/63] release: Fix xcode build --- .github/workflows/build.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8bdb5a2..9dcedc5f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -432,7 +432,7 @@ jobs: SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }} build_apple: name: Build Apple clients - runs-on: macos-15 + runs-on: macos-26 needs: - calculate_version strategy: @@ -479,14 +479,6 @@ jobs: uses: actions/setup-go@v5 with: go-version: ^1.25.1 - - name: Setup Xcode stable - if: matrix.if && github.ref == 'refs/heads/main-next' - run: |- - sudo xcode-select -s /Applications/Xcode_16.4.app - - name: Setup Xcode beta - if: matrix.if && github.ref == 'refs/heads/dev-next' - run: |- - sudo xcode-select -s /Applications/Xcode_16.4.app - name: Set tag if: matrix.if run: |- From 0977c5cf73082ad73101859660ee218b1a76856a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 13 Sep 2025 00:07:42 +0800 Subject: [PATCH 58/63] release: Disable Apple platform CI builds, since ``-allowProvisioningUpdates` is broken by Apple --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9dcedc5f..a15c75ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -433,6 +433,7 @@ jobs: build_apple: name: Build Apple clients runs-on: macos-26 + if: false needs: - calculate_version strategy: From 44559fb7b92e50bb4c45002669782cf8ef6d603d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 12 Sep 2025 11:04:37 +0800 Subject: [PATCH 59/63] Bump version --- clients/android | 2 +- docs/changelog.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 6295dde5..82d0b93e 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 6295dde5b36801a24452ecb65b9c5e02e3de2305 +Subproject commit 82d0b93ef63717155a01b6c9973540a436186bfb diff --git a/docs/changelog.md b/docs/changelog.md index 9f2b1cea..ed94cef1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +#### 1.12.6 + +* Fixes and improvements + #### 1.12.5 * Fixes and improvements From 1955002ed8e71c5393c80e6f8f3fbbf698f2e9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 13 Sep 2025 03:03:55 +0800 Subject: [PATCH 60/63] Do not cache DNS responses with empty answers --- dns/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dns/client.go b/dns/client.go index 6063b1c6..585c541e 100644 --- a/dns/client.go +++ b/dns/client.go @@ -214,6 +214,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m response.Answer = append(response.Answer, validResponse.Answer...) } }*/ + disableCache = disableCache || response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 if responseChecker != nil { var rejected bool // TODO: add accept_any rule and support to check response instead of addresses From ae852e0be4b9f837d26f0e2702e30ca8f518851e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 13 Sep 2025 03:04:29 +0800 Subject: [PATCH 61/63] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/android b/clients/android index 82d0b93e..9fe53a95 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 82d0b93ef63717155a01b6c9973540a436186bfb +Subproject commit 9fe53a952e5cbbb081ec534829280e30b8b877f5 diff --git a/clients/apple b/clients/apple index c5734677..e0e928b8 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit c5734677bdfcba5d2d4faf10c4f10475077a82ab +Subproject commit e0e928b8d9d8d9756c2cd72bfa94d01d7873deeb diff --git a/docs/changelog.md b/docs/changelog.md index ed94cef1..9e4d09b3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -#### 1.12.6 +#### 1.12.7 * Fixes and improvements From 510bf05e3639c47c690d26a08086cad561e9ad79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 13 Sep 2025 12:26:48 +0800 Subject: [PATCH 62/63] Fix UDP exchange for local/dhcp DNS servers --- dns/transport/dhcp/dhcp_shared.go | 2 +- dns/transport/local/local.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dns/transport/dhcp/dhcp_shared.go b/dns/transport/dhcp/dhcp_shared.go index 2a6b5773..6aa83361 100644 --- a/dns/transport/dhcp/dhcp_shared.go +++ b/dns/transport/dhcp/dhcp_shared.go @@ -121,7 +121,7 @@ func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { conn.SetDeadline(deadline) } - buffer := buf.Get(1 + request.Len()) + buffer := buf.Get(buf.UDPBufferSize) defer buf.Put(buffer) rawMessage, err := request.PackBuffer(buffer) if err != nil { diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index ec3baad1..4e53586d 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -182,7 +182,7 @@ func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request } conn.SetDeadline(deadline) } - buffer := buf.Get(1 + request.Len()) + buffer := buf.Get(buf.UDPBufferSize) defer buf.Put(buffer) rawMessage, err := request.PackBuffer(buffer) if err != nil { From 573c6179ab4fc1b9ecb8f049299bb1dfe8babc97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 13 Sep 2025 12:33:01 +0800 Subject: [PATCH 63/63] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/android b/clients/android index 9fe53a95..cd8ac376 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 9fe53a952e5cbbb081ec534829280e30b8b877f5 +Subproject commit cd8ac376f66c2dac3f6d7c9b2114fc2ead405acc diff --git a/clients/apple b/clients/apple index e0e928b8..96bee16b 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit e0e928b8d9d8d9756c2cd72bfa94d01d7873deeb +Subproject commit 96bee16bf04eb3216f1a99bcf02224b012511182 diff --git a/docs/changelog.md b/docs/changelog.md index 9e4d09b3..73e4d04a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -#### 1.12.7 +#### 1.12.8 * Fixes and improvements