mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10874d2dc4 | ||
|
|
5adaf1ac75 | ||
|
|
9668ea69b8 | ||
|
|
ae9bc7acf1 | ||
|
|
594ee480a2 | ||
|
|
a15b5a2463 | ||
|
|
991e755789 | ||
|
|
97d41ffde8 | ||
|
|
24af0766ac | ||
|
|
af17eaa537 | ||
|
|
3adc10a797 | ||
|
|
5eeef6b28e | ||
|
|
f4c29840c3 | ||
|
|
47fc3ebda4 | ||
|
|
9774a659b0 | ||
|
|
2e4a6de4e7 | ||
|
|
a530e424e9 | ||
|
|
0bfd487ee9 | ||
|
|
6aae834493 | ||
|
|
f56131f38e | ||
|
|
273a11d550 | ||
|
|
ae8ce75e41 | ||
|
|
d6d94b689f | ||
|
|
30d785f1ee | ||
|
|
db5ec3cdfc | ||
|
|
9aca54d039 | ||
|
|
d55d5009c2 | ||
|
|
4f3ee61104 | ||
|
|
96eb98c00a | ||
|
|
68ce9577c6 | ||
|
|
3ae036e997 | ||
|
|
5da2d1d470 | ||
|
|
8e2baf40f1 | ||
|
|
c24c40dfee | ||
|
|
32e52ce1ed | ||
|
|
ed46438359 | ||
|
|
0b5490d5a3 | ||
|
|
2d73ef511d | ||
|
|
63e6c85f6f | ||
|
|
8946a6d2d0 | ||
|
|
d3132645fb | ||
|
|
373f158fe0 | ||
|
|
ce36835fab | ||
|
|
619fa671d7 | ||
|
|
eb07c7a79e | ||
|
|
7eb3535094 | ||
|
|
93b68312cf | ||
|
|
97ce666e43 | ||
|
|
4000e1e66d | ||
|
|
270740e859 | ||
|
|
6cad142cfe | ||
|
|
093013687c | ||
|
|
ff31c469a0 | ||
|
|
fbe390268c | ||
|
|
07ac01dcb7 | ||
|
|
badfdb62cd | ||
|
|
986a410b30 | ||
|
|
9db2d58545 | ||
|
|
4eed46ac59 | ||
|
|
abc38d1dab | ||
|
|
8d6c4f1289 | ||
|
|
a2d40eb8b8 | ||
|
|
17b502bb4b | ||
|
|
a0d4421085 | ||
|
|
0d443072d1 | ||
|
|
c9fb99b799 | ||
|
|
92d245ad04 | ||
|
|
0908627297 | ||
|
|
7f79458b4f | ||
|
|
9b4c11ba95 | ||
|
|
27c31eac5d | ||
|
|
bab8dc0b82 |
20
.fpm
Normal file
20
.fpm
Normal file
@@ -0,0 +1,20 @@
|
||||
-s dir
|
||||
--name sing-box
|
||||
--category net
|
||||
--license GPLv3-or-later
|
||||
--description "The universal proxy platform."
|
||||
--url "https://sing-box.sagernet.org/"
|
||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||
--config-files /etc/sing-box/config.json
|
||||
|
||||
release/config/config.json=/etc/sing-box/config.json
|
||||
|
||||
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||
|
||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
||||
|
||||
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
||||
314
.github/workflows/build.yml
vendored
314
.github/workflows/build.yml
vendored
@@ -46,16 +46,16 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
echo "version=${{ inputs.version }}"
|
||||
echo "version=${{ inputs.version }}"
|
||||
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||
- name: Calculate version
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/read_tag --nightly
|
||||
go run -v ./cmd/internal/read_tag --ci --nightly
|
||||
- name: Set outputs
|
||||
id: outputs
|
||||
run: |-
|
||||
@@ -68,142 +68,174 @@ jobs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ linux, windows, darwin, android ]
|
||||
arch: [ "386", amd64, arm64 ]
|
||||
legacy_go: [ false ]
|
||||
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
|
||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||
|
||||
- { os: windows, arch: "386", legacy_go: true }
|
||||
- { os: windows, arch: amd64, legacy_go: true }
|
||||
- { os: darwin, arch: amd64, legacy_go: true }
|
||||
|
||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||
exclude:
|
||||
- { os: darwin, arch: "386" }
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
if: matrix.legacy_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
|
||||
go-version: ~1.20
|
||||
- name: Setup Go
|
||||
if: ${{ ! matrix.legacy_go }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
path: |
|
||||
~/go/go1.20.14
|
||||
key: go120
|
||||
- name: Setup legacy Go
|
||||
if: matrix.require_legacy_go && 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
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
if: matrix.goos == 'android'
|
||||
if: matrix.os == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
ndk-version: r28
|
||||
local-cache: true
|
||||
- name: Setup Goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: 2.5.1
|
||||
install-only: true
|
||||
- name: Extract signing key
|
||||
run: |-
|
||||
mkdir -p $HOME/.gnupg
|
||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
EOF
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
- name: Set tag
|
||||
run: |-
|
||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||
- name: Set build tags
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
||||
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
||||
TAGS="${TAGS},with_ech"
|
||||
fi
|
||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
if: matrix.goos != 'android'
|
||||
run: |-
|
||||
goreleaser release --clean --split
|
||||
if: matrix.os != 'android'
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
mkdir -p dist
|
||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||
./cmd/sing-box
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
GOPATH: ${{ env.HOME }}/go
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: ${{ matrix.os }}
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
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: |-
|
||||
if: matrix.os == 'android'
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
go install -v ./cmd/internal/build
|
||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
|
||||
export CC='${{ matrix.ndk }}-clang'
|
||||
export CXX="${CC}++"
|
||||
mkdir -p dist
|
||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||
./cmd/sing-box
|
||||
env:
|
||||
BUILD_GOOS: ${{ matrix.goos }}
|
||||
BUILD_GOARCH: ${{ matrix.goarch }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
CGO_ENABLED: "1"
|
||||
BUILD_GOOS: ${{ matrix.os }}
|
||||
BUILD_GOARCH: ${{ matrix.arch }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
- name: Set name
|
||||
run: |-
|
||||
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
|
||||
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
|
||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
|
||||
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||
- name: Package DEB
|
||||
if: matrix.debian != ''
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get install -y debsigs
|
||||
fpm -t deb \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.deb" \
|
||||
--architecture ${{ matrix.debian }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||
rm -rf $HOME/.gnupg
|
||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
EOF
|
||||
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
||||
- name: Package RPM
|
||||
if: matrix.rpm != ''
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
fpm -t rpm \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.rpm" \
|
||||
--architecture ${{ matrix.rpm }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
cat > $HOME/.rpmmacros <<EOF
|
||||
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||
EOF
|
||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
EOF
|
||||
rpmsign --addsign dist/*.rpm
|
||||
- name: Package Pacman
|
||||
if: matrix.pacman != ''
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get install -y libarchive-tools
|
||||
fpm -t pacman \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
||||
--architecture ${{ matrix.pacman }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
- name: Archive
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
cd dist
|
||||
mkdir -p "${DIR_NAME}"
|
||||
cp ../LICENSE "${DIR_NAME}"
|
||||
if [ '${{ matrix.os }}' = 'windows' ]; then
|
||||
cp sing-box "${DIR_NAME}/sing-box.exe"
|
||||
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
||||
else
|
||||
cp sing-box "${DIR_NAME}"
|
||||
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
||||
fi
|
||||
rm -r "${DIR_NAME}"
|
||||
- name: Cleanup
|
||||
run: rm dist/sing-box
|
||||
- name: Upload artifact
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-${{ matrix.name }}
|
||||
path: 'dist'
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
path: "dist"
|
||||
build_android:
|
||||
name: Build Android
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||
@@ -219,12 +251,12 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta3
|
||||
ndk-version: r28
|
||||
- name: Setup OpenJDK
|
||||
run: |-
|
||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||
@@ -256,9 +288,16 @@ jobs:
|
||||
with:
|
||||
path: ~/.gradle
|
||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||
- name: Build
|
||||
- name: Update version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci
|
||||
- name: Update nightly version
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci --nightly
|
||||
- name: Build
|
||||
run: |-
|
||||
mkdir clients/android/app/libs
|
||||
cp libbox.aar clients/android/app/libs
|
||||
cd clients/android
|
||||
@@ -268,13 +307,11 @@ jobs:
|
||||
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
|
||||
mkdir -p dist
|
||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
||||
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
|
||||
- name: Upload artifact
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-android-apks
|
||||
@@ -294,12 +331,12 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta3
|
||||
ndk-version: r28
|
||||
- name: Setup OpenJDK
|
||||
run: |-
|
||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||
@@ -392,7 +429,7 @@ jobs:
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
@@ -432,19 +469,19 @@ jobs:
|
||||
|
||||
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
||||
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
||||
|
||||
|
||||
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
mkdir -p "$PROFILES_PATH"
|
||||
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
||||
|
||||
|
||||
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
||||
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
||||
|
||||
|
||||
xcrun notarytool store-credentials "notarytool-password" \
|
||||
--key $ASC_KEY_PATH \
|
||||
--key-id $ASC_KEY_ID \
|
||||
--issuer $ASC_KEY_ISSUER_ID
|
||||
|
||||
|
||||
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
||||
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
||||
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
||||
@@ -520,10 +557,10 @@ jobs:
|
||||
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"
|
||||
|
||||
mkdir -p dist
|
||||
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
|
||||
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/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
|
||||
@@ -544,12 +581,6 @@ jobs:
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: 2.5.1
|
||||
install-only: true
|
||||
- name: Cache ghr
|
||||
uses: actions/cache@v4
|
||||
id: cache-ghr
|
||||
@@ -574,26 +605,17 @@ jobs:
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Merge builds
|
||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||
run: |-
|
||||
goreleaser continue --merge --skip publish
|
||||
mkdir -p dist/release
|
||||
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
- name: Upload builds
|
||||
if: ${{ env.PUBLISHED == 'false' }}
|
||||
run: |-
|
||||
export PATH="$PATH:$HOME/go/bin"
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Replace builds
|
||||
if: ${{ env.PUBLISHED != 'false' }}
|
||||
run: |-
|
||||
export PATH="$PATH:$HOME/go/bin"
|
||||
ghr --replace -p 5 "v${VERSION}" dist/release
|
||||
ghr --replace -p 5 "v${VERSION}" dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
5
.github/workflows/lint.yml
vendored
5
.github/workflows/lint.yml
vendored
@@ -28,10 +28,11 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=30m
|
||||
install-mode: binary
|
||||
install-mode: binary
|
||||
verify: false
|
||||
|
||||
183
.github/workflows/linux.yml
vendored
183
.github/workflows/linux.yml
vendored
@@ -1,13 +1,22 @@
|
||||
name: Release to Linux repository
|
||||
name: Build Linux Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version name"
|
||||
required: true
|
||||
type: string
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
build:
|
||||
calculate_version:
|
||||
name: Calculate version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.outputs.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
@@ -16,23 +25,161 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
- name: Extract signing key
|
||||
go-version: ^1.24
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
mkdir -p $HOME/.gnupg
|
||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||
echo "version=${{ inputs.version }}"
|
||||
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||
- name: Calculate version
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/read_tag --ci --nightly
|
||||
- name: Set outputs
|
||||
id: outputs
|
||||
run: |-
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
build:
|
||||
name: Build binary
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
if: matrix.os == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28
|
||||
local-cache: true
|
||||
- name: Set tag
|
||||
run: |-
|
||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||
- name: Set build tags
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
||||
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
||||
TAGS="${TAGS},with_ech"
|
||||
fi
|
||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
mkdir -p dist
|
||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||
./cmd/sing-box
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: ${{ matrix.os }}
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set mtime
|
||||
run: |-
|
||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||
- name: Set name
|
||||
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
|
||||
run: |-
|
||||
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||
- name: Set beta name
|
||||
if: contains(needs.calculate_version.outputs.version, '-')
|
||||
run: |-
|
||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||
- name: Set version
|
||||
run: |-
|
||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||
- name: Package DEB
|
||||
if: matrix.debian != ''
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get install -y debsigs
|
||||
fpm -t deb \
|
||||
--name "${NAME}" \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
||||
--architecture ${{ matrix.debian }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||
rm -rf $HOME/.gnupg
|
||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
EOF
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
- name: Publish release
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
||||
- name: Package RPM
|
||||
if: matrix.rpm != ''
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
fpm -t rpm \
|
||||
--name "${NAME}" \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
||||
--architecture ${{ matrix.rpm }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
cat > $HOME/.rpmmacros <<EOF
|
||||
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||
EOF
|
||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||
${{ secrets.GPG_KEY }}
|
||||
EOF
|
||||
rpmsign --addsign dist/*.rpm
|
||||
- name: Cleanup
|
||||
run: rm dist/sing-box
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release -f .goreleaser.fury.yaml --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
path: "dist"
|
||||
upload:
|
||||
name: Upload builds
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- calculate_version
|
||||
- build
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set tag
|
||||
run: |-
|
||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||
- name: Download builds
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Publish packages
|
||||
run: |-
|
||||
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
||||
|
||||
@@ -48,7 +48,7 @@ nfpms:
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
type: "config|noreplace"
|
||||
|
||||
- src: release/config/sing-box.service
|
||||
dst: /usr/lib/systemd/system/sing-box.service
|
||||
|
||||
@@ -95,10 +95,12 @@ archives:
|
||||
builds:
|
||||
- main
|
||||
- android
|
||||
format: tar.gz
|
||||
formats:
|
||||
- tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats:
|
||||
- zip
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
@@ -128,7 +130,7 @@ nfpms:
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
dst: /etc/sing-box/config.json
|
||||
type: config
|
||||
type: "config|noreplace"
|
||||
|
||||
- src: release/config/sing-box.service
|
||||
dst: /usr/lib/systemd/system/sing-box.service
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
COPY . /go/src/github.com/sagernet/sing-box
|
||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||
|
||||
@@ -39,17 +39,17 @@ type CacheFile interface {
|
||||
StoreSelected(group string, selected string) error
|
||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
||||
StoreGroupExpand(group string, expand bool) error
|
||||
LoadRuleSet(tag string) *SavedRuleSet
|
||||
SaveRuleSet(tag string, set *SavedRuleSet) error
|
||||
LoadRuleSet(tag string) *SavedBinary
|
||||
SaveRuleSet(tag string, set *SavedBinary) error
|
||||
}
|
||||
|
||||
type SavedRuleSet struct {
|
||||
type SavedBinary struct {
|
||||
Content []byte
|
||||
LastUpdated time.Time
|
||||
LastEtag string
|
||||
}
|
||||
|
||||
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
||||
var buffer bytes.Buffer
|
||||
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
||||
if err != nil {
|
||||
@@ -70,7 +70,7 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
||||
reader := bytes.NewReader(data)
|
||||
var version uint8
|
||||
err := binary.Read(reader, binary.BigEndian, &version)
|
||||
|
||||
@@ -53,10 +53,11 @@ type InboundContext struct {
|
||||
|
||||
// sniffer
|
||||
|
||||
Protocol string
|
||||
Domain string
|
||||
Client string
|
||||
SniffContext any
|
||||
Protocol string
|
||||
Domain string
|
||||
Client string
|
||||
SniffContext any
|
||||
PacketSniffError error
|
||||
|
||||
// cache
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ type NetworkManager interface {
|
||||
PackageManager() tun.PackageManager
|
||||
WIFIState() WIFIState
|
||||
ResetNetwork()
|
||||
UpdateWIFIState()
|
||||
}
|
||||
|
||||
type NetworkOptions struct {
|
||||
|
||||
@@ -38,7 +38,7 @@ type Router interface {
|
||||
ClearDNSCache()
|
||||
Rules() []Rule
|
||||
|
||||
SetTracker(tracker ConnectionTracker)
|
||||
AppendTracker(tracker ConnectionTracker)
|
||||
|
||||
ResetNetwork()
|
||||
}
|
||||
|
||||
17
box.go
17
box.go
@@ -165,7 +165,15 @@ func New(options Options) (*Box, error) {
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = endpointManager.Create(ctx,
|
||||
endpointCtx := ctx
|
||||
if tag != "" {
|
||||
// TODO: remove this
|
||||
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
|
||||
Outbound: tag,
|
||||
})
|
||||
}
|
||||
err = endpointManager.Create(
|
||||
endpointCtx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
@@ -183,7 +191,8 @@ func New(options Options) (*Box, error) {
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = inboundManager.Create(ctx,
|
||||
err = inboundManager.Create(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
@@ -248,7 +257,7 @@ func New(options Options) (*Box, error) {
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create clash-server")
|
||||
}
|
||||
router.SetTracker(clashServer)
|
||||
router.AppendTracker(clashServer)
|
||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||
services = append(services, clashServer)
|
||||
}
|
||||
@@ -258,7 +267,7 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "create v2ray-server")
|
||||
}
|
||||
if v2rayServer.StatsService() != nil {
|
||||
router.SetTracker(v2rayServer.StatsService())
|
||||
router.AppendTracker(v2rayServer.StatsService())
|
||||
services = append(services, v2rayServer)
|
||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||
}
|
||||
|
||||
Submodule clients/android updated: 599d8cecac...8354b78e5d
Submodule clients/apple updated: e43e3a3f64...ae5818ee5a
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/asc-go/asc"
|
||||
@@ -194,6 +195,10 @@ func publishTestflight(ctx context.Context) error {
|
||||
log.Info(string(platform), " ", tag, " create submission")
|
||||
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
||||
log.Error(err)
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func FindSDK() {
|
||||
}
|
||||
|
||||
func findNDK() bool {
|
||||
const fixedVersion = "28.0.12916984"
|
||||
const fixedVersion = "28.0.13004108"
|
||||
const versionFile = "source.properties"
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||
androidNDKPath = fixedPath
|
||||
|
||||
@@ -5,40 +5,49 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||
"github.com/sagernet/sing-box/common/badversion"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
var nightly bool
|
||||
var (
|
||||
flagRunInCI bool
|
||||
flagRunNightly bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if nightly {
|
||||
version, err := build_shared.ReadTagVersionRev()
|
||||
var (
|
||||
versionStr string
|
||||
err error
|
||||
)
|
||||
if flagRunNightly {
|
||||
var version badversion.Version
|
||||
version, err = build_shared.ReadTagVersion()
|
||||
if err == nil {
|
||||
versionStr = version.String()
|
||||
}
|
||||
} else {
|
||||
versionStr, err = build_shared.ReadTag()
|
||||
}
|
||||
if flagRunInCI {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var versionStr string
|
||||
if version.PreReleaseIdentifier != "" {
|
||||
versionStr = version.VersionString() + "-nightly"
|
||||
} else {
|
||||
version.Patch++
|
||||
versionStr = version.VersionString() + "-nightly"
|
||||
}
|
||||
err = setGitHubEnv("version", versionStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
tag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
os.Stdout.WriteString("unknown\n")
|
||||
} else {
|
||||
os.Stdout.WriteString(tag + "\n")
|
||||
os.Stdout.WriteString(versionStr + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,14 @@ import (
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
var flagRunInCI bool
|
||||
var (
|
||||
flagRunInCI bool
|
||||
flagRunNightly bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -46,21 +50,23 @@ func main() {
|
||||
switch propPair[0] {
|
||||
case "VERSION_NAME":
|
||||
if propPair[1] != newVersion {
|
||||
log.Info("updated version from ", propPair[1], " to ", newVersion)
|
||||
versionUpdated = true
|
||||
propPair[1] = newVersion
|
||||
log.Info("updated version to ", newVersion)
|
||||
}
|
||||
case "GO_VERSION":
|
||||
if propPair[1] != runtime.Version() {
|
||||
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
|
||||
goVersionUpdated = true
|
||||
propPair[1] = runtime.Version()
|
||||
log.Info("updated Go version to ", runtime.Version())
|
||||
}
|
||||
}
|
||||
}
|
||||
if !(versionUpdated || goVersionUpdated) {
|
||||
log.Info("version not changed")
|
||||
return
|
||||
} else if flagRunInCI && !flagRunNightly {
|
||||
log.Fatal("version changed, commit changes first.")
|
||||
}
|
||||
for _, propPair := range propsList {
|
||||
switch propPair[0] {
|
||||
|
||||
@@ -30,7 +30,7 @@ func init() {
|
||||
}
|
||||
|
||||
func generateTLSKeyPair(serverName string) error {
|
||||
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, false, E.New("no available network interface")
|
||||
}
|
||||
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
@@ -31,7 +32,9 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||
perNetDialer := dialer
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
if defaultInterface == nil || iif.Index != defaultInterface.Index {
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
}
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
@@ -89,6 +92,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, false, E.New("no available network interface")
|
||||
}
|
||||
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
@@ -103,7 +107,9 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||
perNetDialer := dialer
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
if defaultInterface == nil || iif.Index != defaultInterface.Index {
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
}
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
@@ -149,10 +155,13 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, E.New("no available network interface")
|
||||
}
|
||||
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
|
||||
var errors []error
|
||||
for _, primaryInterface := range primaryInterfaces {
|
||||
perNetListener := listener
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
|
||||
if defaultInterface == nil || primaryInterface.Index != defaultInterface.Index {
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
|
||||
}
|
||||
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
@@ -161,7 +170,9 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
||||
}
|
||||
for _, fallbackInterface := range fallbackInterfaces {
|
||||
perNetListener := listener
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
|
||||
if defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index {
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
|
||||
}
|
||||
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
|
||||
@@ -124,14 +124,6 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
|
||||
for i := 0; i < n; i++ {
|
||||
row := b[4+itemSize*i : 4+itemSize*(i+1)]
|
||||
|
||||
if s.tcpState >= 0 {
|
||||
tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
|
||||
// MIB_TCP_STATE_ESTAB, only check established connections for TCP
|
||||
if tcpState != 5 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
|
||||
// this field can be illustrated as follows depends on different machine endianess:
|
||||
// little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB)
|
||||
@@ -144,7 +136,7 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
|
||||
|
||||
srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
|
||||
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
|
||||
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
||||
if ip != srcIP && (!srcIP.IsUnspecified()) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,7 +24,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
||||
var first byte
|
||||
err := binary.Read(reader, binary.BigEndian, &first)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
|
||||
if first != 19 {
|
||||
@@ -33,7 +34,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
||||
var protocol [19]byte
|
||||
_, err = reader.Read(protocol[:])
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if string(protocol[:]) != "BitTorrent protocol" {
|
||||
return os.ErrInvalid
|
||||
@@ -67,7 +68,9 @@ func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if extension > 0x04 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
var length byte
|
||||
err = binary.Read(reader, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
|
||||
@@ -71,3 +71,19 @@ func TestSniffUDPTracker(t *testing.T) {
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffNotUTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
packets := []string{
|
||||
"0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
}
|
||||
for _, pkt := range packets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.UTP(context.TODO(), &metadata, pkt)
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,11 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/task"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
@@ -21,22 +18,16 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
||||
var length uint16
|
||||
err := binary.Read(reader, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return os.ErrInvalid
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if length == 0 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
buffer := buf.NewSize(int(length))
|
||||
defer buffer.Release()
|
||||
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
|
||||
var readTask task.Group
|
||||
readTask.Append0(func(ctx context.Context) error {
|
||||
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen()))
|
||||
})
|
||||
err = readTask.Run(readCtx)
|
||||
cancel()
|
||||
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
|
||||
}
|
||||
@@ -47,9 +38,6 @@ func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, pack
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
metadata.Protocol = C.ProtocolDNS
|
||||
return nil
|
||||
}
|
||||
|
||||
23
common/sniff/dns_test.go
Normal file
23
common/sniff/dns_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSniffDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.DomainNameQuery(context.TODO(), &metadata, query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||
}
|
||||
@@ -3,10 +3,12 @@ package sniff
|
||||
import (
|
||||
std_bufio "bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/protocol/http"
|
||||
)
|
||||
@@ -14,7 +16,11 @@ import (
|
||||
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||
request, err := http.ReadRequest(std_bufio.NewReader(reader))
|
||||
if err != nil {
|
||||
return err
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
metadata.Protocol = C.ProtocolHTTP
|
||||
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
||||
|
||||
@@ -20,8 +20,6 @@ import (
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection")
|
||||
|
||||
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||
reader := bytes.NewReader(packet)
|
||||
typeByte, err := reader.ReadByte()
|
||||
@@ -308,7 +306,7 @@ find:
|
||||
metadata.Protocol = C.ProtocolQUIC
|
||||
metadata.Client = C.ClientChromium
|
||||
metadata.SniffContext = fragments
|
||||
return ErrClientHelloFragmented
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
metadata.Domain = fingerprint.ServerName
|
||||
for metadata.Client == "" {
|
||||
|
||||
@@ -12,6 +12,26 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSniffQUICChromeNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
pkt, err := hex.DecodeString("ca0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489ad89c322f75f9a383c90d126a0b21104cb519c2bb32e6a134e86896452e942b26c519b8c7ac9e4c99fae5e1f65cf08fb98443b30e4567932e8fb0789820d8f33037b59ac8113530258c9467dfb52489396dae01f099d28b234efa107fa411f2a1ffa2abe74988e03d662d4296024e95ce0fe1671724937157f77b84990478a2d4060676cf0827b4e8c600654111750414dafa0cccb332f3020c2922a015f445df5edc9c7d2d1ceea9fddcc9ff821c9183aa39a70da20fcc057579e1051c1c899148d6cf9d08b4919822082d040d1ce03ca4f216be6cb7ef03db6df0993ef1ccce5c8c648980554f41704526e1809d2545739f5872e75ec797db1c99f5682e2eda9363cb32aa367b7b363c782ddbacf874183cc15c8a2db068dd4093eebdd096ad33832a7939deb0a872279744f5a56dc001ba62fac973bf680f3b362bdd336add4dd102f462b773bf70bfce1921070a802a92025273a177186d1a643081b42175eb789ccddadb71033ef4feacbf6fd282ab622cf61669d73cda559e411c6ccdd8f003443b6933b7729b7a357aa4aa2fba0f365f829a4d497afb5dc2648a53bc9f3e786d955069d0a4781088a5463747dfe9958ea19ea444eae947ec6a67640955f710f93640084f3fbb8ad259b68dbc0ee0b7fab2d81bffd83ed8a6d33522dbfef43bec0a0fb4bdf1cb712dc4ced0680c0687fa240fd157baa232b1c84e14adce6421cf9270f9b3972f98fc67b344b8a4f1fb551e26f7f76d484ed9f8197f231dc5d9a44cc0ddce73d7f810a620851f4e97eb5037ab5135d7c3be5b80cc32d19910b8387aca64c93c02dc3e35238b78e6aff470722078982e58802844932b6041446bfdcc97ba640cbb86721bcd0f40f27b77aa6287ce5674ec1720134b9302875482c3269787e004b9edb483d44f326eef38c0e83cb46af96488c2e696bc2524567fb29c1e8edcd5a73615496d172d46a9d29e0505c0018b7bbb00165eca0389e09c4b1d73b6cc4a2f735a720650134a2e98e8105e20695cf231b92586237dfe0f99c897414e51c21627496276535f07abb53fb2b554376fe520fa45a3e944fd91dfe7a72aead08842b6b63d8edf861fb911954c83bd9a896eb9da4af5eff646455069d747facd4e77c254096843bff7c3e9031dbdf8dc37ea45f1122922fcbc322ec1378f3c7c1af0da62e1052e6210f1b23073f93a82d90e14cb20bc4501d487a1c848674d57a7c269b13590b3a99d8b8b4f6d0dfbd1d2cbbe7a32c0d5c84ae7ec438b0b19f3862d8fabaa828d06c7e3c6967405cd56a1ae90f38633e2ee0e3ecfca3df399fe12f029e0860a1a30da010300d0c94f0bf56091d00011488c1429928b21c739ebf50ba8be91116315d3173f6d2c56735722478c4d74392ba84d1727036b3d64e8c2263b0f33cb8086be587ca6b3940259c06afa2683868856529303ae12e91d7ca874568be7f2bfaa0656dfab0ed31ed90eaea10fb7f3433ec59a334abe6211d547fa0c825ac45d3691e749d15432008de83e9f6d98f368359137ae803d9189b3386f800c7c0cf4b615d1983cf82d9981a8105b60a80fe66c9b0d439b5ba153dd19e9e7483a01cf3b02b4597540b38e658d4eb8455e030b2bf2690bdd78c23f16fe5")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
||||
require.NoError(t, err)
|
||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
|
||||
require.NoError(t, err)
|
||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "www.google.com", metadata.Domain)
|
||||
}
|
||||
|
||||
func TestSniffQUICChromium(t *testing.T) {
|
||||
t.Parallel()
|
||||
pkt, err := hex.DecodeString("c30000000108f40d654cc09b27f5000044d08a94548e57e43cc5483f129986187c432d58d46674830442988f869566a6e31e2ae37c9f7acbf61cc81621594fab0b3dfdc1635460b32389563dc8e74006315661cd22694114612973c1c45910621713a48b375854f095e8a77ccf3afa64e972f0f7f7002f50e0b014b1b146ea47c07fb20b73ad5587872b51a0b3fafdf1c4cf4fe6f8b112142392efa25d993abe2f42582be145148bdfe12edcd96c3655b65a4781b093e5594ba8e3ae5320f12e8314fc3ca374128cc43381046c322b964681ed4395c813b28534505118201459665a44b8f0abead877de322e9040631d20b05f15b81fa7ff785d4041aecc37c7e2ccdc5d1532787ce566517e8985fd5c200dbfd1e67bc255efaba94cfc07bb52fea4a90887413b134f2715b5643542aa897c6116486f428d82da64d2a2c1e1bdd40bd592558901a554b003d6966ac5a7b8b9413eddbf6ef21f28386c74981e3ce1d724c341e95494907626659692720c81114ca4acea35a14c402cfa3dc2228446e78dc1b81fa4325cf7e314a9cad6a6bdff33b3351dcba74eb15fae67f1227283aa4cdd64bcadf8f19358333f8549b596f4350297b5c65274565869d497398339947b9d3d064e5b06d39d34b436d8a41c1a3880de10bd26c3b1c5b4e2a49b0d4d07b8d90cd9e92bc611564d19ea8ec33099e92033caf21f5307dbeaa4708b99eb313bff99e2081ac25fd12d6a72e8335e0724f6718fe023cd0ad0d6e6a6309f09c9c391eec2bc08e9c3210a043c08e1759f354c121f6517fff4d6e20711a871e41285d48d930352fddffb92c96ba57df045ce99f8bfdfa8edc0969ce68a51e9fbb4f54b956d9df74a9e4af27ed2b27839bce1cffeca8333c0aaee81a570217442f9029ba8fedb84a2cf4be4d910982d891ea00e816c7fb98e8020e896a9c6fdd9106611da0a99dde18df1b7a8f6327acb1eed9ad93314451e48cb0dfb9571728521ca3db2ac0968159d5622556a55d51a422d11995b650949aaefc5d24c16080446dfc4fbc10353f9f93ce161ab513367bb89ab83988e0630b689e174e27bcfcc31996ee7b0bca909e251b82d69a28fee5a5d662e127508cd19dbbe5097b7d5b62a49203d66764197a527e472e2627e44a93d44177dace9d60e7d0e03305ddf4cfe47cdf2362e14de79ef46a6763ce696cd7854a48d9419a0817507a4713ffd4977b906d4f2b5fb6dbe1bd15bc505d5fea582190bf531a45d5ee026da8918547fd5105f15e5d061c7b0cf80a34990366ed8e91e13c2f0d85e5dad537298808d193cf54b7eaac33f10051f74cb6b75e52f81618c36f03d86aef613ba237a1a793ba1539938a38f62ccaf7bd5f6c5e0ce53cde4012fcf2b758214a0422d2faaa798e86e19d7481b42df2b36a73d287ff28c20cce01ce598771fec16a8f1f00305c06010126013a6c1de9f589b4e79d693717cd88ad1c42a2d99fa96617ba0bc6365b68e21a70ebc447904aa27979e1514433cfd83bfec09f137c747d47582cb63eb28f873fb94cf7a59ff764ddfbb687d79a58bb10f85949269f7f72c611a5e0fbb52adfa298ff060ec2eb7216fd7302ea8fb07798cbb3be25cb53ac8161aac2b5bbcfbcfb01c113d28bd1cb0333fb89ac82a95930f7abded0a2f5a623cc6a1f62bf3f38ef1b81c1e50a634f657dbb6770e4af45879e2fb1e00c742e7b52205c8015b5c0f5b1e40186ff9aa7288ab3e01a51fb87761f9bc6837082af109b39cc9f620")
|
||||
@@ -20,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) {
|
||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
||||
require.NoError(t, err)
|
||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
@@ -15,7 +16,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
var tpktVersion uint8
|
||||
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if tpktVersion != 0x03 {
|
||||
return os.ErrInvalid
|
||||
@@ -24,7 +25,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
var tpktReserved uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if tpktReserved != 0x00 {
|
||||
return os.ErrInvalid
|
||||
@@ -33,7 +34,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
var tpktLength uint16
|
||||
err = binary.Read(reader, binary.BigEndian, &tpktLength)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
|
||||
if tpktLength != 19 {
|
||||
@@ -43,7 +44,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
var cotpLength uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &cotpLength)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
|
||||
if cotpLength != 14 {
|
||||
@@ -53,7 +54,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
var cotpTpduType uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if cotpTpduType != 0xE0 {
|
||||
return os.ErrInvalid
|
||||
@@ -61,13 +62,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
|
||||
err = rw.SkipN(reader, 5)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
|
||||
var rdpType uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &rdpType)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if rdpType != 0x01 {
|
||||
return os.ErrInvalid
|
||||
@@ -75,12 +76,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
var rdpFlags uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
var rdpLength uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &rdpLength)
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if rdpLength != 8 {
|
||||
return os.ErrInvalid
|
||||
|
||||
@@ -3,12 +3,14 @@ package sniff
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
@@ -18,6 +20,8 @@ type (
|
||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
||||
)
|
||||
|
||||
var ErrNeedMoreData = E.New("need more data")
|
||||
|
||||
func Skip(metadata *adapter.InboundContext) bool {
|
||||
// skip server first protocols
|
||||
switch metadata.Destination.Port {
|
||||
@@ -34,12 +38,12 @@ func Skip(metadata *adapter.InboundContext) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
|
||||
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
|
||||
if timeout == 0 {
|
||||
timeout = C.ReadPayloadTimeout
|
||||
}
|
||||
deadline := time.Now().Add(timeout)
|
||||
var errors []error
|
||||
var sniffError error
|
||||
for i := 0; ; i++ {
|
||||
err := conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
@@ -53,26 +57,32 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
||||
}
|
||||
return E.Cause(err, "read payload")
|
||||
}
|
||||
errors = nil
|
||||
sniffError = nil
|
||||
for _, sniffer := range sniffers {
|
||||
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
|
||||
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
|
||||
return bytes.NewReader(it.Bytes())
|
||||
})...)
|
||||
err = sniffer(ctx, metadata, reader)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
sniffError = E.Errors(sniffError, err)
|
||||
}
|
||||
if !errors.Is(err, ErrNeedMoreData) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return E.Errors(errors...)
|
||||
return sniffError
|
||||
}
|
||||
|
||||
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
|
||||
var errors []error
|
||||
var sniffError []error
|
||||
for _, sniffer := range sniffers {
|
||||
err := sniffer(ctx, metadata, packet)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
sniffError = append(sniffError, err)
|
||||
}
|
||||
return E.Errors(errors...)
|
||||
return E.Errors(sniffError...)
|
||||
}
|
||||
|
||||
@@ -5,22 +5,26 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
if !scanner.Scan() {
|
||||
const sshPrefix = "SSH-2.0-"
|
||||
bReader := bufio.NewReader(reader)
|
||||
prefix, err := bReader.Peek(len(sshPrefix))
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
} else if string(prefix) != sshPrefix {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
fistLine := scanner.Text()
|
||||
if !strings.HasPrefix(fistLine, "SSH-2.0-") {
|
||||
return os.ErrInvalid
|
||||
fistLine, _, err := bReader.ReadLine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadata.Protocol = C.ProtocolSSH
|
||||
metadata.Client = fistLine[8:]
|
||||
metadata.Client = string(fistLine)[8:]
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package sniff
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||
@@ -23,5 +25,9 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
|
||||
metadata.Domain = clientHello.ServerName
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||
privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Cer
|
||||
return &certificate, err
|
||||
}
|
||||
|
||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
||||
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
||||
if timeFunc == nil {
|
||||
timeFunc = time.Now
|
||||
}
|
||||
@@ -47,7 +47,11 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.T
|
||||
},
|
||||
DNSNames: []string{serverName},
|
||||
}
|
||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||
if parent == nil {
|
||||
parent = template
|
||||
parentKey = key
|
||||
}
|
||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
}
|
||||
if certificate == nil && key == nil && options.Insecure {
|
||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||
}
|
||||
} else {
|
||||
if certificate == nil {
|
||||
|
||||
@@ -2,6 +2,38 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
### 1.11.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.4
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
|
||||
|
||||
### 1.11.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.0
|
||||
|
||||
Important changes since 1.10:
|
||||
|
||||
@@ -7,6 +7,10 @@ icon: material/apple
|
||||
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||
|
||||
!!! failure ""
|
||||
|
||||
We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected).
|
||||
|
||||
## :material-graph: Requirements
|
||||
|
||||
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||
|
||||
@@ -8,6 +8,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
{
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"ntp": {},
|
||||
"endpoints": [],
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
@@ -22,6 +23,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
|----------------|------------------------|
|
||||
| `log` | [日志](./log/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
| `ntp` | [NTP](./ntp/) |
|
||||
| `endpoints` | [端点](./endpoint/) |
|
||||
| `inbounds` | [入站](./inbound/) |
|
||||
| `outbounds` | [出站](./outbound/) |
|
||||
|
||||
@@ -8,7 +8,7 @@ icon: material/delete-clock
|
||||
|
||||
### Structure
|
||||
|
||||
```json F
|
||||
```json
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
|
||||
@@ -29,7 +29,7 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
|
||||
|
||||
| QUIC Client | Type |
|
||||
|:------------------------:|:----------:|
|
||||
| Chromium/Cronet | `chrimium` |
|
||||
| Chromium/Cronet | `chromium` |
|
||||
| Safari/Apple Network API | `safari` |
|
||||
| Firefox / uquic firefox | `firefox` |
|
||||
| quic-go / uquic chrome | `quic-go` |
|
||||
| quic-go / uquic chrome | `quic-go` |
|
||||
|
||||
@@ -74,7 +74,7 @@ Tag of rule-set.
|
||||
|
||||
==Required==
|
||||
|
||||
List of [Headless Rule](../headless-rule/).
|
||||
List of [Headless Rule](./headless-rule/).
|
||||
|
||||
### Local or Remote Fields
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ icon: material/new-box
|
||||
|
||||
==必填==
|
||||
|
||||
一组 [无头规则](../headless-rule/).
|
||||
一组 [无头规则](./headless-rule/).
|
||||
|
||||
### 本地或远程字段
|
||||
|
||||
|
||||
@@ -31,12 +31,11 @@ The protocol version, `1` or `2`.
|
||||
|
||||
### Application support
|
||||
|
||||
| Project | UoT v1 | UoT v2 |
|
||||
|--------------|----------------------|-------------------------------------------------------------------------------------------------------------------|
|
||||
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
|
||||
| Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) |
|
||||
| Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) |
|
||||
| Shadowrocket | v2.2.12 (2022/08/13) | / |
|
||||
| Project | UoT v1 | UoT v2 |
|
||||
|--------------|----------------------|----------------------|
|
||||
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
|
||||
| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) |
|
||||
| Shadowrocket | v2.2.12 (2022/08/13) | / |
|
||||
|
||||
### Protocol details
|
||||
|
||||
@@ -50,7 +49,13 @@ The client requests the magic address to the upper layer proxy protocol to indic
|
||||
|------|----------|-------|--------|----------|
|
||||
| u8 | variable | u16be | u16be | variable |
|
||||
|
||||
**ATYP / address / port**: Uses the SOCKS address format.
|
||||
**ATYP / address / port**: Uses the SOCKS address format, but with different address types:
|
||||
|
||||
| ATYP | Address type |
|
||||
|--------|--------------|
|
||||
| `0x00` | IPv4 Address |
|
||||
| `0x01` | IPv6 Address |
|
||||
| `0x02` | Domain Name |
|
||||
|
||||
#### Protocol version 2
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ Inbound fields are deprecated and can be replaced by rule actions.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[Listen Fields](/configuration/inbound/listen/) /
|
||||
[Listen Fields](/configuration/shared/listen/) /
|
||||
[Rule](/configuration/route/rule/) /
|
||||
[Rule Action](/configuration/route/rule_action/) /
|
||||
[DNS Rule](/configuration/dns/rule/) /
|
||||
|
||||
@@ -284,8 +284,8 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet {
|
||||
var savedSet adapter.SavedRuleSet
|
||||
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {
|
||||
var savedSet adapter.SavedBinary
|
||||
err := c.DB.View(func(t *bbolt.Tx) error {
|
||||
bucket := c.bucket(t, bucketRuleSet)
|
||||
if bucket == nil {
|
||||
@@ -303,7 +303,7 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet {
|
||||
return &savedSet
|
||||
}
|
||||
|
||||
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error {
|
||||
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {
|
||||
return c.DB.Batch(func(t *bbolt.Tx) error {
|
||||
bucket, err := c.createBucket(t, bucketRuleSet)
|
||||
if err != nil {
|
||||
|
||||
@@ -129,7 +129,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
||||
s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
||||
chiRouter.Group(func(r chi.Router) {
|
||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP)
|
||||
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.externalUI))))
|
||||
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(Dir(s.externalUI))))
|
||||
})
|
||||
}
|
||||
return s, nil
|
||||
|
||||
18
experimental/clashapi/server_fs.go
Normal file
18
experimental/clashapi/server_fs.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package clashapi
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Dir http.Dir
|
||||
|
||||
func (d Dir) Open(name string) (http.File, error) {
|
||||
file, err := http.Dir(d).Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileWrapper{file}, nil
|
||||
}
|
||||
|
||||
// workaround for #2345 #2596
|
||||
type fileWrapper struct {
|
||||
http.File
|
||||
}
|
||||
@@ -41,7 +41,6 @@ func (s *Server) downloadExternalUI() error {
|
||||
} else {
|
||||
downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
|
||||
}
|
||||
s.logger.Info("downloading external ui")
|
||||
var detour adapter.Outbound
|
||||
if s.externalUIDownloadDetour != "" {
|
||||
outbound, loaded := s.outbound.Outbound(s.externalUIDownloadDetour)
|
||||
@@ -53,6 +52,7 @@ func (s *Server) downloadExternalUI() error {
|
||||
outbound := s.outbound.Default()
|
||||
detour = outbound
|
||||
}
|
||||
s.logger.Info("downloading external ui using outbound/", detour.Type(), "[", detour.Tag(), "]")
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
@@ -71,15 +71,15 @@ func (s *Server) downloadExternalUI() error {
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return E.New("download external ui failed: ", response.Status)
|
||||
}
|
||||
err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
|
||||
err = s.downloadZIP(response.Body, s.externalUI)
|
||||
if err != nil {
|
||||
removeAllInDirectory(s.externalUI)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
|
||||
tempFile, err := filemanager.CreateTemp(s.ctx, name)
|
||||
func (s *Server) downloadZIP(body io.Reader, output string) error {
|
||||
tempFile, err := filemanager.CreateTemp(s.ctx, "external-ui.zip")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
@@ -18,19 +17,7 @@ func (c *CommandClient) ServiceReload() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var hasError bool
|
||||
err = binary.Read(conn, binary.BigEndian, &hasError)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasError {
|
||||
errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return E.New(errorMessage)
|
||||
}
|
||||
return nil
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleServiceReload(conn net.Conn) error {
|
||||
@@ -55,19 +42,7 @@ func (c *CommandClient) ServiceClose() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var hasError bool
|
||||
err = binary.Read(conn, binary.BigEndian, &hasError)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if hasError {
|
||||
errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return E.New(errorMessage)
|
||||
}
|
||||
return nil
|
||||
return readError(conn)
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleServiceClose(conn net.Conn) error {
|
||||
|
||||
@@ -56,7 +56,12 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
|
||||
if sFixAndroidStack {
|
||||
go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
||||
close(done)
|
||||
}()
|
||||
<-done
|
||||
} else {
|
||||
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ type BoxService struct {
|
||||
clashServer adapter.ClashServer
|
||||
pauseManager pause.Manager
|
||||
|
||||
servicePauseFields
|
||||
iOSPauseFields
|
||||
}
|
||||
|
||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||
|
||||
@@ -1,33 +1,39 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
type servicePauseFields struct {
|
||||
pauseAccess sync.Mutex
|
||||
pauseTimer *time.Timer
|
||||
type iOSPauseFields struct {
|
||||
endPauseTimer *time.Timer
|
||||
}
|
||||
|
||||
func (s *BoxService) Pause() {
|
||||
s.pauseAccess.Lock()
|
||||
defer s.pauseAccess.Unlock()
|
||||
if s.pauseTimer != nil {
|
||||
s.pauseTimer.Stop()
|
||||
s.pauseManager.DevicePause()
|
||||
if !C.IsIos {
|
||||
s.instance.Router().ResetNetwork()
|
||||
} else {
|
||||
if s.endPauseTimer == nil {
|
||||
s.endPauseTimer = time.AfterFunc(time.Minute, s.pauseManager.DeviceWake)
|
||||
} else {
|
||||
s.endPauseTimer.Reset(time.Minute)
|
||||
}
|
||||
}
|
||||
s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork)
|
||||
}
|
||||
|
||||
func (s *BoxService) Wake() {
|
||||
s.pauseAccess.Lock()
|
||||
defer s.pauseAccess.Unlock()
|
||||
if s.pauseTimer != nil {
|
||||
s.pauseTimer.Stop()
|
||||
if !C.IsIos {
|
||||
s.pauseManager.DeviceWake()
|
||||
s.instance.Router().ResetNetwork()
|
||||
}
|
||||
s.pauseTimer = time.AfterFunc(3*time.Minute, s.ResetNetwork)
|
||||
}
|
||||
|
||||
func (s *BoxService) ResetNetwork() {
|
||||
s.instance.Router().ResetNetwork()
|
||||
}
|
||||
|
||||
func (s *BoxService) UpdateWIFIState() {
|
||||
s.instance.Network().UpdateWIFIState()
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ var (
|
||||
|
||||
type Locale struct {
|
||||
// deprecated messages for graphical clients
|
||||
Locale string
|
||||
DeprecatedMessage string
|
||||
DeprecatedMessageNoLink string
|
||||
}
|
||||
|
||||
var defaultLocal = &Locale{
|
||||
Locale: "en_US",
|
||||
DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.",
|
||||
DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.",
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么
|
||||
|
||||
func init() {
|
||||
localeRegistry["zh_CN"] = &Locale{
|
||||
Locale: "zh_CN",
|
||||
DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers,
|
||||
DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers,
|
||||
}
|
||||
|
||||
34
go.mod
34
go.mod
@@ -6,16 +6,16 @@ require (
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/cloudflare/circl v1.3.7
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gofrs/uuid/v5 v5.3.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
|
||||
github.com/gofrs/uuid/v5 v5.3.2
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||
github.com/libdns/alidns v1.0.3
|
||||
github.com/libdns/cloudflare v0.1.1
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||
github.com/mholt/acmez v1.2.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/miekg/dns v1.1.63
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||
@@ -24,30 +24,30 @@ require (
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/gomobile v0.1.4
|
||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
|
||||
github.com/sagernet/quic-go v0.48.2-beta.1
|
||||
github.com/sagernet/quic-go v0.49.0-beta.1
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.6.0-beta.12
|
||||
github.com/sagernet/sing-dns v0.4.0-beta.2
|
||||
github.com/sagernet/sing-mux v0.3.0-alpha.1
|
||||
github.com/sagernet/sing-quic v0.4.0-beta.4
|
||||
github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7
|
||||
github.com/sagernet/sing-dns v0.4.1
|
||||
github.com/sagernet/sing-mux v0.3.1
|
||||
github.com/sagernet/sing-quic v0.4.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.8
|
||||
github.com/sagernet/sing-vmess v0.2.0-beta.2
|
||||
github.com/sagernet/sing-shadowtls v0.2.0
|
||||
github.com/sagernet/sing-tun v0.6.1
|
||||
github.com/sagernet/sing-vmess v0.2.0
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||
github.com/sagernet/utls v1.6.7
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.5
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.uber.org/zap v1.27.0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/mod v0.20.0
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/sys v0.30.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.33.0
|
||||
|
||||
70
go.sum
70
go.sum
@@ -18,8 +18,8 @@ github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbY
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
@@ -31,8 +31,8 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
||||
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
||||
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
@@ -47,8 +47,8 @@ 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
@@ -74,12 +74,12 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
|
||||
@@ -114,29 +114,29 @@ 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/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/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg=
|
||||
github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
|
||||
github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+yfK8DQ8Jk=
|
||||
github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8WHNsRs71b3Lt1+p/U=
|
||||
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/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s=
|
||||
github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY=
|
||||
github.com/sagernet/sing-dns v0.4.0-beta.2/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/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
|
||||
github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g=
|
||||
github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
|
||||
github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7 h1:ZJauxLmH12Gzv3nucfjsSBQw9UA8t7Sxu8pYHBSP2TU=
|
||||
github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk=
|
||||
github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
|
||||
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=
|
||||
github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78=
|
||||
github.com/sagernet/sing-quic v0.4.1 h1:pxlMa4efZu/M07RgGagNNDDyl6ZUwpmNUjRTpgHOWK4=
|
||||
github.com/sagernet/sing-quic v0.4.1/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||
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/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw=
|
||||
github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs=
|
||||
github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk=
|
||||
github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
|
||||
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
|
||||
github.com/sagernet/sing-tun v0.6.1 h1:4l0+gnEKcGjlWfUVTD+W0BRApqIny/lU2ZliurE+VMo=
|
||||
github.com/sagernet/sing-tun v0.6.1/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI=
|
||||
github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
||||
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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
||||
@@ -152,8 +152,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
@@ -172,16 +172,16 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -191,10 +191,10 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
|
||||
@@ -20,12 +20,16 @@ type ID struct {
|
||||
}
|
||||
|
||||
func ContextWithNewID(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, (*idKey)(nil), ID{
|
||||
return ContextWithID(ctx, ID{
|
||||
ID: rand.Uint32(),
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
func ContextWithID(ctx context.Context, id ID) context.Context {
|
||||
return context.WithValue(ctx, (*idKey)(nil), id)
|
||||
}
|
||||
|
||||
func IDFromContext(ctx context.Context) (ID, bool) {
|
||||
id, loaded := ctx.Value((*idKey)(nil)).(ID)
|
||||
return id, loaded
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) erro
|
||||
return err
|
||||
}
|
||||
o.RawMessage = content
|
||||
return nil
|
||||
return checkOptions(o)
|
||||
}
|
||||
|
||||
type LogOptions struct {
|
||||
@@ -42,3 +43,52 @@ type LogOptions struct {
|
||||
}
|
||||
|
||||
type StubOptions struct{}
|
||||
|
||||
func checkOptions(options *Options) error {
|
||||
err := checkInbounds(options.Inbounds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkOutbounds(options.Outbounds, options.Endpoints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkInbounds(inbounds []Inbound) error {
|
||||
seen := make(map[string]bool)
|
||||
for _, inbound := range inbounds {
|
||||
if inbound.Tag == "" {
|
||||
continue
|
||||
}
|
||||
if seen[inbound.Tag] {
|
||||
return E.New("duplicate inbound tag: ", inbound.Tag)
|
||||
}
|
||||
seen[inbound.Tag] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkOutbounds(outbounds []Outbound, endpoints []Endpoint) error {
|
||||
seen := make(map[string]bool)
|
||||
for _, outbound := range outbounds {
|
||||
if outbound.Tag == "" {
|
||||
continue
|
||||
}
|
||||
if seen[outbound.Tag] {
|
||||
return E.New("duplicate outbound/endpoint tag: ", outbound.Tag)
|
||||
}
|
||||
seen[outbound.Tag] = true
|
||||
}
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.Tag == "" {
|
||||
continue
|
||||
}
|
||||
if seen[endpoint.Tag] {
|
||||
return E.New("duplicate outbound/endpoint tag: ", endpoint.Tag)
|
||||
}
|
||||
seen[endpoint.Tag] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/udpnat2"
|
||||
@@ -80,7 +81,7 @@ func (i *Inbound) Close() error {
|
||||
}
|
||||
|
||||
func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
|
||||
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.Socksaddr{}, nil)
|
||||
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil)
|
||||
}
|
||||
|
||||
func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
@@ -104,7 +105,6 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
|
||||
|
||||
func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
i.logger.InfoContext(ctx, "inbound packet connection from ", source)
|
||||
i.logger.InfoContext(ctx, "inbound packet connection to ", destination)
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Inbound = i.Tag()
|
||||
metadata.InboundType = i.Type()
|
||||
@@ -123,8 +123,11 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
destination.Port = i.overrideDestination.Port
|
||||
default:
|
||||
}
|
||||
i.logger.InfoContext(ctx, "inbound packet connection to ", destination)
|
||||
metadata.Destination = destination
|
||||
metadata.OriginDestination = i.listener.UDPAddr()
|
||||
if i.overrideOption != 0 {
|
||||
conn = bufio.NewDestinationNATPacketConn(bufio.NewNetPacketConn(conn), i.listener.UDPAddr(), destination)
|
||||
}
|
||||
i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/pause"
|
||||
)
|
||||
@@ -27,10 +28,7 @@ func RegisterURLTest(registry *outbound.Registry) {
|
||||
outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest)
|
||||
}
|
||||
|
||||
var (
|
||||
_ adapter.OutboundGroup = (*URLTest)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*URLTest)(nil)
|
||||
)
|
||||
var _ adapter.OutboundGroup = (*URLTest)(nil)
|
||||
|
||||
type URLTest struct {
|
||||
outbound.Adapter
|
||||
@@ -172,15 +170,12 @@ func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (s *URLTest) InterfaceUpdated() {
|
||||
go s.group.CheckOutbounds(true)
|
||||
return
|
||||
}
|
||||
|
||||
type URLTestGroup struct {
|
||||
ctx context.Context
|
||||
router adapter.Router
|
||||
outboundManager adapter.OutboundManager
|
||||
outbound adapter.OutboundManager
|
||||
pause pause.Manager
|
||||
pauseCallback *list.Element[pause.Callback]
|
||||
logger log.Logger
|
||||
outbounds []adapter.Outbound
|
||||
link string
|
||||
@@ -189,17 +184,15 @@ type URLTestGroup struct {
|
||||
idleTimeout time.Duration
|
||||
history *urltest.HistoryStorage
|
||||
checking atomic.Bool
|
||||
pauseManager pause.Manager
|
||||
selectedOutboundTCP adapter.Outbound
|
||||
selectedOutboundUDP adapter.Outbound
|
||||
interruptGroup *interrupt.Group
|
||||
interruptExternalConnections bool
|
||||
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
close chan struct{}
|
||||
started bool
|
||||
lastActive atomic.TypedValue[time.Time]
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
close chan struct{}
|
||||
started bool
|
||||
lastActive atomic.TypedValue[time.Time]
|
||||
}
|
||||
|
||||
func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) {
|
||||
@@ -224,7 +217,7 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage
|
||||
}
|
||||
return &URLTestGroup{
|
||||
ctx: ctx,
|
||||
outboundManager: outboundManager,
|
||||
outbound: outboundManager,
|
||||
logger: logger,
|
||||
outbounds: outbounds,
|
||||
link: link,
|
||||
@@ -233,13 +226,15 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage
|
||||
idleTimeout: idleTimeout,
|
||||
history: history,
|
||||
close: make(chan struct{}),
|
||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||
pause: service.FromContext[pause.Manager](ctx),
|
||||
interruptGroup: interrupt.NewGroup(),
|
||||
interruptExternalConnections: interruptExternalConnections,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) PostStart() {
|
||||
g.access.Lock()
|
||||
defer g.access.Unlock()
|
||||
g.started = true
|
||||
g.lastActive.Store(time.Now())
|
||||
go g.CheckOutbounds(false)
|
||||
@@ -249,24 +244,25 @@ func (g *URLTestGroup) Touch() {
|
||||
if !g.started {
|
||||
return
|
||||
}
|
||||
g.access.Lock()
|
||||
defer g.access.Unlock()
|
||||
if g.ticker != nil {
|
||||
g.lastActive.Store(time.Now())
|
||||
return
|
||||
}
|
||||
g.access.Lock()
|
||||
defer g.access.Unlock()
|
||||
if g.ticker != nil {
|
||||
return
|
||||
}
|
||||
g.ticker = time.NewTicker(g.interval)
|
||||
go g.loopCheck()
|
||||
g.pauseCallback = pause.RegisterTicker(g.pause, g.ticker, g.interval, nil)
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Close() error {
|
||||
g.access.Lock()
|
||||
defer g.access.Unlock()
|
||||
if g.ticker == nil {
|
||||
return nil
|
||||
}
|
||||
g.ticker.Stop()
|
||||
g.pause.UnregisterCallback(g.pauseCallback)
|
||||
close(g.close)
|
||||
return nil
|
||||
}
|
||||
@@ -330,10 +326,11 @@ func (g *URLTestGroup) loopCheck() {
|
||||
g.access.Lock()
|
||||
g.ticker.Stop()
|
||||
g.ticker = nil
|
||||
g.pause.UnregisterCallback(g.pauseCallback)
|
||||
g.pauseCallback = nil
|
||||
g.access.Unlock()
|
||||
return
|
||||
}
|
||||
g.pauseManager.WaitActive()
|
||||
g.CheckOutbounds(false)
|
||||
}
|
||||
}
|
||||
@@ -366,7 +363,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
|
||||
continue
|
||||
}
|
||||
checked[realTag] = true
|
||||
p, loaded := g.outboundManager.Outbound(realTag)
|
||||
p, loaded := g.outbound.Outbound(realTag)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
logger: logger,
|
||||
}
|
||||
var err error
|
||||
inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex))
|
||||
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -312,9 +312,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
if len(ipSets) == 0 {
|
||||
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
|
||||
}
|
||||
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||
routeRuleSet.DecRef()
|
||||
t.routeAddressSet = append(t.routeAddressSet, ipSets...)
|
||||
if t.autoRedirect != nil {
|
||||
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||
}
|
||||
}
|
||||
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
|
||||
@@ -322,9 +324,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
if len(ipSets) == 0 {
|
||||
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
|
||||
}
|
||||
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||
routeExcludeRuleSet.DecRef()
|
||||
t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
|
||||
if t.autoRedirect != nil {
|
||||
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||
}
|
||||
}
|
||||
}
|
||||
var (
|
||||
|
||||
@@ -120,7 +120,6 @@ func (w *Endpoint) Close() error {
|
||||
|
||||
func (w *Endpoint) InterfaceUpdated() {
|
||||
w.endpoint.BindUpdate()
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error {
|
||||
|
||||
@@ -126,7 +126,6 @@ func (o *Outbound) Close() error {
|
||||
|
||||
func (o *Outbound) InterfaceUpdated() {
|
||||
o.endpoint.BindUpdate()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
|
||||
@@ -4,8 +4,8 @@ Documentation=https://sing-box.sagernet.org
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
ExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
|
||||
@@ -4,8 +4,8 @@ Documentation=https://sing-box.sagernet.org
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
ExecStart=/usr/bin/sing-box -D /var/lib/sing-box-%i -c /etc/sing-box/%i.json run
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
|
||||
@@ -4,8 +4,8 @@ Documentation=https://sing-box.sagernet.org
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
ExecStart=/usr/local/bin/sing-box -D /var/lib/sing-box -C /usr/local/etc/sing-box run
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
|
||||
@@ -2,9 +2,11 @@ package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"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"
|
||||
@@ -149,16 +152,17 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
|
||||
} else {
|
||||
originDestination = metadata.Destination
|
||||
}
|
||||
if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
} else if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
|
||||
if metadata.UDPDisableDomainUnmapping {
|
||||
remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
|
||||
} else {
|
||||
remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
|
||||
}
|
||||
}
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
}
|
||||
} else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination {
|
||||
remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination)
|
||||
}
|
||||
var udpTimeout time.Duration
|
||||
if metadata.UDPTimeout > 0 {
|
||||
@@ -189,14 +193,16 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
|
||||
go m.packetConnectionCopy(ctx, destination, 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
|
||||
originDestination := destination
|
||||
func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
|
||||
var (
|
||||
sourceReader io.Reader = source
|
||||
destinationWriter io.Writer = destination
|
||||
)
|
||||
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 {
|
||||
sourceReader, readCounters = N.UnwrapCountReader(sourceReader, readCounters)
|
||||
destinationWriter, writeCounters = N.UnwrapCountWriter(destinationWriter, writeCounters)
|
||||
if cachedSrc, isCached := sourceReader.(N.CachedReader); isCached {
|
||||
cachedBuffer := cachedSrc.ReadCached()
|
||||
if cachedBuffer != nil {
|
||||
dataLen := cachedBuffer.Len()
|
||||
@@ -206,7 +212,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
|
||||
if done.Swap(true) {
|
||||
onClose(err)
|
||||
}
|
||||
common.Close(originSource, originDestination)
|
||||
common.Close(source, destination)
|
||||
if !direction {
|
||||
m.logger.ErrorContext(ctx, "connection upload payload: ", err)
|
||||
} else {
|
||||
@@ -225,20 +231,35 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
|
||||
}
|
||||
break
|
||||
}
|
||||
_, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
|
||||
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destinationWriter); isEarlyConn && earlyConn.NeedHandshake() {
|
||||
err := m.connectionCopyEarly(source, destination)
|
||||
if err != nil {
|
||||
if done.Swap(true) {
|
||||
onClose(err)
|
||||
}
|
||||
common.Close(source, destination)
|
||||
if !direction {
|
||||
m.logger.ErrorContext(ctx, "connection upload handshake: ", err)
|
||||
} else {
|
||||
m.logger.ErrorContext(ctx, "connection download handshake: ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err := bufio.CopyWithCounters(destinationWriter, sourceReader, source, readCounters, writeCounters)
|
||||
if err != nil {
|
||||
common.Close(originDestination)
|
||||
common.Close(source, destination)
|
||||
} else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex {
|
||||
err = duplexDst.CloseWrite()
|
||||
if err != nil {
|
||||
common.Close(originSource, originDestination)
|
||||
common.Close(source, destination)
|
||||
}
|
||||
} else {
|
||||
common.Close(originDestination)
|
||||
destination.Close()
|
||||
}
|
||||
if done.Swap(true) {
|
||||
onClose(err)
|
||||
common.Close(originSource, originDestination)
|
||||
common.Close(source, destination)
|
||||
}
|
||||
if !direction {
|
||||
if err == nil {
|
||||
@@ -259,16 +280,42 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ConnectionManager) connectionCopyEarly(source net.Conn, destination io.Writer) error {
|
||||
payload := buf.NewPacket()
|
||||
defer payload.Release()
|
||||
err := source.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
|
||||
if err != nil {
|
||||
if err == os.ErrInvalid {
|
||||
return common.Error(destination.Write(nil))
|
||||
}
|
||||
return err
|
||||
}
|
||||
_, err = payload.ReadOnceFrom(source)
|
||||
if err != nil && !(E.IsTimeout(err) || errors.Is(err, io.EOF)) {
|
||||
return E.Cause(err, "read payload")
|
||||
}
|
||||
_ = source.SetReadDeadline(time.Time{})
|
||||
_, err = destination.Write(payload.Bytes())
|
||||
if err != nil {
|
||||
return E.Cause(err, "write payload")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
|
||||
_, err := bufio.CopyPacket(destination, source)
|
||||
if !direction {
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
if err == nil {
|
||||
m.logger.DebugContext(ctx, "packet upload finished")
|
||||
} else if E.IsClosedOrCanceled(err) {
|
||||
m.logger.TraceContext(ctx, "packet upload closed")
|
||||
} else {
|
||||
m.logger.DebugContext(ctx, "packet upload closed: ", err)
|
||||
}
|
||||
} else {
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
if err == nil {
|
||||
m.logger.DebugContext(ctx, "packet download finished")
|
||||
} else if E.IsClosedOrCanceled(err) {
|
||||
m.logger.TraceContext(ctx, "packet download closed")
|
||||
} else {
|
||||
m.logger.DebugContext(ctx, "packet download closed: ", err)
|
||||
|
||||
@@ -354,6 +354,18 @@ func (r *NetworkManager) WIFIState() adapter.WIFIState {
|
||||
return r.wifiState
|
||||
}
|
||||
|
||||
func (r *NetworkManager) UpdateWIFIState() {
|
||||
if r.platformInterface != nil {
|
||||
state := r.platformInterface.ReadWIFIState()
|
||||
if state != r.wifiState {
|
||||
r.wifiState = state
|
||||
if state.SSID != "" {
|
||||
r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *NetworkManager) ResetNetwork() {
|
||||
conntrack.Close()
|
||||
|
||||
@@ -414,15 +426,7 @@ func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interfa
|
||||
}
|
||||
}
|
||||
r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", "))
|
||||
if r.platformInterface != nil {
|
||||
state := r.platformInterface.ReadWIFIState()
|
||||
if state != r.wifiState {
|
||||
r.wifiState = state
|
||||
if state.SSID != "" {
|
||||
r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID)
|
||||
}
|
||||
}
|
||||
}
|
||||
r.UpdateWIFIState()
|
||||
|
||||
if !r.started {
|
||||
return
|
||||
|
||||
@@ -60,10 +60,6 @@ func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata
|
||||
}
|
||||
|
||||
func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
|
||||
if r.pauseManager.IsDevicePaused() {
|
||||
return E.New("reject connection to ", metadata.Destination, " while device paused")
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if metadata.InboundDetour != "" {
|
||||
if metadata.LastInbound == metadata.InboundDetour {
|
||||
@@ -140,8 +136,8 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
for _, buffer := range buffers {
|
||||
conn = bufio.NewCachedConn(conn, buffer)
|
||||
}
|
||||
if r.tracker != nil {
|
||||
conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
|
||||
for _, tracker := range r.trackers {
|
||||
conn = tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
|
||||
}
|
||||
if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler {
|
||||
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
|
||||
@@ -186,9 +182,6 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
}
|
||||
|
||||
func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
|
||||
if r.pauseManager.IsDevicePaused() {
|
||||
return E.New("reject packet connection to ", metadata.Destination, " while device paused")
|
||||
}
|
||||
//nolint:staticcheck
|
||||
if metadata.InboundDetour != "" {
|
||||
if metadata.LastInbound == metadata.InboundDetour {
|
||||
@@ -258,8 +251,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination)
|
||||
N.PutPacketBuffer(buffer)
|
||||
}
|
||||
if r.tracker != nil {
|
||||
conn = r.tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
|
||||
for _, tracker := range r.trackers {
|
||||
conn = tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
|
||||
}
|
||||
if metadata.FakeIP {
|
||||
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
|
||||
@@ -358,7 +351,7 @@ func (r *Router) matchRule(
|
||||
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{
|
||||
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
|
||||
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
||||
}, inputConn, inputPacketConn)
|
||||
}, inputConn, inputPacketConn, nil)
|
||||
if newErr != nil {
|
||||
fatalErr = newErr
|
||||
return
|
||||
@@ -458,7 +451,7 @@ match:
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *rule.RuleActionSniff:
|
||||
if !preMatch {
|
||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn)
|
||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
||||
if newErr != nil {
|
||||
fatalErr = newErr
|
||||
return
|
||||
@@ -489,32 +482,21 @@ match:
|
||||
break match
|
||||
}
|
||||
}
|
||||
if !preMatch && inputPacketConn != nil && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() {
|
||||
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 {
|
||||
fatalErr = newErr
|
||||
return
|
||||
}
|
||||
if newBuffer != nil {
|
||||
buffers = append(buffers, newBuffer)
|
||||
} else if len(newPacketBuffers) > 0 {
|
||||
packetBuffers = append(packetBuffers, newPacketBuffers...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Router) actionSniff(
|
||||
ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff,
|
||||
inputConn net.Conn, inputPacketConn N.PacketConn,
|
||||
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
|
||||
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
||||
if sniff.Skip(metadata) {
|
||||
r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first")
|
||||
return
|
||||
} else if inputConn != nil {
|
||||
} else if metadata.Protocol != "" {
|
||||
r.logger.DebugContext(ctx, "duplicate sniff skipped")
|
||||
return
|
||||
}
|
||||
if inputConn != nil {
|
||||
sniffBuffer := buf.NewPacket()
|
||||
var streamSniffers []sniff.StreamSniffer
|
||||
if len(action.StreamSniffers) > 0 {
|
||||
@@ -533,6 +515,7 @@ func (r *Router) actionSniff(
|
||||
ctx,
|
||||
metadata,
|
||||
inputConn,
|
||||
inputBuffers,
|
||||
sniffBuffer,
|
||||
action.Timeout,
|
||||
streamSniffers...,
|
||||
@@ -559,6 +542,10 @@ func (r *Router) actionSniff(
|
||||
sniffBuffer.Release()
|
||||
}
|
||||
} else if inputPacketConn != nil {
|
||||
if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
|
||||
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
|
||||
return
|
||||
}
|
||||
for {
|
||||
var (
|
||||
sniffBuffer = buf.NewPacket()
|
||||
@@ -590,10 +577,7 @@ func (r *Router) actionSniff(
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !metadata.Destination.Addr.IsGlobalUnicast() {
|
||||
metadata.Destination = destination
|
||||
}
|
||||
if len(packetBuffers) > 0 {
|
||||
if len(packetBuffers) > 0 || metadata.PacketSniffError != nil {
|
||||
err = sniff.PeekPacket(
|
||||
ctx,
|
||||
metadata,
|
||||
@@ -626,7 +610,9 @@ func (r *Router) actionSniff(
|
||||
Destination: destination,
|
||||
}
|
||||
packetBuffers = append(packetBuffers, packetBuffer)
|
||||
if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(packetBuffers) == 0 {
|
||||
metadata.PacketSniffError = err
|
||||
if errors.Is(err, sniff.ErrNeedMoreData) {
|
||||
// TODO: replace with generic message when there are more multi-packet protocols
|
||||
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ type Router struct {
|
||||
fakeIPStore adapter.FakeIPStore
|
||||
processSearcher process.Searcher
|
||||
pauseManager pause.Manager
|
||||
tracker adapter.ConnectionTracker
|
||||
trackers []adapter.ConnectionTracker
|
||||
platformInterface platform.Interface
|
||||
needWIFIState bool
|
||||
started bool
|
||||
@@ -484,6 +484,13 @@ func (r *Router) Close() error {
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
for i, ruleSet := range r.ruleSets {
|
||||
monitor.Start("close rule-set[", i, "]")
|
||||
err = E.Append(err, ruleSet.Close(), func(err error) error {
|
||||
return E.Cause(err, "close rule-set[", i, "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -504,8 +511,8 @@ func (r *Router) Rules() []adapter.Rule {
|
||||
return r.rules
|
||||
}
|
||||
|
||||
func (r *Router) SetTracker(tracker adapter.ConnectionTracker) {
|
||||
r.tracker = tracker
|
||||
func (r *Router) AppendTracker(tracker adapter.ConnectionTracker) {
|
||||
r.trackers = append(r.trackers, tracker)
|
||||
}
|
||||
|
||||
func (r *Router) ResetNetwork() {
|
||||
|
||||
@@ -162,6 +162,24 @@ func (r *RuleActionRouteOptions) Type() string {
|
||||
|
||||
func (r *RuleActionRouteOptions) String() string {
|
||||
var descriptions []string
|
||||
if r.OverrideAddress.IsValid() {
|
||||
descriptions = append(descriptions, F.ToString("override-address=", r.OverrideAddress.AddrString()))
|
||||
}
|
||||
if r.OverridePort > 0 {
|
||||
descriptions = append(descriptions, F.ToString("override-port=", r.OverridePort))
|
||||
}
|
||||
if r.NetworkStrategy != nil {
|
||||
descriptions = append(descriptions, F.ToString("network-strategy=", r.NetworkStrategy))
|
||||
}
|
||||
if r.NetworkType != nil {
|
||||
descriptions = append(descriptions, F.ToString("network-type=", strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), ",")))
|
||||
}
|
||||
if r.FallbackNetworkType != nil {
|
||||
descriptions = append(descriptions, F.ToString("fallback-network-type="+strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), ",")))
|
||||
}
|
||||
if r.FallbackDelay > 0 {
|
||||
descriptions = append(descriptions, F.ToString("fallback-delay=", r.FallbackDelay.String()))
|
||||
}
|
||||
if r.UDPDisableDomainUnmapping {
|
||||
descriptions = append(descriptions, "udp-disable-domain-unmapping")
|
||||
}
|
||||
@@ -261,6 +279,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
||||
default:
|
||||
panic(F.ToString("unknown reject method: ", r.Method))
|
||||
}
|
||||
if r.NoDrop {
|
||||
return returnErr
|
||||
}
|
||||
r.dropAccess.Lock()
|
||||
defer r.dropAccess.Unlock()
|
||||
timeNow := time.Now()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -26,14 +27,16 @@ import (
|
||||
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
||||
|
||||
type LocalRuleSet struct {
|
||||
ctx context.Context
|
||||
logger logger.Logger
|
||||
tag string
|
||||
rules []adapter.HeadlessRule
|
||||
metadata adapter.RuleSetMetadata
|
||||
fileFormat string
|
||||
watcher *fswatch.Watcher
|
||||
refs atomic.Int32
|
||||
ctx context.Context
|
||||
logger logger.Logger
|
||||
tag string
|
||||
rules []adapter.HeadlessRule
|
||||
metadata adapter.RuleSetMetadata
|
||||
fileFormat string
|
||||
watcher *fswatch.Watcher
|
||||
callbackAccess sync.Mutex
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
}
|
||||
|
||||
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
|
||||
@@ -52,13 +55,12 @@ func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.R
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := ruleSet.reloadFile(filemanager.BasePath(ctx, options.LocalOptions.Path))
|
||||
filePath := filemanager.BasePath(ctx, options.LocalOptions.Path)
|
||||
filePath, _ = filepath.Abs(filePath)
|
||||
err := ruleSet.reloadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.Type == C.RuleSetTypeLocal {
|
||||
filePath, _ := filepath.Abs(options.LocalOptions.Path)
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: []string{filePath},
|
||||
Callback: func(path string) {
|
||||
@@ -141,6 +143,12 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||
s.rules = rules
|
||||
s.metadata = metadata
|
||||
s.callbackAccess.Lock()
|
||||
callbacks := s.callbacks.Array()
|
||||
s.callbackAccess.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -173,10 +181,15 @@ func (s *LocalRuleSet) Cleanup() {
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||
return nil
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
return s.callbacks.PushBack(callback)
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||
s.callbackAccess.Lock()
|
||||
defer s.callbackAccess.Unlock()
|
||||
s.callbacks.Remove(element)
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) Close() error {
|
||||
|
||||
@@ -33,23 +33,23 @@ import (
|
||||
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
|
||||
|
||||
type RemoteRuleSet struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
outboundManager adapter.OutboundManager
|
||||
logger logger.ContextLogger
|
||||
options option.RuleSet
|
||||
metadata adapter.RuleSetMetadata
|
||||
updateInterval time.Duration
|
||||
dialer N.Dialer
|
||||
rules []adapter.HeadlessRule
|
||||
lastUpdated time.Time
|
||||
lastEtag string
|
||||
updateTicker *time.Ticker
|
||||
cacheFile adapter.CacheFile
|
||||
pauseManager pause.Manager
|
||||
callbackAccess sync.Mutex
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
logger logger.ContextLogger
|
||||
outbound adapter.OutboundManager
|
||||
options option.RuleSet
|
||||
metadata adapter.RuleSetMetadata
|
||||
updateInterval time.Duration
|
||||
dialer N.Dialer
|
||||
rules []adapter.HeadlessRule
|
||||
lastUpdated time.Time
|
||||
lastEtag string
|
||||
updateTicker *time.Ticker
|
||||
cacheFile adapter.CacheFile
|
||||
pauseManager pause.Manager
|
||||
callbackAccess sync.Mutex
|
||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||
refs atomic.Int32
|
||||
}
|
||||
|
||||
func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {
|
||||
@@ -61,13 +61,13 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options
|
||||
updateInterval = 24 * time.Hour
|
||||
}
|
||||
return &RemoteRuleSet{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
|
||||
logger: logger,
|
||||
options: options,
|
||||
updateInterval: updateInterval,
|
||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
logger: logger,
|
||||
options: options,
|
||||
updateInterval: updateInterval,
|
||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,13 +83,13 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.
|
||||
s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx)
|
||||
var dialer N.Dialer
|
||||
if s.options.RemoteOptions.DownloadDetour != "" {
|
||||
outbound, loaded := s.outboundManager.Outbound(s.options.RemoteOptions.DownloadDetour)
|
||||
outbound, loaded := s.outbound.Outbound(s.options.RemoteOptions.DownloadDetour)
|
||||
if !loaded {
|
||||
return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour)
|
||||
return E.New("download detour not found: ", s.options.RemoteOptions.DownloadDetour)
|
||||
}
|
||||
dialer = outbound
|
||||
} else {
|
||||
dialer = s.outboundManager.Default()
|
||||
dialer = s.outbound.Default()
|
||||
}
|
||||
s.dialer = dialer
|
||||
if s.cacheFile != nil {
|
||||
@@ -103,7 +103,7 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.
|
||||
}
|
||||
}
|
||||
if s.lastUpdated.IsZero() {
|
||||
err := s.fetchOnce(ctx, startContext)
|
||||
err := s.fetch(ctx, startContext)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initial rule-set: ", s.options.Tag)
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
||||
|
||||
func (s *RemoteRuleSet) loopUpdate() {
|
||||
if time.Since(s.lastUpdated) > s.updateInterval {
|
||||
err := s.fetchOnce(s.ctx, nil)
|
||||
err := s.fetch(s.ctx, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
|
||||
} else if s.refs.Load() == 0 {
|
||||
@@ -211,18 +211,21 @@ func (s *RemoteRuleSet) loopUpdate() {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
case <-s.updateTicker.C:
|
||||
s.pauseManager.WaitActive()
|
||||
err := s.fetchOnce(s.ctx, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
|
||||
} else if s.refs.Load() == 0 {
|
||||
s.rules = nil
|
||||
}
|
||||
s.updateOnce()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTTPStartContext) error {
|
||||
func (s *RemoteRuleSet) updateOnce() {
|
||||
err := s.fetch(s.ctx, nil)
|
||||
if err != nil {
|
||||
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
|
||||
} else if s.refs.Load() == 0 {
|
||||
s.rules = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPStartContext) error {
|
||||
s.logger.Debug("updating rule-set ", s.options.Tag, " from URL: ", s.options.RemoteOptions.URL)
|
||||
var httpClient *http.Client
|
||||
if startContext != nil {
|
||||
@@ -286,7 +289,7 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT
|
||||
}
|
||||
s.lastUpdated = time.Now()
|
||||
if s.cacheFile != nil {
|
||||
err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{
|
||||
err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedBinary{
|
||||
LastUpdated: s.lastUpdated,
|
||||
Content: content,
|
||||
LastEtag: s.lastEtag,
|
||||
@@ -301,8 +304,10 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT
|
||||
|
||||
func (s *RemoteRuleSet) Close() error {
|
||||
s.rules = nil
|
||||
s.updateTicker.Stop()
|
||||
s.cancel()
|
||||
if s.updateTicker != nil {
|
||||
s.updateTicker.Stop()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
var _ net.Conn = (*GunConn)(nil)
|
||||
|
||||
type GunConn struct {
|
||||
rawReader io.Reader
|
||||
reader *std_bufio.Reader
|
||||
writer io.Writer
|
||||
flusher http.Flusher
|
||||
@@ -31,9 +32,10 @@ type GunConn struct {
|
||||
|
||||
func newGunConn(reader io.Reader, writer io.Writer, flusher http.Flusher) *GunConn {
|
||||
return &GunConn{
|
||||
reader: std_bufio.NewReader(reader),
|
||||
writer: writer,
|
||||
flusher: flusher,
|
||||
rawReader: reader,
|
||||
reader: std_bufio.NewReader(reader),
|
||||
writer: writer,
|
||||
flusher: flusher,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +48,7 @@ func newLateGunConn(writer io.Writer) *GunConn {
|
||||
|
||||
func (c *GunConn) setup(reader io.Reader, err error) {
|
||||
if reader != nil {
|
||||
c.rawReader = reader
|
||||
c.reader = std_bufio.NewReader(reader)
|
||||
}
|
||||
c.err = err
|
||||
@@ -138,7 +141,7 @@ func (c *GunConn) FrontHeadroom() int {
|
||||
}
|
||||
|
||||
func (c *GunConn) Close() error {
|
||||
return common.Close(c.reader, c.writer)
|
||||
return common.Close(c.rawReader, c.writer)
|
||||
}
|
||||
|
||||
func (c *GunConn) LocalAddr() net.Addr {
|
||||
|
||||
@@ -2,6 +2,7 @@ package v2rayhttp
|
||||
|
||||
import (
|
||||
std_bufio "bufio"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/baderror"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
@@ -255,3 +257,11 @@ func (w *HTTP2ConnWrapper) Close() error {
|
||||
func (w *HTTP2ConnWrapper) Upstream() any {
|
||||
return w.ExtendedConn
|
||||
}
|
||||
|
||||
func DupContext(ctx context.Context) context.Context {
|
||||
id, loaded := log.IDFromContext(ctx)
|
||||
if !loaded {
|
||||
return context.Background()
|
||||
}
|
||||
return log.ContextWithID(context.Background(), id)
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if requestBody != nil {
|
||||
conn = bufio.NewCachedConn(conn, requestBody)
|
||||
}
|
||||
s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil)
|
||||
s.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
|
||||
} else {
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
done := make(chan struct{})
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
@@ -37,6 +38,7 @@ type Server struct {
|
||||
func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
|
||||
server := &Server{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
tlsConfig: tlsConfig,
|
||||
handler: handler,
|
||||
host: options.Host,
|
||||
@@ -110,7 +112,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed"))
|
||||
return
|
||||
}
|
||||
s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
|
||||
s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
|
||||
}
|
||||
|
||||
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
|
||||
|
||||
@@ -74,6 +74,10 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
if header.OpCode.IsControl() {
|
||||
if header.Length > 128 {
|
||||
err = wsutil.ErrFrameTooLarge
|
||||
return
|
||||
}
|
||||
err = c.controlHandler(header, c.reader)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
@@ -114,7 +115,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if len(earlyData) > 0 {
|
||||
conn = bufio.NewCachedConn(conn, buf.As(earlyData))
|
||||
}
|
||||
s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil)
|
||||
s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
|
||||
}
|
||||
|
||||
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
|
||||
|
||||
@@ -30,7 +30,7 @@ type Endpoint struct {
|
||||
allowedAddress []netip.Prefix
|
||||
tunDevice Device
|
||||
device *device.Device
|
||||
pauseManager pause.Manager
|
||||
pause pause.Manager
|
||||
pauseCallback *list.Element[pause.Callback]
|
||||
}
|
||||
|
||||
@@ -187,9 +187,9 @@ func (e *Endpoint) Start(resolve bool) error {
|
||||
return E.Cause(err, "setup wireguard: \n", ipcConf)
|
||||
}
|
||||
e.device = wgDevice
|
||||
e.pauseManager = service.FromContext[pause.Manager](e.options.Context)
|
||||
if e.pauseManager != nil {
|
||||
e.pauseCallback = e.pauseManager.RegisterCallback(e.onPauseUpdated)
|
||||
e.pause = service.FromContext[pause.Manager](e.options.Context)
|
||||
if e.pause != nil {
|
||||
e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -217,16 +217,16 @@ func (e *Endpoint) Close() error {
|
||||
e.device.Close()
|
||||
}
|
||||
if e.pauseCallback != nil {
|
||||
e.pauseManager.UnregisterCallback(e.pauseCallback)
|
||||
e.pause.UnregisterCallback(e.pauseCallback)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Endpoint) onPauseUpdated(event int) {
|
||||
switch event {
|
||||
case pause.EventDevicePaused:
|
||||
case pause.EventDevicePaused, pause.EventNetworkPause:
|
||||
e.device.Down()
|
||||
case pause.EventDeviceWake:
|
||||
case pause.EventDeviceWake, pause.EventNetworkWake:
|
||||
e.device.Up()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user