mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
11 Commits
v1.10.6
...
dev-next-w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47736b27ba | ||
|
|
0b2c7ec35c | ||
|
|
e537c56b6b | ||
|
|
b456aff4ac | ||
|
|
4da652ff02 | ||
|
|
262727ec6c | ||
|
|
81dc9e7698 | ||
|
|
d876077281 | ||
|
|
0df81c297b | ||
|
|
b460484e43 | ||
|
|
0e511791e8 |
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:
|
||||||
|
|||||||
@@ -200,6 +200,4 @@ release:
|
|||||||
ids:
|
ids:
|
||||||
- archive
|
- archive
|
||||||
- package
|
- package
|
||||||
skip_upload: true
|
skip_upload: true
|
||||||
partial:
|
|
||||||
by: target
|
|
||||||
40
Makefile
40
Makefile
@@ -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,13 +147,9 @@ 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" && \
|
||||||
@@ -166,7 +164,7 @@ upload_macos_dsyms:
|
|||||||
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
|
|
||||||
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
|
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||||
|
|
||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -182,22 +180,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 +199,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
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -6,27 +6,53 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
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,13 +2,12 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
|
"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 {
|
||||||
@@ -17,11 +16,19 @@ type Inbound interface {
|
|||||||
Tag() string
|
Tag() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type InjectableInbound interface {
|
type TCPInjectableInbound interface {
|
||||||
Inbound
|
Inbound
|
||||||
Network() []string
|
ConnectionHandlerEx
|
||||||
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
|
||||||
|
CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
@@ -43,10 +50,15 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
InboundDetour string
|
// Deprecated: implement in rule action
|
||||||
LastInbound string
|
InboundDetour string
|
||||||
OriginDestination M.Socksaddr
|
LastInbound string
|
||||||
InboundOptions option.InboundOptions
|
OriginDestination M.Socksaddr
|
||||||
|
// Deprecated
|
||||||
|
InboundOptions option.InboundOptions
|
||||||
|
UDPDisableDomainUnmapping bool
|
||||||
|
DNSServer string
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
|
|||||||
21
adapter/inbound/adapter.go
Normal file
21
adapter/inbound/adapter.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
68
adapter/inbound/registry.go
Normal file
68
adapter/inbound/registry.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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, options any) (adapter.Inbound, error) {
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*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
|
||||||
|
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) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
constructor, loaded := r.constructors[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
r.optionsType[outboundType] = optionsConstructor
|
||||||
|
r.constructors[outboundType] = constructor
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,6 +16,9 @@ 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)
|
||||||
}
|
}
|
||||||
|
|||||||
45
adapter/outbound/adapter.go
Normal file
45
adapter/outbound/adapter.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
protocol string
|
||||||
|
network []string
|
||||||
|
tag string
|
||||||
|
dependencies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
protocol: protocol,
|
||||||
|
network: network,
|
||||||
|
tag: tag,
|
||||||
|
dependencies: dependencies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter {
|
||||||
|
var dependencies []string
|
||||||
|
if dialOptions.Detour != "" {
|
||||||
|
dependencies = []string{dialOptions.Detour}
|
||||||
|
}
|
||||||
|
return NewAdapter(protocol, network, tag, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Network() []string {
|
||||||
|
return a.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Dependencies() []string {
|
||||||
|
return a.dependencies
|
||||||
|
}
|
||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
@@ -21,42 +19,6 @@ import (
|
|||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type myOutboundAdapter struct {
|
|
||||||
protocol string
|
|
||||||
network []string
|
|
||||||
router adapter.Router
|
|
||||||
logger log.ContextLogger
|
|
||||||
tag string
|
|
||||||
dependencies []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Type() string {
|
|
||||||
return a.protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Tag() string {
|
|
||||||
return a.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Network() []string {
|
|
||||||
return a.network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Dependencies() []string {
|
|
||||||
return a.dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) NewError(ctx context.Context, err error) {
|
|
||||||
NewError(a.logger, ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func withDialerDependency(options option.DialerOptions) []string {
|
|
||||||
if options.Detour != "" {
|
|
||||||
return []string{options.Detour}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
@@ -69,7 +31,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
err = N.ReportHandshakeSuccess(conn)
|
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outConn.Close()
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
@@ -96,7 +58,7 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
err = N.ReportHandshakeSuccess(conn)
|
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outConn.Close()
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
@@ -117,14 +79,14 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
err = N.ReportHandshakeSuccess(conn)
|
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outConn.Close()
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if destinationAddress.IsValid() {
|
if destinationAddress.IsValid() {
|
||||||
if metadata.Destination.IsFqdn() {
|
if metadata.Destination.IsFqdn() {
|
||||||
if metadata.InboundOptions.UDPDisableDomainUnmapping {
|
if metadata.UDPDisableDomainUnmapping {
|
||||||
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||||
} else {
|
} else {
|
||||||
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||||
@@ -165,7 +127,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
err = N.ReportHandshakeSuccess(conn)
|
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outConn.Close()
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
@@ -233,12 +195,3 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
|
|||||||
}
|
}
|
||||||
return bufio.CopyConn(ctx, conn, serverConn)
|
return bufio.CopyConn(ctx, conn, serverConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewError(logger log.ContextLogger, ctx context.Context, err error) {
|
|
||||||
common.Close(err)
|
|
||||||
if E.IsClosedOrCanceled(err) {
|
|
||||||
logger.DebugContext(ctx, "connection closed: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
68
adapter/outbound/registry.go
Normal file
68
adapter/outbound/registry.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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, options any) (adapter.Outbound, error) {
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*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
|
||||||
|
}
|
||||||
@@ -34,6 +34,8 @@ type Router interface {
|
|||||||
FakeIPStore() FakeIPStore
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
|
PreMatch(metadata InboundContext) error
|
||||||
|
ConnectionRouterEx
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@@ -70,6 +72,18 @@ type Router interface {
|
|||||||
ResetNetwork() error
|
ResetNetwork() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
type ConnectionRouter interface {
|
||||||
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionRouterEx interface {
|
||||||
|
ConnectionRouter
|
||||||
|
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||||
return service.ContextWith(ctx, router)
|
return service.ContextWith(ctx, router)
|
||||||
}
|
}
|
||||||
@@ -78,28 +92,6 @@ func RouterFromContext(ctx context.Context) Router {
|
|||||||
return service.FromContext[Router](ctx)
|
return service.FromContext[Router](ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadlessRule interface {
|
|
||||||
Match(metadata *InboundContext) bool
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
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 *HTTPStartContext) error
|
||||||
|
|||||||
38
adapter/rule.go
Normal file
38
adapter/rule.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
|
UpdateGeosite() error
|
||||||
|
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,112 +4,165 @@ 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 (
|
||||||
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUpstreamHandler(
|
func NewUpstreamHandlerEx(
|
||||||
metadata InboundContext,
|
metadata InboundContext,
|
||||||
connectionHandler ConnectionHandlerFunc,
|
connectionHandler ConnectionHandlerFuncEx,
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
packetHandler PacketConnectionHandlerFuncEx,
|
||||||
errorHandler E.Handler,
|
) UpstreamHandlerAdapterEx {
|
||||||
) UpstreamHandlerAdapter {
|
return &myUpstreamHandlerWrapperEx{
|
||||||
return &myUpstreamHandlerWrapper{
|
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
type myUpstreamHandlerWrapper struct {
|
type myUpstreamHandlerWrapperEx struct {
|
||||||
metadata InboundContext
|
metadata InboundContext
|
||||||
connectionHandler ConnectionHandlerFunc
|
connectionHandler ConnectionHandlerFuncEx
|
||||||
packetHandler PacketConnectionHandlerFunc
|
packetHandler PacketConnectionHandlerFuncEx
|
||||||
errorHandler E.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.connectionHandler(ctx, conn, myMetadata)
|
w.connectionHandler(ctx, conn, myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.packetHandler(ctx, conn, myMetadata)
|
w.packetHandler(ctx, conn, myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
|
||||||
w.errorHandler.NewError(ctx, err)
|
|
||||||
|
type myUpstreamContextHandlerWrapperEx struct {
|
||||||
|
connectionHandler ConnectionHandlerFuncEx
|
||||||
|
packetHandler PacketConnectionHandlerFuncEx
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func NewUpstreamContextHandlerEx(
|
||||||
return M.Metadata{
|
connectionHandler ConnectionHandlerFuncEx,
|
||||||
Source: metadata.Source,
|
packetHandler PacketConnectionHandlerFuncEx,
|
||||||
Destination: metadata.Destination,
|
) UpstreamHandlerAdapterEx {
|
||||||
}
|
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 *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
w.connectionHandler(ctx, conn, *myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.packetHandler(ctx, conn, *myMetadata)
|
w.packetHandler(ctx, conn, *myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
func NewRouteHandlerEx(
|
||||||
w.errorHandler.NewError(ctx, err)
|
metadata InboundContext,
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
216
adapter/upstream_legacy.go
Normal file
216
adapter/upstream_legacy.go
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
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
|
||||||
|
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
|
||||||
|
type myUpstreamHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
|
return M.Metadata{
|
||||||
|
Source: metadata.Source,
|
||||||
|
Destination: metadata.Destination,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
type myUpstreamContextHandlerWrapper struct {
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
func NewUpstreamContextHandler(
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamContextHandlerWrapper{
|
||||||
|
connectionHandler: connectionHandler,
|
||||||
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,8 +15,7 @@ type V2RayServerTransport interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServerTransportHandler interface {
|
type V2RayServerTransportHandler interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandlerEx
|
||||||
E.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayClientTransport interface {
|
type V2RayClientTransport interface {
|
||||||
|
|||||||
91
box.go
91
box.go
@@ -14,10 +14,9 @@ import (
|
|||||||
"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/outbound"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
"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"
|
||||||
@@ -44,16 +43,37 @@ type Box 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) 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)
|
||||||
|
}
|
||||||
|
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
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
|
if inboundRegistry == nil {
|
||||||
|
return nil, E.New("missing inbound registry in context")
|
||||||
|
}
|
||||||
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
if outboundRegistry == nil {
|
||||||
|
return nil, E.New("missing outbound registry in context")
|
||||||
|
}
|
||||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@@ -70,8 +90,9 @@ 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 options.PlatformInterface != nil {
|
if platformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
}
|
}
|
||||||
logFactory, err := log.New(log.Options{
|
logFactory, err := log.New(log.Options{
|
||||||
@@ -92,64 +113,92 @@ func New(options Options) (*Box, error) {
|
|||||||
common.PtrValueOrDefault(options.DNS),
|
common.PtrValueOrDefault(options.DNS),
|
||||||
common.PtrValueOrDefault(options.NTP),
|
common.PtrValueOrDefault(options.NTP),
|
||||||
options.Inbounds,
|
options.Inbounds,
|
||||||
options.PlatformInterface,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse route options")
|
return nil, E.Cause(err, "parse route options")
|
||||||
}
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
if len(options.LegacyInbounds) > 0 {
|
||||||
|
for _, legacyInbound := range options.LegacyInbounds {
|
||||||
|
options.Inbounds = append(options.Inbounds, option.Inbound{
|
||||||
|
Type: legacyInbound.Type,
|
||||||
|
Tag: legacyInbound.Tag,
|
||||||
|
Options: common.Must1(legacyInbound.RawOptions()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
||||||
|
//nolint:staticcheck
|
||||||
|
if len(options.LegacyOutbounds) > 0 {
|
||||||
|
for _, legacyOutbound := range options.LegacyOutbounds {
|
||||||
|
options.Outbounds = append(options.Outbounds, option.Outbound{
|
||||||
|
Type: legacyOutbound.Type,
|
||||||
|
Tag: legacyOutbound.Tag,
|
||||||
|
Options: common.Must1(legacyOutbound.RawOptions()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
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 currentInbound 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)
|
||||||
}
|
}
|
||||||
in, err = inbound.New(
|
currentInbound, err = inboundRegistry.CreateInbound(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
inboundOptions,
|
inboundOptions.Type,
|
||||||
options.PlatformInterface,
|
inboundOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
}
|
}
|
||||||
inbounds = append(inbounds, in)
|
inbounds = append(inbounds, currentInbound)
|
||||||
}
|
}
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var out adapter.Outbound
|
var currentOutbound 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)
|
||||||
}
|
}
|
||||||
out, err = outbound.New(
|
outboundCtx := ctx
|
||||||
ctx,
|
if tag != "" {
|
||||||
|
// TODO: remove this
|
||||||
|
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
||||||
|
Outbound: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
currentOutbound, err = outboundRegistry.CreateOutbound(
|
||||||
|
outboundCtx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
outboundOptions)
|
outboundOptions.Type,
|
||||||
|
outboundOptions.Options,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||||
}
|
}
|
||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, currentOutbound)
|
||||||
}
|
}
|
||||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{})
|
||||||
common.Must(oErr)
|
common.Must(cErr)
|
||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, defaultOutbound)
|
||||||
return out
|
return defaultOutbound
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.PlatformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = options.PlatformInterface.Initialize(ctx, router)
|
err = platformInterface.Initialize(ctx, router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule clients/android updated: e1049099a0...45a1f5f0aa
Submodule clients/apple updated: 3d889ae017...c7d9b49de7
@@ -1,445 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/asc-go/asc"
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ctx := context.Background()
|
|
||||||
switch os.Args[1] {
|
|
||||||
case "next_macos_project_version":
|
|
||||||
err := fetchMacOSVersion(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
case "publish_testflight":
|
|
||||||
err := publishTestflight(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
case "cancel_app_store":
|
|
||||||
err := cancelAppStore(ctx, os.Args[2])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
case "prepare_app_store":
|
|
||||||
err := prepareAppStore(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
case "publish_app_store":
|
|
||||||
err := publishAppStore(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Fatal("unknown action: ", os.Args[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
appID = "6673731168"
|
|
||||||
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createClient(expireDuration time.Duration) *asc.Client {
|
|
||||||
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return asc.NewClient(tokenConfig.Client())
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchMacOSVersion(ctx context.Context) error {
|
|
||||||
client := createClient(time.Minute)
|
|
||||||
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
|
||||||
FilterPlatform: []string{"MAC_OS"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var versionID string
|
|
||||||
findVersion:
|
|
||||||
for _, version := range versions.Data {
|
|
||||||
switch *version.Attributes.AppStoreState {
|
|
||||||
case asc.AppStoreVersionStateReadyForSale,
|
|
||||||
asc.AppStoreVersionStatePendingDeveloperRelease:
|
|
||||||
versionID = version.ID
|
|
||||||
break findVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if versionID == "" {
|
|
||||||
return E.New("no version found")
|
|
||||||
}
|
|
||||||
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "parse version code")
|
|
||||||
}
|
|
||||||
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func publishTestflight(ctx context.Context) error {
|
|
||||||
tagVersion, err := build_shared.ReadTagVersion()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tag := tagVersion.VersionString()
|
|
||||||
client := createClient(10 * time.Minute)
|
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
|
||||||
return it.ID
|
|
||||||
})
|
|
||||||
var platforms []asc.Platform
|
|
||||||
if len(os.Args) == 3 {
|
|
||||||
switch os.Args[2] {
|
|
||||||
case "ios":
|
|
||||||
platforms = []asc.Platform{asc.PlatformIOS}
|
|
||||||
case "macos":
|
|
||||||
platforms = []asc.Platform{asc.PlatformMACOS}
|
|
||||||
case "tvos":
|
|
||||||
platforms = []asc.Platform{asc.PlatformTVOS}
|
|
||||||
default:
|
|
||||||
return E.New("unknown platform: ", os.Args[2])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
platforms = []asc.Platform{
|
|
||||||
asc.PlatformIOS,
|
|
||||||
asc.PlatformMACOS,
|
|
||||||
asc.PlatformTVOS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, platform := range platforms {
|
|
||||||
log.Info(string(platform), " list builds")
|
|
||||||
for {
|
|
||||||
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
|
||||||
FilterApp: []string{appID},
|
|
||||||
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
build := builds.Data[0]
|
|
||||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if *build.Attributes.ProcessingState != "VALID" {
|
|
||||||
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " list localizations")
|
|
||||||
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
|
|
||||||
return *it.Attributes.Locale == "en-US"
|
|
||||||
})
|
|
||||||
if localization.ID == "" {
|
|
||||||
log.Fatal(string(platform), " ", tag, " no en-US localization found")
|
|
||||||
}
|
|
||||||
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
|
||||||
log.Info(string(platform), " ", tag, " update localization")
|
|
||||||
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
|
|
||||||
F.ToString("sing-box ", tagVersion.String()),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
|
||||||
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
|
||||||
log.Info("waiting for process")
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " list submissions")
|
|
||||||
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
|
||||||
FilterBuild: []string{build.ID},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(betaSubmissions.Data) == 0 {
|
|
||||||
log.Info(string(platform), " ", tag, " create submission")
|
|
||||||
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cancelAppStore(ctx context.Context, platform string) error {
|
|
||||||
switch platform {
|
|
||||||
case "ios":
|
|
||||||
platform = string(asc.PlatformIOS)
|
|
||||||
case "macos":
|
|
||||||
platform = string(asc.PlatformMACOS)
|
|
||||||
case "tvos":
|
|
||||||
platform = string(asc.PlatformTVOS)
|
|
||||||
}
|
|
||||||
tag, err := build_shared.ReadTag()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
client := createClient(time.Minute)
|
|
||||||
for {
|
|
||||||
log.Info(platform, " list versions")
|
|
||||||
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
|
||||||
FilterPlatform: []string{string(platform)},
|
|
||||||
})
|
|
||||||
if isRetryable(response) {
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
|
||||||
return *it.Attributes.VersionString == tag
|
|
||||||
})
|
|
||||||
if version.ID == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Info(platform, " ", tag, " get submission")
|
|
||||||
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
|
||||||
if response != nil && response.StatusCode == http.StatusNotFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if isRetryable(response) {
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info(platform, " ", tag, " delete submission")
|
|
||||||
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareAppStore(ctx context.Context) error {
|
|
||||||
tag, err := build_shared.ReadTag()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
client := createClient(time.Minute)
|
|
||||||
for _, platform := range []asc.Platform{
|
|
||||||
asc.PlatformIOS,
|
|
||||||
asc.PlatformMACOS,
|
|
||||||
asc.PlatformTVOS,
|
|
||||||
} {
|
|
||||||
log.Info(string(platform), " list versions")
|
|
||||||
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
|
||||||
FilterPlatform: []string{string(platform)},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
|
||||||
return *it.Attributes.VersionString == tag
|
|
||||||
})
|
|
||||||
log.Info(string(platform), " ", tag, " list builds")
|
|
||||||
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
|
||||||
FilterApp: []string{appID},
|
|
||||||
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(builds.Data) == 0 {
|
|
||||||
log.Fatal(platform, " ", tag, " no build found")
|
|
||||||
}
|
|
||||||
buildID := common.Ptr(builds.Data[0].ID)
|
|
||||||
if version.ID == "" {
|
|
||||||
log.Info(string(platform), " ", tag, " create version")
|
|
||||||
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
|
|
||||||
Platform: platform,
|
|
||||||
VersionString: tag,
|
|
||||||
}, appID, buildID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
version = newVersion.Data
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.Info(string(platform), " ", tag, " check build")
|
|
||||||
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
|
|
||||||
switch *version.Attributes.AppStoreState {
|
|
||||||
case asc.AppStoreVersionStatePrepareForSubmission,
|
|
||||||
asc.AppStoreVersionStateRejected,
|
|
||||||
asc.AppStoreVersionStateDeveloperRejected:
|
|
||||||
case asc.AppStoreVersionStateWaitingForReview,
|
|
||||||
asc.AppStoreVersionStateInReview,
|
|
||||||
asc.AppStoreVersionStatePendingDeveloperRelease:
|
|
||||||
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if submission != nil {
|
|
||||||
log.Info(string(platform), " ", tag, " delete submission")
|
|
||||||
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " update build")
|
|
||||||
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if response.StatusCode != http.StatusNoContent {
|
|
||||||
response.Write(os.Stderr)
|
|
||||||
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch *version.Attributes.AppStoreState {
|
|
||||||
case asc.AppStoreVersionStatePrepareForSubmission,
|
|
||||||
asc.AppStoreVersionStateRejected,
|
|
||||||
asc.AppStoreVersionStateDeveloperRejected:
|
|
||||||
case asc.AppStoreVersionStateWaitingForReview,
|
|
||||||
asc.AppStoreVersionStateInReview,
|
|
||||||
asc.AppStoreVersionStatePendingDeveloperRelease:
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " list localization")
|
|
||||||
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
|
|
||||||
return *it.Attributes.Locale == "en-US"
|
|
||||||
})
|
|
||||||
if localization.ID == "" {
|
|
||||||
log.Info(string(platform), " ", tag, " no en-US localization found")
|
|
||||||
}
|
|
||||||
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
|
||||||
log.Info(string(platform), " ", tag, " update localization")
|
|
||||||
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
|
|
||||||
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
|
|
||||||
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " create submission")
|
|
||||||
fixSubmit:
|
|
||||||
for {
|
|
||||||
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
|
|
||||||
if err != nil {
|
|
||||||
switch response.StatusCode {
|
|
||||||
case http.StatusInternalServerError:
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch response.StatusCode {
|
|
||||||
case http.StatusCreated:
|
|
||||||
break fixSubmit
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func publishAppStore(ctx context.Context) error {
|
|
||||||
tag, err := build_shared.ReadTag()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
client := createClient(time.Minute)
|
|
||||||
for _, platform := range []asc.Platform{
|
|
||||||
asc.PlatformIOS,
|
|
||||||
asc.PlatformMACOS,
|
|
||||||
asc.PlatformTVOS,
|
|
||||||
} {
|
|
||||||
log.Info(string(platform), " list versions")
|
|
||||||
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
|
||||||
FilterPlatform: []string{string(platform)},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
|
||||||
return *it.Attributes.VersionString == tag
|
|
||||||
})
|
|
||||||
switch *version.Attributes.AppStoreState {
|
|
||||||
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
|
|
||||||
log.Fatal(string(platform), " ", tag, " not submitted")
|
|
||||||
case asc.AppStoreVersionStateWaitingForReview,
|
|
||||||
asc.AppStoreVersionStateInReview:
|
|
||||||
log.Warn(string(platform), " ", tag, " waiting for review")
|
|
||||||
continue
|
|
||||||
case asc.AppStoreVersionStatePendingDeveloperRelease:
|
|
||||||
default:
|
|
||||||
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
|
||||||
}
|
|
||||||
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isRetryable(response *asc.Response) bool {
|
|
||||||
if response == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch response.StatusCode {
|
|
||||||
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,21 +10,17 @@ import (
|
|||||||
_ "github.com/sagernet/gomobile"
|
_ "github.com/sagernet/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 = "27.2.12479018"
|
||||||
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
|
||||||
|
|||||||
@@ -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,8 +7,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
_ "github.com/sagernet/sing-box/include"
|
"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"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
@@ -67,5 +68,6 @@ 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 = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger()))
|
||||||
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ func format() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, optionsEntry := range optionsList {
|
for _, optionsEntry := range optionsList {
|
||||||
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
|
optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,29 +68,19 @@ func merge(outputPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mergePathResources(options *option.Options) error {
|
func mergePathResources(options *option.Options) error {
|
||||||
for index, inbound := range options.Inbounds {
|
for _, inbound := range options.Inbounds {
|
||||||
rawOptions, err := inbound.RawOptions()
|
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
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 index, outbound := range options.Outbounds {
|
for _, 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:
|
||||||
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
|
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
|
||||||
}
|
}
|
||||||
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
||||||
}
|
}
|
||||||
options.Outbounds[index] = outbound
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -138,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
func mergeSSHOutboundOptions(options *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") {
|
||||||
|
|||||||
@@ -10,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"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
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"
|
||||||
@@ -55,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() {
|
||||||
@@ -83,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 = route.NewHeadlessRule(nil, ruleOptions)
|
currentRule, err = rule.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, "]")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.UnmarshalExtended[option.Options](configContent)
|
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, 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(options.options.RawMessage, mergedMessage, false)
|
mergedMessage, err = badjson.MergeJSON(globalCtx, 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.UnmarshalJSON(mergedMessage)
|
err = mergedOptions.UnmarshalJSONContext(globalCtx, 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,6 +1,9 @@
|
|||||||
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"
|
||||||
@@ -23,7 +26,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 {
|
||||||
return nil, err
|
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
instance, err := box.New(box.Options{Options: options})
|
instance, err := box.New(box.Options{Options: options})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func initializeHTTP3Client(instance *box.Box) error {
|
|||||||
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)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -26,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 := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.org",
|
"example.org",
|
||||||
@@ -85,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 := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"google.com",
|
"google.com",
|
||||||
@@ -115,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 := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ 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
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build with_wireguard
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/wireguard-go/conn"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ WireGuardListener = (conn.Listener)(nil)
|
|
||||||
|
|
||||||
var wgControlFns = conn.ControlFns
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
//go:build !with_wireguard
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
var wgControlFns []control.Func
|
|
||||||
136
common/listener/listener.go
Normal file
136
common/listener/listener.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"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()
|
||||||
|
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,6 +1,6 @@
|
|||||||
//go:build go1.21
|
//go:build go1.21
|
||||||
|
|
||||||
package inbound
|
package listener
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
16
common/listener/listener_go123.go
Normal file
16
common/listener/listener_go123.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//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,6 +1,6 @@
|
|||||||
//go:build !go1.21
|
//go:build !go1.21
|
||||||
|
|
||||||
package inbound
|
package listener
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
15
common/listener/listener_nongo123.go
Normal file
15
common/listener/listener_nongo123.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//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))
|
||||||
|
}
|
||||||
85
common/listener/listener_tcp.go
Normal file
85
common/listener/listener_tcp.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"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(), 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
154
common/listener/listener_udp.go
Normal file
154
common/listener/listener_udp.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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(), 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)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
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.ConnectionRouter
|
router adapter.ConnectionRouterEx
|
||||||
service *mux.Service
|
service *mux.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return router, nil
|
return router, nil
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context
|
|||||||
|
|
||||||
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)
|
||||||
@@ -63,3 +64,15 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const (
|
|||||||
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 {
|
||||||
@@ -54,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 {
|
||||||
@@ -68,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
|
||||||
@@ -80,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
|
||||||
}
|
}
|
||||||
@@ -99,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
|
||||||
}
|
}
|
||||||
@@ -130,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)
|
||||||
}
|
}
|
||||||
@@ -235,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
|
||||||
@@ -259,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
|
||||||
}
|
}
|
||||||
@@ -349,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
|
||||||
@@ -455,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
|
||||||
@@ -476,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,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,
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -175,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,
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ import (
|
|||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.ConnectionRouter = (*Router)(nil)
|
var _ adapter.ConnectionRouterEx = (*Router)(nil)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
router adapter.ConnectionRouter
|
router adapter.ConnectionRouterEx
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
|
func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router {
|
||||||
return &Router{router, logger}
|
return &Router{router, logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,3 +51,36 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
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) {
|
||||||
|
switch metadata.Destination.Fqdn {
|
||||||
|
case uot.MagicAddress:
|
||||||
|
request, err := uot.ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
err = E.Cause(err, "UoT read request")
|
||||||
|
r.logger.ErrorContext(ctx, "process connection from ", metadata.Source, ": ", err)
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if request.IsConnect {
|
||||||
|
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
|
||||||
|
} else {
|
||||||
|
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
|
||||||
|
}
|
||||||
|
metadata.Domain = metadata.Destination.Fqdn
|
||||||
|
metadata.Destination = request.Destination
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose)
|
||||||
|
return
|
||||||
|
case uot.LegacyMagicAddress:
|
||||||
|
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
|
||||||
|
metadata.Domain = metadata.Destination.Fqdn
|
||||||
|
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||||
|
r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, 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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,5 +21,22 @@ const (
|
|||||||
const (
|
const (
|
||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersionCurrent = RuleSetVersion2
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RuleActionTypeRoute = "route"
|
||||||
|
RuleActionTypeReturn = "return"
|
||||||
|
RuleActionTypeReject = "reject"
|
||||||
|
RuleActionTypeHijackDNS = "hijack-dns"
|
||||||
|
RuleActionTypeSniff = "sniff"
|
||||||
|
RuleActionTypeResolve = "resolve"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RuleActionRejectMethodDefault = "default"
|
||||||
|
RuleActionRejectMethodReset = "reset"
|
||||||
|
RuleActionRejectMethodNetworkUnreachable = "network-unreachable"
|
||||||
|
RuleActionRejectMethodHostUnreachable = "host-unreachable"
|
||||||
|
RuleActionRejectMethodPortUnreachable = "port-unreachable"
|
||||||
|
RuleActionRejectMethodDrop = "drop"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func applyDebugListenOption(options option.DebugOptions) {
|
|||||||
|
|
||||||
encoder := json.NewEncoder(writer)
|
encoder := json.NewEncoder(writer)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
encoder.Encode(&memObject)
|
encoder.Encode(memObject)
|
||||||
})
|
})
|
||||||
r.Route("/pprof", func(r chi.Router) {
|
r.Route("/pprof", func(r chi.Router) {
|
||||||
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1.10.6
|
#### 1.11.0-alpha.5
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.10.2
|
#### 1.11.0-alpha.2
|
||||||
|
|
||||||
* Add deprecated warnings
|
* Add warnings for usage of deprecated features
|
||||||
* Fix proxying websocket connections in HTTP/mixed inbounds
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.11.0-alpha.1
|
||||||
|
|
||||||
|
* Update quic-go to v0.48.0
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.10.1
|
### 1.10.1
|
||||||
@@ -43,7 +47,7 @@ Important changes since 1.9:
|
|||||||
The new auto-redirect feature allows TUN to automatically
|
The new auto-redirect feature allows TUN to automatically
|
||||||
configure connection redirection to improve proxy performance.
|
configure connection redirection to improve proxy performance.
|
||||||
|
|
||||||
When auto-redirect is enabled, new route address set options will allow you to
|
When auto-redirect is enabled, new route address set options will allow you to
|
||||||
automatically configure destination IP CIDR rules from a specified rule set to the firewall.
|
automatically configure destination IP CIDR rules from a specified rule set to the firewall.
|
||||||
|
|
||||||
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
||||||
|
|||||||
@@ -110,13 +110,13 @@ icon: material/new-box
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"include_uid_range": [
|
"include_uid_range": [
|
||||||
"1000:99999"
|
"1000-99999"
|
||||||
],
|
],
|
||||||
"exclude_uid": [
|
"exclude_uid": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"exclude_uid_range": [
|
"exclude_uid_range": [
|
||||||
"1000:99999"
|
"1000-99999"
|
||||||
],
|
],
|
||||||
"include_android_user": [
|
"include_android_user": [
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -110,13 +110,13 @@ icon: material/new-box
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"include_uid_range": [
|
"include_uid_range": [
|
||||||
"1000:99999"
|
"1000-99999"
|
||||||
],
|
],
|
||||||
"exclude_uid": [
|
"exclude_uid": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"exclude_uid_range": [
|
"exclude_uid_range": [
|
||||||
"1000:99999"
|
"1000-99999"
|
||||||
],
|
],
|
||||||
"include_android_user": [
|
"include_android_user": [
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -59,7 +59,7 @@ func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
|||||||
func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||||
group, ok := proxy.(adapter.OutboundGroup)
|
outboundGroup, ok := proxy.(adapter.OutboundGroup)
|
||||||
if !ok {
|
if !ok {
|
||||||
render.Status(r, http.StatusNotFound)
|
render.Status(r, http.StatusNotFound)
|
||||||
render.JSON(w, r, ErrNotFound)
|
render.JSON(w, r, ErrNotFound)
|
||||||
@@ -82,10 +82,10 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var result map[string]uint16
|
var result map[string]uint16
|
||||||
if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup {
|
if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup {
|
||||||
result, err = urlTestGroup.URLTest(ctx)
|
result, err = urlTestGroup.URLTest(ctx)
|
||||||
} else {
|
} else {
|
||||||
outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound {
|
outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||||
itOutbound, _ := server.router.Outbound(it)
|
itOutbound, _ := server.router.Outbound(it)
|
||||||
return itOutbound
|
return itOutbound
|
||||||
}))
|
}))
|
||||||
@@ -95,7 +95,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
var resultAccess sync.Mutex
|
var resultAccess sync.Mutex
|
||||||
for _, detour := range outbounds {
|
for _, detour := range outbounds {
|
||||||
tag := detour.Tag()
|
tag := detour.Tag()
|
||||||
realTag := outbound.RealTag(detour)
|
realTag := group.RealTag(detour)
|
||||||
if checked[realTag] {
|
if checked[realTag] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,19 +18,17 @@ func configRouter(server *Server, logFactory log.Factory) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type configSchema struct {
|
type configSchema struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
SocksPort int `json:"socks-port"`
|
SocksPort int `json:"socks-port"`
|
||||||
RedirPort int `json:"redir-port"`
|
RedirPort int `json:"redir-port"`
|
||||||
TProxyPort int `json:"tproxy-port"`
|
TProxyPort int `json:"tproxy-port"`
|
||||||
MixedPort int `json:"mixed-port"`
|
MixedPort int `json:"mixed-port"`
|
||||||
AllowLan bool `json:"allow-lan"`
|
AllowLan bool `json:"allow-lan"`
|
||||||
BindAddress string `json:"bind-address"`
|
BindAddress string `json:"bind-address"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
// sing-box added
|
LogLevel string `json:"log-level"`
|
||||||
ModeList []string `json:"mode-list"`
|
IPv6 bool `json:"ipv6"`
|
||||||
LogLevel string `json:"log-level"`
|
Tun map[string]any `json:"tun"`
|
||||||
IPv6 bool `json:"ipv6"`
|
|
||||||
Tun map[string]any `json:"tun"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) {
|
func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -43,7 +41,6 @@ func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWrit
|
|||||||
}
|
}
|
||||||
render.JSON(w, r, &configSchema{
|
render.JSON(w, r, &configSchema{
|
||||||
Mode: server.mode,
|
Mode: server.mode,
|
||||||
ModeList: server.modeList,
|
|
||||||
BindAddress: "*",
|
BindAddress: "*",
|
||||||
LogLevel: log.FormatLevel(logLevel),
|
LogLevel: log.FormatLevel(logLevel),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -168,7 +168,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||||
selector, ok := proxy.(*outbound.Selector)
|
selector, ok := proxy.(*group.Selector)
|
||||||
if !ok {
|
if !ok {
|
||||||
render.Status(r, http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.JSON(w, r, newError("Must be a Selector"))
|
render.JSON(w, r, newError("Must be a Selector"))
|
||||||
@@ -204,7 +204,7 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
delay, err := urltest.URLTest(ctx, url, proxy)
|
delay, err := urltest.URLTest(ctx, url, proxy)
|
||||||
defer func() {
|
defer func() {
|
||||||
realTag := outbound.RealTag(proxy)
|
realTag := group.RealTag(proxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -30,10 +30,9 @@ func getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request
|
|||||||
rules = append(rules, Rule{
|
rules = append(rules, Rule{
|
||||||
Type: rule.Type(),
|
Type: rule.Type(),
|
||||||
Payload: rule.String(),
|
Payload: rule.String(),
|
||||||
Proxy: rule.Outbound(),
|
Proxy: rule.Action().String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render.JSON(w, r, render.M{
|
render.JSON(w, r, render.M{
|
||||||
"rules": rules,
|
"rules": rules,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -124,8 +124,11 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
|||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
||||||
chiRouter.Group(func(r chi.Router) {
|
chiRouter.Group(func(r chi.Router) {
|
||||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP)
|
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
|
||||||
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(server.externalUI))))
|
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
||||||
|
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fs.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return server, nil
|
return server, nil
|
||||||
@@ -313,29 +316,27 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter,
|
|||||||
tick := time.NewTicker(time.Second)
|
tick := time.NewTicker(time.Second)
|
||||||
defer tick.Stop()
|
defer tick.Stop()
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
uploadTotal, downloadTotal := trafficManager.Total()
|
var err error
|
||||||
for range tick.C {
|
for range tick.C {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
uploadTotalNew, downloadTotalNew := trafficManager.Total()
|
up, down := trafficManager.Now()
|
||||||
err := json.NewEncoder(buf).Encode(Traffic{
|
if err := json.NewEncoder(buf).Encode(Traffic{
|
||||||
Up: uploadTotalNew - uploadTotal,
|
Up: up,
|
||||||
Down: downloadTotalNew - downloadTotal,
|
Down: down,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
_, err = w.Write(buf.Bytes())
|
_, err = w.Write(buf.Bytes())
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
} else {
|
} else {
|
||||||
err = wsutil.WriteServerText(conn, buf.Bytes())
|
err = wsutil.WriteServerText(conn, buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadTotal = uploadTotalNew
|
|
||||||
downloadTotal = downloadTotalNew
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,18 +16,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
|
uploadTemp atomic.Int64
|
||||||
|
downloadTemp atomic.Int64
|
||||||
|
uploadBlip atomic.Int64
|
||||||
|
downloadBlip atomic.Int64
|
||||||
uploadTotal atomic.Int64
|
uploadTotal atomic.Int64
|
||||||
downloadTotal atomic.Int64
|
downloadTotal atomic.Int64
|
||||||
|
|
||||||
connections compatible.Map[uuid.UUID, Tracker]
|
connections compatible.Map[uuid.UUID, Tracker]
|
||||||
closedConnectionsAccess sync.Mutex
|
closedConnectionsAccess sync.Mutex
|
||||||
closedConnections list.List[TrackerMetadata]
|
closedConnections list.List[TrackerMetadata]
|
||||||
|
ticker *time.Ticker
|
||||||
|
done chan struct{}
|
||||||
// process *process.Process
|
// process *process.Process
|
||||||
memory uint64
|
memory uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
return &Manager{}
|
manager := &Manager{
|
||||||
|
ticker: time.NewTicker(time.Second),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
// process: &process.Process{Pid: int32(os.Getpid())},
|
||||||
|
}
|
||||||
|
go manager.handle()
|
||||||
|
return manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Join(c Tracker) {
|
func (m *Manager) Join(c Tracker) {
|
||||||
@@ -49,13 +61,19 @@ func (m *Manager) Leave(c Tracker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) PushUploaded(size int64) {
|
func (m *Manager) PushUploaded(size int64) {
|
||||||
|
m.uploadTemp.Add(size)
|
||||||
m.uploadTotal.Add(size)
|
m.uploadTotal.Add(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) PushDownloaded(size int64) {
|
func (m *Manager) PushDownloaded(size int64) {
|
||||||
|
m.downloadTemp.Add(size)
|
||||||
m.downloadTotal.Add(size)
|
m.downloadTotal.Add(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Now() (up int64, down int64) {
|
||||||
|
return m.uploadBlip.Load(), m.downloadBlip.Load()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) Total() (up int64, down int64) {
|
func (m *Manager) Total() (up int64, down int64) {
|
||||||
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
||||||
}
|
}
|
||||||
@@ -109,10 +127,36 @@ func (m *Manager) Snapshot() *Snapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) ResetStatistic() {
|
func (m *Manager) ResetStatistic() {
|
||||||
|
m.uploadTemp.Store(0)
|
||||||
|
m.uploadBlip.Store(0)
|
||||||
m.uploadTotal.Store(0)
|
m.uploadTotal.Store(0)
|
||||||
|
m.downloadTemp.Store(0)
|
||||||
|
m.downloadBlip.Store(0)
|
||||||
m.downloadTotal.Store(0)
|
m.downloadTotal.Store(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) handle() {
|
||||||
|
var uploadTemp int64
|
||||||
|
var downloadTemp int64
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.done:
|
||||||
|
return
|
||||||
|
case <-m.ticker.C:
|
||||||
|
}
|
||||||
|
uploadTemp = m.uploadTemp.Swap(0)
|
||||||
|
downloadTemp = m.downloadTemp.Swap(0)
|
||||||
|
m.uploadBlip.Store(uploadTemp)
|
||||||
|
m.downloadBlip.Store(downloadTemp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.ticker.Stop()
|
||||||
|
close(m.done)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
Download int64
|
Download int64
|
||||||
Upload int64
|
Upload int64
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
@@ -60,7 +61,7 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
var rule string
|
var rule string
|
||||||
if t.Rule != nil {
|
if t.Rule != nil {
|
||||||
rule = F.ToString(t.Rule, " => ", t.Rule.Outbound())
|
rule = F.ToString(t.Rule, " => ", t.Rule.Action())
|
||||||
} else {
|
} else {
|
||||||
rule = "final"
|
rule = "final"
|
||||||
}
|
}
|
||||||
@@ -131,19 +132,21 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont
|
|||||||
outbound string
|
outbound string
|
||||||
outboundType string
|
outboundType string
|
||||||
)
|
)
|
||||||
if rule == nil {
|
var action adapter.RuleAction
|
||||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
if rule != nil {
|
||||||
next = defaultOutbound.Tag()
|
action = rule.Action()
|
||||||
}
|
}
|
||||||
} else {
|
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
|
||||||
next = rule.Outbound()
|
next = routeAction.Outbound
|
||||||
|
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||||
|
next = defaultOutbound.Tag()
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
chain = append(chain, next)
|
|
||||||
detour, loaded := router.Outbound(next)
|
detour, loaded := router.Outbound(next)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
chain = append(chain, next)
|
||||||
outbound = detour.Tag()
|
outbound = detour.Tag()
|
||||||
outboundType = detour.Type()
|
outboundType = detour.Type()
|
||||||
group, isGroup := detour.(adapter.OutboundGroup)
|
group, isGroup := detour.(adapter.OutboundGroup)
|
||||||
@@ -218,19 +221,21 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound
|
|||||||
outbound string
|
outbound string
|
||||||
outboundType string
|
outboundType string
|
||||||
)
|
)
|
||||||
if rule == nil {
|
var action adapter.RuleAction
|
||||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
if rule != nil {
|
||||||
next = defaultOutbound.Tag()
|
action = rule.Action()
|
||||||
}
|
}
|
||||||
} else {
|
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
|
||||||
next = rule.Outbound()
|
next = routeAction.Outbound
|
||||||
|
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
||||||
|
next = defaultOutbound.Tag()
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
chain = append(chain, next)
|
|
||||||
detour, loaded := router.Outbound(next)
|
detour, loaded := router.Outbound(next)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
chain = append(chain, next)
|
||||||
outbound = detour.Tag()
|
outbound = detour.Tag()
|
||||||
outboundType = detour.Type()
|
outboundType = detour.Type()
|
||||||
group, isGroup := detour.(adapter.OutboundGroup)
|
group, isGroup := detour.(adapter.OutboundGroup)
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
package deprecated
|
package deprecated
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badversion"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/locale"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
|
||||||
"golang.org/x/mod/semver"
|
"golang.org/x/mod/semver"
|
||||||
@@ -27,20 +23,18 @@ func (n Note) Impending() bool {
|
|||||||
if !semver.IsValid("v" + C.Version) {
|
if !semver.IsValid("v" + C.Version) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
versionCurrent := badversion.Parse(C.Version)
|
versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion)
|
||||||
versionMinor := badversion.Parse(n.ScheduledVersion).Minor - versionCurrent.Minor
|
if versionMinor < 0 {
|
||||||
if versionCurrent.PreReleaseIdentifier == "" && versionMinor < 0 {
|
|
||||||
panic("invalid deprecated note: " + n.Name)
|
panic("invalid deprecated note: " + n.Name)
|
||||||
}
|
}
|
||||||
return versionMinor <= 1
|
return versionMinor <= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n Note) Message() string {
|
func (n Note) Message() string {
|
||||||
if n.MigrationLink != "" {
|
return F.ToString(
|
||||||
return fmt.Sprintf(locale.Current().DeprecatedMessage, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
|
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||||
} else {
|
" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
|
||||||
return fmt.Sprintf(locale.Current().DeprecatedMessageNoLink, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n Note) MessageWithLink() string {
|
func (n Note) MessageWithLink() string {
|
||||||
@@ -55,7 +49,6 @@ var OptionBadMatchSource = Note{
|
|||||||
Description: "legacy match source rule item",
|
Description: "legacy match source rule item",
|
||||||
DeprecatedVersion: "1.10.0",
|
DeprecatedVersion: "1.10.0",
|
||||||
ScheduledVersion: "1.11.0",
|
ScheduledVersion: "1.11.0",
|
||||||
EnvName: "BAD_MATCH_SOURCE",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed",
|
MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +75,6 @@ var OptionTUNAddressX = Note{
|
|||||||
Description: "legacy tun address fields",
|
Description: "legacy tun address fields",
|
||||||
DeprecatedVersion: "1.10.0",
|
DeprecatedVersion: "1.10.0",
|
||||||
ScheduledVersion: "1.12.0",
|
ScheduledVersion: "1.12.0",
|
||||||
EnvName: "TUN_ADDRESS_X",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged",
|
MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,23 +7,15 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stderrManager struct {
|
type envManager struct {
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
reported map[string]bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStderrManager(logger logger.Logger) Manager {
|
func NewEnvManager(logger logger.Logger) Manager {
|
||||||
return &stderrManager{
|
return &envManager{logger: logger}
|
||||||
logger: logger,
|
|
||||||
reported: make(map[string]bool),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *stderrManager) ReportDeprecated(feature Note) {
|
func (f *envManager) ReportDeprecated(feature Note) {
|
||||||
if f.reported[feature.Name] {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.reported[feature.Name] = true
|
|
||||||
if !feature.Impending() {
|
if !feature.Impending() {
|
||||||
f.logger.Warn(feature.MessageWithLink())
|
f.logger.Warn(feature.MessageWithLink())
|
||||||
return
|
return
|
||||||
@@ -20,6 +20,7 @@ type CommandClient struct {
|
|||||||
type CommandClientOptions struct {
|
type CommandClientOptions struct {
|
||||||
Command int32
|
Command int32
|
||||||
StatusInterval int64
|
StatusInterval int64
|
||||||
|
IsMainClient bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandClientHandler interface {
|
type CommandClientHandler interface {
|
||||||
@@ -28,6 +29,7 @@ type CommandClientHandler interface {
|
|||||||
ClearLogs()
|
ClearLogs()
|
||||||
WriteLogs(messageList StringIterator)
|
WriteLogs(messageList StringIterator)
|
||||||
WriteStatus(message *StatusMessage)
|
WriteStatus(message *StatusMessage)
|
||||||
|
OpenURL(url string)
|
||||||
WriteGroups(message OutboundGroupIterator)
|
WriteGroups(message OutboundGroupIterator)
|
||||||
InitializeClashMode(modeList StringIterator, currentMode string)
|
InitializeClashMode(modeList StringIterator, currentMode string)
|
||||||
UpdateClashMode(newMode string)
|
UpdateClashMode(newMode string)
|
||||||
@@ -91,9 +93,13 @@ func (c *CommandClient) Connect() error {
|
|||||||
c.handler.Connected()
|
c.handler.Connected()
|
||||||
go c.handleLogConn(conn)
|
go c.handleLogConn(conn)
|
||||||
case CommandStatus:
|
case CommandStatus:
|
||||||
|
err = binary.Write(conn, binary.BigEndian, c.options.IsMainClient)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write is main client")
|
||||||
|
}
|
||||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "write interval")
|
return E.Cause(err, "write header")
|
||||||
}
|
}
|
||||||
c.handler.Connected()
|
c.handler.Connected()
|
||||||
go c.handleStatusConn(conn)
|
go c.handleStatusConn(conn)
|
||||||
@@ -113,24 +119,11 @@ func (c *CommandClient) Connect() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if sFixAndroidStack {
|
c.handler.Connected()
|
||||||
go func() {
|
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
||||||
c.handler.Connected()
|
|
||||||
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
|
||||||
if len(modeList) == 0 {
|
|
||||||
conn.Close()
|
|
||||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
c.handler.Connected()
|
|
||||||
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
|
||||||
if len(modeList) == 0 {
|
|
||||||
conn.Close()
|
|
||||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(modeList) == 0 {
|
if len(modeList) == 0 {
|
||||||
|
conn.Close()
|
||||||
|
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
go c.handleModeConn(conn)
|
go c.handleModeConn(conn)
|
||||||
|
|||||||
40
experimental/libbox/command_event.go
Normal file
40
experimental/libbox/command_event.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type myEvent interface {
|
||||||
|
writeTo(writer varbin.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readEvent(reader varbin.Reader) (myEvent, error) {
|
||||||
|
eventType, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch eventType {
|
||||||
|
case eventTypeEmpty:
|
||||||
|
return nil, nil
|
||||||
|
case eventTypeOpenURL:
|
||||||
|
url, err := varbin.ReadValue[string](reader, binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &eventOpenURL{URL: url}, nil
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown event type: ", eventType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventOpenURL struct {
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eventOpenURL) writeTo(writer varbin.Writer) {
|
||||||
|
writer.WriteByte(eventTypeOpenURL)
|
||||||
|
varbin.Write(writer, binary.BigEndian, e.URL)
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -118,14 +118,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
|||||||
}
|
}
|
||||||
var groups []OutboundGroup
|
var groups []OutboundGroup
|
||||||
for _, iGroup := range iGroups {
|
for _, iGroup := range iGroups {
|
||||||
var group OutboundGroup
|
var outboundGroup OutboundGroup
|
||||||
group.Tag = iGroup.Tag()
|
outboundGroup.Tag = iGroup.Tag()
|
||||||
group.Type = iGroup.Type()
|
outboundGroup.Type = iGroup.Type()
|
||||||
_, group.Selectable = iGroup.(*outbound.Selector)
|
_, outboundGroup.Selectable = iGroup.(*group.Selector)
|
||||||
group.Selected = iGroup.Now()
|
outboundGroup.Selected = iGroup.Now()
|
||||||
if cacheFile != nil {
|
if cacheFile != nil {
|
||||||
if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded {
|
if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded {
|
||||||
group.IsExpand = isExpand
|
outboundGroup.IsExpand = isExpand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,12 +142,12 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
|||||||
item.URLTestTime = history.Time.Unix()
|
item.URLTestTime = history.Time.Unix()
|
||||||
item.URLTestDelay = int32(history.Delay)
|
item.URLTestDelay = int32(history.Delay)
|
||||||
}
|
}
|
||||||
group.ItemList = append(group.ItemList, &item)
|
outboundGroup.ItemList = append(outboundGroup.ItemList, &item)
|
||||||
}
|
}
|
||||||
if len(group.ItemList) < 2 {
|
if len(outboundGroup.ItemList) < 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
groups = append(groups, group)
|
groups = append(groups, outboundGroup)
|
||||||
}
|
}
|
||||||
return varbin.Write(writer, binary.BigEndian, groups)
|
return varbin.Write(writer, binary.BigEndian, groups)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
@@ -47,7 +47,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
|
|||||||
if !isLoaded {
|
if !isLoaded {
|
||||||
return writeError(conn, E.New("selector not found: ", groupTag))
|
return writeError(conn, E.New("selector not found: ", groupTag))
|
||||||
}
|
}
|
||||||
selector, isSelector := outboundGroup.(*outbound.Selector)
|
selector, isSelector := outboundGroup.(*group.Selector)
|
||||||
if !isSelector {
|
if !isSelector {
|
||||||
return writeError(conn, E.New("outbound is not a selector: ", groupTag))
|
return writeError(conn, E.New("outbound is not a selector: ", groupTag))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type CommandServer struct {
|
|||||||
urlTestUpdate chan struct{}
|
urlTestUpdate chan struct{}
|
||||||
modeUpdate chan struct{}
|
modeUpdate chan struct{}
|
||||||
logReset chan struct{}
|
logReset chan struct{}
|
||||||
|
events chan myEvent
|
||||||
|
|
||||||
closedConnections []Connection
|
closedConnections []Connection
|
||||||
}
|
}
|
||||||
@@ -52,6 +53,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ
|
|||||||
urlTestUpdate: make(chan struct{}, 1),
|
urlTestUpdate: make(chan struct{}, 1),
|
||||||
modeUpdate: make(chan struct{}, 1),
|
modeUpdate: make(chan struct{}, 1),
|
||||||
logReset: make(chan struct{}, 1),
|
logReset: make(chan struct{}, 1),
|
||||||
|
events: make(chan myEvent, 8),
|
||||||
}
|
}
|
||||||
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
||||||
return server
|
return server
|
||||||
@@ -61,6 +63,12 @@ func (s *CommandServer) SetService(newService *BoxService) {
|
|||||||
if newService != nil {
|
if newService != nil {
|
||||||
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
|
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
|
||||||
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
|
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
|
||||||
|
newService.platformInterface.openURLFunc = func(url string) {
|
||||||
|
select {
|
||||||
|
case s.events <- &eventOpenURL{URL: url}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.service = newService
|
s.service = newService
|
||||||
s.notifyURLTestUpdate()
|
s.notifyURLTestUpdate()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
std_bufio "bufio"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -9,9 +10,15 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/memory"
|
"github.com/sagernet/sing/common/memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
eventTypeEmpty byte = iota
|
||||||
|
eventTypeOpenURL
|
||||||
|
)
|
||||||
|
|
||||||
type StatusMessage struct {
|
type StatusMessage struct {
|
||||||
Memory int64
|
Memory int64
|
||||||
Goroutines int32
|
Goroutines int32
|
||||||
@@ -34,6 +41,7 @@ func (s *CommandServer) readStatus() StatusMessage {
|
|||||||
if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil {
|
if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil {
|
||||||
message.TrafficAvailable = true
|
message.TrafficAvailable = true
|
||||||
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
|
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
|
||||||
|
message.Uplink, message.Downlink = trafficManager.Now()
|
||||||
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
|
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
|
||||||
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
|
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
|
||||||
}
|
}
|
||||||
@@ -43,41 +51,73 @@ func (s *CommandServer) readStatus() StatusMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
||||||
|
var isMainClient bool
|
||||||
|
err := binary.Read(conn, binary.BigEndian, &isMainClient)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read is main client")
|
||||||
|
}
|
||||||
var interval int64
|
var interval int64
|
||||||
err := binary.Read(conn, binary.BigEndian, &interval)
|
err = binary.Read(conn, binary.BigEndian, &interval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read interval")
|
return E.Cause(err, "read interval")
|
||||||
}
|
}
|
||||||
ticker := time.NewTicker(time.Duration(interval))
|
ticker := time.NewTicker(time.Duration(interval))
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
ctx := connKeepAlive(conn)
|
ctx := connKeepAlive(conn)
|
||||||
status := s.readStatus()
|
writer := std_bufio.NewWriter(conn)
|
||||||
uploadTotal := status.UplinkTotal
|
if isMainClient {
|
||||||
downloadTotal := status.DownlinkTotal
|
for {
|
||||||
for {
|
writer.WriteByte(eventTypeEmpty)
|
||||||
err = binary.Write(conn, binary.BigEndian, status)
|
err = binary.Write(conn, binary.BigEndian, s.readStatus())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
writer.Flush()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-ticker.C:
|
||||||
|
case event := <-s.events:
|
||||||
|
event.writeTo(writer)
|
||||||
|
writer.Flush()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
select {
|
} else {
|
||||||
case <-ctx.Done():
|
for {
|
||||||
return ctx.Err()
|
err = binary.Write(conn, binary.BigEndian, s.readStatus())
|
||||||
case <-ticker.C:
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writer.Flush()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
status = s.readStatus()
|
|
||||||
upload := status.UplinkTotal - uploadTotal
|
|
||||||
download := status.DownlinkTotal - downloadTotal
|
|
||||||
uploadTotal = status.UplinkTotal
|
|
||||||
downloadTotal = status.DownlinkTotal
|
|
||||||
status.Uplink = upload
|
|
||||||
status.Downlink = download
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandClient) handleStatusConn(conn net.Conn) {
|
func (c *CommandClient) handleStatusConn(conn net.Conn) {
|
||||||
|
reader := std_bufio.NewReader(conn)
|
||||||
for {
|
for {
|
||||||
|
if c.options.IsMainClient {
|
||||||
|
rawEvent, err := readEvent(reader)
|
||||||
|
if err != nil {
|
||||||
|
c.handler.Disconnected(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch event := rawEvent.(type) {
|
||||||
|
case *eventOpenURL:
|
||||||
|
c.handler.OpenURL(event.URL)
|
||||||
|
continue
|
||||||
|
case nil:
|
||||||
|
default:
|
||||||
|
panic(F.ToString("unexpected event type: ", event))
|
||||||
|
}
|
||||||
|
}
|
||||||
var message StatusMessage
|
var message StatusMessage
|
||||||
err := binary.Read(conn, binary.BigEndian, &message)
|
err := binary.Read(reader, binary.BigEndian, &message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.handler.Disconnected(err.Error())
|
c.handler.Disconnected(err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -49,7 +49,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
|||||||
if !isOutboundGroup {
|
if !isOutboundGroup {
|
||||||
return writeError(conn, E.New("outbound is not a group: ", groupTag))
|
return writeError(conn, E.New("outbound is not a group: ", groupTag))
|
||||||
}
|
}
|
||||||
urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest)
|
urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest)
|
||||||
if isURLTest {
|
if isURLTest {
|
||||||
go urlTest.CheckOutbounds()
|
go urlTest.CheckOutbounds()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -17,10 +18,11 @@ import (
|
|||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseConfig(configContent string) (option.Options, error) {
|
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||||
options, err := json.UnmarshalExtended[option.Options]([]byte(configContent))
|
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "decode config")
|
return option.Options{}, E.Cause(err, "decode config")
|
||||||
}
|
}
|
||||||
@@ -28,16 +30,16 @@ func parseConfig(configContent string) (option.Options, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckConfig(configContent string) error {
|
func CheckConfig(configContent string) error {
|
||||||
options, err := parseConfig(configContent)
|
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil))
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
PlatformInterface: (*platformInterfaceStub)(nil),
|
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
instance.Close()
|
instance.Close()
|
||||||
@@ -55,7 +57,7 @@ func (s *platformInterfaceStub) UsePlatformAutoDetectInterfaceControl() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error {
|
func (s *platformInterfaceStub) AutoDetectInterfaceControl() control.Func {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,21 +137,20 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
|
|||||||
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error {
|
func (s *platformInterfaceStub) OpenURL(url string) {
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatConfig(configContent string) (*StringBox, error) {
|
func FormatConfig(configContent string) (string, error) {
|
||||||
options, err := parseConfig(configContent)
|
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
encoder := json.NewEncoder(&buffer)
|
encoder := json.NewEncoder(&buffer)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
err = encoder.Encode(options)
|
err = encoder.Encode(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
return wrapString(buffer.String()), nil
|
return buffer.String(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,28 +4,27 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ deprecated.Manager = (*deprecatedManager)(nil)
|
var _ deprecated.Manager = (*deprecatedManager)(nil)
|
||||||
|
|
||||||
type deprecatedManager struct {
|
type deprecatedManager struct {
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
notes []deprecated.Note
|
features []deprecated.Note
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
|
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
m.notes = common.Uniq(append(m.notes, feature))
|
m.features = append(m.features, feature)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *deprecatedManager) Get() []deprecated.Note {
|
func (m *deprecatedManager) Get() []deprecated.Note {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
notes := m.notes
|
features := m.features
|
||||||
m.notes = nil
|
m.features = nil
|
||||||
return notes
|
return features
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = deprecated.Note(DeprecatedNote{})
|
var _ = deprecated.Note(DeprecatedNote{})
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ type HTTPRequest interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPResponse interface {
|
type HTTPResponse interface {
|
||||||
GetContent() (*StringBox, error)
|
GetContent() ([]byte, error)
|
||||||
|
GetContentString() (string, error)
|
||||||
WriteTo(path string) error
|
WriteTo(path string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,22 +210,27 @@ type httpResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpResponse) errorString() string {
|
func (h *httpResponse) errorString() string {
|
||||||
content, err := h.GetContent()
|
content, err := h.GetContentString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprint("HTTP ", h.Status)
|
return fmt.Sprint("HTTP ", h.Status)
|
||||||
}
|
}
|
||||||
return fmt.Sprint("HTTP ", h.Status, ": ", content)
|
return fmt.Sprint("HTTP ", h.Status, ": ", content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpResponse) GetContent() (*StringBox, error) {
|
func (h *httpResponse) GetContent() ([]byte, error) {
|
||||||
h.getContentOnce.Do(func() {
|
h.getContentOnce.Do(func() {
|
||||||
defer h.Body.Close()
|
defer h.Body.Close()
|
||||||
h.content, h.contentError = io.ReadAll(h.Body)
|
h.content, h.contentError = io.ReadAll(h.Body)
|
||||||
})
|
})
|
||||||
if h.contentError != nil {
|
return h.content, h.contentError
|
||||||
return nil, h.contentError
|
}
|
||||||
|
|
||||||
|
func (h *httpResponse) GetContentString() (string, error) {
|
||||||
|
content, err := h.GetContent()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
return wrapString(string(h.content)), nil
|
return string(content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpResponse) WriteTo(path string) error {
|
func (h *httpResponse) WriteTo(path string) error {
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// copied from net.linkFlags
|
|
||||||
func linkFlags(rawFlags uint32) net.Flags {
|
|
||||||
var f net.Flags
|
|
||||||
if rawFlags&syscall.IFF_UP != 0 {
|
|
||||||
f |= net.FlagUp
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_RUNNING != 0 {
|
|
||||||
f |= net.FlagRunning
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_BROADCAST != 0 {
|
|
||||||
f |= net.FlagBroadcast
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_LOOPBACK != 0 {
|
|
||||||
f |= net.FlagLoopback
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_POINTOPOINT != 0 {
|
|
||||||
f |= net.FlagPointToPoint
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_MULTICAST != 0 {
|
|
||||||
f |= net.FlagMulticast
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func linkFlags(rawFlags uint32) net.Flags {
|
|
||||||
panic("stub!")
|
|
||||||
}
|
|
||||||
@@ -97,14 +97,6 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
||||||
if sFixAndroidStack {
|
|
||||||
go m.updateDefaultInterface(interfaceName, interfaceIndex32)
|
|
||||||
} else {
|
|
||||||
m.updateDefaultInterface(interfaceName, interfaceIndex32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
|
||||||
if interfaceName == "" || interfaceIndex32 == -1 {
|
if interfaceName == "" || interfaceIndex32 == -1 {
|
||||||
m.defaultInterfaceName = ""
|
m.defaultInterfaceName = ""
|
||||||
m.defaultInterfaceIndex = -1
|
m.defaultInterfaceIndex = -1
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
// https://github.com/golang/go/issues/46893
|
|
||||||
// TODO: remove after `bulkBarrierPreWrite: unaligned arguments` fixed
|
|
||||||
|
|
||||||
type StringBox struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapString(value string) *StringBox {
|
|
||||||
return &StringBox{Value: value}
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@ type PlatformInterface interface {
|
|||||||
IncludeAllNetworks() bool
|
IncludeAllNetworks() bool
|
||||||
ReadWIFIState() *WIFIState
|
ReadWIFIState() *WIFIState
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
SendNotification(notification *Notification) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunInterface interface {
|
type TunInterface interface {
|
||||||
@@ -39,7 +38,6 @@ type NetworkInterface struct {
|
|||||||
MTU int32
|
MTU int32
|
||||||
Name string
|
Name string
|
||||||
Addresses StringIterator
|
Addresses StringIterator
|
||||||
Flags int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WIFIState struct {
|
type WIFIState struct {
|
||||||
@@ -56,16 +54,6 @@ type NetworkInterfaceIterator interface {
|
|||||||
HasNext() bool
|
HasNext() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
|
||||||
Identifier string
|
|
||||||
TypeName string
|
|
||||||
TypeID int32
|
|
||||||
Title string
|
|
||||||
Subtitle string
|
|
||||||
Body string
|
|
||||||
OpenURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
type OnDemandRule interface {
|
type OnDemandRule interface {
|
||||||
Target() int32
|
Target() int32
|
||||||
DNSSearchDomainMatch() StringIterator
|
DNSSearchDomainMatch() StringIterator
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
type Interface interface {
|
type Interface interface {
|
||||||
Initialize(ctx context.Context, router adapter.Router) error
|
Initialize(ctx context.Context, router adapter.Router) error
|
||||||
UsePlatformAutoDetectInterfaceControl() bool
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
AutoDetectInterfaceControl(fd int) error
|
AutoDetectInterfaceControl() control.Func
|
||||||
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||||
UsePlatformDefaultInterfaceMonitor() bool
|
UsePlatformDefaultInterfaceMonitor() bool
|
||||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||||
@@ -25,15 +25,5 @@ type Interface interface {
|
|||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
ReadWIFIState() adapter.WIFIState
|
ReadWIFIState() adapter.WIFIState
|
||||||
process.Searcher
|
process.Searcher
|
||||||
SendNotification(notification *Notification) error
|
OpenURL(url string)
|
||||||
}
|
|
||||||
|
|
||||||
type Notification struct {
|
|
||||||
Identifier string
|
|
||||||
TypeName string
|
|
||||||
TypeID int32
|
|
||||||
Title string
|
|
||||||
Subtitle string
|
|
||||||
Body string
|
|
||||||
OpenURL string
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
@@ -34,28 +35,29 @@ type BoxService struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
instance *box.Box
|
instance *box.Box
|
||||||
|
platformInterface *platformInterfaceWrapper
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
urlTestHistoryStorage *urltest.HistoryStorage
|
urlTestHistoryStorage *urltest.HistoryStorage
|
||||||
|
|
||||||
servicePauseFields
|
servicePauseFields
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||||
options, err := parseConfig(configContent)
|
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry())
|
||||||
|
options, err := parseConfig(ctx, configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
runtimeDebug.FreeOSMemory()
|
runtimeDebug.FreeOSMemory()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||||
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||||
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||||
ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager))
|
ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager))
|
||||||
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
|
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
|
||||||
|
ctx = service.ContextWith[platform.Interface](ctx, platformWrapper)
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
PlatformInterface: platformWrapper,
|
|
||||||
PlatformLogWriter: platformWrapper,
|
PlatformLogWriter: platformWrapper,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -67,42 +69,30 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
|||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
instance: instance,
|
instance: instance,
|
||||||
|
platformInterface: platformWrapper,
|
||||||
urlTestHistoryStorage: urlTestHistoryStorage,
|
urlTestHistoryStorage: urlTestHistoryStorage,
|
||||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) Start() error {
|
func (s *BoxService) Start() error {
|
||||||
if sFixAndroidStack {
|
return s.instance.Start()
|
||||||
var err error
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
err = s.instance.Start()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
<-done
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return s.instance.Start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) Close() error {
|
func (s *BoxService) Close() error {
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case <-time.After(C.FatalStopTimeout):
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
s.cancel()
|
s.cancel()
|
||||||
s.urlTestHistoryStorage.Close()
|
s.urlTestHistoryStorage.Close()
|
||||||
var err error
|
return s.instance.Close()
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
err = s.instance.Close()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return err
|
|
||||||
case <-time.After(C.FatalStopTimeout):
|
|
||||||
os.Exit(1)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) NeedWIFIState() bool {
|
func (s *BoxService) NeedWIFIState() bool {
|
||||||
@@ -115,9 +105,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type platformInterfaceWrapper struct {
|
type platformInterfaceWrapper struct {
|
||||||
iif PlatformInterface
|
iif PlatformInterface
|
||||||
useProcFS bool
|
useProcFS bool
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
|
openURLFunc func(url string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error {
|
func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error {
|
||||||
@@ -129,8 +120,12 @@ func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool
|
|||||||
return w.iif.UsePlatformAutoDetectInterfaceControl()
|
return w.iif.UsePlatformAutoDetectInterfaceControl()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
|
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
||||||
return w.iif.AutoDetectInterfaceControl(int32(fd))
|
return func(network, address string, conn syscall.RawConn) error {
|
||||||
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
|
return w.iif.AutoDetectInterfaceControl(int32(fd))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||||
@@ -188,7 +183,6 @@ func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) {
|
|||||||
MTU: int(netInterface.MTU),
|
MTU: int(netInterface.MTU),
|
||||||
Name: netInterface.Name,
|
Name: netInterface.Name,
|
||||||
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
||||||
Flags: linkFlags(uint32(netInterface.Flags)),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return interfaces, nil
|
return interfaces, nil
|
||||||
@@ -249,6 +243,8 @@ func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string)
|
|||||||
w.iif.WriteLog(message)
|
w.iif.WriteLog(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) SendNotification(notification *platform.Notification) error {
|
func (w *platformInterfaceWrapper) OpenURL(url string) {
|
||||||
return w.iif.SendNotification((*Notification)(notification))
|
if w.openURLFunc != nil {
|
||||||
|
w.openURLFunc(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ func ClearServiceError() {
|
|||||||
os.Remove(serviceErrorPath())
|
os.Remove(serviceErrorPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadServiceError() (*StringBox, error) {
|
func ReadServiceError() (string, error) {
|
||||||
data, err := os.ReadFile(serviceErrorPath())
|
data, err := os.ReadFile(serviceErrorPath())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
os.Remove(serviceErrorPath())
|
os.Remove(serviceErrorPath())
|
||||||
}
|
}
|
||||||
return wrapString(string(data)), err
|
return string(data), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteServiceError(message string) error {
|
func WriteServiceError(message string) error {
|
||||||
|
|||||||
@@ -9,66 +9,49 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/locale"
|
|
||||||
_ "github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sBasePath string
|
sBasePath string
|
||||||
sWorkingPath string
|
sWorkingPath string
|
||||||
sTempPath string
|
sTempPath string
|
||||||
sUserID int
|
sUserID int
|
||||||
sGroupID int
|
sGroupID int
|
||||||
sTVOS bool
|
sTVOS bool
|
||||||
sFixAndroidStack bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
debug.SetPanicOnFault(true)
|
debug.SetPanicOnFault(true)
|
||||||
|
debug.SetTraceback("all")
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetupOptions struct {
|
func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) {
|
||||||
BasePath string
|
sBasePath = basePath
|
||||||
WorkingPath string
|
sWorkingPath = workingPath
|
||||||
TempPath string
|
sTempPath = tempPath
|
||||||
Username string
|
sUserID = os.Getuid()
|
||||||
IsTVOS bool
|
sGroupID = os.Getgid()
|
||||||
FixAndroidStack bool
|
sTVOS = isTVOS
|
||||||
}
|
|
||||||
|
|
||||||
func Setup(options *SetupOptions) error {
|
|
||||||
sBasePath = options.BasePath
|
|
||||||
sWorkingPath = options.WorkingPath
|
|
||||||
sTempPath = options.TempPath
|
|
||||||
if options.Username != "" {
|
|
||||||
sUser, err := user.Lookup(options.Username)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sUserID, _ = strconv.Atoi(sUser.Uid)
|
|
||||||
sGroupID, _ = strconv.Atoi(sUser.Gid)
|
|
||||||
} else {
|
|
||||||
sUserID = os.Getuid()
|
|
||||||
sGroupID = os.Getgid()
|
|
||||||
}
|
|
||||||
sTVOS = options.IsTVOS
|
|
||||||
|
|
||||||
// TODO: remove after fixed
|
|
||||||
// https://github.com/golang/go/issues/68760
|
|
||||||
sFixAndroidStack = options.FixAndroidStack
|
|
||||||
|
|
||||||
os.MkdirAll(sWorkingPath, 0o777)
|
os.MkdirAll(sWorkingPath, 0o777)
|
||||||
os.MkdirAll(sTempPath, 0o777)
|
os.MkdirAll(sTempPath, 0o777)
|
||||||
if options.Username != "" {
|
|
||||||
os.Chown(sWorkingPath, sUserID, sGroupID)
|
|
||||||
os.Chown(sTempPath, sUserID, sGroupID)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetLocale(localeId string) {
|
func SetupWithUsername(basePath string, workingPath string, tempPath string, username string) error {
|
||||||
locale.Set(localeId)
|
sBasePath = basePath
|
||||||
|
sWorkingPath = workingPath
|
||||||
|
sTempPath = tempPath
|
||||||
|
sUser, err := user.Lookup(username)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sUserID, _ = strconv.Atoi(sUser.Uid)
|
||||||
|
sGroupID, _ = strconv.Atoi(sUser.Gid)
|
||||||
|
os.MkdirAll(sWorkingPath, 0o777)
|
||||||
|
os.MkdirAll(sTempPath, 0o777)
|
||||||
|
os.Chown(sWorkingPath, sUserID, sGroupID)
|
||||||
|
os.Chown(sTempPath, sUserID, sGroupID)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Version() string {
|
func Version() string {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
type TunOptions interface {
|
type TunOptions interface {
|
||||||
GetInet4Address() RoutePrefixIterator
|
GetInet4Address() RoutePrefixIterator
|
||||||
GetInet6Address() RoutePrefixIterator
|
GetInet6Address() RoutePrefixIterator
|
||||||
GetDNSServerAddress() (*StringBox, error)
|
GetDNSServerAddress() (string, error)
|
||||||
GetMTU() int32
|
GetMTU() int32
|
||||||
GetAutoRoute() bool
|
GetAutoRoute() bool
|
||||||
GetStrictRoute() bool
|
GetStrictRoute() bool
|
||||||
@@ -89,11 +89,11 @@ func (o *tunOptions) GetInet6Address() RoutePrefixIterator {
|
|||||||
return mapRoutePrefix(o.Inet6Address)
|
return mapRoutePrefix(o.Inet6Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *tunOptions) GetDNSServerAddress() (*StringBox, error) {
|
func (o *tunOptions) GetDNSServerAddress() (string, error) {
|
||||||
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
|
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
|
||||||
return nil, E.New("need one more IPv4 address for DNS hijacking")
|
return "", E.New("need one more IPv4 address for DNS hijacking")
|
||||||
}
|
}
|
||||||
return wrapString(o.Inet4Address[0].Addr().Next().String()), nil
|
return o.Inet4Address[0].Addr().Next().String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *tunOptions) GetMTU() int32 {
|
func (o *tunOptions) GetMTU() int32 {
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package locale
|
|
||||||
|
|
||||||
var (
|
|
||||||
localeRegistry = make(map[string]*Locale)
|
|
||||||
current = defaultLocal
|
|
||||||
)
|
|
||||||
|
|
||||||
type Locale struct {
|
|
||||||
// deprecated messages for graphical clients
|
|
||||||
DeprecatedMessage string
|
|
||||||
DeprecatedMessageNoLink string
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultLocal = &Locale{
|
|
||||||
DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.",
|
|
||||||
DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.",
|
|
||||||
}
|
|
||||||
|
|
||||||
func Current() *Locale {
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
func Set(localeId string) bool {
|
|
||||||
locale, loaded := localeRegistry[localeId]
|
|
||||||
if !loaded {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
current = locale
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user