mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-06 16:39:17 +03:00
Compare commits
25 Commits
dev-tls-fr
...
renovate/g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05a431d203 | ||
|
|
e68a9e623f | ||
|
|
a9bb07db4a | ||
|
|
a07219d16d | ||
|
|
6362b778c9 | ||
|
|
cb46b648d7 | ||
|
|
04613c27e2 | ||
|
|
795e1cf2f0 | ||
|
|
1e36c75336 | ||
|
|
5a2c7037fe | ||
|
|
852c07c050 | ||
|
|
c2ab497ff4 | ||
|
|
1e3c136440 | ||
|
|
7a2cd77798 | ||
|
|
1cdaea49b6 | ||
|
|
a50b0bc1a9 | ||
|
|
00e6210928 | ||
|
|
2dbeb63b4e | ||
|
|
744cc985ba | ||
|
|
bd122478fc | ||
|
|
a18179ad24 | ||
|
|
388c75a815 | ||
|
|
8d0d49c388 | ||
|
|
03f5d6e4f2 | ||
|
|
25065d766a |
599
.github/workflows/build.yml
vendored
599
.github/workflows/build.yml
vendored
@@ -1,599 +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
|
|
||||||
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: Prepare 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: Upload artifact
|
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: binary-android-apks
|
|
||||||
path: 'dist'
|
|
||||||
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
|
mkdir -p $HOME/.gnupg
|
||||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||||
${{ secrets.GPG_KEY }}
|
${{ secrets.GPG_KEY }}
|
||||||
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
EOF
|
EOF
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
|
|||||||
@@ -22,16 +22,6 @@ linters-settings:
|
|||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.23"
|
go: "1.23"
|
||||||
build-tags:
|
|
||||||
- with_gvisor
|
|
||||||
- with_quic
|
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
|
||||||
- with_ech
|
|
||||||
- with_utls
|
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
|
||||||
- with_clash_api
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-dirs:
|
exclude-dirs:
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ builds:
|
|||||||
- -v
|
- -v
|
||||||
- -trimpath
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||||
- -s
|
|
||||||
- -buildid=
|
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
|
|||||||
@@ -201,5 +201,3 @@ release:
|
|||||||
- archive
|
- archive
|
||||||
- package
|
- package
|
||||||
skip_upload: true
|
skip_upload: true
|
||||||
partial:
|
|
||||||
by: target
|
|
||||||
51
Makefile
51
Makefile
@@ -28,7 +28,7 @@ ci_build:
|
|||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
go run -v --tags generate,generate_completions $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -71,7 +71,7 @@ release:
|
|||||||
dist/*_amd64.pkg.tar.zst \
|
dist/*_amd64.pkg.tar.zst \
|
||||||
dist/*_arm64.pkg.tar.zst \
|
dist/*_arm64.pkg.tar.zst \
|
||||||
dist/release
|
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
|
rm -r dist/release
|
||||||
|
|
||||||
release_repo:
|
release_repo:
|
||||||
@@ -90,20 +90,22 @@ upload_android:
|
|||||||
mkdir -p dist/release_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/play/release/*.apk dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.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
|
rm -rf dist/release_android
|
||||||
|
|
||||||
release_android: lib_android update_android_version build_android upload_android
|
release_android: lib_android update_android_version build_android upload_android
|
||||||
|
|
||||||
publish_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: find why and remove `-destination 'generic/platform=iOS'`
|
||||||
# TODO: remove xcode clean when fix control widget fixed
|
|
||||||
build_ios:
|
build_ios:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFI.xcarchive && \
|
rm -rf build/SFI.xcarchive && \
|
||||||
xcodebuild clean -scheme SFI && \
|
|
||||||
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_ios_app_store:
|
upload_ios_app_store:
|
||||||
@@ -145,28 +147,15 @@ build_macos_dmg:
|
|||||||
--hide-extension "SFM.app" \
|
--hide-extension "SFM.app" \
|
||||||
--app-drop-link 0 0 \
|
--app-drop-link 0 0 \
|
||||||
--skip-jenkins \
|
--skip-jenkins \
|
||||||
|
--notarize "notarytool-password" \
|
||||||
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
"../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:
|
upload_macos_dmg:
|
||||||
cd dist/SFM && \
|
cd dist/SFM && \
|
||||||
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
||||||
|
|
||||||
upload_macos_dsyms:
|
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg
|
||||||
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
|
||||||
zip -r SFM.dSYMs.zip dSYMs && \
|
|
||||||
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
|
||||||
popd && \
|
|
||||||
cd dist/SFM && \
|
|
||||||
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
|
|
||||||
|
|
||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -182,22 +171,10 @@ release_tvos: build_tvos upload_tvos_app_store
|
|||||||
update_apple_version:
|
update_apple_version:
|
||||||
go run ./cmd/internal/update_apple_version
|
go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
update_macos_version:
|
|
||||||
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
|
|
||||||
|
|
||||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||||
|
|
||||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||||
|
|
||||||
publish_testflight:
|
|
||||||
go run -v ./cmd/internal/app_store_connect publish_testflight
|
|
||||||
|
|
||||||
prepare_app_store:
|
|
||||||
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
|
||||||
|
|
||||||
publish_app_store:
|
|
||||||
go run -v ./cmd/internal/app_store_connect publish_app_store
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v ./... && \
|
@go test -v ./... && \
|
||||||
cd test && \
|
cd test && \
|
||||||
@@ -213,14 +190,8 @@ test_stdio:
|
|||||||
lib_android:
|
lib_android:
|
||||||
go run ./cmd/internal/build_libbox -target 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:
|
lib_ios:
|
||||||
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
|
|
||||||
lib:
|
lib:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
go run ./cmd/internal/build_libbox -target android
|
||||||
|
|||||||
104
adapter/conn_router.go
Normal file
104
adapter/conn_router.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectionRouter interface {
|
||||||
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteContextHandler(
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeContextHandlerWrapper{
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
type routeHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
type routeContextHandlerWrapper struct {
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
@@ -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,73 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DNSRouter interface {
|
|
||||||
Lifecycle
|
|
||||||
Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error)
|
|
||||||
Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error)
|
|
||||||
ClearCache()
|
|
||||||
LookupReverseMapping(ip netip.Addr) (string, bool)
|
|
||||||
ResetNetwork()
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSClient interface {
|
|
||||||
Start()
|
|
||||||
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
|
||||||
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
|
||||||
LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool)
|
|
||||||
ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool)
|
|
||||||
ClearCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSQueryOptions struct {
|
|
||||||
Transport DNSTransport
|
|
||||||
Strategy C.DomainStrategy
|
|
||||||
DisableCache bool
|
|
||||||
RewriteTTL *uint32
|
|
||||||
ClientSubnet netip.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
type RDRCStore interface {
|
|
||||||
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
|
|
||||||
SaveRDRC(transportName string, qName string, qType uint16) error
|
|
||||||
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSTransport interface {
|
|
||||||
Type() string
|
|
||||||
Tag() string
|
|
||||||
Dependencies() []string
|
|
||||||
Reset()
|
|
||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LegacyDNSTransport interface {
|
|
||||||
LegacyStrategy() C.DomainStrategy
|
|
||||||
LegacyClientSubnet() netip.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSTransportRegistry interface {
|
|
||||||
option.DNSTransportOptionsRegistry
|
|
||||||
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSTransportManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Transports() []DNSTransport
|
|
||||||
Transport(tag string) (DNSTransport, bool)
|
|
||||||
Default() DNSTransport
|
|
||||||
FakeIP() FakeIPTransport
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error
|
|
||||||
}
|
|
||||||
@@ -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,33 +4,34 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"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"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServer interface {
|
type ClashServer interface {
|
||||||
LifecycleService
|
Service
|
||||||
ConnectionTracker
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
}
|
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 V2RayServer interface {
|
|
||||||
LifecycleService
|
|
||||||
StatsService() ConnectionTracker
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheFile interface {
|
type CacheFile interface {
|
||||||
LifecycleService
|
Service
|
||||||
|
PreStarter
|
||||||
|
|
||||||
StoreFakeIP() bool
|
StoreFakeIP() bool
|
||||||
FakeIPStorage
|
FakeIPStorage
|
||||||
|
|
||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
dns.RDRCStore
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
@@ -93,6 +94,10 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Tracker interface {
|
||||||
|
Leave()
|
||||||
|
}
|
||||||
|
|
||||||
type OutboundGroup interface {
|
type OutboundGroup interface {
|
||||||
Outbound
|
Outbound
|
||||||
Now() string
|
Now() string
|
||||||
@@ -110,3 +115,13 @@ func OutboundTag(detour Outbound) string {
|
|||||||
}
|
}
|
||||||
return detour.Tag()
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,6 +27,6 @@ type FakeIPStorage interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FakeIPTransport interface {
|
type FakeIPTransport interface {
|
||||||
DNSTransport
|
dns.Transport
|
||||||
Store() FakeIPStore
|
Store() FakeIPStore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,56 +6,27 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
type ConnectionHandler interface {
|
type ConnectionHandler interface {
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionHandlerEx interface {
|
|
||||||
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: use PacketHandlerEx instead
|
|
||||||
type PacketHandler interface {
|
type PacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type PacketHandlerEx interface {
|
|
||||||
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: use OOBPacketHandlerEx instead
|
|
||||||
type OOBPacketHandler interface {
|
type OOBPacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type OOBPacketHandlerEx interface {
|
|
||||||
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
type PacketConnectionHandler interface {
|
type PacketConnectionHandler interface {
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type PacketConnectionHandlerEx interface {
|
|
||||||
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: use TCPConnectionHandlerEx instead
|
|
||||||
//
|
|
||||||
//nolint:staticcheck
|
|
||||||
type UpstreamHandlerAdapter interface {
|
type UpstreamHandlerAdapter interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandler
|
||||||
N.UDPConnectionHandler
|
N.UDPConnectionHandler
|
||||||
E.Handler
|
E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpstreamHandlerAdapterEx interface {
|
|
||||||
N.TCPConnectionHandlerEx
|
|
||||||
N.UDPConnectionHandlerEx
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,43 +2,26 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
Lifecycle
|
Service
|
||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TCPInjectableInbound interface {
|
type InjectableInbound interface {
|
||||||
Inbound
|
Inbound
|
||||||
ConnectionHandlerEx
|
Network() []string
|
||||||
}
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
type UDPInjectableInbound interface {
|
|
||||||
Inbound
|
|
||||||
PacketConnectionHandlerEx
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
@@ -60,25 +43,10 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
// Deprecated: implement in rule action
|
InboundDetour string
|
||||||
InboundDetour string
|
LastInbound string
|
||||||
LastInbound string
|
OriginDestination M.Socksaddr
|
||||||
OriginDestination M.Socksaddr
|
InboundOptions option.InboundOptions
|
||||||
RouteOriginalDestination M.Socksaddr
|
|
||||||
// Deprecated: to be removed
|
|
||||||
//nolint:staticcheck
|
|
||||||
InboundOptions option.InboundOptions
|
|
||||||
UDPDisableDomainUnmapping bool
|
|
||||||
UDPConnect bool
|
|
||||||
UDPTimeout time.Duration
|
|
||||||
TLSFragment bool
|
|
||||||
TLSFragmentFallbackDelay time.Duration
|
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
|
||||||
NetworkType []C.InterfaceType
|
|
||||||
FallbackNetworkType []C.InterfaceType
|
|
||||||
FallbackDelay time.Duration
|
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
@@ -123,6 +91,15 @@ func ContextFrom(ctx context.Context) *InboundContext {
|
|||||||
return metadata.(*InboundContext)
|
return metadata.(*InboundContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||||
|
metadata := ContextFrom(ctx)
|
||||||
|
if metadata != nil {
|
||||||
|
return ctx, metadata
|
||||||
|
}
|
||||||
|
metadata = new(InboundContext)
|
||||||
|
return WithContext(ctx, metadata), metadata
|
||||||
|
}
|
||||||
|
|
||||||
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||||
var newMetadata InboundContext
|
var newMetadata InboundContext
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
inboundType string
|
|
||||||
inboundTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(inboundType string, inboundTag string) Adapter {
|
|
||||||
return Adapter{
|
|
||||||
inboundType: inboundType,
|
|
||||||
inboundTag: inboundTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.inboundType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.inboundTag
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
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.Inbound, 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.Inbound, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.InboundRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, 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.Inbound, 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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,8 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,18 +15,6 @@ type Outbound interface {
|
|||||||
Network() []string
|
Network() []string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
outboundType string
|
|
||||||
outboundTag string
|
|
||||||
network []string
|
|
||||||
dependencies []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
|
|
||||||
return Adapter{
|
|
||||||
outboundType: outboundType,
|
|
||||||
outboundTag: outboundTag,
|
|
||||||
network: network,
|
|
||||||
dependencies: dependencies,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
|
||||||
var dependencies []string
|
|
||||||
if dialOptions.Detour != "" {
|
|
||||||
dependencies = []string{dialOptions.Detour}
|
|
||||||
}
|
|
||||||
return NewAdapter(outboundType, outboundTag, network, dependencies)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.outboundType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.outboundTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Network() []string {
|
|
||||||
return a.network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Dependencies() []string {
|
|
||||||
return a.dependencies
|
|
||||||
}
|
|
||||||
@@ -1,287 +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.RWMutex
|
|
||||||
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.RLock()
|
|
||||||
defer m.access.RUnlock()
|
|
||||||
return m.outbounds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
|
||||||
m.access.RLock()
|
|
||||||
outbound, found := m.outboundByTag[tag]
|
|
||||||
m.access.RUnlock()
|
|
||||||
if found {
|
|
||||||
return outbound, true
|
|
||||||
}
|
|
||||||
return m.endpoint.Get(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Default() adapter.Outbound {
|
|
||||||
m.access.RLock()
|
|
||||||
defer m.access.RUnlock()
|
|
||||||
if m.defaultOutbound != nil {
|
|
||||||
return m.defaultOutbound
|
|
||||||
} else {
|
|
||||||
return m.defaultOutboundFallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
outbound, found := m.outboundByTag[tag]
|
|
||||||
if !found {
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
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.Outbound, 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.Outbound, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.OutboundRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructors map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructors: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, 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 (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 +1,9 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
|
type PreStarter interface {
|
||||||
|
PreStart() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostStarter interface {
|
||||||
|
PostStart() error
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,50 +2,103 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"net/netip"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
"github.com/sagernet/sing-dns"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
|
mdns "github.com/miekg/dns"
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Service
|
||||||
|
PreStarter
|
||||||
|
PostStarter
|
||||||
|
Cleanup() error
|
||||||
|
|
||||||
|
Outbounds() []Outbound
|
||||||
|
Outbound(tag string) (Outbound, bool)
|
||||||
|
DefaultOutbound(network string) (Outbound, error)
|
||||||
|
|
||||||
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext) error
|
|
||||||
ConnectionRouterEx
|
GeoIPReader() *geoip.Reader
|
||||||
|
LoadGeosite(code string) (Rule, error)
|
||||||
|
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
|
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
|
|
||||||
|
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
||||||
|
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
||||||
|
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||||
|
ClearDNSCache()
|
||||||
|
|
||||||
|
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
|
Rules() []Rule
|
||||||
SetTracker(tracker ConnectionTracker)
|
|
||||||
ResetNetwork()
|
ClashServer() ClashServer
|
||||||
|
SetClashServer(server ClashServer)
|
||||||
|
|
||||||
|
V2RayServer() V2RayServer
|
||||||
|
SetV2RayServer(server V2RayServer)
|
||||||
|
|
||||||
|
ResetNetwork() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionTracker interface {
|
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
|
return service.ContextWith(ctx, router)
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use ConnectionRouterEx instead.
|
func RouterFromContext(ctx context.Context) Router {
|
||||||
type ConnectionRouter interface {
|
return service.FromContext[Router](ctx)
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionRouterEx interface {
|
type HeadlessRule interface {
|
||||||
ConnectionRouter
|
Match(metadata *InboundContext) bool
|
||||||
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
String() string
|
||||||
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
}
|
||||||
|
|
||||||
|
type Rule interface {
|
||||||
|
HeadlessRule
|
||||||
|
Service
|
||||||
|
Type() string
|
||||||
|
UpdateGeosite() error
|
||||||
|
Outbound() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRule interface {
|
||||||
|
Rule
|
||||||
|
DisableCache() bool
|
||||||
|
RewriteTTL() *uint32
|
||||||
|
ClientSubnet() *netip.Prefix
|
||||||
|
WithAddressLimit() bool
|
||||||
|
MatchAddressLimit(metadata *InboundContext) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
Name() string
|
Name() string
|
||||||
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||||
PostStart() error
|
PostStart() error
|
||||||
Metadata() RuleSetMetadata
|
Metadata() RuleSetMetadata
|
||||||
ExtractIPSet() []*netipx.IPSet
|
ExtractIPSet() []*netipx.IPSet
|
||||||
@@ -65,40 +118,17 @@ type RuleSetMetadata struct {
|
|||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
type HTTPStartContext struct {
|
|
||||||
access sync.Mutex
|
type RuleSetStartContext interface {
|
||||||
httpClientCache map[string]*http.Client
|
HTTPClient(detour string, dialer N.Dialer) *http.Client
|
||||||
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPStartContext() *HTTPStartContext {
|
type InterfaceUpdateListener interface {
|
||||||
return &HTTPStartContext{
|
InterfaceUpdated()
|
||||||
httpClientCache: make(map[string]*http.Client),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
|
type WIFIState struct {
|
||||||
c.access.Lock()
|
SSID string
|
||||||
defer c.access.Unlock()
|
BSSID string
|
||||||
if httpClient, loaded := c.httpClientCache[detour]; loaded {
|
|
||||||
return httpClient
|
|
||||||
}
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: C.TCPTimeout,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.httpClientCache[detour] = httpClient
|
|
||||||
return httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HTTPStartContext) Close() {
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
for _, client := range c.httpClientCache {
|
|
||||||
client.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HeadlessRule interface {
|
|
||||||
Match(metadata *InboundContext) bool
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule interface {
|
|
||||||
HeadlessRule
|
|
||||||
Service
|
|
||||||
Type() string
|
|
||||||
Action() RuleAction
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSRule interface {
|
|
||||||
Rule
|
|
||||||
WithAddressLimit() bool
|
|
||||||
MatchAddressLimit(metadata *InboundContext) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type RuleAction interface {
|
|
||||||
Type() string
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsFinalAction(action RuleAction) bool {
|
|
||||||
switch action.Type() {
|
|
||||||
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,165 +4,112 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUpstreamHandlerEx(
|
func NewUpstreamHandler(
|
||||||
metadata InboundContext,
|
metadata InboundContext,
|
||||||
connectionHandler ConnectionHandlerFuncEx,
|
connectionHandler ConnectionHandlerFunc,
|
||||||
packetHandler PacketConnectionHandlerFuncEx,
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
) UpstreamHandlerAdapterEx {
|
errorHandler E.Handler,
|
||||||
return &myUpstreamHandlerWrapperEx{
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamHandlerWrapper{
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
|
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||||
|
|
||||||
type myUpstreamHandlerWrapperEx struct {
|
type myUpstreamHandlerWrapper struct {
|
||||||
metadata InboundContext
|
metadata InboundContext
|
||||||
connectionHandler ConnectionHandlerFuncEx
|
connectionHandler ConnectionHandlerFunc
|
||||||
packetHandler PacketConnectionHandlerFuncEx
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if source.IsValid() {
|
if metadata.Source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = metadata.Source
|
||||||
}
|
}
|
||||||
if destination.IsValid() {
|
if metadata.Destination.IsValid() {
|
||||||
myMetadata.Destination = destination
|
myMetadata.Destination = metadata.Destination
|
||||||
}
|
}
|
||||||
w.connectionHandler(ctx, conn, myMetadata, onClose)
|
return w.connectionHandler(ctx, conn, myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if source.IsValid() {
|
if metadata.Source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = metadata.Source
|
||||||
}
|
}
|
||||||
if destination.IsValid() {
|
if metadata.Destination.IsValid() {
|
||||||
myMetadata.Destination = destination
|
myMetadata.Destination = metadata.Destination
|
||||||
}
|
}
|
||||||
w.packetHandler(ctx, conn, myMetadata, onClose)
|
return w.packetHandler(ctx, conn, myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
|
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
type myUpstreamContextHandlerWrapperEx struct {
|
|
||||||
connectionHandler ConnectionHandlerFuncEx
|
|
||||||
packetHandler PacketConnectionHandlerFuncEx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUpstreamContextHandlerEx(
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
connectionHandler ConnectionHandlerFuncEx,
|
return M.Metadata{
|
||||||
packetHandler PacketConnectionHandlerFuncEx,
|
Source: metadata.Source,
|
||||||
) UpstreamHandlerAdapterEx {
|
Destination: metadata.Destination,
|
||||||
return &myUpstreamContextHandlerWrapperEx{
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type myUpstreamContextHandlerWrapper struct {
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpstreamContextHandler(
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamContextHandlerWrapper{
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if metadata.Source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = metadata.Source
|
||||||
}
|
}
|
||||||
if destination.IsValid() {
|
if metadata.Destination.IsValid() {
|
||||||
myMetadata.Destination = destination
|
myMetadata.Destination = metadata.Destination
|
||||||
}
|
}
|
||||||
w.connectionHandler(ctx, conn, *myMetadata, onClose)
|
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if metadata.Source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = metadata.Source
|
||||||
}
|
}
|
||||||
if destination.IsValid() {
|
if metadata.Destination.IsValid() {
|
||||||
myMetadata.Destination = destination
|
myMetadata.Destination = metadata.Destination
|
||||||
}
|
}
|
||||||
w.packetHandler(ctx, conn, *myMetadata, onClose)
|
return w.packetHandler(ctx, conn, *myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouteHandlerEx(
|
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
metadata InboundContext,
|
w.errorHandler.NewError(ctx, err)
|
||||||
router ConnectionRouterEx,
|
|
||||||
) UpstreamHandlerAdapterEx {
|
|
||||||
return &routeHandlerWrapperEx{
|
|
||||||
metadata: metadata,
|
|
||||||
router: router,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
|
|
||||||
|
|
||||||
type routeHandlerWrapperEx struct {
|
|
||||||
metadata InboundContext
|
|
||||||
router ConnectionRouterEx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
|
||||||
if source.IsValid() {
|
|
||||||
r.metadata.Source = source
|
|
||||||
}
|
|
||||||
if destination.IsValid() {
|
|
||||||
r.metadata.Destination = destination
|
|
||||||
}
|
|
||||||
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
|
||||||
if source.IsValid() {
|
|
||||||
r.metadata.Source = source
|
|
||||||
}
|
|
||||||
if destination.IsValid() {
|
|
||||||
r.metadata.Destination = destination
|
|
||||||
}
|
|
||||||
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouteContextHandlerEx(
|
|
||||||
router ConnectionRouterEx,
|
|
||||||
) UpstreamHandlerAdapterEx {
|
|
||||||
return &routeContextHandlerWrapperEx{
|
|
||||||
router: router,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
|
|
||||||
|
|
||||||
type routeContextHandlerWrapperEx struct {
|
|
||||||
router ConnectionRouterEx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
|
||||||
metadata := ContextFrom(ctx)
|
|
||||||
if source.IsValid() {
|
|
||||||
metadata.Source = source
|
|
||||||
}
|
|
||||||
if destination.IsValid() {
|
|
||||||
metadata.Destination = destination
|
|
||||||
}
|
|
||||||
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
|
||||||
metadata := ContextFrom(ctx)
|
|
||||||
if source.IsValid() {
|
|
||||||
metadata.Source = source
|
|
||||||
}
|
|
||||||
if destination.IsValid() {
|
|
||||||
metadata.Destination = destination
|
|
||||||
}
|
|
||||||
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Deprecated
|
|
||||||
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
|
||||||
// Deprecated
|
|
||||||
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
//
|
|
||||||
//nolint:staticcheck
|
|
||||||
func NewUpstreamHandler(
|
|
||||||
metadata InboundContext,
|
|
||||||
connectionHandler ConnectionHandlerFunc,
|
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
|
||||||
errorHandler E.Handler,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &myUpstreamHandlerWrapper{
|
|
||||||
metadata: metadata,
|
|
||||||
connectionHandler: connectionHandler,
|
|
||||||
packetHandler: packetHandler,
|
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
|
||||||
|
|
||||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
|
||||||
//
|
|
||||||
//nolint:staticcheck
|
|
||||||
type myUpstreamHandlerWrapper struct {
|
|
||||||
metadata InboundContext
|
|
||||||
connectionHandler ConnectionHandlerFunc
|
|
||||||
packetHandler PacketConnectionHandlerFunc
|
|
||||||
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() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
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
|
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
|
||||||
return M.Metadata{
|
|
||||||
Source: metadata.Source,
|
|
||||||
Destination: metadata.Destination,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
|
||||||
type myUpstreamContextHandlerWrapper struct {
|
|
||||||
connectionHandler ConnectionHandlerFunc
|
|
||||||
packetHandler PacketConnectionHandlerFunc
|
|
||||||
errorHandler E.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
|
||||||
func NewUpstreamContextHandler(
|
|
||||||
connectionHandler ConnectionHandlerFunc,
|
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
|
||||||
errorHandler E.Handler,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &myUpstreamContextHandlerWrapper{
|
|
||||||
connectionHandler: connectionHandler,
|
|
||||||
packetHandler: packetHandler,
|
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.packetHandler(ctx, conn, *myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
|
||||||
w.errorHandler.NewError(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use ConnectionRouterEx instead.
|
|
||||||
func NewRouteHandler(
|
|
||||||
metadata InboundContext,
|
|
||||||
router ConnectionRouter,
|
|
||||||
logger logger.ContextLogger,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &routeHandlerWrapper{
|
|
||||||
metadata: metadata,
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use ConnectionRouterEx instead.
|
|
||||||
func NewRouteContextHandler(
|
|
||||||
router ConnectionRouter,
|
|
||||||
logger logger.ContextLogger,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &routeContextHandlerWrapper{
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
|
||||||
|
|
||||||
// Deprecated: Use ConnectionRouterEx instead.
|
|
||||||
type routeContextHandlerWrapper struct {
|
|
||||||
router ConnectionRouter
|
|
||||||
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() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ type V2RayServerTransport interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServerTransportHandler interface {
|
type V2RayServerTransportHandler interface {
|
||||||
N.TCPConnectionHandlerEx
|
N.TCPConnectionHandler
|
||||||
|
E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayClientTransport interface {
|
type V2RayClientTransport interface {
|
||||||
|
|||||||
455
box.go
455
box.go
@@ -9,26 +9,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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/dialer"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/inbound"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
@@ -37,55 +30,24 @@ var _ adapter.Service = (*Box)(nil)
|
|||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
|
router adapter.Router
|
||||||
|
inbounds []adapter.Inbound
|
||||||
|
outbounds []adapter.Outbound
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
preServices1 map[string]adapter.Service
|
||||||
endpoint *endpoint.Manager
|
preServices2 map[string]adapter.Service
|
||||||
inbound *inbound.Manager
|
postServices map[string]adapter.Service
|
||||||
outbound *outbound.Manager
|
|
||||||
dnsTransport *dns.TransportManager
|
|
||||||
dnsRouter *dns.Router
|
|
||||||
connection *route.ConnectionManager
|
|
||||||
router *route.Router
|
|
||||||
services []adapter.LifecycleService
|
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
option.Options
|
option.Options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
|
PlatformInterface platform.Interface
|
||||||
PlatformLogWriter log.PlatformWriter
|
PlatformLogWriter log.PlatformWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func Context(
|
|
||||||
ctx context.Context,
|
|
||||||
inboundRegistry adapter.InboundRegistry,
|
|
||||||
outboundRegistry adapter.OutboundRegistry,
|
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
|
||||||
) context.Context {
|
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
|
|
||||||
}
|
|
||||||
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
|
|
||||||
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
|
||||||
}
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(options Options) (*Box, error) {
|
func New(options Options) (*Box, error) {
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
ctx := options.Context
|
ctx := options.Context
|
||||||
@@ -93,22 +55,6 @@ func New(options Options) (*Box, error) {
|
|||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
|
|
||||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
|
||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](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")
|
|
||||||
}
|
|
||||||
if outboundRegistry == nil {
|
|
||||||
return nil, E.New("missing outbound registry in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
@@ -124,9 +70,8 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if options.PlatformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
}
|
}
|
||||||
logFactory, err := log.New(log.Options{
|
logFactory, err := log.New(log.Options{
|
||||||
@@ -140,203 +85,114 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
router, err := route.NewRouter(
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
ctx,
|
||||||
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
logFactory,
|
||||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
common.PtrValueOrDefault(options.Route),
|
||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
common.PtrValueOrDefault(options.DNS),
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
common.PtrValueOrDefault(options.NTP),
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
options.Inbounds,
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
options.PlatformInterface,
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
|
||||||
if err != nil {
|
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 := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
|
||||||
service.MustRegister[adapter.Router](ctx, router)
|
|
||||||
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
|
|
||||||
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, transportOptions := range dnsOptions.Servers {
|
|
||||||
var tag string
|
|
||||||
if transportOptions.Tag != "" {
|
|
||||||
tag = transportOptions.Tag
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
err = dnsTransportManager.Create(
|
|
||||||
ctx,
|
|
||||||
logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")),
|
|
||||||
tag,
|
|
||||||
transportOptions.Type,
|
|
||||||
transportOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = dnsRouter.Initialize(dnsOptions.Rules)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize dns router")
|
|
||||||
}
|
|
||||||
for i, endpointOptions := range options.Endpoints {
|
|
||||||
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, "]")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
||||||
|
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||||
for i, inboundOptions := range options.Inbounds {
|
for i, inboundOptions := range options.Inbounds {
|
||||||
|
var in adapter.Inbound
|
||||||
var tag string
|
var tag string
|
||||||
if inboundOptions.Tag != "" {
|
if inboundOptions.Tag != "" {
|
||||||
tag = inboundOptions.Tag
|
tag = inboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = inboundManager.Create(
|
in, err = inbound.New(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
inboundOptions.Type,
|
inboundOptions,
|
||||||
inboundOptions.Options,
|
options.PlatformInterface,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
}
|
}
|
||||||
|
inbounds = append(inbounds, in)
|
||||||
}
|
}
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
|
var out adapter.Outbound
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
tag = outboundOptions.Tag
|
tag = outboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
outboundCtx := ctx
|
out, err = outbound.New(
|
||||||
if tag != "" {
|
ctx,
|
||||||
// TODO: remove this
|
|
||||||
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
|
||||||
Outbound: tag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
err = outboundManager.Create(
|
|
||||||
outboundCtx,
|
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
outboundOptions.Type,
|
outboundOptions)
|
||||||
outboundOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||||
}
|
}
|
||||||
|
outbounds = append(outbounds, out)
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(common.Must1(
|
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||||
direct.NewOutbound(
|
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
||||||
ctx,
|
common.Must(oErr)
|
||||||
router,
|
outbounds = append(outbounds, out)
|
||||||
logFactory.NewLogger("outbound/direct"),
|
return out
|
||||||
"direct",
|
})
|
||||||
option.DirectOutboundOptions{},
|
if err != nil {
|
||||||
),
|
return nil, err
|
||||||
))
|
}
|
||||||
dnsTransportManager.Initialize(common.Must1(
|
if options.PlatformInterface != nil {
|
||||||
local.NewTransport(
|
err = options.PlatformInterface.Initialize(ctx, router)
|
||||||
ctx,
|
|
||||||
logFactory.NewLogger("dns/local"),
|
|
||||||
"local",
|
|
||||||
option.LocalDNSServerOptions{},
|
|
||||||
)))
|
|
||||||
if platformInterface != nil {
|
|
||||||
err = platformInterface.Initialize(networkManager)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
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 {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
if cacheFile == nil {
|
||||||
services = append(services, cacheFile)
|
cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
|
}
|
||||||
|
preServices1["cache file"] = cacheFile
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
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 {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash-server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
router.SetTracker(clashServer)
|
router.SetClashServer(clashServer)
|
||||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
preServices2["clash api"] = clashServer
|
||||||
services = append(services, clashServer)
|
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
if err != nil {
|
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.SetV2RayServer(v2rayServer)
|
||||||
router.SetTracker(v2rayServer.StatsService())
|
preServices2["v2ray api"] = v2rayServer
|
||||||
services = append(services, v2rayServer)
|
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ntpOptions.Enabled {
|
|
||||||
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
|
|
||||||
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"))
|
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
|
||||||
endpoint: endpointManager,
|
|
||||||
inbound: inboundManager,
|
|
||||||
outbound: outboundManager,
|
|
||||||
dnsTransport: dnsTransportManager,
|
|
||||||
dnsRouter: dnsRouter,
|
|
||||||
connection: connectionManager,
|
|
||||||
router: router,
|
router: router,
|
||||||
|
inbounds: inbounds,
|
||||||
|
outbounds: outbounds,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
services: services,
|
preServices1: preServices1,
|
||||||
|
preServices2: preServices2,
|
||||||
|
postServices: postServices,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -387,19 +243,35 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
return s.router.Start()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) start() error {
|
func (s *Box) start() error {
|
||||||
@@ -407,33 +279,64 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
for _, in := range s.inbounds {
|
||||||
if err != nil {
|
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
|
||||||
return err
|
err = lateInbound.PostStart()
|
||||||
}
|
if err != nil {
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
|
return E.Cause(err, "post-start inbound/", in.Tag())
|
||||||
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.dnsTransport, s.dnsRouter, 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
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -445,32 +348,58 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
monitor := taskmonitor.New(s.logger, C.StopTimeout)
|
||||||
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
var errors error
|
||||||
)
|
for serviceName, service := range s.postServices {
|
||||||
for _, lifecycleService := range s.services {
|
monitor.Start("close ", serviceName)
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
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 {
|
monitor.Finish()
|
||||||
return E.Cause(err, "close logger")
|
for serviceName, service := range s.preServices1 {
|
||||||
})
|
monitor.Start("close ", serviceName)
|
||||||
return err
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
}
|
return E.Cause(err, "close ", serviceName)
|
||||||
|
})
|
||||||
func (s *Box) Network() adapter.NetworkManager {
|
monitor.Finish()
|
||||||
return s.network
|
}
|
||||||
|
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 {
|
func (s *Box) Router() adapter.Router {
|
||||||
return s.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: b17fb6d857...2aa661d507
Submodule clients/apple updated: 64a4614aca...c223f50ffb
@@ -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/gomobile"
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -35,8 +31,8 @@ func main() {
|
|||||||
switch target {
|
switch target {
|
||||||
case "android":
|
case "android":
|
||||||
buildAndroid()
|
buildAndroid()
|
||||||
case "apple":
|
case "ios":
|
||||||
buildApple()
|
buildiOS()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,35 +62,9 @@ func init() {
|
|||||||
func buildAndroid() {
|
func buildAndroid() {
|
||||||
build_shared.FindSDK()
|
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{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
|
||||||
"-androidapi", "21",
|
"-androidapi", "21",
|
||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
@@ -116,7 +86,7 @@ func buildAndroid() {
|
|||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||||
command.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
command.Stderr = os.Stderr
|
command.Stderr = os.Stderr
|
||||||
err = command.Run()
|
err := command.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -133,20 +103,11 @@ func buildAndroid() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildApple() {
|
func buildiOS() {
|
||||||
var bindTarget string
|
|
||||||
if platform != "" {
|
|
||||||
bindTarget = platform
|
|
||||||
} else if debugEnabled {
|
|
||||||
bindTarget = "ios"
|
|
||||||
} else {
|
|
||||||
bindTarget = "ios,tvos,macos"
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", "ios,iossimulator,tvos,tvossimulator,macos",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/sing/common/shell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -40,6 +42,14 @@ func FindSDK() {
|
|||||||
log.Fatal("android NDK not found")
|
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_HOME", androidSDKPath)
|
||||||
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
||||||
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
||||||
@@ -48,16 +58,12 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "28.0.12674087"
|
const fixedVersion = "26.2.11394342"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
return true
|
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"))
|
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -80,7 +86,7 @@ func findNDK() bool {
|
|||||||
})
|
})
|
||||||
for _, versionName := range versionNames {
|
for _, versionName := range versionNames {
|
||||||
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
||||||
if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {
|
if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) {
|
||||||
androidNDKPath = currentNDKPath
|
androidNDKPath = currentNDKPath
|
||||||
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -20,11 +20,6 @@ func ReadTag() (string, error) {
|
|||||||
return version.String() + "-" + shortCommit, nil
|
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) {
|
func ReadTagVersion() (badversion.Version, error) {
|
||||||
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
|
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
|
||||||
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||||
|
|||||||
@@ -1,62 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nightly bool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
currentTag, err := build_shared.ReadTag()
|
||||||
if nightly {
|
if err != nil {
|
||||||
version, err := build_shared.ReadTagVersionRev()
|
log.Error(err)
|
||||||
if err != nil {
|
_, err = os.Stdout.WriteString("unknown\n")
|
||||||
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)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
tag, err := build_shared.ReadTag()
|
_, err = os.Stdout.WriteString(currentTag + "\n")
|
||||||
if err != nil {
|
}
|
||||||
log.Error(err)
|
if err != nil {
|
||||||
os.Stdout.WriteString("unknown\n")
|
log.Error(err)
|
||||||
} else {
|
|
||||||
os.Stdout.WriteString(tag + "\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -13,22 +12,9 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var flagRunInCI bool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
newVersion := common.Must1(build_shared.ReadTagVersion())
|
||||||
newVersion := common.Must1(build_shared.ReadTag())
|
androidPath, err := filepath.Abs("../sing-box-for-android")
|
||||||
var androidPath string
|
|
||||||
if flagRunInCI {
|
|
||||||
androidPath = "clients/android"
|
|
||||||
} else {
|
|
||||||
androidPath = "../sing-box-for-android"
|
|
||||||
}
|
|
||||||
androidPath, err := filepath.Abs(androidPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -45,10 +31,10 @@ func main() {
|
|||||||
for _, propPair := range propsList {
|
for _, propPair := range propsList {
|
||||||
switch propPair[0] {
|
switch propPair[0] {
|
||||||
case "VERSION_NAME":
|
case "VERSION_NAME":
|
||||||
if propPair[1] != newVersion {
|
if propPair[1] != newVersion.String() {
|
||||||
versionUpdated = true
|
versionUpdated = true
|
||||||
propPair[1] = newVersion
|
propPair[1] = newVersion.String()
|
||||||
log.Info("updated version to ", newVersion)
|
log.Info("updated version to ", newVersion.String())
|
||||||
}
|
}
|
||||||
case "GO_VERSION":
|
case "GO_VERSION":
|
||||||
if propPair[1] != runtime.Version() {
|
if propPair[1] != runtime.Version() {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -14,22 +13,9 @@ import (
|
|||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
)
|
)
|
||||||
|
|
||||||
var flagRunInCI bool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
|
||||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
newVersion := common.Must1(build_shared.ReadTagVersion())
|
||||||
var applePath string
|
applePath, err := filepath.Abs("../sing-box-for-apple")
|
||||||
if flagRunInCI {
|
|
||||||
applePath = "clients/apple"
|
|
||||||
} else {
|
|
||||||
applePath = "../sing-box-for-apple"
|
|
||||||
}
|
|
||||||
applePath, err := filepath.Abs(applePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
_ "github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -68,6 +65,4 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
|
||||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func check() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(globalCtx)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func format() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, optionsEntry := range optionsList {
|
for _, optionsEntry := range optionsList {
|
||||||
optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options)
|
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var commandMerge = &cobra.Command{
|
var commandMerge = &cobra.Command{
|
||||||
Use: "merge <output-path>",
|
Use: "merge <output>",
|
||||||
Short: "Merge configurations",
|
Short: "Merge configurations",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := merge(args[0])
|
err := merge(args[0])
|
||||||
@@ -68,19 +68,29 @@ func merge(outputPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mergePathResources(options *option.Options) error {
|
func mergePathResources(options *option.Options) error {
|
||||||
for _, inbound := range options.Inbounds {
|
for index, inbound := range options.Inbounds {
|
||||||
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
rawOptions, err := inbound.RawOptions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
||||||
}
|
}
|
||||||
|
options.Inbounds[index] = inbound
|
||||||
}
|
}
|
||||||
for _, outbound := range options.Outbounds {
|
for index, outbound := range options.Outbounds {
|
||||||
|
rawOptions, err := outbound.RawOptions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
switch outbound.Type {
|
switch outbound.Type {
|
||||||
case C.TypeSSH:
|
case C.TypeSSH:
|
||||||
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
|
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
|
||||||
}
|
}
|
||||||
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
||||||
}
|
}
|
||||||
|
options.Outbounds[index] = outbound
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -128,12 +138,13 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
|
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
||||||
if options.PrivateKeyPath != "" {
|
if options.PrivateKeyPath != "" {
|
||||||
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
||||||
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimStringArray(array []string) []string {
|
func trimStringArray(array []string) []string {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -55,6 +56,10 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ruleSet, err := plainRuleSet.Upgrade()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".json") {
|
if strings.HasSuffix(sourcePath, ".json") {
|
||||||
@@ -69,7 +74,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -78,7 +77,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outputFile.Close()
|
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 {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -46,10 +48,14 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ruleSet, err := srs.Read(reader, true)
|
plainRuleSet, err := srs.Read(reader, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ruleSet := option.PlainRuleSetCompat{
|
||||||
|
Version: C.RuleSetVersion1,
|
||||||
|
Options: plainRuleSet,
|
||||||
|
}
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -56,25 +55,26 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read rule-set")
|
return E.Cause(err, "read rule-set")
|
||||||
}
|
}
|
||||||
var ruleSet option.PlainRuleSetCompat
|
var plainRuleSet option.PlainRuleSet
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case C.RuleSetFormatBinary:
|
case C.RuleSetFormatBinary:
|
||||||
ruleSet, err = srs.Read(bytes.NewReader(content), false)
|
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
||||||
}
|
}
|
||||||
plainRuleSet, err := ruleSet.Upgrade()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ipAddress := M.ParseAddr(domain)
|
ipAddress := M.ParseAddr(domain)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
if ipAddress.IsValid() {
|
if ipAddress.IsValid() {
|
||||||
@@ -84,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
}
|
}
|
||||||
for i, ruleOptions := range plainRuleSet.Rules {
|
for i, ruleOptions := range plainRuleSet.Rules {
|
||||||
var currentRule adapter.HeadlessRule
|
var currentRule adapter.HeadlessRule
|
||||||
currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions)
|
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
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
|
|
||||||
}
|
|
||||||
@@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read config at ", path)
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
}
|
}
|
||||||
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)
|
options, err := json.UnmarshalExtended[option.Options](configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode config at ", path)
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
}
|
}
|
||||||
@@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
|
|||||||
}
|
}
|
||||||
var mergedMessage json.RawMessage
|
var mergedMessage json.RawMessage
|
||||||
for _, options := range optionsList {
|
for _, options := range optionsList {
|
||||||
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var mergedOptions option.Options
|
var mergedOptions option.Options
|
||||||
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
|
err = mergedOptions.UnmarshalJSON(mergedMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -26,11 +23,9 @@ func init() {
|
|||||||
func createPreStartedClient() (*box.Box, error) {
|
func createPreStartedClient() (*box.Box, error) {
|
||||||
options, err := readConfigAndMerge()
|
options, err := readConfigAndMerge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
|
instance, err := box.New(box.Options{Options: options})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create service")
|
return nil, E.Cause(err, "create service")
|
||||||
}
|
}
|
||||||
@@ -41,11 +36,11 @@ func createPreStartedClient() (*box.Box, error) {
|
|||||||
return instance, nil
|
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 == "" {
|
if outboundTag == "" {
|
||||||
return instance.Outbound().Default(), nil
|
return instance.Router().DefaultOutbound(N.NetworkName(network))
|
||||||
} else {
|
} else {
|
||||||
outbound, loaded := instance.Outbound().Outbound(outboundTag)
|
outbound, loaded := instance.Router().Outbound(outboundTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound not found: ", outboundTag)
|
return nil, E.New("outbound not found: ", outboundTag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func connect(address string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer instance.Close()
|
defer instance.Close()
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func fetch(args []string) error {
|
|||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initializeHTTP3Client(instance *box.Box) error {
|
func initializeHTTP3Client(instance *box.Box) error {
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
http3Client = &http.Client{
|
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) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
destination := M.ParseSocksaddr(addr)
|
destination := M.ParseSocksaddr(addr)
|
||||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -43,7 +45,7 @@ func syncTime() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -57,7 +59,7 @@ func syncTime() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if commandSyncTimeWrite {
|
if commandSyncTimeWrite {
|
||||||
err = ntp.SetSystemTime(response.Time)
|
err = settings.SetSystemTime(response.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "write time to system")
|
return E.Cause(err, "write time to system")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package adguard
|
package adguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -27,7 +26,7 @@ example.arpa
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.org",
|
"example.org",
|
||||||
@@ -86,7 +85,7 @@ func TestHosts(t *testing.T) {
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"google.com",
|
"google.com",
|
||||||
@@ -116,7 +115,7 @@ www.example.org
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|||||||
@@ -2,132 +2,73 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var _ WireGuardListener = (*DefaultDialer)(nil)
|
||||||
_ ParallelInterfaceDialer = (*DefaultDialer)(nil)
|
|
||||||
_ WireGuardListener = (*DefaultDialer)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
dialer4 tcpDialer
|
dialer4 tcpDialer
|
||||||
dialer6 tcpDialer
|
dialer6 tcpDialer
|
||||||
udpDialer4 net.Dialer
|
udpDialer4 net.Dialer
|
||||||
udpDialer6 net.Dialer
|
udpDialer6 net.Dialer
|
||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
isWireGuardListener bool
|
isWireGuardListener bool
|
||||||
networkManager adapter.NetworkManager
|
|
||||||
networkStrategy *C.NetworkStrategy
|
|
||||||
defaultNetworkStrategy bool
|
|
||||||
networkType []C.InterfaceType
|
|
||||||
fallbackNetworkType []C.InterfaceType
|
|
||||||
networkFallbackDelay time.Duration
|
|
||||||
networkLastFallback atomic.TypedValue[time.Time]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
var dialer net.Dialer
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
var listener net.ListenConfig
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
|
var interfaceFinder control.InterfaceFinder
|
||||||
|
if router != nil {
|
||||||
|
interfaceFinder = router.InterfaceFinder()
|
||||||
|
} else {
|
||||||
|
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||||
|
}
|
||||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else if 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 {
|
if options.RoutingMark > 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||||
}
|
|
||||||
if networkManager != nil {
|
|
||||||
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
|
|
||||||
if autoRedirectOutputMark > 0 {
|
if autoRedirectOutputMark > 0 {
|
||||||
if options.RoutingMark > 0 {
|
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
} else if router != nil && router.DefaultMark() > 0 {
|
||||||
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
if disableDefaultBind || options.TCPFastOpen {
|
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 {
|
if autoRedirectOutputMark > 0 {
|
||||||
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`")
|
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
@@ -140,7 +81,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if options.ConnectTimeout != 0 {
|
if options.ConnectTimeout != 0 {
|
||||||
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPConnectTimeout
|
dialer.Timeout = C.TCPTimeout
|
||||||
}
|
}
|
||||||
// TODO: Add an option to customize the keep alive period
|
// TODO: Add an option to customize the keep alive period
|
||||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
dialer.KeepAlive = C.TCPKeepAliveInitial
|
||||||
@@ -161,7 +102,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
)
|
)
|
||||||
if options.Inet4BindAddress != nil {
|
if options.Inet4BindAddress != nil {
|
||||||
bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified())
|
bindAddr := options.Inet4BindAddress.Build()
|
||||||
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
@@ -172,7 +113,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
)
|
)
|
||||||
if options.Inet6BindAddress != nil {
|
if options.Inet6BindAddress != nil {
|
||||||
bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified())
|
bindAddr := options.Inet6BindAddress.Build()
|
||||||
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
@@ -184,7 +125,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
setMultiPathTCP(&dialer4)
|
setMultiPathTCP(&dialer4)
|
||||||
}
|
}
|
||||||
if options.IsWireGuardListener {
|
if options.IsWireGuardListener {
|
||||||
for _, controlFn := range WgControlFns {
|
for _, controlFn := range wgControlFns {
|
||||||
listener.Control = control.Append(listener.Control, controlFn)
|
listener.Control = control.Append(listener.Control, controlFn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,20 +138,14 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &DefaultDialer{
|
return &DefaultDialer{
|
||||||
dialer4: tcpDialer4,
|
tcpDialer4,
|
||||||
dialer6: tcpDialer6,
|
tcpDialer6,
|
||||||
udpDialer4: udpDialer4,
|
udpDialer4,
|
||||||
udpDialer6: udpDialer6,
|
udpDialer6,
|
||||||
udpListener: listener,
|
listener,
|
||||||
udpAddr4: udpAddr4,
|
udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6,
|
||||||
isWireGuardListener: options.IsWireGuardListener,
|
options.IsWireGuardListener,
|
||||||
networkManager: networkManager,
|
|
||||||
networkStrategy: networkStrategy,
|
|
||||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
|
||||||
networkType: networkType,
|
|
||||||
fallbackNetworkType: fallbackNetworkType,
|
|
||||||
networkFallbackDelay: networkFallbackDelay,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,122 +153,33 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == nil {
|
switch N.NetworkName(network) {
|
||||||
switch N.NetworkName(network) {
|
case N.NetworkUDP:
|
||||||
case N.NetworkUDP:
|
|
||||||
if !address.IsIPv6() {
|
|
||||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
|
||||||
} else {
|
|
||||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||||
} else {
|
} else {
|
||||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||||
}
|
|
||||||
} 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !fastFallback && !isPrimary {
|
if !address.IsIPv6() {
|
||||||
d.networkLastFallback.Store(time.Now())
|
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||||
|
} else {
|
||||||
|
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||||
}
|
}
|
||||||
return trackConn(conn, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == nil {
|
if destination.IsIPv6() {
|
||||||
if destination.IsIPv6() {
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
|
||||||
} else {
|
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
|
||||||
}
|
|
||||||
} else {
|
} 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 trackPacketConn(packetConn, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
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 trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
|
|||||||
@@ -13,7 +13,3 @@ type tcpDialer = tfo.Dialer
|
|||||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||||
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
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
|
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 {
|
type DetourDialer struct {
|
||||||
outboundManager adapter.OutboundManager
|
router adapter.Router
|
||||||
detour string
|
detour string
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
initErr error
|
initErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer {
|
func NewDetour(router adapter.Router, detour string) N.Dialer {
|
||||||
return &DetourDialer{outboundManager: outboundManager, detour: detour}
|
return &DetourDialer{router: router, detour: detour}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Start() error {
|
func (d *DetourDialer) Start() error {
|
||||||
@@ -31,7 +31,7 @@ func (d *DetourDialer) Start() error {
|
|||||||
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
||||||
d.initOnce.Do(func() {
|
d.initOnce.Do(func() {
|
||||||
var loaded bool
|
var loaded bool
|
||||||
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
|
d.dialer, loaded = d.router.Outbound(d.detour)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
d.initErr = E.New("outbound detour not found: ", d.detour)
|
d.initErr = E.New("outbound detour not found: ", d.detour)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +1,40 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
"github.com/sagernet/sing-dns"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) {
|
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
|
||||||
if options.IsWireGuardListener {
|
if options.IsWireGuardListener {
|
||||||
return NewDefault(ctx, options)
|
return NewDefault(router, options)
|
||||||
|
}
|
||||||
|
if router == nil {
|
||||||
|
return NewDefault(nil, options)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if options.Detour == "" {
|
if options.Detour == "" {
|
||||||
dialer, err = NewDefault(ctx, options)
|
dialer, err = NewDefault(router, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
outboundManager := service.FromContext[adapter.OutboundManager](ctx)
|
dialer = NewDetour(router, options.Detour)
|
||||||
if outboundManager == nil {
|
|
||||||
return nil, E.New("missing outbound manager")
|
|
||||||
}
|
|
||||||
dialer = NewDetour(outboundManager, options.Detour)
|
|
||||||
}
|
}
|
||||||
if remoteIsDomain && options.Detour == "" && options.DomainResolver == "" {
|
if options.Detour == "" {
|
||||||
deprecated.Report(ctx, deprecated.OptionMissingDomainResolverInDialOptions)
|
dialer = NewResolveDialer(
|
||||||
}
|
router,
|
||||||
if (options.Detour == "" && remoteIsDomain) || options.DomainResolver != "" {
|
dialer,
|
||||||
router := service.FromContext[adapter.DNSRouter](ctx)
|
options.Detour == "" && !options.TCPFastOpen,
|
||||||
if router != nil {
|
dns.DomainStrategy(options.DomainStrategy),
|
||||||
var resolveTransport adapter.DNSTransport
|
time.Duration(options.FallbackDelay))
|
||||||
if options.DomainResolver != "" {
|
|
||||||
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("DNS server not found: " + options.DomainResolver)
|
|
||||||
}
|
|
||||||
resolveTransport = transport
|
|
||||||
}
|
|
||||||
dialer = NewResolveDialer(
|
|
||||||
router,
|
|
||||||
dialer,
|
|
||||||
options.Detour == "" && !options.TCPFastOpen,
|
|
||||||
resolveTransport,
|
|
||||||
C.DomainStrategy(options.DomainStrategy),
|
|
||||||
time.Duration(options.FallbackDelay))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return dialer, nil
|
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
|
|
||||||
}
|
|
||||||
var resolveTransport adapter.DNSTransport
|
|
||||||
if options.DomainResolver != "" {
|
|
||||||
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("DNS server not found: " + options.DomainResolver)
|
|
||||||
}
|
|
||||||
resolveTransport = transport
|
|
||||||
}
|
|
||||||
return NewResolveParallelInterfaceDialer(
|
|
||||||
service.FromContext[adapter.DNSRouter](ctx),
|
|
||||||
dialer,
|
|
||||||
true,
|
|
||||||
resolveTransport,
|
|
||||||
C.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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,82 +3,75 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type ResolveDialer struct {
|
||||||
_ N.Dialer = (*resolveDialer)(nil)
|
|
||||||
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type resolveDialer struct {
|
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
parallel bool
|
parallel bool
|
||||||
router adapter.DNSRouter
|
router adapter.Router
|
||||||
transport adapter.DNSTransport
|
strategy dns.DomainStrategy
|
||||||
strategy C.DomainStrategy
|
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
|
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
||||||
return &resolveDialer{
|
return &ResolveDialer{
|
||||||
dialer,
|
dialer,
|
||||||
parallel,
|
parallel,
|
||||||
router,
|
router,
|
||||||
transport,
|
|
||||||
strategy,
|
strategy,
|
||||||
fallbackDelay,
|
fallbackDelay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type resolveParallelNetworkDialer struct {
|
func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
resolveDialer
|
|
||||||
dialer ParallelInterfaceDialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
|
|
||||||
return &resolveParallelNetworkDialer{
|
|
||||||
resolveDialer{
|
|
||||||
dialer,
|
|
||||||
parallel,
|
|
||||||
router,
|
|
||||||
transport,
|
|
||||||
strategy,
|
|
||||||
fallbackDelay,
|
|
||||||
},
|
|
||||||
dialer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if d.parallel {
|
if d.parallel {
|
||||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
} else {
|
} else {
|
||||||
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -89,41 +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
|
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
func (d *ResolveDialer) Upstream() any {
|
||||||
if !destination.IsFqdn() {
|
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
|
||||||
}
|
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
|
||||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: 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 == C.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 = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
|
||||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: 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 {
|
|
||||||
return d.dialer
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,27 +7,32 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultOutboundDialer struct {
|
type RouterDialer struct {
|
||||||
outbound adapter.OutboundManager
|
router adapter.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultOutbound(ctx context.Context) N.Dialer {
|
func NewRouter(router adapter.Router) N.Dialer {
|
||||||
return &DefaultOutboundDialer{
|
return &RouterDialer{router: router}
|
||||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
}
|
||||||
|
|
||||||
|
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) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return d.outbound.Default().DialContext(ctx, network, destination)
|
dialer, err := d.router.DefaultOutbound(N.NetworkUDP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *RouterDialer) Upstream() any {
|
||||||
return d.outbound.Default().ListenPacket(ctx, destination)
|
return d.router
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DefaultOutboundDialer) Upstream() any {
|
|
||||||
return d.outbound.Default()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,8 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WireGuardListener interface {
|
type WireGuardListener interface {
|
||||||
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var WgControlFns []control.Func
|
|
||||||
|
|||||||
11
common/dialer/wireguard_control.go
Normal file
11
common/dialer/wireguard_control.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build with_wireguard
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ WireGuardListener = (conn.Listener)(nil)
|
||||||
|
|
||||||
|
var wgControlFns = conn.ControlFns
|
||||||
9
common/dialer/wiregurad_stub.go
Normal file
9
common/dialer/wiregurad_stub.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !with_wireguard
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
var wgControlFns []control.Func
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
package listener
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/settings"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Listener struct {
|
|
||||||
ctx context.Context
|
|
||||||
logger logger.ContextLogger
|
|
||||||
network []string
|
|
||||||
listenOptions option.ListenOptions
|
|
||||||
connHandler adapter.ConnectionHandlerEx
|
|
||||||
packetHandler adapter.PacketHandlerEx
|
|
||||||
oobPacketHandler adapter.OOBPacketHandlerEx
|
|
||||||
threadUnsafePacketWriter bool
|
|
||||||
disablePacketOutput bool
|
|
||||||
setSystemProxy bool
|
|
||||||
systemProxySOCKS bool
|
|
||||||
|
|
||||||
tcpListener net.Listener
|
|
||||||
systemProxy settings.SystemProxy
|
|
||||||
udpConn *net.UDPConn
|
|
||||||
udpAddr M.Socksaddr
|
|
||||||
packetOutbound chan *N.PacketBuffer
|
|
||||||
packetOutboundClosed chan struct{}
|
|
||||||
shutdown atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Context context.Context
|
|
||||||
Logger logger.ContextLogger
|
|
||||||
Network []string
|
|
||||||
Listen option.ListenOptions
|
|
||||||
ConnectionHandler adapter.ConnectionHandlerEx
|
|
||||||
PacketHandler adapter.PacketHandlerEx
|
|
||||||
OOBPacketHandler adapter.OOBPacketHandlerEx
|
|
||||||
ThreadUnsafePacketWriter bool
|
|
||||||
DisablePacketOutput bool
|
|
||||||
SetSystemProxy bool
|
|
||||||
SystemProxySOCKS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(
|
|
||||||
options Options,
|
|
||||||
) *Listener {
|
|
||||||
return &Listener{
|
|
||||||
ctx: options.Context,
|
|
||||||
logger: options.Logger,
|
|
||||||
network: options.Network,
|
|
||||||
listenOptions: options.Listen,
|
|
||||||
connHandler: options.ConnectionHandler,
|
|
||||||
packetHandler: options.PacketHandler,
|
|
||||||
oobPacketHandler: options.OOBPacketHandler,
|
|
||||||
threadUnsafePacketWriter: options.ThreadUnsafePacketWriter,
|
|
||||||
disablePacketOutput: options.DisablePacketOutput,
|
|
||||||
setSystemProxy: options.SetSystemProxy,
|
|
||||||
systemProxySOCKS: options.SystemProxySOCKS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Start() error {
|
|
||||||
if common.Contains(l.network, N.NetworkTCP) {
|
|
||||||
_, err := l.ListenTCP()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go l.loopTCPIn()
|
|
||||||
}
|
|
||||||
if common.Contains(l.network, N.NetworkUDP) {
|
|
||||||
_, err := l.ListenUDP()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
l.packetOutboundClosed = make(chan struct{})
|
|
||||||
l.packetOutbound = make(chan *N.PacketBuffer, 64)
|
|
||||||
go l.loopUDPIn()
|
|
||||||
if !l.disablePacketOutput {
|
|
||||||
go l.loopUDPOut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if l.setSystemProxy {
|
|
||||||
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
|
|
||||||
var listenAddrString string
|
|
||||||
listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified())
|
|
||||||
if listenAddr.IsUnspecified() {
|
|
||||||
listenAddrString = "127.0.0.1"
|
|
||||||
} else {
|
|
||||||
listenAddrString = listenAddr.String()
|
|
||||||
}
|
|
||||||
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize system proxy")
|
|
||||||
}
|
|
||||||
err = systemProxy.Enable()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "set system proxy")
|
|
||||||
}
|
|
||||||
l.systemProxy = systemProxy
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) Close() error {
|
|
||||||
l.shutdown.Store(true)
|
|
||||||
var err error
|
|
||||||
if l.systemProxy != nil && l.systemProxy.IsEnabled() {
|
|
||||||
err = l.systemProxy.Disable()
|
|
||||||
}
|
|
||||||
return E.Errors(err, common.Close(
|
|
||||||
l.tcpListener,
|
|
||||||
common.PtrOrNil(l.udpConn),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) TCPListener() net.Listener {
|
|
||||||
return l.tcpListener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) UDPConn() *net.UDPConn {
|
|
||||||
return l.udpConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) ListenOptions() option.ListenOptions {
|
|
||||||
return l.listenOptions
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//go:build go1.23
|
|
||||||
|
|
||||||
package listener
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
|
|
||||||
listener.KeepAliveConfig = net.KeepAliveConfig{
|
|
||||||
Enable: true,
|
|
||||||
Idle: idle,
|
|
||||||
Interval: interval,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build !go1.23
|
|
||||||
|
|
||||||
package listener
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
|
|
||||||
listener.KeepAlive = idle
|
|
||||||
listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval))
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package listener
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"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/metacubex/tfo-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Listener) ListenTCP() (net.Listener, error) {
|
|
||||||
var err error
|
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
|
||||||
var tcpListener net.Listener
|
|
||||||
var listenConfig net.ListenConfig
|
|
||||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
|
||||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
|
||||||
if keepIdle == 0 {
|
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
|
||||||
}
|
|
||||||
keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval)
|
|
||||||
if keepInterval == 0 {
|
|
||||||
keepInterval = C.TCPKeepAliveInterval
|
|
||||||
}
|
|
||||||
setKeepAliveConfig(&listenConfig, keepIdle, keepInterval)
|
|
||||||
}
|
|
||||||
if l.listenOptions.TCPMultiPath {
|
|
||||||
if !go121Available {
|
|
||||||
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
|
|
||||||
}
|
|
||||||
setMultiPathTCP(&listenConfig)
|
|
||||||
}
|
|
||||||
if l.listenOptions.TCPFastOpen {
|
|
||||||
var tfoConfig tfo.ListenConfig
|
|
||||||
tfoConfig.ListenConfig = listenConfig
|
|
||||||
tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
|
||||||
} else {
|
|
||||||
tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
|
||||||
}
|
|
||||||
//nolint:staticcheck
|
|
||||||
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
|
||||||
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
|
||||||
}
|
|
||||||
l.tcpListener = tcpListener
|
|
||||||
return tcpListener, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) loopTCPIn() {
|
|
||||||
tcpListener := l.tcpListener
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
for {
|
|
||||||
conn, err := tcpListener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
//nolint:staticcheck
|
|
||||||
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
|
|
||||||
l.logger.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if l.shutdown.Load() && E.IsClosed(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.tcpListener.Close()
|
|
||||||
l.logger.Error("tcp listener closed: ", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
//nolint:staticcheck
|
|
||||||
metadata.InboundDetour = l.listenOptions.Detour
|
|
||||||
//nolint:staticcheck
|
|
||||||
metadata.InboundOptions = l.listenOptions.InboundOptions
|
|
||||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
|
||||||
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
|
||||||
ctx := log.ContextWithNewID(l.ctx)
|
|
||||||
l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
|
||||||
go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
package listener
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
|
||||||
var lc net.ListenConfig
|
|
||||||
var udpFragment bool
|
|
||||||
if l.listenOptions.UDPFragment != nil {
|
|
||||||
udpFragment = *l.listenOptions.UDPFragment
|
|
||||||
} else {
|
|
||||||
udpFragment = l.listenOptions.UDPFragmentDefault
|
|
||||||
}
|
|
||||||
if !udpFragment {
|
|
||||||
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
|
||||||
}
|
|
||||||
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
l.udpConn = udpConn.(*net.UDPConn)
|
|
||||||
l.udpAddr = bindAddr
|
|
||||||
l.logger.Info("udp server started at ", udpConn.LocalAddr())
|
|
||||||
return udpConn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) UDPAddr() M.Socksaddr {
|
|
||||||
return l.udpAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) PacketWriter() N.PacketWriter {
|
|
||||||
return (*packetWriter)(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) loopUDPIn() {
|
|
||||||
defer close(l.packetOutboundClosed)
|
|
||||||
var buffer *buf.Buffer
|
|
||||||
if !l.threadUnsafePacketWriter {
|
|
||||||
buffer = buf.NewPacket()
|
|
||||||
defer buffer.Release()
|
|
||||||
buffer.IncRef()
|
|
||||||
defer buffer.DecRef()
|
|
||||||
}
|
|
||||||
if l.oobPacketHandler != nil {
|
|
||||||
oob := make([]byte, 1024)
|
|
||||||
for {
|
|
||||||
if l.threadUnsafePacketWriter {
|
|
||||||
buffer = buf.NewPacket()
|
|
||||||
} else {
|
|
||||||
buffer.Reset()
|
|
||||||
}
|
|
||||||
n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
|
|
||||||
if err != nil {
|
|
||||||
if l.threadUnsafePacketWriter {
|
|
||||||
buffer.Release()
|
|
||||||
}
|
|
||||||
if l.shutdown.Load() && E.IsClosed(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.udpConn.Close()
|
|
||||||
l.logger.Error("udp listener closed: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buffer.Truncate(n)
|
|
||||||
l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for {
|
|
||||||
if l.threadUnsafePacketWriter {
|
|
||||||
buffer = buf.NewPacket()
|
|
||||||
} else {
|
|
||||||
buffer.Reset()
|
|
||||||
}
|
|
||||||
n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
|
|
||||||
if err != nil {
|
|
||||||
if l.threadUnsafePacketWriter {
|
|
||||||
buffer.Release()
|
|
||||||
}
|
|
||||||
if l.shutdown.Load() && E.IsClosed(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.udpConn.Close()
|
|
||||||
l.logger.Error("udp listener closed: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buffer.Truncate(n)
|
|
||||||
l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) loopUDPOut() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case packet := <-l.packetOutbound:
|
|
||||||
destination := packet.Destination.AddrPort()
|
|
||||||
_, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination)
|
|
||||||
packet.Buffer.Release()
|
|
||||||
N.PutPacketBuffer(packet)
|
|
||||||
if err != nil {
|
|
||||||
if l.shutdown.Load() && E.IsClosed(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.udpConn.Close()
|
|
||||||
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
case <-l.packetOutboundClosed:
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case packet := <-l.packetOutbound:
|
|
||||||
packet.Buffer.Release()
|
|
||||||
N.PutPacketBuffer(packet)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type packetWriter Listener
|
|
||||||
|
|
||||||
func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
packet := N.NewPacketBuffer()
|
|
||||||
packet.Buffer = buffer
|
|
||||||
packet.Destination = destination
|
|
||||||
select {
|
|
||||||
case w.packetOutbound <- packet:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
buffer.Release()
|
|
||||||
N.PutPacketBuffer(packet)
|
|
||||||
if w.shutdown.Load() {
|
|
||||||
return os.ErrClosed
|
|
||||||
}
|
|
||||||
w.logger.Trace("dropped packet to ", destination)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *packetWriter) WriteIsThreadUnsafe() {
|
|
||||||
}
|
|
||||||
@@ -15,11 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
router adapter.ConnectionRouterEx
|
router adapter.ConnectionRouter
|
||||||
service *mux.Service
|
service *mux.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) {
|
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return router, nil
|
return router, nil
|
||||||
}
|
}
|
||||||
@@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
|||||||
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||||
return log.ContextWithNewID(ctx)
|
return log.ContextWithNewID(ctx)
|
||||||
},
|
},
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
HandlerEx: adapter.NewRouteContextHandlerEx(router),
|
Handler: adapter.NewRouteContextHandler(router, logger),
|
||||||
Padding: options.Padding,
|
Padding: options.Padding,
|
||||||
Brutal: brutalOptions,
|
Brutal: brutalOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -52,29 +52,14 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
|||||||
return &Router{router, service}, nil
|
return &Router{router, service}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use RouteConnectionEx instead.
|
|
||||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
if metadata.Destination == mux.Destination {
|
if metadata.Destination == mux.Destination {
|
||||||
// TODO: check if WithContext is necessary
|
|
||||||
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
} else {
|
} else {
|
||||||
return r.router.RouteConnection(ctx, conn, metadata)
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use RoutePacketConnectionEx instead.
|
|
||||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
|
||||||
if metadata.Destination == mux.Destination {
|
|
||||||
r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
|
||||||
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
|
||||||
}
|
|
||||||
|
|||||||
32
common/mux/v2ray_legacy.go
Normal file
32
common/mux/v2ray_legacy.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
vmess "github.com/sagernet/sing-vmess"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type V2RayLegacyRouter struct {
|
||||||
|
router adapter.ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
|
||||||
|
return &V2RayLegacyRouter{router, logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
|
||||||
|
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
||||||
|
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
|
||||||
|
}
|
||||||
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *V2RayLegacyRouter) 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/shell"
|
"github.com/sagernet/sing/common/shell"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DarwinSystemProxy struct {
|
type DarwinSystemProxy struct {
|
||||||
@@ -25,7 +24,7 @@ type DarwinSystemProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
|
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 {
|
if interfaceMonitor == nil {
|
||||||
return nil, E.New("missing interface monitor")
|
return nil, E.New("missing interface monitor")
|
||||||
}
|
}
|
||||||
@@ -34,7 +33,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo
|
|||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
supportSOCKS: supportSOCKS,
|
supportSOCKS: supportSOCKS,
|
||||||
}
|
}
|
||||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate)
|
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
||||||
return proxy, nil
|
return proxy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,22 +65,25 @@ func (p *DarwinSystemProxy) Disable() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) {
|
func (p *DarwinSystemProxy) update(event int) {
|
||||||
if !p.isEnabled || defaultInterface == nil {
|
if event&tun.EventInterfaceUpdate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !p.isEnabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = p.update0()
|
_ = p.update0()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DarwinSystemProxy) update0() error {
|
func (p *DarwinSystemProxy) update0() error {
|
||||||
newInterface := p.monitor.DefaultInterface()
|
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||||
if p.interfaceName == newInterface.Name {
|
if p.interfaceName == newInterfaceName {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if p.interfaceName != "" {
|
if p.interfaceName != "" {
|
||||||
_ = p.Disable()
|
_ = p.Disable()
|
||||||
}
|
}
|
||||||
p.interfaceName = newInterface.Name
|
p.interfaceName = newInterfaceName
|
||||||
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
12
common/settings/time_stub.go
Normal file
12
common/settings/time_stub.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !(windows || linux || darwin)
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
14
common/settings/time_unix.go
Normal file
14
common/settings/time_unix.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
||||||
|
return unix.Settimeofday(&timeVal)
|
||||||
|
}
|
||||||
32
common/settings/time_windows.go
Normal file
32
common/settings/time_windows.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
var systemTime windows.Systemtime
|
||||||
|
systemTime.Year = uint16(nowTime.Year())
|
||||||
|
systemTime.Month = uint16(nowTime.Month())
|
||||||
|
systemTime.Day = uint16(nowTime.Day())
|
||||||
|
systemTime.Hour = uint16(nowTime.Hour())
|
||||||
|
systemTime.Minute = uint16(nowTime.Minute())
|
||||||
|
systemTime.Second = uint16(nowTime.Second())
|
||||||
|
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
|
||||||
|
|
||||||
|
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
proc := dllKernel32.NewProc("SetSystemTime")
|
||||||
|
|
||||||
|
_, _, err := proc.Call(
|
||||||
|
uintptr(unsafe.Pointer(&systemTime)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil && err.Error() != "The operation completed successfully." {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ type (
|
|||||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
||||||
)
|
)
|
||||||
|
|
||||||
func Skip(metadata *adapter.InboundContext) bool {
|
func Skip(metadata adapter.InboundContext) bool {
|
||||||
// skip server first protocols
|
// skip server first protocols
|
||||||
switch metadata.Destination.Port {
|
switch metadata.Destination.Port {
|
||||||
case 25, 465, 587:
|
case 25, 465, 587:
|
||||||
|
|||||||
@@ -38,13 +38,10 @@ const (
|
|||||||
ruleItemWIFIBSSID
|
ruleItemWIFIBSSID
|
||||||
ruleItemAdGuardDomain
|
ruleItemAdGuardDomain
|
||||||
ruleItemProcessPathRegex
|
ruleItemProcessPathRegex
|
||||||
ruleItemNetworkType
|
|
||||||
ruleItemNetworkIsExpensive
|
|
||||||
ruleItemNetworkIsConstrained
|
|
||||||
ruleItemFinal uint8 = 0xFF
|
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
|
var magicBytes [3]byte
|
||||||
_, err = io.ReadFull(reader, magicBytes[:])
|
_, err = io.ReadFull(reader, magicBytes[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -57,10 +54,10 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
|||||||
var version uint8
|
var version uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &version)
|
err = binary.Read(reader, binary.BigEndian, &version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruleSetCompat, err
|
return ruleSet, err
|
||||||
}
|
}
|
||||||
if version > C.RuleSetVersionCurrent {
|
if version > C.RuleSetVersion2 {
|
||||||
return ruleSetCompat, E.New("unsupported version: ", version)
|
return ruleSet, E.New("unsupported version: ", version)
|
||||||
}
|
}
|
||||||
compressReader, err := zlib.NewReader(reader)
|
compressReader, err := zlib.NewReader(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,10 +68,9 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ruleSetCompat.Version = version
|
ruleSet.Rules = make([]option.HeadlessRule, length)
|
||||||
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
|
|
||||||
for i := uint64(0); i < length; i++ {
|
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 {
|
if err != nil {
|
||||||
err = E.Cause(err, "read rule[", i, "]")
|
err = E.Cause(err, "read rule[", i, "]")
|
||||||
return
|
return
|
||||||
@@ -83,12 +79,18 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
|||||||
return
|
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[:])
|
_, err := writer.Write(MagicBytes[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -102,7 +104,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, rule := range ruleSet.Rules {
|
for _, rule := range ruleSet.Rules {
|
||||||
err = writeRule(bWriter, rule, generateVersion)
|
err = writeRule(bWriter, rule, generateUnstable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -133,12 +135,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err
|
|||||||
return
|
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 {
|
switch rule.Type {
|
||||||
case C.RuleTypeDefault:
|
case C.RuleTypeDefault:
|
||||||
return writeDefaultRule(writer, rule.DefaultOptions, generateVersion)
|
return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable)
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
return writeLogicalRule(writer, rule.LogicalOptions, generateVersion)
|
return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable)
|
||||||
default:
|
default:
|
||||||
panic("unknown rule type: " + rule.Type)
|
panic("unknown rule type: " + rule.Type)
|
||||||
}
|
}
|
||||||
@@ -225,12 +227,6 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
rule.AdGuardDomainMatcher = matcher
|
rule.AdGuardDomainMatcher = matcher
|
||||||
case ruleItemNetworkType:
|
|
||||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
|
||||||
case ruleItemNetworkIsExpensive:
|
|
||||||
rule.NetworkIsExpensive = true
|
|
||||||
case ruleItemNetworkIsConstrained:
|
|
||||||
rule.NetworkIsConstrained = true
|
|
||||||
case ruleItemFinal:
|
case ruleItemFinal:
|
||||||
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||||
return
|
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))
|
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -268,7 +264,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -345,27 +341,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
return err
|
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 {
|
if len(rule.WIFISSID) > 0 {
|
||||||
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -379,9 +354,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rule.AdGuardDomain) > 0 {
|
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)
|
err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -414,18 +386,6 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
|
|||||||
return varbin.Write(writer, binary.BigEndian, value)
|
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) {
|
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
||||||
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
||||||
}
|
}
|
||||||
@@ -497,7 +457,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
|
|||||||
return
|
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))
|
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -518,7 +478,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, rule := range logicalRule.Rules {
|
for _, rule := range logicalRule.Rules {
|
||||||
err = writeRule(writer, rule, generateVersion)
|
err = writeRule(writer, rule, generateUnstable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ import (
|
|||||||
|
|
||||||
cftls "github.com/sagernet/cloudflare-tls"
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -64,7 +63,6 @@ type echConnWrapper struct {
|
|||||||
|
|
||||||
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
//nolint:staticcheck
|
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
@@ -215,7 +213,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response, err := service.FromContext[adapter.DNSRouter](ctx).Exchange(ctx, message, adapter.DNSQueryOptions{})
|
response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,9 +147,6 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
|
|||||||
pair.rawConf = b
|
pair.rawConf = b
|
||||||
|
|
||||||
secBuf, err := sec.MarshalBinary()
|
secBuf, err := sec.MarshalBinary()
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "serialize ECH private key")
|
|
||||||
}
|
|
||||||
sk := []byte{}
|
sk := []byte{}
|
||||||
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
sk = append(sk, secBuf...)
|
sk = append(sk, secBuf...)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
||||||
return &http3.Transport{
|
return &http3.RoundTripper{
|
||||||
TLSClientConfig: c.config,
|
TLSClientConfig: c.config,
|
||||||
QUICConfig: quicConfig,
|
QUICConfig: quicConfig,
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
|||||||
@@ -97,10 +97,6 @@ func (c *echServerConfig) startWatcher() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = watcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.watcher = watcher
|
c.watcher = watcher
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -236,7 +232,7 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
var echKey []byte
|
var echKey []byte
|
||||||
if len(options.ECH.Key) > 0 {
|
if len(options.ECH.Key) > 0 {
|
||||||
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
||||||
} else if options.ECH.KeyPath != "" {
|
} else if options.KeyPath != "" {
|
||||||
content, err := os.ReadFile(options.ECH.KeyPath)
|
content, err := os.ReadFile(options.ECH.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read ECH key")
|
return nil, E.Cause(err, "read ECH key")
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
return nil, E.New("reality verification failed")
|
return nil, E.New("reality verification failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &realityClientConnWrapper{uConn}, nil
|
return &utlsConnWrapper{uConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
@@ -249,36 +249,3 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type realityClientConnWrapper struct {
|
|
||||||
*utls.UConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState {
|
|
||||||
state := c.Conn.ConnectionState()
|
|
||||||
//nolint:staticcheck
|
|
||||||
return tls.ConnectionState{
|
|
||||||
Version: state.Version,
|
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
|
||||||
DidResume: state.DidResume,
|
|
||||||
CipherSuite: state.CipherSuite,
|
|
||||||
NegotiatedProtocol: state.NegotiatedProtocol,
|
|
||||||
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
|
||||||
ServerName: state.ServerName,
|
|
||||||
PeerCertificates: state.PeerCertificates,
|
|
||||||
VerifiedChains: state.VerifiedChains,
|
|
||||||
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
|
||||||
OCSPResponse: state.OCSPResponse,
|
|
||||||
TLSUnique: state.TLSUnique,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityClientConnWrapper) Upstream() any {
|
|
||||||
return c.UConn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
|
||||||
// We fixed it by calling Close() directly.
|
|
||||||
func (c *realityClientConnWrapper) CloseWrite() error {
|
|
||||||
return c.Close()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/reality"
|
"github.com/sagernet/reality"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"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
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -174,7 +175,6 @@ type realityConnWrapper struct {
|
|||||||
|
|
||||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
//nolint:staticcheck
|
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
@@ -194,9 +194,3 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
|||||||
func (c *realityConnWrapper) Upstream() any {
|
func (c *realityConnWrapper) Upstream() any {
|
||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
|
||||||
// We fixed it by calling Close() directly.
|
|
||||||
func (c *realityConnWrapper) CloseWrite() error {
|
|
||||||
return c.Close()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -50,7 +51,9 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
serverName = serverAddress
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
|
serverName = serverAddress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if serverName == "" && !options.Insecure {
|
if serverName == "" && !options.Insecure {
|
||||||
return nil, E.New("missing server_name or insecure=true")
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
|
|||||||
@@ -106,10 +106,6 @@ func (c *STDServerConfig) startWatcher() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = watcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.watcher = watcher
|
c.watcher = watcher
|
||||||
return nil
|
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 {
|
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
//nolint:staticcheck
|
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
syscallConn syscall.Conn
|
|
||||||
ctx context.Context
|
|
||||||
firstPacketWritten bool
|
|
||||||
fallbackDelay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
|
||||||
syscallConn, _ := N.UnwrapReader(conn).(syscall.Conn)
|
|
||||||
return &Conn{
|
|
||||||
Conn: conn,
|
|
||||||
syscallConn: syscallConn,
|
|
||||||
ctx: ctx,
|
|
||||||
fallbackDelay: fallbackDelay,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
|
||||||
if !c.firstPacketWritten {
|
|
||||||
defer func() {
|
|
||||||
c.firstPacketWritten = true
|
|
||||||
}()
|
|
||||||
serverName := indexTLSServerName(b)
|
|
||||||
if serverName != nil {
|
|
||||||
tcpConn, isTCPConn := c.syscallConn.(interface {
|
|
||||||
SetNoDelay(bool) error
|
|
||||||
})
|
|
||||||
if isTCPConn {
|
|
||||||
err = tcpConn.SetNoDelay(true)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
splits := strings.Split(string(b[serverName.Index:serverName.Index+serverName.Length]), ".")
|
|
||||||
currentIndex := serverName.Index
|
|
||||||
var striped bool
|
|
||||||
if len(splits) > 3 {
|
|
||||||
suffix := splits[len(splits)-3] + "." + splits[len(splits)-2] + "." + splits[len(splits)-1]
|
|
||||||
if publicSuffixMatcher().Match(suffix) {
|
|
||||||
splits = splits[:len(splits)-3]
|
|
||||||
}
|
|
||||||
striped = true
|
|
||||||
}
|
|
||||||
if !striped && len(splits) > 2 {
|
|
||||||
suffix := splits[len(splits)-2] + "." + splits[len(splits)-1]
|
|
||||||
if publicSuffixMatcher().Match(suffix) {
|
|
||||||
splits = splits[:len(splits)-2]
|
|
||||||
}
|
|
||||||
striped = true
|
|
||||||
}
|
|
||||||
if !striped && len(splits) > 1 {
|
|
||||||
suffix := splits[len(splits)-1]
|
|
||||||
if publicSuffixMatcher().Match(suffix) {
|
|
||||||
splits = splits[:len(splits)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(splits) > 1 && common.Contains(publicPrefix, splits[0]) {
|
|
||||||
currentIndex += len(splits[0]) + 1
|
|
||||||
splits = splits[1:]
|
|
||||||
}
|
|
||||||
var splitIndexes []int
|
|
||||||
for i, split := range splits {
|
|
||||||
splitAt := rand.Intn(len(split))
|
|
||||||
splitIndexes = append(splitIndexes, currentIndex+splitAt)
|
|
||||||
currentIndex += len(split)
|
|
||||||
if i != len(splits)-1 {
|
|
||||||
currentIndex++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i <= len(splitIndexes); i++ {
|
|
||||||
if i == 0 {
|
|
||||||
_, err = c.Conn.Write(b[:splitIndexes[i]])
|
|
||||||
} else if i == len(splitIndexes) {
|
|
||||||
_, err = c.Conn.Write(b[splitIndexes[i-1]:])
|
|
||||||
} else {
|
|
||||||
_, err = c.Conn.Write(b[splitIndexes[i-1]:splitIndexes[i]])
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.syscallConn != nil && i != len(splitIndexes) {
|
|
||||||
err = waitAck(c.ctx, c.syscallConn, c.fallbackDelay)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isTCPConn {
|
|
||||||
err = tcpConn.SetNoDelay(false)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriterReplaceable() bool {
|
|
||||||
return c.firstPacketWritten
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
recordLayerHeaderLen int = 5
|
|
||||||
handshakeHeaderLen int = 6
|
|
||||||
randomDataLen int = 32
|
|
||||||
sessionIDHeaderLen int = 1
|
|
||||||
cipherSuiteHeaderLen int = 2
|
|
||||||
compressMethodHeaderLen int = 1
|
|
||||||
extensionsHeaderLen int = 2
|
|
||||||
extensionHeaderLen int = 4
|
|
||||||
sniExtensionHeaderLen int = 5
|
|
||||||
contentType uint8 = 22
|
|
||||||
handshakeType uint8 = 1
|
|
||||||
sniExtensionType uint16 = 0
|
|
||||||
sniNameDNSHostnameType uint8 = 0
|
|
||||||
tlsVersionBitmask uint16 = 0xFFFC
|
|
||||||
tls13 uint16 = 0x0304
|
|
||||||
)
|
|
||||||
|
|
||||||
type myServerName struct {
|
|
||||||
Index int
|
|
||||||
Length int
|
|
||||||
sex []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func indexTLSServerName(payload []byte) *myServerName {
|
|
||||||
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
segmentLen := binary.BigEndian.Uint16(payload[3:5])
|
|
||||||
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
|
|
||||||
if serverName == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
serverName.Length += recordLayerHeaderLen
|
|
||||||
return serverName
|
|
||||||
}
|
|
||||||
|
|
||||||
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
|
||||||
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if hs[0] != handshakeType {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
|
||||||
if len(hs[4:]) != int(handshakeLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
|
||||||
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sessionIDLen := hs[38]
|
|
||||||
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
|
||||||
if len(cs) < cipherSuiteHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
|
||||||
numCiphers := int(csLen / 2)
|
|
||||||
cipherSuites := make([]uint16, 0, numCiphers)
|
|
||||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i := 0; i < numCiphers; i++ {
|
|
||||||
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
|
|
||||||
cipherSuites = append(cipherSuites, cipherSuite)
|
|
||||||
}
|
|
||||||
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
|
||||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
|
||||||
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
|
|
||||||
if serverName == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
serverName.Index += currentIndex
|
|
||||||
return serverName
|
|
||||||
}
|
|
||||||
|
|
||||||
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
|
||||||
if len(exs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(exs) < extensionsHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exsLen := uint16(exs[0])<<8 | uint16(exs[1])
|
|
||||||
exs = exs[extensionsHeaderLen:]
|
|
||||||
if len(exs) < int(exsLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for currentIndex := extensionsHeaderLen; len(exs) > 0; {
|
|
||||||
if len(exs) < extensionHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exType := uint16(exs[0])<<8 | uint16(exs[1])
|
|
||||||
exLen := uint16(exs[2])<<8 | uint16(exs[3])
|
|
||||||
if len(exs) < extensionHeaderLen+int(exLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)]
|
|
||||||
|
|
||||||
switch exType {
|
|
||||||
case sniExtensionType:
|
|
||||||
if len(sex) < sniExtensionHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sniType := sex[2]
|
|
||||||
if sniType != sniNameDNSHostnameType {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
|
||||||
sex = sex[sniExtensionHeaderLen:]
|
|
||||||
return &myServerName{
|
|
||||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
|
||||||
Length: int(sniLen),
|
|
||||||
sex: sex,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exs = exs[4+exLen:]
|
|
||||||
currentIndex += 4 + int(exLen)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/domain"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
var publicPrefix = []string{
|
|
||||||
"www",
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate wget -O public_suffix_list.dat https://publicsuffix.org/list/public_suffix_list.dat
|
|
||||||
|
|
||||||
//go:embed public_suffix_list.dat
|
|
||||||
var publicSuffix []byte
|
|
||||||
|
|
||||||
var publicSuffixMatcher = sync.OnceValue(func() *domain.Matcher {
|
|
||||||
matcher, err := initPublicSuffixMatcher()
|
|
||||||
if err != nil {
|
|
||||||
panic(F.ToString("error in initialize public suffix matcher"))
|
|
||||||
}
|
|
||||||
return matcher
|
|
||||||
})
|
|
||||||
|
|
||||||
func initPublicSuffixMatcher() (*domain.Matcher, error) {
|
|
||||||
reader := bufio.NewReader(bytes.NewReader(publicSuffix))
|
|
||||||
var domainList []string
|
|
||||||
for {
|
|
||||||
line, isPrefix, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isPrefix {
|
|
||||||
return nil, E.New("unexpected prefix line")
|
|
||||||
}
|
|
||||||
lineStr := string(line)
|
|
||||||
lineStr = strings.TrimSpace(lineStr)
|
|
||||||
if lineStr == "" || strings.HasPrefix(lineStr, "//") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
domainList = append(domainList, lineStr)
|
|
||||||
}
|
|
||||||
return domain.NewMatcher(domainList, nil, false), nil
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user