mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
100 Commits
v1.11.0-al
...
dev-ndis
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79d3649a8b | ||
|
|
e483c909b4 | ||
|
|
d9579c26ee | ||
|
|
48d3021b2c | ||
|
|
ce0fcd5c8b | ||
|
|
d9d0a2373a | ||
|
|
0c754505f7 | ||
|
|
606ff668da | ||
|
|
f43703801b | ||
|
|
1ed8f3a8d3 | ||
|
|
60fc913dc3 | ||
|
|
be8ee370ac | ||
|
|
0a9bf97438 | ||
|
|
74de437bfb | ||
|
|
c385e7c137 | ||
|
|
01291d16e0 | ||
|
|
11a448b52d | ||
|
|
22bda86bbf | ||
|
|
04cc343b2e | ||
|
|
093e539d3d | ||
|
|
5821b974bd | ||
|
|
8ce40f77b4 | ||
|
|
b45cb0763e | ||
|
|
48d102a0ab | ||
|
|
6a5943f4ce | ||
|
|
c9f9d9ee1c | ||
|
|
f3bf440c91 | ||
|
|
292fcde876 | ||
|
|
53e227a318 | ||
|
|
65cb225a2c | ||
|
|
7819f13489 | ||
|
|
f2780d0713 | ||
|
|
72239dcbd3 | ||
|
|
daf38a84e1 | ||
|
|
f2ddc5883b | ||
|
|
0437ac512b | ||
|
|
d297ad4c56 | ||
|
|
6823670f3d | ||
|
|
4a611eddf4 | ||
|
|
f12a294fb7 | ||
|
|
040a188c66 | ||
|
|
7ed10b35d0 | ||
|
|
afd341adfd | ||
|
|
7d26bac5ac | ||
|
|
63d8f6dc1c | ||
|
|
15cc3b85eb | ||
|
|
5a1c59ca88 | ||
|
|
7686503df8 | ||
|
|
96a8de9548 | ||
|
|
578571b972 | ||
|
|
935beca45d | ||
|
|
3e246f1173 | ||
|
|
1bc27a32c2 | ||
|
|
bc2e3960e4 | ||
|
|
9c4ab0bf33 | ||
|
|
27bdef34c7 | ||
|
|
3c00099ed4 | ||
|
|
2babf07f9a | ||
|
|
4795ed712b | ||
|
|
d4cd564dbe | ||
|
|
1676e13d3e | ||
|
|
50576084c6 | ||
|
|
3a94e792a2 | ||
|
|
9f69f41f68 | ||
|
|
e6847ff50e | ||
|
|
2ac2589d14 | ||
|
|
64a94e8144 | ||
|
|
3ed8a5c5d1 | ||
|
|
0a922c6fe3 | ||
|
|
52f3a4226c | ||
|
|
483d9fa503 | ||
|
|
dd9de694f8 | ||
|
|
5cdf5c1d9e | ||
|
|
cec7e47086 | ||
|
|
1e6a3f1f0b | ||
|
|
f0b6818b4c | ||
|
|
3032317918 | ||
|
|
db22f61846 | ||
|
|
8c3a98faa2 | ||
|
|
1e787cb607 | ||
|
|
558585b01d | ||
|
|
6e7ecbd4f5 | ||
|
|
5a661cde67 | ||
|
|
3cc0e87cfb | ||
|
|
effea5a2b3 | ||
|
|
7f168c5ec6 | ||
|
|
0e9129ee3f | ||
|
|
1086d5e665 | ||
|
|
d9102ba599 | ||
|
|
17019f1729 | ||
|
|
6be07ed51f | ||
|
|
af58e3bec0 | ||
|
|
e58b549d0f | ||
|
|
1d81996ceb | ||
|
|
97c47e72c4 | ||
|
|
122be275b0 | ||
|
|
0bb1132034 | ||
|
|
de14337b4b | ||
|
|
1e07633914 | ||
|
|
e3e203844e |
629
.github/workflows/build.yml
vendored
Normal file
629
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,629 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version name"
|
||||
required: true
|
||||
type: string
|
||||
build:
|
||||
description: "Build type"
|
||||
required: true
|
||||
type: choice
|
||||
default: "All"
|
||||
options:
|
||||
- All
|
||||
- Binary
|
||||
- Android
|
||||
- Apple
|
||||
- app-store
|
||||
- iOS
|
||||
- macOS
|
||||
- tvOS
|
||||
- macOS-standalone
|
||||
- publish-android
|
||||
push:
|
||||
branches:
|
||||
- main-next
|
||||
- dev-next
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
calculate_version:
|
||||
name: Calculate version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.outputs.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
echo "version=${{ inputs.version }}"
|
||||
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||
- name: Calculate version
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/read_tag --nightly
|
||||
- name: Set outputs
|
||||
id: outputs
|
||||
run: |-
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
build:
|
||||
name: Build binary
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: linux_386
|
||||
goos: linux
|
||||
goarch: 386
|
||||
- name: linux_amd64
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
- name: linux_arm64
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
- name: linux_arm
|
||||
goos: linux
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
- name: linux_arm_v7
|
||||
goos: linux
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- name: linux_s390x
|
||||
goos: linux
|
||||
goarch: s390x
|
||||
- name: linux_riscv64
|
||||
goos: linux
|
||||
goarch: riscv64
|
||||
- name: linux_mips64le
|
||||
goos: linux
|
||||
goarch: mips64le
|
||||
- name: windows_amd64
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
require_legacy_go: true
|
||||
- name: windows_386
|
||||
goos: windows
|
||||
goarch: 386
|
||||
require_legacy_go: true
|
||||
- name: windows_arm64
|
||||
goos: windows
|
||||
goarch: arm64
|
||||
- name: darwin_arm64
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
- name: darwin_amd64
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
require_legacy_go: true
|
||||
- name: android_arm64
|
||||
goos: android
|
||||
goarch: arm64
|
||||
- name: android_arm
|
||||
goos: android
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- name: android_amd64
|
||||
goos: android
|
||||
goarch: amd64
|
||||
- name: android_386
|
||||
goos: android
|
||||
goarch: 386
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Cache legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
id: cache-legacy-go
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/go1.20.14
|
||||
key: go120
|
||||
- name: Setup legacy Go
|
||||
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||
run: |-
|
||||
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
||||
tar -xzf go1.20.14.linux-amd64.tar.gz
|
||||
mv go $HOME/go/go1.20.14
|
||||
- name: Setup Android NDK
|
||||
if: matrix.goos == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
local-cache: true
|
||||
- name: Setup Goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
install-only: true
|
||||
- name: Extract signing key
|
||||
run: |-
|
||||
mkdir -p $HOME/.gnupg
|
||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
EOF
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
- name: Set tag
|
||||
run: |-
|
||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||
- name: Build
|
||||
if: matrix.goos != 'android'
|
||||
run: |-
|
||||
goreleaser release --clean --split
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
GOPATH: ${{ env.HOME }}/go
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
- name: Build Android
|
||||
if: matrix.goos == 'android'
|
||||
run: |-
|
||||
go install -v ./cmd/internal/build
|
||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
|
||||
env:
|
||||
BUILD_GOOS: ${{ matrix.goos }}
|
||||
BUILD_GOARCH: ${{ matrix.goarch }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
- name: Upload artifact
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-${{ matrix.name }}
|
||||
path: 'dist'
|
||||
build_android:
|
||||
name: Build Android
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- calculate_version
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
- name: Setup OpenJDK
|
||||
run: |-
|
||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||
- name: Set tag
|
||||
run: |-
|
||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||
- name: Build library
|
||||
run: |-
|
||||
make lib_install
|
||||
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||
make lib_android
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
- name: Checkout main branch
|
||||
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
cd clients/android
|
||||
git checkout main
|
||||
- name: Checkout dev branch
|
||||
if: github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
cd clients/android
|
||||
git checkout dev
|
||||
- name: Gradle cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle
|
||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||
- name: Build release
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci
|
||||
mkdir clients/android/app/libs
|
||||
cp libbox.aar clients/android/app/libs
|
||||
cd clients/android
|
||||
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||
- name: Build debug
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci
|
||||
mkdir clients/android/app/libs
|
||||
cp libbox.aar clients/android/app/libs
|
||||
cd clients/android
|
||||
./gradlew :app:assemblePlayRelease
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||
- name: Prepare release upload
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
mkdir -p dist/release
|
||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
||||
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
|
||||
- name: Prepare debug upload
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
mkdir -p dist/release
|
||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
||||
- name: Upload artifact
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-android-apks
|
||||
path: 'dist'
|
||||
- name: Upload debug apk (arm64-v8a)
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SFA-${{ needs.calculate_version.outputs.version }}-arm64-v8a.apk"
|
||||
path: 'dist/release/*-arm64-v8a.apk'
|
||||
- name: Upload debug apk (universal)
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SFA-${{ needs.calculate_version.outputs.version }}-universal.apk"
|
||||
path: 'dist/release/*-universal.apk'
|
||||
publish_android:
|
||||
name: Publish Android
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- calculate_version
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
- name: Setup OpenJDK
|
||||
run: |-
|
||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||
- name: Set tag
|
||||
run: |-
|
||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||
- name: Build library
|
||||
run: |-
|
||||
make lib_install
|
||||
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||
make lib_android
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
- name: Checkout main branch
|
||||
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
cd clients/android
|
||||
git checkout main
|
||||
- name: Checkout dev branch
|
||||
if: github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
cd clients/android
|
||||
git checkout dev
|
||||
- name: Gradle cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle
|
||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||
- name: Build
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci
|
||||
mkdir clients/android/app/libs
|
||||
cp libbox.aar clients/android/app/libs
|
||||
cd clients/android
|
||||
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
||||
./gradlew :app:publishPlayReleaseBundle
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||
build_apple:
|
||||
name: Build Apple clients
|
||||
runs-on: macos-15
|
||||
needs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: iOS
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
|
||||
platform: ios
|
||||
scheme: SFI
|
||||
destination: 'generic/platform=iOS'
|
||||
archive: build/SFI.xcarchive
|
||||
upload: SFI/Upload.plist
|
||||
- name: macOS
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
|
||||
platform: macos
|
||||
scheme: SFM
|
||||
destination: 'generic/platform=macOS'
|
||||
archive: build/SFM.xcarchive
|
||||
upload: SFI/Upload.plist
|
||||
- name: tvOS
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
|
||||
platform: tvos
|
||||
scheme: SFT
|
||||
destination: 'generic/platform=tvOS'
|
||||
archive: build/SFT.xcarchive
|
||||
upload: SFI/Upload.plist
|
||||
- name: macOS-standalone
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
|
||||
platform: macos
|
||||
scheme: SFM.System
|
||||
destination: 'generic/platform=macOS'
|
||||
archive: build/SFM.System.xcarchive
|
||||
export: SFM.System/Export.plist
|
||||
export_path: build/SFM.System
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: matrix.if
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Setup Go
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||
- name: Setup Xcode beta
|
||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||
- name: Set tag
|
||||
if: matrix.if
|
||||
run: |-
|
||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||
- name: Checkout main branch
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
cd clients/apple
|
||||
git checkout main
|
||||
- name: Checkout dev branch
|
||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
cd clients/apple
|
||||
git checkout dev
|
||||
- name: Setup certificates
|
||||
if: matrix.if
|
||||
run: |-
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db
|
||||
echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||
|
||||
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
||||
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
||||
|
||||
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
mkdir -p "$PROFILES_PATH"
|
||||
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
||||
|
||||
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
||||
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
||||
|
||||
xcrun notarytool store-credentials "notarytool-password" \
|
||||
--key $ASC_KEY_PATH \
|
||||
--key-id $ASC_KEY_ID \
|
||||
--issuer $ASC_KEY_ISSUER_ID
|
||||
|
||||
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
||||
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
||||
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
||||
env:
|
||||
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
|
||||
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||
PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}
|
||||
ASC_KEY: ${{ secrets.ASC_KEY }}
|
||||
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
|
||||
- name: Build library
|
||||
if: matrix.if
|
||||
run: |-
|
||||
make lib_install
|
||||
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||
go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
|
||||
mv Libbox.xcframework clients/apple
|
||||
- name: Update macOS version
|
||||
if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||
- name: Build
|
||||
if: matrix.if
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_apple_version --ci
|
||||
cd clients/apple
|
||||
xcodebuild archive \
|
||||
-scheme "${{ matrix.scheme }}" \
|
||||
-configuration Release \
|
||||
-destination "${{ matrix.destination }}" \
|
||||
-archivePath "${{ matrix.archive }}" \
|
||||
-allowProvisioningUpdates \
|
||||
-authenticationKeyPath $ASC_KEY_PATH \
|
||||
-authenticationKeyID $ASC_KEY_ID \
|
||||
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||
- name: Upload to App Store Connect
|
||||
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
|
||||
cd clients/apple
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath "${{ matrix.archive }}" \
|
||||
-exportOptionsPlist ${{ matrix.upload }} \
|
||||
-allowProvisioningUpdates \
|
||||
-authenticationKeyPath $ASC_KEY_PATH \
|
||||
-authenticationKeyID $ASC_KEY_ID \
|
||||
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||
- name: Publish to TestFlight
|
||||
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
||||
- name: Build image
|
||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
pushd clients/apple
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath "${{ matrix.archive }}" \
|
||||
-exportOptionsPlist ${{ matrix.export }} \
|
||||
-exportPath "${{ matrix.export_path }}"
|
||||
brew install create-dmg
|
||||
create-dmg \
|
||||
--volname "sing-box" \
|
||||
--volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \
|
||||
--icon "SFM.app" 0 0 \
|
||||
--hide-extension "SFM.app" \
|
||||
--app-drop-link 0 0 \
|
||||
--skip-jenkins \
|
||||
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
||||
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
||||
cd "${{ matrix.archive }}"
|
||||
zip -r SFM.dSYMs.zip dSYMs
|
||||
popd
|
||||
|
||||
mkdir -p dist/release
|
||||
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
|
||||
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
|
||||
- name: Upload image
|
||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-macos-dmg
|
||||
path: 'dist'
|
||||
upload:
|
||||
name: Upload builds
|
||||
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- calculate_version
|
||||
- build
|
||||
- build_android
|
||||
- build_apple
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
install-only: true
|
||||
- name: Cache ghr
|
||||
uses: actions/cache@v4
|
||||
id: cache-ghr
|
||||
with:
|
||||
path: |
|
||||
~/go/bin/ghr
|
||||
key: ghr
|
||||
- name: Setup ghr
|
||||
if: steps.cache-ghr.outputs.cache-hit != 'true'
|
||||
run: |-
|
||||
cd $HOME
|
||||
git clone https://github.com/nekohasekai/ghr ghr
|
||||
cd ghr
|
||||
go install -v .
|
||||
- name: Set tag
|
||||
run: |-
|
||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||
- name: Download builds
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Merge builds
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||
run: |-
|
||||
goreleaser continue --merge --skip publish
|
||||
mkdir -p dist/release
|
||||
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
- name: Upload builds
|
||||
if: ${{ env.PUBLISHED == 'false' }}
|
||||
run: |-
|
||||
export PATH="$PATH:$HOME/go/bin"
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Replace builds
|
||||
if: ${{ env.PUBLISHED != 'false' }}
|
||||
run: |-
|
||||
export PATH="$PATH:$HOME/go/bin"
|
||||
ghr --replace -p 5 "v${VERSION}" dist/release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
219
.github/workflows/debug.yml
vendored
219
.github/workflows/debug.yml
vendored
@@ -1,219 +0,0 @@
|
||||
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,7 +22,6 @@ jobs:
|
||||
mkdir -p $HOME/.gnupg
|
||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
EOF
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
- name: Publish release
|
||||
|
||||
@@ -22,6 +22,16 @@ linters-settings:
|
||||
|
||||
run:
|
||||
go: "1.23"
|
||||
build-tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_ech
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
|
||||
@@ -200,4 +200,6 @@ release:
|
||||
ids:
|
||||
- archive
|
||||
- package
|
||||
skip_upload: true
|
||||
skip_upload: true
|
||||
partial:
|
||||
by: target
|
||||
42
Makefile
42
Makefile
@@ -28,7 +28,7 @@ ci_build:
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
generate_completions:
|
||||
go run -v --tags generate,generate_completions $(MAIN)
|
||||
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
||||
|
||||
install:
|
||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||
@@ -71,7 +71,7 @@ release:
|
||||
dist/*_amd64.pkg.tar.zst \
|
||||
dist/*_arm64.pkg.tar.zst \
|
||||
dist/release
|
||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||
rm -r dist/release
|
||||
|
||||
release_repo:
|
||||
@@ -90,22 +90,20 @@ upload_android:
|
||||
mkdir -p dist/release_android
|
||||
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
|
||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||
rm -rf dist/release_android
|
||||
|
||||
release_android: lib_android update_android_version build_android upload_android
|
||||
|
||||
publish_android:
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
|
||||
|
||||
publish_android_appcenter:
|
||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
||||
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||
|
||||
# TODO: find why and remove `-destination 'generic/platform=iOS'`
|
||||
# TODO: remove xcode clean when fix control widget fixed
|
||||
build_ios:
|
||||
cd ../sing-box-for-apple && \
|
||||
rm -rf build/SFI.xcarchive && \
|
||||
xcodebuild clean -scheme SFI && \
|
||||
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
||||
|
||||
upload_ios_app_store:
|
||||
@@ -147,9 +145,13 @@ build_macos_dmg:
|
||||
--hide-extension "SFM.app" \
|
||||
--app-drop-link 0 0 \
|
||||
--skip-jenkins \
|
||||
--notarize "notarytool-password" \
|
||||
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
||||
|
||||
notarize_macos_dmg:
|
||||
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
|
||||
--keychain-profile "notarytool-password" \
|
||||
--no-s3-acceleration
|
||||
|
||||
upload_macos_dmg:
|
||||
cd dist/SFM && \
|
||||
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||
@@ -164,7 +166,7 @@ upload_macos_dsyms:
|
||||
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||
|
||||
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||
|
||||
build_tvos:
|
||||
cd ../sing-box-for-apple && \
|
||||
@@ -180,10 +182,22 @@ release_tvos: build_tvos upload_tvos_app_store
|
||||
update_apple_version:
|
||||
go run ./cmd/internal/update_apple_version
|
||||
|
||||
update_macos_version:
|
||||
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
|
||||
|
||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||
|
||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||
|
||||
publish_testflight:
|
||||
go run -v ./cmd/internal/app_store_connect publish_testflight
|
||||
|
||||
prepare_app_store:
|
||||
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
||||
|
||||
publish_app_store:
|
||||
go run -v ./cmd/internal/app_store_connect publish_app_store
|
||||
|
||||
test:
|
||||
@go test -v ./... && \
|
||||
cd test && \
|
||||
@@ -199,8 +213,14 @@ test_stdio:
|
||||
lib_android:
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
|
||||
lib_android_debug:
|
||||
go run ./cmd/internal/build_libbox -target android -debug
|
||||
|
||||
lib_apple:
|
||||
go run ./cmd/internal/build_libbox -target apple
|
||||
|
||||
lib_ios:
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
||||
|
||||
lib:
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
|
||||
14
adapter/connections.go
Normal file
14
adapter/connections.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ConnectionManager interface {
|
||||
Lifecycle
|
||||
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
28
adapter/endpoint.go
Normal file
28
adapter/endpoint.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type Endpoint interface {
|
||||
Lifecycle
|
||||
Type() string
|
||||
Tag() string
|
||||
Outbound
|
||||
}
|
||||
|
||||
type EndpointRegistry interface {
|
||||
option.EndpointOptionsRegistry
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
|
||||
}
|
||||
|
||||
type EndpointManager interface {
|
||||
Lifecycle
|
||||
Endpoints() []Endpoint
|
||||
Get(tag string) (Endpoint, bool)
|
||||
Remove(tag string) error
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
|
||||
}
|
||||
43
adapter/endpoint/adapter.go
Normal file
43
adapter/endpoint/adapter.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package endpoint
|
||||
|
||||
import "github.com/sagernet/sing-box/option"
|
||||
|
||||
type Adapter struct {
|
||||
endpointType string
|
||||
endpointTag string
|
||||
network []string
|
||||
dependencies []string
|
||||
}
|
||||
|
||||
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
|
||||
return Adapter{
|
||||
endpointType: endpointType,
|
||||
endpointTag: endpointTag,
|
||||
network: network,
|
||||
dependencies: dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||
var dependencies []string
|
||||
if dialOptions.Detour != "" {
|
||||
dependencies = []string{dialOptions.Detour}
|
||||
}
|
||||
return NewAdapter(endpointType, endpointTag, network, dependencies)
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.endpointType
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.endpointTag
|
||||
}
|
||||
|
||||
func (a *Adapter) Network() []string {
|
||||
return a.network
|
||||
}
|
||||
|
||||
func (a *Adapter) Dependencies() []string {
|
||||
return a.dependencies
|
||||
}
|
||||
147
adapter/endpoint/manager.go
Normal file
147
adapter/endpoint/manager.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package endpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var _ adapter.EndpointManager = (*Manager)(nil)
|
||||
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.EndpointRegistry
|
||||
access sync.Mutex
|
||||
started bool
|
||||
stage adapter.StartStage
|
||||
endpoints []adapter.Endpoint
|
||||
endpointByTag map[string]adapter.Endpoint
|
||||
}
|
||||
|
||||
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpointByTag: make(map[string]adapter.Endpoint),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started && m.stage >= stage {
|
||||
panic("already started")
|
||||
}
|
||||
m.started = true
|
||||
m.stage = stage
|
||||
if stage == adapter.StartStateStart {
|
||||
// started with outbound manager
|
||||
return nil
|
||||
}
|
||||
for _, endpoint := range m.endpoints {
|
||||
err := adapter.LegacyStart(endpoint, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if !m.started {
|
||||
return nil
|
||||
}
|
||||
m.started = false
|
||||
endpoints := m.endpoints
|
||||
m.endpoints = nil
|
||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||
var err error
|
||||
for _, endpoint := range endpoints {
|
||||
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Endpoints() []adapter.Endpoint {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
return m.endpoints
|
||||
}
|
||||
|
||||
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
endpoint, found := m.endpointByTag[tag]
|
||||
return endpoint, found
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
m.access.Lock()
|
||||
endpoint, found := m.endpointByTag[tag]
|
||||
if !found {
|
||||
m.access.Unlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
delete(m.endpointByTag, tag)
|
||||
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||
return it == endpoint
|
||||
})
|
||||
if index == -1 {
|
||||
panic("invalid endpoint index")
|
||||
}
|
||||
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
|
||||
started := m.started
|
||||
m.access.Unlock()
|
||||
if started {
|
||||
return endpoint.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
||||
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started {
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(endpoint, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
||||
if m.started {
|
||||
err = existsEndpoint.Close()
|
||||
if err != nil {
|
||||
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||
return it == existsEndpoint
|
||||
})
|
||||
if existsIndex == -1 {
|
||||
panic("invalid endpoint index")
|
||||
}
|
||||
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
|
||||
}
|
||||
m.endpoints = append(m.endpoints, endpoint)
|
||||
m.endpointByTag[tag] = endpoint
|
||||
return nil
|
||||
}
|
||||
72
adapter/endpoint/registry.go
Normal file
72
adapter/endpoint/registry.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package endpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
|
||||
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
})
|
||||
}
|
||||
|
||||
var _ adapter.EndpointRegistry = (*Registry)(nil)
|
||||
|
||||
type (
|
||||
optionsConstructorFunc func() any
|
||||
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
access sync.Mutex
|
||||
optionsType map[string]optionsConstructorFunc
|
||||
constructor map[string]constructorFunc
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
optionsType: make(map[string]optionsConstructorFunc),
|
||||
constructor: make(map[string]constructorFunc),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
return optionsConstructor(), true
|
||||
}
|
||||
|
||||
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
constructor, loaded := m.constructor[outboundType]
|
||||
if !loaded {
|
||||
return nil, E.New("outbound type not found: " + outboundType)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, options)
|
||||
}
|
||||
|
||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.optionsType[outboundType] = optionsConstructor
|
||||
m.constructor[outboundType] = constructor
|
||||
}
|
||||
@@ -46,6 +46,9 @@ type PacketConnectionHandlerEx interface {
|
||||
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
|
||||
// Deprecated: use TCPConnectionHandlerEx instead
|
||||
//
|
||||
//nolint:staticcheck
|
||||
type UpstreamHandlerAdapter interface {
|
||||
N.TCPConnectionHandler
|
||||
N.UDPConnectionHandler
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type Inbound interface {
|
||||
Service
|
||||
Lifecycle
|
||||
Type() string
|
||||
Tag() string
|
||||
}
|
||||
@@ -61,17 +61,21 @@ type InboundContext struct {
|
||||
// cache
|
||||
|
||||
// Deprecated: implement in rule action
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
// Deprecated
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
RouteOriginalDestination M.Socksaddr
|
||||
// Deprecated: to be removed
|
||||
//nolint:staticcheck
|
||||
InboundOptions option.InboundOptions
|
||||
UDPDisableDomainUnmapping bool
|
||||
UDPConnect bool
|
||||
NetworkStrategy C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
UDPTimeout time.Duration
|
||||
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
|
||||
DNSServer string
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ var _ adapter.InboundManager = (*Manager)(nil)
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.InboundRegistry
|
||||
endpoint adapter.EndpointManager
|
||||
access sync.Mutex
|
||||
started bool
|
||||
stage adapter.StartStage
|
||||
@@ -25,10 +26,11 @@ type Manager struct {
|
||||
inboundByTag map[string]adapter.Inbound
|
||||
}
|
||||
|
||||
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry) *Manager {
|
||||
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpoint: endpoint,
|
||||
inboundByTag: make(map[string]adapter.Inbound),
|
||||
}
|
||||
}
|
||||
@@ -79,9 +81,12 @@ func (m *Manager) Inbounds() []adapter.Inbound {
|
||||
|
||||
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
inbound, found := m.inboundByTag[tag]
|
||||
return inbound, found
|
||||
m.access.Unlock()
|
||||
if found {
|
||||
return inbound, true
|
||||
}
|
||||
return m.endpoint.Get(tag)
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
|
||||
@@ -15,8 +15,12 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) {
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package adapter
|
||||
|
||||
func LegacyStart(starter any, stage StartStage) error {
|
||||
if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {
|
||||
return lifecycle.Start(stage)
|
||||
}
|
||||
switch stage {
|
||||
case StartStateInitialize:
|
||||
if preStarter, isPreStarter := starter.(interface {
|
||||
|
||||
@@ -28,7 +28,7 @@ type NetworkManager interface {
|
||||
}
|
||||
|
||||
type NetworkOptions struct {
|
||||
NetworkStrategy C.NetworkStrategy
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
|
||||
@@ -5,35 +5,35 @@ import (
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
protocol string
|
||||
outboundType string
|
||||
outboundTag string
|
||||
network []string
|
||||
tag string
|
||||
dependencies []string
|
||||
}
|
||||
|
||||
func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter {
|
||||
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
|
||||
return Adapter{
|
||||
protocol: protocol,
|
||||
outboundType: outboundType,
|
||||
outboundTag: outboundTag,
|
||||
network: network,
|
||||
tag: tag,
|
||||
dependencies: dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter {
|
||||
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||
var dependencies []string
|
||||
if dialOptions.Detour != "" {
|
||||
dependencies = []string{dialOptions.Detour}
|
||||
}
|
||||
return NewAdapter(protocol, network, tag, dependencies)
|
||||
return NewAdapter(outboundType, outboundTag, network, dependencies)
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.protocol
|
||||
return a.outboundType
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.tag
|
||||
return a.outboundTag
|
||||
}
|
||||
|
||||
func (a *Adapter) Network() []string {
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/canceler"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
defer conn.Close()
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var outConn net.Conn
|
||||
var err error
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
|
||||
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
|
||||
} else {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
|
||||
}
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||
if err != nil {
|
||||
outConn.Close()
|
||||
return err
|
||||
}
|
||||
return CopyEarlyConn(ctx, conn, outConn)
|
||||
}
|
||||
|
||||
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
defer conn.Close()
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var (
|
||||
outPacketConn net.PacketConn
|
||||
outConn net.Conn
|
||||
destinationAddress netip.Addr
|
||||
err error
|
||||
)
|
||||
if metadata.UDPConnect {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
|
||||
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
|
||||
} else {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
|
||||
}
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
outPacketConn = bufio.NewUnbindPacketConn(outConn)
|
||||
connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr())
|
||||
if connRemoteAddr != metadata.Destination.Addr {
|
||||
destinationAddress = connRemoteAddr
|
||||
}
|
||||
} else {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
|
||||
outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
|
||||
} else {
|
||||
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
|
||||
}
|
||||
} else {
|
||||
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
}
|
||||
err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn)
|
||||
if err != nil {
|
||||
outPacketConn.Close()
|
||||
return err
|
||||
}
|
||||
if destinationAddress.IsValid() {
|
||||
if metadata.Destination.IsFqdn() {
|
||||
if metadata.UDPDisableDomainUnmapping {
|
||||
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
} else {
|
||||
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
}
|
||||
}
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
}
|
||||
}
|
||||
switch metadata.Protocol {
|
||||
case C.ProtocolSTUN:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout)
|
||||
case C.ProtocolQUIC:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout)
|
||||
case C.ProtocolDNS:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
|
||||
}
|
||||
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
|
||||
}
|
||||
|
||||
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
|
||||
if cachedReader, isCached := conn.(N.CachedReader); isCached {
|
||||
payload := cachedReader.ReadCached()
|
||||
if payload != nil && !payload.IsEmpty() {
|
||||
_, err := serverConn.Write(payload.Bytes())
|
||||
payload.Release()
|
||||
if err != nil {
|
||||
serverConn.Close()
|
||||
return err
|
||||
}
|
||||
return bufio.CopyConn(ctx, conn, serverConn)
|
||||
}
|
||||
}
|
||||
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](serverConn); isEarlyConn && earlyConn.NeedHandshake() {
|
||||
payload := buf.NewPacket()
|
||||
err := conn.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
|
||||
if err != os.ErrInvalid {
|
||||
if err != nil {
|
||||
payload.Release()
|
||||
serverConn.Close()
|
||||
return err
|
||||
}
|
||||
_, err = payload.ReadOnceFrom(conn)
|
||||
if err != nil && !E.IsTimeout(err) {
|
||||
payload.Release()
|
||||
serverConn.Close()
|
||||
return E.Cause(err, "read payload")
|
||||
}
|
||||
err = conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
payload.Release()
|
||||
serverConn.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = serverConn.Write(payload.Bytes())
|
||||
payload.Release()
|
||||
if err != nil {
|
||||
serverConn.Close()
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
}
|
||||
return bufio.CopyConn(ctx, conn, serverConn)
|
||||
}
|
||||
@@ -21,6 +21,7 @@ var _ adapter.OutboundManager = (*Manager)(nil)
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.OutboundRegistry
|
||||
endpoint adapter.EndpointManager
|
||||
defaultTag string
|
||||
access sync.Mutex
|
||||
started bool
|
||||
@@ -32,10 +33,11 @@ type Manager struct {
|
||||
defaultOutboundFallback adapter.Outbound
|
||||
}
|
||||
|
||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, defaultTag string) *Manager {
|
||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpoint: endpoint,
|
||||
defaultTag: defaultTag,
|
||||
outboundByTag: make(map[string]adapter.Outbound),
|
||||
dependByTag: make(map[string][]string),
|
||||
@@ -56,7 +58,14 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
outbounds := m.outbounds
|
||||
m.access.Unlock()
|
||||
if stage == adapter.StartStateStart {
|
||||
return m.startOutbounds(outbounds)
|
||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||
if !loaded {
|
||||
return E.New("default outbound not found: ", m.defaultTag)
|
||||
}
|
||||
m.defaultOutbound = defaultEndpoint
|
||||
}
|
||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||
} else {
|
||||
for _, outbound := range outbounds {
|
||||
err := adapter.LegacyStart(outbound, stage)
|
||||
@@ -87,7 +96,14 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
||||
}
|
||||
started[outboundTag] = true
|
||||
canContinue = true
|
||||
if starter, isStarter := outboundToStart.(interface {
|
||||
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
err := starter.Start(adapter.StartStateStart)
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
}
|
||||
} else if starter, isStarter := outboundToStart.(interface {
|
||||
Start() error
|
||||
}); isStarter {
|
||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
@@ -160,9 +176,12 @@ func (m *Manager) Outbounds() []adapter.Outbound {
|
||||
|
||||
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
outbound, found := m.outboundByTag[tag]
|
||||
return outbound, found
|
||||
m.access.Unlock()
|
||||
if found {
|
||||
return outbound, true
|
||||
}
|
||||
return m.endpoint.Get(tag)
|
||||
}
|
||||
|
||||
func (m *Manager) Default() adapter.Outbound {
|
||||
|
||||
@@ -15,8 +15,12 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) {
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ type (
|
||||
)
|
||||
|
||||
// Deprecated
|
||||
//
|
||||
//nolint:staticcheck
|
||||
func NewUpstreamHandler(
|
||||
metadata InboundContext,
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
@@ -34,7 +36,9 @@ func NewUpstreamHandler(
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
//
|
||||
//nolint:staticcheck
|
||||
type myUpstreamHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
@@ -42,6 +46,7 @@ type myUpstreamHandlerWrapper struct {
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -53,6 +58,7 @@ func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.C
|
||||
return w.connectionHandler(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -64,11 +70,12 @@ func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn
|
||||
return w.packetHandler(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// Deprecated: removed
|
||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
return M.Metadata{
|
||||
Source: metadata.Source,
|
||||
@@ -76,14 +83,14 @@ func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
type myUpstreamContextHandlerWrapper struct {
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
packetHandler PacketConnectionHandlerFunc
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func NewUpstreamContextHandler(
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
packetHandler PacketConnectionHandlerFunc,
|
||||
@@ -96,6 +103,7 @@ func NewUpstreamContextHandler(
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -107,6 +115,7 @@ func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, con
|
||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -118,6 +127,7 @@ func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Contex
|
||||
return w.packetHandler(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
}
|
||||
@@ -149,12 +159,15 @@ func NewRouteContextHandler(
|
||||
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
//
|
||||
//nolint:staticcheck
|
||||
type routeHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
router ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -166,6 +179,7 @@ func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn,
|
||||
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -177,6 +191,7 @@ func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.Pa
|
||||
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
@@ -189,6 +204,7 @@ type routeContextHandlerWrapper struct {
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -200,6 +216,7 @@ func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net
|
||||
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -211,6 +228,7 @@ func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, co
|
||||
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
|
||||
100
box.go
100
box.go
@@ -9,10 +9,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||
@@ -36,9 +39,11 @@ type Box struct {
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
network *route.NetworkManager
|
||||
router *route.Router
|
||||
endpoint *endpoint.Manager
|
||||
inbound *inbound.Manager
|
||||
outbound *outbound.Manager
|
||||
connection *route.ConnectionManager
|
||||
router *route.Router
|
||||
services []adapter.LifecycleService
|
||||
done chan struct{}
|
||||
}
|
||||
@@ -53,6 +58,7 @@ func Context(
|
||||
ctx context.Context,
|
||||
inboundRegistry adapter.InboundRegistry,
|
||||
outboundRegistry adapter.OutboundRegistry,
|
||||
endpointRegistry adapter.EndpointRegistry,
|
||||
) context.Context {
|
||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||
@@ -64,6 +70,11 @@ func Context(
|
||||
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||
}
|
||||
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -74,20 +85,26 @@ func New(options Options) (*Box, error) {
|
||||
ctx = context.Background()
|
||||
}
|
||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||
|
||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
|
||||
if endpointRegistry == nil {
|
||||
return nil, E.New("missing endpoint registry in context")
|
||||
}
|
||||
if inboundRegistry == nil {
|
||||
return nil, E.New("missing inbound registry in context")
|
||||
}
|
||||
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
if outboundRegistry == nil {
|
||||
return nil, E.New("missing outbound registry in context")
|
||||
}
|
||||
|
||||
ctx = pause.WithDefaultManager(ctx)
|
||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||
debugOptions := common.PtrValueOrDefault(experimentalOptions.Debug)
|
||||
applyDebugOptions(debugOptions)
|
||||
ctx = conntrack.ContextWithDefaultTracker(ctx, debugOptions.OOMKiller, uint64(debugOptions.MemoryLimit))
|
||||
|
||||
var needCacheFile bool
|
||||
var needClashAPI bool
|
||||
var needV2RayAPI bool
|
||||
@@ -118,8 +135,10 @@ func New(options Options) (*Box, error) {
|
||||
}
|
||||
|
||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry)
|
||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final)
|
||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||
|
||||
@@ -128,28 +147,36 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "initialize network manager")
|
||||
}
|
||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||
router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize router")
|
||||
}
|
||||
//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()),
|
||||
})
|
||||
}
|
||||
|
||||
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||
var timeService *tls.TimeServiceWrapper
|
||||
if ntpOptions.Enabled {
|
||||
timeService = new(tls.TimeServiceWrapper)
|
||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||
}
|
||||
//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()),
|
||||
})
|
||||
|
||||
for i, endpointOptions := range options.Endpoints {
|
||||
var tag string
|
||||
if endpointOptions.Tag != "" {
|
||||
tag = endpointOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = endpointManager.Create(ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
endpointOptions.Type,
|
||||
endpointOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||
}
|
||||
}
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
@@ -239,13 +266,12 @@ func New(options Options) (*Box, error) {
|
||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||
}
|
||||
}
|
||||
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||
if ntpOptions.Enabled {
|
||||
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create NTP service")
|
||||
}
|
||||
timeService := ntp.NewService(ntp.Options{
|
||||
ntpService := ntp.NewService(ntp.Options{
|
||||
Context: ctx,
|
||||
Dialer: ntpDialer,
|
||||
Logger: logFactory.NewLogger("ntp"),
|
||||
@@ -253,14 +279,16 @@ func New(options Options) (*Box, error) {
|
||||
Interval: time.Duration(ntpOptions.Interval),
|
||||
WriteToSystem: ntpOptions.WriteToSystem,
|
||||
})
|
||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
|
||||
timeService.TimeService = ntpService
|
||||
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||
}
|
||||
return &Box{
|
||||
network: networkManager,
|
||||
router: router,
|
||||
endpoint: endpointManager,
|
||||
inbound: inboundManager,
|
||||
outbound: outboundManager,
|
||||
connection: connectionManager,
|
||||
router: router,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
@@ -319,11 +347,11 @@ func (s *Box) preStart() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound)
|
||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router)
|
||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -343,7 +371,11 @@ func (s *Box) start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound)
|
||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -351,7 +383,7 @@ func (s *Box) start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound)
|
||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -370,7 +402,7 @@ func (s *Box) Close() error {
|
||||
close(s.done)
|
||||
}
|
||||
err := common.Close(
|
||||
s.inbound, s.outbound, s.router, s.network,
|
||||
s.inbound, s.outbound, s.router, s.connection, s.network,
|
||||
)
|
||||
for _, lifecycleService := range s.services {
|
||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||
|
||||
Submodule clients/android updated: 45a1f5f0aa...e1049099a0
Submodule clients/apple updated: c7d9b49de7...3d889ae017
445
cmd/internal/app_store_connect/main.go
Normal file
445
cmd/internal/app_store_connect/main.go
Normal file
@@ -0,0 +1,445 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/asc-go/asc"
|
||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
switch os.Args[1] {
|
||||
case "next_macos_project_version":
|
||||
err := fetchMacOSVersion(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case "publish_testflight":
|
||||
err := publishTestflight(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case "cancel_app_store":
|
||||
err := cancelAppStore(ctx, os.Args[2])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case "prepare_app_store":
|
||||
err := prepareAppStore(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case "publish_app_store":
|
||||
err := publishAppStore(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
default:
|
||||
log.Fatal("unknown action: ", os.Args[1])
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
appID = "6673731168"
|
||||
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
|
||||
)
|
||||
|
||||
func createClient(expireDuration time.Duration) *asc.Client {
|
||||
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return asc.NewClient(tokenConfig.Client())
|
||||
}
|
||||
|
||||
func fetchMacOSVersion(ctx context.Context) error {
|
||||
client := createClient(time.Minute)
|
||||
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||
FilterPlatform: []string{"MAC_OS"},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var versionID string
|
||||
findVersion:
|
||||
for _, version := range versions.Data {
|
||||
switch *version.Attributes.AppStoreState {
|
||||
case asc.AppStoreVersionStateReadyForSale,
|
||||
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||
versionID = version.ID
|
||||
break findVersion
|
||||
}
|
||||
}
|
||||
if versionID == "" {
|
||||
return E.New("no version found")
|
||||
}
|
||||
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse version code")
|
||||
}
|
||||
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishTestflight(ctx context.Context) error {
|
||||
tagVersion, err := build_shared.ReadTagVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tag := tagVersion.VersionString()
|
||||
client := createClient(10 * time.Minute)
|
||||
|
||||
log.Info(tag, " list build IDs")
|
||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
||||
return it.ID
|
||||
})
|
||||
var platforms []asc.Platform
|
||||
if len(os.Args) == 3 {
|
||||
switch os.Args[2] {
|
||||
case "ios":
|
||||
platforms = []asc.Platform{asc.PlatformIOS}
|
||||
case "macos":
|
||||
platforms = []asc.Platform{asc.PlatformMACOS}
|
||||
case "tvos":
|
||||
platforms = []asc.Platform{asc.PlatformTVOS}
|
||||
default:
|
||||
return E.New("unknown platform: ", os.Args[2])
|
||||
}
|
||||
} else {
|
||||
platforms = []asc.Platform{
|
||||
asc.PlatformIOS,
|
||||
asc.PlatformMACOS,
|
||||
asc.PlatformTVOS,
|
||||
}
|
||||
}
|
||||
for _, platform := range platforms {
|
||||
log.Info(string(platform), " list builds")
|
||||
for {
|
||||
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||
FilterApp: []string{appID},
|
||||
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
build := builds.Data[0]
|
||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||
log.Info(string(platform), " ", tag, " waiting for process")
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
}
|
||||
if *build.Attributes.ProcessingState != "VALID" {
|
||||
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
}
|
||||
log.Info(string(platform), " ", tag, " list localizations")
|
||||
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
|
||||
return *it.Attributes.Locale == "en-US"
|
||||
})
|
||||
if localization.ID == "" {
|
||||
log.Fatal(string(platform), " ", tag, " no en-US localization found")
|
||||
}
|
||||
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||
log.Info(string(platform), " ", tag, " update localization")
|
||||
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
|
||||
F.ToString("sing-box ", tagVersion.String()),
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Info(string(platform), " ", tag, " publish")
|
||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
||||
log.Info("waiting for process")
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(string(platform), " ", tag, " list submissions")
|
||||
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
||||
FilterBuild: []string{build.ID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(betaSubmissions.Data) == 0 {
|
||||
log.Info(string(platform), " ", tag, " create submission")
|
||||
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cancelAppStore(ctx context.Context, platform string) error {
|
||||
switch platform {
|
||||
case "ios":
|
||||
platform = string(asc.PlatformIOS)
|
||||
case "macos":
|
||||
platform = string(asc.PlatformMACOS)
|
||||
case "tvos":
|
||||
platform = string(asc.PlatformTVOS)
|
||||
}
|
||||
tag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := createClient(time.Minute)
|
||||
for {
|
||||
log.Info(platform, " list versions")
|
||||
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||
FilterPlatform: []string{string(platform)},
|
||||
})
|
||||
if isRetryable(response) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||
return *it.Attributes.VersionString == tag
|
||||
})
|
||||
if version.ID == "" {
|
||||
return nil
|
||||
}
|
||||
log.Info(platform, " ", tag, " get submission")
|
||||
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
||||
if response != nil && response.StatusCode == http.StatusNotFound {
|
||||
return nil
|
||||
}
|
||||
if isRetryable(response) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(platform, " ", tag, " delete submission")
|
||||
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareAppStore(ctx context.Context) error {
|
||||
tag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := createClient(time.Minute)
|
||||
for _, platform := range []asc.Platform{
|
||||
asc.PlatformIOS,
|
||||
asc.PlatformMACOS,
|
||||
asc.PlatformTVOS,
|
||||
} {
|
||||
log.Info(string(platform), " list versions")
|
||||
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||
FilterPlatform: []string{string(platform)},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||
return *it.Attributes.VersionString == tag
|
||||
})
|
||||
log.Info(string(platform), " ", tag, " list builds")
|
||||
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||
FilterApp: []string{appID},
|
||||
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(builds.Data) == 0 {
|
||||
log.Fatal(platform, " ", tag, " no build found")
|
||||
}
|
||||
buildID := common.Ptr(builds.Data[0].ID)
|
||||
if version.ID == "" {
|
||||
log.Info(string(platform), " ", tag, " create version")
|
||||
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
|
||||
Platform: platform,
|
||||
VersionString: tag,
|
||||
}, appID, buildID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version = newVersion.Data
|
||||
|
||||
} else {
|
||||
log.Info(string(platform), " ", tag, " check build")
|
||||
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
|
||||
switch *version.Attributes.AppStoreState {
|
||||
case asc.AppStoreVersionStatePrepareForSubmission,
|
||||
asc.AppStoreVersionStateRejected,
|
||||
asc.AppStoreVersionStateDeveloperRejected:
|
||||
case asc.AppStoreVersionStateWaitingForReview,
|
||||
asc.AppStoreVersionStateInReview,
|
||||
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if submission != nil {
|
||||
log.Info(string(platform), " ", tag, " delete submission")
|
||||
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
default:
|
||||
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||
}
|
||||
log.Info(string(platform), " ", tag, " update build")
|
||||
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response.StatusCode != http.StatusNoContent {
|
||||
response.Write(os.Stderr)
|
||||
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
|
||||
}
|
||||
} else {
|
||||
switch *version.Attributes.AppStoreState {
|
||||
case asc.AppStoreVersionStatePrepareForSubmission,
|
||||
asc.AppStoreVersionStateRejected,
|
||||
asc.AppStoreVersionStateDeveloperRejected:
|
||||
case asc.AppStoreVersionStateWaitingForReview,
|
||||
asc.AppStoreVersionStateInReview,
|
||||
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||
continue
|
||||
default:
|
||||
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Info(string(platform), " ", tag, " list localization")
|
||||
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
|
||||
return *it.Attributes.Locale == "en-US"
|
||||
})
|
||||
if localization.ID == "" {
|
||||
log.Info(string(platform), " ", tag, " no en-US localization found")
|
||||
}
|
||||
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||
log.Info(string(platform), " ", tag, " update localization")
|
||||
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
|
||||
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
|
||||
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Info(string(platform), " ", tag, " create submission")
|
||||
fixSubmit:
|
||||
for {
|
||||
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
|
||||
if err != nil {
|
||||
switch response.StatusCode {
|
||||
case http.StatusInternalServerError:
|
||||
continue
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch response.StatusCode {
|
||||
case http.StatusCreated:
|
||||
break fixSubmit
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishAppStore(ctx context.Context) error {
|
||||
tag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := createClient(time.Minute)
|
||||
for _, platform := range []asc.Platform{
|
||||
asc.PlatformIOS,
|
||||
asc.PlatformMACOS,
|
||||
asc.PlatformTVOS,
|
||||
} {
|
||||
log.Info(string(platform), " list versions")
|
||||
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||
FilterPlatform: []string{string(platform)},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||
return *it.Attributes.VersionString == tag
|
||||
})
|
||||
switch *version.Attributes.AppStoreState {
|
||||
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
|
||||
log.Fatal(string(platform), " ", tag, " not submitted")
|
||||
case asc.AppStoreVersionStateWaitingForReview,
|
||||
asc.AppStoreVersionStateInReview:
|
||||
log.Warn(string(platform), " ", tag, " waiting for review")
|
||||
continue
|
||||
case asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||
default:
|
||||
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||
}
|
||||
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isRetryable(response *asc.Response) bool {
|
||||
if response == nil {
|
||||
return false
|
||||
}
|
||||
switch response.StatusCode {
|
||||
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -10,17 +10,21 @@ import (
|
||||
_ "github.com/sagernet/gomobile"
|
||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
)
|
||||
|
||||
var (
|
||||
debugEnabled bool
|
||||
target string
|
||||
platform string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||
flag.StringVar(&target, "target", "android", "target platform")
|
||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -31,8 +35,8 @@ func main() {
|
||||
switch target {
|
||||
case "android":
|
||||
buildAndroid()
|
||||
case "ios":
|
||||
buildiOS()
|
||||
case "apple":
|
||||
buildApple()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +66,35 @@ func init() {
|
||||
func buildAndroid() {
|
||||
build_shared.FindSDK()
|
||||
|
||||
var javaPath string
|
||||
javaHome := os.Getenv("JAVA_HOME")
|
||||
if javaHome == "" {
|
||||
javaPath = "java"
|
||||
} else {
|
||||
javaPath = filepath.Join(javaHome, "bin", "java")
|
||||
}
|
||||
|
||||
javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput()
|
||||
if err != nil {
|
||||
log.Fatal(E.Cause(err, "check java version"))
|
||||
}
|
||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||
log.Fatal("java version should be openjdk 17")
|
||||
}
|
||||
|
||||
var bindTarget string
|
||||
if platform != "" {
|
||||
bindTarget = platform
|
||||
} else if debugEnabled {
|
||||
bindTarget = "android/arm64"
|
||||
} else {
|
||||
bindTarget = "android"
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"bind",
|
||||
"-v",
|
||||
"-target", bindTarget,
|
||||
"-androidapi", "21",
|
||||
"-javapkg=io.nekohasekai",
|
||||
"-libname=box",
|
||||
@@ -86,7 +116,7 @@ func buildAndroid() {
|
||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
err := command.Run()
|
||||
err = command.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -103,11 +133,20 @@ func buildAndroid() {
|
||||
}
|
||||
}
|
||||
|
||||
func buildiOS() {
|
||||
func buildApple() {
|
||||
var bindTarget string
|
||||
if platform != "" {
|
||||
bindTarget = platform
|
||||
} else if debugEnabled {
|
||||
bindTarget = "ios"
|
||||
} else {
|
||||
bindTarget = "ios,tvos,macos"
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"bind",
|
||||
"-v",
|
||||
"-target", "ios,iossimulator,tvos,tvossimulator,macos",
|
||||
"-target", bindTarget,
|
||||
"-libname=box",
|
||||
}
|
||||
if !debugEnabled {
|
||||
|
||||
@@ -11,9 +11,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -42,14 +40,6 @@ func FindSDK() {
|
||||
log.Fatal("android NDK not found")
|
||||
}
|
||||
|
||||
javaVersion, err := shell.Exec("java", "--version").ReadOutput()
|
||||
if err != nil {
|
||||
log.Fatal(E.Cause(err, "check java version"))
|
||||
}
|
||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||
log.Fatal("java version should be openjdk 17")
|
||||
}
|
||||
|
||||
os.Setenv("ANDROID_HOME", androidSDKPath)
|
||||
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
||||
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
||||
@@ -58,12 +48,16 @@ func FindSDK() {
|
||||
}
|
||||
|
||||
func findNDK() bool {
|
||||
const fixedVersion = "26.2.11394342"
|
||||
const fixedVersion = "28.0.12674087"
|
||||
const versionFile = "source.properties"
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||
androidNDKPath = fixedPath
|
||||
return true
|
||||
}
|
||||
if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) {
|
||||
androidNDKPath = ndkHomeEnv
|
||||
return true
|
||||
}
|
||||
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
|
||||
if err != nil {
|
||||
return false
|
||||
|
||||
@@ -20,6 +20,11 @@ func ReadTag() (string, error) {
|
||||
return version.String() + "-" + shortCommit, nil
|
||||
}
|
||||
|
||||
func ReadTagVersionRev() (badversion.Version, error) {
|
||||
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||
return badversion.Parse(currentTagRev[1:]), nil
|
||||
}
|
||||
|
||||
func ReadTagVersion() (badversion.Version, error) {
|
||||
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
|
||||
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||
|
||||
@@ -1,21 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
var nightly bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
||||
}
|
||||
|
||||
func main() {
|
||||
currentTag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
_, err = os.Stdout.WriteString("unknown\n")
|
||||
flag.Parse()
|
||||
if nightly {
|
||||
version, err := build_shared.ReadTagVersionRev()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var versionStr string
|
||||
if version.PreReleaseIdentifier != "" {
|
||||
versionStr = version.VersionString() + "-nightly"
|
||||
} else {
|
||||
version.Patch++
|
||||
versionStr = version.VersionString() + "-nightly"
|
||||
}
|
||||
err = setGitHubEnv("version", versionStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
_, err = os.Stdout.WriteString(currentTag + "\n")
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
tag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
os.Stdout.WriteString("unknown\n")
|
||||
} 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,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -12,9 +13,22 @@ import (
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
var flagRunInCI bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||
}
|
||||
|
||||
func main() {
|
||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
||||
androidPath, err := filepath.Abs("../sing-box-for-android")
|
||||
flag.Parse()
|
||||
newVersion := common.Must1(build_shared.ReadTag())
|
||||
var androidPath string
|
||||
if flagRunInCI {
|
||||
androidPath = "clients/android"
|
||||
} else {
|
||||
androidPath = "../sing-box-for-android"
|
||||
}
|
||||
androidPath, err := filepath.Abs(androidPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -31,10 +45,10 @@ func main() {
|
||||
for _, propPair := range propsList {
|
||||
switch propPair[0] {
|
||||
case "VERSION_NAME":
|
||||
if propPair[1] != newVersion.String() {
|
||||
if propPair[1] != newVersion {
|
||||
versionUpdated = true
|
||||
propPair[1] = newVersion.String()
|
||||
log.Info("updated version to ", newVersion.String())
|
||||
propPair[1] = newVersion
|
||||
log.Info("updated version to ", newVersion)
|
||||
}
|
||||
case "GO_VERSION":
|
||||
if propPair[1] != runtime.Version() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -13,9 +14,22 @@ import (
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
var flagRunInCI bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
||||
applePath, err := filepath.Abs("../sing-box-for-apple")
|
||||
var applePath string
|
||||
if flagRunInCI {
|
||||
applePath = "clients/apple"
|
||||
} else {
|
||||
applePath = "../sing-box-for-apple"
|
||||
}
|
||||
applePath, err := filepath.Abs(applePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
||||
configPaths = append(configPaths, "config.json")
|
||||
}
|
||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
|
||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
var commandMerge = &cobra.Command{
|
||||
Use: "merge <output>",
|
||||
Use: "merge <output-path>",
|
||||
Short: "Merge configurations",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := merge(args[0])
|
||||
|
||||
162
cmd/sing-box/cmd_rule_set_merge.go
Normal file
162
cmd/sing-box/cmd_rule_set_merge.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
ruleSetPaths []string
|
||||
ruleSetDirectories []string
|
||||
)
|
||||
|
||||
var commandRuleSetMerge = &cobra.Command{
|
||||
Use: "merge <output-path>",
|
||||
Short: "Merge rule-set source files",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := mergeRuleSet(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path")
|
||||
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path")
|
||||
commandRuleSet.AddCommand(commandRuleSetMerge)
|
||||
}
|
||||
|
||||
type RuleSetEntry struct {
|
||||
content []byte
|
||||
path string
|
||||
options option.PlainRuleSetCompat
|
||||
}
|
||||
|
||||
func readRuleSetAt(path string) (*RuleSetEntry, error) {
|
||||
var (
|
||||
configContent []byte
|
||||
err error
|
||||
)
|
||||
if path == "stdin" {
|
||||
configContent, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
configContent, err = os.ReadFile(path)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read config at ", path)
|
||||
}
|
||||
options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode config at ", path)
|
||||
}
|
||||
return &RuleSetEntry{
|
||||
content: configContent,
|
||||
path: path,
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readRuleSet() ([]*RuleSetEntry, error) {
|
||||
var optionsList []*RuleSetEntry
|
||||
for _, path := range ruleSetPaths {
|
||||
optionsEntry, err := readRuleSetAt(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optionsList = append(optionsList, optionsEntry)
|
||||
}
|
||||
for _, directory := range ruleSetDirectories {
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read rule-set directory at ", directory)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optionsList = append(optionsList, optionsEntry)
|
||||
}
|
||||
}
|
||||
sort.Slice(optionsList, func(i, j int) bool {
|
||||
return optionsList[i].path < optionsList[j].path
|
||||
})
|
||||
return optionsList, nil
|
||||
}
|
||||
|
||||
func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {
|
||||
optionsList, err := readRuleSet()
|
||||
if err != nil {
|
||||
return option.PlainRuleSetCompat{}, err
|
||||
}
|
||||
if len(optionsList) == 1 {
|
||||
return optionsList[0].options, nil
|
||||
}
|
||||
var optionVersion uint8
|
||||
for _, options := range optionsList {
|
||||
if optionVersion < options.options.Version {
|
||||
optionVersion = options.options.Version
|
||||
}
|
||||
}
|
||||
var mergedMessage json.RawMessage
|
||||
for _, options := range optionsList {
|
||||
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
||||
if err != nil {
|
||||
return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path)
|
||||
}
|
||||
}
|
||||
mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)
|
||||
if err != nil {
|
||||
return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config")
|
||||
}
|
||||
mergedOptions.Version = optionVersion
|
||||
return mergedOptions, nil
|
||||
}
|
||||
|
||||
func mergeRuleSet(outputPath string) error {
|
||||
mergedOptions, err := readRuleSetAndMerge()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(mergedOptions)
|
||||
if err != nil {
|
||||
return E.Cause(err, "encode config")
|
||||
}
|
||||
if existsContent, err := os.ReadFile(outputPath); err != nil {
|
||||
if string(existsContent) == buffer.String() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err = rw.MkdirParent(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputPath, _ = filepath.Abs(outputPath)
|
||||
os.Stderr.WriteString(outputPath + "\n")
|
||||
return nil
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func createPreStartedClient() (*box.Box, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
instance, err := box.New(box.Options{Options: options})
|
||||
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create service")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func initializeHTTP3Client(instance *box.Box) error {
|
||||
return err
|
||||
}
|
||||
http3Client = &http.Client{
|
||||
Transport: &http3.RoundTripper{
|
||||
Transport: &http3.Transport{
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
destination := M.ParseSocksaddr(addr)
|
||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn) (net.Conn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
if KillerEnabled {
|
||||
err := KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
element: element,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
if c.element.Value != nil {
|
||||
connAccess.Lock()
|
||||
if c.element.Value != nil {
|
||||
openConnection.Remove(c.element)
|
||||
c.element.Value = nil
|
||||
}
|
||||
connAccess.Unlock()
|
||||
}
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func (c *Conn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
14
common/conntrack/context.go
Normal file
14
common/conntrack/context.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func ContextWithDefaultTracker(ctx context.Context, killerEnabled bool, memoryLimit uint64) context.Context {
|
||||
if service.FromContext[Tracker](ctx) != nil {
|
||||
return ctx
|
||||
}
|
||||
return service.ContextWith[Tracker](ctx, NewDefaultTracker(killerEnabled, memoryLimit))
|
||||
}
|
||||
245
common/conntrack/default.go
Normal file
245
common/conntrack/default.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
runtimeDebug "runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
var _ Tracker = (*DefaultTracker)(nil)
|
||||
|
||||
type DefaultTracker struct {
|
||||
connAccess sync.RWMutex
|
||||
connList list.List[net.Conn]
|
||||
connAddress map[netip.AddrPort]netip.AddrPort
|
||||
|
||||
packetConnAccess sync.RWMutex
|
||||
packetConnList list.List[AbstractPacketConn]
|
||||
packetConnAddress map[netip.AddrPort]bool
|
||||
|
||||
pendingAccess sync.RWMutex
|
||||
pendingList list.List[netip.AddrPort]
|
||||
|
||||
killerEnabled bool
|
||||
memoryLimit uint64
|
||||
killerLastCheck time.Time
|
||||
}
|
||||
|
||||
func NewDefaultTracker(killerEnabled bool, memoryLimit uint64) *DefaultTracker {
|
||||
return &DefaultTracker{
|
||||
connAddress: make(map[netip.AddrPort]netip.AddrPort),
|
||||
packetConnAddress: make(map[netip.AddrPort]bool),
|
||||
killerEnabled: killerEnabled,
|
||||
memoryLimit: memoryLimit,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) NewConn(conn net.Conn) (net.Conn, error) {
|
||||
err := t.KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
t.connAccess.Lock()
|
||||
element := t.connList.PushBack(conn)
|
||||
t.connAddress[M.AddrPortFromNet(conn.LocalAddr())] = M.AddrPortFromNet(conn.RemoteAddr())
|
||||
t.connAccess.Unlock()
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
closeFunc: common.OnceFunc(func() {
|
||||
t.removeConn(element)
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) NewConnEx(conn net.Conn) (N.CloseHandlerFunc, error) {
|
||||
err := t.KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
t.connAccess.Lock()
|
||||
element := t.connList.PushBack(conn)
|
||||
t.connAddress[M.AddrPortFromNet(conn.LocalAddr())] = M.AddrPortFromNet(conn.RemoteAddr())
|
||||
t.connAccess.Unlock()
|
||||
return N.OnceClose(func(it error) {
|
||||
t.removeConn(element)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
||||
err := t.KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
t.packetConnAccess.Lock()
|
||||
element := t.packetConnList.PushBack(conn)
|
||||
t.packetConnAddress[M.AddrPortFromNet(conn.LocalAddr())] = true
|
||||
t.packetConnAccess.Unlock()
|
||||
return &PacketConn{
|
||||
PacketConn: conn,
|
||||
closeFunc: common.OnceFunc(func() {
|
||||
t.removePacketConn(element)
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) NewPacketConnEx(conn AbstractPacketConn) (N.CloseHandlerFunc, error) {
|
||||
err := t.KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
t.packetConnAccess.Lock()
|
||||
element := t.packetConnList.PushBack(conn)
|
||||
t.packetConnAddress[M.AddrPortFromNet(conn.LocalAddr())] = true
|
||||
t.packetConnAccess.Unlock()
|
||||
return N.OnceClose(func(it error) {
|
||||
t.removePacketConn(element)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) CheckConn(source netip.AddrPort, destination netip.AddrPort) bool {
|
||||
t.connAccess.RLock()
|
||||
defer t.connAccess.RUnlock()
|
||||
return t.connAddress[source] == destination
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) CheckPacketConn(source netip.AddrPort) bool {
|
||||
t.packetConnAccess.RLock()
|
||||
defer t.packetConnAccess.RUnlock()
|
||||
return t.packetConnAddress[source]
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) AddPendingDestination(destination netip.AddrPort) func() {
|
||||
t.pendingAccess.Lock()
|
||||
defer t.pendingAccess.Unlock()
|
||||
element := t.pendingList.PushBack(destination)
|
||||
return func() {
|
||||
t.pendingAccess.Lock()
|
||||
defer t.pendingAccess.Unlock()
|
||||
t.pendingList.Remove(element)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) CheckDestination(destination netip.AddrPort) bool {
|
||||
t.pendingAccess.RLock()
|
||||
defer t.pendingAccess.RUnlock()
|
||||
for element := t.pendingList.Front(); element != nil; element = element.Next() {
|
||||
if element.Value == destination {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) KillerCheck() error {
|
||||
if !t.killerEnabled {
|
||||
return nil
|
||||
}
|
||||
nowTime := time.Now()
|
||||
if nowTime.Sub(t.killerLastCheck) < 3*time.Second {
|
||||
return nil
|
||||
}
|
||||
t.killerLastCheck = nowTime
|
||||
if memory.Total() > t.memoryLimit {
|
||||
t.Close()
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
runtimeDebug.FreeOSMemory()
|
||||
}()
|
||||
return E.New("out of memory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) Count() int {
|
||||
t.connAccess.RLock()
|
||||
defer t.connAccess.RUnlock()
|
||||
t.packetConnAccess.RLock()
|
||||
defer t.packetConnAccess.RUnlock()
|
||||
return t.connList.Len() + t.packetConnList.Len()
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) Close() {
|
||||
t.connAccess.Lock()
|
||||
for element := t.connList.Front(); element != nil; element = element.Next() {
|
||||
element.Value.Close()
|
||||
}
|
||||
t.connList.Init()
|
||||
t.connAccess.Unlock()
|
||||
t.packetConnAccess.Lock()
|
||||
for element := t.packetConnList.Front(); element != nil; element = element.Next() {
|
||||
element.Value.Close()
|
||||
}
|
||||
t.packetConnList.Init()
|
||||
t.packetConnAccess.Unlock()
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) removeConn(element *list.Element[net.Conn]) {
|
||||
t.connAccess.Lock()
|
||||
defer t.connAccess.Unlock()
|
||||
delete(t.connAddress, M.AddrPortFromNet(element.Value.LocalAddr()))
|
||||
t.connList.Remove(element)
|
||||
}
|
||||
|
||||
func (t *DefaultTracker) removePacketConn(element *list.Element[AbstractPacketConn]) {
|
||||
t.packetConnAccess.Lock()
|
||||
defer t.packetConnAccess.Unlock()
|
||||
delete(t.packetConnAddress, M.AddrPortFromNet(element.Value.LocalAddr()))
|
||||
t.packetConnList.Remove(element)
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
closeFunc func()
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
c.closeFunc()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func (c *Conn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
closeFunc func()
|
||||
}
|
||||
|
||||
func (c *PacketConn) Close() error {
|
||||
c.closeFunc()
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return c.PacketConn
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
runtimeDebug "runtime/debug"
|
||||
"time"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
var (
|
||||
KillerEnabled bool
|
||||
MemoryLimit uint64
|
||||
killerLastCheck time.Time
|
||||
)
|
||||
|
||||
func KillerCheck() error {
|
||||
if !KillerEnabled {
|
||||
return nil
|
||||
}
|
||||
nowTime := time.Now()
|
||||
if nowTime.Sub(killerLastCheck) < 3*time.Second {
|
||||
return nil
|
||||
}
|
||||
killerLastCheck = nowTime
|
||||
if memory.Total() > MemoryLimit {
|
||||
Close()
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
runtimeDebug.FreeOSMemory()
|
||||
}()
|
||||
return E.New("out of memory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
if KillerEnabled {
|
||||
err := KillerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &PacketConn{
|
||||
PacketConn: conn,
|
||||
element: element,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *PacketConn) Close() error {
|
||||
if c.element.Value != nil {
|
||||
connAccess.Lock()
|
||||
if c.element.Value != nil {
|
||||
openConnection.Remove(c.element)
|
||||
c.element.Value = nil
|
||||
}
|
||||
connAccess.Unlock()
|
||||
}
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return bufio.NewPacketConn(c.PacketConn)
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
var (
|
||||
connAccess sync.RWMutex
|
||||
openConnection list.List[io.Closer]
|
||||
)
|
||||
|
||||
func Count() int {
|
||||
if !Enabled {
|
||||
return 0
|
||||
}
|
||||
return openConnection.Len()
|
||||
}
|
||||
|
||||
func List() []io.Closer {
|
||||
if !Enabled {
|
||||
return nil
|
||||
}
|
||||
connAccess.RLock()
|
||||
defer connAccess.RUnlock()
|
||||
connList := make([]io.Closer, 0, openConnection.Len())
|
||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||
connList = append(connList, element.Value)
|
||||
}
|
||||
return connList
|
||||
}
|
||||
|
||||
func Close() {
|
||||
if !Enabled {
|
||||
return
|
||||
}
|
||||
connAccess.Lock()
|
||||
defer connAccess.Unlock()
|
||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||
common.Close(element.Value)
|
||||
element.Value = nil
|
||||
}
|
||||
openConnection.Init()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
//go:build !with_conntrack
|
||||
|
||||
package conntrack
|
||||
|
||||
const Enabled = false
|
||||
@@ -1,5 +0,0 @@
|
||||
//go:build with_conntrack
|
||||
|
||||
package conntrack
|
||||
|
||||
const Enabled = true
|
||||
32
common/conntrack/tracker.go
Normal file
32
common/conntrack/tracker.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
// TODO: add to N
|
||||
type AbstractPacketConn interface {
|
||||
Close() error
|
||||
LocalAddr() net.Addr
|
||||
SetDeadline(t time.Time) error
|
||||
SetReadDeadline(t time.Time) error
|
||||
SetWriteDeadline(t time.Time) error
|
||||
}
|
||||
|
||||
type Tracker interface {
|
||||
NewConn(conn net.Conn) (net.Conn, error)
|
||||
NewPacketConn(conn net.PacketConn) (net.PacketConn, error)
|
||||
NewConnEx(conn net.Conn) (N.CloseHandlerFunc, error)
|
||||
NewPacketConnEx(conn AbstractPacketConn) (N.CloseHandlerFunc, error)
|
||||
CheckConn(source netip.AddrPort, destination netip.AddrPort) bool
|
||||
CheckPacketConn(source netip.AddrPort) bool
|
||||
AddPendingDestination(destination netip.AddrPort) func()
|
||||
CheckDestination(destination netip.AddrPort) bool
|
||||
KillerCheck() error
|
||||
Count() int
|
||||
Close()
|
||||
}
|
||||
@@ -2,13 +2,16 @@ package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
@@ -16,6 +19,7 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -24,31 +28,38 @@ var (
|
||||
)
|
||||
|
||||
type DefaultDialer struct {
|
||||
dialer4 tcpDialer
|
||||
dialer6 tcpDialer
|
||||
udpDialer4 net.Dialer
|
||||
udpDialer6 net.Dialer
|
||||
udpListener net.ListenConfig
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
isWireGuardListener bool
|
||||
networkManager adapter.NetworkManager
|
||||
networkStrategy C.NetworkStrategy
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
networkLastFallback atomic.TypedValue[time.Time]
|
||||
tracker conntrack.Tracker
|
||||
dialer4 tcpDialer
|
||||
dialer6 tcpDialer
|
||||
udpDialer4 net.Dialer
|
||||
udpDialer6 net.Dialer
|
||||
udpListener net.ListenConfig
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
isWireGuardListener bool
|
||||
networkManager adapter.NetworkManager
|
||||
networkStrategy *C.NetworkStrategy
|
||||
defaultNetworkStrategy bool
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
networkLastFallback atomic.TypedValue[time.Time]
|
||||
}
|
||||
|
||||
func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) {
|
||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||
tracker := service.FromContext[conntrack.Tracker](ctx)
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
|
||||
var (
|
||||
dialer net.Dialer
|
||||
listener net.ListenConfig
|
||||
interfaceFinder control.InterfaceFinder
|
||||
networkStrategy C.NetworkStrategy
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
dialer net.Dialer
|
||||
listener net.ListenConfig
|
||||
interfaceFinder control.InterfaceFinder
|
||||
networkStrategy *C.NetworkStrategy
|
||||
defaultNetworkStrategy bool
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
)
|
||||
if networkManager != nil {
|
||||
interfaceFinder = networkManager.InterfaceFinder()
|
||||
@@ -74,39 +85,52 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
}
|
||||
}
|
||||
if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault {
|
||||
if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil {
|
||||
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`")
|
||||
}
|
||||
networkStrategy = C.NetworkStrategy(options.NetworkStrategy)
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
networkFallbackDelay = time.Duration(options.NetworkFallbackDelay)
|
||||
if networkManager == nil || !networkManager.AutoDetectInterface() {
|
||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
||||
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
||||
if disableDefaultBind || options.TCPFastOpen {
|
||||
if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 {
|
||||
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`")
|
||||
}
|
||||
}
|
||||
if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil {
|
||||
|
||||
if networkManager != nil {
|
||||
defaultOptions := networkManager.DefaultOptions()
|
||||
if defaultOptions.BindInterface != "" {
|
||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault {
|
||||
networkStrategy = defaultOptions.NetworkStrategy
|
||||
networkType = defaultOptions.NetworkType
|
||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else {
|
||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||
if !disableDefaultBind {
|
||||
if defaultOptions.BindInterface != "" {
|
||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
if platformInterface != nil {
|
||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||
networkStrategy = defaultOptions.NetworkStrategy
|
||||
networkType = defaultOptions.NetworkType
|
||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||
}
|
||||
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
}
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else {
|
||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||
}
|
||||
}
|
||||
if options.ReuseAddr {
|
||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||
@@ -166,9 +190,6 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
||||
listener.Control = control.Append(listener.Control, controlFn)
|
||||
}
|
||||
}
|
||||
if networkStrategy != C.NetworkStrategyDefault && options.TCPFastOpen {
|
||||
return nil, E.New("`tcp_fast_open` is conflict with `network_strategy` or `route.default_network_strategy`")
|
||||
}
|
||||
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -178,19 +199,21 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
||||
return nil, err
|
||||
}
|
||||
return &DefaultDialer{
|
||||
dialer4: tcpDialer4,
|
||||
dialer6: tcpDialer6,
|
||||
udpDialer4: udpDialer4,
|
||||
udpDialer6: udpDialer6,
|
||||
udpListener: listener,
|
||||
udpAddr4: udpAddr4,
|
||||
udpAddr6: udpAddr6,
|
||||
isWireGuardListener: options.IsWireGuardListener,
|
||||
networkManager: networkManager,
|
||||
networkStrategy: networkStrategy,
|
||||
networkType: networkType,
|
||||
fallbackNetworkType: fallbackNetworkType,
|
||||
networkFallbackDelay: networkFallbackDelay,
|
||||
tracker: tracker,
|
||||
dialer4: tcpDialer4,
|
||||
dialer6: tcpDialer6,
|
||||
udpDialer4: udpDialer4,
|
||||
udpDialer6: udpDialer6,
|
||||
udpListener: listener,
|
||||
udpAddr4: udpAddr4,
|
||||
udpAddr6: udpAddr6,
|
||||
isWireGuardListener: options.IsWireGuardListener,
|
||||
networkManager: networkManager,
|
||||
networkStrategy: networkStrategy,
|
||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||
networkType: networkType,
|
||||
fallbackNetworkType: fallbackNetworkType,
|
||||
networkFallbackDelay: networkFallbackDelay,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -198,31 +221,48 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
if !address.IsValid() {
|
||||
return nil, E.New("invalid address")
|
||||
}
|
||||
if d.networkStrategy == C.NetworkStrategyDefault {
|
||||
if d.networkStrategy == nil {
|
||||
if address.IsFqdn() {
|
||||
return nil, E.New("unexpected domain destination")
|
||||
}
|
||||
// Since pending check is only used by ndis, it is not performed for non-windows connections which are only supported on platform clients
|
||||
if d.tracker != nil {
|
||||
done := d.tracker.AddPendingDestination(address.AddrPort())
|
||||
defer done()
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
if !address.IsIPv6() {
|
||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||
return d.trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||
} else {
|
||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||
return d.trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||
}
|
||||
}
|
||||
if !address.IsIPv6() {
|
||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||
return d.trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||
} else {
|
||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||
return d.trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||
}
|
||||
} else {
|
||||
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
if strategy == C.NetworkStrategyDefault {
|
||||
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
if strategy == nil {
|
||||
strategy = d.networkStrategy
|
||||
}
|
||||
if strategy == nil {
|
||||
return d.DialContext(ctx, network, address)
|
||||
}
|
||||
if !d.networkManager.AutoDetectInterface() {
|
||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
||||
if len(interfaceType) == 0 {
|
||||
interfaceType = d.networkType
|
||||
}
|
||||
if len(fallbackInterfaceType) == 0 {
|
||||
fallbackInterfaceType = d.fallbackNetworkType
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = d.networkFallbackDelay
|
||||
}
|
||||
var dialer net.Dialer
|
||||
if N.NetworkName(network) == N.NetworkTCP {
|
||||
@@ -237,61 +277,86 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
||||
err error
|
||||
)
|
||||
if !fastFallback {
|
||||
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
} else {
|
||||
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
|
||||
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// bind interface failed on legacy xiaomi systems
|
||||
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
|
||||
d.networkStrategy = nil
|
||||
return d.DialContext(ctx, network, address)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !fastFallback && !isPrimary {
|
||||
d.networkLastFallback.Store(time.Now())
|
||||
}
|
||||
return trackConn(conn, nil)
|
||||
return d.trackConn(conn, nil)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if d.networkStrategy == C.NetworkStrategyDefault {
|
||||
if d.networkStrategy == nil {
|
||||
if destination.IsIPv6() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||
} else {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
}
|
||||
} else {
|
||||
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
if strategy == C.NetworkStrategyDefault {
|
||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
if strategy == nil {
|
||||
strategy = d.networkStrategy
|
||||
}
|
||||
if strategy == nil {
|
||||
return d.ListenPacket(ctx, destination)
|
||||
}
|
||||
if !d.networkManager.AutoDetectInterface() {
|
||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
||||
if len(interfaceType) == 0 {
|
||||
interfaceType = d.networkType
|
||||
}
|
||||
if len(fallbackInterfaceType) == 0 {
|
||||
fallbackInterfaceType = d.fallbackNetworkType
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = d.networkFallbackDelay
|
||||
}
|
||||
network := N.NetworkUDP
|
||||
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
network += "4"
|
||||
}
|
||||
return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay))
|
||||
packetConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err != nil {
|
||||
// bind interface failed on legacy xiaomi systems
|
||||
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
|
||||
d.networkStrategy = nil
|
||||
return d.ListenPacket(ctx, destination)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return d.trackPacketConn(packetConn, nil)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||
return trackPacketConn(d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay))
|
||||
return d.udpListener.ListenPacket(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
if !conntrack.Enabled || err != nil {
|
||||
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
if d.tracker == nil || err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return conntrack.NewConn(conn)
|
||||
return d.tracker.NewConn(conn)
|
||||
}
|
||||
|
||||
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||
if !conntrack.Enabled || err != nil {
|
||||
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return conntrack.NewPacketConn(conn)
|
||||
return d.tracker.NewPacketConn(conn)
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
|
||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
|
||||
case <-returned:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case results <- dialResult{Conn: conn}:
|
||||
case results <- dialResult{Conn: conn, primary: primary}:
|
||||
case <-returned:
|
||||
conn.Close()
|
||||
}
|
||||
@@ -107,12 +107,12 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
|
||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
|
||||
case <-returned:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case results <- dialResult{Conn: conn}:
|
||||
case results <- dialResult{Conn: conn, primary: primary}:
|
||||
case <-returned:
|
||||
if primary && time.Since(startAt) <= fallbackDelay {
|
||||
resetFastFallback(time.Time{})
|
||||
@@ -149,9 +149,6 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, E.New("no available network interface")
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
var errors []error
|
||||
for _, primaryInterface := range primaryInterfaces {
|
||||
perNetListener := listener
|
||||
@@ -160,7 +157,7 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Name, ")"))
|
||||
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Index, ")"))
|
||||
}
|
||||
for _, fallbackInterface := range fallbackInterfaces {
|
||||
perNetListener := listener
|
||||
@@ -169,7 +166,7 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Name, ")"))
|
||||
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Index, ")"))
|
||||
}
|
||||
return nil, E.Errors(errors...)
|
||||
}
|
||||
@@ -180,44 +177,57 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS
|
||||
case C.NetworkStrategyDefault:
|
||||
if len(interfaceType) == 0 {
|
||||
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||
for _, iif := range interfaces {
|
||||
if iif.Index == defaultIf.Index {
|
||||
primaryInterfaces = append(primaryInterfaces, iif)
|
||||
} else {
|
||||
fallbackInterfaces = append(fallbackInterfaces, iif)
|
||||
if defaultIf != nil {
|
||||
for _, iif := range interfaces {
|
||||
if iif.Index == defaultIf.Index {
|
||||
primaryInterfaces = append(primaryInterfaces, iif)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = interfaces
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, iif.Type)
|
||||
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, it.Type)
|
||||
})
|
||||
}
|
||||
case C.NetworkStrategyHybrid:
|
||||
if len(interfaceType) == 0 {
|
||||
primaryInterfaces = interfaces
|
||||
} else {
|
||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, iif.Type)
|
||||
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, it.Type)
|
||||
})
|
||||
}
|
||||
case C.NetworkStrategyFallback:
|
||||
if len(interfaceType) == 0 {
|
||||
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||
for _, iif := range interfaces {
|
||||
if iif.Index == defaultIf.Index {
|
||||
primaryInterfaces = append(primaryInterfaces, iif)
|
||||
} else {
|
||||
fallbackInterfaces = append(fallbackInterfaces, iif)
|
||||
if defaultIf != nil {
|
||||
for _, iif := range interfaces {
|
||||
if iif.Index == defaultIf.Index {
|
||||
primaryInterfaces = append(primaryInterfaces, iif)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = interfaces
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, iif.Type)
|
||||
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, it.Type)
|
||||
})
|
||||
}
|
||||
if len(fallbackInterfaceType) == 0 {
|
||||
fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||
return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return it.Index == iif.Index
|
||||
})
|
||||
})
|
||||
} else {
|
||||
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(fallbackInterfaceType, iif.Type)
|
||||
})
|
||||
}
|
||||
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(fallbackInterfaceType, iif.Type)
|
||||
})
|
||||
}
|
||||
return primaryInterfaces, fallbackInterfaces
|
||||
}
|
||||
|
||||
@@ -13,22 +13,45 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
if len(destinationAddresses) == 0 {
|
||||
if !destination.IsIP() {
|
||||
panic("invalid usage")
|
||||
}
|
||||
destinationAddresses = []netip.Addr{destination.Addr}
|
||||
}
|
||||
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
var errors []error
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
} else {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port))
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
return nil, E.Errors(errors...)
|
||||
}
|
||||
|
||||
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
if len(destinationAddresses) == 0 {
|
||||
if !destination.IsIP() {
|
||||
panic("invalid usage")
|
||||
}
|
||||
destinationAddresses = []netip.Addr{destination.Addr}
|
||||
}
|
||||
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
@@ -106,17 +129,33 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
|
||||
}
|
||||
}
|
||||
|
||||
func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
|
||||
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
|
||||
if len(destinationAddresses) == 0 {
|
||||
if !destination.IsIP() {
|
||||
panic("invalid usage")
|
||||
}
|
||||
destinationAddresses = []netip.Addr{destination.Addr}
|
||||
}
|
||||
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
var errors []error
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err == nil {
|
||||
return conn, address, nil
|
||||
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err == nil {
|
||||
return conn, address, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
} else {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port))
|
||||
if err == nil {
|
||||
return conn, address, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
return nil, netip.Addr{}, E.Errors(errors...)
|
||||
}
|
||||
|
||||
@@ -17,16 +17,15 @@ import (
|
||||
)
|
||||
|
||||
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(networkManager, options)
|
||||
return NewDefault(ctx, options)
|
||||
}
|
||||
var (
|
||||
dialer N.Dialer
|
||||
err error
|
||||
)
|
||||
if options.Detour == "" {
|
||||
dialer, err = NewDefault(networkManager, options)
|
||||
dialer, err = NewDefault(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -37,9 +36,6 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
|
||||
}
|
||||
dialer = NewDetour(outboundManager, options.Detour)
|
||||
}
|
||||
if networkManager == nil {
|
||||
return NewDefault(networkManager, options)
|
||||
}
|
||||
if options.Detour == "" {
|
||||
router := service.FromContext[adapter.Router](ctx)
|
||||
if router != nil {
|
||||
@@ -58,11 +54,10 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
|
||||
if options.Detour != "" {
|
||||
return nil, E.New("`detour` is not supported in direct context")
|
||||
}
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(networkManager, options)
|
||||
return NewDefault(ctx, options)
|
||||
}
|
||||
dialer, err := NewDefault(networkManager, options)
|
||||
dialer, err := NewDefault(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -77,11 +72,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
|
||||
|
||||
type ParallelInterfaceDialer interface {
|
||||
N.Dialer
|
||||
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
|
||||
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
|
||||
}
|
||||
|
||||
type ParallelNetworkDialer interface {
|
||||
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
||||
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||
}
|
||||
|
||||
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -124,7 +123,7 @@ func (l *Listener) loopUDPOut() {
|
||||
case packet := <-l.packetOutbound:
|
||||
packet.Buffer.Release()
|
||||
N.PutPacketBuffer(packet)
|
||||
case <-time.After(time.Second):
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
||||
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||
return log.ContextWithNewID(ctx)
|
||||
},
|
||||
Logger: logger,
|
||||
Handler: adapter.NewRouteContextHandler(router, logger),
|
||||
Padding: options.Padding,
|
||||
Brutal: brutalOptions,
|
||||
Logger: logger,
|
||||
HandlerEx: adapter.NewRouteContextHandlerEx(router),
|
||||
Padding: options.Padding,
|
||||
Brutal: brutalOptions,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -52,6 +52,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
||||
return &Router{router, service}, nil
|
||||
}
|
||||
|
||||
// Deprecated: Use RouteConnectionEx instead.
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
if metadata.Destination == mux.Destination {
|
||||
// TODO: check if WithContext is necessary
|
||||
@@ -61,6 +62,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use RoutePacketConnectionEx instead.
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ type echConnWrapper struct {
|
||||
|
||||
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
//nolint:staticcheck
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
|
||||
@@ -147,6 +147,9 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
|
||||
pair.rawConf = b
|
||||
|
||||
secBuf, err := sec.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "serialize ECH private key")
|
||||
}
|
||||
sk := []byte{}
|
||||
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||
sk = append(sk, secBuf...)
|
||||
|
||||
@@ -28,7 +28,7 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
|
||||
}
|
||||
|
||||
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
||||
return &http3.RoundTripper{
|
||||
return &http3.Transport{
|
||||
TLSClientConfig: c.config,
|
||||
QUICConfig: quicConfig,
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
|
||||
@@ -97,6 +97,10 @@ func (c *echServerConfig) startWatcher() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.watcher = watcher
|
||||
return nil
|
||||
}
|
||||
@@ -232,7 +236,7 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
var echKey []byte
|
||||
if len(options.ECH.Key) > 0 {
|
||||
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
||||
} else if options.KeyPath != "" {
|
||||
} else if options.ECH.KeyPath != "" {
|
||||
content, err := os.ReadFile(options.ECH.KeyPath)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read ECH key")
|
||||
|
||||
@@ -174,6 +174,7 @@ type realityConnWrapper struct {
|
||||
|
||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
//nolint:staticcheck
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
|
||||
@@ -106,6 +106,10 @@ func (c *STDServerConfig) startWatcher() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.watcher = watcher
|
||||
return nil
|
||||
}
|
||||
|
||||
22
common/tls/time_wrapper.go
Normal file
22
common/tls/time_wrapper.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
)
|
||||
|
||||
type TimeServiceWrapper struct {
|
||||
ntp.TimeService
|
||||
}
|
||||
|
||||
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
||||
if w.TimeService == nil {
|
||||
return nil
|
||||
}
|
||||
return w.TimeService.TimeFunc()
|
||||
}
|
||||
|
||||
func (w *TimeServiceWrapper) Upstream() any {
|
||||
return w.TimeService
|
||||
}
|
||||
@@ -69,6 +69,7 @@ type utlsConnWrapper struct {
|
||||
|
||||
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
state := c.Conn.ConnectionState()
|
||||
//nolint:staticcheck
|
||||
return tls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
|
||||
7
constant/hysteria2.go
Normal file
7
constant/hysteria2.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
Hysterai2MasqueradeTypeFile = "file"
|
||||
Hysterai2MasqueradeTypeProxy = "proxy"
|
||||
Hysterai2MasqueradeTypeString = "string"
|
||||
)
|
||||
@@ -10,6 +10,7 @@ const (
|
||||
ProtocolDTLS = "dtls"
|
||||
ProtocolSSH = "ssh"
|
||||
ProtocolRDP = "rdp"
|
||||
ProtocolNTP = "ntp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -23,6 +23,7 @@ const (
|
||||
TypeVLESS = "vless"
|
||||
TypeTUIC = "tuic"
|
||||
TypeHysteria2 = "hysteria2"
|
||||
TypeNDIS = "ndis"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -80,6 +81,8 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "Selector"
|
||||
case TypeURLTest:
|
||||
return "URLTest"
|
||||
case TypeNDIS:
|
||||
return "NDIS"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ const (
|
||||
TCPTimeout = 15 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
QUICTimeout = 30 * time.Second
|
||||
STUNTimeout = 15 * time.Second
|
||||
UDPTimeout = 5 * time.Minute
|
||||
DefaultURLTestInterval = 3 * time.Minute
|
||||
DefaultURLTestIdleTimeout = 30 * time.Minute
|
||||
@@ -19,3 +17,18 @@ const (
|
||||
FatalStopTimeout = 10 * time.Second
|
||||
FakeIPMetadataSaveInterval = 10 * time.Second
|
||||
)
|
||||
|
||||
var PortProtocols = map[uint16]string{
|
||||
53: ProtocolDNS,
|
||||
123: ProtocolNTP,
|
||||
3478: ProtocolSTUN,
|
||||
443: ProtocolQUIC,
|
||||
}
|
||||
|
||||
var ProtocolTimeouts = map[string]time.Duration{
|
||||
ProtocolDNS: 10 * time.Second,
|
||||
ProtocolNTP: 10 * time.Second,
|
||||
ProtocolSTUN: 10 * time.Second,
|
||||
ProtocolQUIC: 30 * time.Second,
|
||||
ProtocolDTLS: 30 * time.Second,
|
||||
}
|
||||
|
||||
5
debug.go
5
debug.go
@@ -3,7 +3,6 @@ package box
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
@@ -26,9 +25,5 @@ func applyDebugOptions(options option.DebugOptions) {
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func applyDebugListenOption(options option.DebugOptions) {
|
||||
|
||||
encoder := json.NewEncoder(writer)
|
||||
encoder.SetIndent("", " ")
|
||||
encoder.Encode(memObject)
|
||||
encoder.Encode(&memObject)
|
||||
})
|
||||
r.Route("/pprof", func(r chi.Router) {
|
||||
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
@@ -2,6 +2,122 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.11.0-beta.20
|
||||
|
||||
* Hysteria2 `ignore_client_bandwidth` behavior update **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
||||
|
||||
See [Hysteria2](/configuration/inbound/hysteria2/#ignore_client_bandwidth).
|
||||
|
||||
#### 1.11.0-beta.17
|
||||
|
||||
* Add port hopping support for Hysteria2 **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Hysteria2](/configuration/outbound/hysteria2/).
|
||||
|
||||
#### 1.11.0-beta.14
|
||||
|
||||
* Allow adding route (exclude) address sets to routes **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
When `auto_redirect` is not enabled, directly add `route[_exclude]_address_set`
|
||||
to tun routes (equivalent to `route[_exclude]_address`).
|
||||
|
||||
Note that it **doesn't work on the Android graphical client** due to
|
||||
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
|
||||
but otherwise it works fine on all command line clients and Apple platforms.
|
||||
|
||||
See [route_address_set](/configuration/inbound/tun/#route_address_set) and
|
||||
[route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).
|
||||
|
||||
#### 1.11.0-beta.12
|
||||
|
||||
* Add `rule-set merge` command
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-beta.3
|
||||
|
||||
* Add more masquerade options for hysteria2 **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
|
||||
|
||||
#### 1.11.0-alpha.25
|
||||
|
||||
* Update quic-go to v0.48.2
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.22
|
||||
|
||||
* Add UDP timeout route option **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
|
||||
|
||||
#### 1.11.0-alpha.20
|
||||
|
||||
* Add UDP GSO support for WireGuard
|
||||
* Make GSO adaptive **1**
|
||||
|
||||
**1**:
|
||||
|
||||
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
|
||||
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
|
||||
|
||||
For TUN, GSO has been removed,
|
||||
see [Deprecated](/deprecated/#gso-option-in-tun).
|
||||
|
||||
#### 1.11.0-alpha.19
|
||||
|
||||
* Upgrade WireGuard outbound to endpoint **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
The new WireGuard endpoint combines inbound and outbound capabilities,
|
||||
and the old outbound will be removed in sing-box 1.13.0.
|
||||
|
||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||
|
||||
### 1.10.2
|
||||
|
||||
* Add deprecated warnings
|
||||
* Fix proxying websocket connections in HTTP/mixed inbounds
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.18
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.16
|
||||
|
||||
* Add `cache_capacity` DNS option **1**
|
||||
* Add `override_address` and `override_port` route options **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [DNS](/configuration/dns/#cache_capacity).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Rule Action](/configuration/route/#override_address) and
|
||||
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
#### 1.11.0-alpha.15
|
||||
|
||||
* Improve multi network dialing **1**
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
||||
# DNS
|
||||
|
||||
@@ -16,6 +20,7 @@
|
||||
"disable_cache": false,
|
||||
"disable_expire": false,
|
||||
"independent_cache": false,
|
||||
"cache_capacity": 0,
|
||||
"reverse_mapping": false,
|
||||
"client_subnet": "",
|
||||
"fakeip": {}
|
||||
@@ -58,6 +63,14 @@ Disable dns cache expire.
|
||||
|
||||
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
||||
|
||||
#### cache_capacity
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
LRU cache capacity.
|
||||
|
||||
Value less than 1024 will be ignored.
|
||||
|
||||
#### reverse_mapping
|
||||
|
||||
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
||||
# DNS
|
||||
|
||||
@@ -16,6 +20,7 @@
|
||||
"disable_cache": false,
|
||||
"disable_expire": false,
|
||||
"independent_cache": false,
|
||||
"cache_capacity": 0,
|
||||
"reverse_mapping": false,
|
||||
"client_subnet": "",
|
||||
"fakeip": {}
|
||||
@@ -57,6 +62,14 @@
|
||||
|
||||
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
||||
|
||||
#### cache_capacity
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
LRU 缓存容量。
|
||||
|
||||
小于 1024 的值将被忽略。
|
||||
|
||||
#### reverse_mapping
|
||||
|
||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
||||
|
||||
@@ -379,7 +379,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
|
||||
32
docs/configuration/endpoint/index.md
Normal file
32
docs/configuration/endpoint/index.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
# Endpoint
|
||||
|
||||
Endpoint is protocols that has both inbound and outbound behavior.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Type | Format |
|
||||
|-------------|---------------------------|
|
||||
| `wireguard` | [WireGuard](./wireguard/) |
|
||||
|
||||
#### tag
|
||||
|
||||
The tag of the endpoint.
|
||||
32
docs/configuration/endpoint/index.zh.md
Normal file
32
docs/configuration/endpoint/index.zh.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
# 端点
|
||||
|
||||
端点是具有入站和出站行为的协议。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-------------|---------------------------|
|
||||
| `wireguard` | [WireGuard](./wiregaurd/) |
|
||||
|
||||
#### tag
|
||||
|
||||
端点的标签。
|
||||
133
docs/configuration/endpoint/wireguard.md
Normal file
133
docs/configuration/endpoint/wireguard.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-ep",
|
||||
|
||||
"system": false,
|
||||
"name": "",
|
||||
"mtu": 1408,
|
||||
"address": [],
|
||||
"private_key": "",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 10001,
|
||||
"public_key": "",
|
||||
"pre_shared_key": "",
|
||||
"allowed_ips": [],
|
||||
"persistent_keepalive_interval": 0,
|
||||
"reserved": [0, 0, 0]
|
||||
}
|
||||
],
|
||||
"udp_timeout": "",
|
||||
"workers": 0,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
### Fields
|
||||
|
||||
#### system
|
||||
|
||||
Use system interface.
|
||||
|
||||
Requires privilege and cannot conflict with exists system interfaces.
|
||||
|
||||
#### name
|
||||
|
||||
Custom interface name for system interface.
|
||||
|
||||
#### mtu
|
||||
|
||||
WireGuard MTU.
|
||||
|
||||
`1408` will be used by default.
|
||||
|
||||
#### address
|
||||
|
||||
==Required==
|
||||
|
||||
List of IP (v4 or v6) address prefixes to be assigned to the interface.
|
||||
|
||||
#### private_key
|
||||
|
||||
==Required==
|
||||
|
||||
WireGuard requires base64-encoded public and private keys. These can be generated using the wg(8) utility:
|
||||
|
||||
```shell
|
||||
wg genkey
|
||||
echo "private key" || wg pubkey
|
||||
```
|
||||
|
||||
or `sing-box generate wg-keypair`.
|
||||
|
||||
#### peers
|
||||
|
||||
==Required==
|
||||
|
||||
List of WireGuard peers.
|
||||
|
||||
#### peers.address
|
||||
|
||||
WireGuard peer address.
|
||||
|
||||
#### peers.port
|
||||
|
||||
WireGuard peer port.
|
||||
|
||||
#### peers.public_key
|
||||
|
||||
==Required==
|
||||
|
||||
WireGuard peer public key.
|
||||
|
||||
#### peers.pre_shared_key
|
||||
|
||||
WireGuard peer pre-shared key.
|
||||
|
||||
#### peers.allowed_ips
|
||||
|
||||
==Required==
|
||||
|
||||
WireGuard allowed IPs.
|
||||
|
||||
#### peers.persistent_keepalive_interval
|
||||
|
||||
WireGuard persistent keepalive interval, in seconds.
|
||||
|
||||
Disabled by default.
|
||||
|
||||
#### peers.reserved
|
||||
|
||||
WireGuard reserved field bytes.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time.
|
||||
|
||||
`5m` will be used by default.
|
||||
|
||||
#### workers
|
||||
|
||||
WireGuard worker count.
|
||||
|
||||
CPU count is used by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
135
docs/configuration/endpoint/wireguard.zh.md
Normal file
135
docs/configuration/endpoint/wireguard.zh.md
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-ep",
|
||||
|
||||
"system": false,
|
||||
"name": "",
|
||||
"mtu": 1408,
|
||||
"address": [],
|
||||
"private_key": "",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 10001,
|
||||
"public_key": "",
|
||||
"pre_shared_key": "",
|
||||
"allowed_ips": [],
|
||||
"persistent_keepalive_interval": 0,
|
||||
"reserved": [0, 0, 0]
|
||||
}
|
||||
],
|
||||
"udp_timeout": "",
|
||||
"workers": 0,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
|
||||
### 字段
|
||||
|
||||
#### system_interface
|
||||
|
||||
使用系统设备。
|
||||
|
||||
需要特权且不能与已有系统接口冲突。
|
||||
|
||||
#### name
|
||||
|
||||
为系统接口自定义设备名称。
|
||||
|
||||
#### mtu
|
||||
|
||||
WireGuard MTU。
|
||||
|
||||
默认使用 1408。
|
||||
|
||||
#### address
|
||||
|
||||
==必填==
|
||||
|
||||
接口的 IPv4/IPv6 地址或地址段的列表您。
|
||||
|
||||
要分配给接口的 IP(v4 或 v6)地址段列表。
|
||||
|
||||
#### private_key
|
||||
|
||||
==必填==
|
||||
|
||||
WireGuard 需要 base64 编码的公钥和私钥。 这些可以使用 wg(8) 实用程序生成:
|
||||
|
||||
```shell
|
||||
wg genkey
|
||||
echo "private key" || wg pubkey
|
||||
```
|
||||
|
||||
或 `sing-box generate wg-keypair`.
|
||||
|
||||
#### peers
|
||||
|
||||
==必填==
|
||||
|
||||
WireGuard 对等方的列表。
|
||||
|
||||
#### peers.address
|
||||
|
||||
对等方的 IP 地址。
|
||||
|
||||
#### peers.port
|
||||
|
||||
对等方的 WireGuard 端口。
|
||||
|
||||
#### peers.public_key
|
||||
|
||||
==必填==
|
||||
|
||||
对等方的 WireGuard 公钥。
|
||||
|
||||
#### peers.pre_shared_key
|
||||
|
||||
对等方的预共享密钥。
|
||||
|
||||
#### peers.allowed_ips
|
||||
|
||||
==必填==
|
||||
|
||||
对等方的允许 IP 地址。
|
||||
|
||||
#### peers.persistent_keepalive_interval
|
||||
|
||||
对等方的持久性保持活动间隔,以秒为单位。
|
||||
|
||||
默认禁用。
|
||||
|
||||
#### peers.reserved
|
||||
|
||||
对等方的保留字段字节。
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间。
|
||||
|
||||
默认使用 `5m`。
|
||||
|
||||
#### workers
|
||||
|
||||
WireGuard worker 数量。
|
||||
|
||||
默认使用 CPU 数量。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
@@ -1,11 +1,20 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-alert: [masquerade](#masquerade)
|
||||
:material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "hysteria2",
|
||||
"tag": "hy2-in",
|
||||
...
|
||||
// Listen Fields
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
@@ -21,7 +30,7 @@
|
||||
],
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "",
|
||||
"masquerade": "", // or {}
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
@@ -67,9 +76,13 @@ Authentication password
|
||||
|
||||
#### ignore_client_bandwidth
|
||||
|
||||
Commands the client to use the BBR flow control algorithm instead of Hysteria CC.
|
||||
*When `up_mbps` and `down_mbps` are not set*:
|
||||
|
||||
Conflict with `up_mbps` and `down_mbps`.
|
||||
Commands clients to use the BBR CC instead of Hysteria CC.
|
||||
|
||||
*When `up_mbps` and `down_mbps` are set*:
|
||||
|
||||
Deny clients to use the BBR CC.
|
||||
|
||||
#### tls
|
||||
|
||||
@@ -79,14 +92,54 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### masquerade
|
||||
|
||||
HTTP3 server behavior when authentication fails.
|
||||
HTTP3 server behavior (URL string configuration) when authentication fails.
|
||||
|
||||
| Scheme | Example | Description |
|
||||
|--------------|-------------------------|--------------------|
|
||||
| `file` | `file:///var/www` | As a file server |
|
||||
| `http/https` | `http://127.0.0.1:8080` | As a reverse proxy |
|
||||
|
||||
A 404 page will be returned if empty.
|
||||
Conflict with `masquerade.type`.
|
||||
|
||||
A 404 page will be returned if masquerade is not configured.
|
||||
|
||||
#### masquerade.type
|
||||
|
||||
HTTP3 server behavior (Object configuration) when authentication fails.
|
||||
|
||||
| Type | Description | Fields |
|
||||
|----------|-----------------------------|-------------------------------------|
|
||||
| `file` | As a file server | `directory` |
|
||||
| `proxy` | As a reverse proxy | `url`, `rewrite_host` |
|
||||
| `string` | Reply with a fixed response | `status_code`, `headers`, `content` |
|
||||
|
||||
Conflict with `masquerade`.
|
||||
|
||||
A 404 page will be returned if masquerade is not configured.
|
||||
|
||||
#### masquerade.directory
|
||||
|
||||
File server root directory.
|
||||
|
||||
#### masquerade.url
|
||||
|
||||
Reverse proxy target URL.
|
||||
|
||||
#### masquerade.rewrite_host
|
||||
|
||||
Rewrite the `Host` header to the target URL.
|
||||
|
||||
#### masquerade.status_code
|
||||
|
||||
Fixed response status code.
|
||||
|
||||
#### masquerade.headers
|
||||
|
||||
Fixed response headers.
|
||||
|
||||
#### masquerade.content
|
||||
|
||||
Fixed response content.
|
||||
|
||||
#### brutal_debug
|
||||
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-alert: [masquerade](#masquerade)
|
||||
:material-alert: [ignore_client_bandwidth](#ignore_client_bandwidth)
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "hysteria2",
|
||||
"tag": "hy2-in",
|
||||
...
|
||||
// 监听字段
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
@@ -21,7 +30,7 @@
|
||||
],
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "",
|
||||
"masquerade": "", // 或 {}
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
@@ -64,9 +73,13 @@ Hysteria 用户
|
||||
|
||||
#### ignore_client_bandwidth
|
||||
|
||||
*当 `up_mbps` 和 `down_mbps` 未设定时*:
|
||||
|
||||
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
|
||||
|
||||
与 `up_mbps` 和 `down_mbps` 冲突。
|
||||
*当 `up_mbps` 和 `down_mbps` 已设定时*:
|
||||
|
||||
禁止客户端使用 BBR 拥塞控制算法。
|
||||
|
||||
#### tls
|
||||
|
||||
@@ -76,14 +89,54 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### masquerade
|
||||
|
||||
HTTP3 服务器认证失败时的行为。
|
||||
HTTP3 服务器认证失败时的行为 (URL 字符串配置)。
|
||||
|
||||
| Scheme | 示例 | 描述 |
|
||||
|--------------|-------------------------|---------|
|
||||
| `file` | `file:///var/www` | 作为文件服务器 |
|
||||
| `http/https` | `http://127.0.0.1:8080` | 作为反向代理 |
|
||||
|
||||
如果为空,则返回 404 页。
|
||||
如果 masquerade 未配置,则返回 404 页。
|
||||
|
||||
与 `masquerade.type` 冲突。
|
||||
|
||||
#### masquerade.type
|
||||
|
||||
HTTP3 服务器认证失败时的行为 (对象配置)。
|
||||
|
||||
| Type | 描述 | 字段 |
|
||||
|----------|---------|-------------------------------------|
|
||||
| `file` | 作为文件服务器 | `directory` |
|
||||
| `proxy` | 作为反向代理 | `url`, `rewrite_host` |
|
||||
| `string` | 返回固定响应 | `status_code`, `headers`, `content` |
|
||||
|
||||
如果 masquerade 未配置,则返回 404 页。
|
||||
|
||||
与 `masquerade` 冲突。
|
||||
|
||||
#### masquerade.directory
|
||||
|
||||
文件服务器根目录。
|
||||
|
||||
#### masquerade.url
|
||||
|
||||
反向代理目标 URL。
|
||||
|
||||
#### masquerade.rewrite_host
|
||||
|
||||
重写请求头中的 Host 字段到目标 URL。
|
||||
|
||||
#### masquerade.status_code
|
||||
|
||||
固定响应状态码。
|
||||
|
||||
#### masquerade.headers
|
||||
|
||||
固定响应头。
|
||||
|
||||
#### masquerade.content
|
||||
|
||||
固定响应内容。
|
||||
|
||||
#### brutal_debug
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
:material-alert-decagram: [route_address_set](#stack)
|
||||
:material-alert-decagram: [route_exclude_address_set](#stack)
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [address](#address)
|
||||
@@ -46,16 +52,7 @@ icon: material/new-box
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
@@ -69,28 +66,11 @@ icon: material/new-box
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"route_address_set": [
|
||||
"geoip-cloudflare"
|
||||
],
|
||||
@@ -110,13 +90,13 @@ icon: material/new-box
|
||||
0
|
||||
],
|
||||
"include_uid_range": [
|
||||
"1000-99999"
|
||||
"1000:99999"
|
||||
],
|
||||
"exclude_uid": [
|
||||
1000
|
||||
],
|
||||
"exclude_uid_range": [
|
||||
"1000-99999"
|
||||
"1000:99999"
|
||||
],
|
||||
"include_android_user": [
|
||||
0,
|
||||
@@ -137,8 +117,31 @@ icon: material/new-box
|
||||
"match_domain": []
|
||||
}
|
||||
},
|
||||
...
|
||||
// Listen Fields
|
||||
|
||||
// Deprecated
|
||||
"gso": false,
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
|
||||
... // Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
@@ -166,7 +169,7 @@ IPv4 and IPv6 prefix for the tun interface.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||
`inet4_address` is merged to `address` and will be removed in sing-box 1.12.0.
|
||||
|
||||
IPv4 prefix for the tun interface.
|
||||
|
||||
@@ -174,7 +177,7 @@ IPv4 prefix for the tun interface.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||
`inet6_address` is merged to `address` and will be removed in sing-box 1.12.0.
|
||||
|
||||
IPv6 prefix for the tun interface.
|
||||
|
||||
@@ -184,6 +187,10 @@ The maximum transmission unit.
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works, and will be removed in sing-box 1.12.0.
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
!!! quote ""
|
||||
@@ -243,7 +250,7 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Connection input mark used by `route_address_set` and `route_exclude_address_set`.
|
||||
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
|
||||
|
||||
`0x2023` is used by default.
|
||||
|
||||
@@ -251,7 +258,7 @@ Connection input mark used by `route_address_set` and `route_exclude_address_set
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Connection output mark used by `route_address_set` and `route_exclude_address_set`.
|
||||
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
|
||||
|
||||
`0x2024` is used by default.
|
||||
|
||||
@@ -284,7 +291,7 @@ Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
|
||||
`inet4_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address)
|
||||
instead.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
@@ -293,7 +300,7 @@ Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
|
||||
`inet6_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address)
|
||||
instead.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
@@ -308,7 +315,7 @@ Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
|
||||
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please
|
||||
use [route_exclude_address](#route_exclude_address) instead.
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
@@ -317,36 +324,62 @@ Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
|
||||
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please
|
||||
use [route_exclude_address](#route_exclude_address) instead.
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### route_address_set
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
=== "With `auto_redirect` enabled"
|
||||
|
||||
!!! quote ""
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||
Unmatched traffic will bypass the sing-box routes.
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||
Unmatched traffic will bypass the sing-box routes.
|
||||
=== "Without `auto_redirect` enabled"
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_address`.
|
||||
Unmatched traffic will bypass the sing-box routes.
|
||||
|
||||
Note that it **doesn't work on the Android graphical client** due to
|
||||
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
|
||||
but otherwise it works fine on all command line clients and Apple platforms.
|
||||
|
||||
#### route_exclude_address_set
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
=== "With `auto_redirect` enabled"
|
||||
|
||||
!!! quote ""
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||
Matched traffic will bypass the sing-box routes.
|
||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||
Matched traffic will bypass the sing-box routes.
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
=== "Without `auto_redirect` enabled"
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_exclude_address`.
|
||||
Matched traffic will bypass the sing-box routes.
|
||||
|
||||
Note that it **doesn't work on the Android graphical client** due to
|
||||
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
|
||||
but otherwise it works fine on all command line clients and Apple platforms.
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
@@ -360,7 +393,9 @@ Performance may degrade slightly, so it is not recommended to enable on when it
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
UDP NAT expiration time.
|
||||
|
||||
`5m` will be used by default.
|
||||
|
||||
#### stack
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
:material-alert-decagram: [route_address_set](#stack)
|
||||
:material-alert-decagram: [route_exclude_address_set](#stack)
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-plus: [address](#address)
|
||||
:material-delete-clock: [inet4_address](#inet4_address)
|
||||
@@ -46,16 +52,7 @@ icon: material/new-box
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
@@ -69,28 +66,11 @@ icon: material/new-box
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"route_address_set": [
|
||||
"geoip-cloudflare"
|
||||
],
|
||||
@@ -110,13 +90,13 @@ icon: material/new-box
|
||||
0
|
||||
],
|
||||
"include_uid_range": [
|
||||
"1000-99999"
|
||||
"1000:99999"
|
||||
],
|
||||
"exclude_uid": [
|
||||
1000
|
||||
],
|
||||
"exclude_uid_range": [
|
||||
"1000-99999"
|
||||
"1000:99999"
|
||||
],
|
||||
"include_android_user": [
|
||||
0,
|
||||
@@ -137,6 +117,29 @@ icon: material/new-box
|
||||
"match_domain": []
|
||||
}
|
||||
},
|
||||
|
||||
// 已弃用
|
||||
"gso": false,
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
|
||||
... // 监听字段
|
||||
}
|
||||
@@ -168,7 +171,7 @@ tun 接口的 IPv4 和 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet4_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
==必填==
|
||||
|
||||
@@ -178,7 +181,7 @@ tun 接口的 IPv4 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet6_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
tun 接口的 IPv6 前缀。
|
||||
|
||||
@@ -188,6 +191,10 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
GSO 对于透明代理场景没有优势,已废弃和不再生效,且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
!!! quote ""
|
||||
@@ -288,7 +295,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
@@ -296,7 +303,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
@@ -310,7 +317,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
@@ -318,35 +325,59 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### route_address_set
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
=== "`auto_redirect` 已启用"
|
||||
|
||||
!!! quote ""
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||
|
||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||
不匹配的流量将绕过 sing-box 路由。
|
||||
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
|
||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||
=== "`auto_redirect` 未启用"
|
||||
|
||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||
不匹配的流量将绕过 sing-box 路由。
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_address`。
|
||||
不匹配的流量将绕过 sing-box 路由。
|
||||
|
||||
请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException),
|
||||
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
|
||||
|
||||
#### route_exclude_address_set
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
=== "`auto_redirect` 已启用"
|
||||
|
||||
!!! quote ""
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||
|
||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||
匹配的流量将绕过 sing-box 路由。
|
||||
|
||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||
匹配的流量将绕过 sing-box 路由。
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
=== "`auto_redirect` 未启用"
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_exclude_address`。
|
||||
匹配的流量将绕过 sing-box 路由。
|
||||
|
||||
请注意,由于 Android VpnService 无法处理大量路由(DeadSystemException),
|
||||
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
@@ -356,7 +387,9 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
||||
UDP NAT 过期时间。
|
||||
|
||||
默认使用 `5m`。
|
||||
|
||||
#### stack
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ sing-box uses JSON for configuration files.
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"ntp": {},
|
||||
"endpoints": [],
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
@@ -23,6 +24,7 @@ sing-box uses JSON for configuration files.
|
||||
| `log` | [Log](./log/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
| `ntp` | [NTP](./ntp/) |
|
||||
| `endpoints` | [Endpoint](./endpoint/) |
|
||||
| `inbounds` | [Inbound](./inbound/) |
|
||||
| `outbounds` | [Outbound](./outbound/) |
|
||||
| `route` | [Route](./route/) |
|
||||
|
||||
@@ -8,6 +8,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
{
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"endpoints": [],
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
@@ -21,6 +22,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
|----------------|------------------------|
|
||||
| `log` | [日志](./log/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
| `endpoints` | [端点](./endpoint/) |
|
||||
| `inbounds` | [入站](./inbound/) |
|
||||
| `outbounds` | [出站](./outbound/) |
|
||||
| `route` | [路由](./route/) |
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-delete-clock: [override_address](#override_address)
|
||||
:material-delete-clock: [override_port](#override_port)
|
||||
|
||||
`direct` outbound send requests directly.
|
||||
|
||||
### Structure
|
||||
@@ -9,7 +18,6 @@
|
||||
|
||||
"override_address": "1.0.0.1",
|
||||
"override_port": 53,
|
||||
"proxy_protocol": 0,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
@@ -19,16 +27,20 @@
|
||||
|
||||
#### override_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
Override the connection destination address.
|
||||
|
||||
#### override_port
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
Override the connection destination port.
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
Write [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
||||
|
||||
Protocol value can be `1` or `2`.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-alert-decagram: [override_address](#override_address)
|
||||
:material-alert-decagram: [override_port](#override_port)
|
||||
|
||||
`direct` 出站直接发送请求。
|
||||
|
||||
### 结构
|
||||
@@ -9,7 +18,6 @@
|
||||
|
||||
"override_address": "1.0.0.1",
|
||||
"override_port": 53,
|
||||
"proxy_protocol": 0,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
@@ -19,18 +27,20 @@
|
||||
|
||||
#### override_address
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。
|
||||
|
||||
覆盖连接目标地址。
|
||||
|
||||
#### override_port
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。
|
||||
|
||||
覆盖连接目标端口。
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
写出 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) 到连接头。
|
||||
|
||||
可用协议版本值:`1` 或 `2`。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [server_ports](#server_ports)
|
||||
:material-plus: [hop_interval](#hop_interval)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -7,6 +16,10 @@
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"server_ports": [
|
||||
"2080:3000"
|
||||
],
|
||||
"hop_interval": "",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"obfs": {
|
||||
@@ -22,6 +35,10 @@
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
!!! warning "Difference from official Hysteria2"
|
||||
|
||||
The official Hysteria2 supports an authentication method called **userpass**,
|
||||
@@ -44,6 +61,24 @@ The server address.
|
||||
|
||||
The server port.
|
||||
|
||||
Ignored if `server_ports` is set.
|
||||
|
||||
#### server_ports
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
Server port range list.
|
||||
|
||||
Conflicts with `server_port`.
|
||||
|
||||
#### hop_interval
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
Port hopping interval.
|
||||
|
||||
`30s` is used by default.
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
Max bandwidth, in Mbps.
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [server_ports](#server_ports)
|
||||
:material-plus: [hop_interval](#hop_interval)
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
@@ -7,6 +16,10 @@
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"server_ports": [
|
||||
"2080:3000"
|
||||
],
|
||||
"hop_interval": "",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"obfs": {
|
||||
@@ -22,6 +35,10 @@
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
|
||||
!!! warning "与官方 Hysteria2 的区别"
|
||||
|
||||
官方程序支持一种名为 **userpass** 的验证方式,
|
||||
@@ -42,6 +59,24 @@
|
||||
|
||||
服务器端口。
|
||||
|
||||
如果设置了 `server_ports`,则忽略此项。
|
||||
|
||||
#### server_ports
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
服务器端口范围列表。
|
||||
|
||||
与 `server_port` 冲突。
|
||||
|
||||
#### hop_interval
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
端口跳跃间隔。
|
||||
|
||||
默认使用 `30s`。
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
最大带宽。
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
WireGuard outbound is deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
@@ -12,10 +24,9 @@
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"system_interface": false,
|
||||
"gso": false,
|
||||
"interface_name": "wg0",
|
||||
"local_address": [
|
||||
"10.0.0.2/32"
|
||||
"10.0.0.1/32"
|
||||
],
|
||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||
"peers": [
|
||||
@@ -37,6 +48,10 @@
|
||||
"mtu": 1408,
|
||||
"network": "tcp",
|
||||
|
||||
// Deprecated
|
||||
|
||||
"gso": false,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
@@ -69,6 +84,10 @@ Custom interface name for system interface.
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
GSO will be automatically enabled when available since sing-box 1.11.0.
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
WireGuard 出站已被启用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
@@ -12,10 +24,9 @@
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"system_interface": false,
|
||||
"gso": false,
|
||||
"interface_name": "wg0",
|
||||
"local_address": [
|
||||
"10.0.0.2/32"
|
||||
"10.0.0.1/32"
|
||||
],
|
||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
||||
@@ -24,6 +35,10 @@
|
||||
"workers": 4,
|
||||
"mtu": 1408,
|
||||
"network": "tcp",
|
||||
|
||||
// 废弃的
|
||||
|
||||
"gso": false,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
@@ -57,6 +72,10 @@
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
自 sing-box 1.11.0 起,GSO 将可用时自动启用。
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: material/new-box
|
||||
:material-plus: [default_network_strategy](#default_network_strategy)
|
||||
:material-plus: [default_network_type](#default_network_type)
|
||||
:material-plus: [default_fallback_network_type](#default_fallback_network_type)
|
||||
:material-alert: [default_fallback_delay](#default_fallback_delay)
|
||||
:material-plus: [default_fallback_delay](#default_fallback_delay)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: material/new-box
|
||||
:material-plus: [network_strategy](#network_strategy)
|
||||
:material-plus: [default_network_type](#default_network_type)
|
||||
:material-plus: [default_fallback_network_type](#default_fallback_network_type)
|
||||
:material-alert: [default_fallback_delay](#default_fallback_delay)
|
||||
:material-plus: [default_fallback_delay](#default_fallback_delay)
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
|
||||
@@ -388,7 +388,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
|
||||
@@ -10,12 +10,8 @@ icon: material/new-box
|
||||
{
|
||||
"action": "route", // default
|
||||
"outbound": "",
|
||||
"network_strategy": "",
|
||||
"network_type": [],
|
||||
"fallback_network_type": [],
|
||||
"fallback_delay": "",
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false
|
||||
|
||||
... // route-options Fields
|
||||
}
|
||||
```
|
||||
|
||||
@@ -31,6 +27,35 @@ icon: material/new-box
|
||||
|
||||
Tag of target outbound.
|
||||
|
||||
#### route-options Fields
|
||||
|
||||
See `route-options` fields below.
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"override_address": "",
|
||||
"override_port": 0,
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false,
|
||||
"udp_timeout": ""
|
||||
}
|
||||
```
|
||||
|
||||
`route-options` set options for routing.
|
||||
|
||||
#### override_address
|
||||
|
||||
Override the connection destination address.
|
||||
|
||||
#### override_port
|
||||
|
||||
Override the connection destination port.
|
||||
|
||||
#### network_strategy
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
|
||||
@@ -62,19 +87,27 @@ do not support receiving UDP packets with domain addresses, such as Surge.
|
||||
|
||||
If enabled, attempts to connect UDP connection to the destination instead of listen.
|
||||
|
||||
### route-options
|
||||
#### udp_timeout
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false
|
||||
}
|
||||
```
|
||||
Timeout for UDP connections.
|
||||
|
||||
`route-options` set options for routing.
|
||||
Setting a larger value than the UDP timeout in inbounds will have no effect.
|
||||
|
||||
Default value for protocol sniffed connections:
|
||||
|
||||
| Timeout | Protocol |
|
||||
|---------|----------------------|
|
||||
| `10s` | `dns`, `ntp`, `stun` |
|
||||
| `30s` | `quic`, `dtls` |
|
||||
|
||||
If no protocol is sniffed, the following ports will be recognized as protocols by default:
|
||||
|
||||
| Port | Protocol |
|
||||
|------|----------|
|
||||
| 53 | `dns` |
|
||||
| 123 | `ntp` |
|
||||
| 443 | `quic` |
|
||||
| 3478 | `stun` |
|
||||
|
||||
### reject
|
||||
|
||||
|
||||
@@ -10,12 +10,8 @@ icon: material/new-box
|
||||
{
|
||||
"action": "route", // 默认
|
||||
"outbound": "",
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"network_type": [],
|
||||
"fallback_network_type": [],
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false
|
||||
|
||||
... // route-options 字段
|
||||
}
|
||||
```
|
||||
|
||||
@@ -27,6 +23,39 @@ icon: material/new-box
|
||||
|
||||
目标出站的标签。
|
||||
|
||||
#### route-options 字段
|
||||
|
||||
参阅下方的 `route-options` 字段。
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"override_address": "",
|
||||
"override_port": 0,
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false,
|
||||
"udp_timeout": ""
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
|
||||
`route-options` 为路由设置选项。
|
||||
|
||||
#### override_address
|
||||
|
||||
覆盖目标地址。
|
||||
|
||||
#### override_port
|
||||
|
||||
覆盖目标端口。
|
||||
|
||||
#### network_strategy
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。
|
||||
@@ -56,23 +85,27 @@ icon: material/new-box
|
||||
|
||||
如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。
|
||||
|
||||
### route-options
|
||||
#### udp_timeout
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false
|
||||
}
|
||||
```
|
||||
UDP 连接超时时间。
|
||||
|
||||
!!! note ""
|
||||
设置比入站 UDP 超时更大的值将无效。
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
已探测协议连接的默认值:
|
||||
|
||||
`route-options` 为路由设置选项。
|
||||
| 超时 | 协议 |
|
||||
|-------|----------------------|
|
||||
| `10s` | `dns`, `ntp`, `stun` |
|
||||
| `30s` | `quic`, `dtls` |
|
||||
|
||||
如果没有探测到协议,以下端口将默认识别为协议:
|
||||
|
||||
| 端口 | 协议 |
|
||||
|------|--------|
|
||||
| 53 | `dns` |
|
||||
| 123 | `ntp` |
|
||||
| 443 | `quic` |
|
||||
| 3478 | `stun` |
|
||||
|
||||
### reject
|
||||
|
||||
|
||||
@@ -68,9 +68,9 @@ Enable UDP fragmentation.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds.
|
||||
UDP NAT expiration time.
|
||||
|
||||
`5m` is used by default.
|
||||
`5m` will be used by default.
|
||||
|
||||
#### detour
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ icon: material/delete-clock
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间,以秒为单位。
|
||||
UDP NAT 过期时间。
|
||||
|
||||
默认使用 `5m`。
|
||||
|
||||
|
||||
@@ -22,6 +22,25 @@ check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
||||
|
||||
Old fields will be removed in sing-box 1.13.0.
|
||||
|
||||
#### Destination override fields in direct outbound
|
||||
|
||||
Destination override fields (`override_address` / `override_port`) in direct outbound are deprecated
|
||||
and can be replaced by rule actions,
|
||||
check [Migration](../migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
#### WireGuard outbound
|
||||
|
||||
WireGuard outbound is deprecated and can be replaced by endpoint,
|
||||
check [Migration](../migration/#migrate-wireguard-outbound-to-endpoint).
|
||||
|
||||
Old outbound will be removed in sing-box 1.13.0.
|
||||
|
||||
#### GSO option in TUN
|
||||
|
||||
GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works in TUN.
|
||||
|
||||
Old fields will be removed in sing-box 1.13.0.
|
||||
|
||||
## 1.10.0
|
||||
|
||||
#### TUN address fields are merged
|
||||
|
||||
@@ -20,6 +20,26 @@ icon: material/delete-alert
|
||||
|
||||
旧字段将在 sing-box 1.13.0 中被移除。
|
||||
|
||||
#### direct 出站中的目标地址覆盖字段
|
||||
|
||||
direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代,
|
||||
参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。
|
||||
|
||||
旧字段将在 sing-box 1.13.0 中被移除。
|
||||
|
||||
#### WireGuard 出站
|
||||
|
||||
WireGuard 出站已废弃且可以通过端点替代,
|
||||
参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
|
||||
|
||||
旧出站将在 sing-box 1.13.0 中被移除。
|
||||
|
||||
#### TUN 的 GSO 字段
|
||||
|
||||
GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用。
|
||||
|
||||
旧字段将在 sing-box 1.13.0 中被移除。
|
||||
|
||||
## 1.10.0
|
||||
|
||||
#### Match source 规则项已重命名
|
||||
|
||||
@@ -156,6 +156,115 @@ Inbound fields are deprecated and can be replaced by rule actions.
|
||||
}
|
||||
```
|
||||
|
||||
### Migrate destination override fields to route options
|
||||
|
||||
Destination override fields in direct outbound are deprecated and can be replaced by route options.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[Rule Action](/configuration/route/rule_action/) /
|
||||
[Direct](/configuration/outbound/direct/)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"override_address": "1.1.1.1",
|
||||
"override_port": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: New"
|
||||
|
||||
```json
|
||||
{
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "route-options", // or route
|
||||
"override_address": "1.1.1.1",
|
||||
"override_port": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Migrate WireGuard outbound to endpoint
|
||||
|
||||
WireGuard outbound is deprecated and can be replaced by endpoint.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[Endpoint](/configuration/endpoint/) /
|
||||
[WireGuard Endpoint](/configuration/endpoint/wireguard/) /
|
||||
[WireGuard Outbound](/configuration/outbound/wireguard/)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 10001,
|
||||
"system_interface": true,
|
||||
"gso": true,
|
||||
"interface_name": "wg0",
|
||||
"local_address": [
|
||||
"10.0.0.1/32"
|
||||
],
|
||||
"private_key": "<private_key>",
|
||||
"peer_public_key": "<peer_public_key>",
|
||||
"pre_shared_key": "<pre_shared_key>",
|
||||
"reserved": [0, 0, 0],
|
||||
"mtu": 1408
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: New"
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-ep",
|
||||
"system": true,
|
||||
"name": "wg0",
|
||||
"mtu": 1408,
|
||||
"address": [
|
||||
"10.0.0.2/32"
|
||||
],
|
||||
"private_key": "<private_key>",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 10001,
|
||||
"public_key": "<peer_public_key>",
|
||||
"pre_shared_key": "<pre_shared_key>",
|
||||
"allowed_ips": [
|
||||
"0.0.0.0/0"
|
||||
],
|
||||
"persistent_keepalive_interval": 30,
|
||||
"reserved": [0, 0, 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### TUN address fields are merged
|
||||
|
||||
@@ -156,6 +156,116 @@ icon: material/arrange-bring-forward
|
||||
}
|
||||
```
|
||||
|
||||
### 迁移 direct 出站中的目标地址覆盖字段到路由字段
|
||||
|
||||
direct 出站中的目标地址覆盖字段已废弃,且可以被路由字段替代。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[Rule Action](/zh/configuration/route/rule_action/) /
|
||||
[Direct](/zh/configuration/outbound/direct/)
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"override_address": "1.1.1.1",
|
||||
"override_port": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
```json
|
||||
{
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "route-options", // 或 route
|
||||
"override_address": "1.1.1.1",
|
||||
"override_port": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 迁移 WireGuard 出站到端点
|
||||
|
||||
WireGuard 出站已被弃用,且可以被端点替代。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[端点](/zh/configuration/endpoint/) /
|
||||
[WireGuard 端点](/zh/configuration/endpoint/wireguard/) /
|
||||
[WireGuard 出站](/zh/configuration/outbound/wireguard/)
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 10001,
|
||||
"system_interface": true,
|
||||
"gso": true,
|
||||
"interface_name": "wg0",
|
||||
"local_address": [
|
||||
"10.0.0.1/32"
|
||||
],
|
||||
"private_key": "<private_key>",
|
||||
"peer_public_key": "<peer_public_key>",
|
||||
"pre_shared_key": "<pre_shared_key>",
|
||||
"reserved": [0, 0, 0],
|
||||
"mtu": 1408
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-ep",
|
||||
"system": true,
|
||||
"name": "wg0",
|
||||
"mtu": 1408,
|
||||
"address": [
|
||||
"10.0.0.2/32"
|
||||
],
|
||||
"private_key": "<private_key>",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 10001,
|
||||
"public_key": "<peer_public_key>",
|
||||
"pre_shared_key": "<pre_shared_key>",
|
||||
"allowed_ips": [
|
||||
"0.0.0.0/0"
|
||||
],
|
||||
"persistent_keepalive_interval": 30,
|
||||
"reserved": [0, 0, 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### TUN 地址字段已合并
|
||||
@@ -164,8 +274,6 @@ icon: material/arrange-bring-forward
|
||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||
|
||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[TUN](/zh/configuration/inbound/tun/)
|
||||
|
||||
@@ -32,7 +32,7 @@ func groupRouter(server *Server) http.Handler {
|
||||
|
||||
func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
groups := common.Map(common.Filter(server.outboundManager.Outbounds(), func(it adapter.Outbound) bool {
|
||||
groups := common.Map(common.Filter(server.outbound.Outbounds(), func(it adapter.Outbound) bool {
|
||||
_, isGroup := it.(adapter.OutboundGroup)
|
||||
return isGroup
|
||||
}), func(it adapter.Outbound) *badjson.JSONObject {
|
||||
@@ -86,7 +86,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||
result, err = urlTestGroup.URLTest(ctx)
|
||||
} else {
|
||||
outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||
itOutbound, _ := server.outboundManager.Outbound(it)
|
||||
itOutbound, _ := server.outbound.Outbound(it)
|
||||
return itOutbound
|
||||
}))
|
||||
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
||||
@@ -100,7 +100,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||
continue
|
||||
}
|
||||
checked[realTag] = true
|
||||
p, loaded := server.outboundManager.Outbound(realTag)
|
||||
p, loaded := server.outbound.Outbound(realTag)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -18,17 +18,19 @@ func configRouter(server *Server, logFactory log.Factory) http.Handler {
|
||||
}
|
||||
|
||||
type configSchema struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
Mode string `json:"mode"`
|
||||
LogLevel string `json:"log-level"`
|
||||
IPv6 bool `json:"ipv6"`
|
||||
Tun map[string]any `json:"tun"`
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
Mode string `json:"mode"`
|
||||
// sing-box added
|
||||
ModeList []string `json:"mode-list"`
|
||||
LogLevel string `json:"log-level"`
|
||||
IPv6 bool `json:"ipv6"`
|
||||
Tun map[string]any `json:"tun"`
|
||||
}
|
||||
|
||||
func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -41,6 +43,7 @@ func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWrit
|
||||
}
|
||||
render.JSON(w, r, &configSchema{
|
||||
Mode: server.mode,
|
||||
ModeList: server.modeList,
|
||||
BindAddress: "*",
|
||||
LogLevel: log.FormatLevel(logLevel),
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user