Compare commits

..

53 Commits

Author SHA1 Message Date
世界
658d4a180b Use stdlib ech 2024-12-11 19:33:53 +08:00
世界
93966bdd36 documentation: Bump version 2024-12-10 21:38:13 +08:00
世界
14a4b0f922 Fix socks5 UDP implementation 2024-12-10 21:38:04 +08:00
世界
919b08e64c clash-api: Fix missing endpoints 2024-12-10 21:37:36 +08:00
世界
bdd2472065 hysteria2: Add more masquerade options 2024-12-10 21:37:36 +08:00
世界
705c23866a Improve timeouts 2024-12-10 21:37:36 +08:00
世界
ec310170cc Add UDP timeout route option 2024-12-10 21:37:35 +08:00
世界
7405b22dc8 Make GSO adaptive 2024-12-10 21:37:35 +08:00
世界
cd2cf2450e Fix tests 2024-12-10 21:37:35 +08:00
世界
a525f139dc Fix lint 2024-12-10 21:37:34 +08:00
世界
cc8ba050dd refactor: WireGuard endpoint 2024-12-10 21:37:34 +08:00
世界
987556fd3d refactor: connection manager 2024-12-10 21:37:33 +08:00
世界
e6dd7d279d documentation: Fix typo 2024-12-10 21:37:32 +08:00
世界
b1cbf141c7 Add override destination to route options 2024-12-10 21:37:32 +08:00
世界
ab616d8510 Add dns.cache_capacity 2024-12-10 21:37:32 +08:00
世界
097818dfef Refactor multi networks strategy 2024-12-10 21:37:31 +08:00
世界
47af425e45 documentation: Remove unused titles 2024-12-10 21:37:31 +08:00
世界
5d49685683 Add multi network dialing 2024-12-10 21:37:30 +08:00
世界
15624a648c documentation: Merge route options to route actions 2024-12-10 21:37:30 +08:00
世界
1bfedf5332 Add network_[type/is_expensive/is_constrained] rule items 2024-12-10 21:37:30 +08:00
世界
10d15259b6 Merge route options to route actions 2024-12-10 21:37:29 +08:00
世界
ee11ca4935 refactor: Platform Interfaces 2024-12-10 21:37:28 +08:00
世界
e83331c2d9 refactor: Extract services form router 2024-12-10 21:37:28 +08:00
世界
b74df53a9c refactor: Modular network manager 2024-12-10 21:37:27 +08:00
世界
f64107f040 refactor: Modular inbound/outbound manager 2024-12-10 21:37:26 +08:00
世界
7db0e712b6 documentation: Add rule action 2024-12-10 21:37:26 +08:00
世界
edfdf1d4f3 documentation: Update the scheduled removal time of deprecated features 2024-12-10 21:37:25 +08:00
世界
eb3023d66c documentation: Remove outdated icons 2024-12-10 21:37:25 +08:00
世界
2c39c4d19c Migrate bad options to library 2024-12-10 21:37:25 +08:00
世界
aa3fcefb72 Implement udp connect 2024-12-10 21:37:25 +08:00
世界
ce503fd682 Implement new deprecated warnings 2024-12-10 21:37:24 +08:00
世界
017fa5e298 Improve rule actions 2024-12-10 21:37:23 +08:00
世界
94d187397d Remove unused reject methods 2024-12-10 21:37:23 +08:00
世界
1d497b08d7 refactor: Modular inbounds/outbounds 2024-12-10 21:37:23 +08:00
世界
e52ab781bb Implement dns-hijack 2024-12-10 21:37:23 +08:00
世界
2c1e398c78 Implement resolve(server) 2024-12-10 21:37:22 +08:00
世界
d4ad7ff638 Implement TCP and ICMP rejects 2024-12-10 21:37:22 +08:00
世界
ee0f3ba739 Crazy sekai overturns the small pond 2024-12-10 21:37:01 +08:00
世界
b365b1369d Fix play release 2024-12-10 20:44:08 +08:00
世界
69f8d7fa72 Add workaround for bulkBarrierPreWrite: unaligned arguments panic 2024-12-10 20:36:32 +08:00
世界
746f63a9ac Update debug iOS library build 2024-12-10 20:36:32 +08:00
世界
f0b6818b4c Add workaround for golang/go#68760 2024-12-10 10:30:42 +08:00
世界
3032317918 release: Add workflow build 2024-12-10 09:25:11 +08:00
世界
db22f61846 Update NDK to r28-rc1 2024-12-09 15:11:38 +08:00
世界
8c3a98faa2 wireguard: Fix set reserved 2024-12-05 17:58:09 +08:00
世界
1e787cb607 Fix initial traffic value 2024-12-03 21:43:56 +08:00
世界
558585b01d release: Set upload threads to 5 2024-12-03 17:36:19 +08:00
世界
6e7ecbd4f5 Fix wireguard listen 2024-11-30 12:20:48 +08:00
世界
5a661cde67 clashapi: Remove traffic loop 2024-11-28 12:57:56 +08:00
世界
3cc0e87cfb Bump version 2024-11-27 10:45:24 +08:00
世界
effea5a2b3 Update quic-go to v0.48.2 2024-11-27 10:37:00 +08:00
世界
7f168c5ec6 release: Clean before iOS build 2024-11-27 10:35:20 +08:00
Zephyruso
0e9129ee3f clashapi: Add mode list 2024-11-27 10:34:47 +08:00
100 changed files with 1998 additions and 1365 deletions

