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..a15c75ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,13 +40,13 @@ 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 uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.1 - 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" } @@ -102,28 +103,33 @@ 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 - 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.1 + - 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 @@ -287,14 +293,14 @@ jobs: - calculate_version steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 submodules: 'recursive' - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.1 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -367,14 +373,14 @@ jobs: - calculate_version steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 submodules: 'recursive' - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.1 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -426,7 +432,8 @@ jobs: SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }} build_apple: name: Build Apple clients - runs-on: macos-15 + runs-on: macos-26 + if: false needs: - calculate_version strategy: @@ -464,7 +471,7 @@ jobs: steps: - name: Checkout if: matrix.if - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 submodules: 'recursive' @@ -472,15 +479,7 @@ jobs: if: matrix.if uses: actions/setup-go@v5 with: - go-version: ^1.24.6 - - 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 + go-version: ^1.25.1 - name: Set tag if: matrix.if run: |- @@ -624,7 +623,7 @@ jobs: - build_apple steps: - name: Checkout - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 - name: Cache ghr @@ -647,7 +646,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 08d54402..0a8cbd1a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,15 +22,15 @@ 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 uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ~1.24.6 - 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 c64398b0..b43cfb39 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 @@ -19,13 +24,13 @@ 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 uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.1 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -60,13 +65,13 @@ 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 uses: actions/setup-go@v5 with: - go-version: ^1.24.6 + go-version: ^1.25.1 - name: Setup Android NDK if: matrix.os == 'android' uses: nttld/setup-ndk@v1 @@ -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 @@ -166,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 @@ -175,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 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/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 diff --git a/Makefile b/Makefile index 3c0c152d..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) && \ @@ -45,7 +49,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 @@ -245,8 +249,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/adapter/inbound.go b/adapter/inbound.go index 39196749..98a152c7 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -59,6 +59,7 @@ type InboundContext struct { Domain string Client string SniffContext any + SnifferNames []string SniffError error // cache @@ -137,8 +138,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/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/adapter/outbound/manager.go b/adapter/outbound/manager.go index 44ac8bc5..0fdeb390 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.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 { + 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 + } + 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/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/box.go b/box.go index 9cc1feb7..9f1e9512 100644 --- a/box.go +++ b/box.go @@ -318,15 +318,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/clients/android b/clients/android index 6db8e06e..cd8ac376 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 6db8e06e8d6c77648e79e3f93bdd41a4d48cc319 +Subproject commit cd8ac376f66c2dac3f6d7c9b2114fc2ead405acc diff --git a/clients/apple b/clients/apple index c5734677..96bee16b 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit c5734677bdfcba5d2d4faf10c4f10475077a82ab +Subproject commit 96bee16bf04eb3216f1a99bcf02224b012511182 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...) 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/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 }) } 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/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/common/dialer/default.go b/common/dialer/default.go index 4b9cf71c..08111625 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) { @@ -89,37 +88,35 @@ 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 { @@ -127,6 +124,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial 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()) } @@ -271,7 +273,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/dialer/tfo.go b/common/dialer/tfo.go index 8ea59ca6..4832c12d 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -8,8 +8,10 @@ import ( "net" "os" "sync" + "sync/atomic" "time" + "github.com/sagernet/sing/common" "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/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) }) }) } 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/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") } diff --git a/common/tls/ech.go b/common/tls/ech.go index f4434604..5e9fba6d 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,29 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, return nil } -func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error { - echKey, err := os.ReadFile(echKeyPath) +func (c *STDServerConfig) setECHServerConfig(echKey []byte) error { + echKeys, err := parseECHKeys(echKey) if err != nil { - return E.Cause(err, "reload ECH keys from ", echKeyPath) + 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" { - 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 { @@ -125,7 +129,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/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/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 +} 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 +} diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 82ba71ed..94774179 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,10 +152,18 @@ 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) + } + err = c.setECHServerConfig(echKey) if err != nil { return err } @@ -262,7 +284,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 +293,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/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 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 } diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index 5f88d496..2b6a94bd 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/dns/client.go b/dns/client.go index 1223759a..585c541e 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" @@ -17,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 ( @@ -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 @@ -114,12 +116,38 @@ 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 { + 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 +155,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 +172,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 @@ -194,15 +214,17 @@ 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 - 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) @@ -259,7 +281,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) { @@ -305,8 +327,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() } } @@ -320,36 +341,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 @@ -390,15 +411,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)) + } } } @@ -517,6 +538,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) { @@ -561,9 +585,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/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/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 ")) diff --git a/dns/transport/dhcp/dhcp.go b/dns/transport/dhcp/dhcp.go index 92dd1f8b..f55d547e 100644 --- a/dns/transport/dhcp/dhcp.go +++ b/dns/transport/dhcp/dhcp.go @@ -2,17 +2,18 @@ package dhcp import ( "context" + "errors" + "io" "net" "runtime" "strings" "sync" + "syscall" "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 +30,7 @@ import ( "github.com/insomniacslk/dhcp/dhcpv4" mDNS "github.com/miekg/dns" + "golang.org/x/exp/slices" ) func RegisterTransport(registry *dns.TransportRegistry) { @@ -45,9 +47,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 +67,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 +108,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 +163,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 +175,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() @@ -171,13 +198,27 @@ 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 } 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 } @@ -204,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 } @@ -223,31 +267,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..6aa83361 --- /dev/null +++ b/dns/transport/dhcp/dhcp_shared.go @@ -0,0 +1,213 @@ +package dhcp + +import ( + "context" + "errors" + "math/rand" + "strings" + "syscall" + + "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" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +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 = dns.RcodeSuccess + } + } + 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) + 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) (*mDNS.Msg, error) { + if server.Port == 0 { + server.Port = 53 + } + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: uint16(rand.Uint32()), + RecursionDesired: true, + AuthenticatedData: true, + }, + Question: []mDNS.Question{question}, + Compress: true, + } + request.SetEdns0(buf.UDPBufferSize, false) + 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 + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + conn.SetDeadline(deadline) + } + buffer := buf.Get(buf.UDPBufferSize) + 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 { + 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") +} 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/dns/transport/local/local.go b/dns/transport/local/local.go index 70b495ca..4e53586d 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -2,12 +2,15 @@ package local import ( "context" + "errors" "math/rand" + "syscall" "time" "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" @@ -93,7 +96,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 { @@ -149,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()), @@ -164,41 +161,74 @@ 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) + if !useTCP { + return t.exchangeUDP(ctx, server, request, timeout) + } else { + return t.exchangeTCP(ctx, server, request, timeout) + } +} + +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(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 + rawMessage, err := request.PackBuffer(buffer) + if err != nil { + return nil, E.Cause(err, "pack request") } - panic("unexpected") + _, 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) } diff --git a/dns/transport/local/resolv.go b/dns/transport/local/resolv.go index 754d4539..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{} @@ -104,7 +99,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 +113,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/docs/changelog.md b/docs/changelog.md index e54a9d17..73e4d04a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,26 @@ icon: material/alert-decagram --- +#### 1.12.8 + +* Fixes and improvements + +#### 1.12.5 + +* Fixes and improvements + +#### 1.12.4 + +* Fixes and improvements + +#### 1.12.3 + +* Fixes and improvements + +#### 1.12.2 + +* Fixes and improvements + #### 1.12.1 * Fixes and improvements 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)地址段列表。 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 diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index 757ffdf9..bb4822df 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" + "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/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/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/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/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/experimental/libbox/service.go b/experimental/libbox/service.go index 55228651..734c9c72 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 { diff --git a/experimental/v2rayapi/stats.go b/experimental/v2rayapi/stats.go index 6c44518f..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" @@ -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) { @@ -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/go.mod b/go.mod index d19ea379..bb642c81 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,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 @@ -28,20 +28,19 @@ 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 - github.com/sagernet/sing-dns v0.0.0-00010101000000-000000000000 - github.com/sagernet/sing-mux v0.3.2 - github.com/sagernet/sing-quic v0.5.0-beta.3 + 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.0-beta.1 - github.com/sagernet/sing-vmess v0.2.6 + 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-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 @@ -50,12 +49,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/time v0.11.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 @@ -142,7 +140,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/term v0.33.0 // indirect + golang.org/x/sync v0.16.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.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 a72c4651..e69de29b 100644 --- a/go.sum +++ b/go.sum @@ -1,350 +0,0 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdguardTeam/golibs v0.32.7 h1:3dmGlAVgmvquCCwHsvEl58KKcRAK3z1UnjMnwSIeDH4= -github.com/AdguardTeam/golibs v0.32.7/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= -github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= -github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= -github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= -github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= -github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6Vymc= -github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= -github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= -github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= -github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= -github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= -github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= -github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= -github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= -github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= -github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= -github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= -github.com/enfein/mieru/v3 v3.17.1 h1:pIKbspsKRYNyUrORVI33t1/yz2syaaUkIanskAbGBHY= -github.com/enfein/mieru/v3 v3.17.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= -github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec h1:jXekDkSozctYj5JQlV1mCsIW+qHpIkgSFhOIpgRSB1I= -github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec/go.mod h1:akc2Wh+rX9bFFNnHJGsQ8VIV3eJI1LXJYgx2Y+8lcW8= -github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= -github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= -github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= -github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= -github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= -github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= -github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= -github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= -github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= -github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU= -github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= -github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ= -github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g= -github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8= -github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= -github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU= -github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= -github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -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/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= -github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= -github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= -github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= -github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= -github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= -github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= -github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4= -github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU= -github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= -github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= -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/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= -github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= -github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -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-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= -github.com/sagernet/sing-mux v0.3.2/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= -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= -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-vmess v0.2.6 h1:1c4dGzeGy0kpBXXrT1sgiMZtHhdJylIT8eWrGhJYZec= -github.com/sagernet/sing-vmess v0.2.6/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/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= -github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= -github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTVtJ5jDTsTk5wtIIapZTRg= -github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI= -github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0 h1:/fyqTYciGYTEbDDySW22t9vY5bSr+lCeLZfjVlv9qgw= -github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0/go.mod h1:HWuvH9L2ZwuffhzX5J5ZOjrhLQ8DB5qT9ie/YTXW1nU= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= -github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= -github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= -github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= -github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= -github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c= -github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= -github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= -github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= -github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= -github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= -github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= -go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -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/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/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/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= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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/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/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/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -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/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= -golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= -golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= -golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= -golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= -golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= -howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= -lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= -software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= -software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/option/dns.go b/option/dns.go index 8c2b8d28..959dd318 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/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/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/protocol/group/selector.go b/protocol/group/selector.go index 23526d19..f09ab1d3 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 748dbb1d..7bd87639 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" @@ -194,7 +194,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) { @@ -315,7 +315,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) } @@ -325,7 +325,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 @@ -362,7 +362,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/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 { 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/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/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 } 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/route/network.go b/route/network.go index 090e4c0d..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 @@ -312,7 +311,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 +325,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 +355,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 } diff --git a/route/route.go b/route/route.go index 567fb79d..d9cb5f5e 100644 --- a/route/route.go +++ b/route/route.go @@ -5,7 +5,6 @@ import ( "errors" "net" "net/netip" - "os" "strings" "time" @@ -27,6 +26,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 +346,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{ @@ -456,16 +457,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 @@ -492,7 +493,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") @@ -504,7 +505,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 } @@ -531,6 +532,7 @@ func (r *Router) actionSniff( action.Timeout, streamSniffers..., ) + metadata.SnifferNames = action.SnifferNames metadata.SniffError = err if err == nil { //goland:noinspection GoDeprecation @@ -556,10 +558,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 @@ -574,12 +579,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 @@ -600,12 +630,12 @@ func (r *Router) actionSniff( } if err != nil { sniffBuffer.Release() - if !errors.Is(err, os.ErrDeadlineExceeded) { + if !errors.Is(err, context.DeadlineExceeded) { fatalErr = err return } } else { - if len(packetBuffers) > 0 || metadata.SniffError != nil { + if quicMoreData() { err = sniff.PeekPacket( ctx, metadata, @@ -625,32 +655,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 @@ -678,11 +710,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 } diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 539a6db5..ad02db5c 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -88,7 +88,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() @@ -366,7 +366,7 @@ func (r *RuleActionHijackDNS) String() string { } type RuleActionSniff struct { - snifferNames []string + SnifferNames []string StreamSniffers []sniff.StreamSniffer PacketSniffers []sniff.PacketSniffer Timeout time.Duration @@ -379,7 +379,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) @@ -412,14 +412,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(), ")") } } diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index 4f2fabcc..bda7c5ab 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" @@ -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 16a95bb6..73e46f4f 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" @@ -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) } 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 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/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 23a034c1..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" ) @@ -142,86 +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) } - return } -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() } } 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/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 a1c3e3a6..803d58c5 100644 --- a/transport/v2rayquic/client.go +++ b/transport/v2rayquic/client.go @@ -29,7 +29,7 @@ type Client struct { tlsConfig tls.Config quicConfig *quic.Config connAccess sync.Mutex - conn quic.Connection + conn common.TypedValue[quic.Connection] rawConn net.Conn } @@ -50,13 +50,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 } @@ -72,14 +72,13 @@ 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() return nil, err } - c.conn = quicConn + c.conn.Store(quicConn) c.rawConn = udpConn return quicConn, nil } @@ -99,13 +98,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 { 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 }