Compare commits

..

52 Commits

Author SHA1 Message Date
世界
7aab073aa5 documentation: Bump version 2024-11-15 17:21:48 +08:00
世界
0c7c64c2d4 Fix missing inbound options 2024-11-15 17:21:48 +08:00
世界
94abfdcab2 Fix HandshakeFailure usages 2024-11-15 17:14:19 +08:00
世界
8e4327c6cb Fix close listener 2024-11-15 17:14:19 +08:00
世界
fe610db7ab documentation: Add override destination to route options 2024-11-15 17:14:19 +08:00
世界
0ac42102aa Add override destination to route options 2024-11-15 17:11:20 +08:00
世界
667e2ba083 release: Notarize macos standalone manually with --no-s3-acceleration 2024-11-14 17:53:50 +08:00
世界
2fb041ac9a Fix logical dns rule 2024-11-14 17:53:48 +08:00
世界
5139e5232e Add dns.cache_capacity 2024-11-14 17:53:44 +08:00
世界
d1eff7fcca documentation: Bump version 2024-11-13 22:07:38 +08:00
世界
61f89206ec documentation: Refactor multi networks strategy 2024-11-13 22:07:38 +08:00
世界
55b2e114f6 Refactor multi networks strategy 2024-11-13 22:07:38 +08:00
世界
2be7482e32 Fix match clash mode 2024-11-13 17:06:39 +08:00
世界
e18b527eaa documentation: Bump version 2024-11-13 14:33:23 +08:00
世界
63e38cccf5 documentation: Add parallel network dialing 2024-11-13 14:33:23 +08:00
世界
9e42012737 documentation: Remove unused titles 2024-11-13 14:33:14 +08:00
世界
96dab3ba25 selector: Fix crash before start 2024-11-13 14:33:14 +08:00
世界
4d9f11d5f0 http: Fix proxying websocket 2024-11-13 14:33:14 +08:00
世界
15a9876a10 Add multi network dialing 2024-11-13 14:33:14 +08:00
世界
8cb11bf322 Fix rule match 2024-11-13 10:39:00 +08:00
世界
edf40da07c Fix check interface 2024-11-12 14:37:27 +08:00
世界
7f99cab893 Downgrade NDK to 26.2.11394342 2024-11-12 12:56:31 +08:00
世界
c0e48f865e documentation: Bump version 2024-11-12 11:25:30 +08:00
世界
0d1b3226cd Fix match rules 2024-11-12 11:25:12 +08:00
世界
26064a9fdc documentation: Bump version 2024-11-11 20:13:55 +08:00
世界
d8e66b9180 documentation: Add new rule item types 2024-11-11 20:13:55 +08:00
世界
c59f282b7d documentation: Merge route options to route actions 2024-11-11 20:13:55 +08:00
世界
aa35ae1736 Add network_[type/is_expensive/is_constrained] rule items 2024-11-11 20:13:55 +08:00
世界
ef2a2fdd52 Merge route options to route actions 2024-11-11 20:13:55 +08:00
世界
9988144868 Fix decompile rule-set 2024-11-11 20:13:55 +08:00
世界
412701d4c5 refactor: Platform Interfaces 2024-11-11 20:13:55 +08:00
世界
b6c940af61 Fix match rules 2024-11-11 16:06:56 +08:00
世界
1edb80adcc Fix start stage 2024-11-11 16:04:27 +08:00
世界
e3ffffc645 documentation: Bump version 2024-11-10 17:33:00 +08:00
世界
7daf2d1716 Fix hijack-dns 2024-11-10 17:33:00 +08:00
世界
b4f1c2a596 refactor: Extract clash/v2ray/time service form router 2024-11-10 17:33:00 +08:00
世界
1df8dfcade refactor: Modular network manager 2024-11-10 17:33:00 +08:00
世界
beaab2e4db refactor: Modular inbound/outbound manager 2024-11-10 12:12:08 +08:00
世界
1ee7a4a272 documentation: Bump version 2024-11-10 12:12:08 +08:00
世界
44560f0c20 documentation: Add rule action 2024-11-10 12:12:08 +08:00
世界
b8613de673 documentation: Update the scheduled removal time of deprecated features 2024-11-10 12:12:08 +08:00
世界
24496d89b1 documentation: Remove outdated icons 2024-11-10 12:12:08 +08:00
世界
1a230bda5d Migrate bad options to library 2024-11-10 12:12:08 +08:00
世界
85f634d0cb Implement udp connect 2024-11-10 12:12:08 +08:00
世界
b75dbc8a26 Implement new deprecated warnings 2024-11-10 12:12:08 +08:00
世界
3a3ad11cb3 Improve rule actions 2024-11-09 18:41:11 +08:00
世界
866be4acbd Remove unused reject methods 2024-11-09 18:41:11 +08:00
世界
776052de20 refactor: Modular inbounds/outbounds 2024-11-09 18:41:11 +08:00
世界
e45763d5ba Implement dns-hijack 2024-11-09 12:35:48 +08:00
世界
5eb8522205 Implement resolve(server) 2024-11-09 12:35:47 +08:00
世界
c2b833a228 Implement TCP and ICMP rejects 2024-11-09 12:35:47 +08:00
世界
7f65ab8166 Crazy sekai overturns the small pond 2024-11-09 12:35:43 +08:00
236 changed files with 3216 additions and 7589 deletions

View File