615
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,615 @@
name: Build
on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
prerelease:
description: "Is prerelease"
required: true
type: boolean
default: true
build:
description: "Build type"
required: true
type: choice
default: "All"
options:
- All
- Binary
- Android
- Apple
- app-store
- iOS
- macOS
- tvOS
- macOS-standalone
- publish-android
macos_project_version:
description: "macOS project version"
required: false
type: string
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 }}
prerelease: ${{ steps.outputs.outputs.prerelease }}
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 "prerelease=${{ inputs.prerelease }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
echo "prerelease=${{ inputs.prerelease }}" >> "$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"
echo "prerelease=$prerelease" >> "$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 tag v${{ needs.calculate_version.outputs.version }}
- 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 tag v${{ needs.calculate_version.outputs.version }}
- 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: needs.calculate_version.outputs.prerelease == 'false'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: needs.calculate_version.outputs.prerelease == 'true'
run: |-
cd clients/android
git checkout dev
- name: Gradle cache
uses: actions/cache@v4
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Build
run: |-
go run -v ./cmd/internal/update_android_version --ci
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare upload
if: github.event_name == 'workflow_dispatch'
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-android-apks
path: 'dist'
publish_android:
name: Publish Android
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
runs-on: ubuntu-latest
needs:
- calculate_version
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
- 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: needs.calculate_version.outputs.prerelease == 'false'
run: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: needs.calculate_version.outputs.prerelease == 'true'
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_library:
name: Build Apple library
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'
runs-on: macos-15
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 Xcode
run: |-
sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app
- name: Set tag
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
- name: Build library
run: |-
make lib_install
export PATH="$PATH:$(go env GOPATH)/bin"
make lib_ios
- name: Upload library
uses: actions/upload-artifact@v4
with:
name: library-apple
path: 'Libbox.xcframework'
build_apple:
name: Build Apple clients
runs-on: macos-15
needs:
- calculate_version
- build_apple_library
strategy:
matrix:
include:
- name: iOS
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == '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' }}
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' }}
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' }}
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
if: matrix.if
run: |-
sudo xcode-select -s /Applications/Xcode_16.2_beta_3.app
- name: Set tag
if: matrix.if
run: |-
git tag v${{ needs.calculate_version.outputs.version }}
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Checkout main branch
if: matrix.if && needs.calculate_version.outputs.prerelease == 'false'
run: |-
cd clients/apple
git checkout main
- name: Checkout dev branch
if: matrix.if && needs.calculate_version.outputs.prerelease == 'true'
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
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: Download library
if: matrix.if
uses: actions/download-artifact@v4
with:
name: library-apple
path: clients/apple/Libbox.xcframework
- 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 $RUNNER_TEMP/Key.p12 \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
env:
MACOS_PROJECT_VERSION: ${{ inputs.macos_project_version }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- name: Upload to App Store Connect
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
run: |-
cd clients/apple
xcodebuild -exportArchive \
-archivePath "${{ matrix.archive }}" \
-exportOptionsPlist ${{ matrix.upload }} \
-allowProvisioningUpdates \
-authenticationKeyPath $RUNNER_TEMP/Key.p12 \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
- 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 != 'publish-android'
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 tag v${{ needs.calculate_version.outputs.version }}
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
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

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

View File

@@ -71,7 +71,7 @@ release:
dist/*_amd64.pkg.tar.zst \ dist/*_amd64.pkg.tar.zst \
dist/*_arm64.pkg.tar.zst \ dist/*_arm64.pkg.tar.zst \
dist/release dist/release
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
rm -r dist/release rm -r dist/release
release_repo: release_repo:
@@ -90,7 +90,7 @@ upload_android:
mkdir -p dist/release_android mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
rm -rf dist/release_android rm -rf dist/release_android
release_android: lib_android update_android_version build_android upload_android release_android: lib_android update_android_version build_android upload_android
@@ -99,9 +99,11 @@ publish_android:
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
# TODO: find why and remove `-destination 'generic/platform=iOS'` # TODO: find why and remove `-destination 'generic/platform=iOS'`
# TODO: remove xcode clean when fix control widget fixed
build_ios: build_ios:
cd ../sing-box-for-apple && \ cd ../sing-box-for-apple && \
rm -rf build/SFI.xcarchive && \ rm -rf build/SFI.xcarchive && \
xcodebuild clean -scheme SFI && \
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
upload_ios_app_store: upload_ios_app_store:
@@ -199,9 +201,15 @@ test_stdio:
lib_android: lib_android:
go run ./cmd/internal/build_libbox -target android go run ./cmd/internal/build_libbox -target android
lib_android_debug:
go run ./cmd/internal/build_libbox -target android -debug
lib_ios: lib_ios:
go run ./cmd/internal/build_libbox -target ios go run ./cmd/internal/build_libbox -target ios
lib_ios_debug:
go run ./cmd/internal/build_libbox -target ios -debug
lib: lib:
go run ./cmd/internal/build_libbox -target android go run ./cmd/internal/build_libbox -target android
go run ./cmd/internal/build_libbox -target ios go run ./cmd/internal/build_libbox -target ios

View File

@@ -8,8 +8,7 @@ import (
) )
type ConnectionManager interface { type ConnectionManager interface {
Start() error Lifecycle
Close() error
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) 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) NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
} }

View File

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

View File

@@ -65,14 +65,17 @@ type InboundContext struct {
LastInbound string LastInbound string
OriginDestination M.Socksaddr OriginDestination M.Socksaddr
RouteOriginalDestination M.Socksaddr RouteOriginalDestination M.Socksaddr
// Deprecated // Deprecated: to be removed
//nolint:staticcheck
InboundOptions option.InboundOptions InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool UDPDisableDomainUnmapping bool
UDPConnect bool UDPConnect bool
NetworkStrategy C.NetworkStrategy UDPTimeout time.Duration
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType NetworkStrategy C.NetworkStrategy
FallbackDelay time.Duration NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
DNSServer string DNSServer string

View File

@@ -1,157 +0,0 @@
package outbound
import (
"context"
"net"
"net/netip"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn
var err error
if len(metadata.DestinationAddresses) > 0 {
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

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

10
box.go
View File

@@ -336,11 +336,11 @@ func (s *Box) preStart() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router) err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
if err != nil { if err != nil {
return err return err
} }
@@ -364,7 +364,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
@@ -372,7 +372,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
@@ -391,7 +391,7 @@ func (s *Box) Close() error {
close(s.done) close(s.done)
} }
err := common.Close( err := common.Close(
s.inbound, s.outbound, s.router, s.network, s.inbound, s.outbound, s.router, s.connection, s.network,
) )
for _, lifecycleService := range s.services { for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error { err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -10,7 +10,9 @@ import (
_ "github.com/sagernet/gomobile" _ "github.com/sagernet/gomobile"
"github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/shell"
) )
var ( var (
@@ -62,9 +64,33 @@ func init() {
func buildAndroid() { func buildAndroid() {
build_shared.FindSDK() build_shared.FindSDK()
var javaPath string
javaHome := os.Getenv("JAVA_HOME")
if javaHome == "" {
javaPath = "java"
} else {
javaPath = filepath.Join(javaHome, "bin", "java")
}
javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput()
if err != nil {
log.Fatal(E.Cause(err, "check java version"))
}
if !strings.Contains(javaVersion, "openjdk 17") {
log.Fatal("java version should be openjdk 17")
}
var bindTarget string
if debugEnabled {
bindTarget = "android/arm64"
} else {
bindTarget = "android"
}
args := []string{ args := []string{
"bind", "bind",
"-v", "-v",
"-target", bindTarget,
"-androidapi", "21", "-androidapi", "21",
"-javapkg=io.nekohasekai", "-javapkg=io.nekohasekai",
"-libname=box", "-libname=box",
@@ -86,7 +112,7 @@ func buildAndroid() {
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...) command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
command.Stdout = os.Stdout command.Stdout = os.Stdout
command.Stderr = os.Stderr command.Stderr = os.Stderr
err := command.Run() err = command.Run()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -104,10 +130,17 @@ func buildAndroid() {
} }
func buildiOS() { func buildiOS() {
var bindTarget string
if debugEnabled {
bindTarget = "ios"
} else {
bindTarget = "ios,iossimulator,tvos,tvossimulator,macos"
}
args := []string{ args := []string{
"bind", "bind",
"-v", "-v",
"-target", "ios,iossimulator,tvos,tvossimulator,macos", "-target", bindTarget,
"-libname=box", "-libname=box",
} }
if !debugEnabled { if !debugEnabled {

View File

@@ -11,9 +11,7 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/shell"
) )
var ( var (
@@ -42,14 +40,6 @@ func FindSDK() {
log.Fatal("android NDK not found") log.Fatal("android NDK not found")
} }
javaVersion, err := shell.Exec("java", "--version").ReadOutput()
if err != nil {
log.Fatal(E.Cause(err, "check java version"))
}
if !strings.Contains(javaVersion, "openjdk 17") {
log.Fatal("java version should be openjdk 17")
}
os.Setenv("ANDROID_HOME", androidSDKPath) os.Setenv("ANDROID_HOME", androidSDKPath)
os.Setenv("ANDROID_SDK_HOME", androidSDKPath) os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
os.Setenv("ANDROID_NDK_HOME", androidNDKPath) os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
@@ -58,12 +48,16 @@ func FindSDK() {
} }
func findNDK() bool { func findNDK() bool {
const fixedVersion = "26.2.11394342" const fixedVersion = "28.0.12674087"
const versionFile = "source.properties" const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) { if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath androidNDKPath = fixedPath
return true return true
} }
if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) {
androidNDKPath = ndkHomeEnv
return true
}
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk")) ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
if err != nil { if err != nil {
return false return false

View File

@@ -20,6 +20,11 @@ func ReadTag() (string, error) {
return version.String() + "-" + shortCommit, nil return version.String() + "-" + shortCommit, nil
} }
func ReadTagVersionRev() (badversion.Version, error) {
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
return badversion.Parse(currentTagRev[1:]), nil
}
func ReadTagVersion() (badversion.Version, error) { func ReadTagVersion() (badversion.Version, error) {
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput()) currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()) currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
@@ -31,3 +36,11 @@ func ReadTagVersion() (badversion.Version, error) {
} }
return version, nil return version, nil
} }
func IsDevBranch() bool {
branch, err := shell.Exec("git", "branch", "--show-current").ReadOutput()
if err != nil {
return false
}
return branch == "dev-next"
}

View File

@@ -1,21 +1,74 @@
package main package main
import ( import (
"flag"
"os" "os"
"github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
F "github.com/sagernet/sing/common/format"
) )
var nightly bool
func init() {
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
}
func main() { func main() {
currentTag, err := build_shared.ReadTag() flag.Parse()
if err != nil { if nightly {
log.Error(err) version, err := build_shared.ReadTagVersionRev()
_, err = os.Stdout.WriteString("unknown\n") if err != nil {
log.Fatal(err)
}
var (
versionStr string
isPrerelease bool
)
if version.PreReleaseIdentifier != "" {
isPrerelease = true
versionStr = version.VersionString() + "-nightly"
} else {
version.Patch++
versionStr = version.VersionString() + "-nightly"
}
if build_shared.IsDevBranch() {
isPrerelease = true
}
err = setGitHubOutput("version", versionStr)
if err != nil {
log.Fatal(err)
}
err = setGitHubOutput("prerelease", F.ToString(isPrerelease))
if err != nil {
log.Fatal(err)
}
} else { } else {
_, err = os.Stdout.WriteString(currentTag + "\n") tag, err := build_shared.ReadTag()
} if err != nil {
if err != nil { log.Error(err)
log.Error(err) os.Stdout.WriteString("unknown\n")
} else {
os.Stdout.WriteString(tag + "\n")
}
} }
} }
func setGitHubOutput(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,6 +1,7 @@
package main package main
import ( import (
"flag"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@@ -12,9 +13,22 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
) )
var flagRunInCI bool
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
}
func main() { func main() {
newVersion := common.Must1(build_shared.ReadTagVersion()) flag.Parse()
androidPath, err := filepath.Abs("../sing-box-for-android") newVersion := common.Must1(build_shared.ReadTag())
var androidPath string
if flagRunInCI {
androidPath = "clients/android"
} else {
androidPath = "../sing-box-for-android"
}
androidPath, err := filepath.Abs(androidPath)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -31,10 +45,10 @@ func main() {
for _, propPair := range propsList { for _, propPair := range propsList {
switch propPair[0] { switch propPair[0] {
case "VERSION_NAME": case "VERSION_NAME":
if propPair[1] != newVersion.String() { if propPair[1] != newVersion {
versionUpdated = true versionUpdated = true
propPair[1] = newVersion.String() propPair[1] = newVersion
log.Info("updated version to ", newVersion.String()) log.Info("updated version to ", newVersion)
} }
case "GO_VERSION": case "GO_VERSION":
if propPair[1] != runtime.Version() { if propPair[1] != runtime.Version() {

View File

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

View File

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

View File

@@ -88,25 +88,31 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
} }
if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil { if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil {
defaultOptions := networkManager.DefaultOptions() defaultOptions := networkManager.DefaultOptions()
if defaultOptions.BindInterface != "" { if options.BindInterface == "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) if defaultOptions.BindInterface != "" {
dialer.Control = control.Append(dialer.Control, bindFunc) bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
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) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.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)
}
} }
} }
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
}
} }
if options.ReuseAddr { if options.ReuseAddr {
listener.Control = control.Append(listener.Control, control.ReuseAddr()) listener.Control = control.Append(listener.Control, control.ReuseAddr())
@@ -279,7 +285,7 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
} }
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address)) return d.udpListener.ListenPacket(context.Background(), network, address)
} }
func trackConn(conn net.Conn, err error) (net.Conn, error) { func trackConn(conn net.Conn, err error) (net.Conn, error) {

View File

@@ -149,9 +149,6 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, E.New("no available network interface") return nil, E.New("no available network interface")
} }
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
var errors []error var errors []error
for _, primaryInterface := range primaryInterfaces { for _, primaryInterface := range primaryInterfaces {
perNetListener := listener perNetListener := listener

View File

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

View File

@@ -30,14 +30,15 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
return nil, nil return nil, nil
} }
if options.ECH != nil && options.ECH.Enabled { if options.ECH != nil && options.ECH.Enabled {
return NewECHClient(ctx, serverAddress, options) if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
return NewECHClient(ctx, serverAddress, options)
}
} else if options.Reality != nil && options.Reality.Enabled { } else if options.Reality != nil && options.Reality.Enabled {
return NewRealityClient(ctx, serverAddress, options) return NewRealityClient(ctx, serverAddress, options)
} else if options.UTLS != nil && options.UTLS.Enabled { } else if options.UTLS != nil && options.UTLS.Enabled {
return NewUTLSClient(ctx, serverAddress, options) return NewUTLSClient(ctx, serverAddress, options)
} else {
return NewSTDClient(ctx, serverAddress, options)
} }
return NewSTDClient(ctx, serverAddress, options)
} }
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) { func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {

View File

@@ -7,7 +7,6 @@ import (
"encoding/binary" "encoding/binary"
"encoding/pem" "encoding/pem"
cftls "github.com/sagernet/cloudflare-tls"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/cloudflare/circl/hpke" "github.com/cloudflare/circl/hpke"
@@ -59,7 +58,6 @@ func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (config
type echKeyConfigPair struct { type echKeyConfigPair struct {
id uint8 id uint8
key cftls.EXP_ECHKey
rawKey []byte rawKey []byte
conf myECHKeyConfig conf myECHKeyConfig
rawConf []byte rawConf []byte
@@ -153,14 +151,13 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
sk = be.AppendUint16(sk, uint16(len(b))) sk = be.AppendUint16(sk, uint16(len(b)))
sk = append(sk, b...) sk = append(sk, b...)
cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk) cfECHKeys, err := UnmarshalECHKeys(sk)
if err != nil { if err != nil {
return nil, E.Cause(err, "bug: can't parse generated ECH server key") return nil, E.Cause(err, "bug: can't parse generated ECH server key")
} }
if len(cfECHKeys) != 1 { if len(cfECHKeys) != 1 {
return nil, E.New("bug: unexpected server key count") return nil, E.New("bug: unexpected server key count")
} }
pair.key = cfECHKeys[0]
pair.rawKey = sk pair.rawKey = sk
pairs = append(pairs, pair) pairs = append(pairs, pair)

View File

@@ -17,12 +17,13 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS
return nil, nil return nil, nil
} }
if options.ECH != nil && options.ECH.Enabled { if options.ECH != nil && options.ECH.Enabled {
return NewECHServer(ctx, logger, options) if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
return NewECHServer(ctx, logger, options)
}
} else if options.Reality != nil && options.Reality.Enabled { } else if options.Reality != nil && options.Reality.Enabled {
return NewRealityServer(ctx, logger, options) return NewRealityServer(ctx, logger, options)
} else {
return NewSTDServer(ctx, logger, options)
} }
return NewSTDServer(ctx, logger, options)
} }
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) { func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {

View File

@@ -4,16 +4,25 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64"
"net" "net"
"net/netip" "net/netip"
"os" "os"
"strings" "strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
aTLS "github.com/sagernet/sing/common/tls"
"github.com/sagernet/sing/service"
mDNS "github.com/miekg/dns"
) )
var _ ConfigCompat = (*STDClientConfig)(nil)
type STDClientConfig struct { type STDClientConfig struct {
config *tls.Config config *tls.Config
} }
@@ -46,6 +55,63 @@ func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()} return &STDClientConfig{s.config.Clone()}
} }
type STDECHClientConfig struct {
STDClientConfig
}
func (s *STDClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
if len(s.config.EncryptedClientHelloConfigList) == 0 {
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
},
Question: []mDNS.Question{
{
Name: mDNS.Fqdn(s.config.ServerName),
Qtype: mDNS.TypeHTTPS,
Qclass: mDNS.ClassINET,
},
},
}
dnsRouter := service.FromContext[adapter.Router](ctx)
response, err := dnsRouter.Exchange(ctx, message)
if err != nil {
return nil, E.Cause(err, "fetch ECH config list")
}
if response.Rcode != mDNS.RcodeSuccess {
return nil, E.Cause(dns.RCodeError(response.Rcode), "fetch ECH config list")
}
for _, rr := range response.Answer {
switch resource := rr.(type) {
case *mDNS.HTTPS:
for _, value := range resource.Value {
if value.Key().String() == "ech" {
echConfigList, err := base64.StdEncoding.DecodeString(value.String())
if err != nil {
return nil, E.Cause(err, "decode ECH config")
}
s.config.EncryptedClientHelloConfigList = echConfigList
}
}
}
}
return nil, E.New("no ECH config found in DNS records")
}
tlsConn, err := s.Client(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
}
func (s *STDECHClientConfig) Clone() Config {
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}}
}
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
var serverName string var serverName string
if options.ServerName != "" { if options.ServerName != "" {
@@ -128,5 +194,21 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
} }
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = certPool
} }
if options.ECH != nil && options.ECH.Enabled {
var echConfig []byte
if len(options.ECH.Config) > 0 {
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
} else if options.ECH.ConfigPath != "" {
content, err := os.ReadFile(options.ECH.ConfigPath)
if err != nil {
return nil, E.Cause(err, "read ECH config")
}
echConfig = content
}
if echConfig != nil {
tlsConfig.EncryptedClientHelloConfigList = echConfig
}
return &STDECHClientConfig{STDClientConfig{&tlsConfig}}, nil
}
return &STDClientConfig{&tlsConfig}, nil return &STDClientConfig{&tlsConfig}, nil
} }

View File

@@ -3,6 +3,7 @@ package tls
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/pem"
"net" "net"
"os" "os"
"strings" "strings"
@@ -14,6 +15,8 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
"golang.org/x/crypto/cryptobyte"
) )
var errInsecureUnused = E.New("tls: insecure unused") var errInsecureUnused = E.New("tls: insecure unused")
@@ -238,6 +241,31 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
tlsConfig.Certificates = []tls.Certificate{keyPair} tlsConfig.Certificates = []tls.Certificate{keyPair}
} }
} }
if options.ECH != nil && options.ECH.Enabled {
var echKey []byte
if len(options.ECH.Key) > 0 {
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
} else if options.ECH.KeyPath != "" {
content, err := os.ReadFile(options.ECH.KeyPath)
if err != nil {
return nil, E.Cause(err, "read ECH key")
}
echKey = content
} else {
return nil, E.New("missing ECH key")
}
block, rest := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return nil, E.New("invalid ECH keys pem")
}
echKeys, err := UnmarshalECHKeys(block.Bytes)
if err != nil {
return nil, E.Cause(err, "parse ECH keys")
}
tlsConfig.EncryptedClientHelloKeys = echKeys
}
return &STDServerConfig{ return &STDServerConfig{
config: tlsConfig, config: tlsConfig,
logger: logger, logger: logger,
@@ -248,3 +276,22 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
keyPath: options.KeyPath, keyPath: options.KeyPath,
}, nil }, nil
} }
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
var keys []tls.EncryptedClientHelloKey
rawString := cryptobyte.String(raw)
for !rawString.Empty() {
var key tls.EncryptedClientHelloKey
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
return nil, E.New("error parsing private key")
}
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {
return nil, E.New("error parsing config")
}
keys = append(keys, key)
}
if len(keys) == 0 {
return nil, E.New("empty ECH keys")
}
return keys, nil
}

View File

@@ -0,0 +1,8 @@
//go:build android && debug
package constant
// TODO: remove after fixed
// https://github.com/golang/go/issues/68760
const FixAndroidStack = true

View File

@@ -0,0 +1,5 @@
//go:build !(android && debug)
package constant
const FixAndroidStack = false

7
constant/hysteria2.go Normal file
View File

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

View File

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

View File

@@ -9,8 +9,6 @@ const (
TCPTimeout = 15 * time.Second TCPTimeout = 15 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 3 * time.Minute DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute DefaultURLTestIdleTimeout = 30 * time.Minute
@@ -19,3 +17,18 @@ const (
FatalStopTimeout = 10 * time.Second FatalStopTimeout = 10 * time.Second
FakeIPMetadataSaveInterval = 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

@@ -2,6 +2,37 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.11.0-beta.9
* 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.10.3
* Fixes and improvements
#### 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 #### 1.11.0-alpha.20
* Add UDP GSO support for WireGuard * Add UDP GSO support for WireGuard

View File

@@ -1,11 +1,19 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.11.0"
:material-alert: [masquerade](#masquerade)
### Structure ### Structure
```json ```json
{ {
"type": "hysteria2", "type": "hysteria2",
"tag": "hy2-in", "tag": "hy2-in",
...
// Listen Fields ... // Listen Fields
"up_mbps": 100, "up_mbps": 100,
"down_mbps": 100, "down_mbps": 100,
@@ -21,7 +29,7 @@
], ],
"ignore_client_bandwidth": false, "ignore_client_bandwidth": false,
"tls": {}, "tls": {},
"masquerade": "", "masquerade": "", // or {}
"brutal_debug": false "brutal_debug": false
} }
``` ```
@@ -79,14 +87,54 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
#### masquerade #### masquerade
HTTP3 server behavior when authentication fails. HTTP3 server behavior (URL string configuration) when authentication fails.
| Scheme | Example | Description | | Scheme | Example | Description |
|--------------|-------------------------|--------------------| |--------------|-------------------------|--------------------|
| `file` | `file:///var/www` | As a file server | | `file` | `file:///var/www` | As a file server |
| `http/https` | `http://127.0.0.1:8080` | As a reverse proxy | | `http/https` | `http://127.0.0.1:8080` | As a reverse proxy |
A 404 page will be returned if empty. Conflict with `masquerade.type`.
A 404 page will be returned if masquerade is not configured.
#### masquerade.type
HTTP3 server behavior (Object configuration) when authentication fails.
| Type | Description | Fields |
|----------|-----------------------------|-------------------------------------|
| `file` | As a file server | `directory` |
| `proxy` | As a reverse proxy | `url`, `rewrite_host` |
| `string` | Reply with a fixed response | `status_code`, `headers`, `content` |
Conflict with `masquerade`.
A 404 page will be returned if masquerade is not configured.
#### masquerade.directory
File server root directory.
#### masquerade.url
Reverse proxy target URL.
#### masquerade.rewrite_host
Rewrite the `Host` header to the target URL.
#### masquerade.status_code
Fixed response status code.
#### masquerade.headers
Fixed response headers.
#### masquerade.content
Fixed response content.
#### brutal_debug #### brutal_debug

View File

@@ -1,11 +1,19 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.11.0 中的更改"
:material-alert: [masquerade](#masquerade)
### 结构 ### 结构
```json ```json
{ {
"type": "hysteria2", "type": "hysteria2",
"tag": "hy2-in", "tag": "hy2-in",
...
// 监听字段 ... // 监听字段
"up_mbps": 100, "up_mbps": 100,
"down_mbps": 100, "down_mbps": 100,
@@ -21,7 +29,7 @@
], ],
"ignore_client_bandwidth": false, "ignore_client_bandwidth": false,
"tls": {}, "tls": {},
"masquerade": "", "masquerade": "", // 或 {}
"brutal_debug": false "brutal_debug": false
} }
``` ```
@@ -76,14 +84,54 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### masquerade #### masquerade
HTTP3 服务器认证失败时的行为。 HTTP3 服务器认证失败时的行为 URL 字符串配置)
| Scheme | 示例 | 描述 | | Scheme | 示例 | 描述 |
|--------------|-------------------------|---------| |--------------|-------------------------|---------|
| `file` | `file:///var/www` | 作为文件服务器 | | `file` | `file:///var/www` | 作为文件服务器 |
| `http/https` | `http://127.0.0.1:8080` | 作为反向代理 | | `http/https` | `http://127.0.0.1:8080` | 作为反向代理 |
如果为空,则返回 404 页。 如果 masquerade 未配置,则返回 404 页。
`masquerade.type` 冲突。
#### masquerade.type
HTTP3 服务器认证失败时的行为 (对象配置)。
| Type | 描述 | 字段 |
|----------|---------|-------------------------------------|
| `file` | 作为文件服务器 | `directory` |
| `proxy` | 作为反向代理 | `url`, `rewrite_host` |
| `string` | 返回固定响应 | `status_code`, `headers`, `content` |
如果 masquerade 未配置,则返回 404 页。
`masquerade` 冲突。
#### masquerade.directory
文件服务器根目录。
#### masquerade.url
反向代理目标 URL。
#### masquerade.rewrite_host
重写请求头中的 Host 字段到目标 URL。
#### masquerade.status_code
固定响应状态码。
#### masquerade.headers
固定响应头。
#### masquerade.content
固定响应内容。
#### brutal_debug #### brutal_debug

View File

@@ -4,8 +4,8 @@ icon: material/alert-decagram
!!! quote "Changes in sing-box 1.11.0" !!! quote "Changes in sing-box 1.11.0"
:material-alert-decagram: [override_address](#override_address) :material-delete-clock: [override_address](#override_address)
:material-alert-decagram: [override_port](#override_port) :material-delete-clock: [override_port](#override_port)
`direct` outbound send requests directly. `direct` outbound send requests directly.

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ func groupRouter(server *Server) http.Handler {
func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) { func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
groups := common.Map(common.Filter(server.outboundManager.Outbounds(), func(it adapter.Outbound) bool { groups := common.Map(common.Filter(server.outbound.Outbounds(), func(it adapter.Outbound) bool {
_, isGroup := it.(adapter.OutboundGroup) _, isGroup := it.(adapter.OutboundGroup)
return isGroup return isGroup
}), func(it adapter.Outbound) *badjson.JSONObject { }), 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) result, err = urlTestGroup.URLTest(ctx)
} else { } else {
outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
itOutbound, _ := server.outboundManager.Outbound(it) itOutbound, _ := server.outbound.Outbound(it)
return itOutbound return itOutbound
})) }))
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10)) b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
@@ -100,7 +100,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
continue continue
} }
checked[realTag] = true checked[realTag] = true
p, loaded := server.outboundManager.Outbound(realTag) p, loaded := server.outbound.Outbound(realTag)
if !loaded { if !loaded {
continue continue
} }

View File

@@ -18,17 +18,19 @@ func configRouter(server *Server, logFactory log.Factory) http.Handler {
} }
type configSchema struct { type configSchema struct {
Port int `json:"port"` Port int `json:"port"`
SocksPort int `json:"socks-port"` SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"` RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"` TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"` MixedPort int `json:"mixed-port"`
AllowLan bool `json:"allow-lan"` AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"` BindAddress string `json:"bind-address"`
Mode string `json:"mode"` Mode string `json:"mode"`
LogLevel string `json:"log-level"` // sing-box added
IPv6 bool `json:"ipv6"` ModeList []string `json:"mode-list"`
Tun map[string]any `json:"tun"` 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) { func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) {
@@ -41,6 +43,7 @@ func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWrit
} }
render.JSON(w, r, &configSchema{ render.JSON(w, r, &configSchema{
Mode: server.mode, Mode: server.mode,
ModeList: server.modeList,
BindAddress: "*", BindAddress: "*",
LogLevel: log.FormatLevel(logLevel), 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 func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProxyName).(string) name := r.Context().Value(CtxKeyProxyName).(string)
proxy, exist := server.outboundManager.Outbound(name) proxy, exist := server.outbound.Outbound(name)
if !exist { if !exist {
render.Status(r, http.StatusNotFound) render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound) render.JSON(w, r, ErrNotFound)
@@ -86,9 +86,14 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) { func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var proxyMap badjson.JSONObject var proxyMap badjson.JSONObject
outbounds := common.Filter(server.outboundManager.Outbounds(), func(detour adapter.Outbound) bool { outbounds := common.Filter(server.outbound.Outbounds(), func(detour adapter.Outbound) bool {
return detour.Tag() != "" 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)) allProxies := make([]string, 0, len(outbounds))
@@ -100,7 +105,7 @@ func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {
allProxies = append(allProxies, detour.Tag()) allProxies = append(allProxies, detour.Tag())
} }
defaultTag := server.outboundManager.Default().Tag() defaultTag := server.outbound.Default().Tag()
sort.SliceStable(allProxies, func(i, j int) bool { sort.SliceStable(allProxies, func(i, j int) bool {
return allProxies[i] == defaultTag return allProxies[i] == defaultTag

View File

@@ -40,16 +40,17 @@ func init() {
var _ adapter.ClashServer = (*Server)(nil) var _ adapter.ClashServer = (*Server)(nil)
type Server struct { type Server struct {
ctx context.Context ctx context.Context
router adapter.Router router adapter.Router
outboundManager adapter.OutboundManager outbound adapter.OutboundManager
logger log.Logger endpoint adapter.EndpointManager
httpServer *http.Server logger log.Logger
trafficManager *trafficontrol.Manager httpServer *http.Server
urlTestHistory *urltest.HistoryStorage trafficManager *trafficontrol.Manager
mode string urlTestHistory *urltest.HistoryStorage
modeList []string mode string
modeUpdateHook chan<- struct{} modeList []string
modeUpdateHook chan<- struct{}
externalController bool externalController bool
externalUI string externalUI string
@@ -61,10 +62,11 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
trafficManager := trafficontrol.NewManager() trafficManager := trafficontrol.NewManager()
chiRouter := chi.NewRouter() chiRouter := chi.NewRouter()
s := &Server{ s := &Server{
ctx: ctx, ctx: ctx,
router: service.FromContext[adapter.Router](ctx), router: service.FromContext[adapter.Router](ctx),
outboundManager: service.FromContext[adapter.OutboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx),
logger: logFactory.NewLogger("clash-api"), endpoint: service.FromContext[adapter.EndpointManager](ctx),
logger: logFactory.NewLogger("clash-api"),
httpServer: &http.Server{ httpServer: &http.Server{
Addr: options.ExternalController, Addr: options.ExternalController,
Handler: chiRouter, Handler: chiRouter,
@@ -242,11 +244,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 { 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.outboundManager, matchedRule, matchOutbound) return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound)
} }
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn { 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.outboundManager, matchedRule, matchOutbound) return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outbound, matchedRule, matchOutbound)
} }
func authentication(serverSecret string) func(next http.Handler) http.Handler { func authentication(serverSecret string) func(next http.Handler) http.Handler {
@@ -321,27 +323,29 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter,
tick := time.NewTicker(time.Second) tick := time.NewTicker(time.Second)
defer tick.Stop() defer tick.Stop()
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
var err error uploadTotal, downloadTotal := trafficManager.Total()
for range tick.C { for range tick.C {
buf.Reset() buf.Reset()
up, down := trafficManager.Now() uploadTotalNew, downloadTotalNew := trafficManager.Total()
if err := json.NewEncoder(buf).Encode(Traffic{ err := json.NewEncoder(buf).Encode(Traffic{
Up: up, Up: uploadTotalNew - uploadTotal,
Down: down, Down: downloadTotalNew - downloadTotal,
}); err != nil { })
if err != nil {
break break
} }
if conn == nil { if conn == nil {
_, err = w.Write(buf.Bytes()) _, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush() w.(http.Flusher).Flush()
} else { } else {
err = wsutil.WriteServerText(conn, buf.Bytes()) err = wsutil.WriteServerText(conn, buf.Bytes())
} }
if err != nil { if err != nil {
break break
} }
uploadTotal = uploadTotalNew
downloadTotal = downloadTotalNew
} }
} }
} }

View File

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

View File

@@ -16,30 +16,18 @@ import (
) )
type Manager struct { type Manager struct {
uploadTemp atomic.Int64
downloadTemp atomic.Int64
uploadBlip atomic.Int64
downloadBlip atomic.Int64
uploadTotal atomic.Int64 uploadTotal atomic.Int64
downloadTotal atomic.Int64 downloadTotal atomic.Int64
connections compatible.Map[uuid.UUID, Tracker] connections compatible.Map[uuid.UUID, Tracker]
closedConnectionsAccess sync.Mutex closedConnectionsAccess sync.Mutex
closedConnections list.List[TrackerMetadata] closedConnections list.List[TrackerMetadata]
ticker *time.Ticker
done chan struct{}
// process *process.Process // process *process.Process
memory uint64 memory uint64
} }
func NewManager() *Manager { func NewManager() *Manager {
manager := &Manager{ return &Manager{}
ticker: time.NewTicker(time.Second),
done: make(chan struct{}),
// process: &process.Process{Pid: int32(os.Getpid())},
}
go manager.handle()
return manager
} }
func (m *Manager) Join(c Tracker) { func (m *Manager) Join(c Tracker) {
@@ -61,19 +49,13 @@ func (m *Manager) Leave(c Tracker) {
} }
func (m *Manager) PushUploaded(size int64) { func (m *Manager) PushUploaded(size int64) {
m.uploadTemp.Add(size)
m.uploadTotal.Add(size) m.uploadTotal.Add(size)
} }
func (m *Manager) PushDownloaded(size int64) { func (m *Manager) PushDownloaded(size int64) {
m.downloadTemp.Add(size)
m.downloadTotal.Add(size) m.downloadTotal.Add(size)
} }
func (m *Manager) Now() (up int64, down int64) {
return m.uploadBlip.Load(), m.downloadBlip.Load()
}
func (m *Manager) Total() (up int64, down int64) { func (m *Manager) Total() (up int64, down int64) {
return m.uploadTotal.Load(), m.downloadTotal.Load() return m.uploadTotal.Load(), m.downloadTotal.Load()
} }
@@ -127,36 +109,10 @@ func (m *Manager) Snapshot() *Snapshot {
} }
func (m *Manager) ResetStatistic() { func (m *Manager) ResetStatistic() {
m.uploadTemp.Store(0)
m.uploadBlip.Store(0)
m.uploadTotal.Store(0) m.uploadTotal.Store(0)
m.downloadTemp.Store(0)
m.downloadBlip.Store(0)
m.downloadTotal.Store(0) m.downloadTotal.Store(0)
} }
func (m *Manager) handle() {
var uploadTemp int64
var downloadTemp int64
for {
select {
case <-m.done:
return
case <-m.ticker.C:
}
uploadTemp = m.uploadTemp.Swap(0)
downloadTemp = m.downloadTemp.Swap(0)
m.uploadBlip.Store(uploadTemp)
m.downloadBlip.Store(downloadTemp)
}
}
func (m *Manager) Close() error {
m.ticker.Stop()
close(m.done)
return nil
}
type Snapshot struct { type Snapshot struct {
Download int64 Download int64
Upload int64 Upload int64

View File

@@ -7,6 +7,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
@@ -113,11 +114,24 @@ func (c *CommandClient) Connect() error {
if err != nil { if err != nil {
return err return err
} }
c.handler.Connected() if C.FixAndroidStack {
c.handler.InitializeClashMode(newIterator(modeList), currentMode) go func() {
c.handler.Connected()
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
if len(modeList) == 0 {
conn.Close()
c.handler.Disconnected(os.ErrInvalid.Error())
}
}()
} else {
c.handler.Connected()
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
if len(modeList) == 0 {
conn.Close()
c.handler.Disconnected(os.ErrInvalid.Error())
}
}
if len(modeList) == 0 { if len(modeList) == 0 {
conn.Close()
c.handler.Disconnected(os.ErrInvalid.Error())
return nil return nil
} }
go c.handleModeConn(conn) go c.handleModeConn(conn)

View File

@@ -33,7 +33,6 @@ func (s *CommandServer) readStatus() StatusMessage {
if s.service != nil { if s.service != nil {
message.TrafficAvailable = true message.TrafficAvailable = true
trafficManager := s.service.clashServer.(*clashapi.Server).TrafficManager() trafficManager := s.service.clashServer.(*clashapi.Server).TrafficManager()
message.Uplink, message.Downlink = trafficManager.Now()
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
} }
@@ -50,8 +49,11 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
ticker := time.NewTicker(time.Duration(interval)) ticker := time.NewTicker(time.Duration(interval))
defer ticker.Stop() defer ticker.Stop()
ctx := connKeepAlive(conn) ctx := connKeepAlive(conn)
status := s.readStatus()
uploadTotal := status.UplinkTotal
downloadTotal := status.DownlinkTotal
for { for {
err = binary.Write(conn, binary.BigEndian, s.readStatus()) err = binary.Write(conn, binary.BigEndian, status)
if err != nil { if err != nil {
return err return err
} }
@@ -60,6 +62,13 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
return ctx.Err() return ctx.Err()
case <-ticker.C: case <-ticker.C:
} }
status = s.readStatus()
upload := status.UplinkTotal - uploadTotal
download := status.DownlinkTotal - downloadTotal
uploadTotal = status.UplinkTotal
downloadTotal = status.DownlinkTotal
status.Uplink = upload
status.Downlink = download
} }
} }

View File

@@ -130,17 +130,17 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica
return nil return nil
} }
func FormatConfig(configContent string) (string, error) { func FormatConfig(configContent string) (*StringBox, error) {
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()), configContent) options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()), configContent)
if err != nil { if err != nil {
return "", err return nil, err
} }
var buffer bytes.Buffer var buffer bytes.Buffer
encoder := json.NewEncoder(&buffer) encoder := json.NewEncoder(&buffer)
encoder.SetIndent("", " ") encoder.SetIndent("", " ")
err = encoder.Encode(options) err = encoder.Encode(options)
if err != nil { if err != nil {
return "", err return nil, err
} }
return buffer.String(), nil return wrapString(buffer.String()), nil
} }

View File

@@ -50,8 +50,7 @@ type HTTPRequest interface {
} }
type HTTPResponse interface { type HTTPResponse interface {
GetContent() ([]byte, error) GetContent() (*StringBox, error)
GetContentString() (string, error)
WriteTo(path string) error WriteTo(path string) error
} }
@@ -210,27 +209,22 @@ type httpResponse struct {
} }
func (h *httpResponse) errorString() string { func (h *httpResponse) errorString() string {
content, err := h.GetContentString() content, err := h.GetContent()
if err != nil { if err != nil {
return fmt.Sprint("HTTP ", h.Status) return fmt.Sprint("HTTP ", h.Status)
} }
return fmt.Sprint("HTTP ", h.Status, ": ", content) return fmt.Sprint("HTTP ", h.Status, ": ", content)
} }
func (h *httpResponse) GetContent() ([]byte, error) { func (h *httpResponse) GetContent() (*StringBox, error) {
h.getContentOnce.Do(func() { h.getContentOnce.Do(func() {
defer h.Body.Close() defer h.Body.Close()
h.content, h.contentError = io.ReadAll(h.Body) h.content, h.contentError = io.ReadAll(h.Body)
}) })
return h.content, h.contentError if h.contentError != nil {
} return nil, h.contentError
func (h *httpResponse) GetContentString() (string, error) {
content, err := h.GetContent()
if err != nil {
return "", err
} }
return string(content), nil return wrapString(string(h.content)), nil
} }
func (h *httpResponse) WriteTo(path string) error { func (h *httpResponse) WriteTo(path string) error {

View File

@@ -1,6 +1,7 @@
package libbox package libbox
import ( import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@@ -55,6 +56,14 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
} }
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
if C.FixAndroidStack {
go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
} else {
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
}
}
func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
m.isExpensive = isExpensive m.isExpensive = isExpensive
m.isConstrained = isConstrained m.isConstrained = isConstrained
err := m.networkManager.UpdateInterfaces() err := m.networkManager.UpdateInterfaces()

View File

@@ -0,0 +1,12 @@
package libbox
// https://github.com/golang/go/issues/46893
// TODO: remove after `bulkBarrierPreWrite: unaligned arguments` fixed
type StringBox struct {
Value string
}
func wrapString(value string) *StringBox {
return &StringBox{Value: value}
}

View File

@@ -81,23 +81,36 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
} }
func (s *BoxService) Start() error { func (s *BoxService) Start() error {
return s.instance.Start() if C.FixAndroidStack {
var err error
done := make(chan struct{})
go func() {
err = s.instance.Start()
close(done)
}()
<-done
return err
} else {
return s.instance.Start()
}
} }
func (s *BoxService) Close() error { func (s *BoxService) Close() error {
done := make(chan struct{})
defer close(done)
go func() {
select {
case <-done:
return
case <-time.After(C.FatalStopTimeout):
os.Exit(1)
}
}()
s.cancel() s.cancel()
s.urlTestHistoryStorage.Close() s.urlTestHistoryStorage.Close()
return s.instance.Close() var err error
done := make(chan struct{})
go func() {
err = s.instance.Close()
close(done)
}()
select {
case <-done:
return err
case <-time.After(C.FatalStopTimeout):
os.Exit(1)
return nil
}
} }
func (s *BoxService) NeedWIFIState() bool { func (s *BoxService) NeedWIFIState() bool {

View File

@@ -13,12 +13,12 @@ func ClearServiceError() {
os.Remove(serviceErrorPath()) os.Remove(serviceErrorPath())
} }
func ReadServiceError() (string, error) { func ReadServiceError() (*StringBox, error) {
data, err := os.ReadFile(serviceErrorPath()) data, err := os.ReadFile(serviceErrorPath())
if err == nil { if err == nil {
os.Remove(serviceErrorPath()) os.Remove(serviceErrorPath())
} }
return string(data), err return wrapString(string(data)), err
} }
func WriteServiceError(message string) error { func WriteServiceError(message string) error {

12
go.mod
View File

@@ -23,20 +23,20 @@ require (
github.com/sagernet/fswatch v0.1.1 github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gomobile v0.1.4
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/quic-go v0.48.2-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.0-alpha.18 github.com/sagernet/sing v0.6.0-beta.6
github.com/sagernet/sing-dns v0.4.0-alpha.3 github.com/sagernet/sing-dns v0.4.0-beta.1
github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.4.0-alpha.4 github.com/sagernet/sing-quic v0.4.0-alpha.4
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 github.com/sagernet/sing-shadowtls v0.2.0-alpha.2
github.com/sagernet/sing-tun v0.6.0-alpha.14 github.com/sagernet/sing-tun v0.6.0-beta.2
github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/sing-vmess v0.2.0-beta.1
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/utls v1.6.7 github.com/sagernet/utls v1.6.7
github.com/sagernet/wireguard-go v0.0.1-beta.4 github.com/sagernet/wireguard-go v0.0.1-beta.5
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0

24
go.sum
View File

@@ -105,15 +105,15 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AEpT94Z0vwo= github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg=
github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-alpha.18 h1:ih4CurU8KvbhfagYjSqVrE2LR0oBSXSZTNH2sAGPGiM= github.com/sagernet/sing v0.6.0-beta.6 h1:IFnTCG06Z5rLMZJqw1ZmDncDl2N9gsVw0MGvgakrpg8=
github.com/sagernet/sing v0.6.0-alpha.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-alpha.3 h1:TcAQdz68Gs28VD9o9zDIW7IS8A9LZDruTPI9g9JbGHA= github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
github.com/sagernet/sing-dns v0.4.0-alpha.3/go.mod h1:9LHcYKg2bGQpbtXrfNbopz8ok/zBK9ljiI2kmFG9JKg= github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ= github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ=
@@ -124,16 +124,16 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
github.com/sagernet/sing-tun v0.6.0-alpha.14 h1:0nE66HdC6nBSOaUG0CEV5rwB5Te3Gts9buVOPvWrGT4= github.com/sagernet/sing-tun v0.6.0-beta.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO3OlYMcQJ0=
github.com/sagernet/sing-tun v0.6.0-alpha.14/go.mod h1:xvZlEl1EGBbQeshv4UXmG7hA3f0ngFjpdCIYk308vfg= github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM= github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
github.com/sagernet/wireguard-go v0.0.1-beta.4 h1:8uyM5fxfEXdu4RH05uOK+v25i3lTNdCYMPSAUJ14FnI= github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
github.com/sagernet/wireguard-go v0.0.1-beta.4/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=

View File

@@ -1,5 +1,15 @@
package option package option
import (
"net/url"
C "github.com/sagernet/sing-box/constant"
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/json/badoption"
)
type Hysteria2InboundOptions struct { type Hysteria2InboundOptions struct {
ListenOptions ListenOptions
UpMbps int `json:"up_mbps,omitempty"` UpMbps int `json:"up_mbps,omitempty"`
@@ -8,8 +18,8 @@ type Hysteria2InboundOptions struct {
Users []Hysteria2User `json:"users,omitempty"` Users []Hysteria2User `json:"users,omitempty"`
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"` IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
InboundTLSOptionsContainer InboundTLSOptionsContainer
Masquerade string `json:"masquerade,omitempty"` Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"`
BrutalDebug bool `json:"brutal_debug,omitempty"` BrutalDebug bool `json:"brutal_debug,omitempty"`
} }
type Hysteria2Obfs struct { type Hysteria2Obfs struct {
@@ -22,6 +32,82 @@ type Hysteria2User struct {
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
} }
type _Hysteria2Masquerade struct {
Type string `json:"type,omitempty"`
FileOptions Hysteria2MasqueradeFile `json:"-"`
ProxyOptions Hysteria2MasqueradeProxy `json:"-"`
StringOptions Hysteria2MasqueradeString `json:"-"`
}
type Hysteria2Masquerade _Hysteria2Masquerade
func (m Hysteria2Masquerade) MarshalJSON() ([]byte, error) {
var v any
switch m.Type {
case C.Hysterai2MasqueradeTypeFile:
v = m.FileOptions
case C.Hysterai2MasqueradeTypeProxy:
v = m.ProxyOptions
case C.Hysterai2MasqueradeTypeString:
v = m.StringOptions
default:
return nil, E.New("unknown masquerade type: ", m.Type)
}
return badjson.MarshallObjects((_Hysteria2Masquerade)(m), v)
}
func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error {
var urlString string
err := json.Unmarshal(bytes, &urlString)
if err == nil {
masqueradeURL, err := url.Parse(urlString)
if err != nil {
return E.Cause(err, "invalid masquerade URL")
}
switch masqueradeURL.Scheme {
case "file":
m.Type = C.Hysterai2MasqueradeTypeFile
m.FileOptions.Directory = masqueradeURL.Path
case "http", "https":
m.Type = C.Hysterai2MasqueradeTypeProxy
m.ProxyOptions.URL = urlString
default:
return E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme)
}
}
err = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m))
if err != nil {
return err
}
var v any
switch m.Type {
case C.Hysterai2MasqueradeTypeFile:
v = &m.FileOptions
case C.Hysterai2MasqueradeTypeProxy:
v = &m.ProxyOptions
case C.Hysterai2MasqueradeTypeString:
v = &m.StringOptions
default:
return E.New("unknown masquerade type: ", m.Type)
}
return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Masquerade)(m), v)
}
type Hysteria2MasqueradeFile struct {
Directory string `json:"directory"`
}
type Hysteria2MasqueradeProxy struct {
URL string `json:"url"`
RewriteHost bool `json:"rewrite_host,omitempty"`
}
type Hysteria2MasqueradeString struct {
StatusCode int `json:"status_code,omitempty"`
Headers badoption.HTTPHeader `json:"headers,omitempty"`
Content string `json:"content"`
}
type Hysteria2OutboundOptions struct { type Hysteria2OutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions

View File

@@ -12,7 +12,7 @@ type RouteOptions struct {
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
DefaultInterface string `json:"default_interface,omitempty"` DefaultInterface string `json:"default_interface,omitempty"`
DefaultMark uint32 `json:"default_mark,omitempty"` DefaultMark FwMark `json:"default_mark,omitempty"`
DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"`
DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"` DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"`
DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"` DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"`

View File

@@ -148,8 +148,9 @@ type RawRouteOptionsActionOptions struct {
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
FallbackDelay uint32 `json:"fallback_delay,omitempty"` FallbackDelay uint32 `json:"fallback_delay,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
UDPConnect bool `json:"udp_connect,omitempty"` UDPConnect bool `json:"udp_connect,omitempty"`
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
} }
type RouteOptionsActionOptions RawRouteOptionsActionOptions type RouteOptionsActionOptions RawRouteOptionsActionOptions

View File

@@ -34,7 +34,6 @@ type TunInboundOptions struct {
IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"` IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"`
IncludePackage badoption.Listable[string] `json:"include_package,omitempty"` IncludePackage badoption.Listable[string] `json:"include_package,omitempty"`
ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"` ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"`
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
Stack string `json:"stack,omitempty"` Stack string `json:"stack,omitempty"`
Platform *TunPlatformOptions `json:"platform,omitempty"` Platform *TunPlatformOptions `json:"platform,omitempty"`
@@ -54,6 +53,8 @@ type TunInboundOptions struct {
Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"` Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
// Deprecated: merged to RouteExcludeAddress // Deprecated: merged to RouteExcludeAddress
Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"` Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
// Deprecated: removed
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
} }
type FwMark uint32 type FwMark uint32

View File

@@ -14,7 +14,7 @@ type WireGuardEndpointOptions struct {
PrivateKey string `json:"private_key"` PrivateKey string `json:"private_key"`
ListenPort uint16 `json:"listen_port,omitempty"` ListenPort uint16 `json:"listen_port,omitempty"`
Peers []WireGuardPeer `json:"peers,omitempty"` Peers []WireGuardPeer `json:"peers,omitempty"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
Workers int `json:"workers,omitempty"` Workers int `json:"workers,omitempty"`
DialerOptions DialerOptions
} }

View File

@@ -95,6 +95,9 @@ func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
} }
func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = i.Tag()
metadata.InboundType = i.Type()
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr())
switch i.overrideOption { switch i.overrideOption {
case 1: case 1:
metadata.Destination = i.overrideDestination metadata.Destination = i.overrideDestination
@@ -105,11 +108,9 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
case 3: case 3:
metadata.Destination.Port = i.overrideDestination.Port metadata.Destination.Port = i.overrideDestination.Port
} }
i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) if i.overrideOption != 0 {
metadata.Inbound = i.Tag() i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
metadata.InboundType = i.Type() }
metadata.InboundDetour = i.listener.ListenOptions().Detour
metadata.InboundOptions = i.listener.ListenOptions().InboundOptions
i.router.RouteConnectionEx(ctx, conn, metadata, onClose) i.router.RouteConnectionEx(ctx, conn, metadata, onClose)
} }
@@ -119,7 +120,9 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = i.Tag() metadata.Inbound = i.Tag()
metadata.InboundType = i.Type() metadata.InboundType = i.Type()
//nolint:staticcheck
metadata.InboundDetour = i.listener.ListenOptions().Detour metadata.InboundDetour = i.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = i.listener.ListenOptions().InboundOptions metadata.InboundOptions = i.listener.ListenOptions().InboundOptions
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination

View File

@@ -63,9 +63,11 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
dialer: outboundDialer, dialer: outboundDialer,
// loopBack: newLoopBackDetector(router), // loopBack: newLoopBackDetector(router),
} }
//nolint:staticcheck
if options.ProxyProtocol != 0 { if options.ProxyProtocol != 0 {
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
} }
//nolint:staticcheck
if options.OverrideAddress != "" && options.OverridePort != 0 { if options.OverrideAddress != "" && options.OverridePort != 0 {
outbound.overrideOption = 1 outbound.overrideOption = 1
outbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) outbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort)
@@ -161,6 +163,7 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
if h.domainStrategy != dns.DomainStrategyAsIS { if h.domainStrategy != dns.DomainStrategyAsIS {
domainStrategy = h.domainStrategy domainStrategy = h.domainStrategy
} else { } else {
//nolint:staticcheck
domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)
} }
switch domainStrategy { switch domainStrategy {
@@ -200,6 +203,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
if h.domainStrategy != dns.DomainStrategyAsIS { if h.domainStrategy != dns.DomainStrategyAsIS {
domainStrategy = h.domainStrategy domainStrategy = h.domainStrategy
} else { } else {
//nolint:staticcheck
domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) domainStrategy = dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)
} }
switch domainStrategy { switch domainStrategy {

View File

@@ -42,20 +42,21 @@ func (d *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
// Deprecated func (d *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
func (d *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
metadata.Destination = M.Socksaddr{} metadata.Destination = M.Socksaddr{}
defer conn.Close()
for { for {
conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) conn.SetReadDeadline(time.Now().Add(C.DNSTimeout))
err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) err := HandleStreamDNSRequest(ctx, d.router, conn, metadata)
if err != nil { if err != nil {
return err conn.Close()
if onClose != nil {
onClose(err)
}
return
} }
} }
} }
// Deprecated func (d *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
func (d *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { NewDNSPacketConnection(ctx, d.router, conn, nil, metadata)
return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata)
} }

View File

@@ -10,6 +10,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@@ -21,17 +22,22 @@ func RegisterSelector(registry *outbound.Registry) {
outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector) outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector)
} }
var _ adapter.OutboundGroup = (*Selector)(nil) var (
_ adapter.OutboundGroup = (*Selector)(nil)
_ adapter.ConnectionHandlerEx = (*Selector)(nil)
_ adapter.PacketConnectionHandlerEx = (*Selector)(nil)
)
type Selector struct { type Selector struct {
outbound.Adapter outbound.Adapter
ctx context.Context ctx context.Context
outboundManager adapter.OutboundManager outbound adapter.OutboundManager
connection adapter.ConnectionManager
logger logger.ContextLogger logger logger.ContextLogger
tags []string tags []string
defaultTag string defaultTag string
outbounds map[string]adapter.Outbound outbounds map[string]adapter.Outbound
selected adapter.Outbound selected atomic.TypedValue[adapter.Outbound]
interruptGroup *interrupt.Group interruptGroup *interrupt.Group
interruptExternalConnections bool interruptExternalConnections bool
} }
@@ -40,7 +46,8 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL
outbound := &Selector{ outbound := &Selector{
Adapter: outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds), Adapter: outbound.NewAdapter(C.TypeSelector, tag, nil, options.Outbounds),
ctx: ctx, ctx: ctx,
outboundManager: service.FromContext[adapter.OutboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx),
logger: logger, logger: logger,
tags: options.Outbounds, tags: options.Outbounds,
defaultTag: options.Default, defaultTag: options.Default,
@@ -55,15 +62,16 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL
} }
func (s *Selector) Network() []string { func (s *Selector) Network() []string {
if s.selected == nil { selected := s.selected.Load()
if selected == nil {
return []string{N.NetworkTCP, N.NetworkUDP} return []string{N.NetworkTCP, N.NetworkUDP}
} }
return s.selected.Network() return selected.Network()
} }
func (s *Selector) Start() error { func (s *Selector) Start() error {
for i, tag := range s.tags { for i, tag := range s.tags {
detour, loaded := s.outboundManager.Outbound(tag) detour, loaded := s.outbound.Outbound(tag)
if !loaded { if !loaded {
return E.New("outbound ", i, " not found: ", tag) return E.New("outbound ", i, " not found: ", tag)
} }
@@ -77,7 +85,7 @@ func (s *Selector) Start() error {
if selected != "" { if selected != "" {
detour, loaded := s.outbounds[selected] detour, loaded := s.outbounds[selected]
if loaded { if loaded {
s.selected = detour s.selected.Store(detour)
return nil return nil
} }
} }
@@ -89,16 +97,16 @@ func (s *Selector) Start() error {
if !loaded { if !loaded {
return E.New("default outbound not found: ", s.defaultTag) return E.New("default outbound not found: ", s.defaultTag)
} }
s.selected = detour s.selected.Store(detour)
return nil return nil
} }
s.selected = s.outbounds[s.tags[0]] s.selected.Store(s.outbounds[s.tags[0]])
return nil return nil
} }
func (s *Selector) Now() string { func (s *Selector) Now() string {
selected := s.selected selected := s.selected.Load()
if selected == nil { if selected == nil {
return s.tags[0] return s.tags[0]
} }
@@ -114,10 +122,9 @@ func (s *Selector) SelectOutbound(tag string) bool {
if !loaded { if !loaded {
return false return false
} }
if s.selected == detour { if s.selected.Swap(detour) == detour {
return true return true
} }
s.selected = detour
if s.Tag() != "" { if s.Tag() != "" {
cacheFile := service.FromContext[adapter.CacheFile](s.ctx) cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
if cacheFile != nil { if cacheFile != nil {
@@ -132,7 +139,7 @@ func (s *Selector) SelectOutbound(tag string) bool {
} }
func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
conn, err := s.selected.DialContext(ctx, network, destination) conn, err := s.selected.Load().DialContext(ctx, network, destination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -140,32 +147,30 @@ func (s *Selector) DialContext(ctx context.Context, network string, destination
} }
func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
conn, err := s.selected.ListenPacket(ctx, destination) conn, err := s.selected.Load().ListenPacket(ctx, destination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil
} }
// TODO func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
// Deprecated
func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { selected := s.selected.Load()
return legacyHandler.NewConnection(ctx, conn, metadata) if outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler {
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
} else { } else {
return outbound.NewConnection(ctx, s.selected, conn, metadata) s.connection.NewConnection(ctx, selected, conn, metadata, onClose)
} }
} }
// TODO func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
// Deprecated
func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { selected := s.selected.Load()
return legacyHandler.NewPacketConnection(ctx, conn, metadata) if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler {
outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)
} else { } else {
return outbound.NewPacketConnection(ctx, s.selected, conn, metadata) s.connection.NewPacketConnection(ctx, selected, conn, metadata, onClose)
} }
} }

View File

@@ -36,7 +36,8 @@ type URLTest struct {
outbound.Adapter outbound.Adapter
ctx context.Context ctx context.Context
router adapter.Router router adapter.Router
outboundManager adapter.OutboundManager outbound adapter.OutboundManager
connection adapter.ConnectionManager
logger log.ContextLogger logger log.ContextLogger
tags []string tags []string
link string link string
@@ -52,7 +53,8 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds), Adapter: outbound.NewAdapter(C.TypeURLTest, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds),
ctx: ctx, ctx: ctx,
router: router, router: router,
outboundManager: service.FromContext[adapter.OutboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx),
logger: logger, logger: logger,
tags: options.Outbounds, tags: options.Outbounds,
link: options.URL, link: options.URL,
@@ -70,13 +72,13 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
func (s *URLTest) Start() error { func (s *URLTest) Start() error {
outbounds := make([]adapter.Outbound, 0, len(s.tags)) outbounds := make([]adapter.Outbound, 0, len(s.tags))
for i, tag := range s.tags { for i, tag := range s.tags {
detour, loaded := s.outboundManager.Outbound(tag) detour, loaded := s.outbound.Outbound(tag)
if !loaded { if !loaded {
return E.New("outbound ", i, " not found: ", tag) return E.New("outbound ", i, " not found: ", tag)
} }
outbounds = append(outbounds, detour) outbounds = append(outbounds, detour)
} }
group, err := NewURLTestGroup(s.ctx, s.outboundManager, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) group, err := NewURLTestGroup(s.ctx, s.outbound, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections)
if err != nil { if err != nil {
return err return err
} }
@@ -160,18 +162,14 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne
return nil, err return nil, err
} }
// TODO func (s *URLTest) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
// Deprecated
func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
return outbound.NewConnection(ctx, s, conn, metadata) s.connection.NewConnection(ctx, s, conn, metadata, onClose)
} }
// TODO func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
// Deprecated
func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
return outbound.NewPacketConnection(ctx, s, conn, metadata) s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
} }
func (s *URLTest) InterfaceUpdated() { func (s *URLTest) InterfaceUpdated() {

View File

@@ -82,33 +82,25 @@ func (h *Inbound) Close() error {
} }
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.newConnection(ctx, conn, metadata, onClose)
N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil {
if E.IsClosedOrCanceled(err) {
h.logger.DebugContext(ctx, "connection closed: ", err)
} else {
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
}
}
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
var err error var err error
if h.tlsConfig != nil { if h.tlsConfig != nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil { if err != nil {
return err N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
} }
} }
return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) err = http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
} }
func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
user, loaded := auth.UserFromContext[string](ctx) user, loaded := auth.UserFromContext[string](ctx)
if !loaded { if !loaded {
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
@@ -123,8 +115,6 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata
func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
user, loaded := auth.UserFromContext[string](ctx) user, loaded := auth.UserFromContext[string](ctx)
if !loaded { if !loaded {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)

View File

@@ -66,7 +66,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
} }
if len(options.Down) > 0 { if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down) receiveBps, err = humanize.ParseBytes(options.Down)
if receiveBps == 0 { if err != nil {
return nil, E.New("invalid down speed format: ", options.Down) return nil, E.New("invalid down speed format: ", options.Down)
} }
} else { } else {
@@ -123,7 +123,9 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.OriginDestination = h.listener.UDPAddr() metadata.OriginDestination = h.listener.UDPAddr()
metadata.Source = source metadata.Source = source
@@ -144,7 +146,9 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.OriginDestination = h.listener.UDPAddr() metadata.OriginDestination = h.listener.UDPAddr()
metadata.Source = source metadata.Source = source

View File

@@ -69,8 +69,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
} }
if len(options.Down) > 0 { if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down) receiveBps, err = humanize.ParseBytes(options.Down)
if receiveBps == 0 { if err != nil {
return nil, E.New("invalid down speed format: ", options.Down) return nil, E.Cause(err, "invalid down speed format: ", options.Down)
} }
} else { } else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps

View File

@@ -60,26 +60,40 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
} }
} }
var masqueradeHandler http.Handler var masqueradeHandler http.Handler
if options.Masquerade != "" { if options.Masquerade != nil && options.Masquerade.Type != "" {
masqueradeURL, err := url.Parse(options.Masquerade) switch options.Masquerade.Type {
if err != nil { case C.Hysterai2MasqueradeTypeFile:
return nil, E.Cause(err, "parse masquerade URL") masqueradeHandler = http.FileServer(http.Dir(options.Masquerade.FileOptions.Directory))
} case C.Hysterai2MasqueradeTypeProxy:
switch masqueradeURL.Scheme { masqueradeURL, err := url.Parse(options.Masquerade.ProxyOptions.URL)
case "file": if err != nil {
masqueradeHandler = http.FileServer(http.Dir(masqueradeURL.Path)) return nil, E.Cause(err, "parse masquerade URL")
case "http", "https": }
masqueradeHandler = &httputil.ReverseProxy{ masqueradeHandler = &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) { Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(masqueradeURL) r.SetURL(masqueradeURL)
r.Out.Host = r.In.Host if !options.Masquerade.ProxyOptions.RewriteHost {
r.Out.Host = r.In.Host
}
}, },
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusBadGateway) w.WriteHeader(http.StatusBadGateway)
}, },
} }
case C.Hysterai2MasqueradeTypeString:
masqueradeHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if options.Masquerade.StringOptions.StatusCode != 0 {
w.WriteHeader(options.Masquerade.StringOptions.StatusCode)
}
for key, values := range options.Masquerade.StringOptions.Headers {
for _, value := range values {
w.Header().Add(key, value)
}
}
w.Write([]byte(options.Masquerade.StringOptions.Content))
})
default: default:
return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) return nil, E.New("unknown masquerade type: ", options.Masquerade.Type)
} }
} }
inbound := &Inbound{ inbound := &Inbound{
@@ -134,7 +148,9 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.OriginDestination = h.listener.UDPAddr() metadata.OriginDestination = h.listener.UDPAddr()
metadata.Source = source metadata.Source = source
@@ -155,7 +171,9 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.OriginDestination = h.listener.UDPAddr() metadata.OriginDestination = h.listener.UDPAddr()
metadata.Source = source metadata.Source = source

View File

@@ -85,17 +85,15 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada
} }
switch headerBytes[0] { switch headerBytes[0] {
case socks4.Version, socks5.Version: case socks4.Version, socks5.Version:
return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
default: default:
return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
} }
} }
func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
user, loaded := auth.UserFromContext[string](ctx) user, loaded := auth.UserFromContext[string](ctx)
if !loaded { if !loaded {
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
@@ -110,8 +108,6 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata
func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
user, loaded := auth.UserFromContext[string](ctx) user, loaded := auth.UserFromContext[string](ctx)
if !loaded { if !loaded {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)

View File

@@ -195,7 +195,9 @@ func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = n.Tag() metadata.Inbound = n.Tag()
metadata.InboundType = n.Type() metadata.InboundType = n.Type()
//nolint:staticcheck
metadata.InboundDetour = n.listener.ListenOptions().Detour metadata.InboundDetour = n.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = n.listener.ListenOptions().InboundOptions metadata.InboundOptions = n.listener.ListenOptions().InboundOptions
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination

View File

@@ -62,8 +62,6 @@ func (h *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata
} }
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.Destination = M.SocksaddrFromNetIP(destination) metadata.Destination = M.SocksaddrFromNetIP(destination)
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
h.router.RouteConnectionEx(ctx, conn, metadata, onClose) h.router.RouteConnectionEx(ctx, conn, metadata, onClose)

View File

@@ -93,6 +93,8 @@ func (t *TProxy) Close() error {
} }
func (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = t.Tag()
metadata.InboundType = t.Type()
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
t.router.RouteConnectionEx(ctx, conn, metadata, onClose) t.router.RouteConnectionEx(ctx, conn, metadata, onClose)
@@ -104,8 +106,6 @@ func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, s
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = t.Tag() metadata.Inbound = t.Tag()
metadata.InboundType = t.Type() metadata.InboundType = t.Type()
metadata.InboundDetour = t.listener.ListenOptions().Detour
metadata.InboundOptions = t.listener.ListenOptions().InboundOptions
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination
metadata.OriginDestination = t.listener.UDPAddr() metadata.OriginDestination = t.listener.UDPAddr()

View File

@@ -104,6 +104,7 @@ func (h *Inbound) Close() error {
return h.listener.Close() return h.listener.Close()
} }
//nolint:staticcheck
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
@@ -116,6 +117,7 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
} }
} }
//nolint:staticcheck
func (h *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { func (h *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})
if err != nil { if err != nil {
@@ -127,8 +129,6 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RouteConnection(ctx, conn, metadata) return h.router.RouteConnection(ctx, conn, metadata)
} }
@@ -138,8 +138,6 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RoutePacketConnection(ctx, conn, metadata) return h.router.RoutePacketConnection(ctx, conn, metadata)
} }

View File

@@ -112,6 +112,7 @@ func (h *MultiInbound) Close() error {
return h.listener.Close() return h.listener.Close()
} }
//nolint:staticcheck
func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
@@ -124,6 +125,7 @@ func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metad
} }
} }
//nolint:staticcheck
func (h *MultiInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { func (h *MultiInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})
if err != nil { if err != nil {
@@ -145,7 +147,9 @@ func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadat
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RouteConnection(ctx, conn, metadata) return h.router.RouteConnection(ctx, conn, metadata)
} }
@@ -166,11 +170,14 @@ func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketCon
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RoutePacketConnection(ctx, conn, metadata) return h.router.RoutePacketConnection(ctx, conn, metadata)
} }
//nolint:staticcheck
func (h *MultiInbound) NewError(ctx context.Context, err error) { func (h *MultiInbound) NewError(ctx context.Context, err error) {
NewError(h.logger, ctx, err) NewError(h.logger, ctx, err)
} }

View File

@@ -97,6 +97,7 @@ func (h *RelayInbound) Close() error {
return h.listener.Close() return h.listener.Close()
} }
//nolint:staticcheck
func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
@@ -109,6 +110,7 @@ func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metad
} }
} }
//nolint:staticcheck
func (h *RelayInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { func (h *RelayInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})
if err != nil { if err != nil {
@@ -130,7 +132,9 @@ func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadat
h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination)
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RouteConnection(ctx, conn, metadata) return h.router.RouteConnection(ctx, conn, metadata)
} }
@@ -151,11 +155,14 @@ func (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketCon
h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination)
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RoutePacketConnection(ctx, conn, metadata) return h.router.RoutePacketConnection(ctx, conn, metadata)
} }
//nolint:staticcheck
func (h *RelayInbound) NewError(ctx context.Context, err error) { func (h *RelayInbound) NewError(ctx context.Context, err error) {
NewError(h.logger, ctx, err) NewError(h.logger, ctx, err)
} }

View File

@@ -119,7 +119,9 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination

View File

@@ -62,7 +62,7 @@ func (h *Inbound) Close() error {
} }
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil { if err != nil {
if E.IsClosedOrCanceled(err) { if E.IsClosedOrCanceled(err) {
@@ -76,8 +76,6 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
user, loaded := auth.UserFromContext[string](ctx) user, loaded := auth.UserFromContext[string](ctx)
if !loaded { if !loaded {
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
@@ -92,8 +90,6 @@ func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata
func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
user, loaded := auth.UserFromContext[string](ctx) user, loaded := auth.UserFromContext[string](ctx)
if !loaded { if !loaded {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)

View File

@@ -1,13 +1,13 @@
package tor package tor
import ( import (
std_bufio "bufio"
"context" "context"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"net" "net"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/auth"
@@ -15,12 +15,14 @@ import (
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/protocol/socks" "github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/service"
) )
type ProxyListener struct { type ProxyListener struct {
ctx context.Context ctx context.Context
logger log.ContextLogger logger log.ContextLogger
dialer N.Dialer dialer N.Dialer
connection adapter.ConnectionManager
tcpListener *net.TCPListener tcpListener *net.TCPListener
username string username string
password string password string
@@ -38,6 +40,7 @@ func NewProxyListener(ctx context.Context, logger log.ContextLogger, dialer N.Di
ctx: ctx, ctx: ctx,
logger: logger, logger: logger,
dialer: dialer, dialer: dialer,
connection: service.FromContext[adapter.ConnectionManager](ctx),
authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}), authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}),
username: username, username: username,
password: password, password: password,
@@ -95,25 +98,24 @@ func (l *ProxyListener) acceptLoop() {
} }
} }
// TODO: migrate to new api
//
//nolint:staticcheck
func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error { func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error {
return socks.HandleConnection(ctx, conn, l.authenticator, l, M.Metadata{}) return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, M.SocksaddrFromNet(conn.RemoteAddr()), nil)
} }
func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error { func (l *ProxyListener) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Source = source
metadata.Destination = destination
metadata.Network = N.NetworkTCP metadata.Network = N.NetworkTCP
metadata.Destination = upstreamMetadata.Destination
l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination) l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination)
return outbound.NewConnection(ctx, l.dialer, conn, metadata) l.connection.NewConnection(ctx, l.dialer, conn, metadata, onClose)
} }
func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { func (l *ProxyListener) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Source = source
metadata.Destination = destination
metadata.Network = N.NetworkUDP metadata.Network = N.NetworkUDP
metadata.Destination = upstreamMetadata.Destination
l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination) l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination)
return outbound.NewPacketConnection(ctx, l.dialer, conn, metadata) l.connection.NewPacketConnection(ctx, l.dialer, conn, metadata, onClose)
} }

View File

@@ -56,7 +56,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
} }
inbound.tlsConfig = tlsConfig inbound.tlsConfig = tlsConfig
} }
var fallbackHandler N.TCPConnectionHandler var fallbackHandler N.TCPConnectionHandlerEx
if options.Fallback != nil && options.Fallback.Server != "" || len(options.FallbackForALPN) > 0 { if options.Fallback != nil && options.Fallback.Server != "" || len(options.FallbackForALPN) > 0 {
if options.Fallback != nil && options.Fallback.Server != "" { if options.Fallback != nil && options.Fallback.Server != "" {
inbound.fallbackAddr = options.Fallback.Build() inbound.fallbackAddr = options.Fallback.Build()
@@ -78,9 +78,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
} }
inbound.fallbackAddrTLSNextProto = fallbackAddrNextProto inbound.fallbackAddrTLSNextProto = fallbackAddrNextProto
} }
fallbackHandler = adapter.NewUpstreamContextHandler(inbound.fallbackConnection, nil, nil) fallbackHandler = adapter.NewUpstreamContextHandlerEx(inbound.fallbackConnection, nil)
} }
service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, nil), fallbackHandler, logger) service := trojan.NewService[int](adapter.NewUpstreamContextHandlerEx(inbound.newConnection, inbound.newPacketConnection), fallbackHandler, logger)
err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int { err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int {
return index return index
}), common.Map(options.Users, func(it option.TrojanUser) string { }), common.Map(options.Users, func(it option.TrojanUser) string {
@@ -158,37 +158,30 @@ func (h *Inbound) Close() error {
) )
} }
func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error var err error
if h.tlsConfig != nil && h.transport == nil { if h.tlsConfig != nil && h.transport == nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil { if err != nil {
return err N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
} }
} }
return h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) err = h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.NewConnection(ctx, conn, metadata)
N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil { if err != nil {
if E.IsClosedOrCanceled(err) { N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.DebugContext(ctx, "connection closed: ", err) h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
} else {
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
} }
} }
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
userIndex, loaded := auth.UserFromContext[int](ctx) userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded { if !loaded {
return os.ErrInvalid N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
return
} }
user := h.users[userIndex].Name user := h.users[userIndex].Name
if user == "" { if user == "" {
@@ -197,44 +190,16 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada
metadata.User = user metadata.User = user
} }
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
return h.router.RouteConnection(ctx, conn, metadata) h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
} }
func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var fallbackAddr M.Socksaddr
if len(h.fallbackAddrTLSNextProto) > 0 {
if tlsConn, loaded := common.Cast[tls.Conn](conn); loaded {
connectionState := tlsConn.ConnectionState()
if connectionState.NegotiatedProtocol != "" {
if fallbackAddr, loaded = h.fallbackAddrTLSNextProto[connectionState.NegotiatedProtocol]; !loaded {
return E.New("fallback disabled for ALPN: ", connectionState.NegotiatedProtocol)
}
}
}
}
if !fallbackAddr.IsValid() {
if !h.fallbackAddr.IsValid() {
return E.New("fallback disabled by default")
}
fallbackAddr = h.fallbackAddr
}
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
h.logger.InfoContext(ctx, "fallback connection to ", fallbackAddr)
metadata.Destination = fallbackAddr
return h.router.RouteConnection(ctx, conn, metadata)
}
func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
userIndex, loaded := auth.UserFromContext[int](ctx) userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded { if !loaded {
return os.ErrInvalid N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
return
} }
user := h.users[userIndex].Name user := h.users[userIndex].Name
if user == "" { if user == "" {
@@ -243,7 +208,36 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me
metadata.User = user metadata.User = user
} }
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
return h.router.RoutePacketConnection(ctx, conn, metadata) h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}
func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var fallbackAddr M.Socksaddr
if len(h.fallbackAddrTLSNextProto) > 0 {
if tlsConn, loaded := common.Cast[tls.Conn](conn); loaded {
connectionState := tlsConn.ConnectionState()
if connectionState.NegotiatedProtocol != "" {
if fallbackAddr, loaded = h.fallbackAddrTLSNextProto[connectionState.NegotiatedProtocol]; !loaded {
h.logger.DebugContext(ctx, "process connection from ", metadata.Source, ": fallback disabled for ALPN: ", connectionState.NegotiatedProtocol)
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
return
}
}
}
}
if !fallbackAddr.IsValid() {
if !h.fallbackAddr.IsValid() {
h.logger.DebugContext(ctx, "process connection from ", metadata.Source, ": fallback disabled by default")
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
return
}
fallbackAddr = h.fallbackAddr
}
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.Destination = fallbackAddr
h.logger.InfoContext(ctx, "fallback connection to ", fallbackAddr)
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
} }
var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil)
@@ -254,6 +248,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
} }

View File

@@ -105,7 +105,9 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.OriginDestination = h.listener.UDPAddr() metadata.OriginDestination = h.listener.UDPAddr()
metadata.Source = source metadata.Source = source
@@ -126,7 +128,9 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.OriginDestination = h.listener.UDPAddr() metadata.OriginDestination = h.listener.UDPAddr()
metadata.Source = source metadata.Source = source

View File

@@ -41,11 +41,9 @@ type Inbound struct {
router adapter.Router router adapter.Router
networkManager adapter.NetworkManager networkManager adapter.NetworkManager
logger log.ContextLogger logger log.ContextLogger
// Deprecated //nolint:staticcheck
inboundOptions option.InboundOptions inboundOptions option.InboundOptions
tunOptions tun.Options tunOptions tun.Options
// Deprecated
endpointIndependentNat bool
udpTimeout time.Duration udpTimeout time.Duration
stack string stack string
tunIf tun.Tun tunIf tun.Tun
@@ -206,11 +204,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
ExcludePackage: options.ExcludePackage, ExcludePackage: options.ExcludePackage,
InterfaceMonitor: networkManager.InterfaceMonitor(), InterfaceMonitor: networkManager.InterfaceMonitor(),
}, },
endpointIndependentNat: options.EndpointIndependentNat, udpTimeout: udpTimeout,
udpTimeout: udpTimeout, stack: options.Stack,
stack: options.Stack, platformInterface: service.FromContext[platform.Interface](ctx),
platformInterface: service.FromContext[platform.Interface](ctx), platformOptions: common.PtrValueOrDefault(options.Platform),
platformOptions: common.PtrValueOrDefault(options.Platform),
} }
if options.AutoRedirect { if options.AutoRedirect {
if !options.AutoRoute { if !options.AutoRoute {
@@ -440,6 +437,7 @@ func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
metadata.InboundType = C.TypeTun metadata.InboundType = C.TypeTun
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination
//nolint:staticcheck
metadata.InboundOptions = t.inboundOptions metadata.InboundOptions = t.inboundOptions
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
@@ -453,6 +451,7 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
metadata.InboundType = C.TypeTun metadata.InboundType = C.TypeTun
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination
//nolint:staticcheck
metadata.InboundOptions = t.inboundOptions metadata.InboundOptions = t.inboundOptions
t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
@@ -468,6 +467,7 @@ func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn
metadata.InboundType = C.TypeTun metadata.InboundType = C.TypeTun
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination
//nolint:staticcheck
metadata.InboundOptions = t.inboundOptions metadata.InboundOptions = t.inboundOptions
t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source)
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)

View File

@@ -58,7 +58,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if err != nil { if err != nil {
return nil, err return nil, err
} }
service := vless.NewService[int](logger, adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound)) service := vless.NewService[int](logger, adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx))
service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int { service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int {
return index return index
}), common.Map(inbound.users, func(it option.VLESSUser) string { }), common.Map(inbound.users, func(it option.VLESSUser) string {
@@ -138,37 +138,30 @@ func (h *Inbound) Close() error {
) )
} }
func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error var err error
if h.tlsConfig != nil && h.transport == nil { if h.tlsConfig != nil && h.transport == nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil { if err != nil {
return err N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
} }
} }
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) err = h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.NewConnection(ctx, conn, metadata)
N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil { if err != nil {
if E.IsClosedOrCanceled(err) { N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.DebugContext(ctx, "connection closed: ", err) h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
} else {
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
} }
} }
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
userIndex, loaded := auth.UserFromContext[int](ctx) userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded { if !loaded {
return os.ErrInvalid N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
return
} }
user := h.users[userIndex].Name user := h.users[userIndex].Name
if user == "" { if user == "" {
@@ -177,17 +170,16 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada
metadata.User = user metadata.User = user
} }
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
return h.router.RouteConnection(ctx, conn, metadata) h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
} }
func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
userIndex, loaded := auth.UserFromContext[int](ctx) userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded { if !loaded {
return os.ErrInvalid N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
return
} }
user := h.users[userIndex].Name user := h.users[userIndex].Name
if user == "" { if user == "" {
@@ -202,7 +194,7 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me
} else { } else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
} }
return h.router.RoutePacketConnection(ctx, conn, metadata) h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
} }
var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil)
@@ -216,17 +208,3 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
} }
func (h *Inbound) NewError(ctx context.Context, err error) {
NewError(h.logger, ctx, err)
}
// Deprecated: remove
func NewError(logger logger.ContextLogger, ctx context.Context, err error) {
common.Close(err)
if E.IsClosedOrCanceled(err) {
logger.DebugContext(ctx, "connection closed: ", err)
return
}
logger.ErrorContext(ctx, err)
}

View File

@@ -65,7 +65,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if options.Transport != nil && options.Transport.Type != "" { if options.Transport != nil && options.Transport.Type != "" {
serviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection()) serviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection())
} }
service := vmess.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), serviceOptions...) service := vmess.NewService[int](adapter.NewUpstreamContextHandlerEx(inbound.newConnectionEx, inbound.newPacketConnectionEx), serviceOptions...)
inbound.service = service inbound.service = service
err = service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int { err = service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.VMessUser) int {
return index return index
@@ -152,37 +152,30 @@ func (h *Inbound) Close() error {
) )
} }
func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
var err error var err error
if h.tlsConfig != nil && h.transport == nil { if h.tlsConfig != nil && h.transport == nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil { if err != nil {
return err N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
} }
} }
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) err = h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.NewConnection(ctx, conn, metadata)
N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil { if err != nil {
if E.IsClosedOrCanceled(err) { N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.DebugContext(ctx, "connection closed: ", err) h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
} else {
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
} }
} }
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
userIndex, loaded := auth.UserFromContext[int](ctx) userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded { if !loaded {
return os.ErrInvalid N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
return
} }
user := h.users[userIndex].Name user := h.users[userIndex].Name
if user == "" { if user == "" {
@@ -191,17 +184,16 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada
metadata.User = user metadata.User = user
} }
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
return h.router.RouteConnection(ctx, conn, metadata) h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
} }
func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
userIndex, loaded := auth.UserFromContext[int](ctx) userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded { if !loaded {
return os.ErrInvalid N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
return
} }
user := h.users[userIndex].Name user := h.users[userIndex].Name
if user == "" { if user == "" {
@@ -216,7 +208,7 @@ func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, me
} else { } else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
} }
return h.router.RoutePacketConnection(ctx, conn, metadata) h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
} }
var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil)
@@ -230,17 +222,3 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
} }
func (h *Inbound) NewError(ctx context.Context, err error) {
NewError(h.logger, ctx, err)
}
// Deprecated: remove
func NewError(logger logger.ContextLogger, ctx context.Context, err error) {
common.Close(err)
if E.IsClosedOrCanceled(err) {
logger.DebugContext(ctx, "connection closed: ", err)
return
}
logger.ErrorContext(ctx, err)
}

View File

@@ -56,12 +56,18 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{
Context: ctx, Context: ctx,
Logger: logger, Logger: logger,
System: options.System, System: options.System,
Handler: ep, Handler: ep,
UDPTimeout: time.Duration(options.UDPTimeout), UDPTimeout: udpTimeout,
Dialer: outboundDialer, Dialer: outboundDialer,
CreateDialer: func(interfaceName string) N.Dialer { CreateDialer: func(interfaceName string) N.Dialer {
return common.Must1(dialer.NewDefault(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{ return common.Must1(dialer.NewDefault(service.FromContext[adapter.NetworkManager](ctx), option.DialerOptions{

View File

@@ -61,6 +61,25 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
peers := common.Map(options.Peers, func(it option.LegacyWireGuardPeer) wireguard.PeerOptions {
return wireguard.PeerOptions{
Endpoint: it.ServerOptions.Build(),
PublicKey: it.PublicKey,
PreSharedKey: it.PreSharedKey,
AllowedIPs: it.AllowedIPs,
// PersistentKeepaliveInterval: time.Duration(it.PersistentKeepaliveInterval),
Reserved: it.Reserved,
}
})
if len(peers) == 0 {
peers = []wireguard.PeerOptions{{
Endpoint: options.ServerOptions.Build(),
PublicKey: options.PeerPublicKey,
PreSharedKey: options.PreSharedKey,
AllowedIPs: []netip.Prefix{netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.PrefixFrom(netip.IPv6Unspecified(), 0)},
Reserved: options.Reserved,
}}
}
wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{
Context: ctx, Context: ctx,
Logger: logger, Logger: logger,
@@ -82,16 +101,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
} }
return endpointAddresses[0], nil return endpointAddresses[0], nil
}, },
Peers: common.Map(options.Peers, func(it option.LegacyWireGuardPeer) wireguard.PeerOptions { Peers: peers,
return wireguard.PeerOptions{
Endpoint: it.ServerOptions.Build(),
PublicKey: it.PublicKey,
PreSharedKey: it.PreSharedKey,
AllowedIPs: it.AllowedIPs,
// PersistentKeepaliveInterval: time.Duration(it.PersistentKeepaliveInterval),
Reserved: it.Reserved,
}
}),
Workers: options.Workers, Workers: options.Workers,
}) })
if err != nil { if err != nil {

View File

@@ -5,38 +5,49 @@ import (
"io" "io"
"net" "net"
"net/netip" "net/netip"
"sync"
"sync/atomic" "sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
) )
var _ adapter.ConnectionManager = (*ConnectionManager)(nil) var _ adapter.ConnectionManager = (*ConnectionManager)(nil)
type ConnectionManager struct { type ConnectionManager struct {
logger logger.ContextLogger logger logger.ContextLogger
monitor *ConnectionMonitor access sync.Mutex
connections list.List[io.Closer]
} }
func NewConnectionManager(logger logger.ContextLogger) *ConnectionManager { func NewConnectionManager(logger logger.ContextLogger) *ConnectionManager {
return &ConnectionManager{ return &ConnectionManager{
logger: logger, logger: logger,
monitor: NewConnectionMonitor(),
} }
} }
func (m *ConnectionManager) Start() error { func (m *ConnectionManager) Start(stage adapter.StartStage) error {
return m.monitor.Start() return nil
} }
func (m *ConnectionManager) Close() error { func (m *ConnectionManager) Close() error {
return m.monitor.Close() m.access.Lock()
defer m.access.Unlock()
for element := m.connections.Front(); element != nil; element = element.Next() {
common.Close(element.Value)
}
m.connections.Init()
return nil
} }
func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
@@ -51,96 +62,32 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co
remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
} }
if err != nil { if err != nil {
err = E.Cause(err, "open outbound connection")
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
m.logger.ErrorContext(ctx, "open outbound connection: ", err) m.logger.ErrorContext(ctx, err)
return return
} }
err = N.ReportConnHandshakeSuccess(conn, remoteConn) err = N.ReportConnHandshakeSuccess(conn, remoteConn)
if err != nil { if err != nil {
err = E.Cause(err, "report handshake success")
remoteConn.Close() remoteConn.Close()
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
m.logger.ErrorContext(ctx, "report handshake success: ", err) m.logger.ErrorContext(ctx, err)
return return
} }
m.access.Lock()
element := m.connections.PushBack(conn)
m.access.Unlock()
onClose = N.AppendClose(onClose, func(it error) {
m.access.Lock()
defer m.access.Unlock()
m.connections.Remove(element)
})
var done atomic.Bool var done atomic.Bool
if ctx.Done() != nil {
onClose = N.AppendClose(onClose, m.monitor.Add(ctx, conn))
}
go m.connectionCopy(ctx, conn, remoteConn, false, &done, onClose) go m.connectionCopy(ctx, conn, remoteConn, false, &done, onClose)
go m.connectionCopy(ctx, remoteConn, conn, true, &done, onClose) go m.connectionCopy(ctx, remoteConn, conn, true, &done, onClose)
} }
func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader, destination io.Writer, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
originSource := source
var readCounters, writeCounters []N.CountFunc
for {
source, readCounters = N.UnwrapCountReader(source, readCounters)
destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters)
if cachedSrc, isCached := source.(N.CachedReader); isCached {
cachedBuffer := cachedSrc.ReadCached()
if cachedBuffer != nil {
if !cachedBuffer.IsEmpty() {
dataLen := cachedBuffer.Len()
for _, counter := range readCounters {
counter(int64(dataLen))
}
_, err := destination.Write(cachedBuffer.Bytes())
if err != nil {
m.logger.ErrorContext(ctx, "connection upload payload: ", err)
cachedBuffer.Release()
if done.Swap(true) {
if onClose != nil {
onClose(err)
}
common.Close(source, destination)
}
return
}
for _, counter := range writeCounters {
counter(int64(dataLen))
}
}
cachedBuffer.Release()
continue
}
}
break
}
var (
dstDuplex bool
err error
)
_, err = bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
if _, dstDuplex = common.Cast[N.WriteCloser](destination); dstDuplex && err == nil {
N.CloseWrite(destination)
} else {
common.Close(destination)
}
if done.Swap(true) {
if onClose != nil {
onClose(err)
}
common.Close(source, destination)
}
if !direction {
if err == nil {
m.logger.DebugContext(ctx, "connection upload finished")
} else if !E.IsClosedOrCanceled(err) {
m.logger.ErrorContext(ctx, "connection upload closed: ", err)
} else {
m.logger.TraceContext(ctx, "connection upload closed")
}
} else {
if err == nil {
m.logger.DebugContext(ctx, "connection download finished")
} else if !E.IsClosedOrCanceled(err) {
m.logger.ErrorContext(ctx, "connection download closed: ", err)
} else {
m.logger.TraceContext(ctx, "connection download closed")
}
}
}
func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
ctx = adapter.WithContext(ctx, &metadata) ctx = adapter.WithContext(ctx, &metadata)
var ( var (
@@ -206,59 +153,107 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
natConn.UpdateDestination(destinationAddress) natConn.UpdateDestination(destinationAddress)
} }
} }
destination := bufio.NewPacketConn(remotePacketConn) var udpTimeout time.Duration
if ctx.Done() != nil { if metadata.UDPTimeout > 0 {
onClose = N.AppendClose(onClose, m.monitor.Add(ctx, conn)) udpTimeout = metadata.UDPTimeout
} else {
protocol := metadata.Protocol
if protocol == "" {
protocol = C.PortProtocols[metadata.Destination.Port]
}
if protocol != "" {
udpTimeout = C.ProtocolTimeouts[protocol]
}
} }
if udpTimeout > 0 {
ctx, conn = canceler.NewPacketConn(ctx, conn, udpTimeout)
}
destination := bufio.NewPacketConn(remotePacketConn)
m.access.Lock()
element := m.connections.PushBack(conn)
m.access.Unlock()
onClose = N.AppendClose(onClose, func(it error) {
m.access.Lock()
defer m.access.Unlock()
m.connections.Remove(element)
})
var done atomic.Bool var done atomic.Bool
go m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose) go m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose)
go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose)
} }
func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader, destination io.Writer, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
_, err := bufio.CopyPacket(destination, source)
/*var readCounters, writeCounters []N.CountFunc
var cachedPackets []*N.PacketBuffer
originSource := source originSource := source
originDestination := destination
var readCounters, writeCounters []N.CountFunc
for { for {
source, readCounters = N.UnwrapCountPacketReader(source, readCounters) source, readCounters = N.UnwrapCountReader(source, readCounters)
destination, writeCounters = N.UnwrapCountPacketWriter(destination, writeCounters) destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters)
if cachedReader, isCached := source.(N.CachedPacketReader); isCached { if cachedSrc, isCached := source.(N.CachedReader); isCached {
packet := cachedReader.ReadCachedPacket() cachedBuffer := cachedSrc.ReadCached()
if packet != nil { if cachedBuffer != nil {
cachedPackets = append(cachedPackets, packet) dataLen := cachedBuffer.Len()
continue _, err := destination.Write(cachedBuffer.Bytes())
cachedBuffer.Release()
if err != nil {
if done.Swap(true) {
onClose(err)
}
common.Close(originSource, originDestination)
if !direction {
m.logger.ErrorContext(ctx, "connection upload payload: ", err)
} else {
m.logger.ErrorContext(ctx, "connection download payload: ", err)
}
return
}
for _, counter := range readCounters {
counter(int64(dataLen))
}
for _, counter := range writeCounters {
counter(int64(dataLen))
}
} }
continue
} }
break break
} }
var handled bool _, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
if natConn, isNatConn := source.(udpnat.Conn); isNatConn { if err != nil {
natConn.SetHandler(&udpHijacker{ common.Close(originDestination)
ctx: ctx, } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex {
logger: m.logger, err = duplexDst.CloseWrite()
source: natConn,
destination: destination,
direction: direction,
readCounters: readCounters,
writeCounters: writeCounters,
done: done,
onClose: onClose,
})
handled = true
}
if cachedPackets != nil {
_, err := bufio.WritePacketWithPool(originSource, destination, cachedPackets, readCounters, writeCounters)
if err != nil { if err != nil {
common.Close(source, destination) common.Close(originSource, originDestination)
m.logger.ErrorContext(ctx, "packet upload payload: ", err) }
return } else {
common.Close(originDestination)
}
if done.Swap(true) {
onClose(err)
common.Close(originSource, originDestination)
}
if !direction {
if err == nil {
m.logger.DebugContext(ctx, "connection upload finished")
} else if !E.IsClosedOrCanceled(err) {
m.logger.ErrorContext(ctx, "connection upload closed: ", err)
} else {
m.logger.TraceContext(ctx, "connection upload closed")
}
} else {
if err == nil {
m.logger.DebugContext(ctx, "connection download finished")
} else if !E.IsClosedOrCanceled(err) {
m.logger.ErrorContext(ctx, "connection download closed: ", err)
} else {
m.logger.TraceContext(ctx, "connection download closed")
} }
} }
if handled { }
return
} func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
_, err := bufio.CopyPacketWithCounters(destination, source, originSource, readCounters, writeCounters)*/ _, err := bufio.CopyPacket(destination, source)
if !direction { if !direction {
if E.IsClosedOrCanceled(err) { if E.IsClosedOrCanceled(err) {
m.logger.TraceContext(ctx, "packet upload closed") m.logger.TraceContext(ctx, "packet upload closed")
@@ -273,58 +268,7 @@ func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.P
} }
} }
if !done.Swap(true) { if !done.Swap(true) {
common.Close(source, destination) onClose(err)
if onClose != nil {
onClose(err)
}
} }
common.Close(source, destination)
} }
/*type udpHijacker struct {
ctx context.Context
logger logger.ContextLogger
source io.Closer
destination N.PacketWriter
direction bool
readCounters []N.CountFunc
writeCounters []N.CountFunc
done *atomic.Bool
onClose N.CloseHandlerFunc
}
func (u *udpHijacker) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
dataLen := buffer.Len()
for _, counter := range u.readCounters {
counter(int64(dataLen))
}
err := u.destination.WritePacket(buffer, source)
if err != nil {
common.Close(u.source, u.destination)
u.logger.DebugContext(u.ctx, "packet upload closed: ", err)
return
}
for _, counter := range u.writeCounters {
counter(int64(dataLen))
}
}
func (u *udpHijacker) Close() error {
var err error
if !u.done.Swap(true) {
err = common.Close(u.source, u.destination)
if u.onClose != nil {
u.onClose(net.ErrClosed)
}
}
if u.direction {
u.logger.TraceContext(u.ctx, "packet download closed")
} else {
u.logger.TraceContext(u.ctx, "packet upload closed")
}
return err
}
func (u *udpHijacker) Upstream() any {
return u.destination
}
*/

