mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
Compare commits
78 Commits
dev-go124-
...
dev-ts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbcd4cf312 | ||
|
|
2cb5ff521a | ||
|
|
364a055f77 | ||
|
|
8bc14592d7 | ||
|
|
78084f61c8 | ||
|
|
f740d2a2de | ||
|
|
33b7c11470 | ||
|
|
ad3357c863 | ||
|
|
2cf1bfbdcc | ||
|
|
5d8764f34d | ||
|
|
d7361263ba | ||
|
|
f57171cf91 | ||
|
|
5b8e750944 | ||
|
|
fcdfc4e4b6 | ||
|
|
83563645fb | ||
|
|
6c4081f8f4 | ||
|
|
84101cc69b | ||
|
|
cecc7ea56c | ||
|
|
2a995bc716 | ||
|
|
342c633af4 | ||
|
|
3b3325e15e | ||
|
|
bb6bf22824 | ||
|
|
e264c49d7a | ||
|
|
d572343b20 | ||
|
|
a9e51f3204 | ||
|
|
8de6d7e1df | ||
|
|
0ec4929af6 | ||
|
|
24443f7e2c | ||
|
|
d472229f60 | ||
|
|
e5cdf22ffc | ||
|
|
6f7b1ed899 | ||
|
|
0bda2eb98e | ||
|
|
e5f2263e8a | ||
|
|
6cf08f14cc | ||
|
|
3310d0716e | ||
|
|
0f7ffeed5c | ||
|
|
3378102c37 | ||
|
|
bd4858f627 | ||
|
|
be5a45e942 | ||
|
|
2a6e2feebf | ||
|
|
bda049f6a1 | ||
|
|
b50f65f4ab | ||
|
|
c0b9dd2584 | ||
|
|
b3efa1124e | ||
|
|
007b19d768 | ||
|
|
d286b6e631 | ||
|
|
5ebc86c796 | ||
|
|
59342bd152 | ||
|
|
253b41936e | ||
|
|
ce5b4b06b5 | ||
|
|
50f5006c43 | ||
|
|
e42ff22c2e | ||
|
|
578571b972 | ||
|
|
935beca45d | ||
|
|
3e246f1173 | ||
|
|
1bc27a32c2 | ||
|
|
bc2e3960e4 | ||
|
|
9c4ab0bf33 | ||
|
|
27bdef34c7 | ||
|
|
3c00099ed4 | ||
|
|
2babf07f9a | ||
|
|
4795ed712b | ||
|
|
d4cd564dbe | ||
|
|
1676e13d3e | ||
|
|
50576084c6 | ||
|
|
3a94e792a2 | ||
|
|
9f69f41f68 | ||
|
|
e6847ff50e | ||
|
|
2ac2589d14 | ||
|
|
64a94e8144 | ||
|
|
3ed8a5c5d1 | ||
|
|
0a922c6fe3 | ||
|
|
52f3a4226c | ||
|
|
483d9fa503 | ||
|
|
dd9de694f8 | ||
|
|
5cdf5c1d9e | ||
|
|
cec7e47086 | ||
|
|
1e6a3f1f0b |
134
.github/workflows/build.yml
vendored
134
.github/workflows/build.yml
vendored
@@ -7,11 +7,6 @@ on:
|
|||||||
description: "Version name"
|
description: "Version name"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
prerelease:
|
|
||||||
description: "Is prerelease"
|
|
||||||
required: true
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
build:
|
build:
|
||||||
description: "Build type"
|
description: "Build type"
|
||||||
required: true
|
required: true
|
||||||
@@ -28,10 +23,6 @@ on:
|
|||||||
- tvOS
|
- tvOS
|
||||||
- macOS-standalone
|
- macOS-standalone
|
||||||
- publish-android
|
- publish-android
|
||||||
macos_project_version:
|
|
||||||
description: "macOS project version"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main-next
|
- main-next
|
||||||
@@ -47,7 +38,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
prerelease: ${{ steps.outputs.outputs.prerelease }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
@@ -61,9 +51,7 @@ jobs:
|
|||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
echo "version=${{ inputs.version }}"
|
echo "version=${{ inputs.version }}"
|
||||||
echo "prerelease=${{ inputs.prerelease }}"
|
|
||||||
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||||
echo "prerelease=${{ inputs.prerelease }}" >> "$GITHUB_ENV"
|
|
||||||
- name: Calculate version
|
- name: Calculate version
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -72,7 +60,6 @@ jobs:
|
|||||||
id: outputs
|
id: outputs
|
||||||
run: |-
|
run: |-
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
|
|
||||||
build:
|
build:
|
||||||
name: Build binary
|
name: Build binary
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||||
@@ -183,7 +170,8 @@ jobs:
|
|||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git tag v${{ needs.calculate_version.outputs.version }}
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.goos != 'android'
|
if: matrix.goos != 'android'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -243,7 +231,8 @@ jobs:
|
|||||||
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git tag v${{ needs.calculate_version.outputs.version }}
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
- name: Build library
|
- name: Build library
|
||||||
run: |-
|
run: |-
|
||||||
make lib_install
|
make lib_install
|
||||||
@@ -253,12 +242,12 @@ jobs:
|
|||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: needs.calculate_version.outputs.prerelease == 'false'
|
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: needs.calculate_version.outputs.prerelease == 'true'
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -317,7 +306,8 @@ jobs:
|
|||||||
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git tag v${{ needs.calculate_version.outputs.version }}
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
- name: Build library
|
- name: Build library
|
||||||
run: |-
|
run: |-
|
||||||
make lib_install
|
make lib_install
|
||||||
@@ -327,12 +317,12 @@ jobs:
|
|||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: needs.calculate_version.outputs.prerelease == 'false'
|
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: needs.calculate_version.outputs.prerelease == 'true'
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -354,67 +344,38 @@ jobs:
|
|||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||||
build_apple_library:
|
|
||||||
name: Build Apple library
|
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'
|
|
||||||
runs-on: macos-15
|
|
||||||
needs:
|
|
||||||
- calculate_version
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
submodules: 'recursive'
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.23
|
|
||||||
- name: Setup Xcode
|
|
||||||
run: |-
|
|
||||||
sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app
|
|
||||||
- name: Set tag
|
|
||||||
run: |-
|
|
||||||
git tag v${{ needs.calculate_version.outputs.version }}
|
|
||||||
- name: Build library
|
|
||||||
run: |-
|
|
||||||
make lib_install
|
|
||||||
export PATH="$PATH:$(go env GOPATH)/bin"
|
|
||||||
make lib_ios
|
|
||||||
- name: Upload library
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: library-apple
|
|
||||||
path: 'Libbox.xcframework'
|
|
||||||
build_apple:
|
build_apple:
|
||||||
name: Build Apple clients
|
name: Build Apple clients
|
||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
- build_apple_library
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: iOS
|
- name: iOS
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
|
||||||
|
platform: ios
|
||||||
scheme: SFI
|
scheme: SFI
|
||||||
destination: 'generic/platform=iOS'
|
destination: 'generic/platform=iOS'
|
||||||
archive: build/SFI.xcarchive
|
archive: build/SFI.xcarchive
|
||||||
upload: SFI/Upload.plist
|
upload: SFI/Upload.plist
|
||||||
- name: macOS
|
- name: macOS
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
|
||||||
|
platform: macos
|
||||||
scheme: SFM
|
scheme: SFM
|
||||||
destination: 'generic/platform=macOS'
|
destination: 'generic/platform=macOS'
|
||||||
archive: build/SFM.xcarchive
|
archive: build/SFM.xcarchive
|
||||||
upload: SFI/Upload.plist
|
upload: SFI/Upload.plist
|
||||||
- name: tvOS
|
- name: tvOS
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
|
||||||
|
platform: tvos
|
||||||
scheme: SFT
|
scheme: SFT
|
||||||
destination: 'generic/platform=tvOS'
|
destination: 'generic/platform=tvOS'
|
||||||
archive: build/SFT.xcarchive
|
archive: build/SFT.xcarchive
|
||||||
upload: SFI/Upload.plist
|
upload: SFI/Upload.plist
|
||||||
- name: macOS-standalone
|
- name: macOS-standalone
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
|
||||||
|
platform: macos
|
||||||
scheme: SFM.System
|
scheme: SFM.System
|
||||||
destination: 'generic/platform=macOS'
|
destination: 'generic/platform=macOS'
|
||||||
archive: build/SFM.System.xcarchive
|
archive: build/SFM.System.xcarchive
|
||||||
@@ -432,22 +393,27 @@ jobs:
|
|||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.23
|
go-version: ^1.23
|
||||||
- name: Setup Xcode
|
- name: Setup Xcode stable
|
||||||
if: matrix.if
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
|
- name: Setup Xcode beta
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
git tag v${{ needs.calculate_version.outputs.version }}
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
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: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: matrix.if && needs.calculate_version.outputs.prerelease == 'false'
|
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: matrix.if && needs.calculate_version.outputs.prerelease == 'true'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -478,6 +444,10 @@ jobs:
|
|||||||
--key $ASC_KEY_PATH \
|
--key $ASC_KEY_PATH \
|
||||||
--key-id $ASC_KEY_ID \
|
--key-id $ASC_KEY_ID \
|
||||||
--issuer $ASC_KEY_ISSUER_ID
|
--issuer $ASC_KEY_ISSUER_ID
|
||||||
|
|
||||||
|
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
||||||
|
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
||||||
|
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
||||||
env:
|
env:
|
||||||
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
|
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
|
||||||
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||||
@@ -486,12 +456,19 @@ jobs:
|
|||||||
ASC_KEY: ${{ secrets.ASC_KEY }}
|
ASC_KEY: ${{ secrets.ASC_KEY }}
|
||||||
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||||
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
|
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
|
||||||
- name: Download library
|
- name: Build library
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/download-artifact@v4
|
run: |-
|
||||||
with:
|
make lib_install
|
||||||
name: library-apple
|
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||||
path: clients/apple/Libbox.xcframework
|
go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
|
||||||
|
mv Libbox.xcframework clients/apple
|
||||||
|
- name: Update macOS version
|
||||||
|
if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
||||||
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
||||||
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -503,27 +480,25 @@ jobs:
|
|||||||
-destination "${{ matrix.destination }}" \
|
-destination "${{ matrix.destination }}" \
|
||||||
-archivePath "${{ matrix.archive }}" \
|
-archivePath "${{ matrix.archive }}" \
|
||||||
-allowProvisioningUpdates \
|
-allowProvisioningUpdates \
|
||||||
-authenticationKeyPath $RUNNER_TEMP/Key.p12 \
|
-authenticationKeyPath $ASC_KEY_PATH \
|
||||||
-authenticationKeyID $ASC_KEY_ID \
|
-authenticationKeyID $ASC_KEY_ID \
|
||||||
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||||
env:
|
|
||||||
MACOS_PROJECT_VERSION: ${{ inputs.macos_project_version }}
|
|
||||||
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
|
||||||
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
|
|
||||||
- name: Upload to App Store Connect
|
- name: Upload to App Store Connect
|
||||||
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
|
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
xcodebuild -exportArchive \
|
xcodebuild -exportArchive \
|
||||||
-archivePath "${{ matrix.archive }}" \
|
-archivePath "${{ matrix.archive }}" \
|
||||||
-exportOptionsPlist ${{ matrix.upload }} \
|
-exportOptionsPlist ${{ matrix.upload }} \
|
||||||
-allowProvisioningUpdates \
|
-allowProvisioningUpdates \
|
||||||
-authenticationKeyPath $RUNNER_TEMP/Key.p12 \
|
-authenticationKeyPath $ASC_KEY_PATH \
|
||||||
-authenticationKeyID $ASC_KEY_ID \
|
-authenticationKeyID $ASC_KEY_ID \
|
||||||
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||||
env:
|
- name: Publish to TestFlight
|
||||||
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
|
||||||
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
|
run: |-
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
||||||
- name: Build image
|
- name: Build image
|
||||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -557,7 +532,7 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: always() && github.event_name == 'workflow_dispatch' && inputs.build != 'publish-android'
|
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
|
||||||
@@ -591,7 +566,8 @@ jobs:
|
|||||||
go install -v .
|
go install -v .
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git tag v${{ needs.calculate_version.outputs.version }}
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
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@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -608,8 +584,16 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
- name: Upload builds
|
- name: Upload builds
|
||||||
|
if: ${{ env.PUBLISHED == 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Replace builds
|
||||||
|
if: ${{ env.PUBLISHED != 'false' }}
|
||||||
|
run: |-
|
||||||
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
|
ghr --replace -p 5 "v${VERSION}" dist/release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ linters-settings:
|
|||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.23"
|
go: "1.23"
|
||||||
|
build-tags:
|
||||||
|
- with_gvisor
|
||||||
|
- with_quic
|
||||||
|
- with_dhcp
|
||||||
|
- with_wireguard
|
||||||
|
- with_ech
|
||||||
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
- badlinkname
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-dirs:
|
exclude-dirs:
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ builds:
|
|||||||
- -v
|
- -v
|
||||||
- -trimpath
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
|
- -s
|
||||||
|
- -buildid=
|
||||||
|
- -checklinkname=0
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ builds:
|
|||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
- -s
|
- -s
|
||||||
- -buildid=
|
- -buildid=
|
||||||
|
- -checklinkname=0
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ RUN set -ex \
|
|||||||
&& go build -v -trimpath -tags \
|
&& go build -v -trimpath -tags \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -2,14 +2,15 @@ NAME = sing-box
|
|||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||||
TAGS_GO121 = with_ech
|
TAGS_GO121 = with_ech
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
TAGS_GO123 = with_tailscale,badlinkname
|
||||||
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121),$(TAGS_GO123)
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
|
||||||
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
@@ -28,7 +29,7 @@ ci_build:
|
|||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags generate,generate_completions $(MAIN)
|
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -182,10 +183,22 @@ release_tvos: build_tvos upload_tvos_app_store
|
|||||||
update_apple_version:
|
update_apple_version:
|
||||||
go run ./cmd/internal/update_apple_version
|
go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
|
update_macos_version:
|
||||||
|
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||||
|
|
||||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||||
|
|
||||||
|
publish_testflight:
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_testflight
|
||||||
|
|
||||||
|
prepare_app_store:
|
||||||
|
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
||||||
|
|
||||||
|
publish_app_store:
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_app_store
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v ./... && \
|
@go test -v ./... && \
|
||||||
cd test && \
|
cd test && \
|
||||||
@@ -204,11 +217,11 @@ lib_android:
|
|||||||
lib_android_debug:
|
lib_android_debug:
|
||||||
go run ./cmd/internal/build_libbox -target android -debug
|
go run ./cmd/internal/build_libbox -target android -debug
|
||||||
|
|
||||||
lib_ios:
|
lib_apple:
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target apple
|
||||||
|
|
||||||
lib_ios_debug:
|
lib_ios:
|
||||||
go run ./cmd/internal/build_libbox -target ios -debug
|
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
||||||
|
|
||||||
lib:
|
lib:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
go run ./cmd/internal/build_libbox -target android
|
||||||
|
|||||||
73
adapter/dns.go
Normal file
73
adapter/dns.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSRouter interface {
|
||||||
|
Lifecycle
|
||||||
|
Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error)
|
||||||
|
Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error)
|
||||||
|
ClearCache()
|
||||||
|
LookupReverseMapping(ip netip.Addr) (string, bool)
|
||||||
|
ResetNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSClient interface {
|
||||||
|
Start()
|
||||||
|
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
||||||
|
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||||
|
LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool)
|
||||||
|
ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool)
|
||||||
|
ClearCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSQueryOptions struct {
|
||||||
|
Transport DNSTransport
|
||||||
|
Strategy C.DomainStrategy
|
||||||
|
DisableCache bool
|
||||||
|
RewriteTTL *uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
type RDRCStore interface {
|
||||||
|
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
|
||||||
|
SaveRDRC(transportName string, qName string, qType uint16) error
|
||||||
|
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransport interface {
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
Dependencies() []string
|
||||||
|
Reset()
|
||||||
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyDNSTransport interface {
|
||||||
|
LegacyStrategy() C.DomainStrategy
|
||||||
|
LegacyClientSubnet() netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransportRegistry interface {
|
||||||
|
option.DNSTransportOptionsRegistry
|
||||||
|
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransportManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Transports() []DNSTransport
|
||||||
|
Transport(tag string) (DNSTransport, bool)
|
||||||
|
Default() DNSTransport
|
||||||
|
FakeIP() FakeIPTransport
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ type CacheFile interface {
|
|||||||
FakeIPStorage
|
FakeIPStorage
|
||||||
|
|
||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
dns.RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,6 +26,6 @@ type FakeIPStorage interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FakeIPTransport interface {
|
type FakeIPTransport interface {
|
||||||
dns.Transport
|
DNSTransport
|
||||||
Store() FakeIPStore
|
Store() FakeIPStore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,13 +72,11 @@ type InboundContext struct {
|
|||||||
UDPConnect bool
|
UDPConnect bool
|
||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
|
|
||||||
NetworkStrategy C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
FallbackNetworkType []C.InterfaceType
|
FallbackNetworkType []C.InterfaceType
|
||||||
FallbackDelay time.Duration
|
FallbackDelay time.Duration
|
||||||
|
|
||||||
DNSServer string
|
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type NetworkManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NetworkOptions struct {
|
type NetworkOptions struct {
|
||||||
NetworkStrategy C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
FallbackNetworkType []C.InterfaceType
|
FallbackNetworkType []C.InterfaceType
|
||||||
FallbackDelay time.Duration
|
FallbackDelay time.Duration
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type Manager struct {
|
|||||||
registry adapter.OutboundRegistry
|
registry adapter.OutboundRegistry
|
||||||
endpoint adapter.EndpointManager
|
endpoint adapter.EndpointManager
|
||||||
defaultTag string
|
defaultTag string
|
||||||
access sync.Mutex
|
access sync.RWMutex
|
||||||
started bool
|
started bool
|
||||||
stage adapter.StartStage
|
stage adapter.StartStage
|
||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
@@ -169,15 +169,15 @@ func (m *Manager) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Outbounds() []adapter.Outbound {
|
func (m *Manager) Outbounds() []adapter.Outbound {
|
||||||
m.access.Lock()
|
m.access.RLock()
|
||||||
defer m.access.Unlock()
|
defer m.access.RUnlock()
|
||||||
return m.outbounds
|
return m.outbounds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
m.access.Lock()
|
m.access.RLock()
|
||||||
outbound, found := m.outboundByTag[tag]
|
outbound, found := m.outboundByTag[tag]
|
||||||
m.access.Unlock()
|
m.access.RUnlock()
|
||||||
if found {
|
if found {
|
||||||
return outbound, true
|
return outbound, true
|
||||||
}
|
}
|
||||||
@@ -185,8 +185,8 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Default() adapter.Outbound {
|
func (m *Manager) Default() adapter.Outbound {
|
||||||
m.access.Lock()
|
m.access.RLock()
|
||||||
defer m.access.Unlock()
|
defer m.access.RUnlock()
|
||||||
if m.defaultOutbound != nil {
|
if m.defaultOutbound != nil {
|
||||||
return m.defaultOutbound
|
return m.defaultOutbound
|
||||||
} else {
|
} else {
|
||||||
@@ -196,9 +196,9 @@ func (m *Manager) Default() adapter.Outbound {
|
|||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
func (m *Manager) Remove(tag string) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
outbound, found := m.outboundByTag[tag]
|
outbound, found := m.outboundByTag[tag]
|
||||||
if !found {
|
if !found {
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
delete(m.outboundByTag, tag)
|
delete(m.outboundByTag, tag)
|
||||||
@@ -232,7 +232,6 @@ func (m *Manager) Remove(tag string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
if started {
|
||||||
return common.Close(outbound)
|
return common.Close(outbound)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,42 +4,25 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
mdns "github.com/miekg/dns"
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
|
||||||
|
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext) error
|
PreMatch(metadata InboundContext) error
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
|
||||||
LoadGeosite(code string) (Rule, error)
|
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
|
|
||||||
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
|
||||||
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
|
||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
|
||||||
ClearDNSCache()
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
|
||||||
SetTracker(tracker ConnectionTracker)
|
SetTracker(tracker ConnectionTracker)
|
||||||
|
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ type Rule interface {
|
|||||||
HeadlessRule
|
HeadlessRule
|
||||||
Service
|
Service
|
||||||
Type() string
|
Type() string
|
||||||
UpdateGeosite() error
|
|
||||||
Action() RuleAction
|
Action() RuleAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
83
box.go
83
box.go
@@ -14,7 +14,10 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
@@ -40,6 +43,8 @@ type Box struct {
|
|||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
|
dnsTransport *dns.TransportManager
|
||||||
|
dnsRouter *dns.Router
|
||||||
connection *route.ConnectionManager
|
connection *route.ConnectionManager
|
||||||
router *route.Router
|
router *route.Router
|
||||||
services []adapter.LifecycleService
|
services []adapter.LifecycleService
|
||||||
@@ -57,6 +62,7 @@ func Context(
|
|||||||
inboundRegistry adapter.InboundRegistry,
|
inboundRegistry adapter.InboundRegistry,
|
||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -73,6 +79,10 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||||
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||||
}
|
}
|
||||||
|
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
|
}
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +97,7 @@ func New(options Options) (*Box, error) {
|
|||||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@@ -131,13 +142,17 @@ func New(options Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
|
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
@@ -145,10 +160,40 @@ func New(options Options) (*Box, error) {
|
|||||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS))
|
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
||||||
|
service.MustRegister[adapter.Router](ctx, router)
|
||||||
|
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize router")
|
return nil, E.Cause(err, "initialize router")
|
||||||
}
|
}
|
||||||
|
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||||
|
var timeService *tls.TimeServiceWrapper
|
||||||
|
if ntpOptions.Enabled {
|
||||||
|
timeService = new(tls.TimeServiceWrapper)
|
||||||
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
|
}
|
||||||
|
for i, transportOptions := range dnsOptions.Servers {
|
||||||
|
var tag string
|
||||||
|
if transportOptions.Tag != "" {
|
||||||
|
tag = transportOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = dnsTransportManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
transportOptions.Type,
|
||||||
|
transportOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = dnsRouter.Initialize(dnsOptions.Rules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize dns router")
|
||||||
|
}
|
||||||
for i, endpointOptions := range options.Endpoints {
|
for i, endpointOptions := range options.Endpoints {
|
||||||
var tag string
|
var tag string
|
||||||
if endpointOptions.Tag != "" {
|
if endpointOptions.Tag != "" {
|
||||||
@@ -156,7 +201,8 @@ func New(options Options) (*Box, error) {
|
|||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = endpointManager.Create(ctx,
|
err = endpointManager.Create(
|
||||||
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
@@ -174,7 +220,8 @@ func New(options Options) (*Box, error) {
|
|||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = inboundManager.Create(ctx,
|
err = inboundManager.Create(
|
||||||
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
@@ -220,6 +267,13 @@ func New(options Options) (*Box, error) {
|
|||||||
option.DirectOutboundOptions{},
|
option.DirectOutboundOptions{},
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
dnsTransportManager.Initialize(common.Must1(
|
||||||
|
local.NewTransport(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger("dns/local"),
|
||||||
|
"local",
|
||||||
|
option.LocalDNSServerOptions{},
|
||||||
|
)))
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -254,13 +308,12 @@ func New(options Options) (*Box, error) {
|
|||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
|
||||||
if ntpOptions.Enabled {
|
if ntpOptions.Enabled {
|
||||||
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
|
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create NTP service")
|
return nil, E.Cause(err, "create NTP service")
|
||||||
}
|
}
|
||||||
timeService := ntp.NewService(ntp.Options{
|
ntpService := ntp.NewService(ntp.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Dialer: ntpDialer,
|
Dialer: ntpDialer,
|
||||||
Logger: logFactory.NewLogger("ntp"),
|
Logger: logFactory.NewLogger("ntp"),
|
||||||
@@ -268,14 +321,16 @@ func New(options Options) (*Box, error) {
|
|||||||
Interval: time.Duration(ntpOptions.Interval),
|
Interval: time.Duration(ntpOptions.Interval),
|
||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
})
|
})
|
||||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
timeService.TimeService = ntpService
|
||||||
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
|
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
|
dnsTransport: dnsTransportManager,
|
||||||
|
dnsRouter: dnsRouter,
|
||||||
connection: connectionManager,
|
connection: connectionManager,
|
||||||
router: router,
|
router: router,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
@@ -336,11 +391,11 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -364,7 +419,7 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -372,7 +427,7 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -391,7 +446,7 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.inbound, s.outbound, s.router, s.connection, s.network,
|
s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.services {
|
for _, lifecycleService := range s.services {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
|
|||||||
Submodule clients/android updated: cff12c57dd...b17fb6d857
Submodule clients/apple updated: fa107e3b7c...64a4614aca
445
cmd/internal/app_store_connect/main.go
Normal file
445
cmd/internal/app_store_connect/main.go
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/asc-go/asc"
|
||||||
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "next_macos_project_version":
|
||||||
|
err := fetchMacOSVersion(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "publish_testflight":
|
||||||
|
err := publishTestflight(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "cancel_app_store":
|
||||||
|
err := cancelAppStore(ctx, os.Args[2])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "prepare_app_store":
|
||||||
|
err := prepareAppStore(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "publish_app_store":
|
||||||
|
err := publishAppStore(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatal("unknown action: ", os.Args[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
appID = "6673731168"
|
||||||
|
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createClient(expireDuration time.Duration) *asc.Client {
|
||||||
|
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return asc.NewClient(tokenConfig.Client())
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchMacOSVersion(ctx context.Context) error {
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{"MAC_OS"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var versionID string
|
||||||
|
findVersion:
|
||||||
|
for _, version := range versions.Data {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStateReadyForSale,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
versionID = version.ID
|
||||||
|
break findVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if versionID == "" {
|
||||||
|
return E.New("no version found")
|
||||||
|
}
|
||||||
|
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "parse version code")
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishTestflight(ctx context.Context) error {
|
||||||
|
tagVersion, err := build_shared.ReadTagVersion()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tag := tagVersion.VersionString()
|
||||||
|
client := createClient(10 * time.Minute)
|
||||||
|
|
||||||
|
log.Info(tag, " list build IDs")
|
||||||
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
||||||
|
return it.ID
|
||||||
|
})
|
||||||
|
var platforms []asc.Platform
|
||||||
|
if len(os.Args) == 3 {
|
||||||
|
switch os.Args[2] {
|
||||||
|
case "ios":
|
||||||
|
platforms = []asc.Platform{asc.PlatformIOS}
|
||||||
|
case "macos":
|
||||||
|
platforms = []asc.Platform{asc.PlatformMACOS}
|
||||||
|
case "tvos":
|
||||||
|
platforms = []asc.Platform{asc.PlatformTVOS}
|
||||||
|
default:
|
||||||
|
return E.New("unknown platform: ", os.Args[2])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
platforms = []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, platform := range platforms {
|
||||||
|
log.Info(string(platform), " list builds")
|
||||||
|
for {
|
||||||
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
|
FilterApp: []string{appID},
|
||||||
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
build := builds.Data[0]
|
||||||
|
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||||
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *build.Attributes.ProcessingState != "VALID" {
|
||||||
|
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list localizations")
|
||||||
|
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
|
||||||
|
return *it.Attributes.Locale == "en-US"
|
||||||
|
})
|
||||||
|
if localization.ID == "" {
|
||||||
|
log.Fatal(string(platform), " ", tag, " no en-US localization found")
|
||||||
|
}
|
||||||
|
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " update localization")
|
||||||
|
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
|
||||||
|
F.ToString("sing-box ", tagVersion.String()),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
|
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
||||||
|
log.Info("waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list submissions")
|
||||||
|
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
||||||
|
FilterBuild: []string{build.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(betaSubmissions.Data) == 0 {
|
||||||
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
|
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelAppStore(ctx context.Context, platform string) error {
|
||||||
|
switch platform {
|
||||||
|
case "ios":
|
||||||
|
platform = string(asc.PlatformIOS)
|
||||||
|
case "macos":
|
||||||
|
platform = string(asc.PlatformMACOS)
|
||||||
|
case "tvos":
|
||||||
|
platform = string(asc.PlatformTVOS)
|
||||||
|
}
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for {
|
||||||
|
log.Info(platform, " list versions")
|
||||||
|
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if isRetryable(response) {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
if version.ID == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Info(platform, " ", tag, " get submission")
|
||||||
|
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if response != nil && response.StatusCode == http.StatusNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if isRetryable(response) {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info(platform, " ", tag, " delete submission")
|
||||||
|
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareAppStore(ctx context.Context) error {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for _, platform := range []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
} {
|
||||||
|
log.Info(string(platform), " list versions")
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
log.Info(string(platform), " ", tag, " list builds")
|
||||||
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
|
FilterApp: []string{appID},
|
||||||
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(builds.Data) == 0 {
|
||||||
|
log.Fatal(platform, " ", tag, " no build found")
|
||||||
|
}
|
||||||
|
buildID := common.Ptr(builds.Data[0].ID)
|
||||||
|
if version.ID == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " create version")
|
||||||
|
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
|
||||||
|
Platform: platform,
|
||||||
|
VersionString: tag,
|
||||||
|
}, appID, buildID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version = newVersion.Data
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Info(string(platform), " ", tag, " check build")
|
||||||
|
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission,
|
||||||
|
asc.AppStoreVersionStateRejected,
|
||||||
|
asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if submission != nil {
|
||||||
|
log.Info(string(platform), " ", tag, " delete submission")
|
||||||
|
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " update build")
|
||||||
|
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusNoContent {
|
||||||
|
response.Write(os.Stderr)
|
||||||
|
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission,
|
||||||
|
asc.AppStoreVersionStateRejected,
|
||||||
|
asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list localization")
|
||||||
|
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
|
||||||
|
return *it.Attributes.Locale == "en-US"
|
||||||
|
})
|
||||||
|
if localization.ID == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " no en-US localization found")
|
||||||
|
}
|
||||||
|
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " update localization")
|
||||||
|
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
|
||||||
|
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
|
||||||
|
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
|
fixSubmit:
|
||||||
|
for {
|
||||||
|
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
|
||||||
|
if err != nil {
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusCreated:
|
||||||
|
break fixSubmit
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishAppStore(ctx context.Context) error {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for _, platform := range []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
} {
|
||||||
|
log.Info(string(platform), " list versions")
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
log.Fatal(string(platform), " ", tag, " not submitted")
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview:
|
||||||
|
log.Warn(string(platform), " ", tag, " waiting for review")
|
||||||
|
continue
|
||||||
|
case asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRetryable(response *asc.Response) bool {
|
||||||
|
if response == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,11 +18,13 @@ import (
|
|||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
|
platform string
|
||||||
)
|
)
|
||||||
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -33,8 +35,8 @@ func main() {
|
|||||||
switch target {
|
switch target {
|
||||||
case "android":
|
case "android":
|
||||||
buildAndroid()
|
buildAndroid()
|
||||||
case "ios":
|
case "apple":
|
||||||
buildiOS()
|
buildApple()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,10 +55,10 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0")
|
||||||
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_ech", "with_utls", "with_clash_api")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api", "with_tailscale")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
@@ -81,7 +83,9 @@ func buildAndroid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var bindTarget string
|
var bindTarget string
|
||||||
if debugEnabled {
|
if platform != "" {
|
||||||
|
bindTarget = platform
|
||||||
|
} else if debugEnabled {
|
||||||
bindTarget = "android/arm64"
|
bindTarget = "android/arm64"
|
||||||
} else {
|
} else {
|
||||||
bindTarget = "android"
|
bindTarget = "android"
|
||||||
@@ -129,12 +133,14 @@ func buildAndroid() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildiOS() {
|
func buildApple() {
|
||||||
var bindTarget string
|
var bindTarget string
|
||||||
if debugEnabled {
|
if platform != "" {
|
||||||
|
bindTarget = platform
|
||||||
|
} else if debugEnabled {
|
||||||
bindTarget = "ios"
|
bindTarget = "ios"
|
||||||
} else {
|
} else {
|
||||||
bindTarget = "ios,iossimulator,tvos,tvossimulator,macos"
|
bindTarget = "ios,tvos,macos"
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
|
|||||||
@@ -36,11 +36,3 @@ func ReadTagVersion() (badversion.Version, error) {
|
|||||||
}
|
}
|
||||||
return version, nil
|
return version, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsDevBranch() bool {
|
|
||||||
branch, err := shell.Exec("git", "branch", "--show-current").ReadOutput()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return branch == "dev-next"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var nightly bool
|
var nightly bool
|
||||||
@@ -22,25 +21,14 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
var (
|
var versionStr string
|
||||||
versionStr string
|
|
||||||
isPrerelease bool
|
|
||||||
)
|
|
||||||
if version.PreReleaseIdentifier != "" {
|
if version.PreReleaseIdentifier != "" {
|
||||||
isPrerelease = true
|
|
||||||
versionStr = version.VersionString() + "-nightly"
|
versionStr = version.VersionString() + "-nightly"
|
||||||
} else {
|
} else {
|
||||||
version.Patch++
|
version.Patch++
|
||||||
versionStr = version.VersionString() + "-nightly"
|
versionStr = version.VersionString() + "-nightly"
|
||||||
}
|
}
|
||||||
if build_shared.IsDevBranch() {
|
err = setGitHubEnv("version", versionStr)
|
||||||
isPrerelease = true
|
|
||||||
}
|
|
||||||
err = setGitHubOutput("version", versionStr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
err = setGitHubOutput("prerelease", F.ToString(isPrerelease))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -55,7 +43,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setGitHubOutput(name string, value string) error {
|
func setGitHubEnv(name string, value string) error {
|
||||||
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var commandMerge = &cobra.Command{
|
var commandMerge = &cobra.Command{
|
||||||
Use: "merge <output>",
|
Use: "merge <output-path>",
|
||||||
Short: "Merge configurations",
|
Short: "Merge configurations",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := merge(args[0])
|
err := merge(args[0])
|
||||||
|
|||||||
162
cmd/sing-box/cmd_rule_set_merge.go
Normal file
162
cmd/sing-box/cmd_rule_set_merge.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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/badjson"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ruleSetPaths []string
|
||||||
|
ruleSetDirectories []string
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandRuleSetMerge = &cobra.Command{
|
||||||
|
Use: "merge <output-path>",
|
||||||
|
Short: "Merge rule-set source files",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := mergeRuleSet(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path")
|
||||||
|
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path")
|
||||||
|
commandRuleSet.AddCommand(commandRuleSetMerge)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleSetEntry struct {
|
||||||
|
content []byte
|
||||||
|
path string
|
||||||
|
options option.PlainRuleSetCompat
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSetAt(path string) (*RuleSetEntry, error) {
|
||||||
|
var (
|
||||||
|
configContent []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if path == "stdin" {
|
||||||
|
configContent, err = io.ReadAll(os.Stdin)
|
||||||
|
} else {
|
||||||
|
configContent, err = os.ReadFile(path)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
|
}
|
||||||
|
options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
|
}
|
||||||
|
return &RuleSetEntry{
|
||||||
|
content: configContent,
|
||||||
|
path: path,
|
||||||
|
options: options,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSet() ([]*RuleSetEntry, error) {
|
||||||
|
var optionsList []*RuleSetEntry
|
||||||
|
for _, path := range ruleSetPaths {
|
||||||
|
optionsEntry, err := readRuleSetAt(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optionsList = append(optionsList, optionsEntry)
|
||||||
|
}
|
||||||
|
for _, directory := range ruleSetDirectories {
|
||||||
|
entries, err := os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read rule-set directory at ", directory)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optionsList = append(optionsList, optionsEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(optionsList, func(i, j int) bool {
|
||||||
|
return optionsList[i].path < optionsList[j].path
|
||||||
|
})
|
||||||
|
return optionsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {
|
||||||
|
optionsList, err := readRuleSet()
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, err
|
||||||
|
}
|
||||||
|
if len(optionsList) == 1 {
|
||||||
|
return optionsList[0].options, nil
|
||||||
|
}
|
||||||
|
var optionVersion uint8
|
||||||
|
for _, options := range optionsList {
|
||||||
|
if optionVersion < options.options.Version {
|
||||||
|
optionVersion = options.options.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mergedMessage json.RawMessage
|
||||||
|
for _, options := range optionsList {
|
||||||
|
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config")
|
||||||
|
}
|
||||||
|
mergedOptions.Version = optionVersion
|
||||||
|
return mergedOptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRuleSet(outputPath string) error {
|
||||||
|
mergedOptions, err := readRuleSetAndMerge()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(mergedOptions)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "encode config")
|
||||||
|
}
|
||||||
|
if existsContent, err := os.ReadFile(outputPath); err != nil {
|
||||||
|
if string(existsContent) == buffer.String() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = rw.MkdirParent(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputPath, _ = filepath.Abs(outputPath)
|
||||||
|
os.Stderr.WriteString(outputPath + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ func initializeHTTP3Client(instance *box.Box) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
http3Client = &http.Client{
|
http3Client = &http.Client{
|
||||||
Transport: &http3.RoundTripper{
|
Transport: &http3.Transport{
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
destination := M.ParseSocksaddr(addr)
|
destination := M.ParseSocksaddr(addr)
|
||||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/settings"
|
|
||||||
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"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -58,7 +57,7 @@ func syncTime() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if commandSyncTimeWrite {
|
if commandSyncTimeWrite {
|
||||||
err = settings.SetSystemTime(response.Time)
|
err = ntp.SetSystemTime(response.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "write time to system")
|
return E.Cause(err, "write time to system")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
@@ -16,6 +19,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -33,19 +37,24 @@ type DefaultDialer struct {
|
|||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
isWireGuardListener bool
|
isWireGuardListener bool
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
networkStrategy C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
|
defaultNetworkStrategy bool
|
||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
networkLastFallback atomic.TypedValue[time.Time]
|
networkLastFallback atomic.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialer net.Dialer
|
dialer net.Dialer
|
||||||
listener net.ListenConfig
|
listener net.ListenConfig
|
||||||
interfaceFinder control.InterfaceFinder
|
interfaceFinder control.InterfaceFinder
|
||||||
networkStrategy C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
|
defaultNetworkStrategy bool
|
||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
@@ -74,31 +83,38 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
|||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault {
|
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
||||||
if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil {
|
if disableDefaultBind || options.TCPFastOpen {
|
||||||
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`")
|
if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 {
|
||||||
}
|
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`")
|
||||||
networkStrategy = C.NetworkStrategy(options.NetworkStrategy)
|
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
|
||||||
networkFallbackDelay = time.Duration(options.NetworkFallbackDelay)
|
|
||||||
if networkManager == nil || !networkManager.AutoDetectInterface() {
|
|
||||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil {
|
|
||||||
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
if options.BindInterface == "" {
|
if !disableDefaultBind {
|
||||||
if defaultOptions.BindInterface != "" {
|
if defaultOptions.BindInterface != "" {
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if networkManager.AutoDetectInterface() {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault {
|
if platformInterface != nil {
|
||||||
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
networkStrategy = defaultOptions.NetworkStrategy
|
networkStrategy = defaultOptions.NetworkStrategy
|
||||||
networkType = defaultOptions.NetworkType
|
networkType = defaultOptions.NetworkType
|
||||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||||
|
}
|
||||||
|
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
||||||
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
|
}
|
||||||
bindFunc := networkManager.ProtectFunc()
|
bindFunc := networkManager.ProtectFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
@@ -172,9 +188,6 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
|||||||
listener.Control = control.Append(listener.Control, controlFn)
|
listener.Control = control.Append(listener.Control, controlFn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if networkStrategy != C.NetworkStrategyDefault && options.TCPFastOpen {
|
|
||||||
return nil, E.New("`tcp_fast_open` is conflict with `network_strategy` or `route.default_network_strategy`")
|
|
||||||
}
|
|
||||||
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -194,6 +207,7 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
|||||||
isWireGuardListener: options.IsWireGuardListener,
|
isWireGuardListener: options.IsWireGuardListener,
|
||||||
networkManager: networkManager,
|
networkManager: networkManager,
|
||||||
networkStrategy: networkStrategy,
|
networkStrategy: networkStrategy,
|
||||||
|
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||||
networkType: networkType,
|
networkType: networkType,
|
||||||
fallbackNetworkType: fallbackNetworkType,
|
fallbackNetworkType: fallbackNetworkType,
|
||||||
networkFallbackDelay: networkFallbackDelay,
|
networkFallbackDelay: networkFallbackDelay,
|
||||||
@@ -204,7 +218,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == C.NetworkStrategyDefault {
|
if d.networkStrategy == nil {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
@@ -223,12 +237,21 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||||
if strategy == C.NetworkStrategyDefault {
|
if strategy == nil {
|
||||||
|
strategy = d.networkStrategy
|
||||||
|
}
|
||||||
|
if strategy == nil {
|
||||||
return d.DialContext(ctx, network, address)
|
return d.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
if !d.networkManager.AutoDetectInterface() {
|
if len(interfaceType) == 0 {
|
||||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
interfaceType = d.networkType
|
||||||
|
}
|
||||||
|
if len(fallbackInterfaceType) == 0 {
|
||||||
|
fallbackInterfaceType = d.fallbackNetworkType
|
||||||
|
}
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = d.networkFallbackDelay
|
||||||
}
|
}
|
||||||
var dialer net.Dialer
|
var dialer net.Dialer
|
||||||
if N.NetworkName(network) == N.NetworkTCP {
|
if N.NetworkName(network) == N.NetworkTCP {
|
||||||
@@ -243,13 +266,19 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if !fastFallback {
|
if !fastFallback {
|
||||||
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
} else {
|
} else {
|
||||||
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
|
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// bind interface failed on legacy xiaomi systems
|
||||||
|
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
|
||||||
|
d.networkStrategy = nil
|
||||||
|
return d.DialContext(ctx, network, address)
|
||||||
|
} else {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if !fastFallback && !isPrimary {
|
if !fastFallback && !isPrimary {
|
||||||
d.networkLastFallback.Store(time.Now())
|
d.networkLastFallback.Store(time.Now())
|
||||||
}
|
}
|
||||||
@@ -257,7 +286,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == C.NetworkStrategyDefault {
|
if d.networkStrategy == nil {
|
||||||
if destination.IsIPv6() {
|
if destination.IsIPv6() {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
@@ -270,18 +299,37 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
if strategy == C.NetworkStrategyDefault {
|
if strategy == nil {
|
||||||
|
strategy = d.networkStrategy
|
||||||
|
}
|
||||||
|
if strategy == nil {
|
||||||
return d.ListenPacket(ctx, destination)
|
return d.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
if !d.networkManager.AutoDetectInterface() {
|
if len(interfaceType) == 0 {
|
||||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
interfaceType = d.networkType
|
||||||
|
}
|
||||||
|
if len(fallbackInterfaceType) == 0 {
|
||||||
|
fallbackInterfaceType = d.fallbackNetworkType
|
||||||
|
}
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = d.networkFallbackDelay
|
||||||
}
|
}
|
||||||
network := N.NetworkUDP
|
network := N.NetworkUDP
|
||||||
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
network += "4"
|
network += "4"
|
||||||
}
|
}
|
||||||
return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay))
|
packetConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
if err != nil {
|
||||||
|
// bind interface failed on legacy xiaomi systems
|
||||||
|
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
|
||||||
|
d.networkStrategy = nil
|
||||||
|
return d.ListenPacket(ctx, destination)
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trackPacketConn(packetConn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
|
|||||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
select {
|
select {
|
||||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
|
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
select {
|
select {
|
||||||
case results <- dialResult{Conn: conn}:
|
case results <- dialResult{Conn: conn, primary: primary}:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
@@ -107,12 +107,12 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
|
|||||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
select {
|
select {
|
||||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
|
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
select {
|
select {
|
||||||
case results <- dialResult{Conn: conn}:
|
case results <- dialResult{Conn: conn, primary: primary}:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
if primary && time.Since(startAt) <= fallbackDelay {
|
if primary && time.Since(startAt) <= fallbackDelay {
|
||||||
resetFastFallback(time.Time{})
|
resetFastFallback(time.Time{})
|
||||||
@@ -157,7 +157,7 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Name, ")"))
|
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Index, ")"))
|
||||||
}
|
}
|
||||||
for _, fallbackInterface := range fallbackInterfaces {
|
for _, fallbackInterface := range fallbackInterfaces {
|
||||||
perNetListener := listener
|
perNetListener := listener
|
||||||
@@ -166,7 +166,7 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Name, ")"))
|
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Index, ")"))
|
||||||
}
|
}
|
||||||
return nil, E.Errors(errors...)
|
return nil, E.Errors(errors...)
|
||||||
}
|
}
|
||||||
@@ -177,44 +177,57 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS
|
|||||||
case C.NetworkStrategyDefault:
|
case C.NetworkStrategyDefault:
|
||||||
if len(interfaceType) == 0 {
|
if len(interfaceType) == 0 {
|
||||||
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||||
|
if defaultIf != nil {
|
||||||
for _, iif := range interfaces {
|
for _, iif := range interfaces {
|
||||||
if iif.Index == defaultIf.Index {
|
if iif.Index == defaultIf.Index {
|
||||||
primaryInterfaces = append(primaryInterfaces, iif)
|
primaryInterfaces = append(primaryInterfaces, iif)
|
||||||
} else {
|
|
||||||
fallbackInterfaces = append(fallbackInterfaces, iif)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
primaryInterfaces = interfaces
|
||||||
return common.Contains(interfaceType, iif.Type)
|
}
|
||||||
|
} else {
|
||||||
|
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
|
return common.Contains(interfaceType, it.Type)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case C.NetworkStrategyHybrid:
|
case C.NetworkStrategyHybrid:
|
||||||
if len(interfaceType) == 0 {
|
if len(interfaceType) == 0 {
|
||||||
primaryInterfaces = interfaces
|
primaryInterfaces = interfaces
|
||||||
} else {
|
} else {
|
||||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
return common.Contains(interfaceType, iif.Type)
|
return common.Contains(interfaceType, it.Type)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case C.NetworkStrategyFallback:
|
case C.NetworkStrategyFallback:
|
||||||
if len(interfaceType) == 0 {
|
if len(interfaceType) == 0 {
|
||||||
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||||
|
if defaultIf != nil {
|
||||||
for _, iif := range interfaces {
|
for _, iif := range interfaces {
|
||||||
if iif.Index == defaultIf.Index {
|
if iif.Index == defaultIf.Index {
|
||||||
primaryInterfaces = append(primaryInterfaces, iif)
|
primaryInterfaces = append(primaryInterfaces, iif)
|
||||||
} else {
|
break
|
||||||
fallbackInterfaces = append(fallbackInterfaces, iif)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
primaryInterfaces = interfaces
|
||||||
return common.Contains(interfaceType, iif.Type)
|
}
|
||||||
|
} else {
|
||||||
|
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
|
return common.Contains(interfaceType, it.Type)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if len(fallbackInterfaceType) == 0 {
|
||||||
|
fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
|
return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool {
|
||||||
|
return it.Index == iif.Index
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||||
return common.Contains(fallbackInterfaceType, iif.Type)
|
return common.Contains(fallbackInterfaceType, iif.Type)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return primaryInterfaces, fallbackInterfaces
|
return primaryInterfaces, fallbackInterfaces
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ import (
|
|||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||||
|
if len(destinationAddresses) == 0 {
|
||||||
|
if !destination.IsIP() {
|
||||||
|
panic("invalid usage")
|
||||||
|
}
|
||||||
|
destinationAddresses = []netip.Addr{destination.Addr}
|
||||||
|
}
|
||||||
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||||
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
}
|
}
|
||||||
@@ -38,7 +44,14 @@ func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, des
|
|||||||
return nil, E.Errors(errors...)
|
return nil, E.Errors(errors...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||||
|
if len(destinationAddresses) == 0 {
|
||||||
|
if !destination.IsIP() {
|
||||||
|
panic("invalid usage")
|
||||||
|
}
|
||||||
|
destinationAddresses = []netip.Addr{destination.Addr}
|
||||||
|
}
|
||||||
|
|
||||||
if fallbackDelay == 0 {
|
if fallbackDelay == 0 {
|
||||||
fallbackDelay = N.DefaultFallbackDelay
|
fallbackDelay = N.DefaultFallbackDelay
|
||||||
}
|
}
|
||||||
@@ -116,7 +129,13 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
|
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
|
||||||
|
if len(destinationAddresses) == 0 {
|
||||||
|
if !destination.IsIP() {
|
||||||
|
panic("invalid usage")
|
||||||
|
}
|
||||||
|
destinationAddresses = []netip.Addr{destination.Addr}
|
||||||
|
}
|
||||||
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||||
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,25 +8,24 @@ import (
|
|||||||
|
|
||||||
"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/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
|
func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) {
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
|
||||||
if options.IsWireGuardListener {
|
if options.IsWireGuardListener {
|
||||||
return NewDefault(networkManager, options)
|
return NewDefault(ctx, options)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if options.Detour == "" {
|
if options.Detour == "" {
|
||||||
dialer, err = NewDefault(networkManager, options)
|
dialer, err = NewDefault(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -37,17 +36,26 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
|
|||||||
}
|
}
|
||||||
dialer = NewDetour(outboundManager, options.Detour)
|
dialer = NewDetour(outboundManager, options.Detour)
|
||||||
}
|
}
|
||||||
if networkManager == nil {
|
if remoteIsDomain && options.Detour == "" && options.DomainResolver == "" {
|
||||||
return NewDefault(networkManager, options)
|
deprecated.Report(ctx, deprecated.OptionMissingDomainResolverInDialOptions)
|
||||||
}
|
}
|
||||||
if options.Detour == "" {
|
if (options.Detour == "" && remoteIsDomain) || options.DomainResolver != "" {
|
||||||
router := service.FromContext[adapter.Router](ctx)
|
router := service.FromContext[adapter.DNSRouter](ctx)
|
||||||
if router != nil {
|
if router != nil {
|
||||||
|
var resolveTransport adapter.DNSTransport
|
||||||
|
if options.DomainResolver != "" {
|
||||||
|
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("DNS server not found: " + options.DomainResolver)
|
||||||
|
}
|
||||||
|
resolveTransport = transport
|
||||||
|
}
|
||||||
dialer = NewResolveDialer(
|
dialer = NewResolveDialer(
|
||||||
router,
|
router,
|
||||||
dialer,
|
dialer,
|
||||||
options.Detour == "" && !options.TCPFastOpen,
|
options.Detour == "" && !options.TCPFastOpen,
|
||||||
dns.DomainStrategy(options.DomainStrategy),
|
resolveTransport,
|
||||||
|
C.DomainStrategy(options.DomainStrategy),
|
||||||
time.Duration(options.FallbackDelay))
|
time.Duration(options.FallbackDelay))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,30 +66,38 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
|
|||||||
if options.Detour != "" {
|
if options.Detour != "" {
|
||||||
return nil, E.New("`detour` is not supported in direct context")
|
return nil, E.New("`detour` is not supported in direct context")
|
||||||
}
|
}
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
|
||||||
if options.IsWireGuardListener {
|
if options.IsWireGuardListener {
|
||||||
return NewDefault(networkManager, options)
|
return NewDefault(ctx, options)
|
||||||
}
|
}
|
||||||
dialer, err := NewDefault(networkManager, options)
|
dialer, err := NewDefault(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var resolveTransport adapter.DNSTransport
|
||||||
|
if options.DomainResolver != "" {
|
||||||
|
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("DNS server not found: " + options.DomainResolver)
|
||||||
|
}
|
||||||
|
resolveTransport = transport
|
||||||
|
}
|
||||||
return NewResolveParallelInterfaceDialer(
|
return NewResolveParallelInterfaceDialer(
|
||||||
service.FromContext[adapter.Router](ctx),
|
service.FromContext[adapter.DNSRouter](ctx),
|
||||||
dialer,
|
dialer,
|
||||||
true,
|
true,
|
||||||
dns.DomainStrategy(options.DomainStrategy),
|
resolveTransport,
|
||||||
|
C.DomainStrategy(options.DomainStrategy),
|
||||||
time.Duration(options.FallbackDelay),
|
time.Duration(options.FallbackDelay),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParallelInterfaceDialer interface {
|
type ParallelInterfaceDialer interface {
|
||||||
N.Dialer
|
N.Dialer
|
||||||
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||||
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
|
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParallelNetworkDialer interface {
|
type ParallelNetworkDialer interface {
|
||||||
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||||
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"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"
|
||||||
@@ -23,16 +21,18 @@ var (
|
|||||||
type resolveDialer struct {
|
type resolveDialer struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
parallel bool
|
parallel bool
|
||||||
router adapter.Router
|
router adapter.DNSRouter
|
||||||
strategy dns.DomainStrategy
|
transport adapter.DNSTransport
|
||||||
|
strategy C.DomainStrategy
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
|
func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
|
||||||
return &resolveDialer{
|
return &resolveDialer{
|
||||||
dialer,
|
dialer,
|
||||||
parallel,
|
parallel,
|
||||||
router,
|
router,
|
||||||
|
transport,
|
||||||
strategy,
|
strategy,
|
||||||
fallbackDelay,
|
fallbackDelay,
|
||||||
}
|
}
|
||||||
@@ -43,12 +43,13 @@ type resolveParallelNetworkDialer struct {
|
|||||||
dialer ParallelInterfaceDialer
|
dialer ParallelInterfaceDialer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolveParallelInterfaceDialer(router adapter.Router, dialer ParallelInterfaceDialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
|
func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
|
||||||
return &resolveParallelNetworkDialer{
|
return &resolveParallelNetworkDialer{
|
||||||
resolveDialer{
|
resolveDialer{
|
||||||
dialer,
|
dialer,
|
||||||
parallel,
|
parallel,
|
||||||
router,
|
router,
|
||||||
|
transport,
|
||||||
strategy,
|
strategy,
|
||||||
fallbackDelay,
|
fallbackDelay,
|
||||||
},
|
},
|
||||||
@@ -60,22 +61,13 @@ func (d *resolveDialer) DialContext(ctx context.Context, network string, destina
|
|||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
metadata.Destination = destination
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||||
metadata.Domain = ""
|
|
||||||
var addresses []netip.Addr
|
|
||||||
var err error
|
|
||||||
if d.strategy == dns.DomainStrategyAsIS {
|
|
||||||
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
|
|
||||||
} else {
|
|
||||||
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if d.parallel {
|
if d.parallel {
|
||||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
|
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
} else {
|
} else {
|
||||||
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
||||||
}
|
}
|
||||||
@@ -85,17 +77,8 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
metadata.Destination = destination
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||||
metadata.Domain = ""
|
|
||||||
var addresses []netip.Addr
|
|
||||||
var err error
|
|
||||||
if d.strategy == dns.DomainStrategyAsIS {
|
|
||||||
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
|
|
||||||
} else {
|
|
||||||
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -106,21 +89,12 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
metadata.Destination = destination
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||||
metadata.Domain = ""
|
|
||||||
var addresses []netip.Addr
|
|
||||||
var err error
|
|
||||||
if d.strategy == dns.DomainStrategyAsIS {
|
|
||||||
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
|
|
||||||
} else {
|
|
||||||
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -128,27 +102,18 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
|
|||||||
fallbackDelay = d.fallbackDelay
|
fallbackDelay = d.fallbackDelay
|
||||||
}
|
}
|
||||||
if d.parallel {
|
if d.parallel {
|
||||||
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
} else {
|
} else {
|
||||||
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
metadata.Destination = destination
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||||
metadata.Domain = ""
|
|
||||||
var addresses []netip.Addr
|
|
||||||
var err error
|
|
||||||
if d.strategy == dns.DomainStrategyAsIS {
|
|
||||||
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
|
|
||||||
} else {
|
|
||||||
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,27 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultOutboundDialer struct {
|
type DefaultOutboundDialer struct {
|
||||||
outboundManager adapter.OutboundManager
|
outbound adapter.OutboundManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultOutbound(outboundManager adapter.OutboundManager) N.Dialer {
|
func NewDefaultOutbound(ctx context.Context) N.Dialer {
|
||||||
return &DefaultOutboundDialer{outboundManager: outboundManager}
|
return &DefaultOutboundDialer{
|
||||||
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
return d.outboundManager.Default().DialContext(ctx, network, destination)
|
return d.outbound.Default().DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return d.outboundManager.Default().ListenPacket(ctx, destination)
|
return d.outbound.Default().ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultOutboundDialer) Upstream() any {
|
func (d *DefaultOutboundDialer) Upstream() any {
|
||||||
return d.outboundManager.Default()
|
return d.outbound.Default()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !(windows || linux || darwin)
|
|
||||||
|
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build linux || darwin
|
|
||||||
|
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
|
||||||
return unix.Settimeofday(&timeVal)
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
var systemTime windows.Systemtime
|
|
||||||
systemTime.Year = uint16(nowTime.Year())
|
|
||||||
systemTime.Month = uint16(nowTime.Month())
|
|
||||||
systemTime.Day = uint16(nowTime.Day())
|
|
||||||
systemTime.Hour = uint16(nowTime.Hour())
|
|
||||||
systemTime.Minute = uint16(nowTime.Minute())
|
|
||||||
systemTime.Second = uint16(nowTime.Second())
|
|
||||||
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
|
|
||||||
|
|
||||||
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
proc := dllKernel32.NewProc("SetSystemTime")
|
|
||||||
|
|
||||||
_, _, err := proc.Call(
|
|
||||||
uintptr(unsafe.Pointer(&systemTime)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil && err.Error() != "The operation completed successfully." {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -30,15 +30,14 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
|
||||||
return NewECHClient(ctx, serverAddress, options)
|
return NewECHClient(ctx, serverAddress, options)
|
||||||
}
|
|
||||||
} else if options.Reality != nil && options.Reality.Enabled {
|
} else if options.Reality != nil && options.Reality.Enabled {
|
||||||
return NewRealityClient(ctx, serverAddress, options)
|
return NewRealityClient(ctx, serverAddress, options)
|
||||||
} else if options.UTLS != nil && options.UTLS.Enabled {
|
} else if options.UTLS != nil && options.UTLS.Enabled {
|
||||||
return NewUTLSClient(ctx, serverAddress, options)
|
return NewUTLSClient(ctx, serverAddress, options)
|
||||||
}
|
} else {
|
||||||
return NewSTDClient(ctx, serverAddress, options)
|
return NewSTDClient(ctx, serverAddress, options)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
|
|
||||||
cftls "github.com/sagernet/cloudflare-tls"
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
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"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -64,6 +64,7 @@ type echConnWrapper struct {
|
|||||||
|
|
||||||
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
@@ -214,7 +215,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response, err := service.FromContext[adapter.Router](ctx).Exchange(ctx, message)
|
response, err := service.FromContext[adapter.DNSRouter](ctx).Exchange(ctx, message, adapter.DNSQueryOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
|
||||||
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
"github.com/cloudflare/circl/hpke"
|
"github.com/cloudflare/circl/hpke"
|
||||||
@@ -58,6 +59,7 @@ func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (config
|
|||||||
|
|
||||||
type echKeyConfigPair struct {
|
type echKeyConfigPair struct {
|
||||||
id uint8
|
id uint8
|
||||||
|
key cftls.EXP_ECHKey
|
||||||
rawKey []byte
|
rawKey []byte
|
||||||
conf myECHKeyConfig
|
conf myECHKeyConfig
|
||||||
rawConf []byte
|
rawConf []byte
|
||||||
@@ -145,19 +147,23 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
|
|||||||
pair.rawConf = b
|
pair.rawConf = b
|
||||||
|
|
||||||
secBuf, err := sec.MarshalBinary()
|
secBuf, err := sec.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH private key")
|
||||||
|
}
|
||||||
sk := []byte{}
|
sk := []byte{}
|
||||||
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
sk = append(sk, secBuf...)
|
sk = append(sk, secBuf...)
|
||||||
sk = be.AppendUint16(sk, uint16(len(b)))
|
sk = be.AppendUint16(sk, uint16(len(b)))
|
||||||
sk = append(sk, b...)
|
sk = append(sk, b...)
|
||||||
|
|
||||||
cfECHKeys, err := UnmarshalECHKeys(sk)
|
cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "bug: can't parse generated ECH server key")
|
return nil, E.Cause(err, "bug: can't parse generated ECH server key")
|
||||||
}
|
}
|
||||||
if len(cfECHKeys) != 1 {
|
if len(cfECHKeys) != 1 {
|
||||||
return nil, E.New("bug: unexpected server key count")
|
return nil, E.New("bug: unexpected server key count")
|
||||||
}
|
}
|
||||||
|
pair.key = cfECHKeys[0]
|
||||||
pair.rawKey = sk
|
pair.rawKey = sk
|
||||||
|
|
||||||
pairs = append(pairs, pair)
|
pairs = append(pairs, pair)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
||||||
return &http3.RoundTripper{
|
return &http3.Transport{
|
||||||
TLSClientConfig: c.config,
|
TLSClientConfig: c.config,
|
||||||
QUICConfig: quicConfig,
|
QUICConfig: quicConfig,
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
return nil, E.New("reality verification failed")
|
return nil, E.New("reality verification failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &utlsConnWrapper{uConn}, nil
|
return &realityClientConnWrapper{uConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
@@ -249,3 +249,36 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type realityClientConnWrapper struct {
|
||||||
|
*utls.UConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
|
return tls.ConnectionState{
|
||||||
|
Version: state.Version,
|
||||||
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
|
DidResume: state.DidResume,
|
||||||
|
CipherSuite: state.CipherSuite,
|
||||||
|
NegotiatedProtocol: state.NegotiatedProtocol,
|
||||||
|
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
||||||
|
ServerName: state.ServerName,
|
||||||
|
PeerCertificates: state.PeerCertificates,
|
||||||
|
VerifiedChains: state.VerifiedChains,
|
||||||
|
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
||||||
|
OCSPResponse: state.OCSPResponse,
|
||||||
|
TLSUnique: state.TLSUnique,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityClientConnWrapper) Upstream() any {
|
||||||
|
return c.UConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
||||||
|
// We fixed it by calling Close() directly.
|
||||||
|
func (c *realityClientConnWrapper) CloseWrite() error {
|
||||||
|
return c.Close()
|
||||||
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
tlsConfig.ShortIds[shortID] = true
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions)
|
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -174,6 +174,7 @@ type realityConnWrapper struct {
|
|||||||
|
|
||||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
@@ -193,3 +194,9 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
|||||||
func (c *realityConnWrapper) Upstream() any {
|
func (c *realityConnWrapper) Upstream() any {
|
||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
||||||
|
// We fixed it by calling Close() directly.
|
||||||
|
func (c *realityConnWrapper) CloseWrite() error {
|
||||||
|
return c.Close()
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
|
||||||
return NewECHServer(ctx, logger, options)
|
return NewECHServer(ctx, logger, options)
|
||||||
}
|
|
||||||
} else if options.Reality != nil && options.Reality.Enabled {
|
} else if options.Reality != nil && options.Reality.Enabled {
|
||||||
return NewRealityServer(ctx, logger, options)
|
return NewRealityServer(ctx, logger, options)
|
||||||
}
|
} else {
|
||||||
return NewSTDServer(ctx, logger, options)
|
return NewSTDServer(ctx, logger, options)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
||||||
|
|||||||
@@ -4,25 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
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"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ConfigCompat = (*STDClientConfig)(nil)
|
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
}
|
}
|
||||||
@@ -55,72 +45,13 @@ func (s *STDClientConfig) Clone() Config {
|
|||||||
return &STDClientConfig{s.config.Clone()}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
}
|
||||||
|
|
||||||
type STDECHClientConfig struct {
|
|
||||||
STDClientConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *STDClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
|
||||||
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
|
||||||
message := &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
RecursionDesired: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{
|
|
||||||
{
|
|
||||||
Name: mDNS.Fqdn(s.config.ServerName),
|
|
||||||
Qtype: mDNS.TypeHTTPS,
|
|
||||||
Qclass: mDNS.ClassINET,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
dnsRouter := service.FromContext[adapter.Router](ctx)
|
|
||||||
response, err := dnsRouter.Exchange(ctx, message)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "fetch ECH config list")
|
|
||||||
}
|
|
||||||
if response.Rcode != mDNS.RcodeSuccess {
|
|
||||||
return nil, E.Cause(dns.RCodeError(response.Rcode), "fetch ECH config list")
|
|
||||||
}
|
|
||||||
for _, rr := range response.Answer {
|
|
||||||
switch resource := rr.(type) {
|
|
||||||
case *mDNS.HTTPS:
|
|
||||||
for _, value := range resource.Value {
|
|
||||||
if value.Key().String() == "ech" {
|
|
||||||
echConfigList, err := base64.StdEncoding.DecodeString(value.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode ECH config")
|
|
||||||
}
|
|
||||||
s.config.EncryptedClientHelloConfigList = echConfigList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, E.New("no ECH config found in DNS records")
|
|
||||||
}
|
|
||||||
tlsConn, err := s.Client(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *STDECHClientConfig) Clone() Config {
|
|
||||||
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
if _, err := netip.ParseAddr(serverName); err != nil {
|
|
||||||
serverName = serverAddress
|
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")
|
||||||
}
|
}
|
||||||
@@ -194,21 +125,5 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
|
||||||
var echConfig []byte
|
|
||||||
if len(options.ECH.Config) > 0 {
|
|
||||||
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
|
||||||
} else if options.ECH.ConfigPath != "" {
|
|
||||||
content, err := os.ReadFile(options.ECH.ConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read ECH config")
|
|
||||||
}
|
|
||||||
echConfig = content
|
|
||||||
}
|
|
||||||
if echConfig != nil {
|
|
||||||
tlsConfig.EncryptedClientHelloConfigList = echConfig
|
|
||||||
}
|
|
||||||
return &STDECHClientConfig{STDClientConfig{&tlsConfig}}, nil
|
|
||||||
}
|
|
||||||
return &STDClientConfig{&tlsConfig}, nil
|
return &STDClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/pem"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -15,8 +14,6 @@ import (
|
|||||||
"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/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errInsecureUnused = E.New("tls: insecure unused")
|
var errInsecureUnused = E.New("tls: insecure unused")
|
||||||
@@ -241,31 +238,6 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
|
||||||
var echKey []byte
|
|
||||||
if len(options.ECH.Key) > 0 {
|
|
||||||
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
|
||||||
} else if options.ECH.KeyPath != "" {
|
|
||||||
content, err := os.ReadFile(options.ECH.KeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read ECH key")
|
|
||||||
}
|
|
||||||
echKey = content
|
|
||||||
} else {
|
|
||||||
return nil, E.New("missing ECH key")
|
|
||||||
}
|
|
||||||
|
|
||||||
block, rest := pem.Decode(echKey)
|
|
||||||
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
|
||||||
return nil, E.New("invalid ECH keys pem")
|
|
||||||
}
|
|
||||||
|
|
||||||
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse ECH keys")
|
|
||||||
}
|
|
||||||
tlsConfig.EncryptedClientHelloKeys = echKeys
|
|
||||||
}
|
|
||||||
return &STDServerConfig{
|
return &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
@@ -276,22 +248,3 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
keyPath: options.KeyPath,
|
keyPath: options.KeyPath,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
|
||||||
var keys []tls.EncryptedClientHelloKey
|
|
||||||
rawString := cryptobyte.String(raw)
|
|
||||||
for !rawString.Empty() {
|
|
||||||
var key tls.EncryptedClientHelloKey
|
|
||||||
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
|
|
||||||
return nil, E.New("error parsing private key")
|
|
||||||
}
|
|
||||||
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {
|
|
||||||
return nil, E.New("error parsing config")
|
|
||||||
}
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return nil, E.New("empty ECH keys")
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|||||||
22
common/tls/time_wrapper.go
Normal file
22
common/tls/time_wrapper.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeServiceWrapper struct {
|
||||||
|
ntp.TimeService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
||||||
|
if w.TimeService == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.TimeService.TimeFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TimeServiceWrapper) Upstream() any {
|
||||||
|
return w.TimeService
|
||||||
|
}
|
||||||
@@ -69,6 +69,7 @@ type utlsConnWrapper struct {
|
|||||||
|
|
||||||
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
//go:build android && debug
|
|
||||||
|
|
||||||
package constant
|
|
||||||
|
|
||||||
// TODO: remove after fixed
|
|
||||||
// https://github.com/golang/go/issues/68760
|
|
||||||
|
|
||||||
const FixAndroidStack = true
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build !(android && debug)
|
|
||||||
|
|
||||||
package constant
|
|
||||||
|
|
||||||
const FixAndroidStack = false
|
|
||||||
@@ -1,5 +1,34 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultDNSTTL = 600
|
||||||
|
)
|
||||||
|
|
||||||
|
type DomainStrategy = uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
DomainStrategyAsIS DomainStrategy = iota
|
||||||
|
DomainStrategyPreferIPv4
|
||||||
|
DomainStrategyPreferIPv6
|
||||||
|
DomainStrategyIPv4Only
|
||||||
|
DomainStrategyIPv6Only
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNSTypeLegacy = "legacy"
|
||||||
|
DNSTypeUDP = "udp"
|
||||||
|
DNSTypeTCP = "tcp"
|
||||||
|
DNSTypeTLS = "tls"
|
||||||
|
DNSTypeHTTPS = "https"
|
||||||
|
DNSTypeQUIC = "quic"
|
||||||
|
DNSTypeHTTP3 = "h3"
|
||||||
|
DNSTypeLocal = "local"
|
||||||
|
DNSTypePreDefined = "predefined"
|
||||||
|
DNSTypeFakeIP = "fakeip"
|
||||||
|
DNSTypeDHCP = "dhcp"
|
||||||
|
DNSTypeTailscale = "tailscale"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DNSProviderAliDNS = "alidns"
|
DNSProviderAliDNS = "alidns"
|
||||||
DNSProviderCloudflare = "cloudflare"
|
DNSProviderCloudflare = "cloudflare"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const (
|
|||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
|
TypeTailscale = "tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
563
dns/client.go
Normal file
563
dns/client.go
Normal file
@@ -0,0 +1,563 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
|
"github.com/sagernet/sing/contrab/freelru"
|
||||||
|
"github.com/sagernet/sing/contrab/maphash"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoRawSupport = E.New("no raw query support by current transport")
|
||||||
|
ErrNotCached = E.New("not cached")
|
||||||
|
ErrResponseRejected = E.New("response rejected")
|
||||||
|
ErrResponseRejectedCached = E.Extend(ErrResponseRejected, "cached")
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSClient = (*Client)(nil)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
timeout time.Duration
|
||||||
|
disableCache bool
|
||||||
|
disableExpire bool
|
||||||
|
independentCache bool
|
||||||
|
rdrc adapter.RDRCStore
|
||||||
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
|
logger logger.ContextLogger
|
||||||
|
cache freelru.Cache[dns.Question, *dns.Msg]
|
||||||
|
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientOptions struct {
|
||||||
|
Timeout time.Duration
|
||||||
|
DisableCache bool
|
||||||
|
DisableExpire bool
|
||||||
|
IndependentCache bool
|
||||||
|
CacheCapacity uint32
|
||||||
|
RDRC func() adapter.RDRCStore
|
||||||
|
Logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(options ClientOptions) *Client {
|
||||||
|
client := &Client{
|
||||||
|
timeout: options.Timeout,
|
||||||
|
disableCache: options.DisableCache,
|
||||||
|
disableExpire: options.DisableExpire,
|
||||||
|
independentCache: options.IndependentCache,
|
||||||
|
initRDRCFunc: options.RDRC,
|
||||||
|
logger: options.Logger,
|
||||||
|
}
|
||||||
|
if client.timeout == 0 {
|
||||||
|
client.timeout = C.DNSTimeout
|
||||||
|
}
|
||||||
|
cacheCapacity := options.CacheCapacity
|
||||||
|
if cacheCapacity < 1024 {
|
||||||
|
cacheCapacity = 1024
|
||||||
|
}
|
||||||
|
if !client.disableCache {
|
||||||
|
if !client.independentCache {
|
||||||
|
client.cache = common.Must1(freelru.NewSharded[dns.Question, *dns.Msg](cacheCapacity, maphash.NewHasher[dns.Question]().Hash32))
|
||||||
|
} else {
|
||||||
|
client.transportCache = common.Must1(freelru.NewSharded[transportCacheKey, *dns.Msg](cacheCapacity, maphash.NewHasher[transportCacheKey]().Hash32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
type transportCacheKey struct {
|
||||||
|
dns.Question
|
||||||
|
transportTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Start() {
|
||||||
|
if c.initRDRCFunc != nil {
|
||||||
|
c.rdrc = c.initRDRCFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
|
||||||
|
if len(message.Question) == 0 {
|
||||||
|
if c.logger != nil {
|
||||||
|
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||||
|
}
|
||||||
|
responseMessage := dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Response: true,
|
||||||
|
Rcode: dns.RcodeFormatError,
|
||||||
|
},
|
||||||
|
Question: message.Question,
|
||||||
|
}
|
||||||
|
return &responseMessage, nil
|
||||||
|
}
|
||||||
|
question := message.Question[0]
|
||||||
|
if options.ClientSubnet.IsValid() {
|
||||||
|
message = SetClientSubnet(message, options.ClientSubnet, true)
|
||||||
|
}
|
||||||
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
|
len(message.Ns) == 0 &&
|
||||||
|
len(message.Extra) == 0 &&
|
||||||
|
!options.ClientSubnet.IsValid()
|
||||||
|
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||||
|
if !disableCache {
|
||||||
|
response, ttl := c.loadResponse(question, transport)
|
||||||
|
if response != nil {
|
||||||
|
logCachedResponse(c.logger, ctx, response, ttl)
|
||||||
|
response.Id = message.Id
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only {
|
||||||
|
responseMessage := dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Response: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
|
},
|
||||||
|
Question: []dns.Question{question},
|
||||||
|
}
|
||||||
|
if c.logger != nil {
|
||||||
|
c.logger.DebugContext(ctx, "strategy rejected")
|
||||||
|
}
|
||||||
|
return &responseMessage, nil
|
||||||
|
}
|
||||||
|
messageId := message.Id
|
||||||
|
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
||||||
|
if clientSubnetLoaded && transport.Tag() == contextTransport {
|
||||||
|
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
|
||||||
|
}
|
||||||
|
ctx = contextWithTransportTag(ctx, transport.Tag())
|
||||||
|
if responseChecker != nil && c.rdrc != nil {
|
||||||
|
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
||||||
|
if rejected {
|
||||||
|
return nil, ErrResponseRejectedCached
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
|
response, err := transport.Exchange(ctx, message)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
||||||
|
validResponse := response
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
addresses int
|
||||||
|
queryCNAME string
|
||||||
|
)
|
||||||
|
for _, rawRR := range validResponse.Answer {
|
||||||
|
switch rr := rawRR.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
break loop
|
||||||
|
case *dns.AAAA:
|
||||||
|
break loop
|
||||||
|
case *dns.CNAME:
|
||||||
|
queryCNAME = rr.Target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if queryCNAME == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
exMessage := *message
|
||||||
|
exMessage.Question = []dns.Question{{
|
||||||
|
Name: queryCNAME,
|
||||||
|
Qtype: question.Qtype,
|
||||||
|
}}
|
||||||
|
validResponse, err = c.Exchange(ctx, transport, &exMessage, options, responseChecker)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if validResponse != response {
|
||||||
|
response.Answer = append(response.Answer, validResponse.Answer...)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
if responseChecker != nil {
|
||||||
|
addr, addrErr := MessageToAddresses(response)
|
||||||
|
if addrErr != nil || !responseChecker(addr) {
|
||||||
|
if c.rdrc != nil {
|
||||||
|
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
||||||
|
}
|
||||||
|
logRejectedResponse(c.logger, ctx, response)
|
||||||
|
return response, ErrResponseRejected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if question.Qtype == dns.TypeHTTPS {
|
||||||
|
if options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only {
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
https, isHTTPS := rr.(*dns.HTTPS)
|
||||||
|
if !isHTTPS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content := https.SVCB
|
||||||
|
content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool {
|
||||||
|
if options.Strategy == C.DomainStrategyIPv4Only {
|
||||||
|
return it.Key() != dns.SVCB_IPV6HINT
|
||||||
|
} else {
|
||||||
|
return it.Key() != dns.SVCB_IPV4HINT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
https.SVCB = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var timeToLive uint32
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
||||||
|
timeToLive = record.Header().Ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.RewriteTTL != nil {
|
||||||
|
timeToLive = *options.RewriteTTL
|
||||||
|
}
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = timeToLive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.Id = messageId
|
||||||
|
if !disableCache {
|
||||||
|
c.storeCache(transport, question, response, timeToLive)
|
||||||
|
}
|
||||||
|
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
|
domain = FqdnToDomain(domain)
|
||||||
|
dnsName := dns.Fqdn(domain)
|
||||||
|
if options.Strategy == C.DomainStrategyIPv4Only {
|
||||||
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
||||||
|
} else if options.Strategy == C.DomainStrategyIPv6Only {
|
||||||
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
||||||
|
}
|
||||||
|
var response4 []netip.Addr
|
||||||
|
var response6 []netip.Addr
|
||||||
|
var group task.Group
|
||||||
|
group.Append("exchange4", func(ctx context.Context) error {
|
||||||
|
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
response4 = response
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
group.Append("exchange6", func(ctx context.Context) error {
|
||||||
|
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
response6 = response
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
err := group.Run(ctx)
|
||||||
|
if len(response4) == 0 && len(response6) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sortAddresses(response4, response6, options.Strategy), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ClearCache() {
|
||||||
|
if c.cache != nil {
|
||||||
|
c.cache.Purge()
|
||||||
|
}
|
||||||
|
if c.transportCache != nil {
|
||||||
|
c.transportCache.Purge()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool) {
|
||||||
|
if c.disableCache || c.independentCache {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if dns.IsFqdn(domain) {
|
||||||
|
domain = domain[:len(domain)-1]
|
||||||
|
}
|
||||||
|
dnsName := dns.Fqdn(domain)
|
||||||
|
if strategy == C.DomainStrategyIPv4Only {
|
||||||
|
response, err := c.questionCache(dns.Question{
|
||||||
|
Name: dnsName,
|
||||||
|
Qtype: dns.TypeA,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}, nil)
|
||||||
|
if err != ErrNotCached {
|
||||||
|
return response, true
|
||||||
|
}
|
||||||
|
} else if strategy == C.DomainStrategyIPv6Only {
|
||||||
|
response, err := c.questionCache(dns.Question{
|
||||||
|
Name: dnsName,
|
||||||
|
Qtype: dns.TypeAAAA,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}, nil)
|
||||||
|
if err != ErrNotCached {
|
||||||
|
return response, true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response4, _ := c.questionCache(dns.Question{
|
||||||
|
Name: dnsName,
|
||||||
|
Qtype: dns.TypeA,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}, nil)
|
||||||
|
response6, _ := c.questionCache(dns.Question{
|
||||||
|
Name: dnsName,
|
||||||
|
Qtype: dns.TypeAAAA,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}, nil)
|
||||||
|
if len(response4) > 0 || len(response6) > 0 {
|
||||||
|
return sortAddresses(response4, response6, strategy), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool) {
|
||||||
|
if c.disableCache || c.independentCache || len(message.Question) != 1 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
question := message.Question[0]
|
||||||
|
response, ttl := c.loadResponse(question, nil)
|
||||||
|
if response == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
logCachedResponse(c.logger, ctx, response, ttl)
|
||||||
|
response.Id = message.Id
|
||||||
|
return response, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortAddresses(response4 []netip.Addr, response6 []netip.Addr, strategy C.DomainStrategy) []netip.Addr {
|
||||||
|
if strategy == C.DomainStrategyPreferIPv6 {
|
||||||
|
return append(response6, response4...)
|
||||||
|
} else {
|
||||||
|
return append(response4, response6...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, timeToLive uint32) {
|
||||||
|
if timeToLive == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.disableExpire {
|
||||||
|
if !c.independentCache {
|
||||||
|
c.cache.Add(question, message)
|
||||||
|
} else {
|
||||||
|
c.transportCache.Add(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
}, message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !c.independentCache {
|
||||||
|
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||||
|
} else {
|
||||||
|
c.transportCache.AddWithLifetime(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
}, message, time.Second*time.Duration(timeToLive))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
|
question := dns.Question{
|
||||||
|
Name: name,
|
||||||
|
Qtype: qType,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}
|
||||||
|
disableCache := c.disableCache || options.DisableCache
|
||||||
|
if !disableCache {
|
||||||
|
cachedAddresses, err := c.questionCache(question, transport)
|
||||||
|
if err != ErrNotCached {
|
||||||
|
return cachedAddresses, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message := dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
RecursionDesired: true,
|
||||||
|
},
|
||||||
|
Question: []dns.Question{question},
|
||||||
|
}
|
||||||
|
response, err := c.Exchange(ctx, transport, &message, options, responseChecker)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return MessageToAddresses(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
|
||||||
|
response, _ := c.loadResponse(question, transport)
|
||||||
|
if response == nil {
|
||||||
|
return nil, ErrNotCached
|
||||||
|
}
|
||||||
|
return MessageToAddresses(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
|
||||||
|
var (
|
||||||
|
response *dns.Msg
|
||||||
|
loaded bool
|
||||||
|
)
|
||||||
|
if c.disableExpire {
|
||||||
|
if !c.independentCache {
|
||||||
|
response, loaded = c.cache.Get(question)
|
||||||
|
} else {
|
||||||
|
response, loaded = c.transportCache.Get(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !loaded {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
return response.Copy(), 0
|
||||||
|
} else {
|
||||||
|
var expireAt time.Time
|
||||||
|
if !c.independentCache {
|
||||||
|
response, expireAt, loaded = c.cache.GetWithLifetime(question)
|
||||||
|
} else {
|
||||||
|
response, expireAt, loaded = c.transportCache.GetWithLifetime(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !loaded {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
timeNow := time.Now()
|
||||||
|
if timeNow.After(expireAt) {
|
||||||
|
if !c.independentCache {
|
||||||
|
c.cache.Remove(question)
|
||||||
|
} else {
|
||||||
|
c.transportCache.Remove(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
var originTTL int
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {
|
||||||
|
originTTL = int(record.Header().Ttl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nowTTL := int(expireAt.Sub(timeNow).Seconds())
|
||||||
|
if nowTTL < 0 {
|
||||||
|
nowTTL = 0
|
||||||
|
}
|
||||||
|
response = response.Copy()
|
||||||
|
if originTTL > 0 {
|
||||||
|
duration := uint32(originTTL - nowTTL)
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = record.Header().Ttl - duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = uint32(nowTTL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response, nowTTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
||||||
|
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
||||||
|
return nil, RCodeError(response.Rcode)
|
||||||
|
}
|
||||||
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
|
for _, rawAnswer := range response.Answer {
|
||||||
|
switch answer := rawAnswer.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
addresses = append(addresses, M.AddrFromIP(answer.A))
|
||||||
|
case *dns.AAAA:
|
||||||
|
addresses = append(addresses, M.AddrFromIP(answer.AAAA))
|
||||||
|
case *dns.HTTPS:
|
||||||
|
for _, value := range answer.SVCB.Value {
|
||||||
|
if value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT {
|
||||||
|
addresses = append(addresses, common.Map(strings.Split(value.String(), ","), M.ParseAddr)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapError(err error) error {
|
||||||
|
switch dnsErr := err.(type) {
|
||||||
|
case *net.DNSError:
|
||||||
|
if dnsErr.IsNotFound {
|
||||||
|
return RCodeNameError
|
||||||
|
}
|
||||||
|
case *net.AddrError:
|
||||||
|
return RCodeNameError
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type transportKey struct{}
|
||||||
|
|
||||||
|
func contextWithTransportTag(ctx context.Context, transportTag string) context.Context {
|
||||||
|
return context.WithValue(ctx, transportKey{}, transportTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transportTagFromContext(ctx context.Context) (string, bool) {
|
||||||
|
value, loaded := ctx.Value(transportKey{}).(string)
|
||||||
|
return value, loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
|
||||||
|
response := dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: id,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: []dns.Question{question},
|
||||||
|
}
|
||||||
|
for _, address := range addresses {
|
||||||
|
if address.Is4() {
|
||||||
|
response.Answer = append(response.Answer, &dns.A{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: question.Name,
|
||||||
|
Rrtype: dns.TypeA,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
Ttl: timeToLive,
|
||||||
|
},
|
||||||
|
A: address.AsSlice(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
response.Answer = append(response.Answer, &dns.AAAA{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: question.Name,
|
||||||
|
Rrtype: dns.TypeAAAA,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
Ttl: timeToLive,
|
||||||
|
},
|
||||||
|
AAAA: address.AsSlice(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &response
|
||||||
|
}
|
||||||
69
dns/client_log.go
Normal file
69
dns/client_log.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func logCachedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl int) {
|
||||||
|
if logger == nil || len(response.Question) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domain := FqdnToDomain(response.Question[0].Name)
|
||||||
|
logger.DebugContext(ctx, "cached ", domain, " ", dns.RcodeToString[response.Rcode], " ", ttl)
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
logger.InfoContext(ctx, "cached ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) {
|
||||||
|
if logger == nil || len(response.Question) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domain := FqdnToDomain(response.Question[0].Name)
|
||||||
|
logger.DebugContext(ctx, "exchanged ", domain, " ", dns.RcodeToString[response.Rcode], " ", ttl)
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
logger.InfoContext(ctx, "exchanged ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logRejectedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg) {
|
||||||
|
if logger == nil || len(response.Question) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
logger.InfoContext(ctx, "rejected ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FqdnToDomain(fqdn string) string {
|
||||||
|
if dns.IsFqdn(fqdn) {
|
||||||
|
return fqdn[:len(fqdn)-1]
|
||||||
|
}
|
||||||
|
return fqdn
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatQuestion(string string) string {
|
||||||
|
for strings.HasPrefix(string, ";") {
|
||||||
|
string = string[1:]
|
||||||
|
}
|
||||||
|
string = strings.ReplaceAll(string, "\t", " ")
|
||||||
|
string = strings.ReplaceAll(string, "\n", " ")
|
||||||
|
string = strings.ReplaceAll(string, ";; ", " ")
|
||||||
|
string = strings.ReplaceAll(string, "; ", " ")
|
||||||
|
|
||||||
|
for strings.Contains(string, " ") {
|
||||||
|
string = strings.ReplaceAll(string, " ", " ")
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string)
|
||||||
|
}
|
||||||
29
dns/client_truncate.go
Normal file
29
dns/client_truncate.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf.Buffer, error) {
|
||||||
|
maxLen := 512
|
||||||
|
if edns0Option := request.IsEdns0(); edns0Option != nil {
|
||||||
|
if udpSize := int(edns0Option.UDPSize()); udpSize > 512 {
|
||||||
|
maxLen = udpSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseLen := response.Len()
|
||||||
|
if responseLen > maxLen {
|
||||||
|
response.Truncate(maxLen)
|
||||||
|
}
|
||||||
|
buffer := buf.NewSize(headroom*2 + 1 + responseLen)
|
||||||
|
buffer.Resize(headroom, 0)
|
||||||
|
rawMessage, err := response.PackBuffer(buffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
buffer.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buffer.Truncate(len(rawMessage))
|
||||||
|
return buffer, nil
|
||||||
|
}
|
||||||
56
dns/extension_edns0_subnet.go
Normal file
56
dns/extension_edns0_subnet.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg {
|
||||||
|
var (
|
||||||
|
optRecord *dns.OPT
|
||||||
|
subnetOption *dns.EDNS0_SUBNET
|
||||||
|
)
|
||||||
|
findExists:
|
||||||
|
for _, record := range message.Extra {
|
||||||
|
var isOPTRecord bool
|
||||||
|
if optRecord, isOPTRecord = record.(*dns.OPT); isOPTRecord {
|
||||||
|
for _, option := range optRecord.Option {
|
||||||
|
var isEDNS0Subnet bool
|
||||||
|
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
||||||
|
if isEDNS0Subnet {
|
||||||
|
if !override {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
break findExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if optRecord == nil {
|
||||||
|
exMessage := *message
|
||||||
|
message = &exMessage
|
||||||
|
optRecord = &dns.OPT{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: ".",
|
||||||
|
Rrtype: dns.TypeOPT,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
message.Extra = append(message.Extra, optRecord)
|
||||||
|
} else {
|
||||||
|
message = message.Copy()
|
||||||
|
}
|
||||||
|
if subnetOption == nil {
|
||||||
|
subnetOption = new(dns.EDNS0_SUBNET)
|
||||||
|
optRecord.Option = append(optRecord.Option, subnetOption)
|
||||||
|
}
|
||||||
|
subnetOption.Code = dns.EDNS0SUBNET
|
||||||
|
if clientSubnet.Addr().Is4() {
|
||||||
|
subnetOption.Family = 1
|
||||||
|
} else {
|
||||||
|
subnetOption.Family = 2
|
||||||
|
}
|
||||||
|
subnetOption.SourceNetmask = uint8(clientSubnet.Bits())
|
||||||
|
subnetOption.Address = clientSubnet.Addr().AsSlice()
|
||||||
|
return message
|
||||||
|
}
|
||||||
33
dns/rcode.go
Normal file
33
dns/rcode.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import F "github.com/sagernet/sing/common/format"
|
||||||
|
|
||||||
|
const (
|
||||||
|
RCodeSuccess RCodeError = 0 // NoError
|
||||||
|
RCodeFormatError RCodeError = 1 // FormErr
|
||||||
|
RCodeServerFailure RCodeError = 2 // ServFail
|
||||||
|
RCodeNameError RCodeError = 3 // NXDomain
|
||||||
|
RCodeNotImplemented RCodeError = 4 // NotImp
|
||||||
|
RCodeRefused RCodeError = 5 // Refused
|
||||||
|
)
|
||||||
|
|
||||||
|
type RCodeError uint16
|
||||||
|
|
||||||
|
func (e RCodeError) Error() string {
|
||||||
|
switch e {
|
||||||
|
case RCodeSuccess:
|
||||||
|
return "success"
|
||||||
|
case RCodeFormatError:
|
||||||
|
return "format error"
|
||||||
|
case RCodeServerFailure:
|
||||||
|
return "server failure"
|
||||||
|
case RCodeNameError:
|
||||||
|
return "name error"
|
||||||
|
case RCodeNotImplemented:
|
||||||
|
return "not implemented"
|
||||||
|
case RCodeRefused:
|
||||||
|
return "refused"
|
||||||
|
default:
|
||||||
|
return F.ToString("unknown error: ", uint16(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
430
dns/router.go
Normal file
430
dns/router.go
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/contrab/freelru"
|
||||||
|
"github.com/sagernet/sing/contrab/maphash"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSRouter = (*Router)(nil)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
transport adapter.DNSTransportManager
|
||||||
|
outbound adapter.OutboundManager
|
||||||
|
client adapter.DNSClient
|
||||||
|
rules []adapter.DNSRule
|
||||||
|
defaultDomainStrategy C.DomainStrategy
|
||||||
|
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||||
|
platformInterface platform.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
||||||
|
router := &Router{
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logFactory.NewLogger("dns"),
|
||||||
|
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||||
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
|
rules: make([]adapter.DNSRule, 0, len(options.Rules)),
|
||||||
|
defaultDomainStrategy: C.DomainStrategy(options.Strategy),
|
||||||
|
}
|
||||||
|
router.client = NewClient(ClientOptions{
|
||||||
|
DisableCache: options.DNSClientOptions.DisableCache,
|
||||||
|
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||||
|
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||||
|
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||||
|
RDRC: func() adapter.RDRCStore {
|
||||||
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
|
if cacheFile == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !cacheFile.StoreRDRC() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cacheFile
|
||||||
|
},
|
||||||
|
Logger: router.logger,
|
||||||
|
})
|
||||||
|
if options.ReverseMapping {
|
||||||
|
router.dnsReverseMapping = common.Must1(freelru.NewSharded[netip.Addr, string](1024, maphash.NewHasher[netip.Addr]().Hash32))
|
||||||
|
}
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Initialize(rules []option.DNSRule) error {
|
||||||
|
for i, ruleOptions := range rules {
|
||||||
|
dnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "parse dns rule[", i, "]")
|
||||||
|
}
|
||||||
|
r.rules = append(r.rules, dnsRule)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Start(stage adapter.StartStage) error {
|
||||||
|
monitor := taskmonitor.New(r.logger, C.StartTimeout)
|
||||||
|
switch stage {
|
||||||
|
case adapter.StartStateStart:
|
||||||
|
monitor.Start("initialize DNS client")
|
||||||
|
r.client.Start()
|
||||||
|
monitor.Finish()
|
||||||
|
|
||||||
|
for i, rule := range r.rules {
|
||||||
|
monitor.Start("initialize DNS rule[", i, "]")
|
||||||
|
err := rule.Start()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "initialize DNS rule[", i, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Close() error {
|
||||||
|
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for i, rule := range r.rules {
|
||||||
|
monitor.Start("close dns rule[", i, "]")
|
||||||
|
err = E.Append(err, rule.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close dns rule[", i, "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) {
|
||||||
|
metadata := adapter.ContextFrom(ctx)
|
||||||
|
if metadata == nil {
|
||||||
|
panic("no context")
|
||||||
|
}
|
||||||
|
var currentRuleIndex int
|
||||||
|
if ruleIndex != -1 {
|
||||||
|
currentRuleIndex = ruleIndex + 1
|
||||||
|
}
|
||||||
|
for ; currentRuleIndex < len(r.rules); currentRuleIndex++ {
|
||||||
|
currentRule := r.rules[currentRuleIndex]
|
||||||
|
if currentRule.WithAddressLimit() && !isAddressQuery {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
metadata.ResetRuleCache()
|
||||||
|
if currentRule.Match(metadata) {
|
||||||
|
displayRuleIndex := currentRuleIndex
|
||||||
|
if displayRuleIndex != -1 {
|
||||||
|
displayRuleIndex += displayRuleIndex + 1
|
||||||
|
}
|
||||||
|
ruleDescription := currentRule.String()
|
||||||
|
if ruleDescription != "" {
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||||
|
} else {
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
|
}
|
||||||
|
switch action := currentRule.Action().(type) {
|
||||||
|
case *R.RuleActionDNSRoute:
|
||||||
|
transport, loaded := r.transport.Transport(action.Server)
|
||||||
|
if !loaded {
|
||||||
|
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isFakeIP := transport.Type() == C.DNSTypeFakeIP
|
||||||
|
if isFakeIP && !allowFakeIP {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isFakeIP || action.DisableCache {
|
||||||
|
options.DisableCache = true
|
||||||
|
}
|
||||||
|
if action.RewriteTTL != nil {
|
||||||
|
options.RewriteTTL = action.RewriteTTL
|
||||||
|
}
|
||||||
|
if action.ClientSubnet.IsValid() {
|
||||||
|
options.ClientSubnet = action.ClientSubnet
|
||||||
|
}
|
||||||
|
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = legacyTransport.LegacyStrategy()
|
||||||
|
}
|
||||||
|
if !options.ClientSubnet.IsValid() {
|
||||||
|
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
|
return transport, currentRule, currentRuleIndex
|
||||||
|
case *R.RuleActionDNSRouteOptions:
|
||||||
|
if action.DisableCache {
|
||||||
|
options.DisableCache = true
|
||||||
|
}
|
||||||
|
if action.RewriteTTL != nil {
|
||||||
|
options.RewriteTTL = action.RewriteTTL
|
||||||
|
}
|
||||||
|
if action.ClientSubnet.IsValid() {
|
||||||
|
options.ClientSubnet = action.ClientSubnet
|
||||||
|
}
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
|
case *R.RuleActionReject:
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
|
return nil, currentRule, currentRuleIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.transport.Default(), nil, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {
|
||||||
|
if len(message.Question) != 1 {
|
||||||
|
r.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||||
|
responseMessage := mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Response: true,
|
||||||
|
Rcode: mDNS.RcodeFormatError,
|
||||||
|
},
|
||||||
|
Question: message.Question,
|
||||||
|
}
|
||||||
|
return &responseMessage, nil
|
||||||
|
}
|
||||||
|
r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String()))
|
||||||
|
var (
|
||||||
|
transport adapter.DNSTransport
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
response, cached := r.client.ExchangeCache(ctx, message)
|
||||||
|
if !cached {
|
||||||
|
var metadata *adapter.InboundContext
|
||||||
|
ctx, metadata = adapter.ExtendContext(ctx)
|
||||||
|
metadata.Destination = M.Socksaddr{}
|
||||||
|
metadata.QueryType = message.Question[0].Qtype
|
||||||
|
switch metadata.QueryType {
|
||||||
|
case mDNS.TypeA:
|
||||||
|
metadata.IPVersion = 4
|
||||||
|
case mDNS.TypeAAAA:
|
||||||
|
metadata.IPVersion = 6
|
||||||
|
}
|
||||||
|
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
||||||
|
if options.Transport != nil {
|
||||||
|
transport = options.Transport
|
||||||
|
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = legacyTransport.LegacyStrategy()
|
||||||
|
}
|
||||||
|
if !options.ClientSubnet.IsValid() {
|
||||||
|
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = r.defaultDomainStrategy
|
||||||
|
}
|
||||||
|
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||||
|
} else {
|
||||||
|
var (
|
||||||
|
rule adapter.DNSRule
|
||||||
|
ruleIndex int
|
||||||
|
)
|
||||||
|
ruleIndex = -1
|
||||||
|
for {
|
||||||
|
dnsCtx := adapter.OverrideContext(ctx)
|
||||||
|
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &options)
|
||||||
|
if rule != nil {
|
||||||
|
switch action := rule.Action().(type) {
|
||||||
|
case *R.RuleActionReject:
|
||||||
|
switch action.Method {
|
||||||
|
case C.RuleActionRejectMethodDefault:
|
||||||
|
return FixedResponse(message.Id, message.Question[0], nil, 0), nil
|
||||||
|
case C.RuleActionRejectMethodDrop:
|
||||||
|
return nil, tun.ErrDrop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||||
|
if rule != nil && rule.WithAddressLimit() {
|
||||||
|
responseCheck = func(responseAddrs []netip.Addr) bool {
|
||||||
|
metadata.DestinationAddresses = responseAddrs
|
||||||
|
return rule.MatchAddressLimit(metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = r.defaultDomainStrategy
|
||||||
|
}
|
||||||
|
response, err = r.client.Exchange(dnsCtx, transport, message, options, responseCheck)
|
||||||
|
var rejected bool
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrResponseRejectedCached) {
|
||||||
|
rejected = true
|
||||||
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())), " (cached)")
|
||||||
|
} else if errors.Is(err, ErrResponseRejected) {
|
||||||
|
rejected = true
|
||||||
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||||
|
} else if len(message.Question) > 0 {
|
||||||
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||||
|
} else {
|
||||||
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if responseCheck != nil && rejected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {
|
||||||
|
if transport.Type() != C.DNSTypeFakeIP {
|
||||||
|
for _, answer := range response.Answer {
|
||||||
|
switch record := answer.(type) {
|
||||||
|
case *mDNS.A:
|
||||||
|
r.dnsReverseMapping.AddWithLifetime(M.AddrFromIP(record.A), FqdnToDomain(record.Hdr.Name), time.Duration(record.Hdr.Ttl)*time.Second)
|
||||||
|
case *mDNS.AAAA:
|
||||||
|
r.dnsReverseMapping.AddWithLifetime(M.AddrFromIP(record.AAAA), FqdnToDomain(record.Hdr.Name), time.Duration(record.Hdr.Ttl)*time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||||
|
var (
|
||||||
|
responseAddrs []netip.Addr
|
||||||
|
cached bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
printResult := func() {
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrResponseRejectedCached) {
|
||||||
|
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||||
|
} else if errors.Is(err, ErrResponseRejected) {
|
||||||
|
r.logger.DebugContext(ctx, "response rejected for ", domain)
|
||||||
|
} else {
|
||||||
|
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||||
|
}
|
||||||
|
} else if len(responseAddrs) == 0 {
|
||||||
|
r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
||||||
|
err = RCodeNameError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
||||||
|
if cached {
|
||||||
|
if len(responseAddrs) == 0 {
|
||||||
|
return nil, RCodeNameError
|
||||||
|
}
|
||||||
|
return responseAddrs, nil
|
||||||
|
}
|
||||||
|
r.logger.DebugContext(ctx, "lookup domain ", domain)
|
||||||
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
|
metadata.Destination = M.Socksaddr{}
|
||||||
|
metadata.Domain = domain
|
||||||
|
if options.Transport != nil {
|
||||||
|
transport := options.Transport
|
||||||
|
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = r.defaultDomainStrategy
|
||||||
|
}
|
||||||
|
if !options.ClientSubnet.IsValid() {
|
||||||
|
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = r.defaultDomainStrategy
|
||||||
|
}
|
||||||
|
responseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil)
|
||||||
|
} else {
|
||||||
|
var (
|
||||||
|
transport adapter.DNSTransport
|
||||||
|
rule adapter.DNSRule
|
||||||
|
ruleIndex int
|
||||||
|
)
|
||||||
|
ruleIndex = -1
|
||||||
|
for {
|
||||||
|
dnsCtx := adapter.OverrideContext(ctx)
|
||||||
|
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &options)
|
||||||
|
if rule != nil {
|
||||||
|
switch action := rule.Action().(type) {
|
||||||
|
case *R.RuleActionReject:
|
||||||
|
switch action.Method {
|
||||||
|
case C.RuleActionRejectMethodDefault:
|
||||||
|
return nil, nil
|
||||||
|
case C.RuleActionRejectMethodDrop:
|
||||||
|
return nil, tun.ErrDrop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||||
|
if rule != nil && rule.WithAddressLimit() {
|
||||||
|
responseCheck = func(responseAddrs []netip.Addr) bool {
|
||||||
|
metadata.DestinationAddresses = responseAddrs
|
||||||
|
return rule.MatchAddressLimit(metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = r.defaultDomainStrategy
|
||||||
|
}
|
||||||
|
responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, options, responseCheck)
|
||||||
|
if responseCheck == nil || err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
printResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printResult()
|
||||||
|
if len(responseAddrs) > 0 {
|
||||||
|
r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " "))
|
||||||
|
}
|
||||||
|
return responseAddrs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAddressQuery(message *mDNS.Msg) bool {
|
||||||
|
for _, question := range message.Question {
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA || question.Qtype == mDNS.TypeHTTPS {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) ClearCache() {
|
||||||
|
r.client.ClearCache()
|
||||||
|
if r.platformInterface != nil {
|
||||||
|
r.platformInterface.ClearDNSCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) {
|
||||||
|
if r.dnsReverseMapping == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
domain, loaded := r.dnsReverseMapping.Get(ip)
|
||||||
|
return domain, loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) ResetNetwork() {
|
||||||
|
r.ClearCache()
|
||||||
|
for _, transport := range r.transport.Transports() {
|
||||||
|
transport.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,6 @@ package dhcp
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -14,13 +11,18 @@ import (
|
|||||||
"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"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/task"
|
"github.com/sagernet/sing/common/task"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -29,76 +31,70 @@ import (
|
|||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) {
|
dns.RegisterTransport[option.DHCPDNSServerOptions](registry, C.DNSTypeDHCP, NewTransport)
|
||||||
return NewTransport(options)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
options dns.TransportOptions
|
dns.TransportAdapter
|
||||||
router adapter.Router
|
ctx context.Context
|
||||||
|
dialer N.Dialer
|
||||||
|
logger logger.ContextLogger
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
interfaceName string
|
interfaceName string
|
||||||
autoInterface bool
|
|
||||||
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||||
transports []dns.Transport
|
transports []adapter.DNSTransport
|
||||||
updateAccess sync.Mutex
|
updateAccess sync.Mutex
|
||||||
updatedAt time.Time
|
updatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransport(options dns.TransportOptions) (*Transport, error) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
linkURL, err := url.Parse(options.Address)
|
transportDialer, err := dns.NewLocalDialer(ctx, options.LocalDNSServerOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if linkURL.Host == "" {
|
return &Transport{
|
||||||
return nil, E.New("missing interface name for DHCP")
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions),
|
||||||
}
|
ctx: ctx,
|
||||||
transport := &Transport{
|
dialer: transportDialer,
|
||||||
options: options,
|
logger: logger,
|
||||||
networkManager: service.FromContext[adapter.NetworkManager](options.Context),
|
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
interfaceName: linkURL.Host,
|
interfaceName: options.Interface,
|
||||||
autoInterface: linkURL.Host == "auto",
|
}, nil
|
||||||
}
|
|
||||||
return transport, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Name() string {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
return t.options.Name
|
if stage != adapter.StartStateStart {
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
func (t *Transport) Start() error {
|
|
||||||
err := t.fetchServers()
|
err := t.fetchServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if t.autoInterface {
|
if t.interfaceName == "" {
|
||||||
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
|
for _, transport := range t.transports {
|
||||||
|
transport.Reset()
|
||||||
|
}
|
||||||
|
if t.interfaceCallback != nil {
|
||||||
|
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Reset() {
|
||||||
for _, transport := range t.transports {
|
for _, transport := range t.transports {
|
||||||
transport.Reset()
|
transport.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
|
||||||
for _, transport := range t.transports {
|
|
||||||
transport.Close()
|
|
||||||
}
|
|
||||||
if t.interfaceCallback != nil {
|
|
||||||
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Raw() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
||||||
err := t.fetchServers()
|
err := t.fetchServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,7 +116,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
||||||
if t.autoInterface {
|
if t.interfaceName == "" {
|
||||||
if t.networkManager.InterfaceMonitor() == nil {
|
if t.networkManager.InterfaceMonitor() == nil {
|
||||||
return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface")
|
return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface")
|
||||||
}
|
}
|
||||||
@@ -152,8 +148,8 @@ func (t *Transport) updateServers() error {
|
|||||||
return E.Cause(err, "dhcp: prepare interface")
|
return E.Cause(err, "dhcp: prepare interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.options.Logger.Info("dhcp: query DNS servers on ", iface.Name)
|
t.logger.Info("dhcp: query DNS servers on ", iface.Name)
|
||||||
fetchCtx, cancel := context.WithTimeout(t.options.Context, C.DHCPTimeout)
|
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
||||||
err = t.fetchServers0(fetchCtx, iface)
|
err = t.fetchServers0(fetchCtx, iface)
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -169,7 +165,7 @@ func (t *Transport) updateServers() error {
|
|||||||
func (t *Transport) interfaceUpdated(defaultInterface *control.Interface, flags int) {
|
func (t *Transport) interfaceUpdated(defaultInterface *control.Interface, flags int) {
|
||||||
err := t.updateServers()
|
err := t.updateServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.options.Logger.Error("update servers: ", err)
|
t.logger.Error("update servers: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +177,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface)
|
|||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||||
listenAddr = "255.255.255.255:68"
|
listenAddr = "255.255.255.255:68"
|
||||||
}
|
}
|
||||||
packetConn, err := listener.ListenPacket(t.options.Context, "udp4", listenAddr)
|
packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -219,17 +215,17 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
|||||||
|
|
||||||
dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes())
|
dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.options.Logger.Trace("dhcp: parse DHCP response: ", err)
|
t.logger.Trace("dhcp: parse DHCP response: ", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer {
|
if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer {
|
||||||
t.options.Logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType())
|
t.logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if dhcpPacket.TransactionID != transactionID {
|
if dhcpPacket.TransactionID != transactionID {
|
||||||
t.options.Logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID)
|
t.logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,44 +233,27 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
|||||||
if len(dns) == 0 {
|
if len(dns) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return t.recreateServers(iface, common.Map(dns, func(it net.IP) M.Socksaddr {
|
||||||
var addrs []netip.Addr
|
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
||||||
for _, ip := range dns {
|
}))
|
||||||
addr, _ := netip.AddrFromSlice(ip)
|
|
||||||
addrs = append(addrs, addr.Unmap())
|
|
||||||
}
|
|
||||||
return t.recreateServers(iface, addrs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []netip.Addr) error {
|
func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.Socksaddr) error {
|
||||||
if len(serverAddrs) > 0 {
|
if len(serverAddrs) > 0 {
|
||||||
t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string {
|
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "]")
|
||||||
return it.String()
|
|
||||||
}), ","), "]")
|
|
||||||
}
|
}
|
||||||
serverDialer := common.Must1(dialer.NewDefault(t.networkManager, option.DialerOptions{
|
serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{
|
||||||
BindInterface: iface.Name,
|
BindInterface: iface.Name,
|
||||||
UDPFragmentDefault: true,
|
UDPFragmentDefault: true,
|
||||||
}))
|
}))
|
||||||
var transports []dns.Transport
|
var transports []adapter.DNSTransport
|
||||||
for _, serverAddr := range serverAddrs {
|
for _, serverAddr := range serverAddrs {
|
||||||
newOptions := t.options
|
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
||||||
newOptions.Address = serverAddr.String()
|
|
||||||
newOptions.Dialer = serverDialer
|
|
||||||
serverTransport, err := dns.NewUDPTransport(newOptions)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "create UDP transport from DHCP result: ", serverAddr)
|
|
||||||
}
|
|
||||||
transports = append(transports, serverTransport)
|
|
||||||
}
|
}
|
||||||
for _, transport := range t.transports {
|
for _, transport := range t.transports {
|
||||||
transport.Close()
|
transport.Reset()
|
||||||
}
|
}
|
||||||
t.transports = transports
|
t.transports = transports
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
56
dns/transport/fakeip/fakeip.go
Normal file
56
dns/transport/fakeip/fakeip.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package fakeip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.FakeIPDNSServerOptions](registry, C.DNSTypeFakeIP, NewTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.FakeIPTransport = (*Transport)(nil)
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
store adapter.FakeIPStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.FakeIPDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
store := NewStore(ctx, logger, options.Inet4Range.Build(netip.Prefix{}), options.Inet6Range.Build(netip.Prefix{}))
|
||||||
|
return &Transport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeFakeIP, tag, nil),
|
||||||
|
logger: logger,
|
||||||
|
store: store,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
question := message.Question[0]
|
||||||
|
if question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA {
|
||||||
|
return nil, E.New("only IP queries are supported by fakeip")
|
||||||
|
}
|
||||||
|
address, err := t.store.Create(question.Name, question.Qtype == mDNS.TypeAAAA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dns.FixedResponse(message.Id, question, []netip.Addr{address}, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Store() adapter.FakeIPStore {
|
||||||
|
return t.store
|
||||||
|
}
|
||||||
193
dns/transport/https.go
Normal file
193
dns/transport/https.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
|
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MimeType = "application/dns-message"
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*HTTPSTransport)(nil)
|
||||||
|
|
||||||
|
func RegisterHTTPS(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTPS, NewHTTPS)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPSTransport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
dialer N.Dialer
|
||||||
|
destination *url.URL
|
||||||
|
headers http.Header
|
||||||
|
transport *http.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsOptions := common.PtrValueOrDefault(options.TLS)
|
||||||
|
tlsOptions.Enabled = true
|
||||||
|
tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if common.Error(tlsConfig.Config()) == nil && !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||||
|
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), http2.NextProtoTLS))
|
||||||
|
}
|
||||||
|
if !common.Contains(tlsConfig.NextProtos(), "http/1.1") {
|
||||||
|
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), "http/1.1"))
|
||||||
|
}
|
||||||
|
destinationURL := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: options.Host,
|
||||||
|
}
|
||||||
|
if destinationURL.Host == "" {
|
||||||
|
destinationURL.Host = options.Server
|
||||||
|
}
|
||||||
|
if options.ServerPort != 0 && options.ServerPort != 443 {
|
||||||
|
destinationURL.Host = net.JoinHostPort(destinationURL.Host, strconv.Itoa(int(options.ServerPort)))
|
||||||
|
}
|
||||||
|
path := options.Path
|
||||||
|
if path == "" {
|
||||||
|
path = "/dns-query"
|
||||||
|
}
|
||||||
|
err = sHTTP.URLSetPath(&destinationURL, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverAddr := options.ServerOptions.Build()
|
||||||
|
if serverAddr.Port == 0 {
|
||||||
|
serverAddr.Port = 443
|
||||||
|
}
|
||||||
|
return NewHTTPSRaw(
|
||||||
|
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
||||||
|
logger,
|
||||||
|
transportDialer,
|
||||||
|
&destinationURL,
|
||||||
|
options.Headers.Build(),
|
||||||
|
serverAddr,
|
||||||
|
tlsConfig,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPSRaw(
|
||||||
|
adapter dns.TransportAdapter,
|
||||||
|
logger log.ContextLogger,
|
||||||
|
dialer N.Dialer,
|
||||||
|
destination *url.URL,
|
||||||
|
headers http.Header,
|
||||||
|
serverAddr M.Socksaddr,
|
||||||
|
tlsConfig tls.Config,
|
||||||
|
) *HTTPSTransport {
|
||||||
|
var transport *http.Transport
|
||||||
|
if tlsConfig != nil {
|
||||||
|
transport = &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
tcpConn, hErr := dialer.DialContext(ctx, network, serverAddr)
|
||||||
|
if hErr != nil {
|
||||||
|
return nil, hErr
|
||||||
|
}
|
||||||
|
tlsConn, hErr := aTLS.ClientHandshake(ctx, tcpConn, tlsConfig)
|
||||||
|
if hErr != nil {
|
||||||
|
tcpConn.Close()
|
||||||
|
return nil, hErr
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transport = &http.Transport{
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.DialContext(ctx, network, serverAddr)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &HTTPSTransport{
|
||||||
|
TransportAdapter: adapter,
|
||||||
|
logger: logger,
|
||||||
|
dialer: dialer,
|
||||||
|
destination: destination,
|
||||||
|
headers: headers,
|
||||||
|
transport: transport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTPSTransport) Reset() {
|
||||||
|
t.transport.CloseIdleConnections()
|
||||||
|
t.transport = t.transport.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
exMessage := *message
|
||||||
|
exMessage.Id = 0
|
||||||
|
exMessage.Compress = true
|
||||||
|
requestBuffer := buf.NewSize(1 + message.Len())
|
||||||
|
rawMessage, err := exMessage.PackBuffer(requestBuffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
requestBuffer.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage))
|
||||||
|
if err != nil {
|
||||||
|
requestBuffer.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request.Header = t.headers.Clone()
|
||||||
|
request.Header.Set("Content-Type", MimeType)
|
||||||
|
request.Header.Set("Accept", MimeType)
|
||||||
|
response, err := t.transport.RoundTrip(request)
|
||||||
|
requestBuffer.Release()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return nil, E.New("unexpected status: ", response.Status)
|
||||||
|
}
|
||||||
|
var responseMessage mDNS.Msg
|
||||||
|
if response.ContentLength > 0 {
|
||||||
|
responseBuffer := buf.NewSize(int(response.ContentLength))
|
||||||
|
_, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = responseMessage.Unpack(responseBuffer.Bytes())
|
||||||
|
responseBuffer.Release()
|
||||||
|
} else {
|
||||||
|
rawMessage, err = io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = responseMessage.Unpack(rawMessage)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &responseMessage, nil
|
||||||
|
}
|
||||||
177
dns/transport/local/local.go
Normal file
177
dns/transport/local/local.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
dialer N.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transportDialer, err := dns.NewLocalDialer(ctx, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Transport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeTCP, tag, options),
|
||||||
|
dialer: transportDialer,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
question := message.Question[0]
|
||||||
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
addressStrings, _ := lookupStaticHost(domain)
|
||||||
|
if len(addressStrings) > 0 {
|
||||||
|
return dns.FixedResponse(message.Id, question, common.Map(addressStrings, M.ParseAddr), C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
systemConfig := getSystemDNSConfig()
|
||||||
|
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||||
|
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
||||||
|
} else {
|
||||||
|
return t.exchangeParallel(ctx, systemConfig, message, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
|
var lastErr error
|
||||||
|
for _, fqdn := range nameList(systemConfig, domain) {
|
||||||
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
type queryResult struct {
|
||||||
|
response *mDNS.Msg
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
results := make(chan queryResult)
|
||||||
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
|
select {
|
||||||
|
case results <- queryResult{response, err}:
|
||||||
|
case <-returned:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryCtx, queryCancel := context.WithCancel(ctx)
|
||||||
|
defer queryCancel()
|
||||||
|
for _, fqdn := range nameList(systemConfig, domain) {
|
||||||
|
go startRacer(queryCtx, fqdn)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case result := <-results:
|
||||||
|
return result.response, result.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
serverOffset := config.serverOffset()
|
||||||
|
sLen := uint32(len(config.servers))
|
||||||
|
var lastErr error
|
||||||
|
for i := 0; i < config.attempts; i++ {
|
||||||
|
for j := uint32(0); j < sLen; j++ {
|
||||||
|
server := config.servers[(serverOffset+j)%sLen]
|
||||||
|
question := message.Question[0]
|
||||||
|
question.Name = fqdn
|
||||||
|
response, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
||||||
|
var networks []string
|
||||||
|
if useTCP {
|
||||||
|
networks = []string{N.NetworkTCP}
|
||||||
|
} else {
|
||||||
|
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
||||||
|
}
|
||||||
|
request := &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: uint16(randInt()),
|
||||||
|
RecursionDesired: true,
|
||||||
|
AuthenticatedData: ad,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
|
request.SetEdns0(maxDNSPacketSize, false)
|
||||||
|
buffer := buf.Get(buf.UDPBufferSize)
|
||||||
|
defer buf.Put(buffer)
|
||||||
|
for _, network := range networks {
|
||||||
|
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||||
|
defer cancel()
|
||||||
|
conn, err := t.dialer.DialContext(ctx, network, server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||||
|
conn.SetDeadline(deadline)
|
||||||
|
}
|
||||||
|
rawMessage, err := request.PackBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "pack request")
|
||||||
|
}
|
||||||
|
_, err = conn.Write(rawMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "write request")
|
||||||
|
}
|
||||||
|
n, err := conn.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read response")
|
||||||
|
}
|
||||||
|
var response mDNS.Msg
|
||||||
|
err = response.Unpack(buffer[:n])
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "unpack response")
|
||||||
|
}
|
||||||
|
if response.Truncated && network == N.NetworkUDP {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
panic("unexpected")
|
||||||
|
}
|
||||||
19
dns/transport/local/local_badlinkname.go
Normal file
19
dns/transport/local/local_badlinkname.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build badlinkname
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname getSystemDNSConfig net.getSystemDNSConfig
|
||||||
|
func getSystemDNSConfig() *dnsConfig
|
||||||
|
|
||||||
|
//go:linkname nameList net.(*dnsConfig).nameList
|
||||||
|
func nameList(c *dnsConfig, name string) []string
|
||||||
|
|
||||||
|
//go:linkname lookupStaticHost net.lookupStaticHost
|
||||||
|
func lookupStaticHost(host string) ([]string, string)
|
||||||
|
|
||||||
|
//go:linkname splitHostZone net.splitHostZone
|
||||||
|
func splitHostZone(s string) (host, zone string)
|
||||||
44
dns/transport/local/local_linkname.go
Normal file
44
dns/transport/local/local_linkname.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// net.maxDNSPacketSize
|
||||||
|
maxDNSPacketSize = 1232
|
||||||
|
)
|
||||||
|
|
||||||
|
type dnsConfig struct {
|
||||||
|
servers []string // server addresses (in host:port form) to use
|
||||||
|
search []string // rooted suffixes to append to local name
|
||||||
|
ndots int // number of dots in name to trigger absolute lookup
|
||||||
|
timeout time.Duration // wait before giving up on a query, including retries
|
||||||
|
attempts int // lost packets before giving up on server
|
||||||
|
rotate bool // round robin among servers
|
||||||
|
unknownOpt bool // anything unknown was encountered
|
||||||
|
lookup []string // OpenBSD top-level database "lookup" order
|
||||||
|
err error // any error that occurs during open of resolv.conf
|
||||||
|
mtime time.Time // time of resolv.conf modification
|
||||||
|
soffset uint32 // used by serverOffset
|
||||||
|
singleRequest bool // use sequential A and AAAA queries instead of parallel queries
|
||||||
|
useTCP bool // force usage of TCP for DNS resolutions
|
||||||
|
trustAD bool // add AD flag to queries
|
||||||
|
noReload bool // do not check for config file updates
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsConfig) serverOffset() uint32 {
|
||||||
|
if c.rotate {
|
||||||
|
return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname runtime_rand runtime.rand
|
||||||
|
func runtime_rand() uint64
|
||||||
|
|
||||||
|
func randInt() int {
|
||||||
|
return int(uint(runtime_rand()) >> 1) // clear sign bit
|
||||||
|
}
|
||||||
19
dns/transport/local/local_notbadlinkname.go
Normal file
19
dns/transport/local/local_notbadlinkname.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !badlinkname
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
func getSystemDNSConfig() *dnsConfig {
|
||||||
|
panic("stub")
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameList(c *dnsConfig, name string) []string {
|
||||||
|
panic("stub")
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupStaticHost(host string) ([]string, string) {
|
||||||
|
panic("stub")
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitHostZone(s string) (host, zone string) {
|
||||||
|
panic("stub")
|
||||||
|
}
|
||||||
82
dns/transport/predefined.go
Normal file
82
dns/transport/predefined.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*PredefinedTransport)(nil)
|
||||||
|
|
||||||
|
func RegisterPredefined(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.PredefinedDNSServerOptions](registry, C.DNSTypePreDefined, NewPredefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PredefinedTransport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
responses []*predefinedResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type predefinedResponse struct {
|
||||||
|
questions []mDNS.Question
|
||||||
|
answer *mDNS.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPredefined(ctx context.Context, logger log.ContextLogger, tag string, options option.PredefinedDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
var responses []*predefinedResponse
|
||||||
|
for _, response := range options.Responses {
|
||||||
|
questions, msg, err := response.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
responses = append(responses, &predefinedResponse{
|
||||||
|
questions: questions,
|
||||||
|
answer: msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(responses) == 0 {
|
||||||
|
return nil, E.New("empty predefined responses")
|
||||||
|
}
|
||||||
|
return &PredefinedTransport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapter(C.DNSTypePreDefined, tag, nil),
|
||||||
|
responses: responses,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PredefinedTransport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PredefinedTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
for _, response := range t.responses {
|
||||||
|
for _, question := range response.questions {
|
||||||
|
if func() bool {
|
||||||
|
if question.Name == "" && question.Qtype == mDNS.TypeNone {
|
||||||
|
return true
|
||||||
|
} else if question.Name == "" {
|
||||||
|
return common.Any(message.Question, func(it mDNS.Question) bool {
|
||||||
|
return it.Qtype == question.Qtype
|
||||||
|
})
|
||||||
|
} else if question.Qtype == mDNS.TypeNone {
|
||||||
|
return common.Any(message.Question, func(it mDNS.Question) bool {
|
||||||
|
return it.Name == question.Name
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return common.Contains(message.Question, question)
|
||||||
|
}
|
||||||
|
}() {
|
||||||
|
copyAnswer := *response.answer
|
||||||
|
copyAnswer.Id = message.Id
|
||||||
|
return ©Answer, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, dns.RCodeNameError
|
||||||
|
}
|
||||||
156
dns/transport/quic/http3.go
Normal file
156
dns/transport/quic/http3.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package quic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/quic-go/http3"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*HTTP3Transport)(nil)
|
||||||
|
|
||||||
|
func RegisterHTTP3Transport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.RemoteHTTPSDNSServerOptions](registry, C.DNSTypeHTTP3, NewHTTP3)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTP3Transport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
dialer N.Dialer
|
||||||
|
destination *url.URL
|
||||||
|
headers http.Header
|
||||||
|
transport *http3.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsOptions := common.PtrValueOrDefault(options.TLS)
|
||||||
|
tlsOptions.Enabled = true
|
||||||
|
tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stdConfig, err := tlsConfig.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
destinationURL := url.URL{
|
||||||
|
Scheme: "HTTP3",
|
||||||
|
Host: options.Host,
|
||||||
|
}
|
||||||
|
if destinationURL.Host == "" {
|
||||||
|
destinationURL.Host = options.Server
|
||||||
|
}
|
||||||
|
if options.ServerPort != 0 && options.ServerPort != 443 {
|
||||||
|
destinationURL.Host = net.JoinHostPort(destinationURL.Host, strconv.Itoa(int(options.ServerPort)))
|
||||||
|
}
|
||||||
|
path := options.Path
|
||||||
|
if path == "" {
|
||||||
|
path = "/dns-query"
|
||||||
|
}
|
||||||
|
err = sHTTP.URLSetPath(&destinationURL, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverAddr := options.ServerOptions.Build()
|
||||||
|
if serverAddr.Port == 0 {
|
||||||
|
serverAddr.Port = 443
|
||||||
|
}
|
||||||
|
return &HTTP3Transport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
||||||
|
logger: logger,
|
||||||
|
dialer: transportDialer,
|
||||||
|
destination: &destinationURL,
|
||||||
|
headers: options.Headers.Build(),
|
||||||
|
transport: &http3.Transport{
|
||||||
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
destinationAddr := M.ParseSocksaddr(addr)
|
||||||
|
conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, destinationAddr)
|
||||||
|
if dialErr != nil {
|
||||||
|
return nil, dialErr
|
||||||
|
}
|
||||||
|
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(conn), conn.RemoteAddr(), tlsCfg, cfg)
|
||||||
|
},
|
||||||
|
TLSClientConfig: stdConfig,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTP3Transport) Reset() {
|
||||||
|
t.transport.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
exMessage := *message
|
||||||
|
exMessage.Id = 0
|
||||||
|
exMessage.Compress = true
|
||||||
|
requestBuffer := buf.NewSize(1 + message.Len())
|
||||||
|
rawMessage, err := exMessage.PackBuffer(requestBuffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
requestBuffer.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage))
|
||||||
|
if err != nil {
|
||||||
|
requestBuffer.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request.Header = t.headers.Clone()
|
||||||
|
request.Header.Set("Content-Type", transport.MimeType)
|
||||||
|
request.Header.Set("Accept", transport.MimeType)
|
||||||
|
response, err := t.transport.RoundTrip(request)
|
||||||
|
requestBuffer.Release()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return nil, E.New("unexpected status: ", response.Status)
|
||||||
|
}
|
||||||
|
var responseMessage mDNS.Msg
|
||||||
|
if response.ContentLength > 0 {
|
||||||
|
responseBuffer := buf.NewSize(int(response.ContentLength))
|
||||||
|
_, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = responseMessage.Unpack(responseBuffer.Bytes())
|
||||||
|
responseBuffer.Release()
|
||||||
|
} else {
|
||||||
|
rawMessage, err = io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = responseMessage.Unpack(rawMessage)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &responseMessage, nil
|
||||||
|
}
|
||||||
174
dns/transport/quic/quic.go
Normal file
174
dns/transport/quic/quic.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package quic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
sQUIC "github.com/sagernet/sing-quic"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
|
||||||
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeQUIC, NewQUIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
dialer N.Dialer
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
tlsConfig tls.Config
|
||||||
|
access sync.Mutex
|
||||||
|
connection quic.EarlyConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsOptions := common.PtrValueOrDefault(options.TLS)
|
||||||
|
tlsOptions.Enabled = true
|
||||||
|
tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
|
tlsConfig.SetNextProtos([]string{"doq"})
|
||||||
|
}
|
||||||
|
serverAddr := options.ServerOptions.Build()
|
||||||
|
if serverAddr.Port == 0 {
|
||||||
|
serverAddr.Port = 853
|
||||||
|
}
|
||||||
|
return &Transport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
dialer: transportDialer,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
connection := t.connection
|
||||||
|
if connection != nil {
|
||||||
|
connection.CloseWithError(0, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
var (
|
||||||
|
conn quic.Connection
|
||||||
|
err error
|
||||||
|
response *mDNS.Msg
|
||||||
|
)
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
conn, err = t.openConnection()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response, err = t.exchange(ctx, message, conn)
|
||||||
|
if err == nil {
|
||||||
|
return response, nil
|
||||||
|
} else if !isQUICRetryError(err) {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
conn.CloseWithError(quic.ApplicationErrorCode(0), "")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) openConnection() (quic.EarlyConnection, error) {
|
||||||
|
connection := t.connection
|
||||||
|
if connection != nil && !common.Done(connection.Context()) {
|
||||||
|
return connection, nil
|
||||||
|
}
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
connection = t.connection
|
||||||
|
if connection != nil && !common.Done(connection.Context()) {
|
||||||
|
return connection, nil
|
||||||
|
}
|
||||||
|
conn, err := t.dialer.DialContext(t.ctx, N.NetworkUDP, t.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
earlyConnection, err := sQUIC.DialEarly(
|
||||||
|
t.ctx,
|
||||||
|
bufio.NewUnbindPacketConn(conn),
|
||||||
|
t.serverAddr.UDPAddr(),
|
||||||
|
t.tlsConfig,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.connection = earlyConnection
|
||||||
|
return earlyConnection, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.Connection) (*mDNS.Msg, error) {
|
||||||
|
stream, err := conn.OpenStreamSync(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer stream.Close()
|
||||||
|
defer stream.CancelRead(0)
|
||||||
|
err = transport.WriteMessage(stream, 0, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return transport.ReadMessage(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/AdguardTeam/dnsproxy/blob/fd1868577652c639cce3da00e12ca548f421baf1/upstream/upstream_quic.go#L394
|
||||||
|
func isQUICRetryError(err error) (ok bool) {
|
||||||
|
var qAppErr *quic.ApplicationError
|
||||||
|
if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var qIdleErr *quic.IdleTimeoutError
|
||||||
|
if errors.As(err, &qIdleErr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var resetErr *quic.StatelessResetError
|
||||||
|
if errors.As(err, &resetErr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var qTransportError *quic.TransportError
|
||||||
|
if errors.As(err, &qTransportError) && qTransportError.ErrorCode == quic.NoError {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, quic.Err0RTTRejected) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
99
dns/transport/tcp.go
Normal file
99
dns/transport/tcp.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*TCPTransport)(nil)
|
||||||
|
|
||||||
|
func RegisterTCP(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.RemoteDNSServerOptions](registry, C.DNSTypeTCP, NewTCP)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TCPTransport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
dialer N.Dialer
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transportDialer, err := dns.NewRemoteDialer(ctx, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverAddr := options.ServerOptions.Build()
|
||||||
|
if serverAddr.Port == 0 {
|
||||||
|
serverAddr.Port = 53
|
||||||
|
}
|
||||||
|
return &TCPTransport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
||||||
|
dialer: transportDialer,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCPTransport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
err = WriteMessage(conn, 0, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ReadMessage(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadMessage(reader io.Reader) (*mDNS.Msg, error) {
|
||||||
|
var responseLen uint16
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &responseLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if responseLen < 10 {
|
||||||
|
return nil, mDNS.ErrShortRead
|
||||||
|
}
|
||||||
|
buffer := buf.NewSize(int(responseLen))
|
||||||
|
defer buffer.Release()
|
||||||
|
_, err = buffer.ReadFullFrom(reader, int(responseLen))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var message mDNS.Msg
|
||||||
|
err = message.Unpack(buffer.Bytes())
|
||||||
|
return &message, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteMessage(writer io.Writer, messageId uint16, message *mDNS.Msg) error {
|
||||||
|
requestLen := message.Len()
|
||||||
|
buffer := buf.NewSize(3 + requestLen)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(binary.Write(buffer, binary.BigEndian, uint16(requestLen)))
|
||||||
|
exMessage := *message
|
||||||
|
exMessage.Id = messageId
|
||||||
|
exMessage.Compress = true
|
||||||
|
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer.Truncate(2 + len(rawMessage))
|
||||||
|
return common.Error(writer.Write(buffer.Bytes()))
|
||||||
|
}
|
||||||
115
dns/transport/tls.go
Normal file
115
dns/transport/tls.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*TLSTransport)(nil)
|
||||||
|
|
||||||
|
func RegisterTLS(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.RemoteTLSDNSServerOptions](registry, C.DNSTypeTLS, NewTLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLSTransport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
dialer N.Dialer
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
tlsConfig tls.Config
|
||||||
|
access sync.Mutex
|
||||||
|
connections list.List[*tlsDNSConn]
|
||||||
|
}
|
||||||
|
|
||||||
|
type tlsDNSConn struct {
|
||||||
|
tls.Conn
|
||||||
|
queryId uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsOptions := common.PtrValueOrDefault(options.TLS)
|
||||||
|
tlsOptions.Enabled = true
|
||||||
|
tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverAddr := options.ServerOptions.Build()
|
||||||
|
if serverAddr.Port == 0 {
|
||||||
|
serverAddr.Port = 853
|
||||||
|
}
|
||||||
|
return &TLSTransport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
|
||||||
|
logger: logger,
|
||||||
|
dialer: transportDialer,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSTransport) Reset() {
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
for connection := t.connections.Front(); connection != nil; connection = connection.Next() {
|
||||||
|
connection.Value.Close()
|
||||||
|
}
|
||||||
|
t.connections.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
t.access.Lock()
|
||||||
|
conn := t.connections.PopFront()
|
||||||
|
t.access.Unlock()
|
||||||
|
if conn != nil {
|
||||||
|
response, err := t.exchange(message, conn)
|
||||||
|
if err == nil {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tcpConn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
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})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSTransport) exchange(message *mDNS.Msg, conn *tlsDNSConn) (*mDNS.Msg, error) {
|
||||||
|
conn.queryId++
|
||||||
|
err := WriteMessage(conn, conn.queryId, message)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, E.Cause(err, "write request")
|
||||||
|
}
|
||||||
|
response, err := ReadMessage(conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, E.Cause(err, "read response")
|
||||||
|
}
|
||||||
|
t.access.Lock()
|
||||||
|
t.connections.PushBack(conn)
|
||||||
|
t.access.Unlock()
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
217
dns/transport/udp.go
Normal file
217
dns/transport/udp.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*UDPTransport)(nil)
|
||||||
|
|
||||||
|
func RegisterUDP(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.RemoteDNSServerOptions](registry, C.DNSTypeUDP, NewUDP)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPTransport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
dialer N.Dialer
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
udpSize int
|
||||||
|
tcpTransport *TCPTransport
|
||||||
|
access sync.Mutex
|
||||||
|
conn *dnsConnection
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transportDialer, err := dns.NewRemoteDialer(ctx, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverAddr := options.ServerOptions.Build()
|
||||||
|
if serverAddr.Port == 0 {
|
||||||
|
serverAddr.Port = 53
|
||||||
|
}
|
||||||
|
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr) *UDPTransport {
|
||||||
|
return &UDPTransport{
|
||||||
|
TransportAdapter: adapter,
|
||||||
|
logger: logger,
|
||||||
|
dialer: dialer,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
udpSize: 512,
|
||||||
|
tcpTransport: &TCPTransport{
|
||||||
|
dialer: dialer,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
},
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *UDPTransport) Reset() {
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
close(t.done)
|
||||||
|
t.done = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
response, err := t.exchange(ctx, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if response.Truncated {
|
||||||
|
t.logger.InfoContext(ctx, "response truncated, retrying with TCP")
|
||||||
|
return t.tcpTransport.Exchange(ctx, message)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
conn, err := t.open(ctx)
|
||||||
|
if err != nil {
|
||||||
|
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())
|
||||||
|
defer buffer.Release()
|
||||||
|
exMessage := *message
|
||||||
|
exMessage.Compress = true
|
||||||
|
messageId := message.Id
|
||||||
|
callback := &dnsCallback{
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
conn.access.Lock()
|
||||||
|
conn.queryId++
|
||||||
|
exMessage.Id = conn.queryId
|
||||||
|
conn.callbacks[exMessage.Id] = callback
|
||||||
|
conn.access.Unlock()
|
||||||
|
defer func() {
|
||||||
|
conn.access.Lock()
|
||||||
|
delete(conn.callbacks, messageId)
|
||||||
|
conn.access.Unlock()
|
||||||
|
callback.access.Lock()
|
||||||
|
select {
|
||||||
|
case <-callback.done:
|
||||||
|
default:
|
||||||
|
close(callback.done)
|
||||||
|
}
|
||||||
|
callback.access.Unlock()
|
||||||
|
}()
|
||||||
|
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = conn.Write(rawMessage)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-callback.done:
|
||||||
|
callback.message.Id = messageId
|
||||||
|
return callback.message, nil
|
||||||
|
case <-conn.done:
|
||||||
|
return nil, conn.err
|
||||||
|
case <-t.done:
|
||||||
|
return nil, os.ErrClosed
|
||||||
|
case <-ctx.Done():
|
||||||
|
conn.Close(ctx.Err())
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *UDPTransport) open(ctx context.Context) (*dnsConnection, error) {
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dnsConn := &dnsConnection{
|
||||||
|
Conn: conn,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
callbacks: make(map[uint16]*dnsCallback),
|
||||||
|
}
|
||||||
|
go t.recvLoop(dnsConn)
|
||||||
|
return dnsConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *UDPTransport) recvLoop(conn *dnsConnection) {
|
||||||
|
for {
|
||||||
|
buffer := buf.NewSize(t.udpSize)
|
||||||
|
_, err := buffer.ReadOnceFrom(conn)
|
||||||
|
if err != nil {
|
||||||
|
buffer.Release()
|
||||||
|
conn.Close(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var message mDNS.Msg
|
||||||
|
err = message.Unpack(buffer.Bytes())
|
||||||
|
buffer.Release()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.access.RLock()
|
||||||
|
callback, loaded := conn.callbacks[message.Id]
|
||||||
|
conn.access.RUnlock()
|
||||||
|
if !loaded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
callback.access.Lock()
|
||||||
|
select {
|
||||||
|
case <-callback.done:
|
||||||
|
default:
|
||||||
|
callback.message = &message
|
||||||
|
close(callback.done)
|
||||||
|
}
|
||||||
|
callback.access.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dnsConnection struct {
|
||||||
|
net.Conn
|
||||||
|
access sync.RWMutex
|
||||||
|
done chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
|
err error
|
||||||
|
queryId uint16
|
||||||
|
callbacks map[uint16]*dnsCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsConnection) Close(err error) {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
c.closeOnce.Do(func() {
|
||||||
|
close(c.done)
|
||||||
|
c.err = err
|
||||||
|
})
|
||||||
|
c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type dnsCallback struct {
|
||||||
|
access sync.Mutex
|
||||||
|
message *mDNS.Msg
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
70
dns/transport_adapter.go
Normal file
70
dns/transport_adapter.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.LegacyDNSTransport = (*TransportAdapter)(nil)
|
||||||
|
|
||||||
|
type TransportAdapter struct {
|
||||||
|
transportType string
|
||||||
|
transportTag string
|
||||||
|
dependencies []string
|
||||||
|
strategy C.DomainStrategy
|
||||||
|
clientSubnet netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {
|
||||||
|
return TransportAdapter{
|
||||||
|
transportType: transportType,
|
||||||
|
transportTag: transportTag,
|
||||||
|
dependencies: dependencies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter {
|
||||||
|
return TransportAdapter{
|
||||||
|
transportType: transportType,
|
||||||
|
transportTag: transportTag,
|
||||||
|
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||||
|
clientSubnet: localOptions.LegacyClientSubnet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter {
|
||||||
|
var dependencies []string
|
||||||
|
if remoteOptions.AddressResolver != "" {
|
||||||
|
dependencies = []string{remoteOptions.AddressResolver}
|
||||||
|
}
|
||||||
|
return TransportAdapter{
|
||||||
|
transportType: transportType,
|
||||||
|
transportTag: transportTag,
|
||||||
|
dependencies: dependencies,
|
||||||
|
strategy: C.DomainStrategy(remoteOptions.LegacyStrategy),
|
||||||
|
clientSubnet: remoteOptions.LegacyClientSubnet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) Type() string {
|
||||||
|
return a.transportType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) Tag() string {
|
||||||
|
return a.transportTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) Dependencies() []string {
|
||||||
|
return a.dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
|
||||||
|
return a.strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) LegacyClientSubnet() netip.Prefix {
|
||||||
|
return a.clientSubnet
|
||||||
|
}
|
||||||
101
dns/transport_dialer.go
Normal file
101
dns/transport_dialer.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) {
|
||||||
|
if options.LegacyDefaultDialer {
|
||||||
|
return dialer.NewDefaultOutbound(ctx), nil
|
||||||
|
} else {
|
||||||
|
return dialer.New(ctx, options.DialerOptions, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
||||||
|
var (
|
||||||
|
transportDialer N.Dialer
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if options.LegacyDefaultDialer {
|
||||||
|
transportDialer = dialer.NewDefaultOutbound(ctx)
|
||||||
|
} else {
|
||||||
|
transportDialer, err = dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if options.AddressResolver != "" {
|
||||||
|
transport := service.FromContext[adapter.DNSTransportManager](ctx)
|
||||||
|
resolverTransport, loaded := transport.Transport(options.AddressResolver)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("address resolver not found: ", options.AddressResolver)
|
||||||
|
}
|
||||||
|
transportDialer = NewTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.AddressStrategy), time.Duration(options.AddressFallbackDelay))
|
||||||
|
} else if options.ServerIsDomain() {
|
||||||
|
return nil, E.New("missing address resolver for server: ", options.Server)
|
||||||
|
}
|
||||||
|
return transportDialer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransportDialer struct {
|
||||||
|
dialer N.Dialer
|
||||||
|
dnsRouter adapter.DNSRouter
|
||||||
|
transport adapter.DNSTransport
|
||||||
|
strategy C.DomainStrategy
|
||||||
|
fallbackDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *TransportDialer {
|
||||||
|
return &TransportDialer{
|
||||||
|
dialer,
|
||||||
|
dnsRouter,
|
||||||
|
transport,
|
||||||
|
strategy,
|
||||||
|
fallbackDelay,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
if destination.IsIP() {
|
||||||
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
|
}
|
||||||
|
addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{
|
||||||
|
Transport: d.transport,
|
||||||
|
Strategy: d.strategy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
if destination.IsIP() {
|
||||||
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
|
}
|
||||||
|
addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{
|
||||||
|
Transport: d.transport,
|
||||||
|
Strategy: d.strategy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, _, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TransportDialer) Upstream() any {
|
||||||
|
return d.dialer
|
||||||
|
}
|
||||||
288
dns/transport_manager.go
Normal file
288
dns/transport_manager.go
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.DNSTransportManager = (*TransportManager)(nil)
|
||||||
|
|
||||||
|
type TransportManager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.DNSTransportRegistry
|
||||||
|
outbound adapter.OutboundManager
|
||||||
|
defaultTag string
|
||||||
|
access sync.RWMutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
transports []adapter.DNSTransport
|
||||||
|
transportByTag map[string]adapter.DNSTransport
|
||||||
|
dependByTag map[string][]string
|
||||||
|
defaultTransport adapter.DNSTransport
|
||||||
|
defaultTransportFallback adapter.DNSTransport
|
||||||
|
fakeIPTransport adapter.FakeIPTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransportRegistry, outbound adapter.OutboundManager, defaultTag string) *TransportManager {
|
||||||
|
return &TransportManager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
outbound: outbound,
|
||||||
|
defaultTag: defaultTag,
|
||||||
|
transportByTag: make(map[string]adapter.DNSTransport),
|
||||||
|
dependByTag: make(map[string][]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) Initialize(defaultTransportFallback adapter.DNSTransport) {
|
||||||
|
m.defaultTransportFallback = defaultTransportFallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
outbounds := m.transports
|
||||||
|
m.access.Unlock()
|
||||||
|
if stage == adapter.StartStateStart {
|
||||||
|
return m.startTransports(m.transports)
|
||||||
|
} else {
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " dns/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) startTransports(transports []adapter.DNSTransport) error {
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StartTimeout)
|
||||||
|
started := make(map[string]bool)
|
||||||
|
for {
|
||||||
|
canContinue := false
|
||||||
|
startOne:
|
||||||
|
for _, transportToStart := range transports {
|
||||||
|
transportTag := transportToStart.Tag()
|
||||||
|
if started[transportTag] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dependencies := transportToStart.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
if !started[dependency] {
|
||||||
|
continue startOne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
started[transportTag] = true
|
||||||
|
canContinue = true
|
||||||
|
if starter, isStarter := transportToStart.(adapter.Lifecycle); isStarter {
|
||||||
|
monitor.Start("start dns/", transportToStart.Type(), "[", transportTag, "]")
|
||||||
|
err := starter.Start(adapter.StartStateStart)
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start dns/", transportToStart.Type(), "[", transportTag, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(started) == len(transports) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if canContinue {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentTransport := common.Find(transports, func(it adapter.DNSTransport) bool {
|
||||||
|
return !started[it.Tag()]
|
||||||
|
})
|
||||||
|
var lintTransport func(oTree []string, oCurrent adapter.DNSTransport) error
|
||||||
|
lintTransport = func(oTree []string, oCurrent adapter.DNSTransport) error {
|
||||||
|
problemTransportTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
||||||
|
return !started[it]
|
||||||
|
})
|
||||||
|
if common.Contains(oTree, problemTransportTag) {
|
||||||
|
return E.New("circular server dependency: ", strings.Join(oTree, " -> "), " -> ", problemTransportTag)
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
problemTransport := m.transportByTag[problemTransportTag]
|
||||||
|
m.access.Unlock()
|
||||||
|
if problemTransport == nil {
|
||||||
|
return E.New("dependency[", problemTransportTag, "] not found for server[", oCurrent.Tag(), "]")
|
||||||
|
}
|
||||||
|
return lintTransport(append(oTree, problemTransportTag), problemTransport)
|
||||||
|
}
|
||||||
|
return lintTransport([]string{currentTransport.Tag()}, currentTransport)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) Close() error {
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
m.access.Lock()
|
||||||
|
if !m.started {
|
||||||
|
m.access.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
transports := m.transports
|
||||||
|
m.transports = nil
|
||||||
|
m.access.Unlock()
|
||||||
|
var err error
|
||||||
|
for _, transport := range transports {
|
||||||
|
if closer, isCloser := transport.(io.Closer); isCloser {
|
||||||
|
monitor.Start("close server/", transport.Type(), "[", transport.Tag(), "]")
|
||||||
|
err = E.Append(err, closer.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close server/", transport.Type(), "[", transport.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) Transports() []adapter.DNSTransport {
|
||||||
|
m.access.RLock()
|
||||||
|
defer m.access.RUnlock()
|
||||||
|
return m.transports
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) {
|
||||||
|
m.access.RLock()
|
||||||
|
outbound, found := m.transportByTag[tag]
|
||||||
|
m.access.RUnlock()
|
||||||
|
return outbound, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) Default() adapter.DNSTransport {
|
||||||
|
m.access.RLock()
|
||||||
|
defer m.access.RUnlock()
|
||||||
|
if m.defaultTransport != nil {
|
||||||
|
return m.defaultTransport
|
||||||
|
} else {
|
||||||
|
return m.defaultTransportFallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) FakeIP() adapter.FakeIPTransport {
|
||||||
|
m.access.RLock()
|
||||||
|
defer m.access.RUnlock()
|
||||||
|
return m.fakeIPTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
transport, found := m.transportByTag[tag]
|
||||||
|
if !found {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.transportByTag, tag)
|
||||||
|
index := common.Index(m.transports, func(it adapter.DNSTransport) bool {
|
||||||
|
return it == transport
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.transports = append(m.transports[:index], m.transports[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
if m.defaultTransport == transport {
|
||||||
|
if len(m.transports) > 0 {
|
||||||
|
nextTransport := m.transports[0]
|
||||||
|
if nextTransport.Type() != C.DNSTypeFakeIP {
|
||||||
|
return E.New("default server cannot be fakeip")
|
||||||
|
}
|
||||||
|
m.defaultTransport = nextTransport
|
||||||
|
m.logger.Info("updated default server to ", m.defaultTransport.Tag())
|
||||||
|
} else {
|
||||||
|
m.defaultTransport = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependBy := m.dependByTag[tag]
|
||||||
|
if len(dependBy) > 0 {
|
||||||
|
return E.New("server[", tag, "] is depended by ", strings.Join(dependBy, ", "))
|
||||||
|
}
|
||||||
|
dependencies := transport.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
if len(m.dependByTag[dependency]) == 1 {
|
||||||
|
delete(m.dependByTag, dependency)
|
||||||
|
} else {
|
||||||
|
m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {
|
||||||
|
return it != tag
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if started {
|
||||||
|
transport.Reset()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TransportManager) Create(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) error {
|
||||||
|
if tag == "" {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
transport, err := m.registry.CreateDNSTransport(ctx, logger, tag, transportType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(transport, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " dns/", transport.Type(), "[", transport.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsTransport, loaded := m.transportByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = common.Close(existsTransport)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close dns/", existsTransport.Type(), "[", existsTransport.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.transports, func(it adapter.DNSTransport) bool {
|
||||||
|
return it == existsTransport
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.transports = append(m.transports[:existsIndex], m.transports[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.transports = append(m.transports, transport)
|
||||||
|
m.transportByTag[tag] = transport
|
||||||
|
dependencies := transport.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
m.dependByTag[dependency] = append(m.dependByTag[dependency], tag)
|
||||||
|
}
|
||||||
|
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultTransport == nil) {
|
||||||
|
if transport.Type() == C.DNSTypeFakeIP {
|
||||||
|
return E.New("default server cannot be fakeip")
|
||||||
|
}
|
||||||
|
m.defaultTransport = transport
|
||||||
|
if m.started {
|
||||||
|
m.logger.Info("updated default server to ", transport.Tag())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if transport.Type() == C.DNSTypeFakeIP {
|
||||||
|
if m.fakeIPTransport != nil {
|
||||||
|
return E.New("multiple fakeip server are not supported")
|
||||||
|
}
|
||||||
|
m.fakeIPTransport = transport.(adapter.FakeIPTransport)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
dns/transport_registry.go
Normal file
72
dns/transport_registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransportConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.DNSTransport, error)
|
||||||
|
|
||||||
|
func RegisterTransport[Options any](registry *TransportRegistry, transportType string, constructor TransportConstructorFunc[Options]) {
|
||||||
|
registry.register(transportType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.DNSTransport, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.DNSTransportRegistry = (*TransportRegistry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.DNSTransport, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransportRegistry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructors map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransportRegistry() *TransportRegistry {
|
||||||
|
return &TransportRegistry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructors: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TransportRegistry) CreateOptions(transportType string) (any, bool) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
optionsConstructor, loaded := r.optionsType[transportType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TransportRegistry) CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (adapter.DNSTransport, error) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
constructor, loaded := r.constructors[transportType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("transport type not found: " + transportType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TransportRegistry) register(transportType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
r.optionsType[transportType] = optionsConstructor
|
||||||
|
r.constructors[transportType] = constructor
|
||||||
|
}
|
||||||
@@ -2,10 +2,56 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.11.0-beta.9
|
#### 1.11.0-beta.23
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.10.7
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.11.0-beta.20
|
||||||
|
|
||||||
|
* Hysteria2 `ignore_client_bandwidth` behavior update **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
||||||
|
|
||||||
|
See [Hysteria2](/configuration/inbound/hysteria2/#ignore_client_bandwidth).
|
||||||
|
|
||||||
|
#### 1.11.0-beta.17
|
||||||
|
|
||||||
|
* Add port hopping support for Hysteria2 **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Hysteria2](/configuration/outbound/hysteria2/).
|
||||||
|
|
||||||
|
#### 1.11.0-beta.14
|
||||||
|
|
||||||
|
* Allow adding route (exclude) address sets to routes **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
When `auto_redirect` is not enabled, directly add `route[_exclude]_address_set`
|
||||||
|
to tun routes (equivalent to `route[_exclude]_address`).
|
||||||
|
|
||||||
|
Note that it **doesn't work on the Android graphical client** due to
|
||||||
|
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
|
||||||
|
but otherwise it works fine on all command line clients and Apple platforms.
|
||||||
|
|
||||||
|
See [route_address_set](/configuration/inbound/tun/#route_address_set) and
|
||||||
|
[route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).
|
||||||
|
|
||||||
|
#### 1.11.0-beta.12
|
||||||
|
|
||||||
|
* Add `rule-set merge` command
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.11.0-beta.3
|
#### 1.11.0-beta.3
|
||||||
|
|
||||||
* Add more masquerade options for hysteria2 **1**
|
* Add more masquerade options for hysteria2 **1**
|
||||||
@@ -15,10 +61,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
|
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
|
||||||
|
|
||||||
### 1.10.3
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.11.0-alpha.25
|
#### 1.11.0-alpha.25
|
||||||
|
|
||||||
* Update quic-go to v0.48.2
|
* Update quic-go to v0.48.2
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ icon: material/alert-decagram
|
|||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-alert: [masquerade](#masquerade)
|
:material-alert: [masquerade](#masquerade)
|
||||||
|
:material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth)
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
@@ -75,9 +76,13 @@ Authentication password
|
|||||||
|
|
||||||
#### ignore_client_bandwidth
|
#### ignore_client_bandwidth
|
||||||
|
|
||||||
Commands the client to use the BBR flow control algorithm instead of Hysteria CC.
|
*When `up_mbps` and `down_mbps` are not set*:
|
||||||
|
|
||||||
Conflict with `up_mbps` and `down_mbps`.
|
Commands clients to use the BBR CC instead of Hysteria CC.
|
||||||
|
|
||||||
|
*When `up_mbps` and `down_mbps` are set*:
|
||||||
|
|
||||||
|
Deny clients to use the BBR CC.
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ icon: material/alert-decagram
|
|||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-alert: [masquerade](#masquerade)
|
:material-alert: [masquerade](#masquerade)
|
||||||
|
:material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth)
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
@@ -72,9 +73,13 @@ Hysteria 用户
|
|||||||
|
|
||||||
#### ignore_client_bandwidth
|
#### ignore_client_bandwidth
|
||||||
|
|
||||||
|
*当 `up_mbps` 和 `down_mbps` 未设定时*:
|
||||||
|
|
||||||
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
||||||
|
|
||||||
与 `up_mbps` 和 `down_mbps` 冲突。
|
*当 `up_mbps` 和 `down_mbps` 已设定时*:
|
||||||
|
|
||||||
|
禁止客户端使用 BBR 拥塞控制算法。
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ icon: material/alert-decagram
|
|||||||
!!! 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)
|
||||||
|
:material-alert-decagram: [route_address_set](#stack)
|
||||||
|
:material-alert-decagram: [route_exclude_address_set](#stack)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
@@ -88,13 +90,13 @@ icon: material/alert-decagram
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"include_uid_range": [
|
"include_uid_range": [
|
||||||
"1000-99999"
|
"1000:99999"
|
||||||
],
|
],
|
||||||
"exclude_uid": [
|
"exclude_uid": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"exclude_uid_range": [
|
"exclude_uid_range": [
|
||||||
"1000-99999"
|
"1000:99999"
|
||||||
],
|
],
|
||||||
"include_android_user": [
|
"include_android_user": [
|
||||||
0,
|
0,
|
||||||
@@ -248,7 +250,7 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
|||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
Connection input mark used by `route_address_set` and `route_exclude_address_set`.
|
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
|
||||||
|
|
||||||
`0x2023` is used by default.
|
`0x2023` is used by default.
|
||||||
|
|
||||||
@@ -256,7 +258,7 @@ Connection input mark used by `route_address_set` and `route_exclude_address_set
|
|||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
Connection output mark used by `route_address_set` and `route_exclude_address_set`.
|
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
|
||||||
|
|
||||||
`0x2024` is used by default.
|
`0x2024` is used by default.
|
||||||
|
|
||||||
@@ -329,29 +331,55 @@ Exclude custom routes when `auto_route` is enabled.
|
|||||||
|
|
||||||
#### route_address_set
|
#### route_address_set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
=== "With `auto_redirect` enabled"
|
||||||
|
|
||||||
!!! quote ""
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||||
|
|
||||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||||
Unmatched traffic will bypass the sing-box routes.
|
Unmatched traffic will bypass the sing-box routes.
|
||||||
|
|
||||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||||
|
|
||||||
|
=== "Without `auto_redirect` enabled"
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
|
Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_address`.
|
||||||
|
Unmatched traffic will bypass the sing-box routes.
|
||||||
|
|
||||||
|
Note that it **doesn't work on the Android graphical client** due to
|
||||||
|
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
|
||||||
|
but otherwise it works fine on all command line clients and Apple platforms.
|
||||||
|
|
||||||
#### route_exclude_address_set
|
#### route_exclude_address_set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
=== "With `auto_redirect` enabled"
|
||||||
|
|
||||||
!!! quote ""
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||||
|
|
||||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||||
Matched traffic will bypass the sing-box routes.
|
Matched traffic will bypass the sing-box routes.
|
||||||
|
|
||||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||||
|
|
||||||
|
=== "Without `auto_redirect` enabled"
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
|
Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_exclude_address`.
|
||||||
|
Matched traffic will bypass the sing-box routes.
|
||||||
|
|
||||||
|
Note that it **doesn't work on the Android graphical client** due to
|
||||||
|
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
|
||||||
|
but otherwise it works fine on all command line clients and Apple platforms.
|
||||||
|
|
||||||
#### endpoint_independent_nat
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ icon: material/alert-decagram
|
|||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-delete-alert: [gso](#gso)
|
:material-delete-alert: [gso](#gso)
|
||||||
|
:material-alert-decagram: [route_address_set](#stack)
|
||||||
|
:material-alert-decagram: [route_exclude_address_set](#stack)
|
||||||
|
|
||||||
!!! quote "sing-box 1.10.0 中的更改"
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
@@ -88,13 +90,13 @@ icon: material/alert-decagram
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"include_uid_range": [
|
"include_uid_range": [
|
||||||
"1000-99999"
|
"1000:99999"
|
||||||
],
|
],
|
||||||
"exclude_uid": [
|
"exclude_uid": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"exclude_uid_range": [
|
"exclude_uid_range": [
|
||||||
"1000-99999"
|
"1000:99999"
|
||||||
],
|
],
|
||||||
"include_android_user": [
|
"include_android_user": [
|
||||||
0,
|
0,
|
||||||
@@ -329,29 +331,53 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
#### route_address_set
|
#### route_address_set
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
=== "`auto_redirect` 已启用"
|
||||||
|
|
||||||
!!! quote ""
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||||
|
|
||||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||||
不匹配的流量将绕过 sing-box 路由。
|
不匹配的流量将绕过 sing-box 路由。
|
||||||
|
|
||||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||||
|
|
||||||
|
=== "`auto_redirect` 未启用"
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
|
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_address`。
|
||||||
|
不匹配的流量将绕过 sing-box 路由。
|
||||||
|
|
||||||
|
请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException),
|
||||||
|
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
|
||||||
|
|
||||||
#### route_exclude_address_set
|
#### route_exclude_address_set
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
=== "`auto_redirect` 已启用"
|
||||||
|
|
||||||
!!! quote ""
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||||
|
|
||||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||||
匹配的流量将绕过 sing-box 路由。
|
匹配的流量将绕过 sing-box 路由。
|
||||||
|
|
||||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||||
|
|
||||||
|
=== "`auto_redirect` 未启用"
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
|
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_exclude_address`。
|
||||||
|
匹配的流量将绕过 sing-box 路由。
|
||||||
|
|
||||||
|
请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException),
|
||||||
|
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
|
||||||
|
|
||||||
#### endpoint_independent_nat
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
|
:material-plus: [server_ports](#server_ports)
|
||||||
|
:material-plus: [hop_interval](#hop_interval)
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -7,6 +16,10 @@
|
|||||||
|
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
|
"server_ports": [
|
||||||
|
"2080:3000"
|
||||||
|
],
|
||||||
|
"hop_interval": "",
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down_mbps": 100,
|
"down_mbps": 100,
|
||||||
"obfs": {
|
"obfs": {
|
||||||
@@ -22,6 +35,10 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
You can ignore the JSON Array [] tag when the content is only one item
|
||||||
|
|
||||||
!!! warning "Difference from official Hysteria2"
|
!!! warning "Difference from official Hysteria2"
|
||||||
|
|
||||||
The official Hysteria2 supports an authentication method called **userpass**,
|
The official Hysteria2 supports an authentication method called **userpass**,
|
||||||
@@ -44,6 +61,24 @@ The server address.
|
|||||||
|
|
||||||
The server port.
|
The server port.
|
||||||
|
|
||||||
|
Ignored if `server_ports` is set.
|
||||||
|
|
||||||
|
#### server_ports
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
|
Server port range list.
|
||||||
|
|
||||||
|
Conflicts with `server_port`.
|
||||||
|
|
||||||
|
#### hop_interval
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
|
Port hopping interval.
|
||||||
|
|
||||||
|
`30s` is used by default.
|
||||||
|
|
||||||
#### up_mbps, down_mbps
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
Max bandwidth, in Mbps.
|
Max bandwidth, in Mbps.
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [server_ports](#server_ports)
|
||||||
|
:material-plus: [hop_interval](#hop_interval)
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -7,6 +16,10 @@
|
|||||||
|
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
|
"server_ports": [
|
||||||
|
"2080:3000"
|
||||||
|
],
|
||||||
|
"hop_interval": "",
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down_mbps": 100,
|
"down_mbps": 100,
|
||||||
"obfs": {
|
"obfs": {
|
||||||
@@ -22,6 +35,10 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||||
|
|
||||||
!!! warning "与官方 Hysteria2 的区别"
|
!!! warning "与官方 Hysteria2 的区别"
|
||||||
|
|
||||||
官方程序支持一种名为 **userpass** 的验证方式,
|
官方程序支持一种名为 **userpass** 的验证方式,
|
||||||
@@ -42,6 +59,24 @@
|
|||||||
|
|
||||||
服务器端口。
|
服务器端口。
|
||||||
|
|
||||||
|
如果设置了 `server_ports`,则忽略此项。
|
||||||
|
|
||||||
|
#### server_ports
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
|
服务器端口范围列表。
|
||||||
|
|
||||||
|
与 `server_port` 冲突。
|
||||||
|
|
||||||
|
#### hop_interval
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
|
端口跳跃间隔。
|
||||||
|
|
||||||
|
默认使用 `30s`。
|
||||||
|
|
||||||
#### up_mbps, down_mbps
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
最大带宽。
|
最大带宽。
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsRouter(router adapter.Router) http.Handler {
|
func dnsRouter(router adapter.DNSRouter) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/query", queryDNS(router))
|
r.Get("/query", queryDNS(router))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryDNS(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
|
func queryDNS(router adapter.DNSRouter) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
name := r.URL.Query().Get("name")
|
name := r.URL.Query().Get("name")
|
||||||
qTypeStr := r.URL.Query().Get("type")
|
qTypeStr := r.URL.Query().Get("type")
|
||||||
@@ -39,7 +39,7 @@ func queryDNS(router adapter.Router) func(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
msg := dns.Msg{}
|
msg := dns.Msg{}
|
||||||
msg.SetQuestion(dns.Fqdn(name), qType)
|
msg.SetQuestion(dns.Fqdn(name), qType)
|
||||||
resp, err := router.Exchange(ctx, &msg)
|
resp, err := router.Exchange(ctx, &msg, adapter.DNSQueryOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Status(r, http.StatusInternalServerError)
|
render.Status(r, http.StatusInternalServerError)
|
||||||
render.JSON(w, r, newError(err.Error()))
|
render.JSON(w, r, newError(err.Error()))
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ var _ adapter.ClashServer = (*Server)(nil)
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
|
dnsRouter adapter.DNSRouter
|
||||||
outbound adapter.OutboundManager
|
outbound adapter.OutboundManager
|
||||||
endpoint adapter.EndpointManager
|
endpoint adapter.EndpointManager
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
@@ -64,6 +65,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
|||||||
s := &Server{
|
s := &Server{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
router: service.FromContext[adapter.Router](ctx),
|
router: service.FromContext[adapter.Router](ctx),
|
||||||
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
endpoint: service.FromContext[adapter.EndpointManager](ctx),
|
endpoint: service.FromContext[adapter.EndpointManager](ctx),
|
||||||
logger: logFactory.NewLogger("clash-api"),
|
logger: logFactory.NewLogger("clash-api"),
|
||||||
@@ -121,18 +123,15 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
|||||||
r.Mount("/script", scriptRouter())
|
r.Mount("/script", scriptRouter())
|
||||||
r.Mount("/profile", profileRouter())
|
r.Mount("/profile", profileRouter())
|
||||||
r.Mount("/cache", cacheRouter(ctx))
|
r.Mount("/cache", cacheRouter(ctx))
|
||||||
r.Mount("/dns", dnsRouter(s.router))
|
r.Mount("/dns", dnsRouter(s.dnsRouter))
|
||||||
|
|
||||||
s.setupMetaAPI(r)
|
s.setupMetaAPI(r)
|
||||||
})
|
})
|
||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
||||||
chiRouter.Group(func(r chi.Router) {
|
chiRouter.Group(func(r chi.Router) {
|
||||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(s.externalUI)))
|
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP)
|
||||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.externalUI))))
|
||||||
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fs.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -224,7 +223,7 @@ func (s *Server) SetMode(newMode string) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.router.ClearDNSCache()
|
s.dnsRouter.ClearCache()
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
||||||
if cacheFile != nil {
|
if cacheFile != nil {
|
||||||
err := cacheFile.StoreMode(newMode)
|
err := cacheFile.StoreMode(newMode)
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package deprecated
|
package deprecated
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badversion"
|
"github.com/sagernet/sing-box/common/badversion"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/locale"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
|
||||||
"golang.org/x/mod/semver"
|
"golang.org/x/mod/semver"
|
||||||
@@ -34,15 +37,9 @@ func (n Note) Impending() bool {
|
|||||||
|
|
||||||
func (n Note) Message() string {
|
func (n Note) Message() string {
|
||||||
if n.MigrationLink != "" {
|
if n.MigrationLink != "" {
|
||||||
return F.ToString(
|
return fmt.Sprintf(locale.Current().DeprecatedMessage, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
|
||||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
|
||||||
" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
return F.ToString(
|
return fmt.Sprintf(locale.Current().DeprecatedMessageNoLink, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
|
||||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
|
||||||
" and will be removed in sing-box ", n.ScheduledVersion, ".",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +146,35 @@ var OptionTUNGSO = Note{
|
|||||||
EnvName: "TUN_GSO",
|
EnvName: "TUN_GSO",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var OptionLegacyDNSTransport = Note{
|
||||||
|
Name: "legacy-dns-transport",
|
||||||
|
Description: "legacy DNS transport",
|
||||||
|
DeprecatedVersion: "1.12.0",
|
||||||
|
ScheduledVersion: "1.14.0",
|
||||||
|
EnvName: "LEGACY_DNS_TRANSPORT",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionLegacyDNSFakeIPOptions = Note{
|
||||||
|
Name: "legacy-dns-fakeip-options",
|
||||||
|
Description: "legacy DNS fakeip options",
|
||||||
|
DeprecatedVersion: "1.12.0",
|
||||||
|
ScheduledVersion: "1.14.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionOutboundDNSRuleItem = Note{
|
||||||
|
Name: "outbound-dns-rule-item",
|
||||||
|
Description: "outbound DNS rule item",
|
||||||
|
DeprecatedVersion: "1.12.0",
|
||||||
|
ScheduledVersion: "1.14.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionMissingDomainResolverInDialOptions = Note{
|
||||||
|
Name: "missing-domain-resolver-in-dial-options",
|
||||||
|
Description: "missing domain resolver in dial options",
|
||||||
|
DeprecatedVersion: "1.12.0",
|
||||||
|
ScheduledVersion: "1.14.0",
|
||||||
|
}
|
||||||
|
|
||||||
var Options = []Note{
|
var Options = []Note{
|
||||||
OptionBadMatchSource,
|
OptionBadMatchSource,
|
||||||
OptionGEOIP,
|
OptionGEOIP,
|
||||||
@@ -160,4 +186,8 @@ var Options = []Note{
|
|||||||
OptionWireGuardOutbound,
|
OptionWireGuardOutbound,
|
||||||
OptionWireGuardGSO,
|
OptionWireGuardGSO,
|
||||||
OptionTUNGSO,
|
OptionTUNGSO,
|
||||||
|
OptionLegacyDNSTransport,
|
||||||
|
OptionLegacyDNSFakeIPOptions,
|
||||||
|
OptionOutboundDNSRuleItem,
|
||||||
|
OptionMissingDomainResolverInDialOptions,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@@ -114,7 +113,7 @@ func (c *CommandClient) Connect() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if C.FixAndroidStack {
|
if sFixAndroidStack {
|
||||||
go func() {
|
go func() {
|
||||||
c.handler.Connected()
|
c.handler.Connected()
|
||||||
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ import (
|
|||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -21,6 +24,18 @@ import (
|
|||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func BaseContext(platformInterface PlatformInterface) context.Context {
|
||||||
|
dnsRegistry := include.DNSTransportRegistry()
|
||||||
|
if platformInterface != nil {
|
||||||
|
if localTransport := platformInterface.LocalDNSTransport(); localTransport != nil {
|
||||||
|
dns.RegisterTransport[option.LocalDNSServerOptions](dnsRegistry, C.DNSTypeLocal, func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
return newPlatformTransport(localTransport, tag, options), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry)
|
||||||
|
}
|
||||||
|
|
||||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||||
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
|
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -30,7 +45,7 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckConfig(configContent string) error {
|
func CheckConfig(configContent string) error {
|
||||||
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
|
ctx := BaseContext(nil)
|
||||||
options, err := parseConfig(ctx, configContent)
|
options, err := parseConfig(ctx, configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -66,6 +81,10 @@ func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions op
|
|||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *platformInterfaceStub) UpdateRouteOptions(options *tun.Options, platformInterface option.TunPlatformOptions) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool {
|
func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -131,7 +150,7 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FormatConfig(configContent string) (*StringBox, error) {
|
func FormatConfig(configContent string) (*StringBox, error) {
|
||||||
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()), configContent)
|
options, err := parseConfig(BaseContext(nil), configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"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"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -21,53 +24,32 @@ type LocalDNSTransport interface {
|
|||||||
Exchange(ctx *ExchangeContext, message []byte) error
|
Exchange(ctx *ExchangeContext, message []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterLocalDNSTransport(transport LocalDNSTransport) {
|
var _ adapter.DNSTransport = (*platformTransport)(nil)
|
||||||
if transport == nil {
|
|
||||||
dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) {
|
|
||||||
return dns.NewLocalTransport(options), nil
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) {
|
|
||||||
return &platformLocalDNSTransport{
|
|
||||||
iif: transport,
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dns.Transport = (*platformLocalDNSTransport)(nil)
|
type platformTransport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
type platformLocalDNSTransport struct {
|
|
||||||
iif LocalDNSTransport
|
iif LocalDNSTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Name() string {
|
func newPlatformTransport(iif LocalDNSTransport, tag string, options option.LocalDNSServerOptions) *platformTransport {
|
||||||
return "local"
|
return &platformTransport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||||
|
iif: iif,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Start() error {
|
func (p *platformTransport) Reset() {
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Reset() {
|
func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
}
|
response := &ExchangeContext{
|
||||||
|
context: ctx,
|
||||||
func (p *platformLocalDNSTransport) Close() error {
|
}
|
||||||
return nil
|
if p.iif.Raw() {
|
||||||
}
|
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Raw() bool {
|
|
||||||
return p.iif.Raw()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
messageBytes, err := message.Pack()
|
messageBytes, err := message.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
response := &ExchangeContext{
|
|
||||||
context: ctx,
|
|
||||||
}
|
|
||||||
var responseMessage *mDNS.Msg
|
var responseMessage *mDNS.Msg
|
||||||
var group task.Group
|
var group task.Group
|
||||||
group.Append0(func(ctx context.Context) error {
|
group.Append0(func(ctx context.Context) error {
|
||||||
@@ -86,53 +68,36 @@ func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return responseMessage, nil
|
return responseMessage, nil
|
||||||
}
|
} else {
|
||||||
|
question := message.Question[0]
|
||||||
func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
|
|
||||||
var network string
|
var network string
|
||||||
switch strategy {
|
switch question.Qtype {
|
||||||
case dns.DomainStrategyUseIPv4:
|
case mDNS.TypeA:
|
||||||
network = "ip4"
|
network = "ip4"
|
||||||
case dns.DomainStrategyPreferIPv6:
|
case mDNS.TypeAAAA:
|
||||||
network = "ip6"
|
network = "ip6"
|
||||||
default:
|
default:
|
||||||
network = "ip"
|
return nil, E.New("only IP queries are supported by current version of Android")
|
||||||
}
|
}
|
||||||
response := &ExchangeContext{
|
var responseAddrs []netip.Addr
|
||||||
context: ctx,
|
|
||||||
}
|
|
||||||
var responseAddr []netip.Addr
|
|
||||||
var group task.Group
|
var group task.Group
|
||||||
group.Append0(func(ctx context.Context) error {
|
group.Append0(func(ctx context.Context) error {
|
||||||
err := p.iif.Lookup(response, network, domain)
|
err := p.iif.Lookup(response, network, question.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if response.error != nil {
|
if response.error != nil {
|
||||||
return response.error
|
return response.error
|
||||||
}
|
}
|
||||||
switch strategy {
|
responseAddrs = response.addresses
|
||||||
case dns.DomainStrategyUseIPv4:
|
|
||||||
responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool {
|
|
||||||
return it.Is4()
|
|
||||||
})
|
|
||||||
case dns.DomainStrategyPreferIPv6:
|
|
||||||
responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool {
|
|
||||||
return it.Is6()
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
responseAddr = response.addresses
|
|
||||||
}
|
|
||||||
/*if len(responseAddr) == 0 {
|
|
||||||
response.error = dns.RCodeSuccess
|
|
||||||
}*/
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
err := group.Run(ctx)
|
err := group.Run(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return responseAddr, nil
|
return dns.FixedResponse(message.Id, question, responseAddrs, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Func interface {
|
type Func interface {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -56,7 +55,7 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
|
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
|
||||||
if C.FixAndroidStack {
|
if sFixAndroidStack {
|
||||||
go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
||||||
} else {
|
} else {
|
||||||
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PlatformInterface interface {
|
type PlatformInterface interface {
|
||||||
|
LocalDNSTransport() LocalDNSTransport
|
||||||
UsePlatformAutoDetectInterfaceControl() bool
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
AutoDetectInterfaceControl(fd int32) error
|
AutoDetectInterfaceControl(fd int32) error
|
||||||
OpenTun(options TunOptions) (int32, error)
|
OpenTun(options TunOptions) (int32, error)
|
||||||
|
UpdateRouteOptions(options TunOptions) error
|
||||||
WriteLog(message string)
|
WriteLog(message string)
|
||||||
UseProcFS() bool
|
UseProcFS() bool
|
||||||
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
|
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type Interface interface {
|
|||||||
UsePlatformAutoDetectInterfaceControl() bool
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
AutoDetectInterfaceControl(fd int) error
|
AutoDetectInterfaceControl(fd int) error
|
||||||
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||||
|
UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error
|
||||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||||
Interfaces() ([]adapter.NetworkInterface, error)
|
Interfaces() ([]adapter.NetworkInterface, error)
|
||||||
UnderNetworkExtension() bool
|
UnderNetworkExtension() bool
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
@@ -44,7 +43,7 @@ type BoxService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||||
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
|
ctx := BaseContext(platformInterface)
|
||||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
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)
|
||||||
@@ -81,7 +80,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) Start() error {
|
func (s *BoxService) Start() error {
|
||||||
if C.FixAndroidStack {
|
if sFixAndroidStack {
|
||||||
var err error
|
var err error
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
@@ -148,10 +147,10 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
|
|||||||
|
|
||||||
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||||
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
|
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
|
||||||
return nil, E.New("android: unsupported uid options")
|
return nil, E.New("platform: unsupported uid options")
|
||||||
}
|
}
|
||||||
if len(options.IncludeAndroidUser) > 0 {
|
if len(options.IncludeAndroidUser) > 0 {
|
||||||
return nil, E.New("android: unsupported android_user option")
|
return nil, E.New("platform: unsupported android_user option")
|
||||||
}
|
}
|
||||||
routeRanges, err := options.BuildAutoRouteRanges(true)
|
routeRanges, err := options.BuildAutoRouteRanges(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -174,6 +173,20 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
|
|||||||
return tun.New(*options)
|
return tun.New(*options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error {
|
||||||
|
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
|
||||||
|
return E.New("android: unsupported uid options")
|
||||||
|
}
|
||||||
|
if len(options.IncludeAndroidUser) > 0 {
|
||||||
|
return E.New("android: unsupported android_user option")
|
||||||
|
}
|
||||||
|
routeRanges, err := options.BuildAutoRouteRanges(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.iif.UpdateRouteOptions(&tunOptions{options, routeRanges, platformOptions})
|
||||||
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
|
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
|
||||||
return &platformDefaultInterfaceMonitor{
|
return &platformDefaultInterfaceMonitor{
|
||||||
platformInterfaceWrapper: w,
|
platformInterfaceWrapper: w,
|
||||||
@@ -192,6 +205,9 @@ func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, err
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w.defaultInterfaceAccess.Lock()
|
w.defaultInterfaceAccess.Lock()
|
||||||
|
// (GOOS=windows) SA4006: this value of `isDefault` is never used
|
||||||
|
// Why not used?
|
||||||
|
//nolint:staticcheck
|
||||||
isDefault := w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index
|
isDefault := w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index
|
||||||
w.defaultInterfaceAccess.Unlock()
|
w.defaultInterfaceAccess.Unlock()
|
||||||
interfaces = append(interfaces, adapter.NetworkInterface{
|
interfaces = append(interfaces, adapter.NetworkInterface{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/locale"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,40 +20,56 @@ var (
|
|||||||
sUserID int
|
sUserID int
|
||||||
sGroupID int
|
sGroupID int
|
||||||
sTVOS bool
|
sTVOS bool
|
||||||
|
sFixAndroidStack bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
debug.SetPanicOnFault(true)
|
debug.SetPanicOnFault(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) {
|
type SetupOptions struct {
|
||||||
sBasePath = basePath
|
BasePath string
|
||||||
sWorkingPath = workingPath
|
WorkingPath string
|
||||||
sTempPath = tempPath
|
TempPath string
|
||||||
sUserID = os.Getuid()
|
Username string
|
||||||
sGroupID = os.Getgid()
|
IsTVOS bool
|
||||||
sTVOS = isTVOS
|
FixAndroidStack bool
|
||||||
os.MkdirAll(sWorkingPath, 0o777)
|
|
||||||
os.MkdirAll(sTempPath, 0o777)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupWithUsername(basePath string, workingPath string, tempPath string, username string) error {
|
func Setup(options *SetupOptions) error {
|
||||||
sBasePath = basePath
|
sBasePath = options.BasePath
|
||||||
sWorkingPath = workingPath
|
sWorkingPath = options.WorkingPath
|
||||||
sTempPath = tempPath
|
sTempPath = options.TempPath
|
||||||
sUser, err := user.Lookup(username)
|
if options.Username != "" {
|
||||||
|
sUser, err := user.Lookup(options.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sUserID, _ = strconv.Atoi(sUser.Uid)
|
sUserID, _ = strconv.Atoi(sUser.Uid)
|
||||||
sGroupID, _ = strconv.Atoi(sUser.Gid)
|
sGroupID, _ = strconv.Atoi(sUser.Gid)
|
||||||
|
} else {
|
||||||
|
sUserID = os.Getuid()
|
||||||
|
sGroupID = os.Getgid()
|
||||||
|
}
|
||||||
|
sTVOS = options.IsTVOS
|
||||||
|
|
||||||
|
// TODO: remove after fixed
|
||||||
|
// https://github.com/golang/go/issues/68760
|
||||||
|
sFixAndroidStack = options.FixAndroidStack
|
||||||
|
|
||||||
os.MkdirAll(sWorkingPath, 0o777)
|
os.MkdirAll(sWorkingPath, 0o777)
|
||||||
os.MkdirAll(sTempPath, 0o777)
|
os.MkdirAll(sTempPath, 0o777)
|
||||||
|
if options.Username != "" {
|
||||||
os.Chown(sWorkingPath, sUserID, sGroupID)
|
os.Chown(sWorkingPath, sUserID, sGroupID)
|
||||||
os.Chown(sTempPath, sUserID, sGroupID)
|
os.Chown(sTempPath, sUserID, sGroupID)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetLocale(localeId string) {
|
||||||
|
locale.Set(localeId)
|
||||||
|
}
|
||||||
|
|
||||||
func Version() string {
|
func Version() string {
|
||||||
return C.Version
|
return C.Version
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
type TunOptions interface {
|
type TunOptions interface {
|
||||||
GetInet4Address() RoutePrefixIterator
|
GetInet4Address() RoutePrefixIterator
|
||||||
GetInet6Address() RoutePrefixIterator
|
GetInet6Address() RoutePrefixIterator
|
||||||
GetDNSServerAddress() (string, error)
|
GetDNSServerAddress() (*StringBox, error)
|
||||||
GetMTU() int32
|
GetMTU() int32
|
||||||
GetAutoRoute() bool
|
GetAutoRoute() bool
|
||||||
GetStrictRoute() bool
|
GetStrictRoute() bool
|
||||||
@@ -89,11 +89,11 @@ func (o *tunOptions) GetInet6Address() RoutePrefixIterator {
|
|||||||
return mapRoutePrefix(o.Inet6Address)
|
return mapRoutePrefix(o.Inet6Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *tunOptions) GetDNSServerAddress() (string, error) {
|
func (o *tunOptions) GetDNSServerAddress() (*StringBox, error) {
|
||||||
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
|
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
|
||||||
return "", E.New("need one more IPv4 address for DNS hijacking")
|
return nil, E.New("need one more IPv4 address for DNS hijacking")
|
||||||
}
|
}
|
||||||
return o.Inet4Address[0].Addr().Next().String(), nil
|
return wrapString(o.Inet4Address[0].Addr().Next().String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *tunOptions) GetMTU() int32 {
|
func (o *tunOptions) GetMTU() int32 {
|
||||||
|
|||||||
30
experimental/locale/locale.go
Normal file
30
experimental/locale/locale.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package locale
|
||||||
|
|
||||||
|
var (
|
||||||
|
localeRegistry = make(map[string]*Locale)
|
||||||
|
current = defaultLocal
|
||||||
|
)
|
||||||
|
|
||||||
|
type Locale struct {
|
||||||
|
// deprecated messages for graphical clients
|
||||||
|
DeprecatedMessage string
|
||||||
|
DeprecatedMessageNoLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultLocal = &Locale{
|
||||||
|
DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.",
|
||||||
|
DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func Current() *Locale {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set(localeId string) bool {
|
||||||
|
locale, loaded := localeRegistry[localeId]
|
||||||
|
if !loaded {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
current = locale
|
||||||
|
return true
|
||||||
|
}
|
||||||
10
experimental/locale/locale_zh_CN.go
Normal file
10
experimental/locale/locale_zh_CN.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package locale
|
||||||
|
|
||||||
|
var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么:您的配置文件已过时,且将很快不可用。请联系您的配置提供者以更新配置。"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
localeRegistry["zh_CN"] = &Locale{
|
||||||
|
DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers,
|
||||||
|
DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers,
|
||||||
|
}
|
||||||
|
}
|
||||||
91
go.mod
91
go.mod
@@ -1,6 +1,8 @@
|
|||||||
module github.com/sagernet/sing-box
|
module github.com/sagernet/sing-box
|
||||||
|
|
||||||
go 1.20
|
go 1.23.1
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/caddyserver/certmagic v0.20.0
|
github.com/caddyserver/certmagic v0.20.0
|
||||||
@@ -17,6 +19,7 @@ require (
|
|||||||
github.com/mholt/acmez v1.2.0
|
github.com/mholt/acmez v1.2.0
|
||||||
github.com/miekg/dns v1.1.62
|
github.com/miekg/dns v1.1.62
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0
|
github.com/oschwald/maxminddb-golang v1.12.0
|
||||||
|
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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
@@ -25,16 +28,16 @@ require (
|
|||||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
|
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
|
||||||
github.com/sagernet/quic-go v0.48.2-beta.1
|
github.com/sagernet/quic-go v0.48.2-beta.1
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.6.0-beta.6
|
github.com/sagernet/sing v0.6.0-beta.12
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.1
|
|
||||||
github.com/sagernet/sing-mux v0.3.0-alpha.1
|
github.com/sagernet/sing-mux v0.3.0-alpha.1
|
||||||
github.com/sagernet/sing-quic v0.4.0-alpha.4
|
github.com/sagernet/sing-quic v0.4.0-beta.4
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2
|
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2
|
||||||
github.com/sagernet/sing-tun v0.6.0-beta.2
|
github.com/sagernet/sing-tun v0.6.0-beta.8
|
||||||
github.com/sagernet/sing-vmess v0.2.0-beta.1
|
github.com/sagernet/sing-vmess v0.2.0-beta.2
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||||
|
github.com/sagernet/tailscale v0.0.0-20241203114627-8b68177dbcc1
|
||||||
github.com/sagernet/utls v1.6.7
|
github.com/sagernet/utls v1.6.7
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.5
|
github.com/sagernet/wireguard-go v0.0.1-beta.5
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
@@ -42,59 +45,97 @@ require (
|
|||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
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.29.0
|
golang.org/x/crypto v0.31.0
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||||
golang.org/x/mod v0.20.0
|
golang.org/x/mod v0.20.0
|
||||||
golang.org/x/net v0.31.0
|
golang.org/x/net v0.31.0
|
||||||
golang.org/x/sys v0.27.0
|
golang.org/x/sys v0.28.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/grpc v1.63.2
|
google.golang.org/grpc v1.63.2
|
||||||
google.golang.org/protobuf v1.33.0
|
google.golang.org/protobuf v1.33.0
|
||||||
howett.net/plist v1.0.1
|
howett.net/plist v1.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
//replace github.com/sagernet/sing => ../sing
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
github.com/akutz/memconn v0.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
||||||
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
|
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/coder/websocket v1.8.12 // indirect
|
||||||
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||||
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
|
||||||
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||||
|
github.com/gaissmai/bart v0.11.1 // indirect
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // 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.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/csrf v1.7.2 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||||
|
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||||
|
github.com/illarion/gonotify/v2 v2.0.3 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
||||||
github.com/libdns/libdns v0.2.2 // indirect
|
github.com/libdns/libdns v0.2.2 // indirect
|
||||||
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
github.com/mdlayher/socket v0.4.1 // indirect
|
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/mdlayher/socket v0.5.0 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
github.com/onsi/ginkgo/v2 v2.17.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
|
github.com/safchain/ethtool v0.3.0 // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||||
|
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
|
||||||
|
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
|
||||||
|
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
|
||||||
|
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
|
||||||
|
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
|
||||||
|
github.com/tcnksm/go-httpstat v0.2.0 // indirect
|
||||||
|
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/sync v0.9.0 // indirect
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
||||||
golang.org/x/text v0.20.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
|
golang.org/x/term v0.27.0 // indirect
|
||||||
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
golang.org/x/tools v0.24.0 // indirect
|
golang.org/x/tools v0.24.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
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // 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
|
||||||
)
|
)
|
||||||
|
|||||||
206
go.sum
206
go.sum
@@ -1,59 +1,111 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||||
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||||
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||||
|
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||||
|
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
|
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/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||||
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||||
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
|
||||||
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||||
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||||
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
|
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
|
||||||
|
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
||||||
|
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||||
|
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
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-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||||
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
||||||
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
|
||||||
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||||
|
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
|
||||||
|
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||||
|
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||||
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||||
|
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||||
|
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||||
|
github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A=
|
||||||
|
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||||
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||||
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||||
@@ -63,32 +115,47 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
|||||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
github.com/libdns/libdns v0.2.2/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/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
|
||||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
||||||
|
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||||
|
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
|
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
|
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
|
||||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
||||||
|
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||||
|
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
||||||
|
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||||
|
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||||
|
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4=
|
||||||
|
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU=
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
|
||||||
@@ -110,26 +177,26 @@ github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
|
|||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||||
github.com/sagernet/sing v0.6.0-beta.6 h1:IFnTCG06Z5rLMZJqw1ZmDncDl2N9gsVw0MGvgakrpg8=
|
github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s=
|
||||||
github.com/sagernet/sing v0.6.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
|
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
|
|
||||||
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
|
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
|
||||||
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
|
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
|
||||||
github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ=
|
github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g=
|
||||||
github.com/sagernet/sing-quic v0.4.0-alpha.4/go.mod h1:h5RkKTmUhudJKzK7c87FPXD5w1bJjVyxMN9+opZcctA=
|
github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
|
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
|
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
|
||||||
github.com/sagernet/sing-tun v0.6.0-beta.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO3OlYMcQJ0=
|
github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw=
|
||||||
github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
|
github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs=
|
||||||
github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
|
github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk=
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||||
|
github.com/sagernet/tailscale v0.0.0-20241203114627-8b68177dbcc1 h1:7KzocP8ewushqpf/zsgt3LnSevK5IPNPorb/lfT6RYY=
|
||||||
|
github.com/sagernet/tailscale v0.0.0-20241203114627-8b68177dbcc1/go.mod h1:xIn0nkXVWp45voGMMzAXvgzwsQ+2CGCiTt9LkHONbSE=
|
||||||
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
||||||
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
|
github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
|
||||||
@@ -141,14 +208,36 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k
|
|||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||||
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||||
|
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw=
|
||||||
|
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||||
|
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
|
||||||
|
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||||
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||||
|
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
|
||||||
|
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||||
|
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w=
|
||||||
|
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU=
|
||||||
|
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g=
|
||||||
|
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||||
|
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||||
|
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||||
|
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
|
||||||
|
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
|
||||||
|
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
|
||||||
|
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||||
@@ -156,58 +245,73 @@ github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvv
|
|||||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
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.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/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
|
||||||
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba 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.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
|
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||||
|
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.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.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
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.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.7.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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||||
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
@@ -216,3 +320,5 @@ howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
|||||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||||
|
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||||
|
|||||||
@@ -2,4 +2,11 @@
|
|||||||
|
|
||||||
package include
|
package include
|
||||||
|
|
||||||
import _ "github.com/sagernet/sing-box/transport/dhcp"
|
import (
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/dhcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerDHCPTransport(registry *dns.TransportRegistry) {
|
||||||
|
dhcp.RegisterTransport(registry)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,12 +3,18 @@
|
|||||||
package include
|
package include
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-dns"
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func registerDHCPTransport(registry *dns.TransportRegistry) {
|
||||||
dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) {
|
dns.RegisterTransport[option.DHCPDNSServerOptions](registry, C.DNSTypeDHCP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`)
|
return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user