mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-08-17 23:57:36 +08:00
Compare commits
102 Commits
dev-next
...
v1.12.0-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8413d1294d | ||
![]() |
c6e91daec7 | ||
![]() |
18dc7bbced | ||
![]() |
5fc634a55b | ||
![]() |
5f6aba9426 | ||
![]() |
6e6e5027bb | ||
![]() |
2b284b746a | ||
![]() |
a8b25bf537 | ||
![]() |
50e947ea49 | ||
![]() |
cfa9125eed | ||
![]() |
4c1b8909d8 | ||
![]() |
c06fe358e2 | ||
![]() |
4b90ada477 | ||
![]() |
a8a7cb2b4d | ||
![]() |
05390b9e91 | ||
![]() |
30cd4c614f | ||
![]() |
bd54f5e24c | ||
![]() |
f955fa0bd9 | ||
![]() |
21a9ef21e7 | ||
![]() |
e3f689612c | ||
![]() |
4873f6f66b | ||
![]() |
6b0e861afa | ||
![]() |
e32d686d6c | ||
![]() |
844308e128 | ||
![]() |
93c14db281 | ||
![]() |
b893a27dfc | ||
![]() |
d39960fa23 | ||
![]() |
ba0badd4bf | ||
![]() |
cfbb5d63d5 | ||
![]() |
8447a3edfe | ||
![]() |
1a9747a531 | ||
![]() |
583ecbea3b | ||
![]() |
bb6c8535a5 | ||
![]() |
10d90e4acc | ||
![]() |
e625012219 | ||
![]() |
670863fd5b | ||
![]() |
f7cf87142f | ||
![]() |
2597a68a01 | ||
![]() |
7354332daa | ||
![]() |
a0d382fc4e | ||
![]() |
a6da8b6654 | ||
![]() |
7385616cca | ||
![]() |
4b6784b446 | ||
![]() |
68579bb93b | ||
![]() |
6aace7b1b7 | ||
![]() |
148234b742 | ||
![]() |
97b7a451be | ||
![]() |
73b67e0b48 | ||
![]() |
88b4d04d59 | ||
![]() |
d1ec6c6dd2 | ||
![]() |
523825336a | ||
![]() |
032565a026 | ||
![]() |
aeea24ae30 | ||
![]() |
af22549f1a | ||
![]() |
57b17ceb4b | ||
![]() |
3dd308e7c3 | ||
![]() |
7f75195d86 | ||
![]() |
2fe4cad905 | ||
![]() |
f55eb75a53 | ||
![]() |
5ffb5b6ad2 | ||
![]() |
a1d5931759 | ||
![]() |
9e68e909cb | ||
![]() |
117e8b76cc | ||
![]() |
d2f83bfd50 | ||
![]() |
eaef13febe | ||
![]() |
0110c69dc9 | ||
![]() |
fb2f5af1fb | ||
![]() |
1553923118 | ||
![]() |
0ada49489d | ||
![]() |
95d5ca9393 | ||
![]() |
6cebbb4590 | ||
![]() |
0ef81bb5ef | ||
![]() |
0d30a1df9d | ||
![]() |
563499d2f9 | ||
![]() |
f10c0c1c8d | ||
![]() |
428074d88b | ||
![]() |
fa18832ad2 | ||
![]() |
87bce2de29 | ||
![]() |
f5020554e4 | ||
![]() |
31f3623b8a | ||
![]() |
bb42657177 | ||
![]() |
f19ff7eca7 | ||
![]() |
8e45133f2e | ||
![]() |
63df88675f | ||
![]() |
0423244298 | ||
![]() |
a5f1af9587 | ||
![]() |
112817c1a4 | ||
![]() |
6e91de51f1 | ||
![]() |
efc5c542fb | ||
![]() |
f1b569c7d1 | ||
![]() |
a752197d5e | ||
![]() |
65517d4513 | ||
![]() |
ccf4fa4d3a | ||
![]() |
18dbb823a1 | ||
![]() |
4ec058e91a | ||
![]() |
6eed06b2c2 | ||
![]() |
dd209cc9d5 | ||
![]() |
b0c0a6b07d | ||
![]() |
951a8fabbf | ||
![]() |
928298b528 | ||
![]() |
5b84fa0137 | ||
![]() |
2bb85ac8a1 |
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
|
||||||
|
62
.github/workflows/build.yml
vendored
62
.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.4
|
||||||
- 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.4
|
||||||
- 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
|
||||||
@ -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.4
|
||||||
- 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.4
|
||||||
- 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,15 +472,15 @@ 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.4
|
||||||
- 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: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Setup Xcode beta
|
- name: Setup Xcode beta
|
||||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@ -621,7 +615,7 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
|
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
@ -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.4
|
||||||
- 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.4
|
||||||
- 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.4
|
||||||
- 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
|
- transport/simple-obfs
|
||||||
- ineffassign
|
|
||||||
- paralleltest
|
|
||||||
- staticcheck
|
|
||||||
settings:
|
|
||||||
staticcheck:
|
|
||||||
checks:
|
|
||||||
- all
|
|
||||||
- -S1000
|
|
||||||
- -S1008
|
|
||||||
- -S1017
|
|
||||||
- -ST1003
|
|
||||||
- -QF1001
|
|
||||||
- -QF1003
|
|
||||||
- -QF1008
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
presets:
|
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
|
||||||
- std-error-handling
|
|
||||||
paths:
|
|
||||||
- transport/simple-obfs
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- gci
|
|
||||||
- gofumpt
|
|
||||||
settings:
|
|
||||||
gci:
|
|
||||||
sections:
|
|
||||||
- standard
|
|
||||||
- prefix(github.com/sagernet/)
|
|
||||||
- default
|
|
||||||
custom-order: true
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
paths:
|
|
||||||
- transport/simple-obfs
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
|
@ -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
|
||||||
|
26
Makefile
26
Makefile
@ -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
|
||||||
@ -108,16 +108,6 @@ upload_ios_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_ios_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
|
||||||
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
|
||||||
|
|
||||||
upload_ios_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
release_ios: build_ios upload_ios_app_store
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
@ -185,16 +175,6 @@ upload_tvos_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_tvos_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
|
||||||
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
|
||||||
|
|
||||||
upload_tvos_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
@ -245,8 +225,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.6
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
@ -53,11 +53,11 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// sniffer
|
// sniffer
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SniffError error
|
PacketSniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
@ -135,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
|
||||||
|
@ -2,7 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@ -19,11 +18,6 @@ type Outbound interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundWithPreferredRoutes interface {
|
|
||||||
PreferredDomain(domain string) bool
|
|
||||||
PreferredAddress(address netip.Addr) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
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.defaultOutbound == nil {
|
|
||||||
directOutbound, err := m.defaultOutboundFallback()
|
|
||||||
if err != nil {
|
|
||||||
m.access.Unlock()
|
|
||||||
return E.Cause(err, "create direct outbound for fallback")
|
|
||||||
}
|
|
||||||
m.outbounds = append(m.outbounds, directOutbound)
|
|
||||||
m.outboundByTag[directOutbound.Tag()] = directOutbound
|
|
||||||
m.defaultOutbound = directOutbound
|
|
||||||
}
|
|
||||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
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
|
||||||
}
|
}
|
||||||
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()
|
||||||
return m.defaultOutbound
|
if m.defaultOutbound != nil {
|
||||||
|
return m.defaultOutbound
|
||||||
|
} else {
|
||||||
|
return m.defaultOutboundFallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
10
box.go
10
box.go
@ -314,15 +314,15 @@ 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(common.Must1(
|
dnsTransportManager.Initialize(common.Must1(
|
||||||
local.NewTransport(
|
local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
@ -498,7 +498,7 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.internalService {
|
for _, lifecycleService := range s.internalService {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 6db8e06e8d6c77648e79e3f93bdd41a4d48cc319
|
Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0
|
@ -1 +1 @@
|
|||||||
Subproject commit c5734677bdfcba5d2d4faf10c4f10475077a82ab
|
Subproject commit ae5818ee5a24af965dc91f80bffa16e1e6c109c1
|
@ -177,7 +177,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
||||||
log.Info("waiting for process")
|
log.Info("waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
|
@ -16,17 +16,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
withTailscale bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -46,9 +44,8 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
iosTags []string
|
||||||
memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,9 +60,8 @@ func init() {
|
|||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
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")
|
||||||
darwinTags = append(darwinTags, "with_dhcp")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,10 +103,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...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,10 +151,7 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
}
|
|
||||||
if !withTailscale {
|
|
||||||
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@ -169,10 +160,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
if withTailscale {
|
|
||||||
tags = append(tags, memcTags...)
|
|
||||||
}
|
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
@ -1,284 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/include"
|
|
||||||
"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/json"
|
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
|
||||||
|
|
||||||
var iperf3Path string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := main0()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main0() error {
|
|
||||||
err := shell.Exec("sudo", "ls").Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
results, err := runTests()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoder := json.NewEncoder(os.Stdout)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
return encoder.Encode(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTests() ([]TestResult, error) {
|
|
||||||
boxPaths := []string{
|
|
||||||
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
|
|
||||||
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
|
||||||
"./sing-box",
|
|
||||||
}
|
|
||||||
stacks := []string{
|
|
||||||
"gvisor",
|
|
||||||
"system",
|
|
||||||
}
|
|
||||||
mtus := []int{
|
|
||||||
1500,
|
|
||||||
4064,
|
|
||||||
// 16384,
|
|
||||||
// 32768,
|
|
||||||
// 49152,
|
|
||||||
65535,
|
|
||||||
}
|
|
||||||
flagList := [][]string{
|
|
||||||
{},
|
|
||||||
}
|
|
||||||
var results []TestResult
|
|
||||||
for _, boxPath := range boxPaths {
|
|
||||||
for _, stack := range stacks {
|
|
||||||
for _, mtu := range mtus {
|
|
||||||
if strings.HasPrefix(boxPath, ".") {
|
|
||||||
for _, flags := range flagList {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestResult struct {
|
|
||||||
BoxPath string `json:"box_path"`
|
|
||||||
Stack string `json:"stack"`
|
|
||||||
MTU int `json:"mtu"`
|
|
||||||
Flags []string `json:"flags"`
|
|
||||||
MultiThread bool `json:"multi_thread"`
|
|
||||||
UploadSpeed string `json:"upload_speed"`
|
|
||||||
DownloadSpeed string `json:"download_speed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
|
|
||||||
testAddress := netip.MustParseAddr("1.1.1.1")
|
|
||||||
testConfig := option.Options{
|
|
||||||
Inbounds: []option.Inbound{
|
|
||||||
{
|
|
||||||
Type: C.TypeTun,
|
|
||||||
Options: &option.TunInboundOptions{
|
|
||||||
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
|
|
||||||
AutoRoute: true,
|
|
||||||
MTU: uint32(mtu),
|
|
||||||
Stack: stackName,
|
|
||||||
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Route: &option.RouteOptions{
|
|
||||||
Rules: []option.Rule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultRule{
|
|
||||||
RawDefaultRule: option.RawDefaultRule{
|
|
||||||
IPCIDR: []string{testAddress.String()},
|
|
||||||
},
|
|
||||||
RuleAction: option.RuleAction{
|
|
||||||
Action: C.RuleActionTypeRouteOptions,
|
|
||||||
RouteOptionsOptions: option.RouteOptionsActionOptions{
|
|
||||||
OverrideAddress: "127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AutoDetectInterface: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ctx := include.Context(context.Background())
|
|
||||||
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(tempConfig.Name())
|
|
||||||
encoder := json.NewEncoderContext(ctx, tempConfig)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
err = encoder.Encode(testConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "encode test config")
|
|
||||||
}
|
|
||||||
tempConfig.Close()
|
|
||||||
var sudoArgs []string
|
|
||||||
if len(flags) > 0 {
|
|
||||||
sudoArgs = append(sudoArgs, "env")
|
|
||||||
sudoArgs = append(sudoArgs, flags...)
|
|
||||||
}
|
|
||||||
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
|
||||||
boxProcess := shell.Exec("sudo", sudoArgs...)
|
|
||||||
boxProcess.Stdout = &stderrWriter{}
|
|
||||||
boxProcess.Stderr = io.Discard
|
|
||||||
err = boxProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if C.IsDarwin {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3-darwin")
|
|
||||||
} else {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverProcess := shell.Exec(iperf3Path, "-s")
|
|
||||||
serverProcess.Stdout = io.Discard
|
|
||||||
serverProcess.Stderr = io.Discard
|
|
||||||
err = serverProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "start iperf3 server")
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
args := []string{"-c", testAddress.String()}
|
|
||||||
if multiThread {
|
|
||||||
args = append(args, "-P", "10")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadProcess := shell.Exec(iperf3Path, args...)
|
|
||||||
output, err := uploadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
|
|
||||||
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
|
|
||||||
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
|
|
||||||
|
|
||||||
result = &TestResult{
|
|
||||||
BoxPath: boxPath,
|
|
||||||
Stack: stackName,
|
|
||||||
MTU: mtu,
|
|
||||||
Flags: flags,
|
|
||||||
MultiThread: multiThread,
|
|
||||||
UploadSpeed: uploadResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
|
|
||||||
output, err = downloadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
|
|
||||||
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
|
|
||||||
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
|
|
||||||
|
|
||||||
result.DownloadSpeed = downloadResult
|
|
||||||
|
|
||||||
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
|
|
||||||
if len(flags) > 0 {
|
|
||||||
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
|
|
||||||
}
|
|
||||||
if multiThread {
|
|
||||||
printArgs = append(printArgs, "(-P 10)")
|
|
||||||
}
|
|
||||||
fmt.Println(printArgs...)
|
|
||||||
err = boxProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = serverProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
boxProcess.Cmd.Wait()
|
|
||||||
close(boxDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
serverDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
serverProcess.Process.Wait()
|
|
||||||
close(serverDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-boxDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
boxProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("box process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-serverDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
serverProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("server process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type stderrWriter struct{}
|
|
||||||
|
|
||||||
func (w *stderrWriter) Write(p []byte) (n int, err error) {
|
|
||||||
return os.Stderr.Write(p)
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
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"
|
||||||
@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
var rules []option.HeadlessRule
|
var rules []option.HeadlessRule
|
||||||
switch flagRuleSetConvertType {
|
switch flagRuleSetConvertType {
|
||||||
case "adguard":
|
case "adguard":
|
||||||
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
rules, err = adguard.Convert(reader)
|
||||||
case "":
|
case "":
|
||||||
return E.New("source type is required")
|
return E.New("source type is required")
|
||||||
default:
|
default:
|
||||||
|
@ -6,10 +6,7 @@ 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"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.AdGuardDomain) > 0
|
|
||||||
}) {
|
|
||||||
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if hasRule(rule.LogicalOptions.Rules, cond) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,6 @@ package adguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@ -10,10 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
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/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ type agdguardRuleLine struct {
|
|||||||
isImportant bool
|
isImportant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
|
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
var (
|
var (
|
||||||
ruleLines []agdguardRuleLine
|
ruleLines []agdguardRuleLine
|
||||||
@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
|
|||||||
parseLine:
|
parseLine:
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ruleLine := scanner.Text()
|
ruleLine := scanner.Text()
|
||||||
if ruleLine == "" {
|
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originRuleLine := ruleLine
|
originRuleLine := ruleLine
|
||||||
@ -96,7 +92,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if !ignored {
|
if !ignored {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||||
continue parseLine
|
continue parseLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,35 +120,27 @@ parseLine:
|
|||||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||||
if ignoreIPCIDRRegexp(ruleLine) {
|
if ignoreIPCIDRRegexp(ruleLine) {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isRegexp = true
|
isRegexp = true
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(ruleLine, "://") {
|
if strings.Contains(ruleLine, "://") {
|
||||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||||
isSuffix = true
|
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "/") {
|
if strings.Contains(ruleLine, "/") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
if strings.Contains(ruleLine, "##") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
|
if strings.Contains(ruleLine, "#$#") {
|
||||||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
|
|
||||||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
|
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(ruleLine, "~") {
|
|
||||||
ignoredLines++
|
|
||||||
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var domainCheck string
|
var domainCheck string
|
||||||
@ -163,7 +151,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if ruleLine == "" {
|
if ruleLine == "" {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||||
@ -171,13 +159,13 @@ parseLine:
|
|||||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||||
if ipErr == nil {
|
if ipErr == nil {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||||
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||||
}
|
}
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
continue
|
continue
|
||||||
@ -295,112 +283,10 @@ parseLine:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ignoredLines > 0 {
|
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||||
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
|
||||||
}
|
|
||||||
return []option.HeadlessRule{currentRule}, nil
|
return []option.HeadlessRule{currentRule}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
|
||||||
|
|
||||||
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
|
||||||
if len(rules) != 1 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule := rules[0]
|
|
||||||
var (
|
|
||||||
importantDomain []string
|
|
||||||
importantDomainRegex []string
|
|
||||||
importantExcludeDomain []string
|
|
||||||
importantExcludeDomainRegex []string
|
|
||||||
domain []string
|
|
||||||
domainRegex []string
|
|
||||||
excludeDomain []string
|
|
||||||
excludeDomainRegex []string
|
|
||||||
)
|
|
||||||
parse:
|
|
||||||
for {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
|
||||||
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule = rule.LogicalOptions.Rules[1]
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
domain = rule.DefaultOptions.AdGuardDomain
|
|
||||||
domainRegex = rule.DefaultOptions.DomainRegex
|
|
||||||
if len(domain)+len(domainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
break parse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var output bytes.Buffer
|
|
||||||
for _, ruleLine := range importantDomain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantDomainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
return output.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||||
ruleLine = ruleLine[12:]
|
ruleLine = ruleLine[12:]
|
||||||
@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
|||||||
ruleLine = ruleLine[13:]
|
ruleLine = ruleLine[13:]
|
||||||
} else if strings.HasPrefix(ruleLine, "^") {
|
} else if strings.HasPrefix(ruleLine, "^") {
|
||||||
ruleLine = ruleLine[1:]
|
ruleLine = ruleLine[1:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
||||||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
return parseErr == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
||||||
@ -454,5 +342,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
|
||||||
}
|
}
|
@ -7,15 +7,13 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
func TestConverter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ruleString := `||sagernet.org^$important
|
rules, err := Convert(strings.NewReader(`
|
||||||
@@|sing-box.sagernet.org^$important
|
|
||||||
||example.org^
|
||example.org^
|
||||||
|example.com^
|
|example.com^
|
||||||
example.net^
|
example.net^
|
||||||
@ -23,9 +21,10 @@ example.net^
|
|||||||
||example.edu.tw^
|
||example.edu.tw^
|
||||||
|example.gov
|
|example.gov
|
||||||
example.arpa
|
example.arpa
|
||||||
@@|sagernet.example.org^
|
@@|sagernet.example.org|
|
||||||
`
|
||sagernet.org^$important
|
||||||
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
@@|sing-box.sagernet.org^$important
|
||||||
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@ -76,18 +75,15 @@ example.arpa
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}), domain)
|
}), domain)
|
||||||
}
|
}
|
||||||
ruleFromOptions, err := FromOptions(rules)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, ruleString, string(ruleFromOptions))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
::1 localhost #[IPv6]
|
::1 localhost #[IPv6]
|
||||||
0.0.0.0 google.com
|
0.0.0.0 google.com
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimpleHosts(t *testing.T) {
|
func TestSimpleHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
example.com
|
example.com
|
||||||
www.example.org
|
www.example.org
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
@ -271,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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"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,11 +23,9 @@ 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{}
|
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
closeOnce sync.Once
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,30 +44,26 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
network: network,
|
network: network,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
create: make(chan struct{}),
|
create: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
select {
|
||||||
return conn.Read(b)
|
case <-c.create:
|
||||||
}
|
if c.err != nil {
|
||||||
select {
|
return 0, c.err
|
||||||
case <-c.create:
|
}
|
||||||
if c.err != nil {
|
case <-c.ctx.Done():
|
||||||
return 0, c.err
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
return c.conn.Load().Read(b)
|
|
||||||
case <-c.done:
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
}
|
}
|
||||||
|
return c.conn.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
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,16 +72,14 @@ 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:
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
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)
|
||||||
@ -96,87 +87,74 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
c.closeOnce.Do(func() {
|
return common.Close(c.conn)
|
||||||
close(c.done)
|
|
||||||
conn := c.conn.Load()
|
|
||||||
if conn != nil {
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
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 {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.ctx.Done():
|
||||||
return 0, c.err
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn.Load())
|
return bufio.Copy(w, c.conn)
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -76,8 +76,6 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
// rup8(sizeof(xtcpcb_n))
|
// rup8(sizeof(xtcpcb_n))
|
||||||
itemSize += 208
|
itemSize += 208
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackUDPProcess string
|
|
||||||
// skip the first xinpgen(24 bytes) block
|
// skip the first xinpgen(24 bytes) block
|
||||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||||
// offset of xinpcb_n and xsocket_n
|
// offset of xinpcb_n and xsocket_n
|
||||||
@ -92,34 +90,24 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
flag := buf[inp+44]
|
flag := buf[inp+44]
|
||||||
|
|
||||||
var srcIP netip.Addr
|
var srcIP netip.Addr
|
||||||
srcIsIPv4 := false
|
|
||||||
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
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip == srcIP {
|
if ip != srcIP {
|
||||||
// xsocket_n.so_last_pid
|
continue
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
|
||||||
return getExecPathFromPID(pid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// udp packet connection may be not equal with srcIP
|
// xsocket_n.so_last_pid
|
||||||
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
return getExecPathFromPID(pid)
|
||||||
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
|
||||||
return fallbackUDPProcess, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ErrNotFound
|
return "", ErrNotFound
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -219,66 +215,22 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
case ruleItemWIFIBSSID:
|
case ruleItemWIFIBSSID:
|
||||||
rule.WIFIBSSID, err = readRuleItemString(reader)
|
rule.WIFIBSSID, err = readRuleItemString(reader)
|
||||||
case ruleItemAdGuardDomain:
|
case ruleItemAdGuardDomain:
|
||||||
|
if recover {
|
||||||
|
err = E.New("unable to decompile binary AdGuard rules to rule-set")
|
||||||
|
return
|
||||||
|
}
|
||||||
var matcher *domain.AdGuardMatcher
|
var matcher *domain.AdGuardMatcher
|
||||||
matcher, err = domain.ReadAdGuardMatcher(reader)
|
matcher, err = domain.ReadAdGuardMatcher(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rule.AdGuardDomainMatcher = matcher
|
rule.AdGuardDomainMatcher = matcher
|
||||||
if recover {
|
|
||||||
rule.AdGuardDomain = matcher.Dump()
|
|
||||||
}
|
|
||||||
case ruleItemNetworkType:
|
case ruleItemNetworkType:
|
||||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||||
case ruleItemNetworkIsExpensive:
|
case ruleItemNetworkIsExpensive:
|
||||||
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, 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, 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 +347,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,67 +355,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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
@ -5,13 +5,13 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
@ -37,38 +37,7 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type acmeLogWriter struct {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(logLine, "error: "):
|
|
||||||
w.logger.Error(logLine[7:])
|
|
||||||
case strings.HasPrefix(logLine, "warn: "):
|
|
||||||
w.logger.Warn(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "info: "):
|
|
||||||
w.logger.Info(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "debug: "):
|
|
||||||
w.logger.Debug(logLine[7:])
|
|
||||||
default:
|
|
||||||
w.logger.Debug(logLine)
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encoderConfig() zapcore.EncoderConfig {
|
|
||||||
config := zap.NewProductionEncoderConfig()
|
|
||||||
config.TimeKey = zapcore.OmitKey
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case "", "letsencrypt":
|
case "", "letsencrypt":
|
||||||
@ -89,15 +58,14 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
zapLogger := zap.New(zapcore.NewCore(
|
|
||||||
zapcore.NewConsoleEncoder(encoderConfig()),
|
|
||||||
&acmeLogWriter{logger: logger},
|
|
||||||
zap.DebugLevel,
|
|
||||||
))
|
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
DefaultServerName: options.DefaultServerName,
|
DefaultServerName: options.DefaultServerName,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Logger: zapLogger,
|
Logger: zap.New(zapcore.NewCore(
|
||||||
|
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
os.Stderr,
|
||||||
|
zap.InfoLevel,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
acmeConfig := certmagic.ACMEIssuer{
|
acmeConfig := certmagic.ACMEIssuer{
|
||||||
CA: acmeServer,
|
CA: acmeServer,
|
||||||
@ -107,7 +75,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
Logger: zapLogger,
|
Logger: config.Logger,
|
||||||
}
|
}
|
||||||
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
||||||
var solver certmagic.DNS01Solver
|
var solver certmagic.DNS01Solver
|
||||||
@ -135,7 +103,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
},
|
},
|
||||||
Logger: zapLogger,
|
|
||||||
})
|
})
|
||||||
config = certmagic.New(cache, *config)
|
config = certmagic.New(cache, *config)
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
@ -9,9 +9,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,10 @@ 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"
|
||||||
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"
|
||||||
@ -15,7 +14,7 @@ import (
|
|||||||
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
|
||||||
}
|
}
|
||||||
@ -54,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
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
var echConfig []byte
|
var echConfig []byte
|
||||||
if len(options.ECH.Config) > 0 {
|
if len(options.ECH.Config) > 0 {
|
||||||
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
||||||
@ -45,12 +45,12 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
|
|||||||
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
return nil, E.New("invalid ECH configs pem")
|
return nil, E.New("invalid ECH configs pem")
|
||||||
}
|
}
|
||||||
clientConfig.SetECHConfigList(block.Bytes)
|
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return clientConfig, nil
|
return &STDClientConfig{tlsConfig}, nil
|
||||||
} else {
|
} else {
|
||||||
return &ECHClientConfig{
|
return &STDECHClientConfig{
|
||||||
ECHCapableConfig: clientConfig,
|
STDClientConfig: STDClientConfig{tlsConfig},
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,15 +102,15 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type STDECHClientConfig struct {
|
||||||
ECHCapableConfig
|
STDClientConfig
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
lastTTL time.Duration
|
lastTTL time.Duration
|
||||||
lastUpdate time.Time
|
lastUpdate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -122,17 +122,17 @@ func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (a
|
|||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) 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.config.EncryptedClientHelloConfigList) == 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,
|
||||||
},
|
},
|
||||||
Question: []mDNS.Question{
|
Question: []mDNS.Question{
|
||||||
{
|
{
|
||||||
Name: mDNS.Fqdn(s.ServerName()),
|
Name: mDNS.Fqdn(s.config.ServerName),
|
||||||
Qtype: mDNS.TypeHTTPS,
|
Qtype: mDNS.TypeHTTPS,
|
||||||
Qclass: mDNS.ClassINET,
|
Qclass: mDNS.ClassINET,
|
||||||
},
|
},
|
||||||
@ -157,21 +157,21 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
|
|||||||
}
|
}
|
||||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
||||||
s.lastUpdate = time.Now()
|
s.lastUpdate = time.Now()
|
||||||
s.SetECHConfigList(echConfigList)
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
break match
|
break match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(s.ECHConfigList()) == 0 {
|
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||||
return nil, E.New("no ECH config found in DNS records")
|
return nil, E.New("no ECH config found in DNS records")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.Client(conn)
|
return s.Client(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) Clone() Config {
|
func (s *STDECHClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
155
common/tls/ech_keygen.go
Normal file
155
common/tls/ech_keygen.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
|
||||||
|
cipherSuites := []echCipherSuite{
|
||||||
|
{
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_AES128GCM,
|
||||||
|
}, {
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_ChaCha20Poly1305,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
keyConfig := []myECHKeyConfig{
|
||||||
|
{id: 0, kem: hpke.KEM_X25519_HKDF_SHA256},
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var configBuffer bytes.Buffer
|
||||||
|
var totalLen uint16
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
totalLen += uint16(len(keyPair.rawConf))
|
||||||
|
}
|
||||||
|
binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
configBuffer.Write(keyPair.rawConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyBuffer bytes.Buffer
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
keyBuffer.Write(keyPair.rawKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
|
||||||
|
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type echKeyConfigPair struct {
|
||||||
|
id uint8
|
||||||
|
rawKey []byte
|
||||||
|
conf myECHKeyConfig
|
||||||
|
rawConf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type echCipherSuite struct {
|
||||||
|
kdf hpke.KDF
|
||||||
|
aead hpke.AEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
type myECHKeyConfig struct {
|
||||||
|
id uint8
|
||||||
|
kem hpke.KEM
|
||||||
|
seed []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
|
||||||
|
be := binary.BigEndian
|
||||||
|
// prepare for future update
|
||||||
|
if version != 0xfe0d {
|
||||||
|
return nil, E.New("unsupported ECH version", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
suiteBuf := make([]byte, 0, len(suite)*4+2)
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
|
||||||
|
for _, s := range suite {
|
||||||
|
if !s.kdf.IsValid() || !s.aead.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE cipher suite")
|
||||||
|
}
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf))
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead))
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := []echKeyConfigPair{}
|
||||||
|
for _, c := range conf {
|
||||||
|
pair := echKeyConfigPair{}
|
||||||
|
pair.id = c.id
|
||||||
|
pair.conf = c
|
||||||
|
|
||||||
|
if !c.kem.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE KEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
kpGenerator := c.kem.Scheme().GenerateKeyPair
|
||||||
|
if len(c.seed) > 0 {
|
||||||
|
kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) {
|
||||||
|
pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed)
|
||||||
|
return pub, sec, nil
|
||||||
|
}
|
||||||
|
if len(c.seed) < c.kem.Scheme().PrivateKeySize() {
|
||||||
|
return nil, E.New("HPKE KEM seed too short")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, sec, err := kpGenerator()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "generate ECH config key pair")
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
b = be.AppendUint16(b, version)
|
||||||
|
b = be.AppendUint16(b, 0) // length field
|
||||||
|
// contents
|
||||||
|
// key config
|
||||||
|
b = append(b, c.id)
|
||||||
|
b = be.AppendUint16(b, uint16(c.kem))
|
||||||
|
pubBuf, err := pub.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH public key")
|
||||||
|
}
|
||||||
|
b = be.AppendUint16(b, uint16(len(pubBuf)))
|
||||||
|
b = append(b, pubBuf...)
|
||||||
|
|
||||||
|
b = append(b, suiteBuf...)
|
||||||
|
// end key config
|
||||||
|
// max name len, not supported
|
||||||
|
b = append(b, 0)
|
||||||
|
// server name
|
||||||
|
b = append(b, byte(len(serverName)))
|
||||||
|
b = append(b, []byte(serverName)...)
|
||||||
|
// extensions, not supported
|
||||||
|
b = be.AppendUint16(b, 0)
|
||||||
|
|
||||||
|
be.PutUint16(b[2:], uint16(len(b)-4))
|
||||||
|
|
||||||
|
pair.rawConf = b
|
||||||
|
|
||||||
|
secBuf, err := sec.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH private key")
|
||||||
|
}
|
||||||
|
sk := []byte{}
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
|
sk = append(sk, secBuf...)
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(b)))
|
||||||
|
sk = append(sk, b...)
|
||||||
|
pair.rawKey = sk
|
||||||
|
|
||||||
|
pairs = append(pairs, pair)
|
||||||
|
}
|
||||||
|
return pairs, nil
|
||||||
|
}
|
@ -1,81 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdh"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/pem"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ECHCapableConfig interface {
|
|
||||||
Config
|
|
||||||
ECHConfigList() []byte
|
|
||||||
SetECHConfigList([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {
|
|
||||||
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configBuilder := cryptobyte.NewBuilder(nil)
|
|
||||||
configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echConfig)
|
|
||||||
})
|
|
||||||
configBytes, err := configBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
keyBuilder := cryptobyte.NewBuilder(nil)
|
|
||||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echKey.Bytes())
|
|
||||||
})
|
|
||||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echConfig)
|
|
||||||
})
|
|
||||||
keyBytes, err := keyBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes}))
|
|
||||||
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) {
|
|
||||||
const extensionEncryptedClientHello = 0xfe0d
|
|
||||||
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
|
||||||
const KDF_HKDF_SHA256 = 0x0001
|
|
||||||
builder := cryptobyte.NewBuilder(nil)
|
|
||||||
builder.AddUint16(extensionEncryptedClientHello)
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddUint8(id)
|
|
||||||
|
|
||||||
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(pubKey)
|
|
||||||
})
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
const (
|
|
||||||
AEAD_AES_128_GCM = 0x0001
|
|
||||||
AEAD_AES_256_GCM = 0x0002
|
|
||||||
AEAD_ChaCha20Poly1305 = 0x0003
|
|
||||||
)
|
|
||||||
for _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} {
|
|
||||||
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
|
|
||||||
builder.AddUint16(aeadID)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
builder.AddUint8(maxNameLen)
|
|
||||||
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes([]byte(publicName))
|
|
||||||
})
|
|
||||||
builder.AddUint16(0) // extensions
|
|
||||||
})
|
|
||||||
return builder.Bytes()
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
|||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID}, nil
|
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
|
@ -7,60 +7,43 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
ctx context.Context
|
config *tls.Config
|
||||||
config *tls.Config
|
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) ServerName() string {
|
func (s *STDClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return s.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetServerName(serverName string) {
|
func (s *STDClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
s.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) NextProtos() []string {
|
func (s *STDClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return s.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetNextProtos(nextProto []string) {
|
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
|
||||||
c.config.NextProtos = nextProto
|
s.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Config() (*STDConfig, error) {
|
func (s *STDClientConfig) Config() (*STDConfig, error) {
|
||||||
return c.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return tls.Client(conn, s.config), nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return tls.Client(conn, c.config), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
@ -77,7 +60,9 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
@ -86,16 +71,12 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
tlsConfig.InsecureSkipVerify = true
|
tlsConfig.InsecureSkipVerify = true
|
||||||
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
||||||
verifyOptions := x509.VerifyOptions{
|
verifyOptions := x509.VerifyOptions{
|
||||||
Roots: tlsConfig.RootCAs,
|
|
||||||
DNSName: serverName,
|
DNSName: serverName,
|
||||||
Intermediates: x509.NewCertPool(),
|
Intermediates: x509.NewCertPool(),
|
||||||
}
|
}
|
||||||
for _, cert := range state.PeerCertificates[1:] {
|
for _, cert := range state.PeerCertificates[1:] {
|
||||||
verifyOptions.Intermediates.AddCert(cert)
|
verifyOptions.Intermediates.AddCert(cert)
|
||||||
}
|
}
|
||||||
if tlsConfig.Time != nil {
|
|
||||||
verifyOptions.CurrentTime = tlsConfig.Time()
|
|
||||||
}
|
|
||||||
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -146,10 +127,8 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return parseECHClientConfig(ctx, stdConfig, options)
|
return parseECHClientConfig(ctx, options, &tlsConfig)
|
||||||
} else {
|
|
||||||
return stdConfig, nil
|
|
||||||
}
|
}
|
||||||
|
return &STDClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,10 @@ type TimeServiceWrapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
||||||
return func() time.Time {
|
if w.TimeService == nil {
|
||||||
if w.TimeService != nil {
|
return nil
|
||||||
return w.TimeService.TimeFunc()()
|
|
||||||
} else {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return w.TimeService.TimeFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TimeServiceWrapper) Upstream() any {
|
func (w *TimeServiceWrapper) Upstream() any {
|
||||||
|
@ -8,12 +8,11 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@ -23,62 +22,48 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UTLSClientConfig struct {
|
type UTLSClientConfig struct {
|
||||||
ctx context.Context
|
config *utls.Config
|
||||||
config *utls.Config
|
id utls.ClientHelloID
|
||||||
id utls.ClientHelloID
|
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ServerName() string {
|
func (e *UTLSClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return e.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetServerName(serverName string) {
|
func (e *UTLSClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
e.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) NextProtos() []string {
|
func (e *UTLSClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return e.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
||||||
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
||||||
nextProto = append(nextProto, "http/1.1")
|
nextProto = append(nextProto, "http/1.1")
|
||||||
}
|
}
|
||||||
c.config.NextProtos = nextProto
|
e.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Config() (*STDConfig, error) {
|
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
||||||
return nil, E.New("unsupported usage for uTLS")
|
return nil, E.New("unsupported usage for uTLS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
c.config.SessionIDGenerator = generator
|
e.config.SessionIDGenerator = generator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Clone() Config {
|
func (e *UTLSClientConfig) Clone() Config {
|
||||||
return &UTLSClientConfig{
|
return &UTLSClientConfig{
|
||||||
c.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment,
|
config: e.config.Clone(),
|
||||||
|
id: e.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
type utlsConnWrapper struct {
|
||||||
*utls.UConn
|
*utls.UConn
|
||||||
}
|
}
|
||||||
@ -131,12 +116,14 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
|||||||
return c.UConn.HandshakeContext(ctx)
|
return c.UConn.HandshakeContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
serverName = serverAddress
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
|
serverName = serverAddress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if serverName == "" && !options.Insecure {
|
if serverName == "" && !options.Insecure {
|
||||||
return nil, E.New("missing server_name or insecure=true")
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
@ -145,16 +132,15 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
tlsConfig.InsecureSkipVerify = options.Insecure
|
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||||
} else if options.DisableSNI {
|
} else if options.DisableSNI {
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
return nil, E.New("disable_sni is unsupported in uTLS")
|
||||||
return nil, E.New("disable_sni is unsupported in reality")
|
|
||||||
}
|
|
||||||
tlsConfig.InsecureServerNameToVerify = serverName
|
|
||||||
}
|
}
|
||||||
if len(options.ALPN) > 0 {
|
if len(options.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = options.ALPN
|
tlsConfig.NextProtos = options.ALPN
|
||||||
@ -206,15 +192,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}
|
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
|
||||||
return nil, E.New("Reality is conflict with ECH")
|
|
||||||
}
|
|
||||||
return parseECHClientConfig(ctx, uConfig, options)
|
|
||||||
} else {
|
|
||||||
return uConfig, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -242,7 +220,7 @@ func init() {
|
|||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq", "chrome_pq_psk":
|
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "chrome", "":
|
case "chrome", "":
|
||||||
return utls.HelloChrome_Auto, nil
|
return utls.HelloChrome_Auto, nil
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
@ -20,21 +19,16 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
splitPacket bool
|
|
||||||
splitRecord bool
|
splitRecord bool
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
||||||
if fallbackDelay == 0 {
|
|
||||||
fallbackDelay = C.TLSFragmentFallbackDelay
|
|
||||||
}
|
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
tcpConn: tcpConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
splitPacket: splitPacket,
|
|
||||||
splitRecord: splitRecord,
|
splitRecord: splitRecord,
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}
|
}
|
||||||
@ -45,9 +39,9 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
c.firstPacketWritten = true
|
c.firstPacketWritten = true
|
||||||
}()
|
}()
|
||||||
serverName := IndexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if c.splitPacket {
|
if !c.splitRecord {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,41 +81,33 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||||
}
|
}
|
||||||
if c.splitRecord {
|
if c.splitRecord {
|
||||||
if c.splitPacket {
|
|
||||||
buffer.Reset()
|
|
||||||
}
|
|
||||||
payloadLen := uint16(len(payload))
|
payloadLen := uint16(len(payload))
|
||||||
buffer.Write(b[:3])
|
buffer.Write(b[:3])
|
||||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
||||||
buffer.Write(payload)
|
buffer.Write(payload)
|
||||||
if c.splitPacket {
|
} else if c.tcpConn != nil && i != len(splitIndexes) {
|
||||||
payload = buffer.Bytes()
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if c.splitPacket {
|
_, err = c.Conn.Write(payload)
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
if err != nil {
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
return
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = c.Conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.splitRecord && !c.splitPacket {
|
if c.splitRecord {
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
_, err = c.Conn.Write(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
|
@ -15,7 +15,7 @@ func TestTLSFragment(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, false, 0), &tls.Config{
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
|
||||||
ServerName: "www.cloudflare.com",
|
ServerName: "www.cloudflare.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, tlsConn.Handshake())
|
require.NoError(t, tlsConn.Handshake())
|
||||||
@ -25,17 +25,7 @@ func TestTLSRecordFragment(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, true, 0), &tls.Config{
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
|
||||||
ServerName: "www.cloudflare.com",
|
|
||||||
})
|
|
||||||
require.NoError(t, tlsConn.Handshake())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLS2Fragment(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
|
||||||
require.NoError(t, err)
|
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, true, 0), &tls.Config{
|
|
||||||
ServerName: "www.cloudflare.com",
|
ServerName: "www.cloudflare.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, tlsConn.Handshake())
|
require.NoError(t, tlsConn.Handshake())
|
||||||
|
@ -22,13 +22,13 @@ const (
|
|||||||
tls13 uint16 = 0x0304
|
tls13 uint16 = 0x0304
|
||||||
)
|
)
|
||||||
|
|
||||||
type MyServerName struct {
|
type myServerName struct {
|
||||||
Index int
|
Index int
|
||||||
Length int
|
Length int
|
||||||
ServerName string
|
ServerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func IndexTLSServerName(payload []byte) *MyServerName {
|
func indexTLSServerName(payload []byte) *myServerName {
|
||||||
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -36,48 +36,47 @@ func IndexTLSServerName(payload []byte) *MyServerName {
|
|||||||
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen:])
|
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
|
||||||
if serverName == nil {
|
if serverName == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName.Index += recordLayerHeaderLen
|
serverName.Length += recordLayerHeaderLen
|
||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
||||||
if len(handshake) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if handshake[0] != handshakeType {
|
if hs[0] != handshakeType {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
handshakeLen := uint32(handshake[1])<<16 | uint32(handshake[2])<<8 | uint32(handshake[3])
|
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
||||||
if len(handshake[4:]) != int(handshakeLen) {
|
if len(hs[4:]) != int(handshakeLen) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tlsVersion := uint16(handshake[4])<<8 | uint16(handshake[5])
|
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
||||||
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sessionIDLen := handshake[38]
|
sessionIDLen := hs[38]
|
||||||
currentIndex := handshakeHeaderLen + randomDataLen + sessionIDHeaderLen + int(sessionIDLen)
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
||||||
if len(handshake) < currentIndex {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cipherSuites := handshake[currentIndex:]
|
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
||||||
if len(cipherSuites) < cipherSuiteHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
csLen := uint16(cipherSuites[0])<<8 | uint16(cipherSuites[1])
|
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||||
if len(cipherSuites) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
compressMethodLen := uint16(cipherSuites[cipherSuiteHeaderLen+int(csLen)])
|
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||||
currentIndex += cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||||
if len(handshake) < currentIndex {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName := indexTLSServerNameFromExtensions(handshake[currentIndex:])
|
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
||||||
|
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
|
||||||
if serverName == nil {
|
if serverName == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -85,7 +84,7 @@ func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
|||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
||||||
if len(exs) == 0 {
|
if len(exs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -119,8 +118,7 @@ func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
|||||||
}
|
}
|
||||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||||
sex = sex[sniExtensionHeaderLen:]
|
sex = sex[sniExtensionHeaderLen:]
|
||||||
|
return &myServerName{
|
||||||
return &MyServerName{
|
|
||||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||||
Length: int(sniLen),
|
Length: int(sniLen),
|
||||||
ServerName: string(sex),
|
ServerName: string(sex),
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package tf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIndexTLSServerName(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
payload, err := hex.DecodeString("16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100")
|
|
||||||
require.NoError(t, err)
|
|
||||||
serverName := tf.IndexTLSServerName(payload)
|
|
||||||
require.NotNil(t, serverName)
|
|
||||||
require.Equal(t, serverName.ServerName, string(payload[serverName.Index:serverName.Index+serverName.Length]))
|
|
||||||
require.Equal(t, "github.com", serverName.ServerName)
|
|
||||||
}
|
|
@ -22,8 +22,7 @@ const (
|
|||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersion3
|
RuleSetVersion3
|
||||||
RuleSetVersion4
|
RuleSetVersionCurrent = RuleSetVersion3
|
||||||
RuleSetVersionCurrent = RuleSetVersion4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -195,13 +195,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
addr, addrErr := MessageToAddresses(response)
|
||||||
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) {
|
if addrErr != nil || !responseChecker(addr) {
|
||||||
rejected = true
|
|
||||||
} else {
|
|
||||||
rejected = !responseChecker(MessageToAddresses(response))
|
|
||||||
}
|
|
||||||
if rejected {
|
|
||||||
if 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)
|
||||||
}
|
}
|
||||||
@ -425,10 +420,7 @@ func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTran
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
return MessageToAddresses(response)
|
||||||
return nil, RcodeError(response.Rcode)
|
|
||||||
}
|
|
||||||
return MessageToAddresses(response), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
|
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
|
||||||
@ -436,10 +428,7 @@ func (c *Client) questionCache(question dns.Question, transport adapter.DNSTrans
|
|||||||
if response == nil {
|
if response == nil {
|
||||||
return nil, ErrNotCached
|
return nil, ErrNotCached
|
||||||
}
|
}
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
return MessageToAddresses(response)
|
||||||
return nil, RcodeError(response.Rcode)
|
|
||||||
}
|
|
||||||
return MessageToAddresses(response), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
|
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
|
||||||
@ -516,7 +505,10 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
||||||
|
if response.Rcode != dns.RcodeSuccess {
|
||||||
|
return nil, RcodeError(response.Rcode)
|
||||||
|
}
|
||||||
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) {
|
||||||
@ -532,7 +524,7 @@ func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return addresses
|
return addresses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapError(err error) error {
|
func wrapError(err error) error {
|
||||||
|
@ -3,15 +3,11 @@ package transport
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
@ -43,13 +39,11 @@ func RegisterHTTPS(registry *dns.TransportRegistry) {
|
|||||||
|
|
||||||
type HTTPSTransport struct {
|
type HTTPSTransport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
destination *url.URL
|
destination *url.URL
|
||||||
headers http.Header
|
headers http.Header
|
||||||
transportAccess sync.Mutex
|
transport *http.Transport
|
||||||
transport *http.Transport
|
|
||||||
transportResetAt time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
@ -167,33 +161,12 @@ func (t *HTTPSTransport) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Close() error {
|
func (t *HTTPSTransport) Close() error {
|
||||||
t.transportAccess.Lock()
|
|
||||||
defer t.transportAccess.Unlock()
|
|
||||||
t.transport.CloseIdleConnections()
|
t.transport.CloseIdleConnections()
|
||||||
t.transport = t.transport.Clone()
|
t.transport = t.transport.Clone()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
startAt := time.Now()
|
|
||||||
response, err := t.exchange(ctx, message)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
|
||||||
t.transportAccess.Lock()
|
|
||||||
defer t.transportAccess.Unlock()
|
|
||||||
if t.transportResetAt.After(startAt) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.transport.CloseIdleConnections()
|
|
||||||
t.transport = t.transport.Clone()
|
|
||||||
t.transportResetAt = time.Now()
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
exMessage := *message
|
exMessage := *message
|
||||||
exMessage.Id = 0
|
exMessage.Id = 0
|
||||||
exMessage.Compress = true
|
exMessage.Compress = true
|
||||||
|
@ -3,6 +3,7 @@ package local
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -13,7 +14,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"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"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@ -24,11 +24,9 @@ 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
|
|
||||||
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) {
|
||||||
@ -39,42 +37,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,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
switch stage {
|
|
||||||
case adapter.StartStateInitialize:
|
|
||||||
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 {
|
||||||
@ -115,9 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
|||||||
startRacer := func(ctx context.Context, fqdn string) {
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if response.Rcode != mDNS.RcodeSuccess {
|
var addresses []netip.Addr
|
||||||
err = dns.RcodeError(response.Rcode)
|
addresses, err = dns.MessageToAddresses(response)
|
||||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
if err == nil && len(addresses) == 0 {
|
||||||
err = E.New(fqdn, ": empty result")
|
err = E.New(fqdn, ": empty result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,10 +33,6 @@ func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
|
||||||
if platformInterface == nil {
|
|
||||||
return transport, nil
|
|
||||||
}
|
|
||||||
return &FallbackTransport{
|
return &FallbackTransport{
|
||||||
DNSTransport: transport,
|
DNSTransport: transport,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@ -44,11 +40,11 @@ func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FallbackTransport) Start(stage adapter.StartStage) error {
|
func (f *FallbackTransport) Start(stage adapter.StartStage) error {
|
||||||
err := f.DNSTransport.Start(stage)
|
if stage != adapter.StartStateStart {
|
||||||
if err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if stage != adapter.StartStatePostStart {
|
platformInterface := service.FromContext[platform.Interface](f.ctx)
|
||||||
|
if platformInterface == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
inboundManager := service.FromContext[adapter.InboundManager](f.ctx)
|
inboundManager := service.FromContext[adapter.InboundManager](f.ctx)
|
||||||
@ -63,7 +59,7 @@ func (f *FallbackTransport) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FallbackTransport) Close() error {
|
func (f *FallbackTransport) Close() error {
|
||||||
return f.DNSTransport.Close()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
@ -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,150 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/service/resolved"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DBusResolvedResolver struct {
|
|
||||||
logger logger.ContextLogger
|
|
||||||
interfaceMonitor tun.DefaultInterfaceMonitor
|
|
||||||
systemBus *dbus.Conn
|
|
||||||
resoledObject atomic.TypedValue[dbus.BusObject]
|
|
||||||
closeOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
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{
|
|
||||||
logger: logger,
|
|
||||||
interfaceMonitor: interfaceMonitor,
|
|
||||||
systemBus: systemBus,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) Start() error {
|
|
||||||
t.updateStatus()
|
|
||||||
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.systemBus != nil {
|
|
||||||
_ = t.systemBus.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) Object() any {
|
|
||||||
return t.resoledObject.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
defaultInterface := t.interfaceMonitor.DefaultInterface()
|
|
||||||
if defaultInterface == nil {
|
|
||||||
return nil, E.New("missing default interface")
|
|
||||||
}
|
|
||||||
question := message.Question[0]
|
|
||||||
call := object.(*dbus.Object).CallWithContext(
|
|
||||||
ctx,
|
|
||||||
"org.freedesktop.resolve1.Manager.ResolveRecord",
|
|
||||||
0,
|
|
||||||
int32(defaultInterface.Index),
|
|
||||||
question.Name,
|
|
||||||
question.Qclass,
|
|
||||||
question.Qtype,
|
|
||||||
uint64(0),
|
|
||||||
)
|
|
||||||
if call.Err != nil {
|
|
||||||
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 := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
|
|
||||||
err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err
|
|
||||||
if err != nil {
|
|
||||||
if t.resoledObject.Swap(nil) != nil {
|
|
||||||
t.logger.Debug("systemd-resolved service is gone")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.resoledObject.Store(dbusObject)
|
|
||||||
t.logger.Debug("using systemd-resolved service as resolver")
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -104,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 {
|
||||||
@ -118,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)
|
||||||
|
@ -20,8 +20,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||||
var state C.struct___res_state
|
if C.res_init() != 0 {
|
||||||
if C.res_ninit(&state) != 0 {
|
|
||||||
return &dnsConfig{
|
return &dnsConfig{
|
||||||
servers: defaultNS,
|
servers: defaultNS,
|
||||||
search: dnsDefaultSearch(),
|
search: dnsDefaultSearch(),
|
||||||
@ -34,10 +33,10 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
|||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
attempts: int(state.retry),
|
attempts: int(C._res.retry),
|
||||||
}
|
}
|
||||||
for i := 0; i < int(state.nscount); i++ {
|
for i := 0; i < int(C._res.nscount); i++ {
|
||||||
ns := state.nsaddr_list[i]
|
ns := C._res.nsaddr_list[i]
|
||||||
addr := C.inet_ntoa(ns.sin_addr)
|
addr := C.inet_ntoa(ns.sin_addr)
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
continue
|
continue
|
||||||
@ -45,7 +44,7 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
|||||||
conf.servers = append(conf.servers, C.GoString(addr))
|
conf.servers = append(conf.servers, C.GoString(addr))
|
||||||
}
|
}
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
search := state.dnsrch[i]
|
search := C._res.dnsrch[i]
|
||||||
if search == nil {
|
if search == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
udpSize: 2048,
|
udpSize: 512,
|
||||||
tcpTransport: &TCPTransport{
|
tcpTransport: &TCPTransport{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
@ -97,19 +97,15 @@ func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
t.access.Lock()
|
|
||||||
if edns0Opt := message.IsEdns0(); edns0Opt != nil {
|
|
||||||
if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize {
|
|
||||||
t.udpSize = udpSize
|
|
||||||
close(t.done)
|
|
||||||
t.done = make(chan struct{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.access.Unlock()
|
|
||||||
conn, err := t.open(ctx)
|
conn, err := t.open(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if edns0Opt := message.IsEdns0(); edns0Opt != nil {
|
||||||
|
if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize {
|
||||||
|
t.udpSize = udpSize
|
||||||
|
}
|
||||||
|
}
|
||||||
buffer := buf.NewSize(1 + message.Len())
|
buffer := buf.NewSize(1 + message.Len())
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
exMessage := *message
|
exMessage := *message
|
||||||
|
@ -2,240 +2,10 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.13.0-alpha.2
|
#### 1.12.0-beta.22
|
||||||
|
|
||||||
* 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
|
* 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
|
|
||||||
|
|
||||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
|
||||||
violated the rules (TestFlight users are not affected)._
|
|
||||||
|
|
||||||
#### 1.12.0-beta.32
|
|
||||||
|
|
||||||
* Improve tun performance on Apple platforms **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
|
||||||
|
|
||||||
#### 1.11.14
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
|
||||||
violated the rules (TestFlight users are not affected)._
|
|
||||||
|
|
||||||
#### 1.12.0-beta.24
|
|
||||||
|
|
||||||
* Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1**
|
|
||||||
* Also add fragment options for TLS client configuration **2**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
For debugging only, it is recommended to disable if record fragmentation works.
|
|
||||||
|
|
||||||
See [Route Action](/configuration/route/rule_action/#tls_fragment).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
See [TLS](/configuration/shared/tls/).
|
|
||||||
|
|
||||||
#### 1.12.0-beta.23
|
|
||||||
|
|
||||||
* Add loopback address support for tun **1**
|
|
||||||
* Add cache support for ssm-api **2**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
TUN now implements SideStore's StosVPN.
|
|
||||||
|
|
||||||
See [Tun](/configuration/inbound/tun/#loopback_address).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/#cache_path).
|
|
||||||
|
|
||||||
#### 1.12.0-beta.21
|
#### 1.12.0-beta.21
|
||||||
|
|
||||||
* Fix missing `home` option for DERP service **1**
|
* Fix missing `home` option for DERP service **1**
|
||||||
@ -247,7 +17,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
|
||||||
|
|
||||||
@ -285,7 +55,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
|
||||||
|
|
||||||
@ -317,7 +87,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
|
||||||
@ -335,7 +105,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
|
||||||
|
|
||||||
@ -346,7 +116,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
|
||||||
@ -363,7 +133,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
|
||||||
|
|
||||||
@ -379,7 +149,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
|
||||||
|
|
||||||
@ -420,7 +190,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
|
||||||
|
|
||||||
@ -436,7 +206,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
|
||||||
|
|
||||||
@ -492,7 +262,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
|
||||||
|
|
||||||
@ -503,7 +273,7 @@ process._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.11.1
|
### 1.11.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -682,7 +452,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
|
||||||
|
|
||||||
@ -777,7 +547,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
|
||||||
@ -914,7 +684,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 ""
|
||||||
|
@ -25,7 +25,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|-------------|---------------------------|
|
|-------------|---------------------------|
|
||||||
| `wireguard` | [WireGuard](./wireguard/) |
|
| `wireguard` | [WireGuard](./wiregaurd/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
@ -61,7 +61,7 @@ WireGuard MTU。
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
接口的 IPv4/IPv6 地址或地址段的列表。
|
接口的 IPv4/IPv6 地址或地址段的列表您。
|
||||||
|
|
||||||
要分配给接口的 IP(v4 或 v6)地址段列表。
|
要分配给接口的 IP(v4 或 v6)地址段列表。
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
{
|
{
|
||||||
"external_controller": "0.0.0.0:9090",
|
"external_controller": "0.0.0.0:9090",
|
||||||
"external_ui": "dashboard"
|
"external_ui": "dashboard"
|
||||||
// "external_ui_download_detour": "direct"
|
// external_ui_download_detour: "direct"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
{
|
{
|
||||||
"external_controller": "0.0.0.0:9090",
|
"external_controller": "0.0.0.0:9090",
|
||||||
"external_ui": "dashboard"
|
"external_ui": "dashboard"
|
||||||
// "external_ui_download_detour": "direct"
|
// external_ui_download_detour: "direct"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-plus: [loopback_address](#loopback_address)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-delete-alert: [gso](#gso)
|
:material-delete-alert: [gso](#gso)
|
||||||
@ -60,12 +56,9 @@ icon: material/new-box
|
|||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"iproute2_table_index": 2022,
|
"iproute2_table_index": 2022,
|
||||||
"iproute2_rule_index": 9000,
|
"iproute2_rule_index": 9000,
|
||||||
"auto_redirect": true,
|
"auto_redirect": false,
|
||||||
"auto_redirect_input_mark": "0x2023",
|
"auto_redirect_input_mark": "0x2023",
|
||||||
"auto_redirect_output_mark": "0x2024",
|
"auto_redirect_output_mark": "0x2024",
|
||||||
"loopback_address": [
|
|
||||||
"10.7.0.1"
|
|
||||||
],
|
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
"route_address": [
|
"route_address": [
|
||||||
"0.0.0.0/1",
|
"0.0.0.0/1",
|
||||||
@ -73,6 +66,7 @@ icon: material/new-box
|
|||||||
"::/1",
|
"::/1",
|
||||||
"8000::/1"
|
"8000::/1"
|
||||||
],
|
],
|
||||||
|
|
||||||
"route_exclude_address": [
|
"route_exclude_address": [
|
||||||
"192.168.0.0/16",
|
"192.168.0.0/16",
|
||||||
"fc00::/7"
|
"fc00::/7"
|
||||||
@ -123,6 +117,7 @@ icon: material/new-box
|
|||||||
"match_domain": []
|
"match_domain": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
"gso": false,
|
"gso": false,
|
||||||
"inet4_address": [
|
"inet4_address": [
|
||||||
@ -145,8 +140,8 @@ icon: material/new-box
|
|||||||
"inet6_route_exclude_address": [
|
"inet6_route_exclude_address": [
|
||||||
"fc00::/7"
|
"fc00::/7"
|
||||||
],
|
],
|
||||||
...
|
|
||||||
// Listen Fields
|
... // Listen Fields
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -278,16 +273,6 @@ Connection output mark used by `auto_redirect`.
|
|||||||
|
|
||||||
`0x2024` is used by default.
|
`0x2024` is used by default.
|
||||||
|
|
||||||
#### loopback_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
Loopback addresses make TCP connections to the specified address connect to the source address.
|
|
||||||
|
|
||||||
Setting option value to `10.7.0.1` achieves the same behavior as SideStore/StosVPN.
|
|
||||||
|
|
||||||
When `auto_redirect` is enabled, the same behavior can be achieved for LAN devices (not just local) as a gateway.
|
|
||||||
|
|
||||||
#### strict_route
|
#### strict_route
|
||||||
|
|
||||||
Enforce strict routing rules when `auto_route` is enabled:
|
Enforce strict routing rules when `auto_route` is enabled:
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [loopback_address](#loopback_address)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-delete-alert: [gso](#gso)
|
:material-delete-alert: [gso](#gso)
|
||||||
@ -60,12 +56,9 @@ icon: material/new-box
|
|||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"iproute2_table_index": 2022,
|
"iproute2_table_index": 2022,
|
||||||
"iproute2_rule_index": 9000,
|
"iproute2_rule_index": 9000,
|
||||||
"auto_redirect": true,
|
"auto_redirect": false,
|
||||||
"auto_redirect_input_mark": "0x2023",
|
"auto_redirect_input_mark": "0x2023",
|
||||||
"auto_redirect_output_mark": "0x2024",
|
"auto_redirect_output_mark": "0x2024",
|
||||||
"loopback_address": [
|
|
||||||
"10.7.0.1"
|
|
||||||
],
|
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
"route_address": [
|
"route_address": [
|
||||||
"0.0.0.0/1",
|
"0.0.0.0/1",
|
||||||
@ -277,16 +270,6 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
默认使用 `0x2024`。
|
默认使用 `0x2024`。
|
||||||
|
|
||||||
#### loopback_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。
|
|
||||||
|
|
||||||
将选项值设置为 `10.7.0.1` 可实现与 SideStore/StosVPN 相同的行为。
|
|
||||||
|
|
||||||
当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。
|
|
||||||
|
|
||||||
#### strict_route
|
#### strict_route
|
||||||
|
|
||||||
当启用 `auto_route` 时,强制执行严格的路由规则:
|
当启用 `auto_route` 时,强制执行严格的路由规则:
|
||||||
|
@ -88,7 +88,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
接口的 IPv4/IPv6 地址或地址段的列表。
|
接口的 IPv4/IPv6 地址或地址段的列表您。
|
||||||
|
|
||||||
要分配给接口的 IP(v4 或 v6)地址段列表。
|
要分配给接口的 IP(v4 或 v6)地址段列表。
|
||||||
|
|
||||||
|
@ -2,13 +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)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@ -135,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"
|
||||||
@ -387,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 ""
|
||||||
@ -433,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,13 +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)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@ -132,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"
|
||||||
@ -361,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
|
||||||
|
|
||||||
@ -384,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 ""
|
||||||
@ -430,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 起"
|
||||||
|
@ -172,12 +172,14 @@ and should not be used to circumvent real censorship.
|
|||||||
Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.
|
Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.
|
||||||
|
|
||||||
On Linux, Apple platforms, (administrator privileges required) Windows,
|
On Linux, Apple platforms, (administrator privileges required) Windows,
|
||||||
the wait time can be automatically detected. Otherwise, it will fall back to
|
the wait time can be automatically detected, otherwise it will fall back to
|
||||||
waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
||||||
|
|
||||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
||||||
because the target is considered to be local or behind a transparent proxy.
|
because the target is considered to be local or behind a transparent proxy.
|
||||||
|
|
||||||
|
Conflict with `tls_record_fragment`.
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
@ -192,6 +194,11 @@ The fallback value used when TLS segmentation cannot automatically determine the
|
|||||||
|
|
||||||
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
||||||
|
|
||||||
|
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
||||||
|
and should not be used to circumvent real censorship.
|
||||||
|
|
||||||
|
Conflict with `tls_fragment`.
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
@ -170,6 +170,8 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||||
|
|
||||||
|
与 `tls_record_fragment` 冲突。
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
@ -184,6 +186,10 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
||||||
|
|
||||||
|
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||||
|
|
||||||
|
与 `tls_fragment` 冲突。
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: version `4`
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: version `3`
|
:material-plus: version `3`
|
||||||
@ -40,7 +36,6 @@ icon: material/new-box
|
|||||||
* 1: sing-box 1.8.0: 初始规则集版本。
|
* 1: sing-box 1.8.0: 初始规则集版本。
|
||||||
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
||||||
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
||||||
* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。
|
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ See https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadow
|
|||||||
... // Listen Fields
|
... // Listen Fields
|
||||||
|
|
||||||
"servers": {},
|
"servers": {},
|
||||||
"cache_path": "",
|
|
||||||
"tls": {}
|
"tls": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -38,7 +37,7 @@ A mapping Object from HTTP endpoints to [Shadowsocks Inbound](/configuration/inb
|
|||||||
|
|
||||||
Selected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled.
|
Selected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -48,11 +47,6 @@ Example:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### cache_path
|
|
||||||
|
|
||||||
If set, when the server is about to stop, traffic and user state will be saved to the specified JSON file
|
|
||||||
to be restored on the next startup.
|
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
|
@ -4,9 +4,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [fragment](#fragment)
|
|
||||||
:material-plus: [fragment_fallback_delay](#fragment_fallback_delay)
|
|
||||||
:material-plus: [record_fragment](#record_fragment)
|
|
||||||
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
||||||
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
||||||
|
|
||||||
@ -85,9 +82,6 @@ icon: material/alert-decagram
|
|||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": "",
|
"certificate": "",
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"fragment": false,
|
|
||||||
"fragment_fallback_delay": "",
|
|
||||||
"record_fragment": false,
|
|
||||||
"ech": {
|
"ech": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"config": [],
|
"config": [],
|
||||||
@ -319,44 +313,6 @@ The path to ECH configuration, in PEM format.
|
|||||||
|
|
||||||
If empty, load from DNS will be attempted.
|
If empty, load from DNS will be attempted.
|
||||||
|
|
||||||
#### fragment
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
==Client only==
|
|
||||||
|
|
||||||
Fragment TLS handshakes to bypass firewalls.
|
|
||||||
|
|
||||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
|
||||||
and should not be used to circumvent real censorship.
|
|
||||||
|
|
||||||
Due to poor performance, try `record_fragment` first, and only apply to server names known to be blocked.
|
|
||||||
|
|
||||||
On Linux, Apple platforms, (administrator privileges required) Windows,
|
|
||||||
the wait time can be automatically detected. Otherwise, it will fall back to
|
|
||||||
waiting for a fixed time specified by `fragment_fallback_delay`.
|
|
||||||
|
|
||||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
|
||||||
because the target is considered to be local or behind a transparent proxy.
|
|
||||||
|
|
||||||
#### fragment_fallback_delay
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
==Client only==
|
|
||||||
|
|
||||||
The fallback value used when TLS segmentation cannot automatically determine the wait time.
|
|
||||||
|
|
||||||
`500ms` is used by default.
|
|
||||||
|
|
||||||
#### record_fragment
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
==Client only==
|
|
||||||
|
|
||||||
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
|
||||||
|
|
||||||
### ACME Fields
|
### ACME Fields
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
@ -4,9 +4,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
|
||||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
|
||||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
|
||||||
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
||||||
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
||||||
|
|
||||||
@ -85,9 +82,6 @@ icon: material/alert-decagram
|
|||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": [],
|
"certificate": [],
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"fragment": false,
|
|
||||||
"fragment_fallback_delay": "",
|
|
||||||
"record_fragment": false,
|
|
||||||
"ech": {
|
"ech": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"pq_signature_schemes_enabled": false,
|
"pq_signature_schemes_enabled": false,
|
||||||
@ -311,41 +305,6 @@ ECH PEM 配置路径
|
|||||||
如果为 true,则始终使用最大可能的 TLS 记录大小。
|
如果为 true,则始终使用最大可能的 TLS 记录大小。
|
||||||
如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。
|
如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。
|
||||||
|
|
||||||
#### tls_fragment
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
==仅客户端==
|
|
||||||
|
|
||||||
通过分段 TLS 握手数据包来绕过防火墙检测。
|
|
||||||
|
|
||||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
|
||||||
|
|
||||||
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
|
|
||||||
|
|
||||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
|
|
||||||
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
|
||||||
|
|
||||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
==仅客户端==
|
|
||||||
|
|
||||||
当 TLS 分片功能无法自动判定等待时间时使用的回退值。
|
|
||||||
|
|
||||||
默认使用 `500ms`。
|
|
||||||
|
|
||||||
#### tls_record_fragment
|
|
||||||
|
|
||||||
==仅客户端==
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
|
||||||
|
|
||||||
### ACME 字段
|
### ACME 字段
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
@ -94,13 +94,18 @@ flowchart TB
|
|||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "udp",
|
"address": "223.5.5.5",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"strategy": "ipv4_only"
|
"strategy": "ipv4_only"
|
||||||
@ -108,10 +113,9 @@ flowchart TB
|
|||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type": "tun",
|
"type": "tun",
|
||||||
"address": ["172.19.0.1/30"],
|
"inet4_address": "172.19.0.1/30",
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
// "auto_redirect": true, // On linux
|
"strict_route": false
|
||||||
"strict_route": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@ -119,23 +123,25 @@ flowchart TB
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"protocol": "dns",
|
"protocol": "dns",
|
||||||
"action": "hijack-dns"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"geoip": [
|
||||||
|
"private"
|
||||||
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"auto_detect_interface": true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,23 +155,28 @@ flowchart TB
|
|||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "udp",
|
"address": "223.5.5.5",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type": "tun",
|
"type": "tun",
|
||||||
"address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"],
|
"inet4_address": "172.19.0.1/30",
|
||||||
|
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
// "auto_redirect": true, // On linux
|
"strict_route": false
|
||||||
"strict_route": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@ -173,23 +184,25 @@ flowchart TB
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"protocol": "dns",
|
"protocol": "dns",
|
||||||
"action": "hijack-dns"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"geoip": [
|
||||||
|
"private"
|
||||||
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"auto_detect_interface": true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,22 +216,23 @@ flowchart TB
|
|||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "udp",
|
"address": "223.5.5.5",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "remote",
|
"tag": "remote",
|
||||||
"type": "fakeip",
|
"address": "fakeip"
|
||||||
"inet4_range": "198.18.0.0/15",
|
|
||||||
"inet6_range": "fc00::/18"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rules": [
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"query_type": [
|
"query_type": [
|
||||||
"A",
|
"A",
|
||||||
@ -227,14 +241,19 @@ flowchart TB
|
|||||||
"server": "remote"
|
"server": "remote"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"fakeip": {
|
||||||
|
"enabled": true,
|
||||||
|
"inet4_range": "198.18.0.0/15",
|
||||||
|
"inet6_range": "fc00::/18"
|
||||||
|
},
|
||||||
"independent_cache": true
|
"independent_cache": true
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type": "tun",
|
"type": "tun",
|
||||||
"address": ["172.19.0.1/30","fdfe:dcba:9876::1/126"],
|
"inet4_address": "172.19.0.1/30",
|
||||||
|
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
// "auto_redirect": true, // On linux
|
|
||||||
"strict_route": true
|
"strict_route": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -243,23 +262,25 @@ flowchart TB
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"protocol": "dns",
|
"protocol": "dns",
|
||||||
"action": "hijack-dns"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"geoip": [
|
||||||
|
"private"
|
||||||
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"auto_detect_interface": true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,6 +290,54 @@ flowchart TB
|
|||||||
|
|
||||||
=== ":material-dns: DNS rules"
|
=== ":material-dns: DNS rules"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"tag": "google",
|
||||||
|
"address": "tls://8.8.8.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "local",
|
||||||
|
"address": "223.5.5.5",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"server": "google"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule_set": "geosite-geolocation-cn",
|
||||||
|
"server": "local"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"rule_set": [
|
||||||
|
{
|
||||||
|
"type": "remote",
|
||||||
|
"tag": "geosite-geolocation-cn",
|
||||||
|
"format": "binary",
|
||||||
|
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-dns: DNS rules (Enhanced, but slower) (1.9.0+)"
|
||||||
|
|
||||||
=== ":material-shield-off: With DNS leaks"
|
=== ":material-shield-off: With DNS leaks"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@ -277,20 +346,35 @@ flowchart TB
|
|||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "https",
|
"address": "https://223.5.5.5/dns-query",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rules": [
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"server": "google"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-geolocation-cn",
|
"rule_set": "geosite-geolocation-cn",
|
||||||
"server": "local"
|
"server": "local"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Default",
|
||||||
|
"server": "google"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
@ -308,7 +392,6 @@ flowchart TB
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"route": {
|
"route": {
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
{
|
{
|
||||||
"type": "remote",
|
"type": "remote",
|
||||||
@ -342,24 +425,35 @@ flowchart TB
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-security: Without DNS leaks, but slower"
|
=== ":material-security: Without DNS leaks, but slower (1.9.0-alpha.2+)"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "https",
|
"address": "https://223.5.5.5/dns-query",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rules": [
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"server": "google"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-geolocation-cn",
|
"rule_set": "geosite-geolocation-cn",
|
||||||
"server": "local"
|
"server": "local"
|
||||||
@ -382,7 +476,6 @@ flowchart TB
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"route": {
|
"route": {
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
{
|
{
|
||||||
"type": "remote",
|
"type": "remote",
|
||||||
@ -424,13 +517,14 @@ flowchart TB
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"tag": "block"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "or",
|
"mode": "or",
|
||||||
@ -442,12 +536,20 @@ flowchart TB
|
|||||||
"port": 53
|
"port": 53
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"action": "hijack-dns"
|
"outbound": "dns"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"ip_is_private": true,
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"outbound": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"outbound": "default"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "or",
|
"mode": "or",
|
||||||
@ -463,23 +565,12 @@ flowchart TB
|
|||||||
"protocol": "stun"
|
"protocol": "stun"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"action": "reject"
|
"outbound": "block"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-geolocation-cn",
|
"rule_set": [
|
||||||
"outbound": "direct"
|
"geoip-cn",
|
||||||
},
|
"geosite-geolocation-cn"
|
||||||
{
|
|
||||||
"type": "logical",
|
|
||||||
"mode": "and",
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"rule_set": "geoip-cn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule_set": "geosite-geolocation-!cn",
|
|
||||||
"invert": true
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
@ -500,4 +591,4 @@ flowchart TB
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
@ -351,15 +351,14 @@ DNS servers are refactored for better performance and scalability.
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
"rules": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"domain": [
|
"type": "predefined",
|
||||||
"example.com"
|
"responses": [
|
||||||
],
|
{
|
||||||
// other rules
|
"rcode": "REFUSED"
|
||||||
|
}
|
||||||
"action": "predefined",
|
]
|
||||||
"rcode": "REFUSED"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1188,4 +1187,4 @@ which will disrupt the existing `process_path` use cases in Windows.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
@ -8,7 +8,7 @@ icon: material/arrange-bring-forward
|
|||||||
|
|
||||||
DNS 服务器已经重构。
|
DNS 服务器已经重构。
|
||||||
|
|
||||||
!!! info "引用"
|
!!! info "饮用"
|
||||||
|
|
||||||
[DNS 服务器](/configuration/dns/server/) /
|
[DNS 服务器](/configuration/dns/server/) /
|
||||||
[旧 DNS 服务器](/configuration/dns/server/legacy/)
|
[旧 DNS 服务器](/configuration/dns/server/legacy/)
|
||||||
@ -351,15 +351,14 @@ DNS 服务器已经重构。
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
"rules": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"domain": [
|
"type": "predefined",
|
||||||
"example.com"
|
"responses": [
|
||||||
],
|
{
|
||||||
// 其它规则
|
"rcode": "REFUSED"
|
||||||
|
}
|
||||||
"action": "predefined",
|
]
|
||||||
"rcode": "REFUSED"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
func cacheRouter(ctx context.Context) http.Handler {
|
func cacheRouter(ctx context.Context) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/fakeip/flush", flushFakeip(ctx))
|
r.Post("/fakeip/flush", flushFakeip(ctx))
|
||||||
r.Post("/dns/flush", flushDNS(ctx))
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,13 +31,3 @@ func flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Reques
|
|||||||
render.NoContent(w, r)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func flushDNS(ctx context.Context) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
|
|
||||||
if dnsRouter != nil {
|
|
||||||
dnsRouter.ClearCache()
|
|
||||||
}
|
|
||||||
render.NoContent(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -62,7 +62,10 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
_, isGroup := it.(adapter.OutboundGroup)
|
_, isGroup := it.(adapter.OutboundGroup)
|
||||||
return !isGroup
|
if isGroup {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10))
|
b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10))
|
||||||
for _, detour := range outbounds {
|
for _, detour := range outbounds {
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func BaseContext(platformInterface PlatformInterface) context.Context {
|
func BaseContext(platformInterface PlatformInterface) context.Context {
|
||||||
@ -34,9 +33,7 @@ func BaseContext(platformInterface PlatformInterface) context.Context {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
||||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
|
||||||
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||||
|
@ -18,7 +18,6 @@ func newIterator[T any](values []T) *iterator[T] {
|
|||||||
return &iterator[T]{values}
|
return &iterator[T]{values}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:noinline
|
|
||||||
func newPtrIterator[T any](values []T) *iterator[*T] {
|
func newPtrIterator[T any](values []T) *iterator[*T] {
|
||||||
return &iterator[*T]{common.Map(values, func(value T) *T { return &value })}
|
return &iterator[*T]{common.Map(values, func(value T) *T { return &value })}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
_ "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://github.com/SagerNet/sing-box/issues/3233
|
|
||||||
// https://github.com/golang/go/issues/70508
|
|
||||||
// https://github.com/tailscale/tailscale/issues/13452
|
|
||||||
|
|
||||||
//go:linkname checkPidfdOnce os.checkPidfdOnce
|
|
||||||
var checkPidfdOnce func() error
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
checkPidfdOnce = func() error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ type BoxService struct {
|
|||||||
|
|
||||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||||
ctx := BaseContext(platformInterface)
|
ctx := BaseContext(platformInterface)
|
||||||
|
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||||
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
||||||
options, err := parseConfig(ctx, configContent)
|
options, err := parseConfig(ctx, configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -115,7 +115,7 @@ func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.Packet
|
|||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
||||||
}
|
}
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil)
|
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
||||||
@ -192,7 +192,7 @@ func (s *StatsService) GetSysStats(ctx context.Context, request *SysStatsRequest
|
|||||||
var rtm runtime.MemStats
|
var rtm runtime.MemStats
|
||||||
runtime.ReadMemStats(&rtm)
|
runtime.ReadMemStats(&rtm)
|
||||||
response := &SysStatsResponse{
|
response := &SysStatsResponse{
|
||||||
Uptime: uint32(time.Since(s.createdAt).Seconds()),
|
Uptime: uint32(time.Now().Sub(s.createdAt).Seconds()),
|
||||||
NumGoroutine: uint32(runtime.NumGoroutine()),
|
NumGoroutine: uint32(runtime.NumGoroutine()),
|
||||||
Alloc: rtm.Alloc,
|
Alloc: rtm.Alloc,
|
||||||
TotalAlloc: rtm.TotalAlloc,
|
TotalAlloc: rtm.TotalAlloc,
|
||||||
|
49
go.mod
49
go.mod
@ -5,36 +5,37 @@ go 1.23.1
|
|||||||
require (
|
require (
|
||||||
github.com/anytls/sing-anytls v0.0.8
|
github.com/anytls/sing-anytls v0.0.8
|
||||||
github.com/caddyserver/certmagic v0.23.0
|
github.com/caddyserver/certmagic v0.23.0
|
||||||
github.com/coder/websocket v1.8.13
|
github.com/cloudflare/circl v1.6.1
|
||||||
|
github.com/coder/websocket v1.8.12
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/go-chi/chi/v5 v5.2.2
|
github.com/go-chi/chi/v5 v5.2.1
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
||||||
github.com/gofrs/uuid/v5 v5.3.2
|
github.com/gofrs/uuid/v5 v5.3.2
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
||||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1
|
github.com/libdns/alidns v1.0.4-libdns.v1.beta1
|
||||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6
|
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||||
github.com/metacubex/utls v1.8.0
|
github.com/metacubex/utls v1.7.0-alpha.3
|
||||||
github.com/mholt/acmez/v3 v3.1.2
|
github.com/mholt/acmez/v3 v3.1.2
|
||||||
github.com/miekg/dns v1.1.67
|
github.com/miekg/dns v1.1.66
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gomobile v0.1.8
|
github.com/sagernet/gomobile v0.1.6
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1
|
github.com/sagernet/quic-go v0.52.0-beta.1
|
||||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f
|
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b
|
||||||
github.com/sagernet/sing-mux v0.3.3
|
github.com/sagernet/sing-mux v0.3.2
|
||||||
github.com/sagernet/sing-quic v0.5.0
|
github.com/sagernet/sing-quic v0.5.0-beta.2
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1
|
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210
|
||||||
github.com/sagernet/sing-vmess v0.2.6
|
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88
|
||||||
github.com/sagernet/smux v1.5.34-mod.2
|
github.com/sagernet/smux v1.5.34-mod.2
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5
|
github.com/sagernet/tailscale v1.80.3-mod.5
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
||||||
@ -44,13 +45,13 @@ require (
|
|||||||
github.com/vishvananda/netns v0.0.5
|
github.com/vishvananda/netns v0.0.5
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.41.0
|
golang.org/x/crypto v0.38.0
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||||
golang.org/x/mod v0.27.0
|
golang.org/x/mod v0.24.0
|
||||||
golang.org/x/net v0.43.0
|
golang.org/x/net v0.40.0
|
||||||
golang.org/x/sys v0.35.0
|
golang.org/x/sys v0.33.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||||
google.golang.org/grpc v1.73.0
|
google.golang.org/grpc v1.72.0
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
howett.net/plist v1.0.1
|
howett.net/plist v1.0.1
|
||||||
)
|
)
|
||||||
@ -80,7 +81,7 @@ require (
|
|||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
@ -94,7 +95,7 @@ require (
|
|||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
||||||
github.com/libdns/libdns v1.1.0 // indirect
|
github.com/libdns/libdns v1.0.0-beta.1 // indirect
|
||||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
||||||
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
||||||
@ -122,14 +123,14 @@ require (
|
|||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap/exp v0.3.0 // indirect
|
go.uber.org/zap/exp v0.3.0 // indirect
|
||||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.14.0 // indirect
|
||||||
golang.org/x/term v0.34.0 // indirect
|
golang.org/x/term v0.32.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.25.0 // indirect
|
||||||
golang.org/x/time v0.9.0 // indirect
|
golang.org/x/time v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.36.0 // indirect
|
golang.org/x/tools v0.33.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
117
go.sum
117
go.sum
@ -20,8 +20,10 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||||
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
|
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||||
|
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
@ -45,8 +47,8 @@ github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
|
|||||||
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
||||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
|
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
|
||||||
@ -72,8 +74,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
|||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
@ -105,13 +107,12 @@ github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2
|
|||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
||||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ=
|
github.com/libdns/alidns v1.0.4-libdns.v1.beta1 h1:ods22gD4PcT0g4qRX77ucykjz7Rppnkz3vQoxDbbKTM=
|
||||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=
|
github.com/libdns/alidns v1.0.4-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=
|
||||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8=
|
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6 h1:0dlpPjNr8TaYZbkpwCiee4udBNrYrWG8EZPYEbjHEn8=
|
||||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
|
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6/go.mod h1:Aq4IXdjalB6mD0ELvKqJiIGim8zSC6mlIshRPMOAb5w=
|
||||||
|
github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
|
||||||
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||||
github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=
|
|
||||||
github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||||
@ -124,12 +125,12 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos
|
|||||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||||
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
|
github.com/metacubex/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA=
|
||||||
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
|
github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||||
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
||||||
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
||||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
@ -156,8 +157,8 @@ github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
|||||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||||
github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo=
|
github.com/sagernet/gomobile v0.1.6 h1:JkR1ToKOrdoiwULte4pYS5HYdPBzl2N+JNuuwVuLs0k=
|
||||||
github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
|
github.com/sagernet/gomobile v0.1.6/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||||
@ -167,22 +168,22 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
|
|||||||
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f h1:HIBo8l+tsS3wLwuI1E56uRTQw46QytXSUpZTP3vwG/U=
|
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo=
|
||||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
||||||
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||||
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
|
github.com/sagernet/sing-quic v0.5.0-beta.2 h1:j7KAbBuGmsKwSxVAQL5soJ+wDqxim4/llK2kxB0hSKk=
|
||||||
github.com/sagernet/sing-quic v0.5.0/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
github.com/sagernet/sing-quic v0.5.0-beta.2/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY=
|
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210 h1:6H4BZaTqKI3YcDMyTV3E576LuJM4S4wY99xoq2T1ECw=
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ=
|
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
github.com/sagernet/sing-vmess v0.2.6 h1:1c4dGzeGy0kpBXXrT1sgiMZtHhdJylIT8eWrGhJYZec=
|
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 h1:0pVm8sPOel+BoiCddW3pV3cKDKEaSioVTYDdTSKjyFI=
|
||||||
github.com/sagernet/sing-vmess v0.2.6/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
|
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
|
||||||
@ -239,16 +240,16 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
|||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
@ -262,21 +263,21 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -285,20 +286,20 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -308,10 +309,10 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdI
|
|||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
|
||||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
@ -100,6 +100,7 @@ func rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) {
|
|||||||
}
|
}
|
||||||
ruleAction.Action = C.RuleActionTypePredefined
|
ruleAction.Action = C.RuleActionTypePredefined
|
||||||
ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode))
|
ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSClientOptions struct {
|
type DNSClientOptions struct {
|
||||||
@ -179,7 +180,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
options := o.Options.(*LegacyDNSServerOptions)
|
options := o.Options.(*LegacyDNSServerOptions)
|
||||||
serverURL, _ := url.Parse(options.Address)
|
serverURL, _ := url.Parse(options.Address)
|
||||||
var serverType string
|
var serverType string
|
||||||
if serverURL != nil && serverURL.Scheme != "" {
|
if serverURL.Scheme != "" {
|
||||||
serverType = serverURL.Scheme
|
serverType = serverURL.Scheme
|
||||||
} else {
|
} else {
|
||||||
switch options.Address {
|
switch options.Address {
|
||||||
@ -216,7 +217,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
o.Type = C.DNSTypeUDP
|
o.Type = C.DNSTypeUDP
|
||||||
o.Options = &remoteOptions
|
o.Options = &remoteOptions
|
||||||
var serverAddr M.Socksaddr
|
var serverAddr M.Socksaddr
|
||||||
if serverURL == nil || serverURL.Scheme == "" {
|
if serverURL.Scheme == "" {
|
||||||
serverAddr = M.ParseSocksaddr(options.Address)
|
serverAddr = M.ParseSocksaddr(options.Address)
|
||||||
} else {
|
} else {
|
||||||
serverAddr = M.ParseSocksaddr(serverURL.Host)
|
serverAddr = M.ParseSocksaddr(serverURL.Host)
|
||||||
@ -231,9 +232,6 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
case C.DNSTypeTCP:
|
case C.DNSTypeTCP:
|
||||||
o.Type = C.DNSTypeTCP
|
o.Type = C.DNSTypeTCP
|
||||||
o.Options = &remoteOptions
|
o.Options = &remoteOptions
|
||||||
if serverURL == nil {
|
|
||||||
return E.New("invalid server address")
|
|
||||||
}
|
|
||||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||||
if !serverAddr.IsValid() {
|
if !serverAddr.IsValid() {
|
||||||
return E.New("invalid server address")
|
return E.New("invalid server address")
|
||||||
@ -244,9 +242,6 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
case C.DNSTypeTLS, C.DNSTypeQUIC:
|
case C.DNSTypeTLS, C.DNSTypeQUIC:
|
||||||
o.Type = serverType
|
o.Type = serverType
|
||||||
if serverURL == nil {
|
|
||||||
return E.New("invalid server address")
|
|
||||||
}
|
|
||||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||||
if !serverAddr.IsValid() {
|
if !serverAddr.IsValid() {
|
||||||
return E.New("invalid server address")
|
return E.New("invalid server address")
|
||||||
@ -266,9 +261,6 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
o.Options = &httpsOptions
|
o.Options = &httpsOptions
|
||||||
if serverURL == nil {
|
|
||||||
return E.New("invalid server address")
|
|
||||||
}
|
|
||||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||||
if !serverAddr.IsValid() {
|
if !serverAddr.IsValid() {
|
||||||
return E.New("invalid server address")
|
return E.New("invalid server address")
|
||||||
@ -282,9 +274,6 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
case "rcode":
|
case "rcode":
|
||||||
var rcode int
|
var rcode int
|
||||||
if serverURL == nil {
|
|
||||||
return E.New("invalid server address")
|
|
||||||
}
|
|
||||||
switch serverURL.Host {
|
switch serverURL.Host {
|
||||||
case "success":
|
case "success":
|
||||||
rcode = dns.RcodeSuccess
|
rcode = dns.RcodeSuccess
|
||||||
@ -306,9 +295,6 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
case C.DNSTypeDHCP:
|
case C.DNSTypeDHCP:
|
||||||
o.Type = C.DNSTypeDHCP
|
o.Type = C.DNSTypeDHCP
|
||||||
dhcpOptions := DHCPDNSServerOptions{}
|
dhcpOptions := DHCPDNSServerOptions{}
|
||||||
if serverURL == nil {
|
|
||||||
return E.New("invalid server address")
|
|
||||||
}
|
|
||||||
if serverURL.Host != "" && serverURL.Host != "auto" {
|
if serverURL.Host != "" && serverURL.Host != "auto" {
|
||||||
dhcpOptions.Interface = serverURL.Host
|
dhcpOptions.Interface = serverURL.Host
|
||||||
}
|
}
|
||||||
|
@ -67,46 +67,42 @@ func (r Rule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawDefaultRule struct {
|
type RawDefaultRule struct {
|
||||||
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
||||||
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
||||||
Client badoption.Listable[string] `json:"client,omitempty"`
|
Client badoption.Listable[string] `json:"client,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
||||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
||||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
User badoption.Listable[string] `json:"user,omitempty"`
|
User badoption.Listable[string] `json:"user,omitempty"`
|
||||||
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"`
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
|
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
|
|
||||||
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
||||||
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
||||||
|
@ -68,48 +68,45 @@ func (r DNSRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawDefaultDNSRule struct {
|
type RawDefaultDNSRule struct {
|
||||||
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
||||||
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
||||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
||||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||||
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
User badoption.Listable[string] `json:"user,omitempty"`
|
User badoption.Listable[string] `json:"user,omitempty"`
|
||||||
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
||||||
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
|
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"`
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
|
||||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
|
|
||||||
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
||||||
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
||||||
|
@ -182,31 +182,28 @@ func (r HeadlessRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DefaultHeadlessRule struct {
|
type DefaultHeadlessRule struct {
|
||||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
Invert bool `json:"invert,omitempty"`
|
||||||
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
|
||||||
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
|
|
||||||
DomainMatcher *domain.Matcher `json:"-"`
|
DomainMatcher *domain.Matcher `json:"-"`
|
||||||
SourceIPSet *netipx.IPSet `json:"-"`
|
SourceIPSet *netipx.IPSet `json:"-"`
|
||||||
@ -243,7 +240,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat
|
|||||||
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
|
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
|
||||||
var v any
|
var v any
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
||||||
v = r.Options
|
v = r.Options
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown rule-set version: ", r.Version)
|
return nil, E.New("unknown rule-set version: ", r.Version)
|
||||||
@ -258,7 +255,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
var v any
|
var v any
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
||||||
v = &r.Options
|
v = &r.Options
|
||||||
case 0:
|
case 0:
|
||||||
return E.New("missing rule-set version")
|
return E.New("missing rule-set version")
|
||||||
@ -275,7 +272,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
|
|||||||
|
|
||||||
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
|
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
||||||
default:
|
default:
|
||||||
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
|
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
type SSMAPIServiceOptions struct {
|
type SSMAPIServiceOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Servers *badjson.TypedMap[string, string] `json:"servers"`
|
Servers *badjson.TypedMap[string, string] `json:"servers"`
|
||||||
CachePath string `json:"cache_path,omitempty"`
|
|
||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
}
|
}
|
||||||
|
@ -37,22 +37,19 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTLSOptions struct {
|
type OutboundTLSOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
DisableSNI bool `json:"disable_sni,omitempty"`
|
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
Insecure bool `json:"insecure,omitempty"`
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
||||||
MinVersion string `json:"min_version,omitempty"`
|
MinVersion string `json:"min_version,omitempty"`
|
||||||
MaxVersion string `json:"max_version,omitempty"`
|
MaxVersion string `json:"max_version,omitempty"`
|
||||||
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
Fragment bool `json:"fragment,omitempty"`
|
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
||||||
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
|
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
||||||
RecordFragment bool `json:"record_fragment,omitempty"`
|
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
||||||
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
|
||||||
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
|
||||||
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTLSOptionsContainer struct {
|
type OutboundTLSOptionsContainer struct {
|
||||||
|
@ -20,7 +20,6 @@ type TunInboundOptions struct {
|
|||||||
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
||||||
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
|
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
|
||||||
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"`
|
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"`
|
||||||
LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"`
|
|
||||||
StrictRoute bool `json:"strict_route,omitempty"`
|
StrictRoute bool `json:"strict_route,omitempty"`
|
||||||
RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"`
|
RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"`
|
||||||
RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"`
|
RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"`
|
||||||
|
@ -26,7 +26,7 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||||||
|
|
||||||
type Outbound struct {
|
type Outbound struct {
|
||||||
outbound.Adapter
|
outbound.Adapter
|
||||||
dialer tls.Dialer
|
dialer N.Dialer
|
||||||
server M.Socksaddr
|
server M.Socksaddr
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
client *anytls.Client
|
client *anytls.Client
|
||||||
@ -58,8 +58,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
outbound.dialer = outboundDialer
|
||||||
outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig)
|
|
||||||
|
|
||||||
client, err := anytls.NewClient(ctx, anytls.ClientConfig{
|
client, err := anytls.NewClient(ctx, anytls.ClientConfig{
|
||||||
Password: options.Password,
|
Password: options.Password,
|
||||||
@ -92,7 +91,16 @@ func (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
|
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
|
||||||
return h.dialer.DialTLSContext(ctx, h.server)
|
conn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConn, err := tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
common.Close(tlsConn, conn)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
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