mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-08 02:08:48 +08:00
Compare commits
1 Commits
dev-next
...
v1.12.0-rc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
94d9c181d9 |
2
.github/setup_legacy_go.sh
vendored
2
.github/setup_legacy_go.sh
vendored
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.23.12"
|
VERSION="1.23.6"
|
||||||
|
|
||||||
mkdir -p $HOME/go
|
mkdir -p $HOME/go
|
||||||
cd $HOME/go
|
cd $HOME/go
|
||||||
|
60
.github/workflows/build.yml
vendored
60
.github/workflows/build.yml
vendored
@ -40,13 +40,13 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.24.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@ -88,14 +88,13 @@ jobs:
|
|||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
|
|
||||||
- { os: windows, arch: amd64 }
|
- { os: windows, arch: amd64 }
|
||||||
- { os: windows, arch: amd64, legacy_go123: true, legacy_name: "windows-7" }
|
- { os: windows, arch: amd64, legacy_go: true }
|
||||||
- { os: windows, arch: "386" }
|
- { os: windows, arch: "386" }
|
||||||
- { os: windows, arch: "386", legacy_go123: true, legacy_name: "windows-7" }
|
- { os: windows, arch: "386", legacy_go: true }
|
||||||
- { os: windows, arch: arm64 }
|
- { os: windows, arch: arm64 }
|
||||||
|
|
||||||
- { os: darwin, arch: amd64 }
|
- { os: darwin, arch: amd64 }
|
||||||
- { os: darwin, arch: arm64 }
|
- { 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: arm64, ndk: "aarch64-linux-android21" }
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
@ -103,33 +102,28 @@ jobs:
|
|||||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
|
if: ${{ ! matrix.legacy_go }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.24.5
|
||||||
- name: Setup Go 1.24
|
- name: Cache Legacy Go
|
||||||
if: matrix.legacy_go124
|
if: matrix.require_legacy_go
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.24.6
|
|
||||||
- name: Cache Go 1.23
|
|
||||||
if: matrix.legacy_go123
|
|
||||||
id: cache-legacy-go
|
id: cache-legacy-go
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_legacy
|
~/go/go_legacy
|
||||||
key: go_legacy_12312
|
key: go_legacy_1236
|
||||||
- name: Setup Go 1.23
|
- name: Setup Legacy Go
|
||||||
if: matrix.legacy_go123 && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
run: |-
|
run: |-
|
||||||
.github/setup_legacy_go.sh
|
.github/setup_legacy_go.sh
|
||||||
- name: Setup Go 1.23
|
- name: Setup Legacy Go 2
|
||||||
if: matrix.legacy_go123
|
if: matrix.legacy_go
|
||||||
run: |-
|
run: |-
|
||||||
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
||||||
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
||||||
@ -154,7 +148,7 @@ jobs:
|
|||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@ -174,7 +168,7 @@ jobs:
|
|||||||
export CXX="${CC}++"
|
export CXX="${CC}++"
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
@ -190,8 +184,8 @@ jobs:
|
|||||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||||
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
DIR_NAME="${DIR_NAME}-legacy"
|
||||||
fi
|
fi
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
@ -283,7 +277,7 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
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_name && format('-legacy-{0}', matrix.legacy_name) }}
|
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' || '' }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
@ -293,14 +287,14 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.24.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@ -373,14 +367,14 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.24.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@ -470,7 +464,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
@ -478,7 +472,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.24.5
|
||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
@ -630,7 +624,7 @@ jobs:
|
|||||||
- build_apple
|
- build_apple
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
@ -653,7 +647,7 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
echo "ref=$ref"
|
echo "ref=$ref"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@ -107,7 +107,7 @@ jobs:
|
|||||||
echo "latest=$latest"
|
echo "latest=$latest"
|
||||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@ -22,15 +22,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.24.6
|
go-version: ^1.24.5
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v8
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
|
21
.github/workflows/linux.yml
vendored
21
.github/workflows/linux.yml
vendored
@ -7,11 +7,6 @@ on:
|
|||||||
description: "Version name"
|
description: "Version name"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
forceBeta:
|
|
||||||
description: "Force beta"
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@ -24,13 +19,13 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.24.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@ -65,13 +60,13 @@ jobs:
|
|||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.0
|
go-version: ^1.24.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@ -104,11 +99,11 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
- name: Set name
|
- name: Set name
|
||||||
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
|
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
|
||||||
run: |-
|
run: |-
|
||||||
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||||
- name: Set beta name
|
- name: Set beta name
|
||||||
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
|
if: contains(needs.calculate_version.outputs.version, '-')
|
||||||
run: |-
|
run: |-
|
||||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
- name: Set version
|
- name: Set version
|
||||||
@ -171,7 +166,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
@ -180,7 +175,7 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
@ -1,6 +1,27 @@
|
|||||||
version: "2"
|
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
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.24"
|
go: "1.23"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
@ -9,51 +30,7 @@ run:
|
|||||||
- with_utls
|
- with_utls
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
linters:
|
|
||||||
default: none
|
issues:
|
||||||
enable:
|
exclude-dirs:
|
||||||
- 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
|
- 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$
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
@ -15,7 +15,7 @@ RUN set -ex \
|
|||||||
&& go build -v -trimpath -tags \
|
&& go build -v -trimpath -tags \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
8
Makefile
8
Makefile
@ -6,7 +6,7 @@ GOHOSTOS = $(shell go env GOHOSTOS)
|
|||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
@ -45,7 +45,7 @@ lint:
|
|||||||
GOOS=freebsd golangci-lint run ./...
|
GOOS=freebsd golangci-lint run ./...
|
||||||
|
|
||||||
lint_install:
|
lint_install:
|
||||||
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
@go run ./cmd/internal/protogen
|
@go run ./cmd/internal/protogen
|
||||||
@ -245,8 +245,8 @@ lib:
|
|||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.7
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.7
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
@ -57,7 +57,6 @@ type InboundContext struct {
|
|||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SnifferNames []string
|
|
||||||
SniffError error
|
SniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
@ -136,7 +135,8 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|||||||
|
|
||||||
func OverrideContext(ctx context.Context) context.Context {
|
func OverrideContext(ctx context.Context) context.Context {
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
newMetadata := *metadata
|
var newMetadata InboundContext
|
||||||
|
newMetadata = *metadata
|
||||||
return WithContext(ctx, &newMetadata)
|
return WithContext(ctx, &newMetadata)
|
||||||
}
|
}
|
||||||
return ctx
|
return ctx
|
||||||
|
@ -20,7 +20,6 @@ type NetworkManager interface {
|
|||||||
DefaultOptions() NetworkOptions
|
DefaultOptions() NetworkOptions
|
||||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
AutoRedirectOutputMark() uint32
|
AutoRedirectOutputMark() uint32
|
||||||
AutoRedirectOutputMarkFunc() control.Func
|
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
@ -2,12 +2,9 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,17 +18,6 @@ type Outbound interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundWithPreferredRoutes interface {
|
|
||||||
Outbound
|
|
||||||
PreferredDomain(domain string) bool
|
|
||||||
PreferredAddress(address netip.Addr) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type DirectRouteOutbound interface {
|
|
||||||
Outbound
|
|
||||||
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
|
@ -30,7 +30,7 @@ type Manager struct {
|
|||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
dependByTag map[string][]string
|
dependByTag map[string][]string
|
||||||
defaultOutbound adapter.Outbound
|
defaultOutbound adapter.Outbound
|
||||||
defaultOutboundFallback func() (adapter.Outbound, error)
|
defaultOutboundFallback adapter.Outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
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 func() (adapter.Outbound, error)) {
|
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
||||||
m.defaultOutboundFallback = defaultOutboundFallback
|
m.defaultOutboundFallback = defaultOutboundFallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,31 +55,18 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.access.Unlock()
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
m.access.Unlock()
|
|
||||||
return E.New("default outbound not found: ", m.defaultTag)
|
return E.New("default outbound not found: ", m.defaultTag)
|
||||||
}
|
}
|
||||||
m.defaultOutbound = defaultEndpoint
|
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 })...))
|
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||||
} else {
|
} else {
|
||||||
outbounds := m.outbounds
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -200,7 +187,11 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
func (m *Manager) Default() adapter.Outbound {
|
func (m *Manager) Default() adapter.Outbound {
|
||||||
m.access.RLock()
|
m.access.RLock()
|
||||||
defer m.access.RUnlock()
|
defer m.access.RUnlock()
|
||||||
|
if m.defaultOutbound != nil {
|
||||||
return m.defaultOutbound
|
return m.defaultOutbound
|
||||||
|
} else {
|
||||||
|
return m.defaultOutboundFallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
@ -6,10 +6,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@ -21,7 +19,7 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
PreMatch(metadata InboundContext) error
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
|
@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
|||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
return M.Metadata{
|
return M.Metadata{
|
||||||
Source: metadata.Source.Unwrap(),
|
Source: metadata.Source,
|
||||||
Destination: metadata.Destination.Unwrap(),
|
Destination: metadata.Destination,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
box.go
15
box.go
@ -314,23 +314,22 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
outboundManager.Initialize(common.Must1(
|
||||||
return direct.NewOutbound(
|
direct.NewOutbound(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger("outbound/direct"),
|
logFactory.NewLogger("outbound/direct"),
|
||||||
"direct",
|
"direct",
|
||||||
option.DirectOutboundOptions{},
|
option.DirectOutboundOptions{},
|
||||||
)
|
),
|
||||||
})
|
))
|
||||||
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
dnsTransportManager.Initialize(common.Must1(
|
||||||
return local.NewTransport(
|
local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger("dns/local"),
|
logFactory.NewLogger("dns/local"),
|
||||||
"local",
|
"local",
|
||||||
option.LocalDNSServerOptions{},
|
option.LocalDNSServerOptions{},
|
||||||
)
|
)))
|
||||||
})
|
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 9d71ede01a1a51bb459847bb5d4444b2846c77de
|
Subproject commit 7f1fa971e3c7bbc504c2bd455f4e813a562990cb
|
@ -1 +1 @@
|
|||||||
Subproject commit c5734677bdfcba5d2d4faf10c4f10475077a82ab
|
Subproject commit f7883b0f3ec26c449cba26b3b1a692f070f5424d
|
@ -46,7 +46,7 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
macOSTags []string
|
darwinTags []string
|
||||||
memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
notMemcTags []string
|
||||||
debugTags []string
|
debugTags []string
|
||||||
@ -59,11 +59,11 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0")
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||||
macOSTags = append(macOSTags, "with_dhcp")
|
darwinTags = append(darwinTags, "with_dhcp")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
notMemcTags = append(notMemcTags, "with_low_memory")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
@ -107,10 +107,8 @@ func buildAndroid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
// sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
|
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
// debugFlags[1] = debugFlags[1] + " -checklinkname=0"
|
|
||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,9 +158,7 @@ func buildApple() {
|
|||||||
"-tags-not-macos=with_low_memory",
|
"-tags-not-macos=with_low_memory",
|
||||||
}
|
}
|
||||||
if !withTailscale {
|
if !withTailscale {
|
||||||
args = append(args, "-tags-macos="+strings.Join(append(macOSTags, memcTags...), ","))
|
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
||||||
} else {
|
|
||||||
args = append(args, "-tags-macos="+strings.Join(macOSTags, ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@ -171,7 +167,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := sharedTags
|
tags := append(sharedTags, darwinTags...)
|
||||||
if withTailscale {
|
if withTailscale {
|
||||||
tags = append(tags, memcTags...)
|
tags = append(tags, memcTags...)
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ func main0() error {
|
|||||||
|
|
||||||
func runTests() ([]TestResult, error) {
|
func runTests() ([]TestResult, error) {
|
||||||
boxPaths := []string{
|
boxPaths := []string{
|
||||||
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
|
//"/Users/sekai/Downloads/sing-box-1.11.15-darwin-arm64/sing-box",
|
||||||
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
||||||
"./sing-box",
|
"./sing-box",
|
||||||
}
|
}
|
||||||
@ -55,11 +55,11 @@ func runTests() ([]TestResult, error) {
|
|||||||
"system",
|
"system",
|
||||||
}
|
}
|
||||||
mtus := []int{
|
mtus := []int{
|
||||||
1500,
|
// 1500,
|
||||||
4064,
|
// 4064,
|
||||||
// 16384,
|
// 16384,
|
||||||
// 32768,
|
32768,
|
||||||
// 49152,
|
49152,
|
||||||
65535,
|
65535,
|
||||||
}
|
}
|
||||||
flagList := [][]string{
|
flagList := [][]string{
|
||||||
@ -151,7 +151,9 @@ func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags
|
|||||||
var sudoArgs []string
|
var sudoArgs []string
|
||||||
if len(flags) > 0 {
|
if len(flags) > 0 {
|
||||||
sudoArgs = append(sudoArgs, "env")
|
sudoArgs = append(sudoArgs, "env")
|
||||||
sudoArgs = append(sudoArgs, flags...)
|
for _, flag := range flags {
|
||||||
|
sudoArgs = append(sudoArgs, flag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
||||||
boxProcess := shell.Exec("sudo", sudoArgs...)
|
boxProcess := shell.Exec("sudo", sudoArgs...)
|
||||||
@ -180,7 +182,7 @@ func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags
|
|||||||
|
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
args := []string{"-c", testAddress.String()}
|
args := []string{"-c", testAddress.String(), "-t", "5"}
|
||||||
if multiThread {
|
if multiThread {
|
||||||
args = append(args, "-P", "10")
|
args = append(args, "-P", "10")
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -71,7 +69,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options))
|
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
@ -80,18 +78,3 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
|
||||||
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
|
||||||
len(rule.DefaultInterfaceAddress) > 0
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion3
|
|
||||||
}
|
|
||||||
if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion2
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
//go:build go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync/atomic"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RawConn struct {
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
|
|
||||||
IsClient *bool
|
|
||||||
IsHandshakeComplete *atomic.Bool
|
|
||||||
Vers *uint16
|
|
||||||
CipherSuite *uint16
|
|
||||||
|
|
||||||
RawInput *bytes.Buffer
|
|
||||||
Input *bytes.Reader
|
|
||||||
Hand *bytes.Buffer
|
|
||||||
|
|
||||||
CloseNotifySent *bool
|
|
||||||
CloseNotifyErr *error
|
|
||||||
|
|
||||||
In *RawHalfConn
|
|
||||||
Out *RawHalfConn
|
|
||||||
|
|
||||||
BytesSent *int64
|
|
||||||
PacketsSent *int64
|
|
||||||
|
|
||||||
ActiveCall *atomic.Int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) {
|
|
||||||
var (
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
loaded bool
|
|
||||||
)
|
|
||||||
for _, tlsCreator := range methodRegistry {
|
|
||||||
pointer, methods, loaded = tlsCreator(rawTLSConn)
|
|
||||||
if loaded {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !loaded {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &RawConn{
|
|
||||||
pointer: pointer,
|
|
||||||
methods: methods,
|
|
||||||
}
|
|
||||||
|
|
||||||
rawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn))
|
|
||||||
|
|
||||||
rawIsClient := rawConn.FieldByName("isClient")
|
|
||||||
if !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool {
|
|
||||||
return nil, E.New("invalid Conn.isClient")
|
|
||||||
}
|
|
||||||
conn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
|
|
||||||
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.isHandshakeComplete")
|
|
||||||
}
|
|
||||||
conn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawVers := rawConn.FieldByName("vers")
|
|
||||||
if !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("invalid Conn.vers")
|
|
||||||
}
|
|
||||||
conn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCipherSuite := rawConn.FieldByName("cipherSuite")
|
|
||||||
if !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("invalid Conn.cipherSuite")
|
|
||||||
}
|
|
||||||
conn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawRawInput := rawConn.FieldByName("rawInput")
|
|
||||||
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.rawInput")
|
|
||||||
}
|
|
||||||
conn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawInput := rawConn.FieldByName("input")
|
|
||||||
if !rawInput.IsValid() || rawInput.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.input")
|
|
||||||
}
|
|
||||||
conn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawHand := rawConn.FieldByName("hand")
|
|
||||||
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.hand")
|
|
||||||
}
|
|
||||||
conn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
|
|
||||||
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
|
|
||||||
return nil, E.New("invalid Conn.closeNotifySent")
|
|
||||||
}
|
|
||||||
conn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCloseNotifyErr := rawConn.FieldByName("closeNotifyErr")
|
|
||||||
if !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("invalid Conn.closeNotifyErr")
|
|
||||||
}
|
|
||||||
conn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIn := rawConn.FieldByName("in")
|
|
||||||
if !rawIn.IsValid() || rawIn.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.in")
|
|
||||||
}
|
|
||||||
halfIn, err := NewRawHalfConn(rawIn, methods)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "invalid Conn.in")
|
|
||||||
}
|
|
||||||
conn.In = halfIn
|
|
||||||
|
|
||||||
rawOut := rawConn.FieldByName("out")
|
|
||||||
if !rawOut.IsValid() || rawOut.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.out")
|
|
||||||
}
|
|
||||||
halfOut, err := NewRawHalfConn(rawOut, methods)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "invalid Conn.out")
|
|
||||||
}
|
|
||||||
conn.Out = halfOut
|
|
||||||
|
|
||||||
rawBytesSent := rawConn.FieldByName("bytesSent")
|
|
||||||
if !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 {
|
|
||||||
return nil, E.New("invalid Conn.bytesSent")
|
|
||||||
}
|
|
||||||
conn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawPacketsSent := rawConn.FieldByName("packetsSent")
|
|
||||||
if !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 {
|
|
||||||
return nil, E.New("invalid Conn.packetsSent")
|
|
||||||
}
|
|
||||||
conn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawActiveCall := rawConn.FieldByName("activeCall")
|
|
||||||
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.activeCall")
|
|
||||||
}
|
|
||||||
conn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) ReadRecord() error {
|
|
||||||
return c.methods.readRecord(c.pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) HandlePostHandshakeMessage() error {
|
|
||||||
return c.methods.handlePostHandshakeMessage(c.pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) {
|
|
||||||
return c.methods.writeRecordLocked(c.pointer, typ, data)
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
//go:build go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RawHalfConn struct {
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
*sync.Mutex
|
|
||||||
Err *error
|
|
||||||
Version *uint16
|
|
||||||
Cipher *any
|
|
||||||
Seq *[8]byte
|
|
||||||
ScratchBuf *[13]byte
|
|
||||||
TrafficSecret *[]byte
|
|
||||||
Mac *hash.Hash
|
|
||||||
RawKey *[]byte
|
|
||||||
RawIV *[]byte
|
|
||||||
RawMac *[]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) {
|
|
||||||
halfConn := &RawHalfConn{
|
|
||||||
pointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()),
|
|
||||||
methods: methods,
|
|
||||||
}
|
|
||||||
|
|
||||||
rawMutex := rawHalfConn.FieldByName("Mutex")
|
|
||||||
if !rawMutex.IsValid() || rawMutex.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.Mutex")
|
|
||||||
}
|
|
||||||
halfConn.Mutex = (*sync.Mutex)(unsafe.Pointer(rawMutex.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawErr := rawHalfConn.FieldByName("err")
|
|
||||||
if !rawErr.IsValid() || rawErr.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.err")
|
|
||||||
}
|
|
||||||
halfConn.Err = (*error)(unsafe.Pointer(rawErr.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawVersion := rawHalfConn.FieldByName("version")
|
|
||||||
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.version")
|
|
||||||
}
|
|
||||||
halfConn.Version = (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCipher := rawHalfConn.FieldByName("cipher")
|
|
||||||
if !rawCipher.IsValid() || rawCipher.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.cipher")
|
|
||||||
}
|
|
||||||
halfConn.Cipher = (*any)(unsafe.Pointer(rawCipher.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawSeq := rawHalfConn.FieldByName("seq")
|
|
||||||
if !rawSeq.IsValid() || rawSeq.Kind() != reflect.Array || rawSeq.Len() != 8 || rawSeq.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.seq")
|
|
||||||
}
|
|
||||||
halfConn.Seq = (*[8]byte)(unsafe.Pointer(rawSeq.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawScratchBuf := rawHalfConn.FieldByName("scratchBuf")
|
|
||||||
if !rawScratchBuf.IsValid() || rawScratchBuf.Kind() != reflect.Array || rawScratchBuf.Len() != 13 || rawScratchBuf.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.scratchBuf")
|
|
||||||
}
|
|
||||||
halfConn.ScratchBuf = (*[13]byte)(unsafe.Pointer(rawScratchBuf.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawTrafficSecret := rawHalfConn.FieldByName("trafficSecret")
|
|
||||||
if !rawTrafficSecret.IsValid() || rawTrafficSecret.Kind() != reflect.Slice || rawTrafficSecret.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.trafficSecret")
|
|
||||||
}
|
|
||||||
halfConn.TrafficSecret = (*[]byte)(unsafe.Pointer(rawTrafficSecret.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawMac := rawHalfConn.FieldByName("mac")
|
|
||||||
if !rawMac.IsValid() || rawMac.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.mac")
|
|
||||||
}
|
|
||||||
halfConn.Mac = (*hash.Hash)(unsafe.Pointer(rawMac.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawKey := rawHalfConn.FieldByName("rawKey")
|
|
||||||
if rawKey.IsValid() {
|
|
||||||
if /*!rawKey.IsValid() || */ rawKey.Kind() != reflect.Slice || rawKey.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawKey")
|
|
||||||
}
|
|
||||||
halfConn.RawKey = (*[]byte)(unsafe.Pointer(rawKey.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIV := rawHalfConn.FieldByName("rawIV")
|
|
||||||
if !rawIV.IsValid() || rawIV.Kind() != reflect.Slice || rawIV.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawIV")
|
|
||||||
}
|
|
||||||
halfConn.RawIV = (*[]byte)(unsafe.Pointer(rawIV.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawMAC := rawHalfConn.FieldByName("rawMac")
|
|
||||||
if !rawMAC.IsValid() || rawMAC.Kind() != reflect.Slice || rawMAC.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawMac")
|
|
||||||
}
|
|
||||||
halfConn.RawMac = (*[]byte)(unsafe.Pointer(rawMAC.UnsafeAddr()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return halfConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) Decrypt(record []byte) ([]byte, uint8, error) {
|
|
||||||
return hc.methods.decrypt(hc.pointer, record)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) SetErrorLocked(err error) error {
|
|
||||||
return hc.methods.setErrorLocked(hc.pointer, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) SetTrafficSecret(suite unsafe.Pointer, level int, secret []byte) {
|
|
||||||
hc.methods.setTrafficSecret(hc.pointer, suite, level, secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) ExplicitNonceLen() int {
|
|
||||||
return hc.methods.explicitNonceLen(hc.pointer)
|
|
||||||
}
|
|
@ -1,9 +1,18 @@
|
|||||||
//go:build go1.25 && !without_badtls
|
//go:build go1.21 && !without_badtls
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/tls"
|
"github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
@ -12,18 +21,63 @@ var _ N.ReadWaiter = (*ReadWaitConn)(nil)
|
|||||||
|
|
||||||
type ReadWaitConn struct {
|
type ReadWaitConn struct {
|
||||||
tls.Conn
|
tls.Conn
|
||||||
rawConn *RawConn
|
halfAccess *sync.Mutex
|
||||||
|
rawInput *bytes.Buffer
|
||||||
|
input *bytes.Reader
|
||||||
|
hand *bytes.Buffer
|
||||||
readWaitOptions N.ReadWaitOptions
|
readWaitOptions N.ReadWaitOptions
|
||||||
|
tlsReadRecord func() error
|
||||||
|
tlsHandlePostHandshakeMessage func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
||||||
rawConn, err := NewRawConn(conn)
|
var (
|
||||||
if err != nil {
|
loaded bool
|
||||||
return nil, err
|
tlsReadRecord func() error
|
||||||
|
tlsHandlePostHandshakeMessage func() error
|
||||||
|
)
|
||||||
|
for _, tlsCreator := range tlsRegistry {
|
||||||
|
loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn)
|
||||||
|
if loaded {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if !loaded {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||||
|
rawHalfConn := rawConn.FieldByName("in")
|
||||||
|
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid half conn")
|
||||||
|
}
|
||||||
|
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
|
||||||
|
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid half mutex")
|
||||||
|
}
|
||||||
|
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
|
||||||
|
rawRawInput := rawConn.FieldByName("rawInput")
|
||||||
|
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid raw input")
|
||||||
|
}
|
||||||
|
rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
|
||||||
|
rawInput0 := rawConn.FieldByName("input")
|
||||||
|
if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid input")
|
||||||
|
}
|
||||||
|
input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr()))
|
||||||
|
rawHand := rawConn.FieldByName("hand")
|
||||||
|
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid hand")
|
||||||
|
}
|
||||||
|
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
||||||
return &ReadWaitConn{
|
return &ReadWaitConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
rawConn: rawConn,
|
halfAccess: halfAccess,
|
||||||
|
rawInput: rawInput,
|
||||||
|
input: input,
|
||||||
|
hand: hand,
|
||||||
|
tlsReadRecord: tlsReadRecord,
|
||||||
|
tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,36 +87,36 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||||
//err = c.HandshakeContext(context.Background())
|
err = c.HandshakeContext(context.Background())
|
||||||
//if err != nil {
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
c.rawConn.In.Lock()
|
|
||||||
defer c.rawConn.In.Unlock()
|
|
||||||
for c.rawConn.Input.Len() == 0 {
|
|
||||||
err = c.rawConn.ReadRecord()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for c.rawConn.Hand.Len() > 0 {
|
c.halfAccess.Lock()
|
||||||
err = c.rawConn.HandlePostHandshakeMessage()
|
defer c.halfAccess.Unlock()
|
||||||
|
for c.input.Len() == 0 {
|
||||||
|
err = c.tlsReadRecord()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for c.hand.Len() > 0 {
|
||||||
|
err = c.tlsHandlePostHandshakeMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer = c.readWaitOptions.NewBuffer()
|
buffer = c.readWaitOptions.NewBuffer()
|
||||||
n, err := c.rawConn.Input.Read(buffer.FreeBytes())
|
n, err := c.input.Read(buffer.FreeBytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buffer.Release()
|
buffer.Release()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buffer.Truncate(n)
|
buffer.Truncate(n)
|
||||||
|
|
||||||
if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 &&
|
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
|
||||||
// recordType(c.RawInput.Bytes()[0]) == recordTypeAlert {
|
// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
|
||||||
c.rawConn.RawInput.Bytes()[0] == 21 {
|
c.rawInput.Bytes()[0] == 21 {
|
||||||
_ = c.rawConn.ReadRecord()
|
_ = c.tlsReadRecord()
|
||||||
// return n, err // will be io.EOF on closeNotify
|
// return n, err // will be io.EOF on closeNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +128,24 @@ func (c *ReadWaitConn) Upstream() any {
|
|||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ReadWaitConn) ReaderReplaceable() bool {
|
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
||||||
return true
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||||
|
tlsConn, loaded := conn.(*tls.STDConn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
return true, func() error {
|
||||||
|
return stdTLSReadRecord(tlsConn)
|
||||||
|
}, func() error {
|
||||||
|
return stdTLSHandlePostHandshakeMessage(tlsConn)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
||||||
|
func stdTLSReadRecord(c *tls.STDConn) error
|
||||||
|
|
||||||
|
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//go:build !go1.25 || without_badtls
|
//go:build !go1.21 || without_badtls
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
|
32
common/badtls/read_wait_utls.go
Normal file
32
common/badtls/read_wait_utls.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//go:build go1.21 && !without_badtls && with_utls
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return true, func() error {
|
||||||
|
return utlsReadRecord(tlsConn.Conn)
|
||||||
|
}, func() error {
|
||||||
|
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
||||||
|
func utlsReadRecord(c *tls.Conn) error
|
||||||
|
|
||||||
|
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
@ -1,62 +0,0 @@
|
|||||||
//go:build go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Methods struct {
|
|
||||||
readRecord func(c unsafe.Pointer) error
|
|
||||||
handlePostHandshakeMessage func(c unsafe.Pointer) error
|
|
||||||
writeRecordLocked func(c unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
setErrorLocked func(hc unsafe.Pointer, err error) error
|
|
||||||
decrypt func(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
setTrafficSecret func(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
explicitNonceLen func(hc unsafe.Pointer) int
|
|
||||||
}
|
|
||||||
|
|
||||||
var methodRegistry []func(conn net.Conn) (unsafe.Pointer, *Methods, bool)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {
|
|
||||||
tlsConn, loaded := conn.(*tls.Conn)
|
|
||||||
if !loaded {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return unsafe.Pointer(tlsConn), &Methods{
|
|
||||||
readRecord: stdTLSReadRecord,
|
|
||||||
handlePostHandshakeMessage: stdTLSHandlePostHandshakeMessage,
|
|
||||||
writeRecordLocked: stdWriteRecordLocked,
|
|
||||||
|
|
||||||
setErrorLocked: stdSetErrorLocked,
|
|
||||||
decrypt: stdDecrypt,
|
|
||||||
setTrafficSecret: stdSetTrafficSecret,
|
|
||||||
explicitNonceLen: stdExplicitNonceLen,
|
|
||||||
}, true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
|
||||||
func stdTLSReadRecord(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func stdTLSHandlePostHandshakeMessage(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname stdWriteRecordLocked crypto/tls.(*Conn).writeRecordLocked
|
|
||||||
func stdWriteRecordLocked(c unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
//go:linkname stdSetErrorLocked crypto/tls.(*halfConn).setErrorLocked
|
|
||||||
func stdSetErrorLocked(hc unsafe.Pointer, err error) error
|
|
||||||
|
|
||||||
//go:linkname stdDecrypt crypto/tls.(*halfConn).decrypt
|
|
||||||
func stdDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
|
|
||||||
//go:linkname stdSetTrafficSecret crypto/tls.(*halfConn).setTrafficSecret
|
|
||||||
func stdSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
|
|
||||||
//go:linkname stdExplicitNonceLen crypto/tls.(*halfConn).explicitNonceLen
|
|
||||||
func stdExplicitNonceLen(hc unsafe.Pointer) int
|
|
@ -1,56 +0,0 @@
|
|||||||
//go:build go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/metacubex/utls"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {
|
|
||||||
var pointer unsafe.Pointer
|
|
||||||
if uConn, loaded := N.CastReader[*tls.Conn](conn); loaded {
|
|
||||||
pointer = unsafe.Pointer(uConn)
|
|
||||||
} else if uConn, loaded := N.CastReader[*tls.UConn](conn); loaded {
|
|
||||||
pointer = unsafe.Pointer(uConn.Conn)
|
|
||||||
} else {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return pointer, &Methods{
|
|
||||||
readRecord: utlsReadRecord,
|
|
||||||
handlePostHandshakeMessage: utlsHandlePostHandshakeMessage,
|
|
||||||
writeRecordLocked: utlsWriteRecordLocked,
|
|
||||||
|
|
||||||
setErrorLocked: utlsSetErrorLocked,
|
|
||||||
decrypt: utlsDecrypt,
|
|
||||||
setTrafficSecret: utlsSetTrafficSecret,
|
|
||||||
explicitNonceLen: utlsExplicitNonceLen,
|
|
||||||
}, true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
|
||||||
func utlsReadRecord(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func utlsHandlePostHandshakeMessage(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname utlsWriteRecordLocked github.com/metacubex/utls.(*Conn).writeRecordLocked
|
|
||||||
func utlsWriteRecordLocked(hc unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
//go:linkname utlsSetErrorLocked github.com/metacubex/utls.(*halfConn).setErrorLocked
|
|
||||||
func utlsSetErrorLocked(hc unsafe.Pointer, err error) error
|
|
||||||
|
|
||||||
//go:linkname utlsDecrypt github.com/metacubex/utls.(*halfConn).decrypt
|
|
||||||
func utlsDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
|
|
||||||
//go:linkname utlsSetTrafficSecret github.com/metacubex/utls.(*halfConn).setTrafficSecret
|
|
||||||
func utlsSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
|
|
||||||
//go:linkname utlsExplicitNonceLen github.com/metacubex/utls.(*halfConn).explicitNonceLen
|
|
||||||
func utlsExplicitNonceLen(hc unsafe.Pointer) int
|
|
@ -454,5 +454,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
|
|||||||
for len(ruleParts) < 4 {
|
for len(ruleParts) < 4 {
|
||||||
ruleParts = append(ruleParts, 0)
|
ruleParts = append(ruleParts, 0)
|
||||||
}
|
}
|
||||||
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
|
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@ -42,7 +43,7 @@ type DefaultDialer struct {
|
|||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
networkLastFallback common.TypedValue[time.Time]
|
networkLastFallback atomic.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
@ -88,11 +89,12 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
|
if !disableDefaultBind {
|
||||||
if defaultOptions.BindInterface != "" {
|
if defaultOptions.BindInterface != "" {
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
@ -119,16 +121,12 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if networkManager != nil {
|
|
||||||
markFunc := networkManager.AutoRedirectOutputMarkFunc()
|
|
||||||
dialer.Control = control.Append(dialer.Control, markFunc)
|
|
||||||
listener.Control = control.Append(listener.Control, markFunc)
|
|
||||||
}
|
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
@ -273,7 +271,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
} else {
|
} else {
|
||||||
dialer = d.udpDialer4
|
dialer = d.udpDialer4
|
||||||
}
|
}
|
||||||
fastFallback := time.Since(d.networkLastFallback.Load()) < C.TCPTimeout
|
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||||
var (
|
var (
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
isPrimary bool
|
isPrimary bool
|
||||||
@ -315,14 +313,6 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
|
|
||||||
if !destination.Is6() {
|
|
||||||
return dialerFromTCPDialer(d.dialer6)
|
|
||||||
} else {
|
|
||||||
return dialerFromTCPDialer(d.dialer4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
if strategy == nil {
|
if strategy == nil {
|
||||||
strategy = d.networkStrategy
|
strategy = d.networkStrategy
|
||||||
|
@ -111,7 +111,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
dnsQueryOptions.Transport = dnsTransport.Default()
|
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||||
} else if options.NewDialer {
|
} else if options.NewDialer {
|
||||||
return nil, E.New("missing domain resolver for domain server address")
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
} else {
|
} else if !options.DirectOutbound {
|
||||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@ -24,7 +22,7 @@ type slowOpenConn struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
network string
|
network string
|
||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn atomic.Pointer[net.TCPConn]
|
conn net.Conn
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
@ -52,25 +50,22 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn != nil {
|
|
||||||
return conn.Read(b)
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case <-c.create:
|
case <-c.create:
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Load().Read(b)
|
|
||||||
case <-c.done:
|
case <-c.done:
|
||||||
return 0, os.ErrClosed
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return c.conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||||
tcpConn := c.conn.Load()
|
if c.conn != nil {
|
||||||
if tcpConn != nil {
|
return c.conn.Write(b)
|
||||||
return tcpConn.Write(b)
|
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@ -79,7 +74,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Load().Write(b)
|
return c.conn.Write(b)
|
||||||
case <-c.done:
|
case <-c.done:
|
||||||
return 0, os.ErrClosed
|
return 0, os.ErrClosed
|
||||||
default:
|
default:
|
||||||
@ -88,7 +83,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.err = err
|
c.err = err
|
||||||
} else {
|
} else {
|
||||||
c.conn.Store(conn.(*net.TCPConn))
|
c.conn = conn
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@ -98,77 +93,70 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
c.closeOnce.Do(func() {
|
c.closeOnce.Do(func() {
|
||||||
close(c.done)
|
close(c.done)
|
||||||
conn := c.conn.Load()
|
if c.conn != nil {
|
||||||
if conn != nil {
|
c.conn.Close()
|
||||||
conn.Close()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return conn.LocalAddr()
|
return c.conn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return conn.RemoteAddr()
|
return c.conn.RemoteAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return conn.SetDeadline(t)
|
return c.conn.SetDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return conn.SetReadDeadline(t)
|
return c.conn.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return conn.SetWriteDeadline(t)
|
return c.conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Upstream() any {
|
func (c *slowOpenConn) Upstream() any {
|
||||||
return common.PtrOrNil(c.conn.Load())
|
return c.conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) ReaderReplaceable() bool {
|
func (c *slowOpenConn) ReaderReplaceable() bool {
|
||||||
return c.conn.Load() != nil
|
return c.conn != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) WriterReplaceable() bool {
|
func (c *slowOpenConn) WriterReplaceable() bool {
|
||||||
return c.conn.Load() != nil
|
return c.conn != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LazyHeadroom() bool {
|
func (c *slowOpenConn) LazyHeadroom() bool {
|
||||||
return c.conn.Load() == nil
|
return c.conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) NeedHandshake() bool {
|
func (c *slowOpenConn) NeedHandshake() bool {
|
||||||
return c.conn.Load() == nil
|
return c.conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
conn := c.conn.Load()
|
if c.conn == nil {
|
||||||
if conn == nil {
|
|
||||||
select {
|
select {
|
||||||
case <-c.create:
|
case <-c.create:
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
@ -178,5 +166,5 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn.Load())
|
return bufio.Copy(w, c.conn)
|
||||||
}
|
}
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
|
||||||
// C "github.com/sagernet/sing-box/constant"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
aTLS.Conn
|
|
||||||
conn net.Conn
|
|
||||||
rawConn *badtls.RawConn
|
|
||||||
rawSyscallConn syscall.RawConn
|
|
||||||
readWaitOptions N.ReadWaitOptions
|
|
||||||
kernelTx bool
|
|
||||||
kernelRx bool
|
|
||||||
tmp [16]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
|
|
||||||
syscallConn, isSyscallConn := N.CastReader[interface {
|
|
||||||
io.Reader
|
|
||||||
syscall.Conn
|
|
||||||
}](conn.NetConn())
|
|
||||||
if !isSyscallConn {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
rawSyscallConn, err := syscallConn.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawConn, err := badtls.NewRawConn(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if *rawConn.Vers != tls.VersionTLS13 {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
for rawConn.RawInput.Len() > 0 {
|
|
||||||
err = rawConn.ReadRecord()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for rawConn.Hand.Len() > 0 {
|
|
||||||
err = rawConn.HandlePostHandshakeMessage()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "ktls: failed to handle post-handshake messages")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kConn := &Conn{
|
|
||||||
Conn: conn,
|
|
||||||
conn: conn.NetConn(),
|
|
||||||
rawConn: rawConn,
|
|
||||||
rawSyscallConn: rawSyscallConn,
|
|
||||||
}
|
|
||||||
err = kConn.setupKernel(txOffload, rxOffload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return kConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Upstream() any {
|
|
||||||
return c.conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReaderReplaceable() bool {
|
|
||||||
return c.kernelRx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriterReplaceable() bool {
|
|
||||||
return c.kernelTx
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// alert level
|
|
||||||
alertLevelWarning = 1
|
|
||||||
alertLevelError = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
alertCloseNotify = 0
|
|
||||||
alertUnexpectedMessage = 10
|
|
||||||
alertBadRecordMAC = 20
|
|
||||||
alertDecryptionFailed = 21
|
|
||||||
alertRecordOverflow = 22
|
|
||||||
alertDecompressionFailure = 30
|
|
||||||
alertHandshakeFailure = 40
|
|
||||||
alertBadCertificate = 42
|
|
||||||
alertUnsupportedCertificate = 43
|
|
||||||
alertCertificateRevoked = 44
|
|
||||||
alertCertificateExpired = 45
|
|
||||||
alertCertificateUnknown = 46
|
|
||||||
alertIllegalParameter = 47
|
|
||||||
alertUnknownCA = 48
|
|
||||||
alertAccessDenied = 49
|
|
||||||
alertDecodeError = 50
|
|
||||||
alertDecryptError = 51
|
|
||||||
alertExportRestriction = 60
|
|
||||||
alertProtocolVersion = 70
|
|
||||||
alertInsufficientSecurity = 71
|
|
||||||
alertInternalError = 80
|
|
||||||
alertInappropriateFallback = 86
|
|
||||||
alertUserCanceled = 90
|
|
||||||
alertNoRenegotiation = 100
|
|
||||||
alertMissingExtension = 109
|
|
||||||
alertUnsupportedExtension = 110
|
|
||||||
alertCertificateUnobtainable = 111
|
|
||||||
alertUnrecognizedName = 112
|
|
||||||
alertBadCertificateStatusResponse = 113
|
|
||||||
alertBadCertificateHashValue = 114
|
|
||||||
alertUnknownPSKIdentity = 115
|
|
||||||
alertCertificateRequired = 116
|
|
||||||
alertNoApplicationProtocol = 120
|
|
||||||
alertECHRequired = 121
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Conn) sendAlertLocked(err uint8) error {
|
|
||||||
switch err {
|
|
||||||
case alertNoRenegotiation, alertCloseNotify:
|
|
||||||
c.tmp[0] = alertLevelWarning
|
|
||||||
default:
|
|
||||||
c.tmp[0] = alertLevelError
|
|
||||||
}
|
|
||||||
c.tmp[1] = byte(err)
|
|
||||||
|
|
||||||
_, writeErr := c.writeRecordLocked(recordTypeAlert, c.tmp[0:2])
|
|
||||||
if err == alertCloseNotify {
|
|
||||||
// closeNotify is a special case in that it isn't an error.
|
|
||||||
return writeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.rawConn.Out.SetErrorLocked(&net.OpError{Op: "local error", Err: tls.AlertError(err)})
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendAlert sends a TLS alert message.
|
|
||||||
func (c *Conn) sendAlert(err uint8) error {
|
|
||||||
c.rawConn.Out.Lock()
|
|
||||||
defer c.rawConn.Out.Unlock()
|
|
||||||
return c.sendAlertLocked(err)
|
|
||||||
}
|
|
@ -1,326 +0,0 @@
|
|||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
|
||||||
)
|
|
||||||
|
|
||||||
type kernelCryptoCipherType uint16
|
|
||||||
|
|
||||||
const (
|
|
||||||
TLS_CIPHER_AES_GCM_128 kernelCryptoCipherType = 51
|
|
||||||
TLS_CIPHER_AES_GCM_128_IV_SIZE kernelCryptoCipherType = 8
|
|
||||||
TLS_CIPHER_AES_GCM_128_KEY_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_AES_GCM_128_SALT_SIZE kernelCryptoCipherType = 4
|
|
||||||
TLS_CIPHER_AES_GCM_128_TAG_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
|
||||||
|
|
||||||
TLS_CIPHER_AES_GCM_256 kernelCryptoCipherType = 52
|
|
||||||
TLS_CIPHER_AES_GCM_256_IV_SIZE kernelCryptoCipherType = 8
|
|
||||||
TLS_CIPHER_AES_GCM_256_KEY_SIZE kernelCryptoCipherType = 32
|
|
||||||
TLS_CIPHER_AES_GCM_256_SALT_SIZE kernelCryptoCipherType = 4
|
|
||||||
TLS_CIPHER_AES_GCM_256_TAG_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
|
||||||
|
|
||||||
TLS_CIPHER_AES_CCM_128 kernelCryptoCipherType = 53
|
|
||||||
TLS_CIPHER_AES_CCM_128_IV_SIZE kernelCryptoCipherType = 8
|
|
||||||
TLS_CIPHER_AES_CCM_128_KEY_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_AES_CCM_128_SALT_SIZE kernelCryptoCipherType = 4
|
|
||||||
TLS_CIPHER_AES_CCM_128_TAG_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
|
||||||
|
|
||||||
TLS_CIPHER_CHACHA20_POLY1305 kernelCryptoCipherType = 54
|
|
||||||
TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE kernelCryptoCipherType = 12
|
|
||||||
TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE kernelCryptoCipherType = 32
|
|
||||||
TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE kernelCryptoCipherType = 0
|
|
||||||
TLS_CIPHER_CHACHA20_POLY1305_TAG_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
|
||||||
|
|
||||||
// TLS_CIPHER_SM4_GCM kernelCryptoCipherType = 55
|
|
||||||
// TLS_CIPHER_SM4_GCM_IV_SIZE kernelCryptoCipherType = 8
|
|
||||||
// TLS_CIPHER_SM4_GCM_KEY_SIZE kernelCryptoCipherType = 16
|
|
||||||
// TLS_CIPHER_SM4_GCM_SALT_SIZE kernelCryptoCipherType = 4
|
|
||||||
// TLS_CIPHER_SM4_GCM_TAG_SIZE kernelCryptoCipherType = 16
|
|
||||||
// TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
|
||||||
|
|
||||||
// TLS_CIPHER_SM4_CCM kernelCryptoCipherType = 56
|
|
||||||
// TLS_CIPHER_SM4_CCM_IV_SIZE kernelCryptoCipherType = 8
|
|
||||||
// TLS_CIPHER_SM4_CCM_KEY_SIZE kernelCryptoCipherType = 16
|
|
||||||
// TLS_CIPHER_SM4_CCM_SALT_SIZE kernelCryptoCipherType = 4
|
|
||||||
// TLS_CIPHER_SM4_CCM_TAG_SIZE kernelCryptoCipherType = 16
|
|
||||||
// TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
|
||||||
|
|
||||||
TLS_CIPHER_ARIA_GCM_128 kernelCryptoCipherType = 57
|
|
||||||
TLS_CIPHER_ARIA_GCM_128_IV_SIZE kernelCryptoCipherType = 8
|
|
||||||
TLS_CIPHER_ARIA_GCM_128_KEY_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_ARIA_GCM_128_SALT_SIZE kernelCryptoCipherType = 4
|
|
||||||
TLS_CIPHER_ARIA_GCM_128_TAG_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
|
||||||
|
|
||||||
TLS_CIPHER_ARIA_GCM_256 kernelCryptoCipherType = 58
|
|
||||||
TLS_CIPHER_ARIA_GCM_256_IV_SIZE kernelCryptoCipherType = 8
|
|
||||||
TLS_CIPHER_ARIA_GCM_256_KEY_SIZE kernelCryptoCipherType = 32
|
|
||||||
TLS_CIPHER_ARIA_GCM_256_SALT_SIZE kernelCryptoCipherType = 4
|
|
||||||
TLS_CIPHER_ARIA_GCM_256_TAG_SIZE kernelCryptoCipherType = 16
|
|
||||||
TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
type kernelCrypto interface {
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type kernelCryptoInfo struct {
|
|
||||||
version uint16
|
|
||||||
cipher_type kernelCryptoCipherType
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kernelCrypto = &kernelCryptoAES128GCM{}
|
|
||||||
|
|
||||||
type kernelCryptoAES128GCM struct {
|
|
||||||
kernelCryptoInfo
|
|
||||||
iv [TLS_CIPHER_AES_GCM_128_IV_SIZE]byte
|
|
||||||
key [TLS_CIPHER_AES_GCM_128_KEY_SIZE]byte
|
|
||||||
salt [TLS_CIPHER_AES_GCM_128_SALT_SIZE]byte
|
|
||||||
rec_seq [TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (crypto *kernelCryptoAES128GCM) String() string {
|
|
||||||
crypto.cipher_type = TLS_CIPHER_AES_GCM_128
|
|
||||||
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kernelCrypto = &kernelCryptoAES256GCM{}
|
|
||||||
|
|
||||||
type kernelCryptoAES256GCM struct {
|
|
||||||
kernelCryptoInfo
|
|
||||||
iv [TLS_CIPHER_AES_GCM_256_IV_SIZE]byte
|
|
||||||
key [TLS_CIPHER_AES_GCM_256_KEY_SIZE]byte
|
|
||||||
salt [TLS_CIPHER_AES_GCM_256_SALT_SIZE]byte
|
|
||||||
rec_seq [TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (crypto *kernelCryptoAES256GCM) String() string {
|
|
||||||
crypto.cipher_type = TLS_CIPHER_AES_GCM_256
|
|
||||||
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kernelCrypto = &kernelCryptoAES128CCM{}
|
|
||||||
|
|
||||||
type kernelCryptoAES128CCM struct {
|
|
||||||
kernelCryptoInfo
|
|
||||||
iv [TLS_CIPHER_AES_CCM_128_IV_SIZE]byte
|
|
||||||
key [TLS_CIPHER_AES_CCM_128_KEY_SIZE]byte
|
|
||||||
salt [TLS_CIPHER_AES_CCM_128_SALT_SIZE]byte
|
|
||||||
rec_seq [TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (crypto *kernelCryptoAES128CCM) String() string {
|
|
||||||
crypto.cipher_type = TLS_CIPHER_AES_CCM_128
|
|
||||||
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kernelCrypto = &kernelCryptoChacha20Poly1035{}
|
|
||||||
|
|
||||||
type kernelCryptoChacha20Poly1035 struct {
|
|
||||||
kernelCryptoInfo
|
|
||||||
iv [TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE]byte
|
|
||||||
key [TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE]byte
|
|
||||||
salt [TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE]byte
|
|
||||||
rec_seq [TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (crypto *kernelCryptoChacha20Poly1035) String() string {
|
|
||||||
crypto.cipher_type = TLS_CIPHER_CHACHA20_POLY1305
|
|
||||||
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// var _ kernelCrypto = &kernelCryptoSM4GCM{}
|
|
||||||
|
|
||||||
// type kernelCryptoSM4GCM struct {
|
|
||||||
// kernelCryptoInfo
|
|
||||||
// iv [TLS_CIPHER_SM4_GCM_IV_SIZE]byte
|
|
||||||
// key [TLS_CIPHER_SM4_GCM_KEY_SIZE]byte
|
|
||||||
// salt [TLS_CIPHER_SM4_GCM_SALT_SIZE]byte
|
|
||||||
// rec_seq [TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE]byte
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (crypto *kernelCryptoSM4GCM) String() string {
|
|
||||||
// crypto.cipher_type = TLS_CIPHER_SM4_GCM
|
|
||||||
// return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var _ kernelCrypto = &kernelCryptoSM4CCM{}
|
|
||||||
|
|
||||||
// type kernelCryptoSM4CCM struct {
|
|
||||||
// kernelCryptoInfo
|
|
||||||
// iv [TLS_CIPHER_SM4_CCM_IV_SIZE]byte
|
|
||||||
// key [TLS_CIPHER_SM4_CCM_KEY_SIZE]byte
|
|
||||||
// salt [TLS_CIPHER_SM4_CCM_SALT_SIZE]byte
|
|
||||||
// rec_seq [TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE]byte
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (crypto *kernelCryptoSM4CCM) String() string {
|
|
||||||
// crypto.cipher_type = TLS_CIPHER_SM4_CCM
|
|
||||||
// return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
|
||||||
// }
|
|
||||||
|
|
||||||
var _ kernelCrypto = &kernelCryptoARIA128GCM{}
|
|
||||||
|
|
||||||
type kernelCryptoARIA128GCM struct {
|
|
||||||
kernelCryptoInfo
|
|
||||||
iv [TLS_CIPHER_ARIA_GCM_128_IV_SIZE]byte
|
|
||||||
key [TLS_CIPHER_ARIA_GCM_128_KEY_SIZE]byte
|
|
||||||
salt [TLS_CIPHER_ARIA_GCM_128_SALT_SIZE]byte
|
|
||||||
rec_seq [TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (crypto *kernelCryptoARIA128GCM) String() string {
|
|
||||||
crypto.cipher_type = TLS_CIPHER_ARIA_GCM_128
|
|
||||||
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kernelCrypto = &kernelCryptoARIA256GCM{}
|
|
||||||
|
|
||||||
type kernelCryptoARIA256GCM struct {
|
|
||||||
kernelCryptoInfo
|
|
||||||
iv [TLS_CIPHER_ARIA_GCM_256_IV_SIZE]byte
|
|
||||||
key [TLS_CIPHER_ARIA_GCM_256_KEY_SIZE]byte
|
|
||||||
salt [TLS_CIPHER_ARIA_GCM_256_SALT_SIZE]byte
|
|
||||||
rec_seq [TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (crypto *kernelCryptoARIA256GCM) String() string {
|
|
||||||
crypto.cipher_type = TLS_CIPHER_ARIA_GCM_256
|
|
||||||
return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func kernelCipher(kernel *Support, hc *badtls.RawHalfConn, cipherSuite uint16, isRX bool) kernelCrypto {
|
|
||||||
if !kernel.TLS {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch *hc.Version {
|
|
||||||
case tls.VersionTLS12:
|
|
||||||
if isRX && !kernel.TLS_Version13_RX {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case tls.VersionTLS13:
|
|
||||||
if !kernel.TLS_Version13 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if isRX && !kernel.TLS_Version13_RX {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var key, iv []byte
|
|
||||||
if *hc.Version == tls.VersionTLS13 {
|
|
||||||
key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), *hc.TrafficSecret)
|
|
||||||
/*if isRX {
|
|
||||||
key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.RemoteTrafficSecret)
|
|
||||||
} else {
|
|
||||||
key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.TrafficSecret)
|
|
||||||
}*/
|
|
||||||
} else {
|
|
||||||
// csPtr := cipherSuiteByID(cipherSuite)
|
|
||||||
// keysFromMasterSecret(*hc.Version, csPtr, keyLog.Secret, keyLog.Random)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cipherSuite {
|
|
||||||
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
|
|
||||||
crypto := new(kernelCryptoAES128GCM)
|
|
||||||
|
|
||||||
crypto.version = *hc.Version
|
|
||||||
copy(crypto.key[:], key)
|
|
||||||
copy(crypto.iv[:], iv[4:])
|
|
||||||
copy(crypto.salt[:], iv[:4])
|
|
||||||
crypto.rec_seq = *hc.Seq
|
|
||||||
|
|
||||||
return crypto
|
|
||||||
case tls.TLS_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
|
|
||||||
if !kernel.TLS_AES_256_GCM {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
crypto := new(kernelCryptoAES256GCM)
|
|
||||||
|
|
||||||
crypto.version = *hc.Version
|
|
||||||
copy(crypto.key[:], key)
|
|
||||||
copy(crypto.iv[:], iv[4:])
|
|
||||||
copy(crypto.salt[:], iv[:4])
|
|
||||||
crypto.rec_seq = *hc.Seq
|
|
||||||
|
|
||||||
return crypto
|
|
||||||
//case tls.TLS_AES_128_CCM_SHA256, tls.TLS_RSA_WITH_AES_128_CCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_SHA256:
|
|
||||||
// if !kernel.TLS_AES_128_CCM {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// crypto := new(kernelCryptoAES128CCM)
|
|
||||||
//
|
|
||||||
// crypto.version = *hc.Version
|
|
||||||
// copy(crypto.key[:], key)
|
|
||||||
// copy(crypto.iv[:], iv[4:])
|
|
||||||
// copy(crypto.salt[:], iv[:4])
|
|
||||||
// crypto.rec_seq = *hc.Seq
|
|
||||||
//
|
|
||||||
// return crypto
|
|
||||||
case tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
|
|
||||||
if !kernel.TLS_CHACHA20_POLY1305 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
crypto := new(kernelCryptoChacha20Poly1035)
|
|
||||||
|
|
||||||
crypto.version = *hc.Version
|
|
||||||
copy(crypto.key[:], key)
|
|
||||||
copy(crypto.iv[:], iv)
|
|
||||||
crypto.rec_seq = *hc.Seq
|
|
||||||
|
|
||||||
return crypto
|
|
||||||
//case tls.TLS_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:
|
|
||||||
// if !kernel.TLS_ARIA_GCM {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// crypto := new(kernelCryptoARIA128GCM)
|
|
||||||
//
|
|
||||||
// crypto.version = *hc.Version
|
|
||||||
// copy(crypto.key[:], key)
|
|
||||||
// copy(crypto.iv[:], iv[4:])
|
|
||||||
// copy(crypto.salt[:], iv[:4])
|
|
||||||
// crypto.rec_seq = *hc.Seq
|
|
||||||
//
|
|
||||||
// return crypto
|
|
||||||
//case tls.TLS_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:
|
|
||||||
// if !kernel.TLS_ARIA_GCM {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// crypto := new(kernelCryptoARIA256GCM)
|
|
||||||
//
|
|
||||||
// crypto.version = *hc.Version
|
|
||||||
// copy(crypto.key[:], key)
|
|
||||||
// copy(crypto.iv[:], iv[4:])
|
|
||||||
// copy(crypto.salt[:], iv[:4])
|
|
||||||
// crypto.rec_seq = *hc.Seq
|
|
||||||
//
|
|
||||||
// return crypto
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
if !c.kernelTx {
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interlock with Conn.Write above.
|
|
||||||
var x int32
|
|
||||||
for {
|
|
||||||
x = c.rawConn.ActiveCall.Load()
|
|
||||||
if x&1 != 0 {
|
|
||||||
return net.ErrClosed
|
|
||||||
}
|
|
||||||
if c.rawConn.ActiveCall.CompareAndSwap(x, x|1) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if x != 0 {
|
|
||||||
// io.Writer and io.Closer should not be used concurrently.
|
|
||||||
// If Close is called while a Write is currently in-flight,
|
|
||||||
// interpret that as a sign that this Close is really just
|
|
||||||
// being used to break the Write and/or clean up resources and
|
|
||||||
// avoid sending the alertCloseNotify, which may block
|
|
||||||
// waiting on handshakeMutex or the c.out mutex.
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var alertErr error
|
|
||||||
if c.rawConn.IsHandshakeComplete.Load() {
|
|
||||||
if err := c.closeNotify(); err != nil {
|
|
||||||
alertErr = fmt.Errorf("tls: failed to send closeNotify alert (but connection was closed anyway): %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.conn.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return alertErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) closeNotify() error {
|
|
||||||
c.rawConn.Out.Lock()
|
|
||||||
defer c.rawConn.Out.Unlock()
|
|
||||||
|
|
||||||
if !*c.rawConn.CloseNotifySent {
|
|
||||||
// Set a Write Deadline to prevent possibly blocking forever.
|
|
||||||
c.SetWriteDeadline(time.Now().Add(time.Second * 5))
|
|
||||||
*c.rawConn.CloseNotifyErr = c.sendAlertLocked(alertCloseNotify)
|
|
||||||
*c.rawConn.CloseNotifySent = true
|
|
||||||
// Any subsequent writes will fail.
|
|
||||||
c.SetWriteDeadline(time.Now())
|
|
||||||
}
|
|
||||||
return *c.rawConn.CloseNotifyErr
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxPlaintext = 16384 // maximum plaintext payload length
|
|
||||||
maxCiphertext = 16384 + 2048 // maximum ciphertext payload length
|
|
||||||
maxCiphertextTLS13 = 16384 + 256 // maximum ciphertext length in TLS 1.3
|
|
||||||
recordHeaderLen = 5 // record header length
|
|
||||||
maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB)
|
|
||||||
maxHandshakeCertificateMsg = 262144 // maximum certificate message size (256 KiB)
|
|
||||||
maxUselessRecords = 16 // maximum number of consecutive non-advancing records
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
recordTypeChangeCipherSpec = 20
|
|
||||||
recordTypeAlert = 21
|
|
||||||
recordTypeHandshake = 22
|
|
||||||
recordTypeApplicationData = 23
|
|
||||||
)
|
|
@ -1,238 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The marshalingFunction type is an adapter to allow the use of ordinary
|
|
||||||
// functions as cryptobyte.MarshalingValue.
|
|
||||||
type marshalingFunction func(b *cryptobyte.Builder) error
|
|
||||||
|
|
||||||
func (f marshalingFunction) Marshal(b *cryptobyte.Builder) error {
|
|
||||||
return f(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addBytesWithLength appends a sequence of bytes to the cryptobyte.Builder. If
|
|
||||||
// the length of the sequence is not the value specified, it produces an error.
|
|
||||||
func addBytesWithLength(b *cryptobyte.Builder, v []byte, n int) {
|
|
||||||
b.AddValue(marshalingFunction(func(b *cryptobyte.Builder) error {
|
|
||||||
if len(v) != n {
|
|
||||||
return fmt.Errorf("invalid value length: expected %d, got %d", n, len(v))
|
|
||||||
}
|
|
||||||
b.AddBytes(v)
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// addUint64 appends a big-endian, 64-bit value to the cryptobyte.Builder.
|
|
||||||
func addUint64(b *cryptobyte.Builder, v uint64) {
|
|
||||||
b.AddUint32(uint32(v >> 32))
|
|
||||||
b.AddUint32(uint32(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// readUint64 decodes a big-endian, 64-bit value into out and advances over it.
|
|
||||||
// It reports whether the read was successful.
|
|
||||||
func readUint64(s *cryptobyte.String, out *uint64) bool {
|
|
||||||
var hi, lo uint32
|
|
||||||
if !s.ReadUint32(&hi) || !s.ReadUint32(&lo) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
*out = uint64(hi)<<32 | uint64(lo)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a
|
|
||||||
// []byte instead of a cryptobyte.String.
|
|
||||||
func readUint8LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
|
|
||||||
return s.ReadUint8LengthPrefixed((*cryptobyte.String)(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// readUint16LengthPrefixed acts like s.ReadUint16LengthPrefixed, but targets a
|
|
||||||
// []byte instead of a cryptobyte.String.
|
|
||||||
func readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
|
|
||||||
return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// readUint24LengthPrefixed acts like s.ReadUint24LengthPrefixed, but targets a
|
|
||||||
// []byte instead of a cryptobyte.String.
|
|
||||||
func readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
|
|
||||||
return s.ReadUint24LengthPrefixed((*cryptobyte.String)(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyUpdateMsg struct {
|
|
||||||
updateRequested bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *keyUpdateMsg) marshal() ([]byte, error) {
|
|
||||||
var b cryptobyte.Builder
|
|
||||||
b.AddUint8(typeKeyUpdate)
|
|
||||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
||||||
if m.updateRequested {
|
|
||||||
b.AddUint8(1)
|
|
||||||
} else {
|
|
||||||
b.AddUint8(0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *keyUpdateMsg) unmarshal(data []byte) bool {
|
|
||||||
s := cryptobyte.String(data)
|
|
||||||
|
|
||||||
var updateRequested uint8
|
|
||||||
if !s.Skip(4) || // message type and uint24 length field
|
|
||||||
!s.ReadUint8(&updateRequested) || !s.Empty() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch updateRequested {
|
|
||||||
case 0:
|
|
||||||
m.updateRequested = false
|
|
||||||
case 1:
|
|
||||||
m.updateRequested = true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLS handshake message types.
|
|
||||||
const (
|
|
||||||
typeHelloRequest uint8 = 0
|
|
||||||
typeClientHello uint8 = 1
|
|
||||||
typeServerHello uint8 = 2
|
|
||||||
typeNewSessionTicket uint8 = 4
|
|
||||||
typeEndOfEarlyData uint8 = 5
|
|
||||||
typeEncryptedExtensions uint8 = 8
|
|
||||||
typeCertificate uint8 = 11
|
|
||||||
typeServerKeyExchange uint8 = 12
|
|
||||||
typeCertificateRequest uint8 = 13
|
|
||||||
typeServerHelloDone uint8 = 14
|
|
||||||
typeCertificateVerify uint8 = 15
|
|
||||||
typeClientKeyExchange uint8 = 16
|
|
||||||
typeFinished uint8 = 20
|
|
||||||
typeCertificateStatus uint8 = 22
|
|
||||||
typeKeyUpdate uint8 = 24
|
|
||||||
typeCompressedCertificate uint8 = 25
|
|
||||||
typeMessageHash uint8 = 254 // synthetic message
|
|
||||||
)
|
|
||||||
|
|
||||||
// TLS compression types.
|
|
||||||
const (
|
|
||||||
compressionNone uint8 = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
// TLS extension numbers
|
|
||||||
const (
|
|
||||||
extensionServerName uint16 = 0
|
|
||||||
extensionStatusRequest uint16 = 5
|
|
||||||
extensionSupportedCurves uint16 = 10 // supported_groups in TLS 1.3, see RFC 8446, Section 4.2.7
|
|
||||||
extensionSupportedPoints uint16 = 11
|
|
||||||
extensionSignatureAlgorithms uint16 = 13
|
|
||||||
extensionALPN uint16 = 16
|
|
||||||
extensionSCT uint16 = 18
|
|
||||||
extensionPadding uint16 = 21
|
|
||||||
extensionExtendedMasterSecret uint16 = 23
|
|
||||||
extensionCompressCertificate uint16 = 27 // compress_certificate in TLS 1.3
|
|
||||||
extensionSessionTicket uint16 = 35
|
|
||||||
extensionPreSharedKey uint16 = 41
|
|
||||||
extensionEarlyData uint16 = 42
|
|
||||||
extensionSupportedVersions uint16 = 43
|
|
||||||
extensionCookie uint16 = 44
|
|
||||||
extensionPSKModes uint16 = 45
|
|
||||||
extensionCertificateAuthorities uint16 = 47
|
|
||||||
extensionSignatureAlgorithmsCert uint16 = 50
|
|
||||||
extensionKeyShare uint16 = 51
|
|
||||||
extensionQUICTransportParameters uint16 = 57
|
|
||||||
extensionALPS uint16 = 17513
|
|
||||||
extensionRenegotiationInfo uint16 = 0xff01
|
|
||||||
extensionECHOuterExtensions uint16 = 0xfd00
|
|
||||||
extensionEncryptedClientHello uint16 = 0xfe0d
|
|
||||||
)
|
|
||||||
|
|
||||||
type handshakeMessage interface {
|
|
||||||
marshal() ([]byte, error)
|
|
||||||
unmarshal([]byte) bool
|
|
||||||
}
|
|
||||||
type newSessionTicketMsgTLS13 struct {
|
|
||||||
lifetime uint32
|
|
||||||
ageAdd uint32
|
|
||||||
nonce []byte
|
|
||||||
label []byte
|
|
||||||
maxEarlyData uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *newSessionTicketMsgTLS13) marshal() ([]byte, error) {
|
|
||||||
var b cryptobyte.Builder
|
|
||||||
b.AddUint8(typeNewSessionTicket)
|
|
||||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
||||||
b.AddUint32(m.lifetime)
|
|
||||||
b.AddUint32(m.ageAdd)
|
|
||||||
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
||||||
b.AddBytes(m.nonce)
|
|
||||||
})
|
|
||||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
||||||
b.AddBytes(m.label)
|
|
||||||
})
|
|
||||||
|
|
||||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
||||||
if m.maxEarlyData > 0 {
|
|
||||||
b.AddUint16(extensionEarlyData)
|
|
||||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
||||||
b.AddUint32(m.maxEarlyData)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *newSessionTicketMsgTLS13) unmarshal(data []byte) bool {
|
|
||||||
*m = newSessionTicketMsgTLS13{}
|
|
||||||
s := cryptobyte.String(data)
|
|
||||||
|
|
||||||
var extensions cryptobyte.String
|
|
||||||
if !s.Skip(4) || // message type and uint24 length field
|
|
||||||
!s.ReadUint32(&m.lifetime) ||
|
|
||||||
!s.ReadUint32(&m.ageAdd) ||
|
|
||||||
!readUint8LengthPrefixed(&s, &m.nonce) ||
|
|
||||||
!readUint16LengthPrefixed(&s, &m.label) ||
|
|
||||||
!s.ReadUint16LengthPrefixed(&extensions) ||
|
|
||||||
!s.Empty() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for !extensions.Empty() {
|
|
||||||
var extension uint16
|
|
||||||
var extData cryptobyte.String
|
|
||||||
if !extensions.ReadUint16(&extension) ||
|
|
||||||
!extensions.ReadUint16LengthPrefixed(&extData) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch extension {
|
|
||||||
case extensionEarlyData:
|
|
||||||
if !extData.ReadUint32(&m.maxEarlyData) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Ignore unknown extensions.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !extData.Empty() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
@ -1,173 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// handlePostHandshakeMessage processes a handshake message arrived after the
|
|
||||||
// handshake is complete. Up to TLS 1.2, it indicates the start of a renegotiation.
|
|
||||||
func (c *Conn) handlePostHandshakeMessage() error {
|
|
||||||
if *c.rawConn.Vers != tls.VersionTLS13 {
|
|
||||||
return errors.New("ktls: kernel does not support TLS 1.2 renegotiation")
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := c.readHandshake(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//c.retryCount++
|
|
||||||
//if c.retryCount > maxUselessRecords {
|
|
||||||
// c.sendAlert(alertUnexpectedMessage)
|
|
||||||
// return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
|
|
||||||
//}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *newSessionTicketMsgTLS13:
|
|
||||||
// return errors.New("ktls: received new session ticket")
|
|
||||||
return nil
|
|
||||||
case *keyUpdateMsg:
|
|
||||||
return c.handleKeyUpdate(msg)
|
|
||||||
}
|
|
||||||
// The QUIC layer is supposed to treat an unexpected post-handshake CertificateRequest
|
|
||||||
// as a QUIC-level PROTOCOL_VIOLATION error (RFC 9001, Section 4.4). Returning an
|
|
||||||
// unexpected_message alert here doesn't provide it with enough information to distinguish
|
|
||||||
// this condition from other unexpected messages. This is probably fine.
|
|
||||||
c.sendAlert(alertUnexpectedMessage)
|
|
||||||
return fmt.Errorf("tls: received unexpected handshake message of type %T", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {
|
|
||||||
//if c.quic != nil {
|
|
||||||
// c.sendAlert(alertUnexpectedMessage)
|
|
||||||
// return c.in.setErrorLocked(errors.New("tls: received unexpected key update message"))
|
|
||||||
//}
|
|
||||||
|
|
||||||
cipherSuite := cipherSuiteTLS13ByID(*c.rawConn.CipherSuite)
|
|
||||||
if cipherSuite == nil {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertInternalError))
|
|
||||||
}
|
|
||||||
|
|
||||||
newSecret := nextTrafficSecret(cipherSuite, *c.rawConn.In.TrafficSecret)
|
|
||||||
c.rawConn.In.SetTrafficSecret(cipherSuite, 0 /*tls.QUICEncryptionLevelInitial*/, newSecret)
|
|
||||||
|
|
||||||
err := c.resetupRX()
|
|
||||||
if err != nil {
|
|
||||||
c.sendAlert(alertInternalError)
|
|
||||||
return c.rawConn.In.SetErrorLocked(fmt.Errorf("ktls: resetupRX failed: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyUpdate.updateRequested {
|
|
||||||
c.rawConn.Out.Lock()
|
|
||||||
defer c.rawConn.Out.Unlock()
|
|
||||||
|
|
||||||
resetup, err := c.resetupTX()
|
|
||||||
if err != nil {
|
|
||||||
c.sendAlertLocked(alertInternalError)
|
|
||||||
return c.rawConn.Out.SetErrorLocked(fmt.Errorf("ktls: resetupTX failed: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := &keyUpdateMsg{}
|
|
||||||
msgBytes, err := msg.marshal()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = c.writeRecordLocked(recordTypeHandshake, msgBytes)
|
|
||||||
if err != nil {
|
|
||||||
// Surface the error at the next write.
|
|
||||||
c.rawConn.Out.SetErrorLocked(err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newSecret := nextTrafficSecret(cipherSuite, *c.rawConn.Out.TrafficSecret)
|
|
||||||
c.rawConn.Out.SetTrafficSecret(cipherSuite, 0 /*QUICEncryptionLevelInitial*/, newSecret)
|
|
||||||
|
|
||||||
err = resetup()
|
|
||||||
if err != nil {
|
|
||||||
return c.rawConn.Out.SetErrorLocked(fmt.Errorf("ktls: resetupTX failed: %w", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) readHandshakeBytes(n int) error {
|
|
||||||
//if c.quic != nil {
|
|
||||||
// return c.quicReadHandshakeBytes(n)
|
|
||||||
//}
|
|
||||||
for c.rawConn.Hand.Len() < n {
|
|
||||||
if err := c.readRecord(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) readHandshake(transcript io.Writer) (any, error) {
|
|
||||||
if err := c.readHandshakeBytes(4); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := c.rawConn.Hand.Bytes()
|
|
||||||
|
|
||||||
maxHandshakeSize := maxHandshake
|
|
||||||
// hasVers indicates we're past the first message, forcing someone trying to
|
|
||||||
// make us just allocate a large buffer to at least do the initial part of
|
|
||||||
// the handshake first.
|
|
||||||
//if c.haveVers && data[0] == typeCertificate {
|
|
||||||
// Since certificate messages are likely to be the only messages that
|
|
||||||
// can be larger than maxHandshake, we use a special limit for just
|
|
||||||
// those messages.
|
|
||||||
//maxHandshakeSize = maxHandshakeCertificateMsg
|
|
||||||
//}
|
|
||||||
|
|
||||||
n := int(data[1])<<16 | int(data[2])<<8 | int(data[3])
|
|
||||||
if n > maxHandshakeSize {
|
|
||||||
c.sendAlertLocked(alertInternalError)
|
|
||||||
return nil, c.rawConn.In.SetErrorLocked(fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshakeSize))
|
|
||||||
}
|
|
||||||
if err := c.readHandshakeBytes(4 + n); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data = c.rawConn.Hand.Next(4 + n)
|
|
||||||
return c.unmarshalHandshakeMessage(data, transcript)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) unmarshalHandshakeMessage(data []byte, transcript io.Writer) (any, error) {
|
|
||||||
var m handshakeMessage
|
|
||||||
switch data[0] {
|
|
||||||
case typeNewSessionTicket:
|
|
||||||
if *c.rawConn.Vers == tls.VersionTLS13 {
|
|
||||||
m = new(newSessionTicketMsgTLS13)
|
|
||||||
} else {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
case typeKeyUpdate:
|
|
||||||
m = new(keyUpdateMsg)
|
|
||||||
default:
|
|
||||||
return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
// The handshake message unmarshalers
|
|
||||||
// expect to be able to keep references to data,
|
|
||||||
// so pass in a fresh copy that won't be overwritten.
|
|
||||||
data = append([]byte(nil), data...)
|
|
||||||
|
|
||||||
if !m.unmarshal(data) {
|
|
||||||
return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError))
|
|
||||||
}
|
|
||||||
|
|
||||||
if transcript != nil {
|
|
||||||
transcript.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
@ -1,311 +0,0 @@
|
|||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
"github.com/blang/semver/v4"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mod from https://gitlab.com/go-extension/tls
|
|
||||||
|
|
||||||
const (
|
|
||||||
TLS_TX = 1
|
|
||||||
TLS_RX = 2
|
|
||||||
TLS_TX_ZEROCOPY_RO = 3 // TX zerocopy (only sendfile now)
|
|
||||||
TLS_RX_EXPECT_NO_PAD = 4 // Attempt opportunistic zero-copy, TLS 1.3 only
|
|
||||||
|
|
||||||
TLS_SET_RECORD_TYPE = 1
|
|
||||||
TLS_GET_RECORD_TYPE = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type Support struct {
|
|
||||||
TLS, TLS_RX bool
|
|
||||||
TLS_Version13, TLS_Version13_RX bool
|
|
||||||
|
|
||||||
TLS_TX_ZEROCOPY bool
|
|
||||||
TLS_RX_NOPADDING bool
|
|
||||||
|
|
||||||
TLS_AES_256_GCM bool
|
|
||||||
TLS_AES_128_CCM bool
|
|
||||||
TLS_CHACHA20_POLY1305 bool
|
|
||||||
TLS_SM4 bool
|
|
||||||
TLS_ARIA_GCM bool
|
|
||||||
|
|
||||||
TLS_Version13_KeyUpdate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var KernelSupport = sync.OnceValues(func() (*Support, error) {
|
|
||||||
_, err := os.Stat("/sys/module/tls")
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.New("ktls: kernel module tls not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
var uname unix.Utsname
|
|
||||||
err = unix.Uname(&uname)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kernelVersion, err := semver.Parse(strings.Trim(string(uname.Release[:]), "\x00"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
kernelVersion.Pre = nil
|
|
||||||
kernelVersion.Build = nil
|
|
||||||
|
|
||||||
var support Support
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 6, Minor: 14}):
|
|
||||||
support.TLS_Version13_KeyUpdate = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 6, Minor: 1}):
|
|
||||||
support.TLS_ARIA_GCM = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 6}):
|
|
||||||
support.TLS_Version13_RX = true
|
|
||||||
support.TLS_RX_NOPADDING = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 5, Minor: 19}):
|
|
||||||
support.TLS_TX_ZEROCOPY = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 5, Minor: 16}):
|
|
||||||
support.TLS_SM4 = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 5, Minor: 11}):
|
|
||||||
support.TLS_CHACHA20_POLY1305 = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 5, Minor: 2}):
|
|
||||||
support.TLS_AES_128_CCM = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 5, Minor: 1}):
|
|
||||||
support.TLS_AES_256_GCM = true
|
|
||||||
support.TLS_Version13 = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 4, Minor: 17}):
|
|
||||||
support.TLS_RX = true
|
|
||||||
fallthrough
|
|
||||||
case kernelVersion.GTE(semver.Version{Major: 4, Minor: 13}):
|
|
||||||
support.TLS = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return &support, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
func (c *Conn) setupKernel(txOffload, rxOffload bool) error {
|
|
||||||
if !txOffload && !rxOffload {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
support, err := KernelSupport()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !support.TLS {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.rawConn.Out.Lock()
|
|
||||||
defer c.rawConn.Out.Unlock()
|
|
||||||
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
|
||||||
return syscall.SetsockoptString(int(fd), unix.SOL_TCP, unix.TCP_ULP, "tls")
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize kernel TLS")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rxOffload {
|
|
||||||
rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true)
|
|
||||||
if rxCrypto == nil {
|
|
||||||
return E.New("kTLS: unsupported cipher suite")
|
|
||||||
}
|
|
||||||
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
|
||||||
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String())
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if /*config.KernelRXExpectNoPad &&*/ *c.rawConn.Vers >= tls.VersionTLS13 && support.TLS_RX_NOPADDING {
|
|
||||||
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
|
||||||
return syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_RX_EXPECT_NO_PAD, 1)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.kernelRx = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if txOffload {
|
|
||||||
txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false)
|
|
||||||
if txCrypto == nil {
|
|
||||||
return E.New("kTLS: unsupported cipher suite")
|
|
||||||
}
|
|
||||||
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
|
||||||
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String())
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if support.TLS_TX_ZEROCOPY {
|
|
||||||
err = control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
|
||||||
return syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_TX_ZEROCOPY_RO, 1)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.kernelTx = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) resetupTX() (func() error, error) {
|
|
||||||
if !c.kernelTx {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
support, err := KernelSupport()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !support.TLS_Version13_KeyUpdate {
|
|
||||||
return nil, errors.New("ktls: kernel does not support rekey")
|
|
||||||
}
|
|
||||||
txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false)
|
|
||||||
if txCrypto == nil {
|
|
||||||
return nil, errors.New("ktls: set kernelCipher on unsupported tls session")
|
|
||||||
}
|
|
||||||
return func() error {
|
|
||||||
return control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
|
||||||
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String())
|
|
||||||
})
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) resetupRX() error {
|
|
||||||
if !c.kernelRx {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
support, err := KernelSupport()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !support.TLS_Version13_KeyUpdate {
|
|
||||||
return errors.New("ktls: kernel does not support rekey")
|
|
||||||
}
|
|
||||||
rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true)
|
|
||||||
if rxCrypto == nil {
|
|
||||||
return errors.New("ktls: set kernelCipher on unsupported tls session")
|
|
||||||
}
|
|
||||||
return control.Raw(c.rawSyscallConn, func(fd uintptr) error {
|
|
||||||
return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) readKernelRecord() (uint8, []byte, error) {
|
|
||||||
if c.rawConn.RawInput.Len() < maxPlaintext {
|
|
||||||
c.rawConn.RawInput.Grow(maxPlaintext - c.rawConn.RawInput.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
data := c.rawConn.RawInput.Bytes()[:maxPlaintext]
|
|
||||||
|
|
||||||
// cmsg for record type
|
|
||||||
buffer := make([]byte, unix.CmsgSpace(1))
|
|
||||||
cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0]))
|
|
||||||
cmsg.SetLen(unix.CmsgLen(1))
|
|
||||||
|
|
||||||
var iov unix.Iovec
|
|
||||||
iov.Base = &data[0]
|
|
||||||
iov.SetLen(len(data))
|
|
||||||
|
|
||||||
var msg unix.Msghdr
|
|
||||||
msg.Control = &buffer[0]
|
|
||||||
msg.Controllen = cmsg.Len
|
|
||||||
msg.Iov = &iov
|
|
||||||
msg.Iovlen = 1
|
|
||||||
|
|
||||||
var n int
|
|
||||||
var err error
|
|
||||||
er := c.rawSyscallConn.Read(func(fd uintptr) bool {
|
|
||||||
n, err = recvmsg(int(fd), &msg, 0)
|
|
||||||
return err != unix.EAGAIN
|
|
||||||
})
|
|
||||||
if er != nil {
|
|
||||||
return 0, nil, er
|
|
||||||
}
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
case syscall.EINVAL:
|
|
||||||
return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertProtocolVersion))
|
|
||||||
case syscall.EMSGSIZE:
|
|
||||||
return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow))
|
|
||||||
case syscall.EBADMSG:
|
|
||||||
return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecryptError))
|
|
||||||
default:
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n <= 0 {
|
|
||||||
return 0, nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmsg.Level == unix.SOL_TLS && cmsg.Type == TLS_GET_RECORD_TYPE {
|
|
||||||
typ := buffer[unix.CmsgLen(0)]
|
|
||||||
return typ, data[:n], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return recordTypeApplicationData, data[:n], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) writeKernelRecord(typ uint16, data []byte) (int, error) {
|
|
||||||
if typ == recordTypeApplicationData {
|
|
||||||
return c.conn.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmsg for record type
|
|
||||||
buffer := make([]byte, unix.CmsgSpace(1))
|
|
||||||
cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0]))
|
|
||||||
cmsg.SetLen(unix.CmsgLen(1))
|
|
||||||
buffer[unix.CmsgLen(0)] = byte(typ)
|
|
||||||
cmsg.Level = unix.SOL_TLS
|
|
||||||
cmsg.Type = TLS_SET_RECORD_TYPE
|
|
||||||
|
|
||||||
var iov unix.Iovec
|
|
||||||
iov.Base = &data[0]
|
|
||||||
iov.SetLen(len(data))
|
|
||||||
|
|
||||||
var msg unix.Msghdr
|
|
||||||
msg.Control = &buffer[0]
|
|
||||||
msg.Controllen = cmsg.Len
|
|
||||||
msg.Iov = &iov
|
|
||||||
msg.Iovlen = 1
|
|
||||||
|
|
||||||
var n int
|
|
||||||
var err error
|
|
||||||
ew := c.rawSyscallConn.Write(func(fd uintptr) bool {
|
|
||||||
n, err = sendmsg(int(fd), &msg, 0)
|
|
||||||
return err != unix.EAGAIN
|
|
||||||
})
|
|
||||||
if ew != nil {
|
|
||||||
return 0, ew
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname recvmsg golang.org/x/sys/unix.recvmsg
|
|
||||||
func recvmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error)
|
|
||||||
|
|
||||||
//go:linkname sendmsg golang.org/x/sys/unix.sendmsg
|
|
||||||
func sendmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error)
|
|
@ -1,24 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
//go:linkname cipherSuiteByID github.com/metacubex/utls.cipherSuiteByID
|
|
||||||
func cipherSuiteByID(id uint16) unsafe.Pointer
|
|
||||||
|
|
||||||
//go:linkname keysFromMasterSecret github.com/metacubex/utls.keysFromMasterSecret
|
|
||||||
func keysFromMasterSecret(version uint16, suite unsafe.Pointer, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte)
|
|
||||||
|
|
||||||
//go:linkname cipherSuiteTLS13ByID github.com/metacubex/utls.cipherSuiteTLS13ByID
|
|
||||||
func cipherSuiteTLS13ByID(id uint16) unsafe.Pointer
|
|
||||||
|
|
||||||
//go:linkname nextTrafficSecret github.com/metacubex/utls.(*cipherSuiteTLS13).nextTrafficSecret
|
|
||||||
func nextTrafficSecret(cs unsafe.Pointer, trafficSecret []byte) []byte
|
|
||||||
|
|
||||||
//go:linkname trafficKey github.com/metacubex/utls.(*cipherSuiteTLS13).trafficKey
|
|
||||||
func trafficKey(cs unsafe.Pointer, trafficSecret []byte) (key, iv []byte)
|
|
@ -1,292 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Conn) Read(b []byte) (int, error) {
|
|
||||||
if !c.kernelRx {
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) == 0 {
|
|
||||||
// Put this after Handshake, in case people were calling
|
|
||||||
// Read(nil) for the side effect of the Handshake.
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.rawConn.In.Lock()
|
|
||||||
defer c.rawConn.In.Unlock()
|
|
||||||
|
|
||||||
for c.rawConn.Input.Len() == 0 {
|
|
||||||
if err := c.readRecord(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
for c.rawConn.Hand.Len() > 0 {
|
|
||||||
if err := c.handlePostHandshakeMessage(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n, _ := c.rawConn.Input.Read(b)
|
|
||||||
|
|
||||||
// If a close-notify alert is waiting, read it so that we can return (n,
|
|
||||||
// EOF) instead of (n, nil), to signal to the HTTP response reading
|
|
||||||
// goroutine that the connection is now closed. This eliminates a race
|
|
||||||
// where the HTTP response reading goroutine would otherwise not observe
|
|
||||||
// the EOF until its next read, by which time a client goroutine might
|
|
||||||
// have already tried to reuse the HTTP connection for a new request.
|
|
||||||
// See https://golang.org/cl/76400046 and https://golang.org/issue/3514
|
|
||||||
if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.RawInput.Len() > 0 &&
|
|
||||||
c.rawConn.RawInput.Bytes()[0] == recordTypeAlert {
|
|
||||||
if err := c.readRecord(); err != nil {
|
|
||||||
return n, err // will be io.EOF on closeNotify
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) readRecord() error {
|
|
||||||
if *c.rawConn.In.Err != nil {
|
|
||||||
return *c.rawConn.In.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
typ, data, err := c.readRawRecord()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data) > maxPlaintext {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Application Data messages are always protected.
|
|
||||||
if c.rawConn.In.Cipher == nil && typ == recordTypeApplicationData {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
//if typ != recordTypeAlert && typ != recordTypeChangeCipherSpec && len(data) > 0 {
|
|
||||||
// This is a state-advancing message: reset the retry count.
|
|
||||||
// c.retryCount = 0
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Handshake messages MUST NOT be interleaved with other record types in TLS 1.3.
|
|
||||||
if *c.rawConn.Vers == tls.VersionTLS13 && typ != recordTypeHandshake && c.rawConn.Hand.Len() > 0 {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch typ {
|
|
||||||
default:
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
case recordTypeAlert:
|
|
||||||
//if c.quic != nil {
|
|
||||||
// return c.rawConn.In.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
//}
|
|
||||||
if len(data) != 2 {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
}
|
|
||||||
if data[1] == alertCloseNotify {
|
|
||||||
return c.rawConn.In.SetErrorLocked(io.EOF)
|
|
||||||
}
|
|
||||||
if *c.rawConn.Vers == tls.VersionTLS13 {
|
|
||||||
// TLS 1.3 removed warning-level alerts except for alertUserCanceled
|
|
||||||
// (RFC 8446, § 6.1). Since at least one major implementation
|
|
||||||
// (https://bugs.openjdk.org/browse/JDK-8323517) misuses this alert,
|
|
||||||
// many TLS stacks now ignore it outright when seen in a TLS 1.3
|
|
||||||
// handshake (e.g. BoringSSL, NSS, Rustls).
|
|
||||||
if data[1] == alertUserCanceled {
|
|
||||||
// Like TLS 1.2 alertLevelWarning alerts, we drop the record and retry.
|
|
||||||
return c.retryReadRecord( /*expectChangeCipherSpec*/ )
|
|
||||||
}
|
|
||||||
return c.rawConn.In.SetErrorLocked(&net.OpError{Op: "remote error", Err: tls.AlertError(data[1])})
|
|
||||||
}
|
|
||||||
switch data[0] {
|
|
||||||
case alertLevelWarning:
|
|
||||||
// Drop the record on the floor and retry.
|
|
||||||
return c.retryReadRecord( /*expectChangeCipherSpec*/ )
|
|
||||||
case alertLevelError:
|
|
||||||
return c.rawConn.In.SetErrorLocked(&net.OpError{Op: "remote error", Err: tls.AlertError(data[1])})
|
|
||||||
default:
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
case recordTypeChangeCipherSpec:
|
|
||||||
if len(data) != 1 || data[0] != 1 {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError))
|
|
||||||
}
|
|
||||||
// Handshake messages are not allowed to fragment across the CCS.
|
|
||||||
if c.rawConn.Hand.Len() > 0 {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
}
|
|
||||||
// In TLS 1.3, change_cipher_spec records are ignored until the
|
|
||||||
// Finished. See RFC 8446, Appendix D.4. Note that according to Section
|
|
||||||
// 5, a server can send a ChangeCipherSpec before its ServerHello, when
|
|
||||||
// c.vers is still unset. That's not useful though and suspicious if the
|
|
||||||
// server then selects a lower protocol version, so don't allow that.
|
|
||||||
if *c.rawConn.Vers == tls.VersionTLS13 {
|
|
||||||
return c.retryReadRecord( /*expectChangeCipherSpec*/ )
|
|
||||||
}
|
|
||||||
// if !expectChangeCipherSpec {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
//}
|
|
||||||
//if err := c.rawConn.In.changeCipherSpec(); err != nil {
|
|
||||||
// return c.rawConn.In.setErrorLocked(c.sendAlert(err.(alert)))
|
|
||||||
//}
|
|
||||||
|
|
||||||
case recordTypeApplicationData:
|
|
||||||
// Some OpenSSL servers send empty records in order to randomize the
|
|
||||||
// CBC RawIV. Ignore a limited number of empty records.
|
|
||||||
if len(data) == 0 {
|
|
||||||
return c.retryReadRecord( /*expectChangeCipherSpec*/ )
|
|
||||||
}
|
|
||||||
// Note that data is owned by c.rawInput, following the Next call above,
|
|
||||||
// to avoid copying the plaintext. This is safe because c.rawInput is
|
|
||||||
// not read from or written to until c.input is drained.
|
|
||||||
c.rawConn.Input.Reset(data)
|
|
||||||
case recordTypeHandshake:
|
|
||||||
if len(data) == 0 {
|
|
||||||
return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
||||||
}
|
|
||||||
c.rawConn.Hand.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:staticcheck
|
|
||||||
func (c *Conn) readRawRecord() (typ uint8, data []byte, err error) {
|
|
||||||
// Read from kernel.
|
|
||||||
if c.kernelRx {
|
|
||||||
return c.readKernelRecord()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read header, payload.
|
|
||||||
if err = c.readFromUntil(c.conn, recordHeaderLen); err != nil {
|
|
||||||
// RFC 8446, Section 6.1 suggests that EOF without an alertCloseNotify
|
|
||||||
// is an error, but popular web sites seem to do this, so we accept it
|
|
||||||
// if and only if at the record boundary.
|
|
||||||
if err == io.ErrUnexpectedEOF && c.rawConn.RawInput.Len() == 0 {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
if e, ok := err.(net.Error); !ok || !e.Temporary() {
|
|
||||||
c.rawConn.In.SetErrorLocked(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hdr := c.rawConn.RawInput.Bytes()[:recordHeaderLen]
|
|
||||||
typ = hdr[0]
|
|
||||||
|
|
||||||
vers := uint16(hdr[1])<<8 | uint16(hdr[2])
|
|
||||||
expectedVers := *c.rawConn.Vers
|
|
||||||
if expectedVers == tls.VersionTLS13 {
|
|
||||||
// All TLS 1.3 records are expected to have 0x0303 (1.2) after
|
|
||||||
// the initial hello (RFC 8446 Section 5.1).
|
|
||||||
expectedVers = tls.VersionTLS12
|
|
||||||
}
|
|
||||||
n := int(hdr[3])<<8 | int(hdr[4])
|
|
||||||
if /*c.haveVers && */ vers != expectedVers {
|
|
||||||
c.sendAlert(alertProtocolVersion)
|
|
||||||
msg := fmt.Sprintf("received record with version %x when expecting version %x", vers, expectedVers)
|
|
||||||
err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//if !c.haveVers {
|
|
||||||
// // First message, be extra suspicious: this might not be a TLS
|
|
||||||
// // client. Bail out before reading a full 'body', if possible.
|
|
||||||
// // The current max version is 3.3 so if the version is >= 16.0,
|
|
||||||
// // it's probably not real.
|
|
||||||
// if (typ != recordTypeAlert && typ != recordTypeHandshake) || vers >= 0x1000 {
|
|
||||||
// err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(c.conn, "first record does not look like a TLS handshake"))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
if *c.rawConn.Vers == tls.VersionTLS13 && n > maxCiphertextTLS13 || n > maxCiphertext {
|
|
||||||
c.sendAlert(alertRecordOverflow)
|
|
||||||
msg := fmt.Sprintf("oversized record received with length %d", n)
|
|
||||||
err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = c.readFromUntil(c.conn, recordHeaderLen+n); err != nil {
|
|
||||||
if e, ok := err.(net.Error); !ok || !e.Temporary() {
|
|
||||||
c.rawConn.In.SetErrorLocked(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process message.
|
|
||||||
record := c.rawConn.RawInput.Next(recordHeaderLen + n)
|
|
||||||
data, typ, err = c.rawConn.In.Decrypt(record)
|
|
||||||
if err != nil {
|
|
||||||
err = c.rawConn.In.SetErrorLocked(c.sendAlert(uint8(err.(tls.AlertError))))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// retryReadRecord recurs into readRecordOrCCS to drop a non-advancing record, like
|
|
||||||
// a warning alert, empty application_data, or a change_cipher_spec in TLS 1.3.
|
|
||||||
func (c *Conn) retryReadRecord( /*expectChangeCipherSpec bool*/ ) error {
|
|
||||||
//c.retryCount++
|
|
||||||
//if c.retryCount > maxUselessRecords {
|
|
||||||
// c.sendAlert(alertUnexpectedMessage)
|
|
||||||
// return c.in.setErrorLocked(errors.New("tls: too many ignored records"))
|
|
||||||
//}
|
|
||||||
return c.readRecord( /*expectChangeCipherSpec*/ )
|
|
||||||
}
|
|
||||||
|
|
||||||
// atLeastReader reads from R, stopping with EOF once at least N bytes have been
|
|
||||||
// read. It is different from an io.LimitedReader in that it doesn't cut short
|
|
||||||
// the last Read call, and in that it considers an early EOF an error.
|
|
||||||
type atLeastReader struct {
|
|
||||||
R io.Reader
|
|
||||||
N int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *atLeastReader) Read(p []byte) (int, error) {
|
|
||||||
if r.N <= 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
n, err := r.R.Read(p)
|
|
||||||
r.N -= int64(n) // won't underflow unless len(p) >= n > 9223372036854775809
|
|
||||||
if r.N > 0 && err == io.EOF {
|
|
||||||
return n, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if r.N <= 0 && err == nil {
|
|
||||||
return n, io.EOF
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// readFromUntil reads from r into c.rawConn.RawInput until c.rawConn.RawInput contains
|
|
||||||
// at least n bytes or else returns an error.
|
|
||||||
func (c *Conn) readFromUntil(r io.Reader, n int) error {
|
|
||||||
if c.rawConn.RawInput.Len() >= n {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
needs := n - c.rawConn.RawInput.Len()
|
|
||||||
// There might be extra input waiting on the wire. Make a best effort
|
|
||||||
// attempt to fetch it so that it can be used in (*Conn).Read to
|
|
||||||
// "predict" closeNotify alerts.
|
|
||||||
c.rawConn.RawInput.Grow(needs + bytes.MinRead)
|
|
||||||
_, err := c.rawConn.RawInput.ReadFrom(&atLeastReader{r, int64(needs)})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) newRecordHeaderError(conn net.Conn, msg string) (err tls.RecordHeaderError) {
|
|
||||||
err.Msg = msg
|
|
||||||
err.Conn = conn
|
|
||||||
copy(err.RecordHeader[:], c.rawConn.RawInput.Bytes())
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Conn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
|
|
||||||
c.readWaitOptions = options
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
|
||||||
c.rawConn.In.Lock()
|
|
||||||
defer c.rawConn.In.Unlock()
|
|
||||||
for c.rawConn.Input.Len() == 0 {
|
|
||||||
err = c.readRecord()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer = c.readWaitOptions.NewBuffer()
|
|
||||||
n, err := c.rawConn.Input.Read(buffer.FreeBytes())
|
|
||||||
if err != nil {
|
|
||||||
buffer.Release()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buffer.Truncate(n)
|
|
||||||
if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 &&
|
|
||||||
c.rawConn.RawInput.Bytes()[0] == recordTypeAlert {
|
|
||||||
_ = c.rawConn.ReadRecord()
|
|
||||||
}
|
|
||||||
c.readWaitOptions.PostReturn(buffer)
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
//go:build !linux || !go1.25 || without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewConn(conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
@ -1,154 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build linux && go1.25 && !without_badtls
|
|
||||||
|
|
||||||
package ktls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (int, error) {
|
|
||||||
if !c.kernelTx {
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
// interlock with Close below
|
|
||||||
for {
|
|
||||||
x := c.rawConn.ActiveCall.Load()
|
|
||||||
if x&1 != 0 {
|
|
||||||
return 0, net.ErrClosed
|
|
||||||
}
|
|
||||||
if c.rawConn.ActiveCall.CompareAndSwap(x, x+2) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer c.rawConn.ActiveCall.Add(-2)
|
|
||||||
|
|
||||||
//if err := c.Conn.HandshakeContext(context.Background()); err != nil {
|
|
||||||
// return 0, err
|
|
||||||
//}
|
|
||||||
|
|
||||||
c.rawConn.Out.Lock()
|
|
||||||
defer c.rawConn.Out.Unlock()
|
|
||||||
|
|
||||||
if err := *c.rawConn.Out.Err; err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.rawConn.IsHandshakeComplete.Load() {
|
|
||||||
return 0, tls.AlertError(alertInternalError)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *c.rawConn.CloseNotifySent {
|
|
||||||
// return 0, errShutdown
|
|
||||||
return 0, errors.New("tls: protocol is shutdown")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLS 1.0 is susceptible to a chosen-plaintext
|
|
||||||
// attack when using block mode ciphers due to predictable IVs.
|
|
||||||
// This can be prevented by splitting each Application Data
|
|
||||||
// record into two records, effectively randomizing the RawIV.
|
|
||||||
//
|
|
||||||
// https://www.openssl.org/~bodo/tls-cbc.txt
|
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=665814
|
|
||||||
// https://www.imperialviolet.org/2012/01/15/beastfollowup.html
|
|
||||||
|
|
||||||
var m int
|
|
||||||
if len(b) > 1 && *c.rawConn.Vers == tls.VersionTLS10 {
|
|
||||||
if _, ok := (*c.rawConn.Out.Cipher).(cipher.BlockMode); ok {
|
|
||||||
n, err := c.writeRecordLocked(recordTypeApplicationData, b[:1])
|
|
||||||
if err != nil {
|
|
||||||
return n, c.rawConn.Out.SetErrorLocked(err)
|
|
||||||
}
|
|
||||||
m, b = 1, b[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := c.writeRecordLocked(recordTypeApplicationData, b)
|
|
||||||
return n + m, c.rawConn.Out.SetErrorLocked(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) {
|
|
||||||
if !c.kernelTx {
|
|
||||||
return c.rawConn.WriteRecordLocked(typ, data)
|
|
||||||
}
|
|
||||||
/*for len(data) > 0 {
|
|
||||||
m := len(data)
|
|
||||||
if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload {
|
|
||||||
m = maxPayload
|
|
||||||
}
|
|
||||||
_, err = c.writeKernelRecord(typ, data[:m])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n += m
|
|
||||||
data = data[m:]
|
|
||||||
}*/
|
|
||||||
return c.writeKernelRecord(typ, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// tcpMSSEstimate is a conservative estimate of the TCP maximum segment
|
|
||||||
// size (MSS). A constant is used, rather than querying the kernel for
|
|
||||||
// the actual MSS, to avoid complexity. The value here is the IPv6
|
|
||||||
// minimum MTU (1280 bytes) minus the overhead of an IPv6 header (40
|
|
||||||
// bytes) and a TCP header with timestamps (32 bytes).
|
|
||||||
tcpMSSEstimate = 1208
|
|
||||||
|
|
||||||
// recordSizeBoostThreshold is the number of bytes of application data
|
|
||||||
// sent after which the TLS record size will be increased to the
|
|
||||||
// maximum.
|
|
||||||
recordSizeBoostThreshold = 128 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Conn) maxPayloadSizeForWrite(typ uint16) int {
|
|
||||||
if /*c.config.DynamicRecordSizingDisabled ||*/ typ != recordTypeApplicationData {
|
|
||||||
return maxPlaintext
|
|
||||||
}
|
|
||||||
|
|
||||||
if *c.rawConn.PacketsSent >= recordSizeBoostThreshold {
|
|
||||||
return maxPlaintext
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subtract TLS overheads to get the maximum payload size.
|
|
||||||
payloadBytes := tcpMSSEstimate - recordHeaderLen - c.rawConn.Out.ExplicitNonceLen()
|
|
||||||
if rawCipher := *c.rawConn.Out.Cipher; rawCipher != nil {
|
|
||||||
switch ciph := rawCipher.(type) {
|
|
||||||
case cipher.Stream:
|
|
||||||
payloadBytes -= (*c.rawConn.Out.Mac).Size()
|
|
||||||
case cipher.AEAD:
|
|
||||||
payloadBytes -= ciph.Overhead()
|
|
||||||
/*case cbcMode:
|
|
||||||
blockSize := ciph.BlockSize()
|
|
||||||
// The payload must fit in a multiple of blockSize, with
|
|
||||||
// room for at least one padding byte.
|
|
||||||
payloadBytes = (payloadBytes & ^(blockSize - 1)) - 1
|
|
||||||
// The RawMac is appended before padding so affects the
|
|
||||||
// payload size directly.
|
|
||||||
payloadBytes -= c.out.mac.Size()*/
|
|
||||||
default:
|
|
||||||
panic("unknown cipher type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if *c.rawConn.Vers == tls.VersionTLS13 {
|
|
||||||
payloadBytes-- // encrypted ContentType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow packet growth in arithmetic progression up to max.
|
|
||||||
pkt := *c.rawConn.PacketsSent
|
|
||||||
*c.rawConn.PacketsSent++
|
|
||||||
if pkt > 1000 {
|
|
||||||
return maxPlaintext // avoid overflow in multiply below
|
|
||||||
}
|
|
||||||
|
|
||||||
n := payloadBytes * int(pkt+1)
|
|
||||||
if n > maxPlaintext {
|
|
||||||
n = maxPlaintext
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
@ -164,8 +164,9 @@ func (l *Listener) loopUDPOut() {
|
|||||||
if l.shutdown.Load() && E.IsClosed(err) {
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case <-l.packetOutboundClosed:
|
case <-l.packetOutboundClosed:
|
||||||
|
@ -96,11 +96,11 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
switch {
|
switch {
|
||||||
case flag&0x1 > 0 && isIPv4:
|
case flag&0x1 > 0 && isIPv4:
|
||||||
// ipv4
|
// ipv4
|
||||||
srcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))
|
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
||||||
srcIsIPv4 = true
|
srcIsIPv4 = true
|
||||||
case flag&0x2 > 0 && !isIPv4:
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
// ipv6
|
// ipv6
|
||||||
srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
|
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -17,5 +17,8 @@ var uQUICChrome115 = &ja3.ClientHello{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func maybeUQUIC(fingerprint *ja3.ClientHello) bool {
|
func maybeUQUIC(fingerprint *ja3.ClientHello) bool {
|
||||||
return !uQUICChrome115.Equals(fingerprint, true)
|
if uQUICChrome115.Equals(fingerprint, true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func TestSniffUQUICChrome115(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientQUICGo)
|
||||||
require.Equal(t, metadata.Domain, "www.google.com")
|
require.Equal(t, metadata.Domain, "www.google.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,8 +12,6 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/domain"
|
"github.com/sagernet/sing/common/domain"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
@ -43,8 +41,6 @@ const (
|
|||||||
ruleItemNetworkType
|
ruleItemNetworkType
|
||||||
ruleItemNetworkIsExpensive
|
ruleItemNetworkIsExpensive
|
||||||
ruleItemNetworkIsConstrained
|
ruleItemNetworkIsConstrained
|
||||||
ruleItemNetworkInterfaceAddress
|
|
||||||
ruleItemDefaultInterfaceAddress
|
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -234,51 +230,6 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
rule.NetworkIsExpensive = true
|
rule.NetworkIsExpensive = true
|
||||||
case ruleItemNetworkIsConstrained:
|
case ruleItemNetworkIsConstrained:
|
||||||
rule.NetworkIsConstrained = true
|
rule.NetworkIsConstrained = true
|
||||||
case ruleItemNetworkInterfaceAddress:
|
|
||||||
rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]])
|
|
||||||
var size uint64
|
|
||||||
size, err = binary.ReadUvarint(reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := uint64(0); i < size; i++ {
|
|
||||||
var key uint8
|
|
||||||
err = binary.Read(reader, binary.BigEndian, &key)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var value []*badoption.Prefixable
|
|
||||||
var prefixCount uint64
|
|
||||||
prefixCount, err = binary.ReadUvarint(reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for j := uint64(0); j < prefixCount; j++ {
|
|
||||||
var prefix netip.Prefix
|
|
||||||
prefix, err = readPrefix(reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
value = append(value, common.Ptr(badoption.Prefixable(prefix)))
|
|
||||||
}
|
|
||||||
rule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value)
|
|
||||||
}
|
|
||||||
case ruleItemDefaultInterfaceAddress:
|
|
||||||
var value []*badoption.Prefixable
|
|
||||||
var prefixCount uint64
|
|
||||||
prefixCount, err = binary.ReadUvarint(reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for j := uint64(0); j < prefixCount; j++ {
|
|
||||||
var prefix netip.Prefix
|
|
||||||
prefix, err = readPrefix(reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
value = append(value, common.Ptr(badoption.Prefixable(prefix)))
|
|
||||||
}
|
|
||||||
rule.DefaultInterfaceAddress = value
|
|
||||||
case ruleItemFinal:
|
case ruleItemFinal:
|
||||||
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||||
return
|
return
|
||||||
@ -395,7 +346,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
if len(rule.NetworkType) > 0 {
|
if len(rule.NetworkType) > 0 {
|
||||||
if generateVersion < C.RuleSetVersion3 {
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
return E.New("`network_type` rule item is only supported in version 3 or later")
|
return E.New("network_type rule item is only supported in version 3 or later")
|
||||||
}
|
}
|
||||||
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -403,71 +354,17 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rule.NetworkIsExpensive {
|
if rule.NetworkIsExpensive {
|
||||||
if generateVersion < C.RuleSetVersion3 {
|
|
||||||
return E.New("`network_is_expensive` rule item is only supported in version 3 or later")
|
|
||||||
}
|
|
||||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rule.NetworkIsConstrained {
|
if rule.NetworkIsConstrained {
|
||||||
if generateVersion < C.RuleSetVersion3 {
|
|
||||||
return E.New("`network_is_constrained` rule item is only supported in version 3 or later")
|
|
||||||
}
|
|
||||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 {
|
|
||||||
if generateVersion < C.RuleSetVersion4 {
|
|
||||||
return E.New("`network_interface_address` rule item is only supported in version 4 or later")
|
|
||||||
}
|
|
||||||
err = writer.WriteByte(ruleItemNetworkInterfaceAddress)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(rule.NetworkInterfaceAddress.Size()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, entry := range rule.NetworkInterfaceAddress.Entries() {
|
|
||||||
err = binary.Write(writer, binary.BigEndian, uint8(entry.Key.Build()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(entry.Value)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, rawPrefix := range entry.Value {
|
|
||||||
err = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(rule.DefaultInterfaceAddress) > 0 {
|
|
||||||
if generateVersion < C.RuleSetVersion4 {
|
|
||||||
return E.New("`default_interface_address` rule item is only supported in version 4 or later")
|
|
||||||
}
|
|
||||||
err = writer.WriteByte(ruleItemDefaultInterfaceAddress)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(rule.DefaultInterfaceAddress)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, rawPrefix := range rule.DefaultInterfaceAddress {
|
|
||||||
err = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(rule.WIFISSID) > 0 {
|
if len(rule.WIFISSID) > 0 {
|
||||||
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package srs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
|
||||||
addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian)
|
|
||||||
if err != nil {
|
|
||||||
return netip.Prefix{}, err
|
|
||||||
}
|
|
||||||
prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian)
|
|
||||||
if err != nil {
|
|
||||||
return netip.Prefix{}, err
|
|
||||||
}
|
|
||||||
return netip.PrefixFrom(M.AddrFromIP(addrSlice), int(prefixBits)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
|
||||||
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -2,22 +2,19 @@ package tls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
"github.com/sagernet/sing-box/common/ktls"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDialerFromOptions(ctx context.Context, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
func NewDialerFromOptions(ctx context.Context, router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
@ -47,12 +44,6 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if kConfig, isKConfig := config.(KTLSCapableConfig); isKConfig && (kConfig.KernelTx() || kConfig.KernelRx()) {
|
|
||||||
if !C.IsLinux {
|
|
||||||
return nil, E.New("kTLS is only supported on Linux")
|
|
||||||
}
|
|
||||||
return ktls.NewConn(tlsConn, kConfig.KernelTx(), kConfig.KernelRx())
|
|
||||||
}
|
|
||||||
readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
|
readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return readWaitConn, nil
|
return readWaitConn, nil
|
||||||
@ -62,57 +53,26 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e
|
|||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dialer interface {
|
type Dialer struct {
|
||||||
N.Dialer
|
|
||||||
DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultDialer struct {
|
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDialer(dialer N.Dialer, config Config) Dialer {
|
func NewDialer(dialer N.Dialer, config Config) N.Dialer {
|
||||||
return &defaultDialer{dialer, config}
|
return &Dialer{dialer, config}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *defaultDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if N.NetworkName(network) != N.NetworkTCP {
|
if network != N.NetworkTCP {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
return d.DialTLSContext(ctx, destination)
|
conn, err := d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
|
||||||
|
|
||||||
func (d *defaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *defaultDialer) DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) {
|
|
||||||
return d.dialContext(ctx, destination, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr, echRetry bool) (Conn, error) {
|
|
||||||
conn, err := d.dialer.DialContext(ctx, N.NetworkTCP, destination)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConn, err := ClientHandshake(ctx, conn, d.config)
|
return ClientHandshake(ctx, conn, d.config)
|
||||||
if err == nil {
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
|
||||||
conn.Close()
|
|
||||||
if echRetry {
|
|
||||||
var echErr *tls.ECHRejectionError
|
|
||||||
if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
|
|
||||||
if echConfig, isECH := d.config.(ECHCapableConfig); isECH {
|
|
||||||
echConfig.SetECHConfigList(echErr.RetryConfigList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return d.dialContext(ctx, destination, false)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *defaultDialer) Upstream() any {
|
func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return d.dialer
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,6 @@ type (
|
|||||||
CurveID = tls.CurveID
|
CurveID = tls.CurveID
|
||||||
)
|
)
|
||||||
|
|
||||||
type KTLSCapableConfig interface {
|
|
||||||
Config
|
|
||||||
KernelTx() bool
|
|
||||||
KernelRx() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseTLSVersion(version string) (uint16, error) {
|
func ParseTLSVersion(version string) (uint16, error) {
|
||||||
switch version {
|
switch version {
|
||||||
case "1.0":
|
case "1.0":
|
||||||
|
@ -125,7 +125,7 @@ func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (a
|
|||||||
func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
defer s.access.Unlock()
|
defer s.access.Unlock()
|
||||||
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL {
|
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
|
@ -307,11 +307,3 @@ func (c *realityClientConnWrapper) Upstream() any {
|
|||||||
func (c *realityClientConnWrapper) CloseWrite() error {
|
func (c *realityClientConnWrapper) CloseWrite() error {
|
||||||
return c.Close()
|
return c.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realityClientConnWrapper) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityClientConnWrapper) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
@ -206,11 +206,3 @@ func (c *realityConnWrapper) Upstream() any {
|
|||||||
func (c *realityConnWrapper) CloseWrite() error {
|
func (c *realityConnWrapper) CloseWrite() error {
|
||||||
return c.Close()
|
return c.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realityConnWrapper) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityConnWrapper) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
@ -6,11 +6,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
"github.com/sagernet/sing-box/common/ktls"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,12 +29,6 @@ func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if kConfig, isKConfig := config.(KTLSCapableConfig); isKConfig && (kConfig.KernelTx() || kConfig.KernelRx()) {
|
|
||||||
if !C.IsLinux {
|
|
||||||
return nil, E.New("kTLS is only supported on Linux")
|
|
||||||
}
|
|
||||||
return ktls.NewConn(tlsConn, kConfig.KernelTx(), kConfig.KernelRx())
|
|
||||||
}
|
|
||||||
readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
|
readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return readWaitConn, nil
|
return readWaitConn, nil
|
||||||
|
@ -22,7 +22,6 @@ type STDClientConfig struct {
|
|||||||
fragment bool
|
fragment bool
|
||||||
fragmentFallbackDelay time.Duration
|
fragmentFallbackDelay time.Duration
|
||||||
recordFragment bool
|
recordFragment bool
|
||||||
kernelTx, kernelRx bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) ServerName() string {
|
func (c *STDClientConfig) ServerName() string {
|
||||||
@ -53,15 +52,7 @@ func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Clone() Config {
|
func (c *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{
|
return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment}
|
||||||
ctx: c.ctx,
|
|
||||||
config: c.config.Clone(),
|
|
||||||
fragment: c.fragment,
|
|
||||||
fragmentFallbackDelay: c.fragmentFallbackDelay,
|
|
||||||
recordFragment: c.recordFragment,
|
|
||||||
kernelTx: c.kernelTx,
|
|
||||||
kernelRx: c.kernelRx,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) ECHConfigList() []byte {
|
func (c *STDClientConfig) ECHConfigList() []byte {
|
||||||
@ -72,14 +63,6 @@ func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte
|
|||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) KernelTx() bool {
|
|
||||||
return c.kernelTx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) KernelRx() bool {
|
|
||||||
return c.kernelRx
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
@ -163,15 +146,7 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
stdConfig := &STDClientConfig{
|
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
||||||
ctx: ctx,
|
|
||||||
config: &tlsConfig,
|
|
||||||
fragment: options.Fragment,
|
|
||||||
fragmentFallbackDelay: time.Duration(options.FragmentFallbackDelay),
|
|
||||||
recordFragment: options.RecordFragment,
|
|
||||||
kernelTx: options.KernelTx,
|
|
||||||
kernelRx: options.KernelRx,
|
|
||||||
}
|
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return parseECHClientConfig(ctx, stdConfig, options)
|
return parseECHClientConfig(ctx, stdConfig, options)
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,7 +22,6 @@ var errInsecureUnused = E.New("tls: insecure unused")
|
|||||||
type STDServerConfig struct {
|
type STDServerConfig struct {
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
kernelTx, kernelRx bool
|
|
||||||
acmeService adapter.SimpleLifecycle
|
acmeService adapter.SimpleLifecycle
|
||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
@ -71,19 +70,9 @@ func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
|
|||||||
func (c *STDServerConfig) Clone() Config {
|
func (c *STDServerConfig) Clone() Config {
|
||||||
return &STDServerConfig{
|
return &STDServerConfig{
|
||||||
config: c.config.Clone(),
|
config: c.config.Clone(),
|
||||||
kernelTx: c.kernelTx,
|
|
||||||
kernelRx: c.kernelRx,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) KernelTx() bool {
|
|
||||||
return c.kernelTx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDServerConfig) KernelRx() bool {
|
|
||||||
return c.kernelRx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDServerConfig) Start() error {
|
func (c *STDServerConfig) Start() error {
|
||||||
if c.acmeService != nil {
|
if c.acmeService != nil {
|
||||||
return c.acmeService.Start()
|
return c.acmeService.Start()
|
||||||
@ -276,8 +265,6 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
return &STDServerConfig{
|
return &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
kernelTx: options.KernelTx,
|
|
||||||
kernelRx: options.KernelRx,
|
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
certificate: certificate,
|
certificate: certificate,
|
||||||
key: key,
|
key: key,
|
||||||
|
@ -29,8 +29,6 @@ type UTLSClientConfig struct {
|
|||||||
fragment bool
|
fragment bool
|
||||||
fragmentFallbackDelay time.Duration
|
fragmentFallbackDelay time.Duration
|
||||||
recordFragment bool
|
recordFragment bool
|
||||||
kernelTx bool
|
|
||||||
kernelRx bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ServerName() string {
|
func (c *UTLSClientConfig) ServerName() string {
|
||||||
@ -69,7 +67,7 @@ func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []by
|
|||||||
|
|
||||||
func (c *UTLSClientConfig) Clone() Config {
|
func (c *UTLSClientConfig) Clone() Config {
|
||||||
return &UTLSClientConfig{
|
return &UTLSClientConfig{
|
||||||
c.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment, c.kernelTx, c.kernelRx,
|
c.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,14 +79,6 @@ func (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byt
|
|||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) KernelTx() bool {
|
|
||||||
return c.kernelTx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UTLSClientConfig) KernelRx() bool {
|
|
||||||
return c.kernelRx
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
type utlsConnWrapper struct {
|
||||||
*utls.UConn
|
*utls.UConn
|
||||||
}
|
}
|
||||||
@ -116,14 +106,6 @@ func (c *utlsConnWrapper) Upstream() any {
|
|||||||
return c.UConn
|
return c.UConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *utlsConnWrapper) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *utlsConnWrapper) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsALPNWrapper struct {
|
type utlsALPNWrapper struct {
|
||||||
utlsConnWrapper
|
utlsConnWrapper
|
||||||
nextProtocols []string
|
nextProtocols []string
|
||||||
@ -224,12 +206,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment, options.KernelTx, options.KernelRx}
|
uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
||||||
if uConfig.kernelTx || uConfig.kernelRx {
|
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
|
||||||
return nil, E.New("Reality is conflict with kTLS")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
if options.Reality != nil && options.Reality.Enabled {
|
||||||
return nil, E.New("Reality is conflict with ECH")
|
return nil, E.New("Reality is conflict with ECH")
|
||||||
|
@ -109,9 +109,6 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if i != len(splitIndexes) {
|
|
||||||
time.Sleep(c.fallbackDelay)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
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)
|
time.Sleep(fallbackDelay)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,6 @@ func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fal
|
|||||||
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
|
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||||
if _, err := conn.Write(payload); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
time.Sleep(fallbackDelay)
|
time.Sleep(fallbackDelay)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,7 @@ const (
|
|||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersion3
|
RuleSetVersion3
|
||||||
RuleSetVersion4
|
RuleSetVersionCurrent = RuleSetVersion3
|
||||||
RuleSetVersionCurrent = RuleSetVersion4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -40,5 +39,4 @@ const (
|
|||||||
const (
|
const (
|
||||||
RuleActionRejectMethodDefault = "default"
|
RuleActionRejectMethodDefault = "default"
|
||||||
RuleActionRejectMethodDrop = "drop"
|
RuleActionRejectMethodDrop = "drop"
|
||||||
RuleActionRejectMethodReply = "reply"
|
|
||||||
)
|
)
|
||||||
|
108
dns/client.go
108
dns/client.go
@ -2,14 +2,12 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/compatible"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -19,7 +17,7 @@ import (
|
|||||||
"github.com/sagernet/sing/contrab/freelru"
|
"github.com/sagernet/sing/contrab/freelru"
|
||||||
"github.com/sagernet/sing/contrab/maphash"
|
"github.com/sagernet/sing/contrab/maphash"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
dns "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -41,9 +39,7 @@ type Client struct {
|
|||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
cache freelru.Cache[dns.Question, *dns.Msg]
|
cache freelru.Cache[dns.Question, *dns.Msg]
|
||||||
cacheLock compatible.Map[dns.Question, chan struct{}]
|
|
||||||
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||||
transportCacheLock compatible.Map[dns.Question, chan struct{}]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
@ -100,15 +96,17 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
if c.logger != nil {
|
if c.logger != nil {
|
||||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||||
}
|
}
|
||||||
return FixedResponseStatus(message, dns.RcodeFormatError), nil
|
responseMessage := dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Response: true,
|
||||||
|
Rcode: dns.RcodeFormatError,
|
||||||
|
},
|
||||||
|
Question: message.Question,
|
||||||
|
}
|
||||||
|
return &responseMessage, nil
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
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
|
clientSubnet := options.ClientSubnet
|
||||||
if !clientSubnet.IsValid() {
|
if !clientSubnet.IsValid() {
|
||||||
clientSubnet = c.clientSubnet
|
clientSubnet = c.clientSubnet
|
||||||
@ -116,38 +114,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
if clientSubnet.IsValid() {
|
if clientSubnet.IsValid() {
|
||||||
message = SetClientSubnet(message, clientSubnet)
|
message = SetClientSubnet(message, clientSubnet)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
(len(message.Extra) == 0 || len(message.Extra) == 1 &&
|
len(message.Extra) == 0 &&
|
||||||
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()
|
!options.ClientSubnet.IsValid()
|
||||||
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||||
if !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)
|
response, ttl := c.loadResponse(question, transport)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
logCachedResponse(c.logger, ctx, response, ttl)
|
logCachedResponse(c.logger, ctx, response, ttl)
|
||||||
@ -155,14 +127,27 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return response, nil
|
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
|
messageId := message.Id
|
||||||
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
||||||
if clientSubnetLoaded && transport.Tag() == contextTransport {
|
if clientSubnetLoaded && transport.Tag() == contextTransport {
|
||||||
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
|
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
|
||||||
}
|
}
|
||||||
ctx = contextWithTransportTag(ctx, transport.Tag())
|
ctx = contextWithTransportTag(ctx, transport.Tag())
|
||||||
if !disableCache && responseChecker != nil && c.rdrc != nil {
|
if responseChecker != nil && c.rdrc != nil {
|
||||||
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
||||||
if rejected {
|
if rejected {
|
||||||
return nil, ErrResponseRejectedCached
|
return nil, ErrResponseRejectedCached
|
||||||
@ -172,13 +157,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
response, err := transport.Exchange(ctx, message)
|
response, err := transport.Exchange(ctx, message)
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var rcodeError RcodeError
|
|
||||||
if errors.As(err, &rcodeError) {
|
|
||||||
response = FixedResponseStatus(message, int(rcodeError))
|
|
||||||
} else {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
||||||
validResponse := response
|
validResponse := response
|
||||||
loop:
|
loop:
|
||||||
@ -216,14 +196,13 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}*/
|
}*/
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
var rejected bool
|
||||||
// TODO: add accept_any rule and support to check response instead of addresses
|
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) {
|
||||||
if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 {
|
|
||||||
rejected = true
|
rejected = true
|
||||||
} else {
|
} else {
|
||||||
rejected = !responseChecker(MessageToAddresses(response))
|
rejected = !responseChecker(MessageToAddresses(response))
|
||||||
}
|
}
|
||||||
if rejected {
|
if rejected {
|
||||||
if !disableCache && c.rdrc != nil {
|
if c.rdrc != nil {
|
||||||
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
||||||
}
|
}
|
||||||
logRejectedResponse(c.logger, ctx, response)
|
logRejectedResponse(c.logger, ctx, response)
|
||||||
@ -326,7 +305,8 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
func (c *Client) ClearCache() {
|
func (c *Client) ClearCache() {
|
||||||
if c.cache != nil {
|
if c.cache != nil {
|
||||||
c.cache.Purge()
|
c.cache.Purge()
|
||||||
} else if c.transportCache != nil {
|
}
|
||||||
|
if c.transportCache != nil {
|
||||||
c.transportCache.Purge()
|
c.transportCache.Purge()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -340,36 +320,36 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.
|
|||||||
}
|
}
|
||||||
dnsName := dns.Fqdn(domain)
|
dnsName := dns.Fqdn(domain)
|
||||||
if strategy == C.DomainStrategyIPv4Only {
|
if strategy == C.DomainStrategyIPv4Only {
|
||||||
addresses, err := c.questionCache(dns.Question{
|
response, err := c.questionCache(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeA,
|
Qtype: dns.TypeA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != ErrNotCached {
|
if err != ErrNotCached {
|
||||||
return addresses, true
|
return response, true
|
||||||
}
|
}
|
||||||
} else if strategy == C.DomainStrategyIPv6Only {
|
} else if strategy == C.DomainStrategyIPv6Only {
|
||||||
addresses, err := c.questionCache(dns.Question{
|
response, err := c.questionCache(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeAAAA,
|
Qtype: dns.TypeAAAA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != ErrNotCached {
|
if err != ErrNotCached {
|
||||||
return addresses, true
|
return response, true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response4, _ := c.loadResponse(dns.Question{
|
response4, _ := c.questionCache(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeA,
|
Qtype: dns.TypeA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
response6, _ := c.loadResponse(dns.Question{
|
response6, _ := c.questionCache(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeAAAA,
|
Qtype: dns.TypeAAAA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
if response4 != nil || response6 != nil {
|
if len(response4) > 0 || len(response6) > 0 {
|
||||||
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
return sortAddresses(response4, response6, strategy), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
@ -410,7 +390,8 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
|||||||
transportTag: transport.Tag(),
|
transportTag: transport.Tag(),
|
||||||
}, message)
|
}, message)
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
if !c.independentCache {
|
if !c.independentCache {
|
||||||
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||||
} else {
|
} else {
|
||||||
@ -420,7 +401,6 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
|||||||
}, message, time.Second*time.Duration(timeToLive))
|
}, message, time.Second*time.Duration(timeToLive))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
question := dns.Question{
|
question := dns.Question{
|
||||||
@ -537,9 +517,6 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
for _, rawAnswer := range response.Answer {
|
for _, rawAnswer := range response.Answer {
|
||||||
switch answer := rawAnswer.(type) {
|
switch answer := rawAnswer.(type) {
|
||||||
@ -585,11 +562,8 @@ func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
|||||||
return &dns.Msg{
|
return &dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: message.Id,
|
Id: message.Id,
|
||||||
Response: true,
|
|
||||||
Authoritative: true,
|
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: rcode,
|
Rcode: rcode,
|
||||||
|
Response: true,
|
||||||
},
|
},
|
||||||
Question: message.Question,
|
Question: message.Question,
|
||||||
}
|
}
|
||||||
|
@ -293,7 +293,12 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
} else if errors.Is(err, ErrResponseRejected) {
|
} else if errors.Is(err, ErrResponseRejected) {
|
||||||
rejected = true
|
rejected = true
|
||||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
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 {
|
} else if len(message.Question) > 0 {
|
||||||
|
rejected = true
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||||
|
@ -9,8 +9,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
@ -27,7 +29,6 @@ import (
|
|||||||
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
@ -44,12 +45,9 @@ type Transport struct {
|
|||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
interfaceName string
|
interfaceName string
|
||||||
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||||
transportLock sync.RWMutex
|
transports []adapter.DNSTransport
|
||||||
|
updateAccess sync.Mutex
|
||||||
updatedAt time.Time
|
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) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
@ -64,40 +62,27 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
interfaceName: options.Interface,
|
interfaceName: options.Interface,
|
||||||
ndots: 1,
|
|
||||||
attempts: 2,
|
|
||||||
}, nil
|
}, 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 {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
if stage != adapter.StartStateStart {
|
if stage != adapter.StartStateStart {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
err := t.fetchServers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if t.interfaceName == "" {
|
if t.interfaceName == "" {
|
||||||
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
|
for _, transport := range t.transports {
|
||||||
|
transport.Close()
|
||||||
|
}
|
||||||
if t.interfaceCallback != nil {
|
if t.interfaceCallback != nil {
|
||||||
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
||||||
}
|
}
|
||||||
@ -105,45 +90,24 @@ func (t *Transport) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
servers, err := t.Fetch()
|
err := t.fetchServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(servers) == 0 {
|
|
||||||
|
if len(t.transports) == 0 {
|
||||||
return nil, E.New("dhcp: empty DNS servers from response")
|
return nil, E.New("dhcp: empty DNS servers from response")
|
||||||
}
|
}
|
||||||
return t.Exchange0(ctx, message, servers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) {
|
var response *mDNS.Msg
|
||||||
question := message.Question[0]
|
for _, transport := range t.transports {
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
response, err = transport.Exchange(ctx, message)
|
||||||
if len(servers) == 1 || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
if err == nil {
|
||||||
return t.exchangeSingleRequest(ctx, servers, message, domain)
|
return response, nil
|
||||||
} else {
|
|
||||||
return t.exchangeParallel(ctx, servers, message, domain)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 nil, err
|
||||||
}
|
}
|
||||||
return t.servers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
||||||
if t.interfaceName == "" {
|
if t.interfaceName == "" {
|
||||||
@ -160,6 +124,18 @@ 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 {
|
func (t *Transport) updateServers() error {
|
||||||
iface, err := t.fetchInterface()
|
iface, err := t.fetchInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -172,7 +148,7 @@ func (t *Transport) updateServers() error {
|
|||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if len(t.servers) == 0 {
|
} else if len(t.transports) == 0 {
|
||||||
return E.New("dhcp: empty DNS servers response")
|
return E.New("dhcp: empty DNS servers response")
|
||||||
} else {
|
} else {
|
||||||
t.updatedAt = time.Now()
|
t.updatedAt = time.Now()
|
||||||
@ -201,11 +177,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface)
|
|||||||
}
|
}
|
||||||
defer packetConn.Close()
|
defer packetConn.Close()
|
||||||
|
|
||||||
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(
|
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer))
|
||||||
dhcpv4.OptionDomainName,
|
|
||||||
dhcpv4.OptionDomainNameServer,
|
|
||||||
dhcpv4.OptionDNSDomainSearchList,
|
|
||||||
))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -251,23 +223,31 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.recreateServers(iface, dhcpPacket)
|
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)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
|
func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.Socksaddr) error {
|
||||||
searchList := dhcpPacket.DomainSearch()
|
if len(serverAddrs) > 0 {
|
||||||
if searchList != nil && len(searchList.Labels) > 0 {
|
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "]")
|
||||||
t.search = searchList.Labels
|
|
||||||
} else if dhcpPacket.DomainName() != "" {
|
|
||||||
t.search = []string{dhcpPacket.DomainName()}
|
|
||||||
}
|
}
|
||||||
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
|
serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{
|
||||||
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
BindInterface: iface.Name,
|
||||||
})
|
UDPFragmentDefault: true,
|
||||||
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, ","), "]")
|
var transports []adapter.DNSTransport
|
||||||
|
for _, serverAddr := range serverAddrs {
|
||||||
|
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
||||||
}
|
}
|
||||||
t.servers = serverAddrs
|
for _, transport := range t.transports {
|
||||||
|
transport.Close()
|
||||||
|
}
|
||||||
|
t.transports = transports
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,197 +0,0 @@
|
|||||||
package dhcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
|
||||||
var lastErr error
|
|
||||||
for _, fqdn := range t.nameList(domain) {
|
|
||||||
response, err := t.tryOneName(ctx, servers, fqdn, message)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
return nil, lastErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
|
||||||
returned := make(chan struct{})
|
|
||||||
defer close(returned)
|
|
||||||
type queryResult struct {
|
|
||||||
response *mDNS.Msg
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
results := make(chan queryResult)
|
|
||||||
startRacer := func(ctx context.Context, fqdn string) {
|
|
||||||
response, err := t.tryOneName(ctx, servers, fqdn, message)
|
|
||||||
if err == nil {
|
|
||||||
if response.Rcode != mDNS.RcodeSuccess {
|
|
||||||
err = dns.RcodeError(response.Rcode)
|
|
||||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
|
||||||
err = E.New(fqdn, ": empty result")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case results <- queryResult{response, err}:
|
|
||||||
case <-returned:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queryCtx, queryCancel := context.WithCancel(ctx)
|
|
||||||
defer queryCancel()
|
|
||||||
var nameCount int
|
|
||||||
for _, fqdn := range t.nameList(domain) {
|
|
||||||
nameCount++
|
|
||||||
go startRacer(queryCtx, fqdn)
|
|
||||||
}
|
|
||||||
var errors []error
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case result := <-results:
|
|
||||||
if result.err == nil {
|
|
||||||
return result.response, nil
|
|
||||||
}
|
|
||||||
errors = append(errors, result.err)
|
|
||||||
if len(errors) == nameCount {
|
|
||||||
return nil, E.Errors(errors...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
sLen := len(servers)
|
|
||||||
var lastErr error
|
|
||||||
for i := 0; i < t.attempts; i++ {
|
|
||||||
for j := 0; j < sLen; j++ {
|
|
||||||
server := servers[j]
|
|
||||||
question := message.Question[0]
|
|
||||||
question.Name = fqdn
|
|
||||||
response, err := t.exchangeOne(ctx, server, question, C.DNSTimeout, false, true)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, E.Cause(lastErr, fqdn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
|
||||||
if server.Port == 0 {
|
|
||||||
server.Port = 53
|
|
||||||
}
|
|
||||||
var networks []string
|
|
||||||
if useTCP {
|
|
||||||
networks = []string{N.NetworkTCP}
|
|
||||||
} else {
|
|
||||||
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
|
||||||
}
|
|
||||||
request := &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: uint16(rand.Uint32()),
|
|
||||||
RecursionDesired: true,
|
|
||||||
AuthenticatedData: ad,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
Compress: true,
|
|
||||||
}
|
|
||||||
request.SetEdns0(buf.UDPBufferSize, false)
|
|
||||||
buffer := buf.Get(buf.UDPBufferSize)
|
|
||||||
defer buf.Put(buffer)
|
|
||||||
for _, network := range networks {
|
|
||||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
|
||||||
defer cancel()
|
|
||||||
conn, err := t.dialer.DialContext(ctx, network, server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
|
||||||
conn.SetDeadline(deadline)
|
|
||||||
}
|
|
||||||
rawMessage, err := request.PackBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "pack request")
|
|
||||||
}
|
|
||||||
_, err = conn.Write(rawMessage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "write request")
|
|
||||||
}
|
|
||||||
n, err := conn.Read(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read response")
|
|
||||||
}
|
|
||||||
var response mDNS.Msg
|
|
||||||
err = response.Unpack(buffer[:n])
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "unpack response")
|
|
||||||
}
|
|
||||||
if response.Truncated && network == N.NetworkUDP {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
panic("unexpected")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) nameList(name string) []string {
|
|
||||||
l := len(name)
|
|
||||||
rooted := l > 0 && name[l-1] == '.'
|
|
||||||
if l > 254 || l == 254 && !rooted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if rooted {
|
|
||||||
if avoidDNS(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []string{name}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasNdots := strings.Count(name, ".") >= t.ndots
|
|
||||||
name += "."
|
|
||||||
// l++
|
|
||||||
|
|
||||||
names := make([]string, 0, 1+len(t.search))
|
|
||||||
if hasNdots && !avoidDNS(name) {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
for _, suffix := range t.search {
|
|
||||||
fqdn := name + suffix
|
|
||||||
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
|
|
||||||
names = append(names, fqdn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasNdots && !avoidDNS(name) {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
func avoidDNS(name string) bool {
|
|
||||||
if name == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if name[len(name)-1] == '.' {
|
|
||||||
name = name[:len(name)-1]
|
|
||||||
}
|
|
||||||
return strings.HasSuffix(name, ".onion")
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
//go:build !darwin
|
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@ -11,27 +11,21 @@ import (
|
|||||||
"github.com/sagernet/sing-box/dns/transport/hosts"
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
|
||||||
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.DNSTransport = (*Transport)(nil)
|
var _ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.ContextLogger
|
|
||||||
hosts *hosts.File
|
hosts *hosts.File
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
preferGo bool
|
|
||||||
resolved ResolvedResolver
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
@ -42,45 +36,20 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logger,
|
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
preferGo: options.PreferGo,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
switch stage {
|
|
||||||
case adapter.StartStateInitialize:
|
|
||||||
if !t.preferGo {
|
|
||||||
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
|
|
||||||
if err == nil {
|
|
||||||
err = resolvedResolver.Start()
|
|
||||||
if err == nil {
|
|
||||||
t.resolved = resolvedResolver
|
|
||||||
} else {
|
|
||||||
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
if t.resolved != nil {
|
|
||||||
return t.resolved.Close()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
if t.resolved != nil {
|
|
||||||
resolverObject := t.resolved.Object()
|
|
||||||
if resolverObject != nil {
|
|
||||||
return t.resolved.Exchange(resolverObject, ctx, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
@ -89,5 +58,147 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return t.exchange(ctx, message, domain)
|
systemConfig := getSystemDNSConfig(t.ctx)
|
||||||
|
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||||
|
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
||||||
|
} else {
|
||||||
|
return t.exchangeParallel(ctx, systemConfig, message, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
|
var lastErr error
|
||||||
|
for _, fqdn := range systemConfig.nameList(domain) {
|
||||||
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, 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, systemConfig, fqdn, message)
|
||||||
|
if err == nil {
|
||||||
|
if response.Rcode != mDNS.RcodeSuccess {
|
||||||
|
err = dns.RcodeError(response.Rcode)
|
||||||
|
} else if len(dns.MessageToAddresses(response)) == 0 {
|
||||||
|
err = E.New(fqdn, ": empty result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case results <- queryResult{response, err}:
|
||||||
|
case <-returned:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryCtx, queryCancel := context.WithCancel(ctx)
|
||||||
|
defer queryCancel()
|
||||||
|
var nameCount int
|
||||||
|
for _, fqdn := range systemConfig.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, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
serverOffset := config.serverOffset()
|
||||||
|
sLen := uint32(len(config.servers))
|
||||||
|
var lastErr error
|
||||||
|
for i := 0; i < config.attempts; i++ {
|
||||||
|
for j := uint32(0); j < sLen; j++ {
|
||||||
|
server := config.servers[(serverOffset+j)%sLen]
|
||||||
|
question := message.Question[0]
|
||||||
|
question.Name = fqdn
|
||||||
|
response, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.Cause(lastErr, fqdn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
||||||
|
if server.Port == 0 {
|
||||||
|
server.Port = 53
|
||||||
|
}
|
||||||
|
var networks []string
|
||||||
|
if useTCP {
|
||||||
|
networks = []string{N.NetworkTCP}
|
||||||
|
} else {
|
||||||
|
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
||||||
|
}
|
||||||
|
request := &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: uint16(rand.Uint32()),
|
||||||
|
RecursionDesired: true,
|
||||||
|
AuthenticatedData: ad,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
|
request.SetEdns0(maxDNSPacketSize, false)
|
||||||
|
buffer := buf.Get(buf.UDPBufferSize)
|
||||||
|
defer buf.Put(buffer)
|
||||||
|
for _, network := range networks {
|
||||||
|
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||||
|
defer cancel()
|
||||||
|
conn, err := t.dialer.DialContext(ctx, network, server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||||
|
conn.SetDeadline(deadline)
|
||||||
|
}
|
||||||
|
rawMessage, err := request.PackBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "pack request")
|
||||||
|
}
|
||||||
|
_, err = conn.Write(rawMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "write request")
|
||||||
|
}
|
||||||
|
n, err := conn.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read response")
|
||||||
|
}
|
||||||
|
var response mDNS.Msg
|
||||||
|
err = response.Unpack(buffer[:n])
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "unpack response")
|
||||||
|
}
|
||||||
|
if response.Truncated && network == N.NetworkUDP {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
panic("unexpected")
|
||||||
}
|
}
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"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/hosts"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"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"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
|
||||||
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.DNSTransport = (*Transport)(nil)
|
|
||||||
|
|
||||||
type Transport struct {
|
|
||||||
dns.TransportAdapter
|
|
||||||
ctx context.Context
|
|
||||||
logger logger.ContextLogger
|
|
||||||
hosts *hosts.File
|
|
||||||
dialer N.Dialer
|
|
||||||
preferGo bool
|
|
||||||
fallback bool
|
|
||||||
dhcpTransport dhcpTransport
|
|
||||||
resolver net.Resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
type dhcpTransport interface {
|
|
||||||
adapter.DNSTransport
|
|
||||||
Fetch() ([]M.Socksaddr, error)
|
|
||||||
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
|
||||||
transportDialer, err := dns.NewLocalDialer(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
transportAdapter := dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options)
|
|
||||||
return &Transport{
|
|
||||||
TransportAdapter: transportAdapter,
|
|
||||||
ctx: ctx,
|
|
||||||
logger: logger,
|
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
|
||||||
dialer: transportDialer,
|
|
||||||
preferGo: options.PreferGo,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
|
||||||
if stage != adapter.StartStateStart {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
inboundManager := service.FromContext[adapter.InboundManager](t.ctx)
|
|
||||||
for _, inbound := range inboundManager.Inbounds() {
|
|
||||||
if inbound.Type() == C.TypeTun {
|
|
||||||
t.fallback = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !C.IsIos {
|
|
||||||
if t.fallback {
|
|
||||||
t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)
|
|
||||||
if t.dhcpTransport != nil {
|
|
||||||
err := t.dhcpTransport.Start(stage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
|
||||||
return common.Close(
|
|
||||||
t.dhcpTransport,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
question := message.Question[0]
|
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
|
||||||
addresses := t.hosts.Lookup(domain)
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !t.fallback {
|
|
||||||
return t.exchange(ctx, message, domain)
|
|
||||||
}
|
|
||||||
if !C.IsIos {
|
|
||||||
if t.dhcpTransport != nil {
|
|
||||||
dhcpTransports, _ := t.dhcpTransport.Fetch()
|
|
||||||
if len(dhcpTransports) > 0 {
|
|
||||||
return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.preferGo {
|
|
||||||
// Assuming the user knows what they are doing, we still execute the query which will fail.
|
|
||||||
return t.exchange(ctx, message, domain)
|
|
||||||
}
|
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
|
||||||
var network string
|
|
||||||
if question.Qtype == mDNS.TypeA {
|
|
||||||
network = "ip4"
|
|
||||||
} else {
|
|
||||||
network = "ip6"
|
|
||||||
}
|
|
||||||
addresses, err := t.resolver.LookupNetIP(ctx, network, domain)
|
|
||||||
if err != nil {
|
|
||||||
var dnsError *net.DNSError
|
|
||||||
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
|
||||||
return nil, dns.RcodeRefused
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
|
||||||
}
|
|
||||||
if C.IsIos {
|
|
||||||
return nil, E.New("only A and AAAA queries are supported on iOS and tvOS when using NetworkExtension.")
|
|
||||||
} else {
|
|
||||||
return nil, E.New("only A and AAAA queries are supported on macOS when using NetworkExtension and DHCP unavailable.")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
//go:build darwin && with_dhcp
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/dns/transport/dhcp"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport {
|
|
||||||
return dhcp.NewRawTransport(transportAdapter, ctx, dialer, logger)
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
//go:build darwin && !with_dhcp
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport {
|
|
||||||
return nil
|
|
||||||
}
|
|
205
dns/transport/local/local_fallback.go
Normal file
205
dns/transport/local/local_fallback.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewFallbackTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FallbackTransport struct {
|
||||||
|
adapter.DNSTransport
|
||||||
|
ctx context.Context
|
||||||
|
fallback bool
|
||||||
|
resolver net.Resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transport, err := NewTransport(ctx, logger, tag, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &FallbackTransport{
|
||||||
|
DNSTransport: transport,
|
||||||
|
ctx: ctx,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FallbackTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
platformInterface := service.FromContext[platform.Interface](f.ctx)
|
||||||
|
if platformInterface == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
inboundManager := service.FromContext[adapter.InboundManager](f.ctx)
|
||||||
|
for _, inbound := range inboundManager.Inbounds() {
|
||||||
|
if inbound.Type() == C.TypeTun {
|
||||||
|
// platform tun hijacks DNS, so we can only use cgo resolver here
|
||||||
|
f.fallback = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FallbackTransport) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
if !f.fallback {
|
||||||
|
return f.DNSTransport.Exchange(ctx, message)
|
||||||
|
}
|
||||||
|
question := message.Question[0]
|
||||||
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
var network string
|
||||||
|
if question.Qtype == mDNS.TypeA {
|
||||||
|
network = "ip4"
|
||||||
|
} else {
|
||||||
|
network = "ip6"
|
||||||
|
}
|
||||||
|
addresses, err := f.resolver.LookupNetIP(ctx, network, domain)
|
||||||
|
if err != nil {
|
||||||
|
var dnsError *net.DNSError
|
||||||
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
|
return nil, dns.RcodeRefused
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
} else if question.Qtype == mDNS.TypeNS {
|
||||||
|
records, err := f.resolver.LookupNS(ctx, domain)
|
||||||
|
if err != nil {
|
||||||
|
var dnsError *net.DNSError
|
||||||
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
|
return nil, dns.RcodeRefused
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response := &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: mDNS.RcodeSuccess,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
response.Answer = append(response.Answer, &mDNS.NS{
|
||||||
|
Hdr: mDNS.RR_Header{
|
||||||
|
Name: question.Name,
|
||||||
|
Rrtype: mDNS.TypeNS,
|
||||||
|
Class: mDNS.ClassINET,
|
||||||
|
Ttl: C.DefaultDNSTTL,
|
||||||
|
},
|
||||||
|
Ns: record.Host,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
} else if question.Qtype == mDNS.TypeCNAME {
|
||||||
|
cname, err := f.resolver.LookupCNAME(ctx, domain)
|
||||||
|
if err != nil {
|
||||||
|
var dnsError *net.DNSError
|
||||||
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
|
return nil, dns.RcodeRefused
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: mDNS.RcodeSuccess,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
Answer: []mDNS.RR{
|
||||||
|
&mDNS.CNAME{
|
||||||
|
Hdr: mDNS.RR_Header{
|
||||||
|
Name: question.Name,
|
||||||
|
Rrtype: mDNS.TypeCNAME,
|
||||||
|
Class: mDNS.ClassINET,
|
||||||
|
Ttl: C.DefaultDNSTTL,
|
||||||
|
},
|
||||||
|
Target: cname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else if question.Qtype == mDNS.TypeTXT {
|
||||||
|
records, err := f.resolver.LookupTXT(ctx, domain)
|
||||||
|
if err != nil {
|
||||||
|
var dnsError *net.DNSError
|
||||||
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
|
return nil, dns.RcodeRefused
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: mDNS.RcodeSuccess,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
Answer: []mDNS.RR{
|
||||||
|
&mDNS.TXT{
|
||||||
|
Hdr: mDNS.RR_Header{
|
||||||
|
Name: question.Name,
|
||||||
|
Rrtype: mDNS.TypeCNAME,
|
||||||
|
Class: mDNS.ClassINET,
|
||||||
|
Ttl: C.DefaultDNSTTL,
|
||||||
|
},
|
||||||
|
Txt: records,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else if question.Qtype == mDNS.TypeMX {
|
||||||
|
records, err := f.resolver.LookupMX(ctx, domain)
|
||||||
|
if err != nil {
|
||||||
|
var dnsError *net.DNSError
|
||||||
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
|
return nil, dns.RcodeRefused
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response := &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: mDNS.RcodeSuccess,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
response.Answer = append(response.Answer, &mDNS.MX{
|
||||||
|
Hdr: mDNS.RR_Header{
|
||||||
|
Name: question.Name,
|
||||||
|
Rrtype: mDNS.TypeA,
|
||||||
|
Class: mDNS.ClassINET,
|
||||||
|
Ttl: C.DefaultDNSTTL,
|
||||||
|
},
|
||||||
|
Preference: record.Pref,
|
||||||
|
Mx: record.Host,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
} else {
|
||||||
|
return nil, E.New("only A, AAAA, NS, CNAME, TXT, MX queries are supported on current platform when using TUN, please switch to a fixed DNS server.")
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResolvedResolver interface {
|
|
||||||
Start() error
|
|
||||||
Close() error
|
|
||||||
Object() any
|
|
||||||
Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
|
|
||||||
}
|
|
@ -1,230 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/service/resolved"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DBusResolvedResolver struct {
|
|
||||||
ctx context.Context
|
|
||||||
logger logger.ContextLogger
|
|
||||||
interfaceMonitor tun.DefaultInterfaceMonitor
|
|
||||||
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
|
||||||
systemBus *dbus.Conn
|
|
||||||
resoledObject atomic.Pointer[ResolvedObject]
|
|
||||||
closeOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResolvedObject struct {
|
|
||||||
dbus.BusObject
|
|
||||||
InterfaceIndex int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
|
|
||||||
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
|
|
||||||
if interfaceMonitor == nil {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
systemBus, err := dbus.SystemBus()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &DBusResolvedResolver{
|
|
||||||
ctx: ctx,
|
|
||||||
logger: logger,
|
|
||||||
interfaceMonitor: interfaceMonitor,
|
|
||||||
systemBus: systemBus,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) Start() error {
|
|
||||||
t.updateStatus()
|
|
||||||
t.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface)
|
|
||||||
err := t.systemBus.BusObject().AddMatchSignal(
|
|
||||||
"org.freedesktop.DBus",
|
|
||||||
"NameOwnerChanged",
|
|
||||||
dbus.WithMatchSender("org.freedesktop.DBus"),
|
|
||||||
dbus.WithMatchArg(0, "org.freedesktop.resolve1.Manager"),
|
|
||||||
).Err
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "configure resolved restart listener")
|
|
||||||
}
|
|
||||||
go t.loopUpdateStatus()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) Close() error {
|
|
||||||
t.closeOnce.Do(func() {
|
|
||||||
if t.interfaceCallback != nil {
|
|
||||||
t.interfaceMonitor.UnregisterCallback(t.interfaceCallback)
|
|
||||||
}
|
|
||||||
if t.systemBus != nil {
|
|
||||||
_ = t.systemBus.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) Object() any {
|
|
||||||
return common.PtrOrNil(t.resoledObject.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
question := message.Question[0]
|
|
||||||
resolvedObject := object.(*ResolvedObject)
|
|
||||||
call := resolvedObject.CallWithContext(
|
|
||||||
ctx,
|
|
||||||
"org.freedesktop.resolve1.Manager.ResolveRecord",
|
|
||||||
0,
|
|
||||||
resolvedObject.InterfaceIndex,
|
|
||||||
question.Name,
|
|
||||||
question.Qclass,
|
|
||||||
question.Qtype,
|
|
||||||
uint64(0),
|
|
||||||
)
|
|
||||||
if call.Err != nil {
|
|
||||||
var dbusError dbus.Error
|
|
||||||
if errors.As(call.Err, &dbusError) && dbusError.Name == "org.freedesktop.resolve1.NoNameServers" {
|
|
||||||
t.updateStatus()
|
|
||||||
}
|
|
||||||
return nil, E.Cause(call.Err, " resolve record via resolved")
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
records []resolved.ResourceRecord
|
|
||||||
outflags uint64
|
|
||||||
)
|
|
||||||
err := call.Store(&records, &outflags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response := &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Response: true,
|
|
||||||
Authoritative: true,
|
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: mDNS.RcodeSuccess,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
}
|
|
||||||
for _, record := range records {
|
|
||||||
var rr mDNS.RR
|
|
||||||
rr, _, err = mDNS.UnpackRR(record.Data, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "unpack resource record")
|
|
||||||
}
|
|
||||||
response.Answer = append(response.Answer, rr)
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) loopUpdateStatus() {
|
|
||||||
signalChan := make(chan *dbus.Signal, 1)
|
|
||||||
t.systemBus.Signal(signalChan)
|
|
||||||
for signal := range signalChan {
|
|
||||||
var restarted bool
|
|
||||||
if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" {
|
|
||||||
if len(signal.Body) != 3 || signal.Body[2].(string) == "" {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
restarted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if restarted {
|
|
||||||
t.updateStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) updateStatus() {
|
|
||||||
dbusObject, err := t.checkResolved(context.Background())
|
|
||||||
oldValue := t.resoledObject.Swap(dbusObject)
|
|
||||||
if err != nil {
|
|
||||||
var dbusErr dbus.Error
|
|
||||||
if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwnerCould" {
|
|
||||||
t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable"))
|
|
||||||
}
|
|
||||||
if oldValue != nil {
|
|
||||||
t.logger.Debug("systemd-resolved service is gone")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else if oldValue == nil {
|
|
||||||
t.logger.Debug("using systemd-resolved service as resolver")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObject, error) {
|
|
||||||
dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
|
|
||||||
err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defaultInterface := t.interfaceMonitor.DefaultInterface()
|
|
||||||
if defaultInterface == nil {
|
|
||||||
return nil, E.New("missing default interface")
|
|
||||||
}
|
|
||||||
call := dbusObject.(*dbus.Object).CallWithContext(
|
|
||||||
ctx,
|
|
||||||
"org.freedesktop.resolve1.Manager.GetLink",
|
|
||||||
0,
|
|
||||||
int32(defaultInterface.Index),
|
|
||||||
)
|
|
||||||
if call.Err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var linkPath dbus.ObjectPath
|
|
||||||
err = call.Store(&linkPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
linkObject := t.systemBus.Object("org.freedesktop.resolve1", linkPath)
|
|
||||||
if linkObject == nil {
|
|
||||||
return nil, E.New("missing link object for default interface")
|
|
||||||
}
|
|
||||||
dnsProp, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var linkDNS []resolved.LinkDNS
|
|
||||||
err = dnsProp.Store(&linkDNS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(linkDNS) == 0 {
|
|
||||||
for _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() {
|
|
||||||
if inbound.Type() == C.TypeTun {
|
|
||||||
return nil, E.New("No appropriate name servers or networks for name found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &ResolvedObject{
|
|
||||||
BusObject: dbusObject,
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
return &ResolvedObject{
|
|
||||||
BusObject: dbusObject,
|
|
||||||
InterfaceIndex: int32(defaultInterface.Index),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) {
|
|
||||||
t.updateStatus()
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
|
||||||
systemConfig := getSystemDNSConfig(t.ctx)
|
|
||||||
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
|
||||||
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
|
||||||
} else {
|
|
||||||
return t.exchangeParallel(ctx, systemConfig, message, domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
|
||||||
var lastErr error
|
|
||||||
for _, fqdn := range systemConfig.nameList(domain) {
|
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
return nil, lastErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, 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, systemConfig, fqdn, message)
|
|
||||||
if err == nil {
|
|
||||||
if response.Rcode != mDNS.RcodeSuccess {
|
|
||||||
err = dns.RcodeError(response.Rcode)
|
|
||||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
|
||||||
err = E.New(fqdn, ": empty result")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case results <- queryResult{response, err}:
|
|
||||||
case <-returned:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queryCtx, queryCancel := context.WithCancel(ctx)
|
|
||||||
defer queryCancel()
|
|
||||||
var nameCount int
|
|
||||||
for _, fqdn := range systemConfig.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, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
serverOffset := config.serverOffset()
|
|
||||||
sLen := uint32(len(config.servers))
|
|
||||||
var lastErr error
|
|
||||||
for i := 0; i < config.attempts; i++ {
|
|
||||||
for j := uint32(0); j < sLen; j++ {
|
|
||||||
server := config.servers[(serverOffset+j)%sLen]
|
|
||||||
question := message.Question[0]
|
|
||||||
question.Name = fqdn
|
|
||||||
response, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, E.Cause(lastErr, fqdn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
|
||||||
if server.Port == 0 {
|
|
||||||
server.Port = 53
|
|
||||||
}
|
|
||||||
var networks []string
|
|
||||||
if useTCP {
|
|
||||||
networks = []string{N.NetworkTCP}
|
|
||||||
} else {
|
|
||||||
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
|
||||||
}
|
|
||||||
request := &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: uint16(rand.Uint32()),
|
|
||||||
RecursionDesired: true,
|
|
||||||
AuthenticatedData: ad,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
Compress: true,
|
|
||||||
}
|
|
||||||
request.SetEdns0(buf.UDPBufferSize, false)
|
|
||||||
buffer := buf.Get(buf.UDPBufferSize)
|
|
||||||
defer buf.Put(buffer)
|
|
||||||
for _, network := range networks {
|
|
||||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
|
||||||
defer cancel()
|
|
||||||
conn, err := t.dialer.DialContext(ctx, network, server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
|
||||||
conn.SetDeadline(deadline)
|
|
||||||
}
|
|
||||||
rawMessage, err := request.PackBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "pack request")
|
|
||||||
}
|
|
||||||
_, err = conn.Write(rawMessage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "write request")
|
|
||||||
}
|
|
||||||
n, err := conn.Read(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read response")
|
|
||||||
}
|
|
||||||
var response mDNS.Msg
|
|
||||||
err = response.Unpack(buffer[:n])
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "unpack response")
|
|
||||||
}
|
|
||||||
if response.Truncated && network == N.NetworkUDP {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
panic("unexpected")
|
|
||||||
}
|
|
@ -10,6 +10,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// net.maxDNSPacketSize
|
||||||
|
maxDNSPacketSize = 1232
|
||||||
|
)
|
||||||
|
|
||||||
type resolverConfig struct {
|
type resolverConfig struct {
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
ch chan struct{}
|
ch chan struct{}
|
||||||
@ -99,7 +104,7 @@ func (c *dnsConfig) serverOffset() uint32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dnsConfig) nameList(name string) []string {
|
func (conf *dnsConfig) nameList(name string) []string {
|
||||||
l := len(name)
|
l := len(name)
|
||||||
rooted := l > 0 && name[l-1] == '.'
|
rooted := l > 0 && name[l-1] == '.'
|
||||||
if l > 254 || l == 254 && !rooted {
|
if l > 254 || l == 254 && !rooted {
|
||||||
@ -113,15 +118,15 @@ func (c *dnsConfig) nameList(name string) []string {
|
|||||||
return []string{name}
|
return []string{name}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasNdots := strings.Count(name, ".") >= c.ndots
|
hasNdots := strings.Count(name, ".") >= conf.ndots
|
||||||
name += "."
|
name += "."
|
||||||
// l++
|
// l++
|
||||||
|
|
||||||
names := make([]string, 0, 1+len(c.search))
|
names := make([]string, 0, 1+len(conf.search))
|
||||||
if hasNdots && !avoidDNS(name) {
|
if hasNdots && !avoidDNS(name) {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
for _, suffix := range c.search {
|
for _, suffix := range conf.search {
|
||||||
fqdn := name + suffix
|
fqdn := name + suffix
|
||||||
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
|
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
|
||||||
names = append(names, fqdn)
|
names = append(names, fqdn)
|
||||||
|
55
dns/transport/local/resolv_darwin_cgo.go
Normal file
55
dns/transport/local/resolv_darwin_cgo.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//go:build darwin && cgo
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <resolv.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||||
|
var state C.res_state
|
||||||
|
if C.res_ninit(state) != 0 {
|
||||||
|
return &dnsConfig{
|
||||||
|
servers: defaultNS,
|
||||||
|
search: dnsDefaultSearch(),
|
||||||
|
ndots: 1,
|
||||||
|
timeout: 5 * time.Second,
|
||||||
|
attempts: 2,
|
||||||
|
err: E.New("libresolv initialization failed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conf := &dnsConfig{
|
||||||
|
ndots: 1,
|
||||||
|
timeout: 5 * time.Second,
|
||||||
|
attempts: int(state.retry),
|
||||||
|
}
|
||||||
|
for i := 0; i < int(state.nscount); i++ {
|
||||||
|
ns := state.nsaddr_list[i]
|
||||||
|
addr := C.inet_ntoa(ns.sin_addr)
|
||||||
|
if addr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conf.servers = append(conf.servers, C.GoString(addr))
|
||||||
|
}
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
search := state.dnsrch[i]
|
||||||
|
if search == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
conf.search = append(conf.search, dns.Fqdn(C.GoString(search)))
|
||||||
|
}
|
||||||
|
return conf
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDNSReadConfig(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
require.NoError(t, dnsReadConfig(context.Background(), "/etc/resolv.conf").err)
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
//go:build !windows
|
//go:build !windows && !(darwin && cgo)
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ func RegisterTLS(registry *dns.TransportRegistry) {
|
|||||||
type TLSTransport struct {
|
type TLSTransport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
dialer tls.Dialer
|
dialer N.Dialer
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
@ -67,7 +67,7 @@ func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
|
|||||||
return &TLSTransport{
|
return &TLSTransport{
|
||||||
TransportAdapter: adapter,
|
TransportAdapter: adapter,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
dialer: tls.NewDialer(dialer, tlsConfig),
|
dialer: dialer,
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
@ -100,10 +100,15 @@ func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr)
|
tcpConn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
tlsConn, err := tls.ClientHandshake(ctx, tcpConn, t.tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
tcpConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return t.exchange(message, &tlsDNSConn{Conn: tlsConn})
|
return t.exchange(message, &tlsDNSConn{Conn: tlsConn})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ type TransportManager struct {
|
|||||||
transportByTag map[string]adapter.DNSTransport
|
transportByTag map[string]adapter.DNSTransport
|
||||||
dependByTag map[string][]string
|
dependByTag map[string][]string
|
||||||
defaultTransport adapter.DNSTransport
|
defaultTransport adapter.DNSTransport
|
||||||
defaultTransportFallback func() (adapter.DNSTransport, error)
|
defaultTransportFallback adapter.DNSTransport
|
||||||
fakeIPTransport adapter.FakeIPTransport
|
fakeIPTransport adapter.FakeIPTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *TransportManager) Initialize(defaultTransportFallback func() (adapter.DNSTransport, error)) {
|
func (m *TransportManager) Initialize(defaultTransportFallback adapter.DNSTransport) {
|
||||||
m.defaultTransportFallback = defaultTransportFallback
|
m.defaultTransportFallback = defaultTransportFallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,27 +56,14 @@ func (m *TransportManager) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
|
transports := m.transports
|
||||||
|
m.access.Unlock()
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
if m.defaultTag != "" && m.defaultTransport == nil {
|
if m.defaultTag != "" && m.defaultTransport == nil {
|
||||||
m.access.Unlock()
|
|
||||||
return E.New("default DNS server not found: ", m.defaultTag)
|
return E.New("default DNS server not found: ", m.defaultTag)
|
||||||
}
|
}
|
||||||
if m.defaultTransport == nil {
|
return m.startTransports(m.transports)
|
||||||
defaultTransport, err := m.defaultTransportFallback()
|
|
||||||
if err != nil {
|
|
||||||
m.access.Unlock()
|
|
||||||
return E.Cause(err, "default DNS server fallback")
|
|
||||||
}
|
|
||||||
m.transports = append(m.transports, defaultTransport)
|
|
||||||
m.transportByTag[defaultTransport.Tag()] = defaultTransport
|
|
||||||
m.defaultTransport = defaultTransport
|
|
||||||
}
|
|
||||||
transports := m.transports
|
|
||||||
m.access.Unlock()
|
|
||||||
return m.startTransports(transports)
|
|
||||||
} else {
|
} else {
|
||||||
transports := m.transports
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, outbound := range transports {
|
for _, outbound := range transports {
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -185,7 +172,11 @@ func (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) {
|
|||||||
func (m *TransportManager) Default() adapter.DNSTransport {
|
func (m *TransportManager) Default() adapter.DNSTransport {
|
||||||
m.access.RLock()
|
m.access.RLock()
|
||||||
defer m.access.RUnlock()
|
defer m.access.RUnlock()
|
||||||
|
if m.defaultTransport != nil {
|
||||||
return m.defaultTransport
|
return m.defaultTransport
|
||||||
|
} else {
|
||||||
|
return m.defaultTransportFallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *TransportManager) FakeIP() adapter.FakeIPTransport {
|
func (m *TransportManager) FakeIP() adapter.FakeIPTransport {
|
||||||
|
@ -2,242 +2,11 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.13.0-alpha.9
|
#### 1.12.0-rc.4
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.4
|
### 1.11.15
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.13.0-alpha.7
|
|
||||||
|
|
||||||
* Add reject support for ICMP echo supports **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
You can now reject, drop, or directly reply to ICMP echo (ping) requests using `reject` Route Action.
|
|
||||||
|
|
||||||
See [Route Action](/configuration/route/rule_action/#reject).
|
|
||||||
|
|
||||||
#### 1.13.0-alpha.6
|
|
||||||
|
|
||||||
* Add proxy support for ICMP echo requests **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
You can now match ICMP echo (ping) requests using the new `icmp` network in routing rules.
|
|
||||||
|
|
||||||
Such traffic originates from `TUN`, `WireGuard`, and `Tailscale` inbounds and can be routed to `Direct`, `WireGuard`, and `Tailscale` outbounds.
|
|
||||||
|
|
||||||
See [Route Rule](/configuration/route/rule/#network).
|
|
||||||
|
|
||||||
#### 1.12.3
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.13.0-alpha.4
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.12.2
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.13.0-alpha.3
|
|
||||||
|
|
||||||
* Improve `local` DNS server **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
On Apple platforms, Windows, and Linux (when using systemd-resolved),
|
|
||||||
`local` DNS server now works with Tun inbound which overrides system DNS servers.
|
|
||||||
|
|
||||||
See [Local DNS Server](/configuration/dns/server/local/).
|
|
||||||
|
|
||||||
#### 1.13.0-alpha.2
|
|
||||||
|
|
||||||
* Add `preferred_by` rule item **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
The new `preferred_by` routing rule item allows you to
|
|
||||||
match preferred domains and addresses for specific outbounds.
|
|
||||||
|
|
||||||
See [Route Rule](/configuration/route/rule/#preferred_by).
|
|
||||||
|
|
||||||
#### 1.13.0-alpha.1
|
|
||||||
|
|
||||||
* Add interface address rule items **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
New interface address rules allow you to dynamically adjust rules based on your network environment.
|
|
||||||
|
|
||||||
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
|
|
||||||
and [Headless Rule](/configuration/rule-set/headless-rule/).
|
|
||||||
|
|
||||||
#### 1.12.1
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
### 1.12.0
|
|
||||||
|
|
||||||
* Refactor DNS servers **1**
|
|
||||||
* Add domain resolver options**2**
|
|
||||||
* Add TLS fragment/record fragment support to route options and outbound TLS options **3**
|
|
||||||
* Add certificate options **4**
|
|
||||||
* Add Tailscale endpoint and DNS server **5**
|
|
||||||
* Drop support for go1.22 **6**
|
|
||||||
* Add AnyTLS protocol **7**
|
|
||||||
* Migrate to stdlib ECH implementation **8**
|
|
||||||
* Add NTP sniffer **9**
|
|
||||||
* Add wildcard SNI support for ShadowTLS inbound **10**
|
|
||||||
* Improve `auto_redirect` **11**
|
|
||||||
* Add control options for listeners **12**
|
|
||||||
* Add DERP service **13**
|
|
||||||
* Add Resolved service and DNS server **14**
|
|
||||||
* Add SSM API service **15**
|
|
||||||
* Add loopback address support for tun **16**
|
|
||||||
* Improve tun performance on Apple platforms **17**
|
|
||||||
* Update quic-go to v0.52.0
|
|
||||||
* Update gVisor to 20250319.0
|
|
||||||
* Update the status of graphical clients in stores **18**
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
DNS servers are refactored for better performance and scalability.
|
|
||||||
|
|
||||||
See [DNS server](/configuration/dns/server/).
|
|
||||||
|
|
||||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
|
||||||
|
|
||||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Legacy `outbound` DNS rules are deprecated
|
|
||||||
and can be replaced by the new `domain_resolver` option.
|
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver) and
|
|
||||||
[Route](/configuration/route/#default_domain_resolver).
|
|
||||||
|
|
||||||
For migration,
|
|
||||||
see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
See [Route Action](/configuration/route/rule_action/#tls_fragment) and [TLS](/configuration/shared/tls/).
|
|
||||||
|
|
||||||
**4**:
|
|
||||||
|
|
||||||
New certificate options allow you to manage the default list of trusted X509 CA certificates.
|
|
||||||
|
|
||||||
For the system certificate list, fixed Go not reading Android trusted certificates correctly.
|
|
||||||
|
|
||||||
You can also use the Mozilla Included List instead, or add trusted certificates yourself.
|
|
||||||
|
|
||||||
See [Certificate](/configuration/certificate/).
|
|
||||||
|
|
||||||
**5**:
|
|
||||||
|
|
||||||
See [Tailscale](/configuration/endpoint/tailscale/).
|
|
||||||
|
|
||||||
**6**:
|
|
||||||
|
|
||||||
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
|
||||||
|
|
||||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
|
||||||
|
|
||||||
**7**:
|
|
||||||
|
|
||||||
The new AnyTLS protocol claims to mitigate TLS proxy traffic characteristics and comes with a new multiplexing scheme.
|
|
||||||
|
|
||||||
See [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/configuration/outbound/anytls/).
|
|
||||||
|
|
||||||
**8**:
|
|
||||||
|
|
||||||
See [TLS](/configuration/shared/tls).
|
|
||||||
|
|
||||||
The build tag `with_ech` is no longer needed and has been removed.
|
|
||||||
|
|
||||||
**9**:
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/).
|
|
||||||
|
|
||||||
**10**:
|
|
||||||
|
|
||||||
See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).
|
|
||||||
|
|
||||||
**11**:
|
|
||||||
|
|
||||||
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
|
||||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
|
||||||
|
|
||||||
**12**:
|
|
||||||
|
|
||||||
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
|
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen/).
|
|
||||||
|
|
||||||
**13**:
|
|
||||||
|
|
||||||
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
|
||||||
|
|
||||||
See [DERP Service](/configuration/service/derp/).
|
|
||||||
|
|
||||||
**14**:
|
|
||||||
|
|
||||||
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
|
||||||
(e.g. NetworkManager) and provide DNS resolution.
|
|
||||||
|
|
||||||
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
|
|
||||||
|
|
||||||
**15**:
|
|
||||||
|
|
||||||
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
|
||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/).
|
|
||||||
|
|
||||||
**16**:
|
|
||||||
|
|
||||||
TUN now implements SideStore's StosVPN.
|
|
||||||
|
|
||||||
See [Tun](/configuration/inbound/tun/#loopback_address).
|
|
||||||
|
|
||||||
**17**:
|
|
||||||
|
|
||||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
|
||||||
|
|
||||||
The following data was tested using [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro.
|
|
||||||
|
|
||||||
| Version | Stack | MTU | Upload | Download |
|
|
||||||
|-------------|--------|-------|--------|----------|
|
|
||||||
| 1.11.15 | gvisor | 1500 | 852M | 2.57G |
|
|
||||||
| 1.12.0-rc.4 | gvisor | 1500 | 2.90G | 4.68G |
|
|
||||||
| 1.11.15 | gvisor | 4064 | 2.31G | 6.34G |
|
|
||||||
| 1.12.0-rc.4 | gvisor | 4064 | 7.54G | 12.2G |
|
|
||||||
| 1.11.15 | gvisor | 65535 | 27.6G | 18.1G |
|
|
||||||
| 1.12.0-rc.4 | gvisor | 65535 | 39.8G | 34.7G |
|
|
||||||
| 1.11.15 | system | 1500 | 664M | 706M |
|
|
||||||
| 1.12.0-rc.4 | system | 1500 | 2.44G | 2.51G |
|
|
||||||
| 1.11.15 | system | 4064 | 1.88G | 1.94G |
|
|
||||||
| 1.12.0-rc.4 | system | 4064 | 6.45G | 6.27G |
|
|
||||||
| 1.11.15 | system | 65535 | 26.2G | 17.4G |
|
|
||||||
| 1.12.0-rc.4 | system | 65535 | 17.6G | 21.0G |
|
|
||||||
|
|
||||||
**18**:
|
|
||||||
|
|
||||||
We continue to experience issues updating our sing-box apps on the App Store and Play Store.
|
|
||||||
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
|
||||||
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
|
||||||
|
|
||||||
#### 1.11.15
|
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -253,7 +22,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
||||||
|
|
||||||
#### 1.11.14
|
### 1.11.14
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -303,7 +72,7 @@ You can now choose what the DERP home page shows, just like with derper's `-home
|
|||||||
|
|
||||||
See [DERP](/configuration/service/derp/#home).
|
See [DERP](/configuration/service/derp/#home).
|
||||||
|
|
||||||
#### 1.11.13
|
### 1.11.13
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -341,7 +110,7 @@ SSM API service is a RESTful API server for managing Shadowsocks servers.
|
|||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/).
|
See [SSM API Service](/configuration/service/ssm-api/).
|
||||||
|
|
||||||
#### 1.11.11
|
### 1.11.11
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -373,7 +142,7 @@ You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fiel
|
|||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen/).
|
See [Listen Fields](/configuration/shared/listen/).
|
||||||
|
|
||||||
#### 1.11.10
|
### 1.11.10
|
||||||
|
|
||||||
* Undeprecate the `block` outbound **1**
|
* Undeprecate the `block` outbound **1**
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@ -391,7 +160,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
* Update quic-go to v0.51.0
|
* Update quic-go to v0.51.0
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.11.9
|
### 1.11.9
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -402,7 +171,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.11.8
|
### 1.11.8
|
||||||
|
|
||||||
* Improve `auto_redirect` **1**
|
* Improve `auto_redirect` **1**
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@ -419,7 +188,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.11.7
|
### 1.11.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -435,7 +204,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
||||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||||
|
|
||||||
#### 1.11.6
|
### 1.11.6
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -476,7 +245,7 @@ See [Protocol Sniff](/configuration/route/sniff/).
|
|||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
||||||
|
|
||||||
#### 1.11.5
|
### 1.11.5
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -492,7 +261,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
|
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
|
||||||
|
|
||||||
#### 1.11.4
|
### 1.11.4
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -548,7 +317,7 @@ Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to co
|
|||||||
|
|
||||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||||
|
|
||||||
#### 1.11.3
|
### 1.11.3
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -559,7 +328,7 @@ process._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.11.1
|
### 1.11.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -738,7 +507,7 @@ See [Hysteria2](/configuration/outbound/hysteria2/).
|
|||||||
|
|
||||||
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
||||||
|
|
||||||
#### 1.10.7
|
### 1.10.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -833,7 +602,7 @@ and the old outbound will be removed in sing-box 1.13.0.
|
|||||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
||||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||||
|
|
||||||
#### 1.10.2
|
### 1.10.2
|
||||||
|
|
||||||
* Add deprecated warnings
|
* Add deprecated warnings
|
||||||
* Fix proxying websocket connections in HTTP/mixed inbounds
|
* Fix proxying websocket connections in HTTP/mixed inbounds
|
||||||
@ -970,7 +739,7 @@ See [Rule Action](/configuration/route/rule_action/).
|
|||||||
* Update quic-go to v0.48.0
|
* Update quic-go to v0.48.0
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.10.1
|
### 1.10.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
@ -2,12 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
|
||||||
:material-plus: [default_interface_address](#default_interface_address)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [ip_accept_any](#ip_accept_any)
|
:material-plus: [ip_accept_any](#ip_accept_any)
|
||||||
@ -136,19 +130,6 @@ icon: material/alert-decagram
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
"interface_address": {
|
|
||||||
"en0": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"network_interface_address": {
|
|
||||||
"wifi": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default_interface_address": [
|
|
||||||
"2000::/3"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -378,36 +359,6 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
#### interface_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
|
||||||
|
|
||||||
Match interface address.
|
|
||||||
|
|
||||||
#### network_interface_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
|
||||||
|
|
||||||
Matches network interface (same values as `network_type`) address.
|
|
||||||
|
|
||||||
#### default_interface_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
|
||||||
|
|
||||||
Match default interface address.
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,12 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
|
||||||
:material-plus: [default_interface_address](#default_interface_address)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [ip_accept_any](#ip_accept_any)
|
:material-plus: [ip_accept_any](#ip_accept_any)
|
||||||
@ -136,19 +130,6 @@ icon: material/alert-decagram
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
"interface_address": {
|
|
||||||
"en0": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"network_interface_address": {
|
|
||||||
"wifi": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default_interface_address": [
|
|
||||||
"2000::/3"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -377,36 +358,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
#### interface_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
|
||||||
|
|
||||||
匹配接口地址。
|
|
||||||
|
|
||||||
#### network_interface_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
|
||||||
|
|
||||||
匹配网络接口(可用值同 `network_type`)地址。
|
|
||||||
|
|
||||||
#### default_interface_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
|
||||||
|
|
||||||
匹配默认接口地址。
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
|
||||||
|
|
||||||
:material-plus: [prefer_go](#prefer_go)
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
# Local
|
# Local
|
||||||
@ -19,7 +15,6 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"type": "local",
|
"type": "local",
|
||||||
"tag": "",
|
"tag": "",
|
||||||
"prefer_go": false
|
|
||||||
|
|
||||||
// Dial Fields
|
// Dial Fields
|
||||||
}
|
}
|
||||||
@ -33,29 +28,6 @@ icon: material/new-box
|
|||||||
* The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests.
|
* The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests.
|
||||||
* The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
* The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### prefer_go
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
When enabled, `local` DNS server will resolve DNS by dialing itself whenever possible.
|
|
||||||
|
|
||||||
Specifically, it disables following behaviors which was added as features in sing-box 1.13.0:
|
|
||||||
|
|
||||||
1. On Apple platforms: Attempt to resolve A/AAAA requests using `getaddrinfo` in NetworkExtension.
|
|
||||||
2. On Linux: Resolve through `systemd-resolvd`'s DBus interface when available.
|
|
||||||
|
|
||||||
As a sole exception, it cannot disable the following behavior:
|
|
||||||
|
|
||||||
1. In the Android graphical client,
|
|
||||||
`local` will always resolve DNS through the platform interface,
|
|
||||||
as there is no other way to obtain upstream DNS servers;
|
|
||||||
On devices running Android versions lower than 10, this interface can only resolve A/AAAA requests.
|
|
||||||
|
|
||||||
2. On macOS, `local` will try DHCP first in Network Extension, since DHCP respects DIal Fields,
|
|
||||||
it will not be disabled by `prefer_go`.
|
|
||||||
|
|
||||||
### Dial Fields
|
### Dial Fields
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
# Endpoint
|
# Endpoint
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
# 端点
|
# 端点
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
@ -57,7 +61,7 @@ WireGuard MTU。
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
接口的 IPv4/IPv6 地址或地址段的列表。
|
接口的 IPv4/IPv6 地址或地址段的列表您。
|
||||||
|
|
||||||
要分配给接口的 IP(v4 或 v6)地址段列表。
|
要分配给接口的 IP(v4 或 v6)地址段列表。
|
||||||
|
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
:material-plus: [server_ports](#server_ports)
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
:material-plus: [server_ports](#server_ports)
|
||||||
|
@ -88,7 +88,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
接口的 IPv4/IPv6 地址或地址段的列表。
|
接口的 IPv4/IPv6 地址或地址段的列表您。
|
||||||
|
|
||||||
要分配给接口的 IP(v4 或 v6)地址段列表。
|
要分配给接口的 IP(v4 或 v6)地址段列表。
|
||||||
|
|
||||||
|
@ -2,14 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
|
||||||
:material-plus: [default_interface_address](#default_interface_address)
|
|
||||||
:material-plus: [preferred_by](#preferred_by)
|
|
||||||
:material-alert: [network](#network)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@ -136,29 +128,12 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
"interface_address": {
|
|
||||||
"en0": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"network_interface_address": {
|
|
||||||
"wifi": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default_interface_address": [
|
|
||||||
"2000::/3"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
"wifi_bssid": [
|
"wifi_bssid": [
|
||||||
"00:00:00:00:00:00"
|
"00:00:00:00:00:00"
|
||||||
],
|
],
|
||||||
"preferred_by": [
|
|
||||||
"tailscale",
|
|
||||||
"wireguard"
|
|
||||||
],
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@ -227,15 +202,7 @@ Sniffed client type, see [Protocol Sniff](/configuration/route/sniff/) for detai
|
|||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
`tcp` or `udp`.
|
||||||
|
|
||||||
Since sing-box 1.13.0, you can match ICMP echo (ping) requests via the new `icmp` network.
|
|
||||||
|
|
||||||
Such traffic originates from `TUN`, `WireGuard`, and `Tailscale` inbounds and can be routed to `Direct`, `WireGuard`, and `Tailscale` outbounds.
|
|
||||||
|
|
||||||
Match network type.
|
|
||||||
|
|
||||||
`tcp`, `udp` or `icmp`.
|
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
|
||||||
@ -396,36 +363,6 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
#### interface_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
|
||||||
|
|
||||||
Match interface address.
|
|
||||||
|
|
||||||
#### network_interface_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
|
||||||
|
|
||||||
Matches network interface (same values as `network_type`) address.
|
|
||||||
|
|
||||||
#### default_interface_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
|
||||||
|
|
||||||
Match default interface address.
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@ -442,17 +379,6 @@ Match WiFi SSID.
|
|||||||
|
|
||||||
Match WiFi BSSID.
|
Match WiFi BSSID.
|
||||||
|
|
||||||
#### preferred_by
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
Match specified outbounds' preferred routes.
|
|
||||||
|
|
||||||
| Type | Match |
|
|
||||||
|-------------|-----------------------------------------------|
|
|
||||||
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
|
||||||
| `wireguard` | Match peers's allowed IPs |
|
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
@ -2,14 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
|
||||||
:material-plus: [default_interface_address](#default_interface_address)
|
|
||||||
:material-plus: [preferred_by](#preferred_by)
|
|
||||||
:material-alert: [network](#network)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@ -133,29 +125,12 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
"interface_address": {
|
|
||||||
"en0": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"network_interface_address": {
|
|
||||||
"wifi": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default_interface_address": [
|
|
||||||
"2000::/3"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
"wifi_bssid": [
|
"wifi_bssid": [
|
||||||
"00:00:00:00:00:00"
|
"00:00:00:00:00:00"
|
||||||
],
|
],
|
||||||
"preferred_by": [
|
|
||||||
"tailscale",
|
|
||||||
"wireguard"
|
|
||||||
],
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@ -224,15 +199,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
`tcp` 或 `udp`。
|
||||||
|
|
||||||
自 sing-box 1.13.0 起,您可以通过新的 `icmp` 网络匹配 ICMP 回显(ping)请求。
|
|
||||||
|
|
||||||
此类流量源自 `TUN`、`WireGuard` 和 `Tailscale` 入站,并可路由至 `Direct`、`WireGuard` 和 `Tailscale` 出站。
|
|
||||||
|
|
||||||
匹配网络类型。
|
|
||||||
|
|
||||||
`tcp`、`udp` 或 `icmp`。
|
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
|
||||||
@ -370,7 +337,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
匹配网络类型。
|
匹配网络类型。
|
||||||
|
|
||||||
可用值: `wifi`, `cellular`, `ethernet` and `other`.
|
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||||
|
|
||||||
#### network_is_expensive
|
#### network_is_expensive
|
||||||
|
|
||||||
@ -393,36 +360,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
#### interface_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
|
||||||
|
|
||||||
匹配接口地址。
|
|
||||||
|
|
||||||
#### network_interface_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
|
||||||
|
|
||||||
匹配网络接口(可用值同 `network_type`)地址。
|
|
||||||
|
|
||||||
#### default_interface_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
|
||||||
|
|
||||||
匹配默认接口地址。
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@ -439,17 +376,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
匹配 WiFi BSSID。
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
#### preferred_by
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
匹配制定出站的首选路由。
|
|
||||||
|
|
||||||
| 类型 | 匹配 |
|
|
||||||
|-------------|--------------------------------|
|
|
||||||
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
|
||||||
| `wireguard` | 匹配对端的 allowed IPs |
|
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
|
||||||
|
|
||||||
:material-alert: [reject](#reject)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
@ -46,10 +42,6 @@ See `route-options` fields below.
|
|||||||
|
|
||||||
### reject
|
### reject
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
|
||||||
|
|
||||||
Since sing-box 1.13.0, you can reject (or directly reply to) ICMP echo (ping) requests using `reject` action.
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "reject",
|
"action": "reject",
|
||||||
@ -66,17 +58,9 @@ For non-tun connections and already established connections, will just be closed
|
|||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
For TCP and UDP connections:
|
|
||||||
|
|
||||||
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
|
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
|
||||||
- `drop`: Drop packets.
|
- `drop`: Drop packets.
|
||||||
|
|
||||||
For ICMP echo requests:
|
|
||||||
|
|
||||||
- `default`: Reply with ICMP host unreachable.
|
|
||||||
- `drop`: Drop packets.
|
|
||||||
- `reply`: Reply with ICMP echo reply.
|
|
||||||
|
|
||||||
#### no_drop
|
#### no_drop
|
||||||
|
|
||||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
|
||||||
|
|
||||||
:material-alert: [reject](#reject)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
@ -42,10 +38,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
### reject
|
### reject
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
|
||||||
|
|
||||||
自 sing-box 1.13.0 起,您可以通过 `reject` 动作拒绝(或直接回复)ICMP 回显(ping)请求。
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "reject",
|
"action": "reject",
|
||||||
@ -62,17 +54,9 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
对于 TCP 和 UDP 连接:
|
|
||||||
|
|
||||||
- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。
|
- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。
|
||||||
- `drop`: 丢弃数据包。
|
- `drop`: 丢弃数据包。
|
||||||
|
|
||||||
对于 ICMP 回显请求:
|
|
||||||
|
|
||||||
- `default`: 回复 ICMP 主机不可达。
|
|
||||||
- `drop`: 丢弃数据包。
|
|
||||||
- `reply`: 回复以 ICMP 回显应答。
|
|
||||||
|
|
||||||
#### no_drop
|
#### no_drop
|
||||||
|
|
||||||
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
||||||
|
@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
|
||||||
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
|
||||||
:material-plus: [default_interface_address](#default_interface_address)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [network_type](#network_type)
|
:material-plus: [network_type](#network_type)
|
||||||
@ -83,14 +78,6 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
"network_interface_address": {
|
|
||||||
"wifi": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default_interface_address": [
|
|
||||||
"2000::/3"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -238,26 +225,6 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
#### network_interface_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
|
||||||
|
|
||||||
Matches network interface (same values as `network_type`) address.
|
|
||||||
|
|
||||||
#### default_interface_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.13.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
|
||||||
|
|
||||||
Match default interface address.
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
|
||||||
:material-plus: [default_interface_address](#default_interface_address)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [network_type](#network_type)
|
:material-plus: [network_type](#network_type)
|
||||||
@ -83,14 +78,6 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
"network_interface_address": {
|
|
||||||
"wifi": [
|
|
||||||
"2000::/3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default_interface_address": [
|
|
||||||
"2000::/3"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -234,26 +221,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
#### network_interface_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
|
||||||
|
|
||||||
匹配网络接口(可用值同 `network_type`)地址。
|
|
||||||
|
|
||||||
#### default_interface_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS.
|
|
||||||
|
|
||||||
匹配默认接口地址。
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
|
||||||
|
|
||||||
:material-plus: version `4`
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: version `3`
|
:material-plus: version `3`
|
||||||
@ -40,7 +36,6 @@ Version of rule-set.
|
|||||||
* 1: sing-box 1.8.0: Initial rule-set version.
|
* 1: sing-box 1.8.0: Initial rule-set version.
|
||||||
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
||||||
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
||||||
* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.
|
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user