View File

@@ -1,124 +0,0 @@
package route
import (
"context"
"io"
"reflect"
"sync"
"time"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
)
type ConnectionMonitor struct {
access sync.RWMutex
reloadChan chan struct{}
connections list.List[*monitorEntry]
}
type monitorEntry struct {
ctx context.Context
closer io.Closer
}
func NewConnectionMonitor() *ConnectionMonitor {
return &ConnectionMonitor{
reloadChan: make(chan struct{}, 1),
}
}
func (m *ConnectionMonitor) Add(ctx context.Context, closer io.Closer) N.CloseHandlerFunc {
m.access.Lock()
defer m.access.Unlock()
element := m.connections.PushBack(&monitorEntry{
ctx: ctx,
closer: closer,
})
select {
case <-m.reloadChan:
return nil
default:
select {
case m.reloadChan <- struct{}{}:
default:
}
}
return func(it error) {
m.access.Lock()
defer m.access.Unlock()
m.connections.Remove(element)
select {
case <-m.reloadChan:
default:
select {
case m.reloadChan <- struct{}{}:
default:
}
}
}
}
func (m *ConnectionMonitor) Start() error {
go m.monitor()
return nil
}
func (m *ConnectionMonitor) Close() error {
m.access.Lock()
defer m.access.Unlock()
close(m.reloadChan)
return nil
}
func (m *ConnectionMonitor) monitor() {
var (
selectCases []reflect.SelectCase
elements []*list.Element[*monitorEntry]
)
rootCase := reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(m.reloadChan),
}
for {
m.access.RLock()
if m.connections.Len() == 0 {
m.access.RUnlock()
if _, loaded := <-m.reloadChan; !loaded {
return
} else {
continue
}
}
if len(elements) < m.connections.Len() {
elements = make([]*list.Element[*monitorEntry], 0, m.connections.Len())
}
if len(selectCases) < m.connections.Len()+1 {
selectCases = make([]reflect.SelectCase, 0, m.connections.Len()+1)
}
selectCases = selectCases[:1]
selectCases[0] = rootCase
for element := m.connections.Front(); element != nil; element = element.Next() {
elements = append(elements, element)
selectCases = append(selectCases, reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(element.Value.ctx.Done()),
})
}
m.access.RUnlock()
selected, _, loaded := reflect.Select(selectCases)
if selected == 0 {
if !loaded {
return
} else {
time.Sleep(time.Second)
continue
}
}
element := elements[selected-1]
m.access.Lock()
m.connections.Remove(element)
m.access.Unlock()
element.Value.closer.Close() // maybe go close
}
}

