mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
14 Commits
dev-ndis
...
v1.11.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5c554c67a | ||
|
|
b3111b6837 | ||
|
|
e4a24c91e2 | ||
|
|
10e5b29e6c | ||
|
|
a79ff72c68 | ||
|
|
63345a53ba | ||
|
|
9509fba67a | ||
|
|
42ffa1b3a3 | ||
|
|
5b28c5c53b | ||
|
|
28becd7538 | ||
|
|
a09e00e3bc | ||
|
|
95ebd4f761 | ||
|
|
10b78ac744 | ||
|
|
385d3d55ec |
629
.github/workflows/build.yml
vendored
629
.github/workflows/build.yml
vendored
@@ -1,629 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version name"
|
||||
required: true
|
||||
type: string
|
||||
build:
|
||||
description: "Build type"
|
||||
required: true
|
||||
type: choice
|
||||
default: "All"
|
||||
options:
|
||||
- All
|
||||
- Binary
|
||||
- Android
|
||||
- Apple
|
||||
- app-store
|
||||
- iOS
|
||||
- macOS
|
||||
- tvOS
|
||||
- macOS-standalone
|
||||
- publish-android
|
||||
push:
|
||||
branches:
|
||||
- main-next
|
||||
- dev-next
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
calculate_version:
|
||||
name: Calculate version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.outputs.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
echo "version=${{ inputs.version }}"
|
||||
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||
- name: Calculate version
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/read_tag --nightly
|
||||
- name: Set outputs
|
||||
id: outputs
|
||||
run: |-
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
build:
|
||||
name: Build binary
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: linux_386
|
||||
goos: linux
|
||||
goarch: 386
|
||||
- name: linux_amd64
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
- name: linux_arm64
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
- name: linux_arm
|
||||
goos: linux
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
- name: linux_arm_v7
|
||||
goos: linux
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- name: linux_s390x
|
||||
goos: linux
|
||||
goarch: s390x
|
||||
- name: linux_riscv64
|
||||
goos: linux
|
||||
goarch: riscv64
|
||||
- name: linux_mips64le
|
||||
goos: linux
|
||||
goarch: mips64le
|
||||
- name: windows_amd64
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
require_legacy_go: true
|
||||
- name: windows_386
|
||||
goos: windows
|
||||
goarch: 386
|
||||
require_legacy_go: true
|
||||
- name: windows_arm64
|
||||
goos: windows
|
||||
goarch: arm64
|
||||
- name: darwin_arm64
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
- name: darwin_amd64
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
require_legacy_go: true
|
||||
- name: android_arm64
|
||||
goos: android
|
||||
goarch: arm64
|
||||
- name: android_arm
|
||||
goos: android
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- name: android_amd64
|
||||
goos: android
|
||||
goarch: amd64
|
||||
- name: android_386
|
||||
goos: android
|
||||
goarch: 386
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Cache legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
id: cache-legacy-go
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/go1.20.14
|
||||
key: go120
|
||||
- name: Setup legacy Go
|
||||
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||
run: |-
|
||||
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
||||
tar -xzf go1.20.14.linux-amd64.tar.gz
|
||||
mv go $HOME/go/go1.20.14
|
||||
- name: Setup Android NDK
|
||||
if: matrix.goos == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
local-cache: true
|
||||
- name: Setup Goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
install-only: true
|
||||
- name: Extract signing key
|
||||
run: |-
|
||||
mkdir -p $HOME/.gnupg
|
||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
EOF
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
- name: Set tag
|
||||
run: |-
|
||||
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
|
||||
if: matrix.goos != 'android'
|
||||
run: |-
|
||||
goreleaser release --clean --split
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
GOPATH: ${{ env.HOME }}/go
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
- name: Build Android
|
||||
if: matrix.goos == 'android'
|
||||
run: |-
|
||||
go install -v ./cmd/internal/build
|
||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
|
||||
env:
|
||||
BUILD_GOOS: ${{ matrix.goos }}
|
||||
BUILD_GOARCH: ${{ matrix.goarch }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
- name: Upload artifact
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-${{ matrix.name }}
|
||||
path: 'dist'
|
||||
build_android:
|
||||
name: Build Android
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||
runs-on: ubuntu-latest
|
||||
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 Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
- name: Setup OpenJDK
|
||||
run: |-
|
||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||
- name: Set tag
|
||||
run: |-
|
||||
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
|
||||
run: |-
|
||||
make lib_install
|
||||
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||
make lib_android
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
- name: Checkout main branch
|
||||
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
cd clients/android
|
||||
git checkout main
|
||||
- name: Checkout dev branch
|
||||
if: github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
cd clients/android
|
||||
git checkout dev
|
||||
- name: Gradle cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle
|
||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||
- name: Build release
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci
|
||||
mkdir clients/android/app/libs
|
||||
cp libbox.aar clients/android/app/libs
|
||||
cd clients/android
|
||||
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||
- name: Build debug
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci
|
||||
mkdir clients/android/app/libs
|
||||
cp libbox.aar clients/android/app/libs
|
||||
cd clients/android
|
||||
./gradlew :app:assemblePlayRelease
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||
- name: Prepare release upload
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
mkdir -p dist/release
|
||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
||||
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
|
||||
- name: Prepare debug upload
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
mkdir -p dist/release
|
||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
||||
- name: Upload artifact
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-android-apks
|
||||
path: 'dist'
|
||||
- name: Upload debug apk (arm64-v8a)
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SFA-${{ needs.calculate_version.outputs.version }}-arm64-v8a.apk"
|
||||
path: 'dist/release/*-arm64-v8a.apk'
|
||||
- name: Upload debug apk (universal)
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SFA-${{ needs.calculate_version.outputs.version }}-universal.apk"
|
||||
path: 'dist/release/*-universal.apk'
|
||||
publish_android:
|
||||
name: Publish Android
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
|
||||
runs-on: ubuntu-latest
|
||||
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 Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
- name: Setup OpenJDK
|
||||
run: |-
|
||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||
- name: Set tag
|
||||
run: |-
|
||||
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
|
||||
run: |-
|
||||
make lib_install
|
||||
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||
make lib_android
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
- name: Checkout main branch
|
||||
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
cd clients/android
|
||||
git checkout main
|
||||
- name: Checkout dev branch
|
||||
if: github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
cd clients/android
|
||||
git checkout dev
|
||||
- name: Gradle cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle
|
||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||
- name: Build
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci
|
||||
mkdir clients/android/app/libs
|
||||
cp libbox.aar clients/android/app/libs
|
||||
cd clients/android
|
||||
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
||||
./gradlew :app:publishPlayReleaseBundle
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||
build_apple:
|
||||
name: Build Apple clients
|
||||
runs-on: macos-15
|
||||
needs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: iOS
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
|
||||
platform: ios
|
||||
scheme: SFI
|
||||
destination: 'generic/platform=iOS'
|
||||
archive: build/SFI.xcarchive
|
||||
upload: SFI/Upload.plist
|
||||
- name: macOS
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
|
||||
platform: macos
|
||||
scheme: SFM
|
||||
destination: 'generic/platform=macOS'
|
||||
archive: build/SFM.xcarchive
|
||||
upload: SFI/Upload.plist
|
||||
- name: tvOS
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
|
||||
platform: tvos
|
||||
scheme: SFT
|
||||
destination: 'generic/platform=tvOS'
|
||||
archive: build/SFT.xcarchive
|
||||
upload: SFI/Upload.plist
|
||||
- name: macOS-standalone
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
|
||||
platform: macos
|
||||
scheme: SFM.System
|
||||
destination: 'generic/platform=macOS'
|
||||
archive: build/SFM.System.xcarchive
|
||||
export: SFM.System/Export.plist
|
||||
export_path: build/SFM.System
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: matrix.if
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Setup Go
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
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
|
||||
if: matrix.if
|
||||
run: |-
|
||||
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"
|
||||
- name: Checkout main branch
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
cd clients/apple
|
||||
git checkout main
|
||||
- name: Checkout dev branch
|
||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
cd clients/apple
|
||||
git checkout dev
|
||||
- name: Setup certificates
|
||||
if: matrix.if
|
||||
run: |-
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db
|
||||
echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||
|
||||
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
||||
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
||||
|
||||
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
mkdir -p "$PROFILES_PATH"
|
||||
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
||||
|
||||
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
||||
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
||||
|
||||
xcrun notarytool store-credentials "notarytool-password" \
|
||||
--key $ASC_KEY_PATH \
|
||||
--key-id $ASC_KEY_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:
|
||||
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
|
||||
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||
PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}
|
||||
ASC_KEY: ${{ secrets.ASC_KEY }}
|
||||
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
|
||||
- name: Build library
|
||||
if: matrix.if
|
||||
run: |-
|
||||
make lib_install
|
||||
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||
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
|
||||
if: matrix.if
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_apple_version --ci
|
||||
cd clients/apple
|
||||
xcodebuild archive \
|
||||
-scheme "${{ matrix.scheme }}" \
|
||||
-configuration Release \
|
||||
-destination "${{ matrix.destination }}" \
|
||||
-archivePath "${{ matrix.archive }}" \
|
||||
-allowProvisioningUpdates \
|
||||
-authenticationKeyPath $ASC_KEY_PATH \
|
||||
-authenticationKeyID $ASC_KEY_ID \
|
||||
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||
- name: Upload to App Store Connect
|
||||
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
|
||||
cd clients/apple
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath "${{ matrix.archive }}" \
|
||||
-exportOptionsPlist ${{ matrix.upload }} \
|
||||
-allowProvisioningUpdates \
|
||||
-authenticationKeyPath $ASC_KEY_PATH \
|
||||
-authenticationKeyID $ASC_KEY_ID \
|
||||
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||
- name: Publish to TestFlight
|
||||
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
||||
- name: Build image
|
||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
pushd clients/apple
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath "${{ matrix.archive }}" \
|
||||
-exportOptionsPlist ${{ matrix.export }} \
|
||||
-exportPath "${{ matrix.export_path }}"
|
||||
brew install create-dmg
|
||||
create-dmg \
|
||||
--volname "sing-box" \
|
||||
--volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \
|
||||
--icon "SFM.app" 0 0 \
|
||||
--hide-extension "SFM.app" \
|
||||
--app-drop-link 0 0 \
|
||||
--skip-jenkins \
|
||||
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
||||
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
||||
cd "${{ matrix.archive }}"
|
||||
zip -r SFM.dSYMs.zip dSYMs
|
||||
popd
|
||||
|
||||
mkdir -p dist/release
|
||||
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
|
||||
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
|
||||
- name: Upload image
|
||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-macos-dmg
|
||||
path: 'dist'
|
||||
upload:
|
||||
name: Upload builds
|
||||
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
|
||||
needs:
|
||||
- calculate_version
|
||||
- build
|
||||
- build_android
|
||||
- build_apple
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
install-only: true
|
||||
- name: Cache ghr
|
||||
uses: actions/cache@v4
|
||||
id: cache-ghr
|
||||
with:
|
||||
path: |
|
||||
~/go/bin/ghr
|
||||
key: ghr
|
||||
- name: Setup ghr
|
||||
if: steps.cache-ghr.outputs.cache-hit != 'true'
|
||||
run: |-
|
||||
cd $HOME
|
||||
git clone https://github.com/nekohasekai/ghr ghr
|
||||
cd ghr
|
||||
go install -v .
|
||||
- name: Set tag
|
||||
run: |-
|
||||
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"
|
||||
- name: Download builds
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Merge builds
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||
run: |-
|
||||
goreleaser continue --merge --skip publish
|
||||
mkdir -p dist/release
|
||||
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
- name: Upload builds
|
||||
if: ${{ env.PUBLISHED == 'false' }}
|
||||
run: |-
|
||||
export PATH="$PATH:$HOME/go/bin"
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||
env:
|
||||
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 }}
|
||||
219
.github/workflows/debug.yml
vendored
Normal file
219
.github/workflows/debug.yml
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
name: Debug build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable-next
|
||||
- main-next
|
||||
- dev-next
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/debug.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- stable-next
|
||||
- main-next
|
||||
- dev-next
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Debug build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Run Test
|
||||
run: |
|
||||
go test -v ./...
|
||||
build_go120:
|
||||
name: Debug build (Go 1.20)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.20
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go120-${{ hashFiles('**/go.sum') }}
|
||||
- name: Run Test
|
||||
run: make ci_build_go120
|
||||
build_go121:
|
||||
name: Debug build (Go 1.21)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.21
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go121-${{ hashFiles('**/go.sum') }}
|
||||
- name: Run Test
|
||||
run: make ci_build
|
||||
build_go122:
|
||||
name: Debug build (Go 1.22)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.22
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go122-${{ hashFiles('**/go.sum') }}
|
||||
- name: Run Test
|
||||
run: make ci_build
|
||||
cross:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# windows
|
||||
- name: windows-amd64
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
goamd64: v1
|
||||
- name: windows-amd64-v3
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
goamd64: v3
|
||||
- name: windows-386
|
||||
goos: windows
|
||||
goarch: 386
|
||||
- name: windows-arm64
|
||||
goos: windows
|
||||
goarch: arm64
|
||||
- name: windows-arm32v7
|
||||
goos: windows
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
|
||||
# linux
|
||||
- name: linux-amd64
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
goamd64: v1
|
||||
- name: linux-amd64-v3
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
goamd64: v3
|
||||
- name: linux-386
|
||||
goos: linux
|
||||
goarch: 386
|
||||
- name: linux-arm64
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
- name: linux-armv5
|
||||
goos: linux
|
||||
goarch: arm
|
||||
goarm: 5
|
||||
- name: linux-armv6
|
||||
goos: linux
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
- name: linux-armv7
|
||||
goos: linux
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- name: linux-mips-softfloat
|
||||
goos: linux
|
||||
goarch: mips
|
||||
gomips: softfloat
|
||||
- name: linux-mips-hardfloat
|
||||
goos: linux
|
||||
goarch: mips
|
||||
gomips: hardfloat
|
||||
- name: linux-mipsel-softfloat
|
||||
goos: linux
|
||||
goarch: mipsle
|
||||
gomips: softfloat
|
||||
- name: linux-mipsel-hardfloat
|
||||
goos: linux
|
||||
goarch: mipsle
|
||||
gomips: hardfloat
|
||||
- name: linux-mips64
|
||||
goos: linux
|
||||
goarch: mips64
|
||||
- name: linux-mips64el
|
||||
goos: linux
|
||||
goarch: mips64le
|
||||
- name: linux-s390x
|
||||
goos: linux
|
||||
goarch: s390x
|
||||
# darwin
|
||||
- name: darwin-amd64
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
goamd64: v1
|
||||
- name: darwin-amd64-v3
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
goamd64: v3
|
||||
- name: darwin-arm64
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
# freebsd
|
||||
- name: freebsd-amd64
|
||||
goos: freebsd
|
||||
goarch: amd64
|
||||
goamd64: v1
|
||||
- name: freebsd-amd64-v3
|
||||
goos: freebsd
|
||||
goarch: amd64
|
||||
goamd64: v3
|
||||
- name: freebsd-386
|
||||
goos: freebsd
|
||||
goarch: 386
|
||||
- name: freebsd-arm64
|
||||
goos: freebsd
|
||||
goarch: arm64
|
||||
fail-fast: true
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
GOAMD64: ${{ matrix.goamd64 }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GOMIPS: ${{ matrix.gomips }}
|
||||
CGO_ENABLED: 0
|
||||
TAGS: with_clash_api,with_quic
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.21
|
||||
- name: Build
|
||||
id: build
|
||||
run: make
|
||||
1
.github/workflows/linux.yml
vendored
1
.github/workflows/linux.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
mkdir -p $HOME/.gnupg
|
||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
EOF
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
- name: Publish release
|
||||
|
||||
@@ -22,16 +22,6 @@ linters-settings:
|
||||
|
||||
run:
|
||||
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
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
|
||||
@@ -200,6 +200,4 @@ release:
|
||||
ids:
|
||||
- archive
|
||||
- package
|
||||
skip_upload: true
|
||||
partial:
|
||||
by: target
|
||||
skip_upload: true
|
||||
42
Makefile
42
Makefile
@@ -28,7 +28,7 @@ ci_build:
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
generate_completions:
|
||||
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
||||
go run -v --tags generate,generate_completions $(MAIN)
|
||||
|
||||
install:
|
||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||
@@ -71,7 +71,7 @@ release:
|
||||
dist/*_amd64.pkg.tar.zst \
|
||||
dist/*_arm64.pkg.tar.zst \
|
||||
dist/release
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
||||
rm -r dist/release
|
||||
|
||||
release_repo:
|
||||
@@ -90,20 +90,22 @@ upload_android:
|
||||
mkdir -p dist/release_android
|
||||
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
||||
rm -rf dist/release_android
|
||||
|
||||
release_android: lib_android update_android_version build_android upload_android
|
||||
|
||||
publish_android:
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
|
||||
|
||||
publish_android_appcenter:
|
||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
||||
|
||||
|
||||
# TODO: find why and remove `-destination 'generic/platform=iOS'`
|
||||
# TODO: remove xcode clean when fix control widget fixed
|
||||
build_ios:
|
||||
cd ../sing-box-for-apple && \
|
||||
rm -rf build/SFI.xcarchive && \
|
||||
xcodebuild clean -scheme SFI && \
|
||||
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
||||
|
||||
upload_ios_app_store:
|
||||
@@ -145,13 +147,9 @@ build_macos_dmg:
|
||||
--hide-extension "SFM.app" \
|
||||
--app-drop-link 0 0 \
|
||||
--skip-jenkins \
|
||||
--notarize "notarytool-password" \
|
||||
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
||||
|
||||
notarize_macos_dmg:
|
||||
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
|
||||
--keychain-profile "notarytool-password" \
|
||||
--no-s3-acceleration
|
||||
|
||||
upload_macos_dmg:
|
||||
cd dist/SFM && \
|
||||
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||
@@ -166,7 +164,7 @@ upload_macos_dsyms:
|
||||
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||
|
||||
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||
|
||||
build_tvos:
|
||||
cd ../sing-box-for-apple && \
|
||||
@@ -182,22 +180,10 @@ release_tvos: build_tvos upload_tvos_app_store
|
||||
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_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:
|
||||
@go test -v ./... && \
|
||||
cd test && \
|
||||
@@ -213,14 +199,8 @@ test_stdio:
|
||||
lib_android:
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
|
||||
lib_android_debug:
|
||||
go run ./cmd/internal/build_libbox -target android -debug
|
||||
|
||||
lib_apple:
|
||||
go run ./cmd/internal/build_libbox -target apple
|
||||
|
||||
lib_ios:
|
||||
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
|
||||
lib:
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ConnectionManager interface {
|
||||
Lifecycle
|
||||
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type Endpoint interface {
|
||||
Lifecycle
|
||||
Type() string
|
||||
Tag() string
|
||||
Outbound
|
||||
}
|
||||
|
||||
type EndpointRegistry interface {
|
||||
option.EndpointOptionsRegistry
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
|
||||
}
|
||||
|
||||
type EndpointManager interface {
|
||||
Lifecycle
|
||||
Endpoints() []Endpoint
|
||||
Get(tag string) (Endpoint, bool)
|
||||
Remove(tag string) error
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package endpoint
|
||||
|
||||
import "github.com/sagernet/sing-box/option"
|
||||
|
||||
type Adapter struct {
|
||||
endpointType string
|
||||
endpointTag string
|
||||
network []string
|
||||
dependencies []string
|
||||
}
|
||||
|
||||
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
|
||||
return Adapter{
|
||||
endpointType: endpointType,
|
||||
endpointTag: endpointTag,
|
||||
network: network,
|
||||
dependencies: dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||
var dependencies []string
|
||||
if dialOptions.Detour != "" {
|
||||
dependencies = []string{dialOptions.Detour}
|
||||
}
|
||||
return NewAdapter(endpointType, endpointTag, network, dependencies)
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.endpointType
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.endpointTag
|
||||
}
|
||||
|
||||
func (a *Adapter) Network() []string {
|
||||
return a.network
|
||||
}
|
||||
|
||||
func (a *Adapter) Dependencies() []string {
|
||||
return a.dependencies
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package endpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"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"
|
||||
)
|
||||
|
||||
var _ adapter.EndpointManager = (*Manager)(nil)
|
||||
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.EndpointRegistry
|
||||
access sync.Mutex
|
||||
started bool
|
||||
stage adapter.StartStage
|
||||
endpoints []adapter.Endpoint
|
||||
endpointByTag map[string]adapter.Endpoint
|
||||
}
|
||||
|
||||
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpointByTag: make(map[string]adapter.Endpoint),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started && m.stage >= stage {
|
||||
panic("already started")
|
||||
}
|
||||
m.started = true
|
||||
m.stage = stage
|
||||
if stage == adapter.StartStateStart {
|
||||
// started with outbound manager
|
||||
return nil
|
||||
}
|
||||
for _, endpoint := range m.endpoints {
|
||||
err := adapter.LegacyStart(endpoint, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if !m.started {
|
||||
return nil
|
||||
}
|
||||
m.started = false
|
||||
endpoints := m.endpoints
|
||||
m.endpoints = nil
|
||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||
var err error
|
||||
for _, endpoint := range endpoints {
|
||||
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Endpoints() []adapter.Endpoint {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
return m.endpoints
|
||||
}
|
||||
|
||||
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
endpoint, found := m.endpointByTag[tag]
|
||||
return endpoint, found
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
m.access.Lock()
|
||||
endpoint, found := m.endpointByTag[tag]
|
||||
if !found {
|
||||
m.access.Unlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
delete(m.endpointByTag, tag)
|
||||
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||
return it == endpoint
|
||||
})
|
||||
if index == -1 {
|
||||
panic("invalid endpoint index")
|
||||
}
|
||||
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
|
||||
started := m.started
|
||||
m.access.Unlock()
|
||||
if started {
|
||||
return endpoint.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
||||
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started {
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(endpoint, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
||||
if m.started {
|
||||
err = existsEndpoint.Close()
|
||||
if err != nil {
|
||||
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||
return it == existsEndpoint
|
||||
})
|
||||
if existsIndex == -1 {
|
||||
panic("invalid endpoint index")
|
||||
}
|
||||
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
|
||||
}
|
||||
m.endpoints = append(m.endpoints, endpoint)
|
||||
m.endpointByTag[tag] = endpoint
|
||||
return nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package endpoint
|
||||
|
||||
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 ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
|
||||
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
})
|
||||
}
|
||||
|
||||
var _ adapter.EndpointRegistry = (*Registry)(nil)
|
||||
|
||||
type (
|
||||
optionsConstructorFunc func() any
|
||||
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
access sync.Mutex
|
||||
optionsType map[string]optionsConstructorFunc
|
||||
constructor map[string]constructorFunc
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
optionsType: make(map[string]optionsConstructorFunc),
|
||||
constructor: make(map[string]constructorFunc),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
return optionsConstructor(), true
|
||||
}
|
||||
|
||||
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
constructor, loaded := m.constructor[outboundType]
|
||||
if !loaded {
|
||||
return nil, E.New("outbound type not found: " + outboundType)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, options)
|
||||
}
|
||||
|
||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.optionsType[outboundType] = optionsConstructor
|
||||
m.constructor[outboundType] = constructor
|
||||
}
|
||||
@@ -4,28 +4,28 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-dns"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
type ClashServer interface {
|
||||
LifecycleService
|
||||
ConnectionTracker
|
||||
Service
|
||||
PreStarter
|
||||
Mode() string
|
||||
ModeList() []string
|
||||
HistoryStorage() *urltest.HistoryStorage
|
||||
}
|
||||
|
||||
type V2RayServer interface {
|
||||
LifecycleService
|
||||
StatsService() ConnectionTracker
|
||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
||||
}
|
||||
|
||||
type CacheFile interface {
|
||||
LifecycleService
|
||||
Service
|
||||
PreStarter
|
||||
|
||||
StoreFakeIP() bool
|
||||
FakeIPStorage
|
||||
@@ -94,6 +94,10 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Tracker interface {
|
||||
Leave()
|
||||
}
|
||||
|
||||
type OutboundGroup interface {
|
||||
Outbound
|
||||
Now() string
|
||||
@@ -111,3 +115,13 @@ func OutboundTag(detour Outbound) string {
|
||||
}
|
||||
return detour.Tag()
|
||||
}
|
||||
|
||||
type V2RayServer interface {
|
||||
Service
|
||||
StatsService() V2RayStatsService
|
||||
}
|
||||
|
||||
type V2RayStatsService interface {
|
||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
||||
}
|
||||
|
||||
@@ -46,9 +46,6 @@ type PacketConnectionHandlerEx interface {
|
||||
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
|
||||
// Deprecated: use TCPConnectionHandlerEx instead
|
||||
//
|
||||
//nolint:staticcheck
|
||||
type UpstreamHandlerAdapter interface {
|
||||
N.TCPConnectionHandler
|
||||
N.UDPConnectionHandler
|
||||
|
||||
@@ -3,17 +3,15 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type Inbound interface {
|
||||
Lifecycle
|
||||
Service
|
||||
Type() string
|
||||
Tag() string
|
||||
}
|
||||
@@ -30,15 +28,7 @@ type UDPInjectableInbound interface {
|
||||
|
||||
type InboundRegistry interface {
|
||||
option.InboundOptionsRegistry
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
||||
}
|
||||
|
||||
type InboundManager interface {
|
||||
Lifecycle
|
||||
Inbounds() []Inbound
|
||||
Get(tag string) (Inbound, bool)
|
||||
Remove(tag string) error
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error
|
||||
CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error)
|
||||
}
|
||||
|
||||
type InboundContext struct {
|
||||
@@ -61,21 +51,13 @@ type InboundContext struct {
|
||||
// cache
|
||||
|
||||
// Deprecated: implement in rule action
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
RouteOriginalDestination M.Socksaddr
|
||||
// Deprecated: to be removed
|
||||
//nolint:staticcheck
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
// Deprecated
|
||||
InboundOptions option.InboundOptions
|
||||
UDPDisableDomainUnmapping bool
|
||||
UDPConnect bool
|
||||
UDPTimeout time.Duration
|
||||
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
|
||||
DNSServer string
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"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"
|
||||
)
|
||||
|
||||
var _ adapter.InboundManager = (*Manager)(nil)
|
||||
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.InboundRegistry
|
||||
endpoint adapter.EndpointManager
|
||||
access sync.Mutex
|
||||
started bool
|
||||
stage adapter.StartStage
|
||||
inbounds []adapter.Inbound
|
||||
inboundByTag map[string]adapter.Inbound
|
||||
}
|
||||
|
||||
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpoint: endpoint,
|
||||
inboundByTag: make(map[string]adapter.Inbound),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started && m.stage >= stage {
|
||||
panic("already started")
|
||||
}
|
||||
m.started = true
|
||||
m.stage = stage
|
||||
for _, inbound := range m.inbounds {
|
||||
err := adapter.LegacyStart(inbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if !m.started {
|
||||
return nil
|
||||
}
|
||||
m.started = false
|
||||
inbounds := m.inbounds
|
||||
m.inbounds = nil
|
||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||
var err error
|
||||
for _, inbound := range inbounds {
|
||||
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
err = E.Append(err, inbound.Close(), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Inbounds() []adapter.Inbound {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
return m.inbounds
|
||||
}
|
||||
|
||||
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
|
||||
m.access.Lock()
|
||||
inbound, found := m.inboundByTag[tag]
|
||||
m.access.Unlock()
|
||||
if found {
|
||||
return inbound, true
|
||||
}
|
||||
return m.endpoint.Get(tag)
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
m.access.Lock()
|
||||
inbound, found := m.inboundByTag[tag]
|
||||
if !found {
|
||||
m.access.Unlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
delete(m.inboundByTag, tag)
|
||||
index := common.Index(m.inbounds, func(it adapter.Inbound) bool {
|
||||
return it == inbound
|
||||
})
|
||||
if index == -1 {
|
||||
panic("invalid inbound index")
|
||||
}
|
||||
m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...)
|
||||
started := m.started
|
||||
m.access.Unlock()
|
||||
if started {
|
||||
return inbound.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
||||
inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started {
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(inbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
||||
if m.started {
|
||||
err = existsInbound.Close()
|
||||
if err != nil {
|
||||
return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool {
|
||||
return it == existsInbound
|
||||
})
|
||||
if existsIndex == -1 {
|
||||
panic("invalid inbound index")
|
||||
}
|
||||
m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...)
|
||||
}
|
||||
m.inbounds = append(m.inbounds, inbound)
|
||||
m.inboundByTag[tag] = inbound
|
||||
return nil
|
||||
}
|
||||
@@ -15,12 +15,8 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) {
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,41 +28,41 @@ type (
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
access sync.Mutex
|
||||
optionsType map[string]optionsConstructorFunc
|
||||
constructor map[string]constructorFunc
|
||||
access sync.Mutex
|
||||
optionsType map[string]optionsConstructorFunc
|
||||
constructors map[string]constructorFunc
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
optionsType: make(map[string]optionsConstructorFunc),
|
||||
constructor: make(map[string]constructorFunc),
|
||||
optionsType: make(map[string]optionsConstructorFunc),
|
||||
constructors: make(map[string]constructorFunc),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
optionsConstructor, loaded := r.optionsType[outboundType]
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
return optionsConstructor(), true
|
||||
}
|
||||
|
||||
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
constructor, loaded := m.constructor[outboundType]
|
||||
func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
constructor, loaded := r.constructors[outboundType]
|
||||
if !loaded {
|
||||
return nil, E.New("outbound type not found: " + outboundType)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, options)
|
||||
}
|
||||
|
||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.optionsType[outboundType] = optionsConstructor
|
||||
m.constructor[outboundType] = constructor
|
||||
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
r.optionsType[outboundType] = optionsConstructor
|
||||
r.constructors[outboundType] = constructor
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
type StartStage uint8
|
||||
|
||||
const (
|
||||
StartStateInitialize StartStage = iota
|
||||
StartStateStart
|
||||
StartStatePostStart
|
||||
StartStateStarted
|
||||
)
|
||||
|
||||
var ListStartStages = []StartStage{
|
||||
StartStateInitialize,
|
||||
StartStateStart,
|
||||
StartStatePostStart,
|
||||
StartStateStarted,
|
||||
}
|
||||
|
||||
func (s StartStage) String() string {
|
||||
switch s {
|
||||
case StartStateInitialize:
|
||||
return "initialize"
|
||||
case StartStateStart:
|
||||
return "start"
|
||||
case StartStatePostStart:
|
||||
return "post-start"
|
||||
case StartStateStarted:
|
||||
return "finish-start"
|
||||
default:
|
||||
panic("unknown stage")
|
||||
}
|
||||
}
|
||||
|
||||
type Lifecycle interface {
|
||||
Start(stage StartStage) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type LifecycleService interface {
|
||||
Name() string
|
||||
Lifecycle
|
||||
}
|
||||
|
||||
func Start(stage StartStage, services ...Lifecycle) error {
|
||||
for _, service := range services {
|
||||
err := service.Start(stage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartNamed(stage StartStage, services []LifecycleService) error {
|
||||
for _, service := range services {
|
||||
err := service.Start(stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage.String(), " ", service.Name())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package adapter
|
||||
|
||||
func LegacyStart(starter any, stage StartStage) error {
|
||||
if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {
|
||||
return lifecycle.Start(stage)
|
||||
}
|
||||
switch stage {
|
||||
case StartStateInitialize:
|
||||
if preStarter, isPreStarter := starter.(interface {
|
||||
PreStart() error
|
||||
}); isPreStarter {
|
||||
return preStarter.PreStart()
|
||||
}
|
||||
case StartStateStart:
|
||||
if starter, isStarter := starter.(interface {
|
||||
Start() error
|
||||
}); isStarter {
|
||||
return starter.Start()
|
||||
}
|
||||
case StartStateStarted:
|
||||
if postStarter, isPostStarter := starter.(interface {
|
||||
PostStart() error
|
||||
}); isPostStarter {
|
||||
return postStarter.PostStart()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type lifecycleServiceWrapper struct {
|
||||
Service
|
||||
name string
|
||||
}
|
||||
|
||||
func NewLifecycleService(service Service, name string) LifecycleService {
|
||||
return &lifecycleServiceWrapper{
|
||||
Service: service,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lifecycleServiceWrapper) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||
return LegacyStart(l.Service, stage)
|
||||
}
|
||||
|
||||
func (l *lifecycleServiceWrapper) Close() error {
|
||||
return l.Service.Close()
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
)
|
||||
|
||||
type NetworkManager interface {
|
||||
Lifecycle
|
||||
InterfaceFinder() control.InterfaceFinder
|
||||
UpdateInterfaces() error
|
||||
DefaultNetworkInterface() *NetworkInterface
|
||||
NetworkInterfaces() []NetworkInterface
|
||||
AutoDetectInterface() bool
|
||||
AutoDetectInterfaceFunc() control.Func
|
||||
ProtectFunc() control.Func
|
||||
DefaultOptions() NetworkOptions
|
||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||
AutoRedirectOutputMark() uint32
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
WIFIState() WIFIState
|
||||
ResetNetwork()
|
||||
}
|
||||
|
||||
type NetworkOptions struct {
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
BindInterface string
|
||||
RoutingMark uint32
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
InterfaceUpdated()
|
||||
}
|
||||
|
||||
type WIFIState struct {
|
||||
SSID string
|
||||
BSSID string
|
||||
}
|
||||
|
||||
type NetworkInterface struct {
|
||||
control.Interface
|
||||
Type C.InterfaceType
|
||||
DNSServers []string
|
||||
Expensive bool
|
||||
Constrained bool
|
||||
}
|
||||
@@ -22,12 +22,3 @@ type OutboundRegistry interface {
|
||||
option.OutboundOptionsRegistry
|
||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||
}
|
||||
|
||||
type OutboundManager interface {
|
||||
Lifecycle
|
||||
Outbounds() []Outbound
|
||||
Outbound(tag string) (Outbound, bool)
|
||||
Default() Outbound
|
||||
Remove(tag string) error
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error
|
||||
}
|
||||
|
||||
@@ -5,35 +5,35 @@ import (
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
outboundType string
|
||||
outboundTag string
|
||||
protocol string
|
||||
network []string
|
||||
tag string
|
||||
dependencies []string
|
||||
}
|
||||
|
||||
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
|
||||
func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter {
|
||||
return Adapter{
|
||||
outboundType: outboundType,
|
||||
outboundTag: outboundTag,
|
||||
protocol: protocol,
|
||||
network: network,
|
||||
tag: tag,
|
||||
dependencies: dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||
func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter {
|
||||
var dependencies []string
|
||||
if dialOptions.Detour != "" {
|
||||
dependencies = []string{dialOptions.Detour}
|
||||
}
|
||||
return NewAdapter(outboundType, outboundTag, network, dependencies)
|
||||
return NewAdapter(protocol, network, tag, dependencies)
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.outboundType
|
||||
return a.protocol
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.outboundTag
|
||||
return a.tag
|
||||
}
|
||||
|
||||
func (a *Adapter) Network() []string {
|
||||
|
||||
245
adapter/outbound/default.go
Normal file
245
adapter/outbound/default.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/canceler"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
defer conn.Close()
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var outConn net.Conn
|
||||
var err error
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||
if err != nil {
|
||||
outConn.Close()
|
||||
return err
|
||||
}
|
||||
return CopyEarlyConn(ctx, conn, outConn)
|
||||
}
|
||||
|
||||
func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
|
||||
defer conn.Close()
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var outConn net.Conn
|
||||
var err error
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else if metadata.Destination.IsFqdn() {
|
||||
var destinationAddresses []netip.Addr
|
||||
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, destinationAddresses)
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||
if err != nil {
|
||||
outConn.Close()
|
||||
return err
|
||||
}
|
||||
return CopyEarlyConn(ctx, conn, outConn)
|
||||
}
|
||||
|
||||
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
defer conn.Close()
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var (
|
||||
outPacketConn net.PacketConn
|
||||
outConn net.Conn
|
||||
destinationAddress netip.Addr
|
||||
err error
|
||||
)
|
||||
if metadata.UDPConnect {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
outPacketConn = bufio.NewUnbindPacketConn(outConn)
|
||||
connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr())
|
||||
if connRemoteAddr != metadata.Destination.Addr {
|
||||
destinationAddress = connRemoteAddr
|
||||
}
|
||||
} else {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else {
|
||||
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
}
|
||||
err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn)
|
||||
if err != nil {
|
||||
outPacketConn.Close()
|
||||
return err
|
||||
}
|
||||
if destinationAddress.IsValid() {
|
||||
if metadata.Destination.IsFqdn() {
|
||||
if metadata.UDPDisableDomainUnmapping {
|
||||
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
} else {
|
||||
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
}
|
||||
}
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
}
|
||||
}
|
||||
switch metadata.Protocol {
|
||||
case C.ProtocolSTUN:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout)
|
||||
case C.ProtocolQUIC:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout)
|
||||
case C.ProtocolDNS:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
|
||||
}
|
||||
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
|
||||
}
|
||||
|
||||
func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
|
||||
defer conn.Close()
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var (
|
||||
outPacketConn net.PacketConn
|
||||
outConn net.Conn
|
||||
destinationAddress netip.Addr
|
||||
err error
|
||||
)
|
||||
if metadata.UDPConnect {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else if metadata.Destination.IsFqdn() {
|
||||
var destinationAddresses []netip.Addr
|
||||
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, destinationAddresses)
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr())
|
||||
if connRemoteAddr != metadata.Destination.Addr {
|
||||
destinationAddress = connRemoteAddr
|
||||
}
|
||||
} else {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else if metadata.Destination.IsFqdn() {
|
||||
var destinationAddresses []netip.Addr
|
||||
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses)
|
||||
} else {
|
||||
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
}
|
||||
err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn)
|
||||
if err != nil {
|
||||
outPacketConn.Close()
|
||||
return err
|
||||
}
|
||||
if destinationAddress.IsValid() {
|
||||
if metadata.Destination.IsFqdn() {
|
||||
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
}
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
}
|
||||
}
|
||||
switch metadata.Protocol {
|
||||
case C.ProtocolSTUN:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout)
|
||||
case C.ProtocolQUIC:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout)
|
||||
case C.ProtocolDNS:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
|
||||
}
|
||||
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
|
||||
}
|
||||
|
||||
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
|
||||
if cachedReader, isCached := conn.(N.CachedReader); isCached {
|
||||
payload := cachedReader.ReadCached()
|
||||
if payload != nil && !payload.IsEmpty() {
|
||||
_, err := serverConn.Write(payload.Bytes())
|
||||
payload.Release()
|
||||
if err != nil {
|
||||
serverConn.Close()
|
||||
return err
|
||||
}
|
||||
return bufio.CopyConn(ctx, conn, serverConn)
|
||||
}
|
||||
}
|
||||
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](serverConn); isEarlyConn && earlyConn.NeedHandshake() {
|
||||
payload := buf.NewPacket()
|
||||
err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
|
||||
if err != os.ErrInvalid {
|
||||
if err != nil {
|
||||
payload.Release()
|
||||
serverConn.Close()
|
||||
return err
|
||||
}
|
||||
_, err = payload.ReadOnceFrom(conn)
|
||||
if err != nil && !E.IsTimeout(err) {
|
||||
payload.Release()
|
||||
serverConn.Close()
|
||||
return E.Cause(err, "read payload")
|
||||
}
|
||||
err = conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
payload.Release()
|
||||
serverConn.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = serverConn.Write(payload.Bytes())
|
||||
payload.Release()
|
||||
if err != nil {
|
||||
serverConn.Close()
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
}
|
||||
return bufio.CopyConn(ctx, conn, serverConn)
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
package outbound
|
||||
|
||||
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.OutboundManager = (*Manager)(nil)
|
||||
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.OutboundRegistry
|
||||
endpoint adapter.EndpointManager
|
||||
defaultTag string
|
||||
access sync.Mutex
|
||||
started bool
|
||||
stage adapter.StartStage
|
||||
outbounds []adapter.Outbound
|
||||
outboundByTag map[string]adapter.Outbound
|
||||
dependByTag map[string][]string
|
||||
defaultOutbound adapter.Outbound
|
||||
defaultOutboundFallback adapter.Outbound
|
||||
}
|
||||
|
||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpoint: endpoint,
|
||||
defaultTag: defaultTag,
|
||||
outboundByTag: make(map[string]adapter.Outbound),
|
||||
dependByTag: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
||||
m.defaultOutboundFallback = defaultOutboundFallback
|
||||
}
|
||||
|
||||
func (m *Manager) 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.outbounds
|
||||
m.access.Unlock()
|
||||
if stage == adapter.StartStateStart {
|
||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||
if !loaded {
|
||||
return E.New("default outbound not found: ", m.defaultTag)
|
||||
}
|
||||
m.defaultOutbound = defaultEndpoint
|
||||
}
|
||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||
} else {
|
||||
for _, outbound := range outbounds {
|
||||
err := adapter.LegacyStart(outbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
||||
monitor := taskmonitor.New(m.logger, C.StartTimeout)
|
||||
started := make(map[string]bool)
|
||||
for {
|
||||
canContinue := false
|
||||
startOne:
|
||||
for _, outboundToStart := range outbounds {
|
||||
outboundTag := outboundToStart.Tag()
|
||||
if started[outboundTag] {
|
||||
continue
|
||||
}
|
||||
dependencies := outboundToStart.Dependencies()
|
||||
for _, dependency := range dependencies {
|
||||
if !started[dependency] {
|
||||
continue startOne
|
||||
}
|
||||
}
|
||||
started[outboundTag] = true
|
||||
canContinue = true
|
||||
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
err := starter.Start(adapter.StartStateStart)
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
}
|
||||
} else if starter, isStarter := outboundToStart.(interface {
|
||||
Start() error
|
||||
}); isStarter {
|
||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
err := starter.Start()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(started) == len(outbounds) {
|
||||
break
|
||||
}
|
||||
if canContinue {
|
||||
continue
|
||||
}
|
||||
currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool {
|
||||
return !started[it.Tag()]
|
||||
})
|
||||
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
||||
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
||||
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
||||
return !started[it]
|
||||
})
|
||||
if common.Contains(oTree, problemOutboundTag) {
|
||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
||||
}
|
||||
m.access.Lock()
|
||||
problemOutbound := m.outboundByTag[problemOutboundTag]
|
||||
m.access.Unlock()
|
||||
if problemOutbound == nil {
|
||||
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]")
|
||||
}
|
||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
||||
}
|
||||
return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Close() error {
|
||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||
m.access.Lock()
|
||||
if !m.started {
|
||||
m.access.Unlock()
|
||||
return nil
|
||||
}
|
||||
m.started = false
|
||||
outbounds := m.outbounds
|
||||
m.outbounds = nil
|
||||
m.access.Unlock()
|
||||
var err error
|
||||
for _, outbound := range outbounds {
|
||||
if closer, isCloser := outbound.(io.Closer); isCloser {
|
||||
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
err = E.Append(err, closer.Close(), func(err error) error {
|
||||
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Outbounds() []adapter.Outbound {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
return m.outbounds
|
||||
}
|
||||
|
||||
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
||||
m.access.Lock()
|
||||
outbound, found := m.outboundByTag[tag]
|
||||
m.access.Unlock()
|
||||
if found {
|
||||
return outbound, true
|
||||
}
|
||||
return m.endpoint.Get(tag)
|
||||
}
|
||||
|
||||
func (m *Manager) Default() adapter.Outbound {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.defaultOutbound != nil {
|
||||
return m.defaultOutbound
|
||||
} else {
|
||||
return m.defaultOutboundFallback
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
m.access.Lock()
|
||||
outbound, found := m.outboundByTag[tag]
|
||||
if !found {
|
||||
m.access.Unlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
delete(m.outboundByTag, tag)
|
||||
index := common.Index(m.outbounds, func(it adapter.Outbound) bool {
|
||||
return it == outbound
|
||||
})
|
||||
if index == -1 {
|
||||
panic("invalid inbound index")
|
||||
}
|
||||
m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...)
|
||||
started := m.started
|
||||
if m.defaultOutbound == outbound {
|
||||
if len(m.outbounds) > 0 {
|
||||
m.defaultOutbound = m.outbounds[0]
|
||||
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
|
||||
} else {
|
||||
m.defaultOutbound = nil
|
||||
}
|
||||
}
|
||||
dependBy := m.dependByTag[tag]
|
||||
if len(dependBy) > 0 {
|
||||
return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", "))
|
||||
}
|
||||
dependencies := outbound.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
|
||||
})
|
||||
}
|
||||
}
|
||||
m.access.Unlock()
|
||||
if started {
|
||||
return common.Close(outbound)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error {
|
||||
if tag == "" {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started {
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(outbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
|
||||
if m.started {
|
||||
err = common.Close(existsOutbound)
|
||||
if err != nil {
|
||||
return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool {
|
||||
return it == existsOutbound
|
||||
})
|
||||
if existsIndex == -1 {
|
||||
panic("invalid inbound index")
|
||||
}
|
||||
m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...)
|
||||
}
|
||||
m.outbounds = append(m.outbounds, outbound)
|
||||
m.outboundByTag[tag] = outbound
|
||||
dependencies := outbound.Dependencies()
|
||||
for _, dependency := range dependencies {
|
||||
m.dependByTag[dependency] = append(m.dependByTag[dependency], tag)
|
||||
}
|
||||
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
|
||||
m.defaultOutbound = outbound
|
||||
if m.started {
|
||||
m.logger.Info("updated default outbound to ", outbound.Tag())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -15,12 +15,8 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) {
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
package adapter
|
||||
|
||||
type PreStarter interface {
|
||||
PreStart() error
|
||||
}
|
||||
|
||||
type PostStarter interface {
|
||||
PostStart() error
|
||||
}
|
||||
|
||||
@@ -10,16 +10,26 @@ import (
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
mdns "github.com/miekg/dns"
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
Lifecycle
|
||||
Service
|
||||
PreStarter
|
||||
PostStarter
|
||||
Cleanup() error
|
||||
|
||||
Outbounds() []Outbound
|
||||
Outbound(tag string) (Outbound, bool)
|
||||
DefaultOutbound(network string) (Outbound, error)
|
||||
|
||||
FakeIPStore() FakeIPStore
|
||||
|
||||
@@ -29,23 +39,37 @@ type Router interface {
|
||||
|
||||
GeoIPReader() *geoip.Reader
|
||||
LoadGeosite(code string) (Rule, error)
|
||||
|
||||
RuleSet(tag string) (RuleSet, 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()
|
||||
|
||||
InterfaceFinder() control.InterfaceFinder
|
||||
UpdateInterfaces() error
|
||||
DefaultInterface() string
|
||||
AutoDetectInterface() bool
|
||||
AutoDetectInterfaceFunc() control.Func
|
||||
DefaultMark() uint32
|
||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||
AutoRedirectOutputMark() uint32
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
WIFIState() WIFIState
|
||||
Rules() []Rule
|
||||
|
||||
SetTracker(tracker ConnectionTracker)
|
||||
ClashServer() ClashServer
|
||||
SetClashServer(server ClashServer)
|
||||
|
||||
ResetNetwork()
|
||||
}
|
||||
V2RayServer() V2RayServer
|
||||
SetV2RayServer(server V2RayServer)
|
||||
|
||||
type ConnectionTracker interface {
|
||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
|
||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
|
||||
ResetNetwork() error
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
@@ -60,6 +84,14 @@ type ConnectionRouterEx interface {
|
||||
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
|
||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||
return service.ContextWith(ctx, router)
|
||||
}
|
||||
|
||||
func RouterFromContext(ctx context.Context) Router {
|
||||
return service.FromContext[Router](ctx)
|
||||
}
|
||||
|
||||
type RuleSet interface {
|
||||
Name() string
|
||||
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||
@@ -119,3 +151,12 @@ func (c *HTTPStartContext) Close() {
|
||||
client.CloseIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
InterfaceUpdated()
|
||||
}
|
||||
|
||||
type WIFIState struct {
|
||||
SSID string
|
||||
BSSID string
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ type (
|
||||
)
|
||||
|
||||
// Deprecated
|
||||
//
|
||||
//nolint:staticcheck
|
||||
func NewUpstreamHandler(
|
||||
metadata InboundContext,
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
@@ -36,9 +34,7 @@ func NewUpstreamHandler(
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
//
|
||||
//nolint:staticcheck
|
||||
// Deprecated
|
||||
type myUpstreamHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
@@ -46,7 +42,6 @@ type myUpstreamHandlerWrapper struct {
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -58,7 +53,6 @@ func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.C
|
||||
return w.connectionHandler(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -70,12 +64,11 @@ func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn
|
||||
return w.packetHandler(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
}
|
||||
|
||||
// Deprecated: removed
|
||||
// Deprecated
|
||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
return M.Metadata{
|
||||
Source: metadata.Source,
|
||||
@@ -83,14 +76,14 @@ func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
// Deprecated
|
||||
type myUpstreamContextHandlerWrapper struct {
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
packetHandler PacketConnectionHandlerFunc
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
// Deprecated
|
||||
func NewUpstreamContextHandler(
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
packetHandler PacketConnectionHandlerFunc,
|
||||
@@ -103,7 +96,6 @@ func NewUpstreamContextHandler(
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -115,7 +107,6 @@ func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, con
|
||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -127,7 +118,6 @@ func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Contex
|
||||
return w.packetHandler(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
}
|
||||
@@ -159,15 +149,12 @@ func NewRouteContextHandler(
|
||||
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
//
|
||||
//nolint:staticcheck
|
||||
type routeHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
router ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -179,7 +166,6 @@ func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn,
|
||||
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -191,7 +177,6 @@ func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.Pa
|
||||
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
@@ -204,7 +189,6 @@ type routeContextHandlerWrapper struct {
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -216,7 +200,6 @@ func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net
|
||||
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -228,7 +211,6 @@ func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, co
|
||||
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
|
||||
406
box.go
406
box.go
@@ -9,13 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||
@@ -27,7 +21,6 @@ import (
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/pause"
|
||||
)
|
||||
@@ -35,17 +28,16 @@ import (
|
||||
var _ adapter.Service = (*Box)(nil)
|
||||
|
||||
type Box struct {
|
||||
createdAt time.Time
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
network *route.NetworkManager
|
||||
endpoint *endpoint.Manager
|
||||
inbound *inbound.Manager
|
||||
outbound *outbound.Manager
|
||||
connection *route.ConnectionManager
|
||||
router *route.Router
|
||||
services []adapter.LifecycleService
|
||||
done chan struct{}
|
||||
createdAt time.Time
|
||||
router adapter.Router
|
||||
inbounds []adapter.Inbound
|
||||
outbounds []adapter.Outbound
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
preServices1 map[string]adapter.Service
|
||||
preServices2 map[string]adapter.Service
|
||||
postServices map[string]adapter.Service
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
@@ -54,12 +46,7 @@ type Options struct {
|
||||
PlatformLogWriter log.PlatformWriter
|
||||
}
|
||||
|
||||
func Context(
|
||||
ctx context.Context,
|
||||
inboundRegistry adapter.InboundRegistry,
|
||||
outboundRegistry adapter.OutboundRegistry,
|
||||
endpointRegistry adapter.EndpointRegistry,
|
||||
) context.Context {
|
||||
func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context {
|
||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||
@@ -70,11 +57,6 @@ func Context(
|
||||
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||
}
|
||||
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -84,27 +66,18 @@ func New(options Options) (*Box, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
|
||||
if endpointRegistry == nil {
|
||||
return nil, E.New("missing endpoint registry in context")
|
||||
}
|
||||
if inboundRegistry == nil {
|
||||
return nil, E.New("missing inbound registry in context")
|
||||
}
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
if outboundRegistry == nil {
|
||||
return nil, E.New("missing outbound registry in context")
|
||||
}
|
||||
|
||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||
ctx = pause.WithDefaultManager(ctx)
|
||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||
debugOptions := common.PtrValueOrDefault(experimentalOptions.Debug)
|
||||
applyDebugOptions(debugOptions)
|
||||
ctx = conntrack.ContextWithDefaultTracker(ctx, debugOptions.OOMKiller, uint64(debugOptions.MemoryLimit))
|
||||
|
||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||
var needCacheFile bool
|
||||
var needClashAPI bool
|
||||
var needV2RayAPI bool
|
||||
@@ -133,60 +106,49 @@ func New(options Options) (*Box, error) {
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create log factory")
|
||||
}
|
||||
|
||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||
|
||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||
router, err := route.NewRouter(
|
||||
ctx,
|
||||
logFactory,
|
||||
common.PtrValueOrDefault(options.Route),
|
||||
common.PtrValueOrDefault(options.DNS),
|
||||
common.PtrValueOrDefault(options.NTP),
|
||||
options.Inbounds,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize network manager")
|
||||
return nil, E.Cause(err, "parse route options")
|
||||
}
|
||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||
router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS))
|
||||
if err != nil {
|
||||
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, endpointOptions := range options.Endpoints {
|
||||
var tag string
|
||||
if endpointOptions.Tag != "" {
|
||||
tag = endpointOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = endpointManager.Create(ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
endpointOptions.Type,
|
||||
endpointOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||
//nolint:staticcheck
|
||||
if len(options.LegacyInbounds) > 0 {
|
||||
for _, legacyInbound := range options.LegacyInbounds {
|
||||
options.Inbounds = append(options.Inbounds, option.Inbound{
|
||||
Type: legacyInbound.Type,
|
||||
Tag: legacyInbound.Tag,
|
||||
Options: common.Must1(legacyInbound.RawOptions()),
|
||||
})
|
||||
}
|
||||
}
|
||||
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
||||
//nolint:staticcheck
|
||||
if len(options.LegacyOutbounds) > 0 {
|
||||
for _, legacyOutbound := range options.LegacyOutbounds {
|
||||
options.Outbounds = append(options.Outbounds, option.Outbound{
|
||||
Type: legacyOutbound.Type,
|
||||
Tag: legacyOutbound.Tag,
|
||||
Options: common.Must1(legacyOutbound.RawOptions()),
|
||||
})
|
||||
}
|
||||
}
|
||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
var currentInbound adapter.Inbound
|
||||
var tag string
|
||||
if inboundOptions.Tag != "" {
|
||||
tag = inboundOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = inboundManager.Create(ctx,
|
||||
currentInbound, err = inboundRegistry.CreateInbound(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
@@ -194,10 +156,12 @@ func New(options Options) (*Box, error) {
|
||||
inboundOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||
}
|
||||
inbounds = append(inbounds, currentInbound)
|
||||
}
|
||||
for i, outboundOptions := range options.Outbounds {
|
||||
var currentOutbound adapter.Outbound
|
||||
var tag string
|
||||
if outboundOptions.Tag != "" {
|
||||
tag = outboundOptions.Tag
|
||||
@@ -211,7 +175,7 @@ func New(options Options) (*Box, error) {
|
||||
Outbound: tag,
|
||||
})
|
||||
}
|
||||
err = outboundManager.Create(
|
||||
currentOutbound, err = outboundRegistry.CreateOutbound(
|
||||
outboundCtx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||
@@ -220,80 +184,65 @@ func New(options Options) (*Box, error) {
|
||||
outboundOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||
}
|
||||
outbounds = append(outbounds, currentOutbound)
|
||||
}
|
||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||
defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{})
|
||||
common.Must(cErr)
|
||||
outbounds = append(outbounds, defaultOutbound)
|
||||
return defaultOutbound
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outboundManager.Initialize(common.Must1(
|
||||
direct.NewOutbound(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger("outbound/direct"),
|
||||
"direct",
|
||||
option.DirectOutboundOptions{},
|
||||
),
|
||||
))
|
||||
if platformInterface != nil {
|
||||
err = platformInterface.Initialize(networkManager)
|
||||
err = platformInterface.Initialize(ctx, router)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize platform interface")
|
||||
}
|
||||
}
|
||||
var services []adapter.LifecycleService
|
||||
preServices1 := make(map[string]adapter.Service)
|
||||
preServices2 := make(map[string]adapter.Service)
|
||||
postServices := make(map[string]adapter.Service)
|
||||
if needCacheFile {
|
||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||
services = append(services, cacheFile)
|
||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||
if cacheFile == nil {
|
||||
cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||
}
|
||||
preServices1["cache file"] = cacheFile
|
||||
}
|
||||
if needClashAPI {
|
||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
||||
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)
|
||||
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create clash-server")
|
||||
return nil, E.Cause(err, "create clash api server")
|
||||
}
|
||||
router.SetTracker(clashServer)
|
||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||
services = append(services, clashServer)
|
||||
router.SetClashServer(clashServer)
|
||||
preServices2["clash api"] = clashServer
|
||||
}
|
||||
if needV2RayAPI {
|
||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create v2ray-server")
|
||||
return nil, E.Cause(err, "create v2ray api server")
|
||||
}
|
||||
if v2rayServer.StatsService() != nil {
|
||||
router.SetTracker(v2rayServer.StatsService())
|
||||
services = append(services, v2rayServer)
|
||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||
}
|
||||
}
|
||||
if ntpOptions.Enabled {
|
||||
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create NTP service")
|
||||
}
|
||||
ntpService := ntp.NewService(ntp.Options{
|
||||
Context: ctx,
|
||||
Dialer: ntpDialer,
|
||||
Logger: logFactory.NewLogger("ntp"),
|
||||
Server: ntpOptions.ServerOptions.Build(),
|
||||
Interval: time.Duration(ntpOptions.Interval),
|
||||
WriteToSystem: ntpOptions.WriteToSystem,
|
||||
})
|
||||
timeService.TimeService = ntpService
|
||||
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||
router.SetV2RayServer(v2rayServer)
|
||||
preServices2["v2ray api"] = v2rayServer
|
||||
}
|
||||
return &Box{
|
||||
network: networkManager,
|
||||
endpoint: endpointManager,
|
||||
inbound: inboundManager,
|
||||
outbound: outboundManager,
|
||||
connection: connectionManager,
|
||||
router: router,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
services: services,
|
||||
done: make(chan struct{}),
|
||||
router: router,
|
||||
inbounds: inbounds,
|
||||
outbounds: outbounds,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
preServices1: preServices1,
|
||||
preServices2: preServices2,
|
||||
postServices: postServices,
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -343,19 +292,35 @@ func (s *Box) preStart() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "start logger")
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
||||
for serviceName, service := range s.preServices1 {
|
||||
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
||||
monitor.Start("pre-start ", serviceName)
|
||||
err := preService.PreStart()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "pre-start ", serviceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
for serviceName, service := range s.preServices2 {
|
||||
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
||||
monitor.Start("pre-start ", serviceName)
|
||||
err := preService.PreStart()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "pre-start ", serviceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
err = s.router.PreStart()
|
||||
if err != nil {
|
||||
return E.Cause(err, "pre-start router")
|
||||
}
|
||||
err = s.startOutbounds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return s.router.Start()
|
||||
}
|
||||
|
||||
func (s *Box) start() error {
|
||||
@@ -363,33 +328,64 @@ func (s *Box) start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||
for serviceName, service := range s.preServices1 {
|
||||
err = service.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
}
|
||||
}
|
||||
for serviceName, service := range s.preServices2 {
|
||||
err = service.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
}
|
||||
}
|
||||
for i, in := range s.inbounds {
|
||||
var tag string
|
||||
if in.Tag() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = in.Tag()
|
||||
}
|
||||
err = in.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
err = s.postStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.inbound.Start(adapter.StartStateStart)
|
||||
return s.router.Cleanup()
|
||||
}
|
||||
|
||||
func (s *Box) postStart() error {
|
||||
for serviceName, service := range s.postServices {
|
||||
err := service.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
}
|
||||
}
|
||||
// TODO: reorganize ALL start order
|
||||
for _, out := range s.outbounds {
|
||||
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
|
||||
err := lateOutbound.PostStart()
|
||||
if err != nil {
|
||||
return E.Cause(err, "post-start outbound/", out.Tag())
|
||||
}
|
||||
}
|
||||
}
|
||||
err := s.router.PostStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, in := range s.inbounds {
|
||||
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
|
||||
err = lateInbound.PostStart()
|
||||
if err != nil {
|
||||
return E.Cause(err, "post-start inbound/", in.Tag())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -401,32 +397,58 @@ func (s *Box) Close() error {
|
||||
default:
|
||||
close(s.done)
|
||||
}
|
||||
err := common.Close(
|
||||
s.inbound, s.outbound, s.router, s.connection, s.network,
|
||||
)
|
||||
for _, lifecycleService := range s.services {
|
||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", lifecycleService.Name())
|
||||
monitor := taskmonitor.New(s.logger, C.StopTimeout)
|
||||
var errors error
|
||||
for serviceName, service := range s.postServices {
|
||||
monitor.Start("close ", serviceName)
|
||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", serviceName)
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
for i, in := range s.inbounds {
|
||||
monitor.Start("close inbound/", in.Type(), "[", i, "]")
|
||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
for i, out := range s.outbounds {
|
||||
monitor.Start("close outbound/", out.Type(), "[", i, "]")
|
||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
monitor.Start("close router")
|
||||
if err := common.Close(s.router); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close router")
|
||||
})
|
||||
}
|
||||
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
||||
return E.Cause(err, "close logger")
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Box) Network() adapter.NetworkManager {
|
||||
return s.network
|
||||
monitor.Finish()
|
||||
for serviceName, service := range s.preServices1 {
|
||||
monitor.Start("close ", serviceName)
|
||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", serviceName)
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
for serviceName, service := range s.preServices2 {
|
||||
monitor.Start("close ", serviceName)
|
||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", serviceName)
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
if err := common.Close(s.logFactory); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close logger")
|
||||
})
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
func (s *Box) Router() adapter.Router {
|
||||
return s.router
|
||||
}
|
||||
|
||||
func (s *Box) Inbound() adapter.InboundManager {
|
||||
return s.inbound
|
||||
}
|
||||
|
||||
func (s *Box) Outbound() adapter.OutboundManager {
|
||||
return s.outbound
|
||||
}
|
||||
|
||||
85
box_outbound.go
Normal file
85
box_outbound.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package box
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
func (s *Box) startOutbounds() error {
|
||||
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
||||
outboundTags := make(map[adapter.Outbound]string)
|
||||
outbounds := make(map[string]adapter.Outbound)
|
||||
for i, outboundToStart := range s.outbounds {
|
||||
var outboundTag string
|
||||
if outboundToStart.Tag() == "" {
|
||||
outboundTag = F.ToString(i)
|
||||
} else {
|
||||
outboundTag = outboundToStart.Tag()
|
||||
}
|
||||
if _, exists := outbounds[outboundTag]; exists {
|
||||
return E.New("outbound tag ", outboundTag, " duplicated")
|
||||
}
|
||||
outboundTags[outboundToStart] = outboundTag
|
||||
outbounds[outboundTag] = outboundToStart
|
||||
}
|
||||
started := make(map[string]bool)
|
||||
for {
|
||||
canContinue := false
|
||||
startOne:
|
||||
for _, outboundToStart := range s.outbounds {
|
||||
outboundTag := outboundTags[outboundToStart]
|
||||
if started[outboundTag] {
|
||||
continue
|
||||
}
|
||||
dependencies := outboundToStart.Dependencies()
|
||||
for _, dependency := range dependencies {
|
||||
if !started[dependency] {
|
||||
continue startOne
|
||||
}
|
||||
}
|
||||
started[outboundTag] = true
|
||||
canContinue = true
|
||||
if starter, isStarter := outboundToStart.(interface {
|
||||
Start() error
|
||||
}); isStarter {
|
||||
monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
err := starter.Start()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(started) == len(s.outbounds) {
|
||||
break
|
||||
}
|
||||
if canContinue {
|
||||
continue
|
||||
}
|
||||
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
|
||||
return !started[outboundTags[it]]
|
||||
})
|
||||
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
||||
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
||||
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
||||
return !started[it]
|
||||
})
|
||||
if common.Contains(oTree, problemOutboundTag) {
|
||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
||||
}
|
||||
problemOutbound := outbounds[problemOutboundTag]
|
||||
if problemOutbound == nil {
|
||||
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
|
||||
}
|
||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
||||
}
|
||||
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Submodule clients/android updated: e1049099a0...45a1f5f0aa
Submodule clients/apple updated: 3d889ae017...c7d9b49de7
@@ -1,445 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -10,21 +10,17 @@ import (
|
||||
_ "github.com/sagernet/gomobile"
|
||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
)
|
||||
|
||||
var (
|
||||
debugEnabled bool
|
||||
target string
|
||||
platform string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||
flag.StringVar(&target, "target", "android", "target platform")
|
||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -35,8 +31,8 @@ func main() {
|
||||
switch target {
|
||||
case "android":
|
||||
buildAndroid()
|
||||
case "apple":
|
||||
buildApple()
|
||||
case "ios":
|
||||
buildiOS()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,35 +62,9 @@ func init() {
|
||||
func buildAndroid() {
|
||||
build_shared.FindSDK()
|
||||
|
||||
var javaPath string
|
||||
javaHome := os.Getenv("JAVA_HOME")
|
||||
if javaHome == "" {
|
||||
javaPath = "java"
|
||||
} else {
|
||||
javaPath = filepath.Join(javaHome, "bin", "java")
|
||||
}
|
||||
|
||||
javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput()
|
||||
if err != nil {
|
||||
log.Fatal(E.Cause(err, "check java version"))
|
||||
}
|
||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||
log.Fatal("java version should be openjdk 17")
|
||||
}
|
||||
|
||||
var bindTarget string
|
||||
if platform != "" {
|
||||
bindTarget = platform
|
||||
} else if debugEnabled {
|
||||
bindTarget = "android/arm64"
|
||||
} else {
|
||||
bindTarget = "android"
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"bind",
|
||||
"-v",
|
||||
"-target", bindTarget,
|
||||
"-androidapi", "21",
|
||||
"-javapkg=io.nekohasekai",
|
||||
"-libname=box",
|
||||
@@ -116,7 +86,7 @@ func buildAndroid() {
|
||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
err = command.Run()
|
||||
err := command.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -133,20 +103,11 @@ func buildAndroid() {
|
||||
}
|
||||
}
|
||||
|
||||
func buildApple() {
|
||||
var bindTarget string
|
||||
if platform != "" {
|
||||
bindTarget = platform
|
||||
} else if debugEnabled {
|
||||
bindTarget = "ios"
|
||||
} else {
|
||||
bindTarget = "ios,tvos,macos"
|
||||
}
|
||||
|
||||
func buildiOS() {
|
||||
args := []string{
|
||||
"bind",
|
||||
"-v",
|
||||
"-target", bindTarget,
|
||||
"-target", "ios,iossimulator,tvos,tvossimulator,macos",
|
||||
"-libname=box",
|
||||
}
|
||||
if !debugEnabled {
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -40,6 +42,14 @@ func FindSDK() {
|
||||
log.Fatal("android NDK not found")
|
||||
}
|
||||
|
||||
javaVersion, err := shell.Exec("java", "--version").ReadOutput()
|
||||
if err != nil {
|
||||
log.Fatal(E.Cause(err, "check java version"))
|
||||
}
|
||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||
log.Fatal("java version should be openjdk 17")
|
||||
}
|
||||
|
||||
os.Setenv("ANDROID_HOME", androidSDKPath)
|
||||
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
||||
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
||||
@@ -48,16 +58,12 @@ func FindSDK() {
|
||||
}
|
||||
|
||||
func findNDK() bool {
|
||||
const fixedVersion = "28.0.12674087"
|
||||
const fixedVersion = "26.3.11579264"
|
||||
const versionFile = "source.properties"
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||
androidNDKPath = fixedPath
|
||||
return true
|
||||
}
|
||||
if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) {
|
||||
androidNDKPath = ndkHomeEnv
|
||||
return true
|
||||
}
|
||||
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
|
||||
if err != nil {
|
||||
return false
|
||||
|
||||
@@ -20,11 +20,6 @@ func ReadTag() (string, error) {
|
||||
return version.String() + "-" + shortCommit, nil
|
||||
}
|
||||
|
||||
func ReadTagVersionRev() (badversion.Version, error) {
|
||||
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||
return badversion.Parse(currentTagRev[1:]), nil
|
||||
}
|
||||
|
||||
func ReadTagVersion() (badversion.Version, error) {
|
||||
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
|
||||
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||
|
||||
@@ -1,62 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
var nightly bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if nightly {
|
||||
version, err := build_shared.ReadTagVersionRev()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var versionStr string
|
||||
if version.PreReleaseIdentifier != "" {
|
||||
versionStr = version.VersionString() + "-nightly"
|
||||
} else {
|
||||
version.Patch++
|
||||
versionStr = version.VersionString() + "-nightly"
|
||||
}
|
||||
err = setGitHubEnv("version", versionStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
currentTag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
_, err = os.Stdout.WriteString("unknown\n")
|
||||
} else {
|
||||
tag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
os.Stdout.WriteString("unknown\n")
|
||||
} else {
|
||||
os.Stdout.WriteString(tag + "\n")
|
||||
}
|
||||
_, err = os.Stdout.WriteString(currentTag + "\n")
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = outputFile.WriteString(name + "=" + value + "\n")
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
return err
|
||||
}
|
||||
err = outputFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stderr.WriteString(name + "=" + value + "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -13,22 +12,9 @@ import (
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
var flagRunInCI bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
newVersion := common.Must1(build_shared.ReadTag())
|
||||
var androidPath string
|
||||
if flagRunInCI {
|
||||
androidPath = "clients/android"
|
||||
} else {
|
||||
androidPath = "../sing-box-for-android"
|
||||
}
|
||||
androidPath, err := filepath.Abs(androidPath)
|
||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
||||
androidPath, err := filepath.Abs("../sing-box-for-android")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -45,10 +31,10 @@ func main() {
|
||||
for _, propPair := range propsList {
|
||||
switch propPair[0] {
|
||||
case "VERSION_NAME":
|
||||
if propPair[1] != newVersion {
|
||||
if propPair[1] != newVersion.String() {
|
||||
versionUpdated = true
|
||||
propPair[1] = newVersion
|
||||
log.Info("updated version to ", newVersion)
|
||||
propPair[1] = newVersion.String()
|
||||
log.Info("updated version to ", newVersion.String())
|
||||
}
|
||||
case "GO_VERSION":
|
||||
if propPair[1] != runtime.Version() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -14,22 +13,9 @@ import (
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
var flagRunInCI bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
||||
var applePath string
|
||||
if flagRunInCI {
|
||||
applePath = "clients/apple"
|
||||
} else {
|
||||
applePath = "../sing-box-for-apple"
|
||||
}
|
||||
applePath, err := filepath.Abs(applePath)
|
||||
applePath, err := filepath.Abs("../sing-box-for-apple")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
||||
configPaths = append(configPaths, "config.json")
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
var commandMerge = &cobra.Command{
|
||||
Use: "merge <output-path>",
|
||||
Use: "merge <output>",
|
||||
Short: "Merge configurations",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := merge(args[0])
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
@@ -55,6 +56,10 @@ func compileRuleSet(sourcePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleSet, err := plainRuleSet.Upgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var outputPath string
|
||||
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
||||
if strings.HasSuffix(sourcePath, ".json") {
|
||||
@@ -69,7 +74,7 @@ func compileRuleSet(sourcePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
||||
err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2)
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
os.Remove(outputPath)
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -78,7 +77,7 @@ func convertRuleSet(sourcePath string) error {
|
||||
return err
|
||||
}
|
||||
defer outputFile.Close()
|
||||
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2)
|
||||
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, true)
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
os.Remove(outputPath)
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -46,10 +48,14 @@ func decompileRuleSet(sourcePath string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ruleSet, err := srs.Read(reader, true)
|
||||
plainRuleSet, err := srs.Read(reader, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleSet := option.PlainRuleSetCompat{
|
||||
Version: C.RuleSetVersion1,
|
||||
Options: plainRuleSet,
|
||||
}
|
||||
var outputPath string
|
||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||
if strings.HasSuffix(sourcePath, ".srs") {
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
@@ -56,25 +55,26 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
var ruleSet option.PlainRuleSetCompat
|
||||
var plainRuleSet option.PlainRuleSet
|
||||
switch flagRuleSetMatchFormat {
|
||||
case C.RuleSetFormatSource:
|
||||
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||
var compat option.PlainRuleSetCompat
|
||||
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plainRuleSet, err = compat.Upgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case C.RuleSetFormatBinary:
|
||||
ruleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
||||
}
|
||||
plainRuleSet, err := ruleSet.Upgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipAddress := M.ParseAddr(domain)
|
||||
var metadata adapter.InboundContext
|
||||
if ipAddress.IsValid() {
|
||||
@@ -84,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
||||
}
|
||||
for i, ruleOptions := range plainRuleSet.Rules {
|
||||
var currentRule adapter.HeadlessRule
|
||||
currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions)
|
||||
currentRule, err = rule.NewHeadlessRule(nil, ruleOptions)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||
}
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func createPreStartedClient() (*box.Box, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
|
||||
instance, err := box.New(box.Options{Options: options})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create service")
|
||||
}
|
||||
@@ -41,11 +41,11 @@ func createPreStartedClient() (*box.Box, error) {
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
|
||||
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
||||
if outboundTag == "" {
|
||||
return instance.Outbound().Default(), nil
|
||||
return instance.Router().DefaultOutbound(N.NetworkName(network))
|
||||
} else {
|
||||
outbound, loaded := instance.Outbound().Outbound(outboundTag)
|
||||
outbound, loaded := instance.Router().Outbound(outboundTag)
|
||||
if !loaded {
|
||||
return nil, E.New("outbound not found: ", outboundTag)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func connect(address string) error {
|
||||
return err
|
||||
}
|
||||
defer instance.Close()
|
||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func fetch(args []string) error {
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ import (
|
||||
)
|
||||
|
||||
func initializeHTTP3Client(instance *box.Box) error {
|
||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http3Client = &http.Client{
|
||||
Transport: &http3.Transport{
|
||||
Transport: &http3.RoundTripper{
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
destination := M.ParseSocksaddr(addr)
|
||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
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/common/ntp"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -44,7 +45,7 @@ func syncTime() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package adguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -27,7 +26,7 @@ example.arpa
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"example.org",
|
||||
@@ -86,7 +85,7 @@ func TestHosts(t *testing.T) {
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"google.com",
|
||||
@@ -116,7 +115,7 @@ www.example.org
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"example.com",
|
||||
|
||||
54
common/conntrack/conn.go
Normal file
54
common/conntrack/conn.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn) (net.Conn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
if KillerEnabled {
|
||||
err := KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
element: element,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
if c.element.Value != nil {
|
||||
connAccess.Lock()
|
||||
if c.element.Value != nil {
|
||||
openConnection.Remove(c.element)
|
||||
c.element.Value = nil
|
||||
}
|
||||
connAccess.Unlock()
|
||||
}
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func (c *Conn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func ContextWithDefaultTracker(ctx context.Context, killerEnabled bool, memoryLimit uint64) context.Context {
|
||||
if service.FromContext[Tracker](ctx) != nil {
|
||||
return ctx
|
||||
}
|
||||
return service.ContextWith[Tracker](ctx, NewDefaultTracker(killerEnabled, memoryLimit))
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
runtimeDebug "runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
var _ Tracker = (*DefaultTracker)(nil)
|
||||
|
||||
type DefaultTracker struct {
|
||||
connAccess sync.RWMutex
|
||||
connList list.List[net.Conn]
|
||||
connAddress map[netip.AddrPort]netip.AddrPort
|
||||
|
||||
packetConnAccess sync.RWMutex
|
||||
packetConnList list.List[AbstractPacketConn]
|
||||
packetConnAddress map[netip.AddrPort]bool
|
||||
|
||||
pendingAccess sync.RWMutex
|
||||
pendingList list.List[netip.AddrPort]
|
||||
|
||||
killerEnabled bool
|
||||
memoryLimit uint64
|
||||
killerLastCheck time.Time
|
||||
}
|
||||
|
||||
func NewDefaultTracker(killerEnabled bool, memoryLimit uint64) *DefaultTracker {
|
||||
return &DefaultTracker{
|
||||
connAddress: make(map[netip.AddrPort]netip.AddrPort),
|
||||
packetConnAddress: make(map[netip.AddrPort]bool),
|
||||
killerEnabled: killerEnabled,
|
||||
memoryLimit: memoryLimit,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) NewConn(conn net.Conn) (net.Conn, error) {
|
||||
err := t.KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
t.connAccess.Lock()
|
||||
element := t.connList.PushBack(conn)
|
||||
t.connAddress[M.AddrPortFromNet(conn.LocalAddr())] = M.AddrPortFromNet(conn.RemoteAddr())
|
||||
t.connAccess.Unlock()
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
closeFunc: common.OnceFunc(func() {
|
||||
t.removeConn(element)
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) NewConnEx(conn net.Conn) (N.CloseHandlerFunc, error) {
|
||||
err := t.KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
t.connAccess.Lock()
|
||||
element := t.connList.PushBack(conn)
|
||||
t.connAddress[M.AddrPortFromNet(conn.LocalAddr())] = M.AddrPortFromNet(conn.RemoteAddr())
|
||||
t.connAccess.Unlock()
|
||||
return N.OnceClose(func(it error) {
|
||||
t.removeConn(element)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
||||
err := t.KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
t.packetConnAccess.Lock()
|
||||
element := t.packetConnList.PushBack(conn)
|
||||
t.packetConnAddress[M.AddrPortFromNet(conn.LocalAddr())] = true
|
||||
t.packetConnAccess.Unlock()
|
||||
return &PacketConn{
|
||||
PacketConn: conn,
|
||||
closeFunc: common.OnceFunc(func() {
|
||||
t.removePacketConn(element)
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) NewPacketConnEx(conn AbstractPacketConn) (N.CloseHandlerFunc, error) {
|
||||
err := t.KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
t.packetConnAccess.Lock()
|
||||
element := t.packetConnList.PushBack(conn)
|
||||
t.packetConnAddress[M.AddrPortFromNet(conn.LocalAddr())] = true
|
||||
t.packetConnAccess.Unlock()
|
||||
return N.OnceClose(func(it error) {
|
||||
t.removePacketConn(element)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) CheckConn(source netip.AddrPort, destination netip.AddrPort) bool {
|
||||
t.connAccess.RLock()
|
||||
defer t.connAccess.RUnlock()
|
||||
return t.connAddress[source] == destination
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) CheckPacketConn(source netip.AddrPort) bool {
|
||||
t.packetConnAccess.RLock()
|
||||
defer t.packetConnAccess.RUnlock()
|
||||
return t.packetConnAddress[source]
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) AddPendingDestination(destination netip.AddrPort) func() {
|
||||
t.pendingAccess.Lock()
|
||||
defer t.pendingAccess.Unlock()
|
||||
element := t.pendingList.PushBack(destination)
|
||||
return func() {
|
||||
t.pendingAccess.Lock()
|
||||
defer t.pendingAccess.Unlock()
|
||||
t.pendingList.Remove(element)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) CheckDestination(destination netip.AddrPort) bool {
|
||||
t.pendingAccess.RLock()
|
||||
defer t.pendingAccess.RUnlock()
|
||||
for element := t.pendingList.Front(); element != nil; element = element.Next() {
|
||||
if element.Value == destination {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) KillerCheck() error {
|
||||
if !t.killerEnabled {
|
||||
return nil
|
||||
}
|
||||
nowTime := time.Now()
|
||||
if nowTime.Sub(t.killerLastCheck) < 3*time.Second {
|
||||
return nil
|
||||
}
|
||||
t.killerLastCheck = nowTime
|
||||
if memory.Total() > t.memoryLimit {
|
||||
t.Close()
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
runtimeDebug.FreeOSMemory()
|
||||
}()
|
||||
return E.New("out of memory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) Count() int {
|
||||
t.connAccess.RLock()
|
||||
defer t.connAccess.RUnlock()
|
||||
t.packetConnAccess.RLock()
|
||||
defer t.packetConnAccess.RUnlock()
|
||||
return t.connList.Len() + t.packetConnList.Len()
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) Close() {
|
||||
t.connAccess.Lock()
|
||||
for element := t.connList.Front(); element != nil; element = element.Next() {
|
||||
element.Value.Close()
|
||||
}
|
||||
t.connList.Init()
|
||||
t.connAccess.Unlock()
|
||||
t.packetConnAccess.Lock()
|
||||
for element := t.packetConnList.Front(); element != nil; element = element.Next() {
|
||||
element.Value.Close()
|
||||
}
|
||||
t.packetConnList.Init()
|
||||
t.packetConnAccess.Unlock()
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) removeConn(element *list.Element[net.Conn]) {
|
||||
t.connAccess.Lock()
|
||||
defer t.connAccess.Unlock()
|
||||
delete(t.connAddress, M.AddrPortFromNet(element.Value.LocalAddr()))
|
||||
t.connList.Remove(element)
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) removePacketConn(element *list.Element[AbstractPacketConn]) {
|
||||
t.packetConnAccess.Lock()
|
||||
defer t.packetConnAccess.Unlock()
|
||||
delete(t.packetConnAddress, M.AddrPortFromNet(element.Value.LocalAddr()))
|
||||
t.packetConnList.Remove(element)
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
closeFunc func()
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
c.closeFunc()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func (c *Conn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
closeFunc func()
|
||||
}
|
||||
|
||||
func (c *PacketConn) Close() error {
|
||||
c.closeFunc()
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return c.PacketConn
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
35
common/conntrack/killer.go
Normal file
35
common/conntrack/killer.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
runtimeDebug "runtime/debug"
|
||||
"time"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
var (
|
||||
KillerEnabled bool
|
||||
MemoryLimit uint64
|
||||
killerLastCheck time.Time
|
||||
)
|
||||
|
||||
func KillerCheck() error {
|
||||
if !KillerEnabled {
|
||||
return nil
|
||||
}
|
||||
nowTime := time.Now()
|
||||
if nowTime.Sub(killerLastCheck) < 3*time.Second {
|
||||
return nil
|
||||
}
|
||||
killerLastCheck = nowTime
|
||||
if memory.Total() > MemoryLimit {
|
||||
Close()
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
runtimeDebug.FreeOSMemory()
|
||||
}()
|
||||
return E.New("out of memory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
55
common/conntrack/packet_conn.go
Normal file
55
common/conntrack/packet_conn.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
if KillerEnabled {
|
||||
err := KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &PacketConn{
|
||||
PacketConn: conn,
|
||||
element: element,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *PacketConn) Close() error {
|
||||
if c.element.Value != nil {
|
||||
connAccess.Lock()
|
||||
if c.element.Value != nil {
|
||||
openConnection.Remove(c.element)
|
||||
c.element.Value = nil
|
||||
}
|
||||
connAccess.Unlock()
|
||||
}
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return bufio.NewPacketConn(c.PacketConn)
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
47
common/conntrack/track.go
Normal file
47
common/conntrack/track.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
var (
|
||||
connAccess sync.RWMutex
|
||||
openConnection list.List[io.Closer]
|
||||
)
|
||||
|
||||
func Count() int {
|
||||
if !Enabled {
|
||||
return 0
|
||||
}
|
||||
return openConnection.Len()
|
||||
}
|
||||
|
||||
func List() []io.Closer {
|
||||
if !Enabled {
|
||||
return nil
|
||||
}
|
||||
connAccess.RLock()
|
||||
defer connAccess.RUnlock()
|
||||
connList := make([]io.Closer, 0, openConnection.Len())
|
||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||
connList = append(connList, element.Value)
|
||||
}
|
||||
return connList
|
||||
}
|
||||
|
||||
func Close() {
|
||||
if !Enabled {
|
||||
return
|
||||
}
|
||||
connAccess.Lock()
|
||||
defer connAccess.Unlock()
|
||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||
common.Close(element.Value)
|
||||
element.Value = nil
|
||||
}
|
||||
openConnection.Init()
|
||||
}
|
||||
5
common/conntrack/track_disable.go
Normal file
5
common/conntrack/track_disable.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build !with_conntrack
|
||||
|
||||
package conntrack
|
||||
|
||||
const Enabled = false
|
||||
5
common/conntrack/track_enable.go
Normal file
5
common/conntrack/track_enable.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build with_conntrack
|
||||
|
||||
package conntrack
|
||||
|
||||
const Enabled = true
|
||||
@@ -1,32 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
// TODO: add to N
|
||||
type AbstractPacketConn interface {
|
||||
Close() error
|
||||
LocalAddr() net.Addr
|
||||
SetDeadline(t time.Time) error
|
||||
SetReadDeadline(t time.Time) error
|
||||
SetWriteDeadline(t time.Time) error
|
||||
}
|
||||
|
||||
type Tracker interface {
|
||||
NewConn(conn net.Conn) (net.Conn, error)
|
||||
NewPacketConn(conn net.PacketConn) (net.PacketConn, error)
|
||||
NewConnEx(conn net.Conn) (N.CloseHandlerFunc, error)
|
||||
NewPacketConnEx(conn AbstractPacketConn) (N.CloseHandlerFunc, error)
|
||||
CheckConn(source netip.AddrPort, destination netip.AddrPort) bool
|
||||
CheckPacketConn(source netip.AddrPort) bool
|
||||
AddPendingDestination(destination netip.AddrPort) func()
|
||||
CheckDestination(destination netip.AddrPort) bool
|
||||
KillerCheck() error
|
||||
Count() int
|
||||
Close()
|
||||
}
|
||||
@@ -2,134 +2,74 @@ package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
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/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var (
|
||||
_ ParallelInterfaceDialer = (*DefaultDialer)(nil)
|
||||
_ WireGuardListener = (*DefaultDialer)(nil)
|
||||
)
|
||||
var _ WireGuardListener = (*DefaultDialer)(nil)
|
||||
|
||||
type DefaultDialer struct {
|
||||
tracker conntrack.Tracker
|
||||
dialer4 tcpDialer
|
||||
dialer6 tcpDialer
|
||||
udpDialer4 net.Dialer
|
||||
udpDialer6 net.Dialer
|
||||
udpListener net.ListenConfig
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
isWireGuardListener bool
|
||||
networkManager adapter.NetworkManager
|
||||
networkStrategy *C.NetworkStrategy
|
||||
defaultNetworkStrategy bool
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
networkLastFallback atomic.TypedValue[time.Time]
|
||||
dialer4 tcpDialer
|
||||
dialer6 tcpDialer
|
||||
udpDialer4 net.Dialer
|
||||
udpDialer6 net.Dialer
|
||||
udpListener net.ListenConfig
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
isWireGuardListener bool
|
||||
}
|
||||
|
||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||
tracker := service.FromContext[conntrack.Tracker](ctx)
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
|
||||
var (
|
||||
dialer net.Dialer
|
||||
listener net.ListenConfig
|
||||
interfaceFinder control.InterfaceFinder
|
||||
networkStrategy *C.NetworkStrategy
|
||||
defaultNetworkStrategy bool
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
)
|
||||
if networkManager != nil {
|
||||
interfaceFinder = networkManager.InterfaceFinder()
|
||||
} else {
|
||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||
}
|
||||
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
|
||||
var dialer net.Dialer
|
||||
var listener net.ListenConfig
|
||||
if options.BindInterface != "" {
|
||||
var interfaceFinder control.InterfaceFinder
|
||||
if router != nil {
|
||||
interfaceFinder = router.InterfaceFinder()
|
||||
} else {
|
||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||
}
|
||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router != nil && router.AutoDetectInterface() {
|
||||
bindFunc := router.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router != nil && router.DefaultInterface() != "" {
|
||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
var autoRedirectOutputMark uint32
|
||||
if router != nil {
|
||||
autoRedirectOutputMark = router.AutoRedirectOutputMark()
|
||||
}
|
||||
if autoRedirectOutputMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
}
|
||||
if options.RoutingMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||
}
|
||||
if networkManager != nil {
|
||||
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||
if autoRedirectOutputMark > 0 {
|
||||
if options.RoutingMark > 0 {
|
||||
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
|
||||
}
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
|
||||
}
|
||||
}
|
||||
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
||||
if disableDefaultBind || options.TCPFastOpen {
|
||||
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`")
|
||||
}
|
||||
}
|
||||
|
||||
if networkManager != nil {
|
||||
defaultOptions := networkManager.DefaultOptions()
|
||||
if !disableDefaultBind {
|
||||
if defaultOptions.BindInterface != "" {
|
||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
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
|
||||
networkType = defaultOptions.NetworkType
|
||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||
}
|
||||
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
}
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else {
|
||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||
} else if router != nil && router.DefaultMark() > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||
if autoRedirectOutputMark > 0 {
|
||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
|
||||
}
|
||||
}
|
||||
if options.ReuseAddr {
|
||||
@@ -199,21 +139,14 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
return nil, err
|
||||
}
|
||||
return &DefaultDialer{
|
||||
tracker: tracker,
|
||||
dialer4: tcpDialer4,
|
||||
dialer6: tcpDialer6,
|
||||
udpDialer4: udpDialer4,
|
||||
udpDialer6: udpDialer6,
|
||||
udpListener: listener,
|
||||
udpAddr4: udpAddr4,
|
||||
udpAddr6: udpAddr6,
|
||||
isWireGuardListener: options.IsWireGuardListener,
|
||||
networkManager: networkManager,
|
||||
networkStrategy: networkStrategy,
|
||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||
networkType: networkType,
|
||||
fallbackNetworkType: fallbackNetworkType,
|
||||
networkFallbackDelay: networkFallbackDelay,
|
||||
tcpDialer4,
|
||||
tcpDialer6,
|
||||
udpDialer4,
|
||||
udpDialer6,
|
||||
listener,
|
||||
udpAddr4,
|
||||
udpAddr6,
|
||||
options.IsWireGuardListener,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -221,142 +154,45 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
if !address.IsValid() {
|
||||
return nil, E.New("invalid address")
|
||||
}
|
||||
if d.networkStrategy == nil {
|
||||
if address.IsFqdn() {
|
||||
return nil, E.New("unexpected domain destination")
|
||||
}
|
||||
// Since pending check is only used by ndis, it is not performed for non-windows connections which are only supported on platform clients
|
||||
if d.tracker != nil {
|
||||
done := d.tracker.AddPendingDestination(address.AddrPort())
|
||||
defer done()
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
if !address.IsIPv6() {
|
||||
return d.trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||
} else {
|
||||
return d.trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||
}
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
if !address.IsIPv6() {
|
||||
return d.trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||
} else {
|
||||
return d.trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||
}
|
||||
} else {
|
||||
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||
}
|
||||
}
|
||||
|
||||
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 == nil {
|
||||
strategy = d.networkStrategy
|
||||
}
|
||||
if strategy == nil {
|
||||
return d.DialContext(ctx, network, address)
|
||||
}
|
||||
if len(interfaceType) == 0 {
|
||||
interfaceType = d.networkType
|
||||
}
|
||||
if len(fallbackInterfaceType) == 0 {
|
||||
fallbackInterfaceType = d.fallbackNetworkType
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = d.networkFallbackDelay
|
||||
}
|
||||
var dialer net.Dialer
|
||||
if N.NetworkName(network) == N.NetworkTCP {
|
||||
dialer = dialerFromTCPDialer(d.dialer4)
|
||||
} else {
|
||||
dialer = d.udpDialer4
|
||||
}
|
||||
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||
var (
|
||||
conn net.Conn
|
||||
isPrimary bool
|
||||
err error
|
||||
)
|
||||
if !fastFallback {
|
||||
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
} else {
|
||||
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
|
||||
}
|
||||
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 trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||
}
|
||||
}
|
||||
if !fastFallback && !isPrimary {
|
||||
d.networkLastFallback.Store(time.Now())
|
||||
if !address.IsIPv6() {
|
||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||
} else {
|
||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||
}
|
||||
return d.trackConn(conn, nil)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if d.networkStrategy == nil {
|
||||
if destination.IsIPv6() {
|
||||
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||
} else {
|
||||
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
}
|
||||
if destination.IsIPv6() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||
} else {
|
||||
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
if strategy == nil {
|
||||
strategy = d.networkStrategy
|
||||
}
|
||||
if strategy == nil {
|
||||
return d.ListenPacket(ctx, destination)
|
||||
}
|
||||
if len(interfaceType) == 0 {
|
||||
interfaceType = d.networkType
|
||||
}
|
||||
if len(fallbackInterfaceType) == 0 {
|
||||
fallbackInterfaceType = d.fallbackNetworkType
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = d.networkFallbackDelay
|
||||
}
|
||||
network := N.NetworkUDP
|
||||
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
network += "4"
|
||||
}
|
||||
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 d.trackPacketConn(packetConn, nil)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||
return d.udpListener.ListenPacket(context.Background(), network, address)
|
||||
return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address))
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
if d.tracker == nil || err != nil {
|
||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
if !conntrack.Enabled || err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return d.tracker.NewConn(conn)
|
||||
return conntrack.NewConn(conn)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||
if err != nil {
|
||||
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||
if !conntrack.Enabled || err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return d.tracker.NewPacketConn(conn)
|
||||
return conntrack.NewPacketConn(conn)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,3 @@ type tcpDialer = tfo.Dialer
|
||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
||||
}
|
||||
|
||||
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
||||
return dialer.Dialer
|
||||
}
|
||||
|
||||
@@ -16,7 +16,3 @@ func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
||||
return dialer
|
||||
}
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) {
|
||||
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, false, E.New("no available network interface")
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
type dialResult struct {
|
||||
net.Conn
|
||||
error
|
||||
primary bool
|
||||
}
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||
perNetDialer := dialer
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
|
||||
case <-returned:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case results <- dialResult{Conn: conn, primary: primary}:
|
||||
case <-returned:
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
primaryCtx, primaryCancel := context.WithCancel(ctx)
|
||||
defer primaryCancel()
|
||||
for _, iif := range primaryInterfaces {
|
||||
go startRacer(primaryCtx, true, iif)
|
||||
}
|
||||
var (
|
||||
fallbackTimer *time.Timer
|
||||
fallbackChan <-chan time.Time
|
||||
)
|
||||
if len(fallbackInterfaces) > 0 {
|
||||
fallbackTimer = time.NewTimer(fallbackDelay)
|
||||
defer fallbackTimer.Stop()
|
||||
fallbackChan = fallbackTimer.C
|
||||
}
|
||||
var errors []error
|
||||
for {
|
||||
select {
|
||||
case <-fallbackChan:
|
||||
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||
defer fallbackCancel()
|
||||
for _, iif := range fallbackInterfaces {
|
||||
go startRacer(fallbackCtx, false, iif)
|
||||
}
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, res.primary, nil
|
||||
}
|
||||
errors = append(errors, res.error)
|
||||
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
|
||||
return nil, false, E.Errors(errors...)
|
||||
}
|
||||
if res.primary && fallbackTimer != nil && fallbackTimer.Stop() {
|
||||
fallbackTimer.Reset(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) {
|
||||
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, false, E.New("no available network interface")
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
type dialResult struct {
|
||||
net.Conn
|
||||
error
|
||||
primary bool
|
||||
}
|
||||
startAt := time.Now()
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||
perNetDialer := dialer
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
|
||||
case <-returned:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case results <- dialResult{Conn: conn, primary: primary}:
|
||||
case <-returned:
|
||||
if primary && time.Since(startAt) <= fallbackDelay {
|
||||
resetFastFallback(time.Time{})
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, iif := range primaryInterfaces {
|
||||
go startRacer(ctx, true, iif)
|
||||
}
|
||||
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||
defer fallbackCancel()
|
||||
for _, iif := range fallbackInterfaces {
|
||||
go startRacer(fallbackCtx, false, iif)
|
||||
}
|
||||
var errors []error
|
||||
for {
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, res.primary, nil
|
||||
}
|
||||
errors = append(errors, res.error)
|
||||
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
|
||||
return nil, false, E.Errors(errors...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, E.New("no available network interface")
|
||||
}
|
||||
var errors []error
|
||||
for _, primaryInterface := range primaryInterfaces {
|
||||
perNetListener := listener
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
|
||||
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Index, ")"))
|
||||
}
|
||||
for _, fallbackInterface := range fallbackInterfaces {
|
||||
perNetListener := listener
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
|
||||
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Index, ")"))
|
||||
}
|
||||
return nil, E.Errors(errors...)
|
||||
}
|
||||
|
||||
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
|
||||
interfaces := networkManager.NetworkInterfaces()
|
||||
switch strategy {
|
||||
case C.NetworkStrategyDefault:
|
||||
if len(interfaceType) == 0 {
|
||||
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||
if defaultIf != nil {
|
||||
for _, iif := range interfaces {
|
||||
if iif.Index == defaultIf.Index {
|
||||
primaryInterfaces = append(primaryInterfaces, iif)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = interfaces
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, it.Type)
|
||||
})
|
||||
}
|
||||
case C.NetworkStrategyHybrid:
|
||||
if len(interfaceType) == 0 {
|
||||
primaryInterfaces = interfaces
|
||||
} else {
|
||||
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, it.Type)
|
||||
})
|
||||
}
|
||||
case C.NetworkStrategyFallback:
|
||||
if len(interfaceType) == 0 {
|
||||
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||
if defaultIf != nil {
|
||||
for _, iif := range interfaces {
|
||||
if iif.Index == defaultIf.Index {
|
||||
primaryInterfaces = append(primaryInterfaces, iif)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = interfaces
|
||||
}
|
||||
} 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 {
|
||||
return common.Contains(fallbackInterfaceType, iif.Type)
|
||||
})
|
||||
}
|
||||
}
|
||||
return primaryInterfaces, fallbackInterfaces
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
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) {
|
||||
if len(destinationAddresses) == 0 {
|
||||
if !destination.IsIP() {
|
||||
panic("invalid usage")
|
||||
}
|
||||
destinationAddresses = []netip.Addr{destination.Addr}
|
||||
}
|
||||
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
var errors []error
|
||||
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
} else {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port))
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if len(destinationAddresses) == 0 {
|
||||
if !destination.IsIP() {
|
||||
panic("invalid usage")
|
||||
}
|
||||
destinationAddresses = []netip.Addr{destination.Addr}
|
||||
}
|
||||
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
|
||||
addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
|
||||
return address.Is4() || address.Is4In6()
|
||||
})
|
||||
addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
|
||||
return address.Is6() && !address.Is4In6()
|
||||
})
|
||||
if len(addresses4) == 0 || len(addresses6) == 0 {
|
||||
return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
var primaries, fallbacks []netip.Addr
|
||||
if preferIPv6 {
|
||||
primaries = addresses6
|
||||
fallbacks = addresses4
|
||||
} else {
|
||||
primaries = addresses4
|
||||
fallbacks = addresses6
|
||||
}
|
||||
type dialResult struct {
|
||||
net.Conn
|
||||
error
|
||||
primary bool
|
||||
done bool
|
||||
}
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool) {
|
||||
ras := primaries
|
||||
if !primary {
|
||||
ras = fallbacks
|
||||
}
|
||||
c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
select {
|
||||
case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:
|
||||
case <-returned:
|
||||
if c != nil {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
var primary, fallback dialResult
|
||||
primaryCtx, primaryCancel := context.WithCancel(ctx)
|
||||
defer primaryCancel()
|
||||
go startRacer(primaryCtx, true)
|
||||
fallbackTimer := time.NewTimer(fallbackDelay)
|
||||
defer fallbackTimer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-fallbackTimer.C:
|
||||
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||
defer fallbackCancel()
|
||||
go startRacer(fallbackCtx, false)
|
||||
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
if res.primary {
|
||||
primary = res
|
||||
} else {
|
||||
fallback = res
|
||||
}
|
||||
if primary.done && fallback.done {
|
||||
return nil, primary.error
|
||||
}
|
||||
if res.primary && fallbackTimer.Stop() {
|
||||
fallbackTimer.Reset(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
var errors []error
|
||||
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err == nil {
|
||||
return conn, address, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
} else {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port))
|
||||
if err == nil {
|
||||
return conn, address, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
return nil, netip.Addr{}, E.Errors(errors...)
|
||||
}
|
||||
@@ -12,15 +12,15 @@ import (
|
||||
)
|
||||
|
||||
type DetourDialer struct {
|
||||
outboundManager adapter.OutboundManager
|
||||
detour string
|
||||
dialer N.Dialer
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
router adapter.Router
|
||||
detour string
|
||||
dialer N.Dialer
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
}
|
||||
|
||||
func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer {
|
||||
return &DetourDialer{outboundManager: outboundManager, detour: detour}
|
||||
func NewDetour(router adapter.Router, detour string) N.Dialer {
|
||||
return &DetourDialer{router: router, detour: detour}
|
||||
}
|
||||
|
||||
func (d *DetourDialer) Start() error {
|
||||
@@ -31,7 +31,7 @@ func (d *DetourDialer) Start() error {
|
||||
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
||||
d.initOnce.Do(func() {
|
||||
var loaded bool
|
||||
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
|
||||
d.dialer, loaded = d.router.Outbound(d.detour)
|
||||
if !loaded {
|
||||
d.initErr = E.New("outbound detour not found: ", d.detour)
|
||||
}
|
||||
|
||||
@@ -1,82 +1,40 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-dns"
|
||||
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 New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
|
||||
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(ctx, options)
|
||||
return NewDefault(router, options)
|
||||
}
|
||||
if router == nil {
|
||||
return NewDefault(nil, options)
|
||||
}
|
||||
var (
|
||||
dialer N.Dialer
|
||||
err error
|
||||
)
|
||||
if options.Detour == "" {
|
||||
dialer, err = NewDefault(ctx, options)
|
||||
dialer, err = NewDefault(router, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
outboundManager := service.FromContext[adapter.OutboundManager](ctx)
|
||||
if outboundManager == nil {
|
||||
return nil, E.New("missing outbound manager")
|
||||
}
|
||||
dialer = NewDetour(outboundManager, options.Detour)
|
||||
dialer = NewDetour(router, options.Detour)
|
||||
}
|
||||
if options.Detour == "" {
|
||||
router := service.FromContext[adapter.Router](ctx)
|
||||
if router != nil {
|
||||
dialer = NewResolveDialer(
|
||||
router,
|
||||
dialer,
|
||||
options.Detour == "" && !options.TCPFastOpen,
|
||||
dns.DomainStrategy(options.DomainStrategy),
|
||||
time.Duration(options.FallbackDelay))
|
||||
}
|
||||
dialer = NewResolveDialer(
|
||||
router,
|
||||
dialer,
|
||||
options.Detour == "" && !options.TCPFastOpen,
|
||||
dns.DomainStrategy(options.DomainStrategy),
|
||||
time.Duration(options.FallbackDelay))
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) {
|
||||
if options.Detour != "" {
|
||||
return nil, E.New("`detour` is not supported in direct context")
|
||||
}
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(ctx, options)
|
||||
}
|
||||
dialer, err := NewDefault(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewResolveParallelInterfaceDialer(
|
||||
service.FromContext[adapter.Router](ctx),
|
||||
dialer,
|
||||
true,
|
||||
dns.DomainStrategy(options.DomainStrategy),
|
||||
time.Duration(options.FallbackDelay),
|
||||
), nil
|
||||
}
|
||||
|
||||
type ParallelInterfaceDialer interface {
|
||||
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)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
@@ -15,12 +14,7 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var (
|
||||
_ N.Dialer = (*resolveDialer)(nil)
|
||||
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
|
||||
)
|
||||
|
||||
type resolveDialer struct {
|
||||
type ResolveDialer struct {
|
||||
dialer N.Dialer
|
||||
parallel bool
|
||||
router adapter.Router
|
||||
@@ -28,8 +22,8 @@ type resolveDialer struct {
|
||||
fallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
|
||||
return &resolveDialer{
|
||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
||||
return &ResolveDialer{
|
||||
dialer,
|
||||
parallel,
|
||||
router,
|
||||
@@ -38,25 +32,7 @@ func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, str
|
||||
}
|
||||
}
|
||||
|
||||
type resolveParallelNetworkDialer struct {
|
||||
resolveDialer
|
||||
dialer ParallelInterfaceDialer
|
||||
}
|
||||
|
||||
func NewResolveParallelInterfaceDialer(router adapter.Router, dialer ParallelInterfaceDialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
|
||||
return &resolveParallelNetworkDialer{
|
||||
resolveDialer{
|
||||
dialer,
|
||||
parallel,
|
||||
router,
|
||||
strategy,
|
||||
fallbackDelay,
|
||||
},
|
||||
dialer,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
@@ -81,7 +57,7 @@ func (d *resolveDialer) DialContext(ctx context.Context, network string, destina
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
@@ -106,59 +82,6 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
||||
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) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
metadata.Destination = destination
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = d.fallbackDelay
|
||||
}
|
||||
if d.parallel {
|
||||
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
} else {
|
||||
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) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
metadata.Destination = destination
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||
}
|
||||
|
||||
func (d *resolveDialer) Upstream() any {
|
||||
func (d *ResolveDialer) Upstream() any {
|
||||
return d.dialer
|
||||
}
|
||||
|
||||
@@ -9,22 +9,30 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type DefaultOutboundDialer struct {
|
||||
outboundManager adapter.OutboundManager
|
||||
type RouterDialer struct {
|
||||
router adapter.Router
|
||||
}
|
||||
|
||||
func NewDefaultOutbound(outboundManager adapter.OutboundManager) N.Dialer {
|
||||
return &DefaultOutboundDialer{outboundManager: outboundManager}
|
||||
func NewRouter(router adapter.Router) N.Dialer {
|
||||
return &RouterDialer{router: router}
|
||||
}
|
||||
|
||||
func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
return d.outboundManager.Default().DialContext(ctx, network, destination)
|
||||
func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
dialer, err := d.router.DefaultOutbound(network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
|
||||
func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return d.outboundManager.Default().ListenPacket(ctx, destination)
|
||||
func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
dialer, err := d.router.DefaultOutbound(N.NetworkUDP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
func (d *DefaultOutboundDialer) Upstream() any {
|
||||
return d.outboundManager.Default()
|
||||
func (d *RouterDialer) Upstream() any {
|
||||
return d.router
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -123,7 +124,7 @@ func (l *Listener) loopUDPOut() {
|
||||
case packet := <-l.packetOutbound:
|
||||
packet.Buffer.Release()
|
||||
N.PutPacketBuffer(packet)
|
||||
default:
|
||||
case <-time.After(time.Second):
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
||||
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||
return log.ContextWithNewID(ctx)
|
||||
},
|
||||
Logger: logger,
|
||||
HandlerEx: adapter.NewRouteContextHandlerEx(router),
|
||||
Padding: options.Padding,
|
||||
Brutal: brutalOptions,
|
||||
Logger: logger,
|
||||
Handler: adapter.NewRouteContextHandler(router, logger),
|
||||
Padding: options.Padding,
|
||||
Brutal: brutalOptions,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -52,7 +52,6 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
||||
return &Router{router, service}, nil
|
||||
}
|
||||
|
||||
// Deprecated: Use RouteConnectionEx instead.
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
if metadata.Destination == mux.Destination {
|
||||
// TODO: check if WithContext is necessary
|
||||
@@ -62,7 +61,6 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use RoutePacketConnectionEx instead.
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@ package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
type DarwinSystemProxy struct {
|
||||
@@ -25,7 +24,7 @@ type DarwinSystemProxy struct {
|
||||
}
|
||||
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
|
||||
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
|
||||
interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor()
|
||||
if interfaceMonitor == nil {
|
||||
return nil, E.New("missing interface monitor")
|
||||
}
|
||||
@@ -34,7 +33,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo
|
||||
serverAddr: serverAddr,
|
||||
supportSOCKS: supportSOCKS,
|
||||
}
|
||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate)
|
||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
@@ -66,22 +65,25 @@ func (p *DarwinSystemProxy) Disable() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) {
|
||||
if !p.isEnabled || defaultInterface == nil {
|
||||
func (p *DarwinSystemProxy) update(event int) {
|
||||
if event&tun.EventInterfaceUpdate == 0 {
|
||||
return
|
||||
}
|
||||
if !p.isEnabled {
|
||||
return
|
||||
}
|
||||
_ = p.update0()
|
||||
}
|
||||
|
||||
func (p *DarwinSystemProxy) update0() error {
|
||||
newInterface := p.monitor.DefaultInterface()
|
||||
if p.interfaceName == newInterface.Name {
|
||||
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||
if p.interfaceName == newInterfaceName {
|
||||
return nil
|
||||
}
|
||||
if p.interfaceName != "" {
|
||||
_ = p.Disable()
|
||||
}
|
||||
p.interfaceName = newInterface.Name
|
||||
p.interfaceName = newInterfaceName
|
||||
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -38,13 +38,10 @@ const (
|
||||
ruleItemWIFIBSSID
|
||||
ruleItemAdGuardDomain
|
||||
ruleItemProcessPathRegex
|
||||
ruleItemNetworkType
|
||||
ruleItemNetworkIsExpensive
|
||||
ruleItemNetworkIsConstrained
|
||||
ruleItemFinal uint8 = 0xFF
|
||||
)
|
||||
|
||||
func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) {
|
||||
func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) {
|
||||
var magicBytes [3]byte
|
||||
_, err = io.ReadFull(reader, magicBytes[:])
|
||||
if err != nil {
|
||||
@@ -57,10 +54,10 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
||||
var version uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &version)
|
||||
if err != nil {
|
||||
return ruleSetCompat, err
|
||||
return ruleSet, err
|
||||
}
|
||||
if version > C.RuleSetVersionCurrent {
|
||||
return ruleSetCompat, E.New("unsupported version: ", version)
|
||||
if version > C.RuleSetVersion2 {
|
||||
return ruleSet, E.New("unsupported version: ", version)
|
||||
}
|
||||
compressReader, err := zlib.NewReader(reader)
|
||||
if err != nil {
|
||||
@@ -71,10 +68,9 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ruleSetCompat.Version = version
|
||||
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
|
||||
ruleSet.Rules = make([]option.HeadlessRule, length)
|
||||
for i := uint64(0); i < length; i++ {
|
||||
ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover)
|
||||
ruleSet.Rules[i], err = readRule(bReader, recover)
|
||||
if err != nil {
|
||||
err = E.Cause(err, "read rule[", i, "]")
|
||||
return
|
||||
@@ -83,12 +79,18 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
||||
return
|
||||
}
|
||||
|
||||
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error {
|
||||
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) error {
|
||||
_, err := writer.Write(MagicBytes[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, generateVersion)
|
||||
var version uint8
|
||||
if generateUnstable {
|
||||
version = C.RuleSetVersion2
|
||||
} else {
|
||||
version = C.RuleSetVersion1
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -102,7 +104,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8)
|
||||
return err
|
||||
}
|
||||
for _, rule := range ruleSet.Rules {
|
||||
err = writeRule(bWriter, rule, generateVersion)
|
||||
err = writeRule(bWriter, rule, generateUnstable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -133,12 +135,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err
|
||||
return
|
||||
}
|
||||
|
||||
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error {
|
||||
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateUnstable bool) error {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
return writeDefaultRule(writer, rule.DefaultOptions, generateVersion)
|
||||
return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable)
|
||||
case C.RuleTypeLogical:
|
||||
return writeLogicalRule(writer, rule.LogicalOptions, generateVersion)
|
||||
return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable)
|
||||
default:
|
||||
panic("unknown rule type: " + rule.Type)
|
||||
}
|
||||
@@ -225,12 +227,6 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
||||
return
|
||||
}
|
||||
rule.AdGuardDomainMatcher = matcher
|
||||
case ruleItemNetworkType:
|
||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||
case ruleItemNetworkIsExpensive:
|
||||
rule.NetworkIsExpensive = true
|
||||
case ruleItemNetworkIsConstrained:
|
||||
rule.NetworkIsConstrained = true
|
||||
case ruleItemFinal:
|
||||
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||
return
|
||||
@@ -244,7 +240,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
||||
}
|
||||
}
|
||||
|
||||
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error {
|
||||
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateUnstable bool) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -268,7 +264,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer)
|
||||
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, !generateUnstable).Write(writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -345,27 +341,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(rule.NetworkType) > 0 {
|
||||
if generateVersion < C.RuleSetVersion3 {
|
||||
return E.New("network_type rule item is only supported in version 3 or later")
|
||||
}
|
||||
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rule.NetworkIsExpensive {
|
||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rule.NetworkIsConstrained {
|
||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(rule.WIFISSID) > 0 {
|
||||
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||
if err != nil {
|
||||
@@ -379,9 +354,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
||||
}
|
||||
}
|
||||
if len(rule.AdGuardDomain) > 0 {
|
||||
if generateVersion < C.RuleSetVersion2 {
|
||||
return E.New("AdGuard rule items is only supported in version 2 or later")
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -414,18 +386,6 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
|
||||
return varbin.Write(writer, binary.BigEndian, value)
|
||||
}
|
||||
|
||||
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
|
||||
return varbin.ReadValue[[]E](reader, binary.BigEndian)
|
||||
}
|
||||
|
||||
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
|
||||
err := writer.WriteByte(itemType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return varbin.Write(writer, binary.BigEndian, value)
|
||||
}
|
||||
|
||||
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
||||
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
||||
}
|
||||
@@ -497,7 +457,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
|
||||
return
|
||||
}
|
||||
|
||||
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error {
|
||||
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateUnstable bool) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -518,7 +478,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu
|
||||
return err
|
||||
}
|
||||
for _, rule := range logicalRule.Rules {
|
||||
err = writeRule(writer, rule, generateVersion)
|
||||
err = writeRule(writer, rule, generateUnstable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/sagernet/sing-dns"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
@@ -64,7 +63,6 @@ type echConnWrapper struct {
|
||||
|
||||
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
//nolint:staticcheck
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
@@ -215,7 +213,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam
|
||||
},
|
||||
},
|
||||
}
|
||||
response, err := service.FromContext[adapter.Router](ctx).Exchange(ctx, message)
|
||||
response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -147,9 +147,6 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
|
||||
pair.rawConf = b
|
||||
|
||||
secBuf, err := sec.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "serialize ECH private key")
|
||||
}
|
||||
sk := []byte{}
|
||||
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||
sk = append(sk, secBuf...)
|
||||
|
||||
@@ -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 {
|
||||
return &http3.Transport{
|
||||
return &http3.RoundTripper{
|
||||
TLSClientConfig: c.config,
|
||||
QUICConfig: quicConfig,
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
|
||||
@@ -97,10 +97,6 @@ func (c *echServerConfig) startWatcher() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.watcher = watcher
|
||||
return nil
|
||||
}
|
||||
@@ -236,7 +232,7 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
var echKey []byte
|
||||
if len(options.ECH.Key) > 0 {
|
||||
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
||||
} else if options.ECH.KeyPath != "" {
|
||||
} else if options.KeyPath != "" {
|
||||
content, err := os.ReadFile(options.ECH.KeyPath)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read ECH key")
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/reality"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@@ -101,7 +102,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
||||
tlsConfig.ShortIds[shortID] = true
|
||||
}
|
||||
|
||||
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions)
|
||||
handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -174,7 +175,6 @@ type realityConnWrapper struct {
|
||||
|
||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
//nolint:staticcheck
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
|
||||
@@ -106,10 +106,6 @@ func (c *STDServerConfig) startWatcher() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.watcher = watcher
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
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,7 +69,6 @@ type utlsConnWrapper struct {
|
||||
|
||||
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
//nolint:staticcheck
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
Hysterai2MasqueradeTypeFile = "file"
|
||||
Hysterai2MasqueradeTypeProxy = "proxy"
|
||||
Hysterai2MasqueradeTypeString = "string"
|
||||
)
|
||||
@@ -1,58 +0,0 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing/common"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
type InterfaceType uint8
|
||||
|
||||
const (
|
||||
InterfaceTypeWIFI InterfaceType = iota
|
||||
InterfaceTypeCellular
|
||||
InterfaceTypeEthernet
|
||||
InterfaceTypeOther
|
||||
)
|
||||
|
||||
var (
|
||||
interfaceTypeToString = map[InterfaceType]string{
|
||||
InterfaceTypeWIFI: "wifi",
|
||||
InterfaceTypeCellular: "cellular",
|
||||
InterfaceTypeEthernet: "ethernet",
|
||||
InterfaceTypeOther: "other",
|
||||
}
|
||||
StringToInterfaceType = common.ReverseMap(interfaceTypeToString)
|
||||
)
|
||||
|
||||
func (t InterfaceType) String() string {
|
||||
name, loaded := interfaceTypeToString[t]
|
||||
if !loaded {
|
||||
return F.ToString(int(t))
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
type NetworkStrategy uint8
|
||||
|
||||
const (
|
||||
NetworkStrategyDefault NetworkStrategy = iota
|
||||
NetworkStrategyFallback
|
||||
NetworkStrategyHybrid
|
||||
)
|
||||
|
||||
var (
|
||||
networkStrategyToString = map[NetworkStrategy]string{
|
||||
NetworkStrategyDefault: "default",
|
||||
NetworkStrategyFallback: "fallback",
|
||||
NetworkStrategyHybrid: "hybrid",
|
||||
}
|
||||
StringToNetworkStrategy = common.ReverseMap(networkStrategyToString)
|
||||
)
|
||||
|
||||
func (s NetworkStrategy) String() string {
|
||||
name, loaded := networkStrategyToString[s]
|
||||
if !loaded {
|
||||
return F.ToString(int(s))
|
||||
}
|
||||
return name
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
const IsAndroid = goos.IsAndroid == 1
|
||||
|
||||
const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1
|
||||
const IsDarwin = goos.IsDarwin == 1
|
||||
|
||||
const IsDragonfly = goos.IsDragonfly == 1
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ const (
|
||||
ProtocolDTLS = "dtls"
|
||||
ProtocolSSH = "ssh"
|
||||
ProtocolRDP = "rdp"
|
||||
ProtocolNTP = "ntp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -23,7 +23,6 @@ const (
|
||||
TypeVLESS = "vless"
|
||||
TypeTUIC = "tuic"
|
||||
TypeHysteria2 = "hysteria2"
|
||||
TypeNDIS = "ndis"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -81,8 +80,6 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "Selector"
|
||||
case TypeURLTest:
|
||||
return "URLTest"
|
||||
case TypeNDIS:
|
||||
return "NDIS"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ const (
|
||||
const (
|
||||
RuleSetVersion1 = 1 + iota
|
||||
RuleSetVersion2
|
||||
RuleSetVersion3
|
||||
RuleSetVersionCurrent = RuleSetVersion3
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -9,6 +9,8 @@ const (
|
||||
TCPTimeout = 15 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
QUICTimeout = 30 * time.Second
|
||||
STUNTimeout = 15 * time.Second
|
||||
UDPTimeout = 5 * time.Minute
|
||||
DefaultURLTestInterval = 3 * time.Minute
|
||||
DefaultURLTestIdleTimeout = 30 * time.Minute
|
||||
@@ -17,18 +19,3 @@ const (
|
||||
FatalStopTimeout = 10 * time.Second
|
||||
FakeIPMetadataSaveInterval = 10 * time.Second
|
||||
)
|
||||
|
||||
var PortProtocols = map[uint16]string{
|
||||
53: ProtocolDNS,
|
||||
123: ProtocolNTP,
|
||||
3478: ProtocolSTUN,
|
||||
443: ProtocolQUIC,
|
||||
}
|
||||
|
||||
var ProtocolTimeouts = map[string]time.Duration{
|
||||
ProtocolDNS: 10 * time.Second,
|
||||
ProtocolNTP: 10 * time.Second,
|
||||
ProtocolSTUN: 10 * time.Second,
|
||||
ProtocolQUIC: 30 * time.Second,
|
||||
ProtocolDTLS: 30 * time.Second,
|
||||
}
|
||||
|
||||
5
debug.go
5
debug.go
@@ -3,6 +3,7 @@ package box
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
@@ -25,5 +26,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func applyDebugListenOption(options option.DebugOptions) {
|
||||
|
||||
encoder := json.NewEncoder(writer)
|
||||
encoder.SetIndent("", " ")
|
||||
encoder.Encode(&memObject)
|
||||
encoder.Encode(memObject)
|
||||
})
|
||||
r.Route("/pprof", func(r chi.Router) {
|
||||
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
@@ -2,189 +2,10 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 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
|
||||
|
||||
* Add more masquerade options for hysteria2 **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
|
||||
|
||||
#### 1.11.0-alpha.25
|
||||
|
||||
* Update quic-go to v0.48.2
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.22
|
||||
|
||||
* Add UDP timeout route option **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
|
||||
|
||||
#### 1.11.0-alpha.20
|
||||
|
||||
* Add UDP GSO support for WireGuard
|
||||
* Make GSO adaptive **1**
|
||||
|
||||
**1**:
|
||||
|
||||
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
|
||||
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
|
||||
|
||||
For TUN, GSO has been removed,
|
||||
see [Deprecated](/deprecated/#gso-option-in-tun).
|
||||
|
||||
#### 1.11.0-alpha.19
|
||||
|
||||
* Upgrade WireGuard outbound to endpoint **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
The new WireGuard endpoint combines inbound and outbound capabilities,
|
||||
and the old outbound will be removed in sing-box 1.13.0.
|
||||
|
||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||
|
||||
### 1.10.2
|
||||
|
||||
* Add deprecated warnings
|
||||
* Fix proxying websocket connections in HTTP/mixed inbounds
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.18
|
||||
#### 1.11.0-alpha.8
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.16
|
||||
|
||||
* Add `cache_capacity` DNS option **1**
|
||||
* Add `override_address` and `override_port` route options **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [DNS](/configuration/dns/#cache_capacity).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Rule Action](/configuration/route/#override_address) and
|
||||
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
#### 1.11.0-alpha.15
|
||||
|
||||
* Improve multi network dialing **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
New options allow you to configure the network strategy flexibly.
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_strategy),
|
||||
[Rule Action](/configuration/route/rule_action/#network_strategy)
|
||||
and [Route](/configuration/route/#default_network_strategy).
|
||||
|
||||
#### 1.11.0-alpha.14
|
||||
|
||||
* Add multi network dialing **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Similar to Surge's strategy.
|
||||
|
||||
New options allow you to connect using multiple network interfaces,
|
||||
prefer or only use one type of interface,
|
||||
and configure a timeout to fallback to other interfaces.
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_strategy),
|
||||
[Rule Action](/configuration/route/rule_action/#network_strategy)
|
||||
and [Route](/configuration/route/#default_network_strategy).
|
||||
|
||||
#### 1.11.0-alpha.13
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.12
|
||||
|
||||
* Merge route options to route actions **1**
|
||||
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Route options in DNS route actions will no longer be considered deprecated,
|
||||
see [DNS Route Action](/configuration/dns/rule_action/).
|
||||
|
||||
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
|
||||
see [Route Action](/configuration/route/rule_action/).
|
||||
|
||||
**2**:
|
||||
|
||||
When using in graphical clients, new routing rule items allow you to match on
|
||||
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
|
||||
|
||||
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
|
||||
and [Headless Rule](/configuration/rule-set/headless-rule/).
|
||||
|
||||
#### 1.11.0-alpha.9
|
||||
|
||||
* Improve tun compatibility **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
When `gvisor` tun stack is enabled, even if the request passes routing,
|
||||
if the outbound connection establishment fails,
|
||||
the connection still does not need to be established and a TCP RST is replied.
|
||||
|
||||
#### 1.11.0-alpha.7
|
||||
|
||||
* Introducing rule actions **1**
|
||||
@@ -200,7 +21,7 @@ See [Rule](/configuration/route/rule/),
|
||||
[DNS Rule Action](/configuration/dns/rule_action/).
|
||||
|
||||
For migration, see
|
||||
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
|
||||
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
|
||||
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
|
||||
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
||||
|
||||
@@ -208,7 +29,7 @@ and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legac
|
||||
|
||||
Similar to Surge's pre-matching.
|
||||
|
||||
Specifically, new rule actions allow you to reject connections with
|
||||
Specifically, the new rule actions allow you to reject connections with
|
||||
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
|
||||
before connection established to improve tun's compatibility.
|
||||
|
||||
@@ -305,7 +126,7 @@ allows you to write headless rules directly without creating a rule-set file.
|
||||
|
||||
**8**:
|
||||
|
||||
With new access control options, not only can you allow Clash dashboards
|
||||
With the new access control options, not only can you allow Clash dashboards
|
||||
to access the Clash API on your local network,
|
||||
you can also manually limit the websites that can access the API instead of allowing everyone.
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# FakeIP
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# FakeIP
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
|
||||
# DNS
|
||||
|
||||
@@ -20,7 +16,6 @@ icon: material/new-box
|
||||
"disable_cache": false,
|
||||
"disable_expire": false,
|
||||
"independent_cache": false,
|
||||
"cache_capacity": 0,
|
||||
"reverse_mapping": false,
|
||||
"client_subnet": "",
|
||||
"fakeip": {}
|
||||
@@ -63,14 +58,6 @@ Disable dns cache expire.
|
||||
|
||||
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
||||
|
||||
#### cache_capacity
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
LRU cache capacity.
|
||||
|
||||
Value less than 1024 will be ignored.
|
||||
|
||||
#### reverse_mapping
|
||||
|
||||
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
|
||||
# DNS
|
||||
|
||||
@@ -20,7 +16,6 @@ icon: material/new-box
|
||||
"disable_cache": false,
|
||||
"disable_expire": false,
|
||||
"independent_cache": false,
|
||||
"cache_capacity": 0,
|
||||
"reverse_mapping": false,
|
||||
"client_subnet": "",
|
||||
"fakeip": {}
|
||||
@@ -62,14 +57,6 @@ icon: material/new-box
|
||||
|
||||
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
||||
|
||||
#### cache_capacity
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
LRU 缓存容量。
|
||||
|
||||
小于 1024 的值将被忽略。
|
||||
|
||||
#### reverse_mapping
|
||||
|
||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
||||
|
||||
@@ -8,10 +8,7 @@ icon: material/new-box
|
||||
:material-alert: [server](#server)
|
||||
:material-alert: [disable_cache](#disable_cache)
|
||||
:material-alert: [rewrite_ttl](#rewrite_ttl)
|
||||
:material-alert: [client_subnet](#client_subnet)
|
||||
:material-plus: [network_type](#network_type)
|
||||
:material-plus: [network_is_expensive](#network_is_expensive)
|
||||
:material-plus: [network_is_constrained](#network_is_constrained)
|
||||
:material-alert: [client_subnet](#client_subnet)
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
@@ -128,11 +125,6 @@ icon: material/new-box
|
||||
1000
|
||||
],
|
||||
"clash_mode": "direct",
|
||||
"network_type": [
|
||||
"wifi"
|
||||
],
|
||||
"network_is_expensive": false,
|
||||
"network_is_constrained": false,
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -318,39 +310,6 @@ Match user id.
|
||||
|
||||
Match Clash mode.
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match network type.
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### network_is_expensive
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match if network is considered Metered (on Android) or considered expensive,
|
||||
such as Cellular or a Personal Hotspot (on Apple platforms).
|
||||
|
||||
#### network_is_constrained
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Apple platforms.
|
||||
|
||||
Match if network is in Low Data Mode.
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -2,17 +2,6 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [action](#action)
|
||||
:material-alert: [server](#server)
|
||||
:material-alert: [disable_cache](#disable_cache)
|
||||
:material-alert: [rewrite_ttl](#rewrite_ttl)
|
||||
:material-alert: [client_subnet](#client_subnet)
|
||||
:material-plus: [network_type](#network_type)
|
||||
:material-plus: [network_is_expensive](#network_is_expensive)
|
||||
:material-plus: [network_is_constrained](#network_is_constrained)
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
@@ -128,11 +117,6 @@ icon: material/new-box
|
||||
1000
|
||||
],
|
||||
"clash_mode": "direct",
|
||||
"network_type": [
|
||||
"wifi"
|
||||
],
|
||||
"network_is_expensive": false,
|
||||
"network_is_constrained": false,
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -151,15 +135,17 @@ icon: material/new-box
|
||||
"outbound": [
|
||||
"direct"
|
||||
],
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [],
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -318,39 +304,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
匹配 Clash 模式。
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配网络类型。
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### network_is_expensive
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配如果网络被视为计费 (在 Android) 或被视为昂贵,
|
||||
像蜂窝网络或个人热点 (在 Apple 平台)。
|
||||
|
||||
#### network_is_constrained
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配如果网络在低数据模式下。
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
@@ -379,7 +332,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
@@ -399,35 +352,29 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
`any` 可作为值用于匹配任意出站。
|
||||
|
||||
#### action
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
参阅 [规则动作](../rule_action/)。
|
||||
|
||||
#### server
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
在此查询中禁用缓存。
|
||||
|
||||
#### rewrite_ttl
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
重写 DNS 回应中的 TTL。
|
||||
|
||||
#### client_subnet
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
|
||||
|
||||
### 地址筛选字段
|
||||
|
||||
@@ -473,12 +420,8 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### mode
|
||||
|
||||
==必填==
|
||||
|
||||
`and` 或 `or`
|
||||
|
||||
#### rules
|
||||
|
||||
==必填==
|
||||
|
||||
包括的规则。
|
||||
包括的规则。
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# DNS Rule Action
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
### route
|
||||
@@ -10,6 +12,8 @@ icon: material/new-box
|
||||
{
|
||||
"action": "route", // default
|
||||
"server": "",
|
||||
|
||||
// for compatibility
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": 0,
|
||||
"client_subnet": null
|
||||
@@ -24,6 +28,23 @@ icon: material/new-box
|
||||
|
||||
Tag of target server.
|
||||
|
||||
#### disable_cache/rewrite_ttl/client_subnet
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
#### disable_cache
|
||||
|
||||
Disable cache and save cache in this query.
|
||||
@@ -40,19 +61,6 @@ If value is an IP address instead of prefix, `/32` or `/128` will be appended au
|
||||
|
||||
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
`route-options` set options for routing.
|
||||
|
||||
### reject
|
||||
|
||||
```json
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# DNS 规则动作
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
### route
|
||||
@@ -26,6 +28,24 @@ icon: material/new-box
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### disable_cache/rewrite_ttl/client_subnet
|
||||
|
||||
!!! failure "自 sing-box 1.11.0 起"
|
||||
|
||||
旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
@@ -42,19 +62,6 @@ icon: material/new-box
|
||||
|
||||
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
`route-options` 为路由设置选项。
|
||||
|
||||
### reject
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
# Endpoint
|
||||
|
||||
Endpoint is protocols that has both inbound and outbound behavior.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Type | Format |
|
||||
|-------------|---------------------------|
|
||||
| `wireguard` | [WireGuard](./wireguard/) |
|
||||
|
||||
#### tag
|
||||
|
||||
The tag of the endpoint.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user