@@ -1,629 +0,0 @@
name: Build
on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
build:
description: "Build type"
required: true
type: choice
default: "All"
options:
- All
- Binary
- Android
- Apple
- app-store
- iOS
- macOS
- tvOS
- macOS-standalone
- publish-android
push:
branches:
- main-next
- dev-next
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
cancel-in-progress: true
jobs:
calculate_version:
name: Calculate version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --nightly
- name: Set outputs
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
build:
name: Build binary
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
runs-on: ubuntu-latest
needs:
- calculate_version
strategy:
matrix:
include:
- name: linux_386
goos: linux
goarch: 386
- name: linux_amd64
goos: linux
goarch: amd64
- name: linux_arm64
goos: linux
goarch: arm64
- name: linux_arm
goos: linux
goarch: arm
goarm: 6
- name: linux_arm_v7
goos: linux
goarch: arm
goarm: 7
- name: linux_s390x
goos: linux
goarch: s390x
- name: linux_riscv64
goos: linux
goarch: riscv64
- name: linux_mips64le
goos: linux
goarch: mips64le
- name: windows_amd64
goos: windows
goarch: amd64
require_legacy_go: true
- name: windows_386
goos: windows
goarch: 386
require_legacy_go: true
- name: windows_arm64
goos: windows
goarch: arm64
- name: darwin_arm64
goos: darwin
goarch: arm64
- name: darwin_amd64
goos: darwin
goarch: amd64
require_legacy_go: true
- name: android_arm64
goos: android
goarch: arm64
- name: android_arm
goos: android
goarch: arm
goarm: 7
- name: android_amd64
goos: android
goarch: amd64
- name: android_386
goos: android
goarch: 386
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Cache legacy Go
if: matrix.require_legacy_go
id: cache-legacy-go
uses: actions/cache@v4
with:
path: |
~/go/go1.20.14
key: go120
- name: Setup legacy Go
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |-
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
tar -xzf go1.20.14.linux-amd64.tar.gz
mv go $HOME/go/go1.20.14
- name: Setup Android NDK
if: matrix.goos == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
local-cache: true
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
install-only: true
- name: Extract signing key
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build
if: matrix.goos != 'android'
run: |-
goreleaser release --clean --split
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOPATH: ${{ env.HOME }}/go
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Build Android
if: matrix.goos == 'android'
run: |-
go install -v ./cmd/internal/build
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
env:
BUILD_GOOS: ${{ matrix.goos }}
BUILD_GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.name }}
path: 'dist'
build_android:
name: Build Android
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
runs-on: ubuntu-latest
needs:
- calculate_version
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
make lib_android
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Checkout main branch
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/dev-next'
run: |-
cd clients/android
git checkout dev
- name: Gradle cache
uses: actions/cache@v4
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Build release
if: github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Build debug
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
./gradlew :app:assemblePlayRelease
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare release upload
if: github.event_name == 'workflow_dispatch'
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
- name: Prepare debug upload
if: github.event_name != 'workflow_dispatch'
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-android-apks
path: 'dist'
- name: Upload debug apk (arm64-v8a)
if: github.event_name != 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: "SFA-${{ needs.calculate_version.outputs.version }}-arm64-v8a.apk"
path: 'dist/release/*-arm64-v8a.apk'
- name: Upload debug apk (universal)
if: github.event_name != 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: "SFA-${{ needs.calculate_version.outputs.version }}-universal.apk"
path: 'dist/release/*-universal.apk'
publish_android:
name: Publish Android
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
runs-on: ubuntu-latest
needs:
- calculate_version
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
make lib_android
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Checkout main branch
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/dev-next'
run: |-
cd clients/android
git checkout dev
- name: Gradle cache
uses: actions/cache@v4
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Build
run: |-
go run -v ./cmd/internal/update_android_version --ci
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
./gradlew :app:publishPlayReleaseBundle
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
build_apple:
name: Build Apple clients
runs-on: macos-15
needs:
- calculate_version
strategy:
matrix:
include:
- name: iOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
platform: ios
scheme: SFI
destination: 'generic/platform=iOS'
archive: build/SFI.xcarchive
upload: SFI/Upload.plist
- name: macOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
platform: macos
scheme: SFM
destination: 'generic/platform=macOS'
archive: build/SFM.xcarchive
upload: SFI/Upload.plist
- name: tvOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
platform: tvos
scheme: SFT
destination: 'generic/platform=tvOS'
archive: build/SFT.xcarchive
upload: SFI/Upload.plist
- name: macOS-standalone
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
platform: macos
scheme: SFM.System
destination: 'generic/platform=macOS'
archive: build/SFM.System.xcarchive
export: SFM.System/Export.plist
export_path: build/SFM.System
steps:
- name: Checkout
if: matrix.if
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Set tag
if: matrix.if
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Checkout main branch
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
run: |-
cd clients/apple
git checkout main
- name: Checkout dev branch
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
cd clients/apple
git checkout dev
- name: Setup certificates
if: matrix.if
run: |-
CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12
KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db
echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILES_PATH"
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
xcrun notarytool store-credentials "notarytool-password" \
--key $ASC_KEY_PATH \
--key-id $ASC_KEY_ID \
--issuer $ASC_KEY_ISSUER_ID
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
env:
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}
PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}
ASC_KEY: ${{ secrets.ASC_KEY }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Build library
if: matrix.if
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
mv Libbox.xcframework clients/apple
- name: Update macOS version
if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
run: |-
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
- name: Build
if: matrix.if
run: |-
go run -v ./cmd/internal/update_apple_version --ci
cd clients/apple
xcodebuild archive \
-scheme "${{ matrix.scheme }}" \
-configuration Release \
-destination "${{ matrix.destination }}" \
-archivePath "${{ matrix.archive }}" \
-allowProvisioningUpdates \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
- name: Upload to App Store Connect
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
cd clients/apple
xcodebuild -exportArchive \
-archivePath "${{ matrix.archive }}" \
-exportOptionsPlist ${{ matrix.upload }} \
-allowProvisioningUpdates \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
- name: Publish to TestFlight
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
run: |-
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
- name: Build image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
pushd clients/apple
xcodebuild -exportArchive \
-archivePath "${{ matrix.archive }}" \
-exportOptionsPlist ${{ matrix.export }} \
-exportPath "${{ matrix.export_path }}"
brew install create-dmg
create-dmg \
--volname "sing-box" \
--volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \
--icon "SFM.app" 0 0 \
--hide-extension "SFM.app" \
--app-drop-link 0 0 \
--skip-jenkins \
SFM.dmg "${{ matrix.export_path }}/SFM.app"
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
cd "${{ matrix.archive }}"
zip -r SFM.dSYMs.zip dSYMs
popd
mkdir -p dist/release
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
- name: Upload image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-macos-dmg
path: 'dist'
upload:
name: Upload builds
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
runs-on: ubuntu-latest
needs:
- calculate_version
- build
- build_android
- build_apple
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
install-only: true
- name: Cache ghr
uses: actions/cache@v4
id: cache-ghr
with:
path: |
~/go/bin/ghr
key: ghr
- name: Setup ghr
if: steps.cache-ghr.outputs.cache-hit != 'true'
run: |-
cd $HOME
git clone https://github.com/nekohasekai/ghr ghr
cd ghr
go install -v .
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Merge builds
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
run: |-
goreleaser continue --merge --skip publish
mkdir -p dist/release
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: Upload builds
if: ${{ env.PUBLISHED == 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Replace builds
if: ${{ env.PUBLISHED != 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace -p 5 "v${VERSION}" dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

219
.github/workflows/debug.yml vendored Normal file
View File

@@ -0,0 +1,219 @@
name: Debug build
on:
push:
branches:
- stable-next
- main-next
- dev-next
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/debug.yml'
pull_request:
branches:
- stable-next
- main-next
- dev-next
jobs:
build:
name: Debug build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Run Test
run: |
go test -v ./...
build_go120:
name: Debug build (Go 1.20)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.20
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
key: go120-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make ci_build_go120
build_go121:
name: Debug build (Go 1.21)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.21
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
key: go121-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make ci_build
build_go122:
name: Debug build (Go 1.22)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.22
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
key: go122-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make ci_build
cross:
strategy:
matrix:
include:
# windows
- name: windows-amd64
goos: windows
goarch: amd64
goamd64: v1
- name: windows-amd64-v3
goos: windows
goarch: amd64
goamd64: v3
- name: windows-386
goos: windows
goarch: 386
- name: windows-arm64
goos: windows
goarch: arm64
- name: windows-arm32v7
goos: windows
goarch: arm
goarm: 7
# linux
- name: linux-amd64
goos: linux
goarch: amd64
goamd64: v1
- name: linux-amd64-v3
goos: linux
goarch: amd64
goamd64: v3
- name: linux-386
goos: linux
goarch: 386
- name: linux-arm64
goos: linux
goarch: arm64
- name: linux-armv5
goos: linux
goarch: arm
goarm: 5
- name: linux-armv6
goos: linux
goarch: arm
goarm: 6
- name: linux-armv7
goos: linux
goarch: arm
goarm: 7
- name: linux-mips-softfloat
goos: linux
goarch: mips
gomips: softfloat
- name: linux-mips-hardfloat
goos: linux
goarch: mips
gomips: hardfloat
- name: linux-mipsel-softfloat
goos: linux
goarch: mipsle
gomips: softfloat
- name: linux-mipsel-hardfloat
goos: linux
goarch: mipsle
gomips: hardfloat
- name: linux-mips64
goos: linux
goarch: mips64
- name: linux-mips64el
goos: linux
goarch: mips64le
- name: linux-s390x
goos: linux
goarch: s390x
# darwin
- name: darwin-amd64
goos: darwin
goarch: amd64
goamd64: v1
- name: darwin-amd64-v3
goos: darwin
goarch: amd64
goamd64: v3
- name: darwin-arm64
goos: darwin
goarch: arm64
# freebsd
- name: freebsd-amd64
goos: freebsd
goarch: amd64
goamd64: v1
- name: freebsd-amd64-v3
goos: freebsd
goarch: amd64
goamd64: v3
- name: freebsd-386
goos: freebsd
goarch: 386
- name: freebsd-arm64
goos: freebsd
goarch: arm64
fail-fast: true
runs-on: ubuntu-latest
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOAMD64: ${{ matrix.goamd64 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
CGO_ENABLED: 0
TAGS: with_clash_api,with_quic
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.21
- name: Build
id: build
run: make

View File

@@ -22,6 +22,7 @@ jobs:
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
echo "HOME=$HOME" >> "$GITHUB_ENV"
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Publish release

View File

@@ -22,16 +22,6 @@ linters-settings:
run:
go: "1.23"
build-tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
issues:
exclude-dirs:

View File

@@ -200,6 +200,4 @@ release:
ids:
- archive
- package
skip_upload: true
partial:
by: target
skip_upload: true

View File

@@ -28,7 +28,7 @@ ci_build:
go build $(MAIN_PARAMS) $(MAIN)
generate_completions:
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
go run -v --tags generate,generate_completions $(MAIN)
install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
@@ -71,7 +71,7 @@ release:
dist/*_amd64.pkg.tar.zst \
dist/*_arm64.pkg.tar.zst \
dist/release
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
rm -r dist/release
release_repo:
@@ -90,7 +90,7 @@ upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
rm -rf dist/release_android
release_android: lib_android update_android_version build_android upload_android
@@ -99,11 +99,9 @@ publish_android:
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:
@@ -182,22 +180,10 @@ release_tvos: build_tvos upload_tvos_app_store
update_apple_version:
go run ./cmd/internal/update_apple_version
update_macos_version:
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
release_apple_beta: update_apple_version release_ios release_macos release_tvos
publish_testflight:
go run -v ./cmd/internal/app_store_connect publish_testflight
prepare_app_store:
go run -v ./cmd/internal/app_store_connect prepare_app_store
publish_app_store:
go run -v ./cmd/internal/app_store_connect publish_app_store
test:
@go test -v ./... && \
cd test && \
@@ -213,14 +199,8 @@ test_stdio:
lib_android:
go run ./cmd/internal/build_libbox -target android
lib_android_debug:
go run ./cmd/internal/build_libbox -target android -debug
lib_apple:
go run ./cmd/internal/build_libbox -target apple
lib_ios:
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
go run ./cmd/internal/build_libbox -target ios
lib:
go run ./cmd/internal/build_libbox -target android

View File

@@ -1,14 +0,0 @@
package adapter
import (
"context"
"net"
N "github.com/sagernet/sing/common/network"
)
type ConnectionManager interface {
Lifecycle
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}

View File

@@ -1,28 +0,0 @@
package adapter
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type Endpoint interface {
Lifecycle
Type() string
Tag() string
Outbound
}
type EndpointRegistry interface {
option.EndpointOptionsRegistry
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
}
type EndpointManager interface {
Lifecycle
Endpoints() []Endpoint
Get(tag string) (Endpoint, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
}

View File

@@ -1,43 +0,0 @@
package endpoint
import "github.com/sagernet/sing-box/option"
type Adapter struct {
endpointType string
endpointTag string
network []string
dependencies []string
}
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
return Adapter{
endpointType: endpointType,
endpointTag: endpointTag,
network: network,
dependencies: dependencies,
}
}
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
var dependencies []string
if dialOptions.Detour != "" {
dependencies = []string{dialOptions.Detour}
}
return NewAdapter(endpointType, endpointTag, network, dependencies)
}
func (a *Adapter) Type() string {
return a.endpointType
}
func (a *Adapter) Tag() string {
return a.endpointTag
}
func (a *Adapter) Network() []string {
return a.network
}
func (a *Adapter) Dependencies() []string {
return a.dependencies
}

View File

@@ -1,147 +0,0 @@
package endpoint
import (
"context"
"os"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ adapter.EndpointManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.EndpointRegistry
access sync.Mutex
started bool
stage adapter.StartStage
endpoints []adapter.Endpoint
endpointByTag map[string]adapter.Endpoint
}
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpointByTag: make(map[string]adapter.Endpoint),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
defer m.access.Unlock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
if stage == adapter.StartStateStart {
// started with outbound manager
return nil
}
for _, endpoint := range m.endpoints {
err := adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
}
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
endpoints := m.endpoints
m.endpoints = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, endpoint := range endpoints {
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
err = E.Append(err, endpoint.Close(), func(err error) error {
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
})
monitor.Finish()
}
return nil
}
func (m *Manager) Endpoints() []adapter.Endpoint {
m.access.Lock()
defer m.access.Unlock()
return m.endpoints
}
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
m.access.Lock()
defer m.access.Unlock()
endpoint, found := m.endpointByTag[tag]
return endpoint, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
endpoint, found := m.endpointByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.endpointByTag, tag)
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
return it == endpoint
})
if index == -1 {
panic("invalid endpoint index")
}
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return endpoint.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
}
}
}
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
if m.started {
err = existsEndpoint.Close()
if err != nil {
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
}
}
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
return it == existsEndpoint
})
if existsIndex == -1 {
panic("invalid endpoint index")
}
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
}
m.endpoints = append(m.endpoints, endpoint)
m.endpointByTag[tag] = endpoint
return nil
}

View File

@@ -1,72 +0,0 @@
package endpoint
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.EndpointRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

View File

@@ -46,9 +46,6 @@ type PacketConnectionHandlerEx interface {
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
// Deprecated: use TCPConnectionHandlerEx instead
//
//nolint:staticcheck
type UpstreamHandlerAdapter interface {
N.TCPConnectionHandler
N.UDPConnectionHandler

View File

@@ -13,7 +13,7 @@ import (
)
type Inbound interface {
Lifecycle
Service
Type() string
Tag() string
}
@@ -65,17 +65,14 @@ type InboundContext struct {
LastInbound string
OriginDestination M.Socksaddr
RouteOriginalDestination M.Socksaddr
// Deprecated: to be removed
//nolint:staticcheck
// Deprecated
InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool
UDPConnect bool
UDPTimeout time.Duration
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
NetworkStrategy C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
DNSServer string

View File

@@ -18,7 +18,6 @@ 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
@@ -26,11 +25,10 @@ type Manager struct {
inboundByTag map[string]adapter.Inbound
}
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpoint: endpoint,
inboundByTag: make(map[string]adapter.Inbound),
}
}
@@ -81,12 +79,9 @@ 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]
m.access.Unlock()
if found {
return inbound, true
}
return m.endpoint.Get(tag)
return inbound, found
}
func (m *Manager) Remove(tag string) error {

View File

@@ -15,12 +15,8 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) {
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
})
}

View File

@@ -1,9 +1,6 @@
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 {

View File

@@ -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

View File

@@ -5,35 +5,35 @@ import (
)
type Adapter struct {
outboundType string
outboundTag string
protocol string
network []string
tag string
dependencies []string
}
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter {
return Adapter{
outboundType: outboundType,
outboundTag: outboundTag,
protocol: protocol,
network: network,
tag: tag,
dependencies: dependencies,
}
}
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter {
var dependencies []string
if dialOptions.Detour != "" {
dependencies = []string{dialOptions.Detour}
}
return NewAdapter(outboundType, outboundTag, network, dependencies)
return NewAdapter(protocol, network, tag, dependencies)
}
func (a *Adapter) Type() string {
return a.outboundType
return a.protocol
}
func (a *Adapter) Tag() string {
return a.outboundTag
return a.tag
}
func (a *Adapter) Network() []string {

157
adapter/outbound/default.go Normal file
View File

@@ -0,0 +1,157 @@
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 {
outConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} 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 {
outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} 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() {
var originDestination M.Socksaddr
if metadata.RouteOriginalDestination.IsValid() {
originDestination = metadata.RouteOriginalDestination
} else {
originDestination = metadata.Destination
}
if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
if metadata.UDPDisableDomainUnmapping {
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
} else {
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
}
}
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)
}

View File

@@ -21,7 +21,6 @@ var _ adapter.OutboundManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.OutboundRegistry
endpoint adapter.EndpointManager
defaultTag string
access sync.Mutex
started bool
@@ -33,11 +32,10 @@ type Manager struct {
defaultOutboundFallback adapter.Outbound
}
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, defaultTag string) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpoint: endpoint,
defaultTag: defaultTag,
outboundByTag: make(map[string]adapter.Outbound),
dependByTag: make(map[string][]string),
@@ -58,14 +56,7 @@ func (m *Manager) Start(stage adapter.StartStage) error {
outbounds := m.outbounds
m.access.Unlock()
if stage == adapter.StartStateStart {
if m.defaultTag != "" && m.defaultOutbound == nil {
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
if !loaded {
return E.New("default outbound not found: ", m.defaultTag)
}
m.defaultOutbound = defaultEndpoint
}
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
return m.startOutbounds(outbounds)
} else {
for _, outbound := range outbounds {
err := adapter.LegacyStart(outbound, stage)
@@ -96,14 +87,7 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
}
started[outboundTag] = true
canContinue = true
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start(adapter.StartStateStart)
monitor.Finish()
if err != nil {
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
} else if starter, isStarter := outboundToStart.(interface {
if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
@@ -176,12 +160,9 @@ 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]
m.access.Unlock()
if found {
return outbound, true
}
return m.endpoint.Get(tag)
return outbound, found
}
func (m *Manager) Default() adapter.Outbound {

View File

@@ -15,12 +15,8 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) {
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
})
}

View File

@@ -18,8 +18,6 @@ type (
)
// Deprecated
//
//nolint:staticcheck
func NewUpstreamHandler(
metadata InboundContext,
connectionHandler ConnectionHandlerFunc,
@@ -36,9 +34,7 @@ func NewUpstreamHandler(
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
// Deprecated: use myUpstreamHandlerWrapperEx instead.
//
//nolint:staticcheck
// Deprecated
type myUpstreamHandlerWrapper struct {
metadata InboundContext
connectionHandler ConnectionHandlerFunc
@@ -46,7 +42,6 @@ type myUpstreamHandlerWrapper struct {
errorHandler E.Handler
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
@@ -58,7 +53,6 @@ func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.C
return w.connectionHandler(ctx, conn, myMetadata)
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
@@ -70,12 +64,11 @@ func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn
return w.packetHandler(ctx, conn, myMetadata)
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated: removed
// Deprecated
func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{
Source: metadata.Source,
@@ -83,14 +76,14 @@ func UpstreamMetadata(metadata InboundContext) M.Metadata {
}
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
// Deprecated
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
// Deprecated
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
@@ -103,7 +96,6 @@ func NewUpstreamContextHandler(
}
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
@@ -115,7 +107,6 @@ func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, con
return w.connectionHandler(ctx, conn, *myMetadata)
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
@@ -127,7 +118,6 @@ func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Contex
return w.packetHandler(ctx, conn, *myMetadata)
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
@@ -159,15 +149,12 @@ func NewRouteContextHandler(
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
//
//nolint:staticcheck
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
@@ -179,7 +166,6 @@ func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn,
return w.router.RouteConnection(ctx, conn, myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
@@ -191,7 +177,6 @@ func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.Pa
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
@@ -204,7 +189,6 @@ type routeContextHandlerWrapper struct {
logger logger.ContextLogger
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
@@ -216,7 +200,6 @@ func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net
return w.router.RouteConnection(ctx, conn, *myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
@@ -228,7 +211,6 @@ func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, co
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

100
box.go
View File

@@ -9,13 +9,10 @@ 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"
@@ -39,11 +36,9 @@ type Box struct {
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
endpoint *endpoint.Manager
router *route.Router
inbound *inbound.Manager
outbound *outbound.Manager
connection *route.ConnectionManager
router *route.Router
services []adapter.LifecycleService
done chan struct{}
}
@@ -58,7 +53,6 @@ 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 {
@@ -70,11 +64,6 @@ func Context(
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
}
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
}
return ctx
}
@@ -85,26 +74,20 @@ 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")
}
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
if inboundRegistry == nil {
return nil, E.New("missing inbound registry in context")
}
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
debugOptions := common.PtrValueOrDefault(experimentalOptions.Debug)
applyDebugOptions(debugOptions)
ctx = conntrack.ContextWithDefaultTracker(ctx, debugOptions.OOMKiller, uint64(debugOptions.MemoryLimit))
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needCacheFile bool
var needClashAPI bool
var needV2RayAPI bool
@@ -135,10 +118,8 @@ func New(options Options) (*Box, error) {
}
routeOptions := common.PtrValueOrDefault(options.Route)
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final)
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
@@ -147,36 +128,28 @@ 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")
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
}
for i, endpointOptions := range options.Endpoints {
var tag string
if endpointOptions.Tag != "" {
tag = endpointOptions.Tag
} else {
tag = F.ToString(i)
//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()),
})
}
err = endpointManager.Create(ctx,
router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag,
endpointOptions.Type,
endpointOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize inbound[", i, "]")
}
//nolint:staticcheck
if len(options.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, inboundOptions := range options.Inbounds {
@@ -266,12 +239,13 @@ 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")
}
ntpService := ntp.NewService(ntp.Options{
timeService := ntp.NewService(ntp.Options{
Context: ctx,
Dialer: ntpDialer,
Logger: logFactory.NewLogger("ntp"),
@@ -279,16 +253,14 @@ func New(options Options) (*Box, error) {
Interval: time.Duration(ntpOptions.Interval),
WriteToSystem: ntpOptions.WriteToSystem,
})
timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
service.MustRegister[ntp.TimeService](ctx, timeService)
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
}
return &Box{
network: networkManager,
endpoint: endpointManager,
router: router,
inbound: inboundManager,
outbound: outboundManager,
connection: connectionManager,
router: router,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
@@ -347,11 +319,11 @@ func (s *Box) preStart() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router)
if err != nil {
return err
}
@@ -371,11 +343,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.endpoint)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound)
if err != nil {
return err
}
@@ -383,7 +351,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound)
if err != nil {
return err
}
@@ -402,7 +370,7 @@ func (s *Box) Close() error {
close(s.done)
}
err := common.Close(
s.inbound, s.outbound, s.router, s.connection, s.network,
s.inbound, s.outbound, s.router, s.network,
)
for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -1,445 +0,0 @@
package main
import (
"context"
"net/http"
"os"
"strconv"
"time"
"github.com/sagernet/asc-go/asc"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
func main() {
ctx := context.Background()
switch os.Args[1] {
case "next_macos_project_version":
err := fetchMacOSVersion(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_testflight":
err := publishTestflight(ctx)
if err != nil {
log.Fatal(err)
}
case "cancel_app_store":
err := cancelAppStore(ctx, os.Args[2])
if err != nil {
log.Fatal(err)
}
case "prepare_app_store":
err := prepareAppStore(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_app_store":
err := publishAppStore(ctx)
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("unknown action: ", os.Args[1])
}
}
const (
appID = "6673731168"
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
)
func createClient(expireDuration time.Duration) *asc.Client {
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
if err != nil {
log.Fatal(err)
}
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
if err != nil {
log.Fatal(err)
}
return asc.NewClient(tokenConfig.Client())
}
func fetchMacOSVersion(ctx context.Context) error {
client := createClient(time.Minute)
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{"MAC_OS"},
})
if err != nil {
return err
}
var versionID string
findVersion:
for _, version := range versions.Data {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStateReadyForSale,
asc.AppStoreVersionStatePendingDeveloperRelease:
versionID = version.ID
break findVersion
}
}
if versionID == "" {
return E.New("no version found")
}
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
if err != nil {
return err
}
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
if err != nil {
return E.Cause(err, "parse version code")
}
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
return nil
}
func publishTestflight(ctx context.Context) error {
tagVersion, err := build_shared.ReadTagVersion()
if err != nil {
return err
}
tag := tagVersion.VersionString()
client := createClient(10 * time.Minute)
log.Info(tag, " list build IDs")
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
if err != nil {
return err
}
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
return it.ID
})
var platforms []asc.Platform
if len(os.Args) == 3 {
switch os.Args[2] {
case "ios":
platforms = []asc.Platform{asc.PlatformIOS}
case "macos":
platforms = []asc.Platform{asc.PlatformMACOS}
case "tvos":
platforms = []asc.Platform{asc.PlatformTVOS}
default:
return E.New("unknown platform: ", os.Args[2])
}
} else {
platforms = []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
}
}
for _, platform := range platforms {
log.Info(string(platform), " list builds")
for {
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
build := builds.Data[0]
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
log.Info(string(platform), " ", tag, " waiting for process")
time.Sleep(15 * time.Second)
continue
}
if *build.Attributes.ProcessingState != "VALID" {
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
time.Sleep(15 * time.Second)
continue
}
log.Info(string(platform), " ", tag, " list localizations")
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Fatal(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
F.ToString("sing-box ", tagVersion.String()),
))
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
log.Info("waiting for process")
time.Sleep(15 * time.Second)
continue
} else if err != nil {
return err
}
log.Info(string(platform), " ", tag, " list submissions")
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
FilterBuild: []string{build.ID},
})
if err != nil {
return err
}
if len(betaSubmissions.Data) == 0 {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil {
return err
}
}
break
}
}
return nil
}
func cancelAppStore(ctx context.Context, platform string) error {
switch platform {
case "ios":
platform = string(asc.PlatformIOS)
case "macos":
platform = string(asc.PlatformMACOS)
case "tvos":
platform = string(asc.PlatformTVOS)
}
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for {
log.Info(platform, " list versions")
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if isRetryable(response) {
continue
} else if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
if version.ID == "" {
return nil
}
log.Info(platform, " ", tag, " get submission")
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if response != nil && response.StatusCode == http.StatusNotFound {
return nil
}
if isRetryable(response) {
continue
} else if err != nil {
return err
}
log.Info(platform, " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
return nil
}
}
func prepareAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
log.Info(string(platform), " ", tag, " list builds")
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
if len(builds.Data) == 0 {
log.Fatal(platform, " ", tag, " no build found")
}
buildID := common.Ptr(builds.Data[0].ID)
if version.ID == "" {
log.Info(string(platform), " ", tag, " create version")
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
Platform: platform,
VersionString: tag,
}, appID, buildID)
if err != nil {
return err
}
version = newVersion.Data
} else {
log.Info(string(platform), " ", tag, " check build")
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
if submission != nil {
log.Info(string(platform), " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
time.Sleep(5 * time.Second)
}
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
log.Info(string(platform), " ", tag, " update build")
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
if err != nil {
return err
}
if response.StatusCode != http.StatusNoContent {
response.Write(os.Stderr)
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
}
} else {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
continue
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
}
}
log.Info(string(platform), " ", tag, " list localization")
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Info(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
})
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " create submission")
fixSubmit:
for {
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
if err != nil {
switch response.StatusCode {
case http.StatusInternalServerError:
continue
default:
return err
}
}
switch response.StatusCode {
case http.StatusCreated:
break fixSubmit
default:
return err
}
}
}
return nil
}
func publishAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
log.Fatal(string(platform), " ", tag, " not submitted")
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview:
log.Warn(string(platform), " ", tag, " waiting for review")
continue
case asc.AppStoreVersionStatePendingDeveloperRelease:
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
if err != nil {
return err
}
}
return nil
}
func isRetryable(response *asc.Response) bool {
if response == nil {
return false
}
switch response.StatusCode {
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
return true
default:
return false
}
}

View File

@@ -10,21 +10,17 @@ import (
_ "github.com/sagernet/gomobile"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/shell"
)
var (
debugEnabled bool
target string
platform string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
flag.StringVar(&platform, "platform", "", "specify platform")
}
func main() {
@@ -35,8 +31,8 @@ func main() {
switch target {
case "android":
buildAndroid()
case "apple":
buildApple()
case "ios":
buildiOS()
}
}
@@ -66,35 +62,9 @@ func init() {
func buildAndroid() {
build_shared.FindSDK()
var javaPath string
javaHome := os.Getenv("JAVA_HOME")
if javaHome == "" {
javaPath = "java"
} else {
javaPath = filepath.Join(javaHome, "bin", "java")
}
javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput()
if err != nil {
log.Fatal(E.Cause(err, "check java version"))
}
if !strings.Contains(javaVersion, "openjdk 17") {
log.Fatal("java version should be openjdk 17")
}
var bindTarget string
if platform != "" {
bindTarget = platform
} else if debugEnabled {
bindTarget = "android/arm64"
} else {
bindTarget = "android"
}
args := []string{
"bind",
"-v",
"-target", bindTarget,
"-androidapi", "21",
"-javapkg=io.nekohasekai",
"-libname=box",
@@ -116,7 +86,7 @@ func buildAndroid() {
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err = command.Run()
err := command.Run()
if err != nil {
log.Fatal(err)
}
@@ -133,20 +103,11 @@ func buildAndroid() {
}
}
func buildApple() {
var bindTarget string
if platform != "" {
bindTarget = platform
} else if debugEnabled {
bindTarget = "ios"
} else {
bindTarget = "ios,tvos,macos"
}
func buildiOS() {
args := []string{
"bind",
"-v",
"-target", bindTarget,
"-target", "ios,iossimulator,tvos,tvossimulator,macos",
"-libname=box",
}
if !debugEnabled {

View File

@@ -11,7 +11,9 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/shell"
)
var (
@@ -40,6 +42,14 @@ func FindSDK() {
log.Fatal("android NDK not found")
}
javaVersion, err := shell.Exec("java", "--version").ReadOutput()
if err != nil {
log.Fatal(E.Cause(err, "check java version"))
}
if !strings.Contains(javaVersion, "openjdk 17") {
log.Fatal("java version should be openjdk 17")
}
os.Setenv("ANDROID_HOME", androidSDKPath)
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
@@ -48,16 +58,12 @@ func FindSDK() {
}
func findNDK() bool {
const fixedVersion = "28.0.12674087"
const fixedVersion = "26.2.11394342"
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

View File

@@ -20,11 +20,6 @@ func ReadTag() (string, error) {
return version.String() + "-" + shortCommit, nil
}
func ReadTagVersionRev() (badversion.Version, error) {
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
return badversion.Parse(currentTagRev[1:]), nil
}
func ReadTagVersion() (badversion.Version, error) {
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())

View File

@@ -1,62 +1,21 @@
package main
import (
"flag"
"os"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
)
var nightly bool
func init() {
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
}
func main() {
flag.Parse()
if nightly {
version, err := build_shared.ReadTagVersionRev()
if err != nil {
log.Fatal(err)
}
var versionStr string
if version.PreReleaseIdentifier != "" {
versionStr = version.VersionString() + "-nightly"
} else {
version.Patch++
versionStr = version.VersionString() + "-nightly"
}
err = setGitHubEnv("version", versionStr)
if err != nil {
log.Fatal(err)
}
currentTag, err := build_shared.ReadTag()
if err != nil {
log.Error(err)
_, err = os.Stdout.WriteString("unknown\n")
} else {
tag, err := build_shared.ReadTag()
if err != nil {
log.Error(err)
os.Stdout.WriteString("unknown\n")
} else {
os.Stdout.WriteString(tag + "\n")
}
_, err = os.Stdout.WriteString(currentTag + "\n")
}
if err != nil {
log.Error(err)
}
}
func setGitHubEnv(name string, value string) error {
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err
}
_, err = outputFile.WriteString(name + "=" + value + "\n")
if err != nil {
outputFile.Close()
return err
}
err = outputFile.Close()
if err != nil {
return err
}
os.Stderr.WriteString(name + "=" + value + "\n")
return nil
}

View File

@@ -1,7 +1,6 @@
package main
import (
"flag"
"os"
"path/filepath"
"runtime"
@@ -13,22 +12,9 @@ import (
"github.com/sagernet/sing/common"
)
var flagRunInCI bool
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
}
func main() {
flag.Parse()
newVersion := common.Must1(build_shared.ReadTag())
var androidPath string
if flagRunInCI {
androidPath = "clients/android"
} else {
androidPath = "../sing-box-for-android"
}
androidPath, err := filepath.Abs(androidPath)
newVersion := common.Must1(build_shared.ReadTagVersion())
androidPath, err := filepath.Abs("../sing-box-for-android")
if err != nil {
log.Fatal(err)
}
@@ -45,10 +31,10 @@ func main() {
for _, propPair := range propsList {
switch propPair[0] {
case "VERSION_NAME":
if propPair[1] != newVersion {
if propPair[1] != newVersion.String() {
versionUpdated = true
propPair[1] = newVersion
log.Info("updated version to ", newVersion)
propPair[1] = newVersion.String()
log.Info("updated version to ", newVersion.String())
}
case "GO_VERSION":
if propPair[1] != runtime.Version() {

View File

@@ -1,7 +1,6 @@
package main
import (
"flag"
"os"
"path/filepath"
"regexp"
@@ -14,22 +13,9 @@ import (
"howett.net/plist"
)
var flagRunInCI bool
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
}
func main() {
flag.Parse()
newVersion := common.Must1(build_shared.ReadTagVersion())
var applePath string
if flagRunInCI {
applePath = "clients/apple"
} else {
applePath = "../sing-box-for-apple"
}
applePath, err := filepath.Abs(applePath)
applePath, err := filepath.Abs("../sing-box-for-apple")
if err != nil {
log.Fatal(err)
}

View File

@@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) {
configPaths = append(configPaths, "config.json")
}
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
}

View File

@@ -18,7 +18,7 @@ import (
)
var commandMerge = &cobra.Command{
Use: "merge <output-path>",
Use: "merge <output>",
Short: "Merge configurations",
Run: func(cmd *cobra.Command, args []string) {
err := merge(args[0])

View File

@@ -1,162 +0,0 @@
package main
import (
"bytes"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/rw"
"github.com/spf13/cobra"
)
var (
ruleSetPaths []string
ruleSetDirectories []string
)
var commandRuleSetMerge = &cobra.Command{
Use: "merge <output-path>",
Short: "Merge rule-set source files",
Run: func(cmd *cobra.Command, args []string) {
err := mergeRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
Args: cobra.ExactArgs(1),
}
func init() {
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path")
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path")
commandRuleSet.AddCommand(commandRuleSetMerge)
}
type RuleSetEntry struct {
content []byte
path string
options option.PlainRuleSetCompat
}
func readRuleSetAt(path string) (*RuleSetEntry, error) {
var (
configContent []byte
err error
)
if path == "stdin" {
configContent, err = io.ReadAll(os.Stdin)
} else {
configContent, err = os.ReadFile(path)
}
if err != nil {
return nil, E.Cause(err, "read config at ", path)
}
options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)
if err != nil {
return nil, E.Cause(err, "decode config at ", path)
}
return &RuleSetEntry{
content: configContent,
path: path,
options: options,
}, nil
}
func readRuleSet() ([]*RuleSetEntry, error) {
var optionsList []*RuleSetEntry
for _, path := range ruleSetPaths {
optionsEntry, err := readRuleSetAt(path)
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
for _, directory := range ruleSetDirectories {
entries, err := os.ReadDir(directory)
if err != nil {
return nil, E.Cause(err, "read rule-set directory at ", directory)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
continue
}
optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name()))
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
}
sort.Slice(optionsList, func(i, j int) bool {
return optionsList[i].path < optionsList[j].path
})
return optionsList, nil
}
func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {
optionsList, err := readRuleSet()
if err != nil {
return option.PlainRuleSetCompat{}, err
}
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var optionVersion uint8
for _, options := range optionsList {
if optionVersion < options.options.Version {
optionVersion = options.options.Version
}
}
var mergedMessage json.RawMessage
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
if err != nil {
return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path)
}
}
mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)
if err != nil {
return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config")
}
mergedOptions.Version = optionVersion
return mergedOptions, nil
}
func mergeRuleSet(outputPath string) error {
mergedOptions, err := readRuleSetAndMerge()
if err != nil {
return err
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(mergedOptions)
if err != nil {
return E.Cause(err, "encode config")
}
if existsContent, err := os.ReadFile(outputPath); err != nil {
if string(existsContent) == buffer.String() {
return nil
}
}
err = rw.MkdirParent(outputPath)
if err != nil {
return err
}
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
if err != nil {
return err
}
outputPath, _ = filepath.Abs(outputPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}

View File

@@ -30,7 +30,7 @@ func createPreStartedClient() (*box.Box, error) {
return nil, err
}
}
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
instance, err := box.New(box.Options{Options: options})
if err != nil {
return nil, E.Cause(err, "create service")
}

View File

@@ -21,7 +21,7 @@ func initializeHTTP3Client(instance *box.Box) error {
return err
}
http3Client = &http.Client{
Transport: &http3.Transport{
Transport: &http3.RoundTripper{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
destination := M.ParseSocksaddr(addr)
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)

54
common/conntrack/conn.go Normal file
View File

@@ -0,0 +1,54 @@
package conntrack
import (
"io"
"net"
"github.com/sagernet/sing/common/x/list"
)
type Conn struct {
net.Conn
element *list.Element[io.Closer]
}
func NewConn(conn net.Conn) (net.Conn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()
if KillerEnabled {
err := KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
}
return &Conn{
Conn: conn,
element: element,
}, nil
}
func (c *Conn) Close() error {
if c.element.Value != nil {
connAccess.Lock()
if c.element.Value != nil {
openConnection.Remove(c.element)
c.element.Value = nil
}
connAccess.Unlock()
}
return c.Conn.Close()
}
func (c *Conn) Upstream() any {
return c.Conn
}
func (c *Conn) ReaderReplaceable() bool {
return true
}
func (c *Conn) WriterReplaceable() bool {
return true
}

View File

@@ -1,14 +0,0 @@
package conntrack
import (
"context"
"github.com/sagernet/sing/service"
)
func ContextWithDefaultTracker(ctx context.Context, killerEnabled bool, memoryLimit uint64) context.Context {
if service.FromContext[Tracker](ctx) != nil {
return ctx
}
return service.ContextWith[Tracker](ctx, NewDefaultTracker(killerEnabled, memoryLimit))
}

View File

@@ -1,245 +0,0 @@
package conntrack
import (
"net"
"net/netip"
runtimeDebug "runtime/debug"
"sync"
"time"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
)
var _ Tracker = (*DefaultTracker)(nil)
type DefaultTracker struct {
connAccess sync.RWMutex
connList list.List[net.Conn]
connAddress map[netip.AddrPort]netip.AddrPort
packetConnAccess sync.RWMutex
packetConnList list.List[AbstractPacketConn]
packetConnAddress map[netip.AddrPort]bool
pendingAccess sync.RWMutex
pendingList list.List[netip.AddrPort]
killerEnabled bool
memoryLimit uint64
killerLastCheck time.Time
}
func NewDefaultTracker(killerEnabled bool, memoryLimit uint64) *DefaultTracker {
return &DefaultTracker{
connAddress: make(map[netip.AddrPort]netip.AddrPort),
packetConnAddress: make(map[netip.AddrPort]bool),
killerEnabled: killerEnabled,
memoryLimit: memoryLimit,
}
}
func (t *DefaultTracker) NewConn(conn net.Conn) (net.Conn, error) {
err := t.KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
t.connAccess.Lock()
element := t.connList.PushBack(conn)
t.connAddress[M.AddrPortFromNet(conn.LocalAddr())] = M.AddrPortFromNet(conn.RemoteAddr())
t.connAccess.Unlock()
return &Conn{
Conn: conn,
closeFunc: common.OnceFunc(func() {
t.removeConn(element)
}),
}, nil
}
func (t *DefaultTracker) NewConnEx(conn net.Conn) (N.CloseHandlerFunc, error) {
err := t.KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
t.connAccess.Lock()
element := t.connList.PushBack(conn)
t.connAddress[M.AddrPortFromNet(conn.LocalAddr())] = M.AddrPortFromNet(conn.RemoteAddr())
t.connAccess.Unlock()
return N.OnceClose(func(it error) {
t.removeConn(element)
}), nil
}
func (t *DefaultTracker) NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
err := t.KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
t.packetConnAccess.Lock()
element := t.packetConnList.PushBack(conn)
t.packetConnAddress[M.AddrPortFromNet(conn.LocalAddr())] = true
t.packetConnAccess.Unlock()
return &PacketConn{
PacketConn: conn,
closeFunc: common.OnceFunc(func() {
t.removePacketConn(element)
}),
}, nil
}
func (t *DefaultTracker) NewPacketConnEx(conn AbstractPacketConn) (N.CloseHandlerFunc, error) {
err := t.KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
t.packetConnAccess.Lock()
element := t.packetConnList.PushBack(conn)
t.packetConnAddress[M.AddrPortFromNet(conn.LocalAddr())] = true
t.packetConnAccess.Unlock()
return N.OnceClose(func(it error) {
t.removePacketConn(element)
}), nil
}
func (t *DefaultTracker) CheckConn(source netip.AddrPort, destination netip.AddrPort) bool {
t.connAccess.RLock()
defer t.connAccess.RUnlock()
return t.connAddress[source] == destination
}
func (t *DefaultTracker) CheckPacketConn(source netip.AddrPort) bool {
t.packetConnAccess.RLock()
defer t.packetConnAccess.RUnlock()
return t.packetConnAddress[source]
}
func (t *DefaultTracker) AddPendingDestination(destination netip.AddrPort) func() {
t.pendingAccess.Lock()
defer t.pendingAccess.Unlock()
element := t.pendingList.PushBack(destination)
return func() {
t.pendingAccess.Lock()
defer t.pendingAccess.Unlock()
t.pendingList.Remove(element)
}
}
func (t *DefaultTracker) CheckDestination(destination netip.AddrPort) bool {
t.pendingAccess.RLock()
defer t.pendingAccess.RUnlock()
for element := t.pendingList.Front(); element != nil; element = element.Next() {
if element.Value == destination {
return true
}
}
return false
}
func (t *DefaultTracker) KillerCheck() error {
if !t.killerEnabled {
return nil
}
nowTime := time.Now()
if nowTime.Sub(t.killerLastCheck) < 3*time.Second {
return nil
}
t.killerLastCheck = nowTime
if memory.Total() > t.memoryLimit {
t.Close()
go func() {
time.Sleep(time.Second)
runtimeDebug.FreeOSMemory()
}()
return E.New("out of memory")
}
return nil
}
func (t *DefaultTracker) Count() int {
t.connAccess.RLock()
defer t.connAccess.RUnlock()
t.packetConnAccess.RLock()
defer t.packetConnAccess.RUnlock()
return t.connList.Len() + t.packetConnList.Len()
}
func (t *DefaultTracker) Close() {
t.connAccess.Lock()
for element := t.connList.Front(); element != nil; element = element.Next() {
element.Value.Close()
}
t.connList.Init()
t.connAccess.Unlock()
t.packetConnAccess.Lock()
for element := t.packetConnList.Front(); element != nil; element = element.Next() {
element.Value.Close()
}
t.packetConnList.Init()
t.packetConnAccess.Unlock()
}
func (t *DefaultTracker) removeConn(element *list.Element[net.Conn]) {
t.connAccess.Lock()
defer t.connAccess.Unlock()
delete(t.connAddress, M.AddrPortFromNet(element.Value.LocalAddr()))
t.connList.Remove(element)
}
func (t *DefaultTracker) removePacketConn(element *list.Element[AbstractPacketConn]) {
t.packetConnAccess.Lock()
defer t.packetConnAccess.Unlock()
delete(t.packetConnAddress, M.AddrPortFromNet(element.Value.LocalAddr()))
t.packetConnList.Remove(element)
}
type Conn struct {
net.Conn
closeFunc func()
}
func (c *Conn) Close() error {
c.closeFunc()
return c.Conn.Close()
}
func (c *Conn) Upstream() any {
return c.Conn
}
func (c *Conn) ReaderReplaceable() bool {
return true
}
func (c *Conn) WriterReplaceable() bool {
return true
}
type PacketConn struct {
net.PacketConn
closeFunc func()
}
func (c *PacketConn) Close() error {
c.closeFunc()
return c.PacketConn.Close()
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
}
func (c *PacketConn) ReaderReplaceable() bool {
return true
}
func (c *PacketConn) WriterReplaceable() bool {
return true
}

View File

@@ -0,0 +1,35 @@
package conntrack
import (
runtimeDebug "runtime/debug"
"time"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory"
)
var (
KillerEnabled bool
MemoryLimit uint64
killerLastCheck time.Time
)
func KillerCheck() error {
if !KillerEnabled {
return nil
}
nowTime := time.Now()
if nowTime.Sub(killerLastCheck) < 3*time.Second {
return nil
}
killerLastCheck = nowTime
if memory.Total() > MemoryLimit {
Close()
go func() {
time.Sleep(time.Second)
runtimeDebug.FreeOSMemory()
}()
return E.New("out of memory")
}
return nil
}

View File

@@ -0,0 +1,55 @@
package conntrack
import (
"io"
"net"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/x/list"
)
type PacketConn struct {
net.PacketConn
element *list.Element[io.Closer]
}
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()
if KillerEnabled {
err := KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
}
return &PacketConn{
PacketConn: conn,
element: element,
}, nil
}
func (c *PacketConn) Close() error {
if c.element.Value != nil {
connAccess.Lock()
if c.element.Value != nil {
openConnection.Remove(c.element)
c.element.Value = nil
}
connAccess.Unlock()
}
return c.PacketConn.Close()
}
func (c *PacketConn) Upstream() any {
return bufio.NewPacketConn(c.PacketConn)
}
func (c *PacketConn) ReaderReplaceable() bool {
return true
}
func (c *PacketConn) WriterReplaceable() bool {
return true
}

47
common/conntrack/track.go Normal file
View File

@@ -0,0 +1,47 @@
package conntrack
import (
"io"
"sync"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/x/list"
)
var (
connAccess sync.RWMutex
openConnection list.List[io.Closer]
)
func Count() int {
if !Enabled {
return 0
}
return openConnection.Len()
}
func List() []io.Closer {
if !Enabled {
return nil
}
connAccess.RLock()
defer connAccess.RUnlock()
connList := make([]io.Closer, 0, openConnection.Len())
for element := openConnection.Front(); element != nil; element = element.Next() {
connList = append(connList, element.Value)
}
return connList
}
func Close() {
if !Enabled {
return
}
connAccess.Lock()
defer connAccess.Unlock()
for element := openConnection.Front(); element != nil; element = element.Next() {
common.Close(element.Value)
element.Value = nil
}
openConnection.Init()
}

View File

@@ -0,0 +1,5 @@
//go:build !with_conntrack
package conntrack
const Enabled = false

View File

@@ -0,0 +1,5 @@
//go:build with_conntrack
package conntrack
const Enabled = true

View File

@@ -1,32 +0,0 @@
package conntrack
import (
"net"
"net/netip"
"time"
N "github.com/sagernet/sing/common/network"
)
// TODO: add to N
type AbstractPacketConn interface {
Close() error
LocalAddr() net.Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
type Tracker interface {
NewConn(conn net.Conn) (net.Conn, error)
NewPacketConn(conn net.PacketConn) (net.PacketConn, error)
NewConnEx(conn net.Conn) (N.CloseHandlerFunc, error)
NewPacketConnEx(conn AbstractPacketConn) (N.CloseHandlerFunc, error)
CheckConn(source netip.AddrPort, destination netip.AddrPort) bool
CheckPacketConn(source netip.AddrPort) bool
AddPendingDestination(destination netip.AddrPort) func()
CheckDestination(destination netip.AddrPort) bool
KillerCheck() error
Count() int
Close()
}

View File

@@ -2,16 +2,13 @@ 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"
@@ -19,7 +16,6 @@ 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 (
@@ -28,38 +24,31 @@ var (
)
type DefaultDialer struct {
tracker conntrack.Tracker
dialer4 tcpDialer
dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
isWireGuardListener bool
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
networkLastFallback atomic.TypedValue[time.Time]
dialer4 tcpDialer
dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
isWireGuardListener bool
networkManager adapter.NetworkManager
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
networkLastFallback atomic.TypedValue[time.Time]
}
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)
func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) {
var (
dialer net.Dialer
listener net.ListenConfig
interfaceFinder control.InterfaceFinder
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
dialer net.Dialer
listener net.ListenConfig
interfaceFinder control.InterfaceFinder
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
)
if networkManager != nil {
interfaceFinder = networkManager.InterfaceFinder()
@@ -85,52 +74,39 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
}
}
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 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`")
}
}
if networkManager != nil {
if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil {
defaultOptions := networkManager.DefaultOptions()
if !disableDefaultBind {
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
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()
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())
@@ -190,6 +166,9 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
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
@@ -199,21 +178,19 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
return nil, err
}
return &DefaultDialer{
tracker: tracker,
dialer4: tcpDialer4,
dialer6: tcpDialer6,
udpDialer4: udpDialer4,
udpDialer6: udpDialer6,
udpListener: listener,
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
isWireGuardListener: options.IsWireGuardListener,
networkManager: networkManager,
networkStrategy: networkStrategy,
defaultNetworkStrategy: defaultNetworkStrategy,
networkType: networkType,
fallbackNetworkType: fallbackNetworkType,
networkFallbackDelay: networkFallbackDelay,
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,
}, nil
}
@@ -221,48 +198,31 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
if !address.IsValid() {
return nil, E.New("invalid address")
}
if d.networkStrategy == nil {
if address.IsFqdn() {
return nil, E.New("unexpected domain destination")
}
// Since pending check is only used by ndis, it is not performed for non-windows connections which are only supported on platform clients
if d.tracker != nil {
done := d.tracker.AddPendingDestination(address.AddrPort())
defer done()
}
if d.networkStrategy == C.NetworkStrategyDefault {
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
return d.trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else {
return d.trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
}
}
if !address.IsIPv6() {
return d.trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else {
return d.trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
}
} else {
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
}
}
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if strategy == nil {
strategy = d.networkStrategy
}
if strategy == nil {
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 {
return d.DialContext(ctx, network, address)
}
if len(interfaceType) == 0 {
interfaceType = d.networkType
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaceType = d.fallbackNetworkType
}
if fallbackDelay == 0 {
fallbackDelay = d.networkFallbackDelay
if !d.networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
}
var dialer net.Dialer
if N.NetworkName(network) == N.NetworkTCP {
@@ -277,86 +237,61 @@ 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 {
// bind interface failed on legacy xiaomi systems
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
d.networkStrategy = nil
return d.DialContext(ctx, network, address)
} else {
return nil, err
}
return nil, err
}
if !fastFallback && !isPrimary {
d.networkLastFallback.Store(time.Now())
}
return d.trackConn(conn, nil)
return trackConn(conn, nil)
}
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if d.networkStrategy == nil {
if d.networkStrategy == C.NetworkStrategyDefault {
if destination.IsIPv6() {
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
} else {
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
return 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 == nil {
strategy = d.networkStrategy
}
if strategy == nil {
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 {
return d.ListenPacket(ctx, destination)
}
if len(interfaceType) == 0 {
interfaceType = d.networkType
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaceType = d.fallbackNetworkType
}
if fallbackDelay == 0 {
fallbackDelay = d.networkFallbackDelay
if !d.networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
}
network := N.NetworkUDP
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
network += "4"
}
packetConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err != nil {
// bind interface failed on legacy xiaomi systems
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
d.networkStrategy = nil
return d.ListenPacket(ctx, destination)
} else {
return nil, err
}
}
return d.trackPacketConn(packetConn, nil)
return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay))
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
return d.udpListener.ListenPacket(context.Background(), network, address)
return trackPacketConn(d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay))
}
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
if d.tracker == nil || err != nil {
func trackConn(conn net.Conn, err error) (net.Conn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return d.tracker.NewConn(conn)
return conntrack.NewConn(conn)
}
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
if err != nil {
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return d.tracker.NewPacketConn(conn)
return conntrack.NewPacketConn(conn)
}

View File

@@ -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.Index, ")"), primary: primary}:
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
case <-returned:
}
} else {
select {
case results <- dialResult{Conn: conn, primary: primary}:
case results <- dialResult{Conn: conn}:
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.Index, ")"), primary: primary}:
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
case <-returned:
}
} else {
select {
case results <- dialResult{Conn: conn, primary: primary}:
case results <- dialResult{Conn: conn}:
case <-returned:
if primary && time.Since(startAt) <= fallbackDelay {
resetFastFallback(time.Time{})
@@ -149,6 +149,9 @@ 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
@@ -157,7 +160,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.Index, ")"))
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Name, ")"))
}
for _, fallbackInterface := range fallbackInterfaces {
perNetListener := listener
@@ -166,7 +169,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.Index, ")"))
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Name, ")"))
}
return nil, E.Errors(errors...)
}
@@ -177,57 +180,44 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS
case C.NetworkStrategyDefault:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
if defaultIf != nil {
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
}
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
}
} else {
primaryInterfaces = interfaces
}
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
case C.NetworkStrategyHybrid:
if len(interfaceType) == 0 {
primaryInterfaces = interfaces
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
case C.NetworkStrategyFallback:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
if defaultIf != nil {
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
break
}
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
}
} else {
primaryInterfaces = interfaces
}
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
})
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool {
return it.Index == iif.Index
})
})
} else {
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(fallbackInterfaceType, iif.Type)
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(fallbackInterfaceType, iif.Type)
})
}
return primaryInterfaces, fallbackInterfaces
}

View File

@@ -13,13 +13,7 @@ import (
N "github.com/sagernet/sing/common/network"
)
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
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 parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
@@ -44,14 +38,7 @@ func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, des
return nil, E.Errors(errors...)
}
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
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 fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -129,13 +116,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
}
}
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
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 parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}

View File

@@ -17,15 +17,16 @@ import (
)
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
if options.IsWireGuardListener {
return NewDefault(ctx, options)
return NewDefault(networkManager, options)
}
var (
dialer N.Dialer
err error
)
if options.Detour == "" {
dialer, err = NewDefault(ctx, options)
dialer, err = NewDefault(networkManager, options)
if err != nil {
return nil, err
}
@@ -36,6 +37,9 @@ 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 {
@@ -54,10 +58,11 @@ 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(ctx, options)
return NewDefault(networkManager, options)
}
dialer, err := NewDefault(ctx, options)
dialer, err := NewDefault(networkManager, options)
if err != nil {
return nil, err
}
@@ -72,11 +77,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)
}

View File

@@ -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)
}

View File

@@ -4,6 +4,7 @@ import (
"net"
"net/netip"
"os"
"time"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
@@ -123,7 +124,7 @@ func (l *Listener) loopUDPOut() {
case packet := <-l.packetOutbound:
packet.Buffer.Release()
N.PutPacketBuffer(packet)
default:
case <-time.After(time.Second):
return
}
}

View File

@@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
return log.ContextWithNewID(ctx)
},
Logger: logger,
HandlerEx: adapter.NewRouteContextHandlerEx(router),
Padding: options.Padding,
Brutal: brutalOptions,
Logger: logger,
Handler: adapter.NewRouteContextHandler(router, logger),
Padding: options.Padding,
Brutal: brutalOptions,
})
if err != nil {
return nil, err
@@ -52,7 +52,6 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
return &Router{router, service}, nil
}
// Deprecated: Use RouteConnectionEx instead.
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination == mux.Destination {
// TODO: check if WithContext is necessary
@@ -62,7 +61,6 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
}
}
// Deprecated: Use RoutePacketConnectionEx instead.
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@@ -64,7 +64,6 @@ type echConnWrapper struct {
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,

View File

@@ -147,9 +147,6 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
pair.rawConf = b
secBuf, err := sec.MarshalBinary()
if err != nil {
return nil, E.Cause(err, "serialize ECH private key")
}
sk := []byte{}
sk = be.AppendUint16(sk, uint16(len(secBuf)))
sk = append(sk, secBuf...)

View File

@@ -28,7 +28,7 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
}
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
return &http3.Transport{
return &http3.RoundTripper{
TLSClientConfig: c.config,
QUICConfig: quicConfig,
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {

View File

@@ -97,10 +97,6 @@ func (c *echServerConfig) startWatcher() error {
if err != nil {
return err
}
err = watcher.Start()
if err != nil {
return err
}
c.watcher = watcher
return nil
}
@@ -236,7 +232,7 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound
var echKey []byte
if len(options.ECH.Key) > 0 {
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
} else if options.ECH.KeyPath != "" {
} else if options.KeyPath != "" {
content, err := os.ReadFile(options.ECH.KeyPath)
if err != nil {
return nil, E.Cause(err, "read ECH key")

View File

@@ -174,7 +174,6 @@ type realityConnWrapper struct {
func (c *realityConnWrapper) ConnectionState() ConnectionState {
state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,

View File

@@ -106,10 +106,6 @@ func (c *STDServerConfig) startWatcher() error {
if err != nil {
return err
}
err = watcher.Start()
if err != nil {
return err
}
c.watcher = watcher
return nil
}

View File

@@ -1,22 +0,0 @@
package tls
import (
"time"
"github.com/sagernet/sing/common/ntp"
)
type TimeServiceWrapper struct {
ntp.TimeService
}
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
if w.TimeService == nil {
return nil
}
return w.TimeService.TimeFunc()
}
func (w *TimeServiceWrapper) Upstream() any {
return w.TimeService
}

View File

@@ -69,7 +69,6 @@ type utlsConnWrapper struct {
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,

View File

@@ -1,7 +0,0 @@
package constant
const (
Hysterai2MasqueradeTypeFile = "file"
Hysterai2MasqueradeTypeProxy = "proxy"
Hysterai2MasqueradeTypeString = "string"
)

View File

@@ -10,7 +10,6 @@ const (
ProtocolDTLS = "dtls"
ProtocolSSH = "ssh"
ProtocolRDP = "rdp"
ProtocolNTP = "ntp"
)
const (

View File

@@ -23,7 +23,6 @@ const (
TypeVLESS = "vless"
TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2"
TypeNDIS = "ndis"
)
const (
@@ -81,8 +80,6 @@ func ProxyDisplayName(proxyType string) string {
return "Selector"
case TypeURLTest:
return "URLTest"
case TypeNDIS:
return "NDIS"
default:
return "Unknown"
}

View File

@@ -9,6 +9,8 @@ const (
TCPTimeout = 15 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute
@@ -17,18 +19,3 @@ const (
FatalStopTimeout = 10 * time.Second
FakeIPMetadataSaveInterval = 10 * time.Second
)
var PortProtocols = map[uint16]string{
53: ProtocolDNS,
123: ProtocolNTP,
3478: ProtocolSTUN,
443: ProtocolQUIC,
}
var ProtocolTimeouts = map[string]time.Duration{
ProtocolDNS: 10 * time.Second,
ProtocolNTP: 10 * time.Second,
ProtocolSTUN: 10 * time.Second,
ProtocolQUIC: 30 * time.Second,
ProtocolDTLS: 30 * time.Second,
}

View File

@@ -3,6 +3,7 @@ package box
import (
"runtime/debug"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/option"
)
@@ -25,5 +26,9 @@ func applyDebugOptions(options option.DebugOptions) {
}
if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
conntrack.MemoryLimit = uint64(options.MemoryLimit)
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller
}
}

View File

@@ -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) {

View File

@@ -2,107 +2,6 @@
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**

View File

@@ -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。

View File

@@ -1,32 +0,0 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.11.0"
# Endpoint
Endpoint is protocols that has both inbound and outbound behavior.
### Structure
```json
{
"endpoints": [
{
"type": "",
"tag": ""
}
]
}
```
### Fields
| Type | Format |
|-------------|---------------------------|
| `wireguard` | [WireGuard](./wireguard/) |
#### tag
The tag of the endpoint.

View File

@@ -1,32 +0,0 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.11.0 起"
# 端点
端点是具有入站和出站行为的协议。
### 结构
```json
{
"endpoints": [
{
"type": "",
"tag": ""
}
]
}
```
### 字段
| 类型 | 格式 |
|-------------|---------------------------|
| `wireguard` | [WireGuard](./wiregaurd/) |
#### tag
端点的标签。

View File

@@ -1,133 +0,0 @@
---
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.

View File

@@ -1,135 +0,0 @@
---
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 地址或地址段的列表您。
要分配给接口的 IPv4 或 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/)。

View File

@@ -1,20 +1,11 @@
---
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,
@@ -30,7 +21,7 @@ icon: material/alert-decagram
],
"ignore_client_bandwidth": false,
"tls": {},
"masquerade": "", // or {}
"masquerade": "",
"brutal_debug": false
}
```
@@ -76,13 +67,9 @@ Authentication password
#### ignore_client_bandwidth
*When `up_mbps` and `down_mbps` are not set*:
Commands the client to use the BBR flow control algorithm instead of Hysteria CC.
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.
Conflict with `up_mbps` and `down_mbps`.
#### tls
@@ -92,54 +79,14 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### masquerade
HTTP3 server behavior (URL string configuration) when authentication fails.
HTTP3 server behavior 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 |
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.
A 404 page will be returned if empty.
#### brutal_debug

View File

@@ -1,20 +1,11 @@
---
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,
@@ -30,7 +21,7 @@ icon: material/alert-decagram
],
"ignore_client_bandwidth": false,
"tls": {},
"masquerade": "", // 或 {}
"masquerade": "",
"brutal_debug": false
}
```
@@ -73,13 +64,9 @@ Hysteria 用户
#### ignore_client_bandwidth
*当 `up_mbps` 和 `down_mbps` 未设定时*:
命令客户端使用 BBR 拥塞控制算法而不是 Hysteria CC。
*`up_mbps` 和 `down_mbps` 已设定时*:
禁止客户端使用 BBR 拥塞控制算法。
`up_mbps``down_mbps` 冲突。
#### tls
@@ -89,54 +76,14 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### masquerade
HTTP3 服务器认证失败时的行为 URL 字符串配置)
HTTP3 服务器认证失败时的行为。
| Scheme | 示例 | 描述 |
|--------------|-------------------------|---------|
| `file` | `file:///var/www` | 作为文件服务器 |
| `http/https` | `http://127.0.0.1:8080` | 作为反向代理 |
如果 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
固定响应内容。
如果为空,则返回 404 页。
#### brutal_debug

View File

@@ -1,13 +1,7 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! 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)
@@ -52,7 +46,16 @@ icon: material/alert-decagram
"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,
@@ -66,11 +69,28 @@ icon: material/alert-decagram
"::/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"
],
@@ -90,13 +110,13 @@ icon: material/alert-decagram
0
],
"include_uid_range": [
"1000:99999"
"1000-99999"
],
"exclude_uid": [
1000
],
"exclude_uid_range": [
"1000:99999"
"1000-99999"
],
"include_android_user": [
0,
@@ -117,31 +137,8 @@ icon: material/alert-decagram
"match_domain": []
}
},
// 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
...
// Listen Fields
}
```
@@ -169,7 +166,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.12.0.
`inet4_address` is merged to `address` and will be removed in sing-box 1.11.0.
IPv4 prefix for the tun interface.
@@ -177,7 +174,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.12.0.
`inet6_address` is merged to `address` and will be removed in sing-box 1.11.0.
IPv6 prefix for the tun interface.
@@ -187,10 +184,6 @@ 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 ""
@@ -250,7 +243,7 @@ use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
!!! question "Since sing-box 1.10.0"
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
Connection input mark used by `route_address_set` and `route_exclude_address_set`.
`0x2023` is used by default.
@@ -258,7 +251,7 @@ Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`
!!! question "Since sing-box 1.10.0"
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
Connection output mark used by `route_address_set` and `route_exclude_address_set`.
`0x2024` is used by default.
@@ -291,7 +284,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.12.0, please use [route_address](#route_address)
`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
instead.
Use custom routes instead of default when `auto_route` is enabled.
@@ -300,7 +293,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.12.0, please use [route_address](#route_address)
`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
instead.
Use custom routes instead of default when `auto_route` is enabled.
@@ -315,7 +308,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.12.0, please
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
use [route_exclude_address](#route_exclude_address) instead.
Exclude custom routes when `auto_route` is enabled.
@@ -324,62 +317,36 @@ 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.12.0, please
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
use [route_exclude_address](#route_exclude_address) instead.
Exclude custom routes when `auto_route` is enabled.
#### route_address_set
=== "With `auto_redirect` enabled"
!!! question "Since sing-box 1.10.0"
!!! 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.
Unmatched traffic will bypass the sing-box routes.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
=== "Without `auto_redirect` enabled"
!!! question "Since sing-box 1.11.0"
Add the destination IP CIDR rules in the specified rule-sets to routes, equivalent to adding to `route_address`.
Unmatched traffic will bypass the sing-box routes.
Note that it **doesn't work on the Android graphical client** due to
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
but otherwise it works fine on all command line clients and Apple platforms.
#### route_exclude_address_set
=== "With `auto_redirect` enabled"
!!! question "Since sing-box 1.10.0"
!!! quote ""
!!! 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.
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_exclude_address`.
Matched traffic will bypass the sing-box routes.
#### route_exclude_address_set
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.
!!! 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.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
#### endpoint_independent_nat
@@ -393,9 +360,7 @@ Performance may degrade slightly, so it is not recommended to enable on when it
#### udp_timeout
UDP NAT expiration time.
`5m` will be used by default.
UDP NAT expiration time in seconds, default is 300 (5 minutes).
#### stack

View File

@@ -1,14 +1,8 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! 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 中的更改"
!!! quote "Changes in sing-box 1.10.0"
:material-plus: [address](#address)
:material-delete-clock: [inet4_address](#inet4_address)
@@ -52,7 +46,16 @@ icon: material/alert-decagram
"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,
@@ -66,11 +69,28 @@ icon: material/alert-decagram
"::/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"
],
@@ -90,13 +110,13 @@ icon: material/alert-decagram
0
],
"include_uid_range": [
"1000:99999"
"1000-99999"
],
"exclude_uid": [
1000
],
"exclude_uid_range": [
"1000:99999"
"1000-99999"
],
"include_android_user": [
0,
@@ -117,29 +137,6 @@ icon: material/alert-decagram
"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"
],
... // 监听字段
}
@@ -171,7 +168,7 @@ tun 接口的 IPv4 和 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet4_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。
==必填==
@@ -181,7 +178,7 @@ tun 接口的 IPv4 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet6_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。
tun 接口的 IPv6 前缀。
@@ -191,10 +188,6 @@ tun 接口的 IPv6 前缀。
#### gso
!!! failure "已在 sing-box 1.11.0 废弃"
GSO 对于透明代理场景没有优势,已废弃和不再生效,且将在 sing-box 1.12.0 中被移除。
!!! question "自 sing-box 1.8.0 起"
!!! quote ""
@@ -295,7 +288,7 @@ tun 接口的 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。
启用 `auto_route` 时使用自定义路由而不是默认路由。
@@ -303,7 +296,7 @@ tun 接口的 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。
启用 `auto_route` 时使用自定义路由而不是默认路由。
@@ -317,7 +310,7 @@ tun 接口的 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。
启用 `auto_route` 时排除自定义路由。
@@ -325,59 +318,35 @@ tun 接口的 IPv6 前缀。
!!! failure "已在 sing-box 1.10.0 废弃"
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。
启用 `auto_route` 时排除自定义路由。
#### route_address_set
=== "`auto_redirect` 已启用"
!!! question "自 sing-box 1.10.0 起"
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Linux且需要 nftables`auto_route``auto_redirect` 已启用。
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
不匹配的流量将绕过 sing-box 路由。
`route.default_mark``[dialOptions].routing_mark` 冲突。
!!! quote ""
=== "`auto_redirect` 启用"
仅支持 Linux且需要 nftables`auto_route``auto_redirect` 启用
!!! question "自 sing-box 1.11.0 起"
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
不匹配的流量将绕过 sing-box 路由。
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_address`
不匹配的流量将绕过 sing-box 路由。
请注意,由于 Android VpnService 无法处理大量路由DeadSystemException
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
`route.default_mark``[dialOptions].routing_mark` 冲突
#### route_exclude_address_set
=== "`auto_redirect` 已启用"
!!! question "自 sing-box 1.10.0 起"
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Linux且需要 nftables`auto_route``auto_redirect` 已启用。
!!! quote ""
将指定规则集中的目标 IP CIDR 规则添加到防火墙
匹配的流量将绕过 sing-box 路由。
仅支持 Linux且需要 nftables`auto_route``auto_redirect` 已启用
`route.default_mark``[dialOptions].routing_mark` 冲突
将指定规则集中的目标 IP CIDR 规则添加到防火墙
匹配的流量将绕过 sing-box 路由。
=== "`auto_redirect` 未启用"
!!! question "自 sing-box 1.11.0 起"
将指定规则集中的目标 IP CIDR 规则添加到路由,相当于添加到 `route_exclude_address`
匹配的流量将绕过 sing-box 路由。
请注意,由于 Android VpnService 无法处理大量路由DeadSystemException
因此它**在 Android 图形客户端上不起作用**,但除此之外,它在所有命令行客户端和 Apple 平台上都可以正常工作。
`route.default_mark``[dialOptions].routing_mark` 冲突。
#### endpoint_independent_nat
@@ -387,9 +356,7 @@ tun 接口的 IPv6 前缀。
#### udp_timeout
UDP NAT 过期时间。
默认使用 `5m`
UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)
#### stack

View File

@@ -9,7 +9,6 @@ sing-box uses JSON for configuration files.
"log": {},
"dns": {},
"ntp": {},
"endpoints": [],
"inbounds": [],
"outbounds": [],
"route": {},
@@ -24,7 +23,6 @@ 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/) |

View File

@@ -8,7 +8,6 @@ sing-box 使用 JSON 作为配置文件格式。
{
"log": {},
"dns": {},
"endpoints": [],
"inbounds": [],
"outbounds": [],
"route": {},
@@ -22,7 +21,6 @@ sing-box 使用 JSON 作为配置文件格式。
|----------------|------------------------|
| `log` | [日志](./log/) |
| `dns` | [DNS](./dns/) |
| `endpoints` | [端点](./endpoint/) |
| `inbounds` | [入站](./inbound/) |
| `outbounds` | [出站](./outbound/) |
| `route` | [路由](./route/) |

View File

@@ -4,8 +4,8 @@ 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)
:material-alert-decagram: [override_address](#override_address)
:material-alert-decagram: [override_port](#override_port)
`direct` outbound send requests directly.

View File

@@ -1,12 +1,3 @@
---
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
@@ -16,10 +7,6 @@ icon: material/new-box
"server": "127.0.0.1",
"server_port": 1080,
"server_ports": [
"2080:3000"
],
"hop_interval": "",
"up_mbps": 100,
"down_mbps": 100,
"obfs": {
@@ -35,10 +22,6 @@ icon: material/new-box
}
```
!!! 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**,
@@ -61,24 +44,6 @@ 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.

View File

@@ -1,12 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [server_ports](#server_ports)
:material-plus: [hop_interval](#hop_interval)
### 结构
```json
@@ -16,10 +7,6 @@ icon: material/new-box
"server": "127.0.0.1",
"server_port": 1080,
"server_ports": [
"2080:3000"
],
"hop_interval": "",
"up_mbps": 100,
"down_mbps": 100,
"obfs": {
@@ -35,10 +22,6 @@ icon: material/new-box
}
```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签
!!! warning "与官方 Hysteria2 的区别"
官方程序支持一种名为 **userpass** 的验证方式,
@@ -59,24 +42,6 @@ icon: material/new-box
服务器端口。
如果设置了 `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
最大带宽。

View File

@@ -1,15 +1,3 @@
---
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)
@@ -24,9 +12,10 @@ icon: material/delete-clock
"server": "127.0.0.1",
"server_port": 1080,
"system_interface": false,
"gso": false,
"interface_name": "wg0",
"local_address": [
"10.0.0.1/32"
"10.0.0.2/32"
],
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
"peers": [
@@ -48,10 +37,6 @@ icon: material/delete-clock
"mtu": 1408,
"network": "tcp",
// Deprecated
"gso": false,
... // Dial Fields
}
```
@@ -84,10 +69,6 @@ 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 ""

View File

@@ -1,15 +1,3 @@
---
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)
@@ -24,9 +12,10 @@ icon: material/delete-clock
"server": "127.0.0.1",
"server_port": 1080,
"system_interface": false,
"gso": false,
"interface_name": "wg0",
"local_address": [
"10.0.0.1/32"
"10.0.0.2/32"
],
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
@@ -35,10 +24,6 @@ icon: material/delete-clock
"workers": 4,
"mtu": 1408,
"network": "tcp",
// 废弃的
"gso": false,
... // 拨号字段
}
@@ -72,10 +57,6 @@ icon: material/delete-clock
#### gso
!!! failure "已在 sing-box 1.11.0 废弃"
自 sing-box 1.11.0 起GSO 将可用时自动启用。
!!! question "自 sing-box 1.8.0 起"
!!! quote ""

View File

@@ -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-plus: [default_fallback_delay](#default_fallback_delay)
:material-alert: [default_fallback_delay](#default_fallback_delay)
!!! quote "Changes in sing-box 1.8.0"

View File

@@ -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-plus: [default_fallback_delay](#default_fallback_delay)
:material-alert: [default_fallback_delay](#default_fallback_delay)
!!! quote "sing-box 1.8.0 中的更改"

View File

@@ -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。

View File

@@ -41,8 +41,7 @@ See `route-options` fields below.
"network_strategy": "",
"fallback_delay": "",
"udp_disable_domain_unmapping": false,
"udp_connect": false,
"udp_timeout": ""
"udp_connect": false
}
```
@@ -87,28 +86,6 @@ 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.
#### udp_timeout
Timeout for UDP connections.
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
```json

View File

@@ -37,8 +37,7 @@ icon: material/new-box
"network_strategy": "",
"fallback_delay": "",
"udp_disable_domain_unmapping": false,
"udp_connect": false,
"udp_timeout": ""
"udp_connect": false
}
```
@@ -85,28 +84,6 @@ icon: material/new-box
如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。
#### udp_timeout
UDP 连接超时时间。
设置比入站 UDP 超时更大的值将无效。
已探测协议连接的默认值:
| 超时 | 协议 |
|-------|----------------------|
| `10s` | `dns`, `ntp`, `stun` |
| `30s` | `quic`, `dtls` |
如果没有探测到协议,以下端口将默认识别为协议:
| 端口 | 协议 |
|------|--------|
| 53 | `dns` |
| 123 | `ntp` |
| 443 | `quic` |
| 3478 | `stun` |
### reject
```json

View File

@@ -68,9 +68,9 @@ Enable UDP fragmentation.
#### udp_timeout
UDP NAT expiration time.
UDP NAT expiration time in seconds.
`5m` will be used by default.
`5m` is used by default.
#### detour

View File

@@ -69,7 +69,7 @@ icon: material/delete-clock
#### udp_timeout
UDP NAT 过期时间。
UDP NAT 过期时间,以秒为单位
默认使用 `5m`

View File

@@ -28,19 +28,6 @@ Destination override fields (`override_address` / `override_port`) in direct out
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

View File

@@ -27,19 +27,6 @@ direct 出站中的目标地址覆盖字段(`override_address` / `override_por
旧字段将在 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 规则项已重命名

View File

@@ -194,77 +194,6 @@ Destination override fields in direct outbound are deprecated and can be replace
}
```
### 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

View File

@@ -104,6 +104,7 @@ icon: material/arrange-bring-forward
### 迁移旧的入站字段到规则动作
入站选项已被弃用,且可以被规则动作替代。
!!! info "参考"
@@ -195,77 +196,6 @@ direct 出站中的目标地址覆盖字段已废弃,且可以被路由字段
}
```
### 迁移 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 地址字段已合并

View File

@@ -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.outbound.Outbounds(), func(it adapter.Outbound) bool {
groups := common.Map(common.Filter(server.outboundManager.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.outbound.Outbound(it)
itOutbound, _ := server.outboundManager.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.outbound.Outbound(realTag)
p, loaded := server.outboundManager.Outbound(realTag)
if !loaded {
continue
}

View File

@@ -18,19 +18,17 @@ 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"`
// sing-box added
ModeList []string `json:"mode-list"`
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"`
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) {
@@ -43,7 +41,6 @@ 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),
})

View File

@@ -46,7 +46,7 @@ func findProxyByName(server *Server) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProxyName).(string)
proxy, exist := server.outbound.Outbound(name)
proxy, exist := server.outboundManager.Outbound(name)
if !exist {
render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound)
@@ -86,14 +86,9 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var proxyMap badjson.JSONObject
outbounds := common.Filter(server.outbound.Outbounds(), func(detour adapter.Outbound) bool {
outbounds := common.Filter(server.outboundManager.Outbounds(), func(detour adapter.Outbound) bool {
return detour.Tag() != ""
})
outbounds = append(outbounds, common.Map(common.Filter(server.endpoint.Endpoints(), func(detour adapter.Endpoint) bool {
return detour.Tag() != ""
}), func(it adapter.Endpoint) adapter.Outbound {
return it
})...)
allProxies := make([]string, 0, len(outbounds))
@@ -105,7 +100,7 @@ func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {
allProxies = append(allProxies, detour.Tag())
}
defaultTag := server.outbound.Default().Tag()
defaultTag := server.outboundManager.Default().Tag()
sort.SliceStable(allProxies, func(i, j int) bool {
return allProxies[i] == defaultTag

View File

@@ -40,17 +40,16 @@ func init() {
var _ adapter.ClashServer = (*Server)(nil)
type Server struct {
ctx context.Context
router adapter.Router
outbound adapter.OutboundManager
endpoint adapter.EndpointManager
logger log.Logger
httpServer *http.Server
trafficManager *trafficontrol.Manager
urlTestHistory *urltest.HistoryStorage
mode string
modeList []string
modeUpdateHook chan<- struct{}
ctx context.Context
router adapter.Router
outboundManager adapter.OutboundManager
logger log.Logger
httpServer *http.Server
trafficManager *trafficontrol.Manager
urlTestHistory *urltest.HistoryStorage
mode string
modeList []string
modeUpdateHook chan<- struct{}
externalController bool
externalUI string
@@ -62,11 +61,10 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
trafficManager := trafficontrol.NewManager()
chiRouter := chi.NewRouter()
s := &Server{
ctx: ctx,
router: service.FromContext[adapter.Router](ctx),
outbound: service.FromContext[adapter.OutboundManager](ctx),
endpoint: service.FromContext[adapter.EndpointManager](ctx),
logger: logFactory.NewLogger("clash-api"),
ctx: ctx,
router: service.FromContext[adapter.Router](ctx),
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
logger: logFactory.NewLogger("clash-api"),
httpServer: &http.Server{
Addr: options.ExternalController,
Handler: chiRouter,
@@ -128,8 +126,11 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
if options.ExternalUI != "" {
s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
chiRouter.Group(func(r chi.Router) {
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP)
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.externalUI))))
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(s.externalUI)))
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
})
}
return s, nil
@@ -241,11 +242,11 @@ func (s *Server) TrafficManager() *trafficontrol.Manager {
}
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound)
return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound)
}
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn {
return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound)
return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound)
}
func authentication(serverSecret string) func(next http.Handler) http.Handler {
@@ -320,29 +321,27 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter,
tick := time.NewTicker(time.Second)
defer tick.Stop()
buf := &bytes.Buffer{}
uploadTotal, downloadTotal := trafficManager.Total()
var err error
for range tick.C {
buf.Reset()
uploadTotalNew, downloadTotalNew := trafficManager.Total()
err := json.NewEncoder(buf).Encode(Traffic{
Up: uploadTotalNew - uploadTotal,
Down: downloadTotalNew - downloadTotal,
})
if err != nil {
up, down := trafficManager.Now()
if err := json.NewEncoder(buf).Encode(Traffic{
Up: up,
Down: down,
}); err != nil {
break
}
if conn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsutil.WriteServerText(conn, buf.Bytes())
}
if err != nil {
break
}
uploadTotal = uploadTotalNew
downloadTotal = downloadTotalNew
}
}
}

View File

@@ -44,13 +44,13 @@ func (s *Server) downloadExternalUI() error {
s.logger.Info("downloading external ui")
var detour adapter.Outbound
if s.externalUIDownloadDetour != "" {
outbound, loaded := s.outbound.Outbound(s.externalUIDownloadDetour)
outbound, loaded := s.outboundManager.Outbound(s.externalUIDownloadDetour)
if !loaded {
return E.New("detour outbound not found: ", s.externalUIDownloadDetour)
}
detour = outbound
} else {
outbound := s.outbound.Default()
outbound := s.outboundManager.Default()
detour = outbound
}
httpClient := &http.Client{

Some files were not shown because too many files have changed in this diff Show More