View File

@@ -1,43 +0,0 @@
package route_test
import (
"context"
"sync"
"testing"
"time"
"github.com/sagernet/sing-box/route"
"github.com/stretchr/testify/require"
)
func TestMonitor(t *testing.T) {
t.Parallel()
var closer myCloser
closer.Add(1)
monitor := route.NewConnectionMonitor()
require.NoError(t, monitor.Start())
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
monitor.Add(ctx, &closer)
done := make(chan struct{})
go func() {
closer.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(time.Second + 100*time.Millisecond):
t.Fatal("timeout")
}
cancel()
require.NoError(t, monitor.Close())
}
type myCloser struct {
sync.WaitGroup
}
func (c *myCloser) Close() error {
c.Done()
return nil
}

View File

@@ -2,6 +2,7 @@ package route
import ( import (
"context" "context"
"errors"
"net" "net"
"time" "time"
@@ -9,6 +10,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
dnsOutbound "github.com/sagernet/sing-box/protocol/dns" dnsOutbound "github.com/sagernet/sing-box/protocol/dns"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@@ -54,7 +56,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) { func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) {
err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination) err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination)
if err != nil && !E.IsClosedOrCanceled(err) { if err != nil && !errors.Is(err, tun.ErrDrop) && !E.IsClosedOrCanceled(err) {
router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
} }
} }

View File

@@ -61,7 +61,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
autoDetectInterface: routeOptions.AutoDetectInterface, autoDetectInterface: routeOptions.AutoDetectInterface,
defaultOptions: adapter.NetworkOptions{ defaultOptions: adapter.NetworkOptions{
BindInterface: routeOptions.DefaultInterface, BindInterface: routeOptions.DefaultInterface,
RoutingMark: routeOptions.DefaultMark, RoutingMark: uint32(routeOptions.DefaultMark),
NetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy), NetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy),
NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build), NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build),
FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build), FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build),
@@ -205,7 +205,7 @@ func (r *NetworkManager) Close() error {
}) })
monitor.Finish() monitor.Finish()
} }
return nil return err
} }
func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder {
@@ -241,7 +241,7 @@ func (r *NetworkManager) UpdateInterfaces() error {
return it.Flags&net.FlagUp != 0 return it.Flags&net.FlagUp != 0
}) })
r.networkInterfaces.Store(newInterfaces) r.networkInterfaces.Store(newInterfaces)
if !slices.EqualFunc(oldInterfaces, newInterfaces, func(oldInterface adapter.NetworkInterface, newInterface adapter.NetworkInterface) bool { if len(newInterfaces) > 0 && !slices.EqualFunc(oldInterfaces, newInterfaces, func(oldInterface adapter.NetworkInterface, newInterface adapter.NetworkInterface) bool {
return oldInterface.Interface.Index == newInterface.Interface.Index && return oldInterface.Interface.Index == newInterface.Interface.Index &&
oldInterface.Interface.Name == newInterface.Interface.Name && oldInterface.Interface.Name == newInterface.Interface.Name &&
oldInterface.Interface.Flags == newInterface.Interface.Flags && oldInterface.Interface.Flags == newInterface.Interface.Flags &&

View File

@@ -53,6 +53,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
return E.New("reject connection to ", metadata.Destination, " while device paused") return E.New("reject connection to ", metadata.Destination, " while device paused")
} }
//nolint:staticcheck
if metadata.InboundDetour != "" { if metadata.InboundDetour != "" {
if metadata.LastInbound == metadata.InboundDetour { if metadata.LastInbound == metadata.InboundDetour {
return E.New("routing loop on detour: ", metadata.InboundDetour) return E.New("routing loop on detour: ", metadata.InboundDetour)
@@ -131,23 +132,11 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
if r.tracker != nil { if r.tracker != nil {
conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound) conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
} }
legacyOutbound, isLegacy := selectedOutbound.(adapter.ConnectionHandler) if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler {
if isLegacy { outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
err = legacyOutbound.NewConnection(ctx, conn, metadata) } else {
if err != nil { r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose)
conn.Close()
if onClose != nil {
onClose(err)
}
return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]"))
} else {
if onClose != nil {
onClose(nil)
}
}
return nil
} }
r.connection.NewConnection(ctx, selectedOutbound, conn, metadata, onClose)
return nil return nil
} }
@@ -182,6 +171,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
if r.pauseManager.IsDevicePaused() { if r.pauseManager.IsDevicePaused() {
return E.New("reject packet connection to ", metadata.Destination, " while device paused") return E.New("reject packet connection to ", metadata.Destination, " while device paused")
} }
//nolint:staticcheck
if metadata.InboundDetour != "" { if metadata.InboundDetour != "" {
if metadata.LastInbound == metadata.InboundDetour { if metadata.LastInbound == metadata.InboundDetour {
return E.New("routing loop on detour: ", metadata.InboundDetour) return E.New("routing loop on detour: ", metadata.InboundDetour)
@@ -256,16 +246,11 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
if metadata.FakeIP { if metadata.FakeIP {
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
} }
legacyOutbound, isLegacy := selectedOutbound.(adapter.PacketConnectionHandler) if outboundHandler, isHandler := selectedOutbound.(adapter.PacketConnectionHandlerEx); isHandler {
if isLegacy { outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)
err = legacyOutbound.NewPacketConnection(ctx, conn, metadata) } else {
N.CloseOnHandshakeFailure(conn, onClose, err) r.connection.NewPacketConnection(ctx, selectedOutbound, conn, metadata, onClose)
if err != nil {
return E.Cause(err, F.ToString("outbound/", selectedOutbound.Type(), "[", selectedOutbound.Tag(), "]"))
}
return nil
} }
r.connection.NewPacketConnection(ctx, selectedOutbound, conn, metadata, onClose)
return nil return nil
} }
@@ -281,7 +266,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error {
if !isReject { if !isReject {
return nil return nil
} }
return rejectAction.Error(nil) return rejectAction.Error(context.Background())
} }
func (r *Router) matchRule( func (r *Router) matchRule(
@@ -438,6 +423,9 @@ match:
if routeOptions.UDPConnect { if routeOptions.UDPConnect {
metadata.UDPConnect = true metadata.UDPConnect = true
} }
if routeOptions.UDPTimeout > 0 {
metadata.UDPTimeout = routeOptions.UDPTimeout
}
} }
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *rule.RuleActionSniff: case *rule.RuleActionSniff:
@@ -473,8 +461,12 @@ match:
break match break match
} }
} }
if !preMatch && metadata.Destination.Addr.IsUnspecified() { if !preMatch && inputPacketConn != nil && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{}, inputConn, inputPacketConn) var timeout time.Duration
if metadata.InboundType == C.TypeSOCKS {
timeout = C.TCPTimeout
}
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: timeout}, inputConn, inputPacketConn)
if newErr != nil { if newErr != nil {
fatalErr = newErr fatalErr = newErr
return return
@@ -570,8 +562,7 @@ func (r *Router) actionSniff(
return return
} }
} else { } else {
// TODO: maybe always override destination if !metadata.Destination.Addr.IsGlobalUnicast() {
if metadata.Destination.Addr.IsUnspecified() {
metadata.Destination = destination metadata.Destination = destination
} }
if len(packetBuffers) > 0 { if len(packetBuffers) > 0 {

View File

@@ -120,9 +120,19 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
} }
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
if len(message.Question) > 0 { if len(message.Question) != 1 {
r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String())) r.dnsLogger.WarnContext(ctx, "bad question size: ", len(message.Question))
responseMessage := mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Response: true,
Rcode: mDNS.RcodeFormatError,
},
Question: message.Question,
}
return &responseMessage, nil
} }
r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()))
var ( var (
response *mDNS.Msg response *mDNS.Msg
cached bool cached bool
@@ -134,16 +144,14 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
var metadata *adapter.InboundContext var metadata *adapter.InboundContext
ctx, metadata = adapter.ExtendContext(ctx) ctx, metadata = adapter.ExtendContext(ctx)
metadata.Destination = M.Socksaddr{} metadata.Destination = M.Socksaddr{}
if len(message.Question) > 0 { metadata.QueryType = message.Question[0].Qtype
metadata.QueryType = message.Question[0].Qtype switch metadata.QueryType {
switch metadata.QueryType { case mDNS.TypeA:
case mDNS.TypeA: metadata.IPVersion = 4
metadata.IPVersion = 4 case mDNS.TypeAAAA:
case mDNS.TypeAAAA: metadata.IPVersion = 6
metadata.IPVersion = 6
}
metadata.Domain = fqdnToDomain(message.Question[0].Name)
} }
metadata.Domain = fqdnToDomain(message.Question[0].Name)
var ( var (
options dns.QueryOptions options dns.QueryOptions
rule adapter.DNSRule rule adapter.DNSRule
@@ -202,7 +210,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
if err != nil { if err != nil {
return nil, err return nil, err
} }
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 { if r.dnsReverseMapping != nil && response != nil && len(response.Answer) > 0 {
if _, isFakeIP := transport.(adapter.FakeIPTransport); !isFakeIP { if _, isFakeIP := transport.(adapter.FakeIPTransport); !isFakeIP {
for _, answer := range response.Answer { for _, answer := range response.Answer {
switch record := answer.(type) { switch record := answer.(type) {

View File

@@ -47,6 +47,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay),
UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping,
UDPConnect: action.RouteOptionsOptions.UDPConnect, UDPConnect: action.RouteOptionsOptions.UDPConnect,
UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout),
}, nil }, nil
case C.RuleActionTypeDirect: case C.RuleActionTypeDirect:
directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions)) directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions))
@@ -152,6 +153,7 @@ type RuleActionRouteOptions struct {
FallbackDelay time.Duration FallbackDelay time.Duration
UDPDisableDomainUnmapping bool UDPDisableDomainUnmapping bool
UDPConnect bool UDPConnect bool
UDPTimeout time.Duration
} }
func (r *RuleActionRouteOptions) Type() string { func (r *RuleActionRouteOptions) Type() string {

View File

@@ -12,9 +12,9 @@ require (
github.com/docker/docker v27.3.1+incompatible github.com/docker/docker v27.3.1+incompatible
github.com/docker/go-connections v0.5.0 github.com/docker/go-connections v0.5.0
github.com/gofrs/uuid/v5 v5.3.0 github.com/gofrs/uuid/v5 v5.3.0
github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/quic-go v0.48.2-beta.1
github.com/sagernet/sing v0.6.0-alpha.18 github.com/sagernet/sing v0.6.0-beta.5
github.com/sagernet/sing-dns v0.4.0-alpha.3 github.com/sagernet/sing-dns v0.4.0-beta.1
github.com/sagernet/sing-quic v0.4.0-alpha.4 github.com/sagernet/sing-quic v0.4.0-alpha.4
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
@@ -25,7 +25,7 @@ require (
) )
require ( require (
github.com/Microsoft/go-winio v0.4.14 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/caddyserver/certmagic v0.20.0 // indirect github.com/caddyserver/certmagic v0.20.0 // indirect
@@ -33,7 +33,7 @@ require (
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/cretz/bine v0.2.0 // indirect github.com/cretz/bine v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
@@ -68,10 +68,10 @@ require (
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pkg/errors v0.8.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
@@ -79,27 +79,27 @@ require (
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
github.com/sagernet/cors v1.2.1 // indirect github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 // indirect github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect
github.com/sagernet/sing-tun v0.6.0-alpha.9 // indirect github.com/sagernet/sing-tun v0.6.0-beta.2 // indirect
github.com/sagernet/sing-vmess v0.1.12 // indirect github.com/sagernet/sing-vmess v0.2.0-beta.1 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/utls v1.6.7 // indirect github.com/sagernet/utls v1.6.7 // indirect
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.3 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/sdk v1.32.0 // indirect go.opentelemetry.io/otel/sdk v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
@@ -111,7 +111,8 @@ require (
golang.org/x/text v0.20.0 // indirect golang.org/x/text v0.20.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.24.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
google.golang.org/grpc v1.67.1 // indirect google.golang.org/grpc v1.67.1 // indirect
google.golang.org/protobuf v1.35.1 // indirect google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@@ -1,7 +1,7 @@
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
@@ -19,8 +19,8 @@ github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbe
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@@ -62,8 +62,8 @@ github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5X
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
@@ -78,7 +78,6 @@ github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
@@ -114,14 +113,14 @@ github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
@@ -136,21 +135,21 @@ github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 h1:RxEz7LhPNiF/gX/Hg+OXr5lqsM9iVAgmaK1L1vzlDRM= github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs=
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AEpT94Z0vwo= github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg=
github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-alpha.18 h1:ih4CurU8KvbhfagYjSqVrE2LR0oBSXSZTNH2sAGPGiM= github.com/sagernet/sing v0.6.0-beta.5 h1:RD2j8WmJsvAbbBkAlJWaiYmnd+v/JohBiweoew7kMwo=
github.com/sagernet/sing v0.6.0-alpha.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.0-beta.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-alpha.3 h1:TcAQdz68Gs28VD9o9zDIW7IS8A9LZDruTPI9g9JbGHA= github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
github.com/sagernet/sing-dns v0.4.0-alpha.3/go.mod h1:9LHcYKg2bGQpbtXrfNbopz8ok/zBK9ljiI2kmFG9JKg= github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ= github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ=
@@ -161,26 +160,23 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
github.com/sagernet/sing-tun v0.6.0-alpha.9 h1:Qf667035KnlydZ+ftj3U4HH+oddi3RdyKzBiCcnSgaI= github.com/sagernet/sing-tun v0.6.0-beta.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO3OlYMcQJ0=
github.com/sagernet/sing-tun v0.6.0-alpha.9/go.mod h1:TgvxE2YD7O9c/unHju0nWAGBGsVppWIuju13vlmdllM= github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM= github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84= github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84=
github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
@@ -197,20 +193,20 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -246,10 +242,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -280,10 +274,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=

View File

@@ -4,17 +4,19 @@ import (
std_bufio "bufio" std_bufio "bufio"
"context" "context"
"net" "net"
"os"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/task"
"github.com/sagernet/smux" "github.com/sagernet/smux"
) )
func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) error { func HandleMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler, logger logger.ContextLogger, onClose N.CloseHandlerFunc) error {
session, err := smux.Server(conn, smuxConfig()) session, err := smux.Server(conn, smuxConfig())
if err != nil { if err != nil {
return err return err
@@ -27,29 +29,32 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata
if err != nil { if err != nil {
return err return err
} }
go newMuxConnection(ctx, stream, metadata, handler, logger) go newMuxConnection(ctx, stream, source, handler, logger)
} }
}) })
group.Cleanup(func() { group.Cleanup(func() {
session.Close() session.Close()
if onClose != nil {
onClose(os.ErrClosed)
}
}) })
return group.Run(ctx) return group.Run(ctx)
} }
func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) { func newMuxConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler, logger logger.ContextLogger) {
err := newMuxConnection0(ctx, conn, metadata, handler) err := newMuxConnection0(ctx, conn, source, handler)
if err != nil { if err != nil {
logger.ErrorContext(ctx, E.Cause(err, "process trojan-go multiplex connection")) logger.ErrorContext(ctx, E.Cause(err, "process trojan-go multiplex connection"))
} }
} }
func newMuxConnection0(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) error { func newMuxConnection0(ctx context.Context, conn net.Conn, source M.Socksaddr, handler Handler) error {
reader := std_bufio.NewReader(conn) reader := std_bufio.NewReader(conn)
command, err := reader.ReadByte() command, err := reader.ReadByte()
if err != nil { if err != nil {
return E.Cause(err, "read command") return E.Cause(err, "read command")
} }
metadata.Destination, err = M.SocksaddrSerializer.ReadAddrPort(reader) destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
if err != nil { if err != nil {
return E.Cause(err, "read destination") return E.Cause(err, "read destination")
} }
@@ -63,12 +68,13 @@ func newMuxConnection0(ctx context.Context, conn net.Conn, metadata M.Metadata,
} }
switch command { switch command {
case CommandTCP: case CommandTCP:
return handler.NewConnection(ctx, conn, metadata) handler.NewConnectionEx(ctx, conn, source, destination, nil)
case CommandUDP: case CommandUDP:
return handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, source, destination, nil)
default: default:
return E.New("unknown command ", command) return E.New("unknown command ", command)
} }
return nil
} }
func smuxConfig() *smux.Config { func smuxConfig() *smux.Config {

View File

@@ -16,19 +16,19 @@ import (
) )
type Handler interface { type Handler interface {
N.TCPConnectionHandler N.TCPConnectionHandlerEx
N.UDPConnectionHandler N.UDPConnectionHandlerEx
} }
type Service[K comparable] struct { type Service[K comparable] struct {
users map[K][56]byte users map[K][56]byte
keys map[[56]byte]K keys map[[56]byte]K
handler Handler handler Handler
fallbackHandler N.TCPConnectionHandler fallbackHandler N.TCPConnectionHandlerEx
logger logger.ContextLogger logger logger.ContextLogger
} }
func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler, logger logger.ContextLogger) *Service[K] { func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandlerEx, logger logger.ContextLogger) *Service[K] {
return &Service[K]{ return &Service[K]{
users: make(map[K][56]byte), users: make(map[K][56]byte),
keys: make(map[[56]byte]K), keys: make(map[[56]byte]K),
@@ -59,19 +59,19 @@ func (s *Service[K]) UpdateUsers(userList []K, passwordList []string) error {
return nil return nil
} }
func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, onClose N.CloseHandlerFunc) error {
var key [KeyLength]byte var key [KeyLength]byte
n, err := conn.Read(key[:]) n, err := conn.Read(key[:])
if err != nil { if err != nil {
return err return err
} else if n != KeyLength { } else if n != KeyLength {
return s.fallback(ctx, conn, metadata, key[:n], E.New("bad request size")) return s.fallback(ctx, conn, source, key[:n], E.New("bad request size"), onClose)
} }
if user, loaded := s.keys[key]; loaded { if user, loaded := s.keys[key]; loaded {
ctx = auth.ContextWithUser(ctx, user) ctx = auth.ContextWithUser(ctx, user)
} else { } else {
return s.fallback(ctx, conn, metadata, key[:], E.New("bad request")) return s.fallback(ctx, conn, source, key[:], E.New("bad request"), onClose)
} }
err = rw.SkipN(conn, 2) err = rw.SkipN(conn, 2)
@@ -102,26 +102,25 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata
return E.Cause(err, "skip crlf") return E.Cause(err, "skip crlf")
} }
metadata.Protocol = "trojan"
metadata.Destination = destination
switch command { switch command {
case CommandTCP: case CommandTCP:
return s.handler.NewConnection(ctx, conn, metadata) s.handler.NewConnectionEx(ctx, conn, source, destination, onClose)
case CommandUDP: case CommandUDP:
return s.handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) s.handler.NewPacketConnectionEx(ctx, &PacketConn{Conn: conn}, source, destination, onClose)
// case CommandMux: // case CommandMux:
default: default:
return HandleMuxConnection(ctx, conn, metadata, s.handler, s.logger) return HandleMuxConnection(ctx, conn, source, s.handler, s.logger, onClose)
} }
return nil
} }
func (s *Service[K]) fallback(ctx context.Context, conn net.Conn, metadata M.Metadata, header []byte, err error) error { func (s *Service[K]) fallback(ctx context.Context, conn net.Conn, source M.Socksaddr, header []byte, err error, onClose N.CloseHandlerFunc) error {
if s.fallbackHandler == nil { if s.fallbackHandler == nil {
return E.Extend(err, "fallback disabled") return E.Extend(err, "fallback disabled")
} }
conn = bufio.NewCachedConn(conn, buf.As(header).ToOwned()) conn = bufio.NewCachedConn(conn, buf.As(header).ToOwned())
return s.fallbackHandler.NewConnection(ctx, conn, metadata) s.fallbackHandler.NewConnectionEx(ctx, conn, source, M.Socksaddr{}, onClose)
return nil
} }
type PacketConn struct { type PacketConn struct {

View File

@@ -71,6 +71,7 @@ func (w *systemDevice) Start() error {
Inet6RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { return it.Addr().Is6() }), Inet6RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool { return it.Addr().Is6() }),
InterfaceMonitor: networkManager.InterfaceMonitor(), InterfaceMonitor: networkManager.InterfaceMonitor(),
InterfaceFinder: networkManager.InterfaceFinder(), InterfaceFinder: networkManager.InterfaceFinder(),
Logger: w.options.Logger,
} }
// works with Linux, macOS with IFSCOPE routes, not tested on Windows // works with Linux, macOS with IFSCOPE routes, not tested on Windows
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {

View File

@@ -150,14 +150,20 @@ func (e *Endpoint) Start(resolve bool) error {
connectAddr netip.AddrPort connectAddr netip.AddrPort
reserved [3]uint8 reserved [3]uint8
) )
peerLen := len(e.peers) if len(e.peers) == 1 {
if peerLen == 1 {
isConnect = true isConnect = true
connectAddr = e.peers[0].endpoint connectAddr = e.peers[0].endpoint
reserved = e.peers[0].reserved reserved = e.peers[0].reserved
} }
bind = NewClientBind(e.options.Context, e.options.Logger, e.options.Dialer, isConnect, connectAddr, reserved) bind = NewClientBind(e.options.Context, e.options.Logger, e.options.Dialer, isConnect, connectAddr, reserved)
} }
if isWgListener || len(e.peers) > 1 {
for _, peer := range e.peers {
if peer.reserved != [3]uint8{} {
bind.SetReservedForEndpoint(peer.endpoint, peer.reserved)
}
}
}
err := e.tunDevice.Start() err := e.tunDevice.Start()
if err != nil { if err != nil {
return err return err