Compare commits

..

47 Commits

Author SHA1 Message Date
世界
0d2334d719 documentation: Bump version 2025-01-08 17:04:46 +08:00
世界
5b7841ef8b Fix tests 2025-01-08 17:04:01 +08:00
世界
24a57b5270 Add port hopping for hysteria2 2025-01-08 17:04:01 +08:00
世界
6f73096972 Revert "platform: Implement set underlying networks for android"
This reverts commit eb4a184b7e.
2025-01-08 17:04:01 +08:00
世界
1ab255d636 tun: Set address sets to routes 2025-01-08 17:04:01 +08:00
世界
b70078adba platform: Implement set underlying networks for android 2025-01-08 12:18:04 +08:00
世界
8db3b34f42 release: Upload debug apks to action 2025-01-08 12:18:04 +08:00
世界
62ee8903e1 Fix default dialer on legacy xiaomi systems 2025-01-08 12:18:03 +08:00
世界
eb2f45babe Add rule-set merge command 2025-01-08 12:18:03 +08:00
世界
f72fb39dac Fix DNS match 2025-01-08 12:18:03 +08:00
世界
85542f9ecd Fix domain strategy 2025-01-08 12:18:02 +08:00
世界
3105e88e66 Fix time service 2025-01-08 12:18:01 +08:00
世界
8aedc9f5a9 Fix socks5 UDP implementation 2025-01-08 12:18:01 +08:00
世界
1e505f6726 clash-api: Fix missing endpoints 2025-01-08 12:18:01 +08:00
世界
3df7f5e639 hysteria2: Add more masquerade options 2025-01-08 12:18:00 +08:00
世界
a2c4dbd245 Improve timeouts 2025-01-08 12:18:00 +08:00
世界
7bd148e74d Add UDP timeout route option 2025-01-08 12:17:59 +08:00
世界
9f1aa535f0 Make GSO adaptive 2025-01-08 12:17:59 +08:00
世界
8835ca44b4 Fix lint 2025-01-08 12:17:58 +08:00
世界
fb02440650 refactor: WireGuard endpoint 2025-01-08 12:17:58 +08:00
世界
445c0ff9cb refactor: connection manager 2025-01-08 12:17:57 +08:00
世界
2f515cd2a0 documentation: Fix typo 2025-01-08 12:17:57 +08:00
世界
8e1f29cb7d Add override destination to route options 2025-01-08 12:17:56 +08:00
世界
f26d80cea8 Add dns.cache_capacity 2025-01-08 12:17:56 +08:00
世界
b2b2843a5b Refactor multi networks strategy 2025-01-08 12:17:35 +08:00
世界
fc6f89d1f8 documentation: Remove unused titles 2025-01-08 12:17:34 +08:00
世界
83c2ee91d8 Add multi network dialing 2025-01-08 12:17:34 +08:00
世界
9bd7493168 documentation: Merge route options to route actions 2025-01-08 12:17:34 +08:00
世界
f24f7462de Add network_[type/is_expensive/is_constrained] rule items 2025-01-08 12:17:33 +08:00
世界
f47871262c Merge route options to route actions 2025-01-08 12:17:33 +08:00
世界
f92fb77bed refactor: Platform Interfaces 2025-01-08 12:17:33 +08:00
世界
ca21816051 refactor: Extract services form router 2025-01-08 12:17:32 +08:00
世界
b7b269bc07 refactor: Modular network manager 2025-01-08 12:17:32 +08:00
世界
0a79f58891 refactor: Modular inbound/outbound manager 2025-01-08 12:17:31 +08:00
世界
28a6d46aff documentation: Add rule action 2025-01-08 12:17:31 +08:00
世界
a78575f371 documentation: Update the scheduled removal time of deprecated features 2025-01-08 12:17:31 +08:00
世界
bdd1477880 documentation: Remove outdated icons 2025-01-08 12:17:31 +08:00
世界
7f614bbd3a Migrate bad options to library 2025-01-08 12:17:30 +08:00
世界
eec9cdad3d Implement udp connect 2025-01-08 12:17:30 +08:00
世界
53953ac85a Implement new deprecated warnings 2025-01-08 12:17:30 +08:00
世界
c58b906180 Improve rule actions 2025-01-08 12:17:30 +08:00
世界
40f1ead687 Remove unused reject methods 2025-01-08 12:17:29 +08:00
世界
4e29895be3 refactor: Modular inbounds/outbounds 2025-01-08 12:17:29 +08:00
世界
a27475ea38 Implement dns-hijack 2025-01-08 12:17:28 +08:00
世界
3117137a43 Implement resolve(server) 2025-01-08 12:17:16 +08:00
世界
854e2971df Implement TCP and ICMP rejects 2025-01-08 12:17:15 +08:00
世界
bec88944e0 Crazy sekai overturns the small pond 2025-01-08 12:17:08 +08:00
107 changed files with 931 additions and 1668 deletions

View File

@@ -1,30 +0,0 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--no-deb-generate-changes
--config-files /etc/config/sing-box
--config-files /etc/sing-box/config.json
--depends ca-bundle
--depends kmod-inet-diag
--depends kmod-tun
--depends firewall4
--before-remove release/config/openwrt.prerm
release/config/config.json=/etc/sing-box/config.json
release/config/openwrt.conf=/etc/config/sing-box
release/config/openwrt.init=/etc/init.d/sing-box
release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box
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

View File

@@ -1,21 +0,0 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-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"
--no-deb-generate-changes
--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

28
.github/deb2ipk.sh vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
set -e -o pipefail
PROJECT=$(dirname "$0")/../..
TMP_PATH=`mktemp -d`
cp $2 $TMP_PATH
pushd $TMP_PATH
DEB_NAME=`ls *.deb`
ar x $DEB_NAME
mkdir control
pushd control
tar xf ../control.tar.gz
rm md5sums
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
cat control
tar czf ../control.tar.gz ./*
popd
DEB_NAME=${DEB_NAME%.deb}
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
popd
cp $TMP_PATH/$DEB_NAME.ipk $3
rm -r $TMP_PATH

View File

@@ -46,16 +46,16 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.23
- 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 --ci --nightly
go run -v ./cmd/internal/read_tag --nightly
- name: Set outputs
id: outputs
run: |-
@@ -69,206 +69,141 @@ jobs:
strategy:
matrix:
include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- { os: windows, arch: amd64 }
- { os: windows, arch: amd64, legacy_go: true }
- { os: windows, arch: "386" }
- { os: windows, arch: "386", legacy_go: true }
- { os: windows, arch: arm64 }
- { os: darwin, arch: amd64 }
- { os: darwin, arch: amd64, legacy_go: true }
- { os: darwin, arch: arm64 }
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- { os: android, arch: "386", ndk: "i686-linux-android21" }
- name: linux_386
goos: linux
goarch: 386
- name: linux_amd64
goos: linux
goarch: amd64
- name: linux_arm64
goos: linux
goarch: arm64
- name: linux_arm
goos: linux
goarch: arm
goarm: 6
- name: linux_arm_v7
goos: linux
goarch: arm
goarm: 7
- name: linux_s390x
goos: linux
goarch: s390x
- name: linux_riscv64
goos: linux
goarch: riscv64
- name: linux_mips64le
goos: linux
goarch: mips64le
- name: windows_amd64
goos: windows
goarch: amd64
require_legacy_go: true
- name: windows_386
goos: windows
goarch: 386
require_legacy_go: true
- name: windows_arm64
goos: windows
goarch: arm64
- name: darwin_arm64
goos: darwin
goarch: arm64
- name: darwin_amd64
goos: darwin
goarch: amd64
require_legacy_go: true
- name: android_arm64
goos: android
goarch: arm64
- name: android_arm
goos: android
goarch: arm
goarm: 7
- name: android_amd64
goos: android
goarch: amd64
- name: android_386
goos: android
goarch: 386
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
if: matrix.legacy_go
uses: actions/setup-go@v5
with:
go-version: ~1.20
- name: Setup Go
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5
go-version: ^1.23
- name: Cache legacy Go
if: matrix.require_legacy_go
id: cache-legacy-go
uses: actions/cache@v4
with:
go-version: ^1.24
path: |
~/go/go1.20.14
key: go120
- name: Setup legacy Go
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |-
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
tar -xzf go1.20.14.linux-amd64.tar.gz
mv go $HOME/go/go1.20.14
- name: Setup Android NDK
if: matrix.os == 'android'
if: matrix.goos == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
ndk-version: r28-beta2
local-cache: true
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
install-only: true
- name: Extract signing key
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: 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.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
if: matrix.goos != 'android'
run: |-
goreleaser release --clean --split
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOPATH: ${{ env.HOME }}/go
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
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.os == 'android'
run: |
set -xeuo pipefail
if: matrix.goos == 'android'
run: |-
go install -v ./cmd/internal/build
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
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
env:
CGO_ENABLED: "1"
BUILD_GOOS: ${{ matrix.os }}
BUILD_GOARCH: ${{ matrix.arch }}
BUILD_GOOS: ${{ matrix.goos }}
BUILD_GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name
run: |-
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
if [[ -n "${{ matrix.goarm }}" ]]; then
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
DIR_NAME="${DIR_NAME}-legacy"
fi
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}-1"
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
cp .fpm_systemd .fpm
fpm -t deb \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ 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
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
cp .fpm_systemd .fpm
fpm -t rpm \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ 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: Package Pacman
if: matrix.pacman != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y libarchive-tools
cp .fpm_systemd .fpm
fpm -t pacman \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
--architecture ${{ matrix.pacman }} \
dist/sing-box=/usr/bin/sing-box
- name: Package OpenWrt
if: matrix.openwrt != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_openwrt .fpm
fpm -t deb \
-v "$PKG_VERSION" \
-p "dist/openwrt.deb" \
--architecture all \
dist/sing-box=/usr/bin/sing-box
for architecture in ${{ matrix.openwrt }}; do
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
done
rm "dist/openwrt.deb"
- 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
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
name: binary-${{ matrix.name }}
path: 'dist'
build_android:
name: Build Android
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
@@ -284,12 +219,12 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.23
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
ndk-version: r28-beta2
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
@@ -321,16 +256,10 @@ jobs:
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Update version
- name: Build release
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
@@ -339,16 +268,47 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare upload
- name: Build debug
if: github.event_name != 'workflow_dispatch'
run: |-
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
go run -v ./cmd/internal/update_android_version --ci
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
./gradlew :app:assemblePlayRelease
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare release upload
if: github.event_name == 'workflow_dispatch'
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
- name: Prepare debug upload
if: github.event_name != 'workflow_dispatch'
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-android-apks
path: 'dist'
- name: Upload debug apk (arm64-v8a)
if: github.event_name != 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: "SFA-${{ needs.calculate_version.outputs.version }}-arm64-v8a.apk"
path: 'dist/release/*-arm64-v8a.apk'
- name: Upload debug apk (universal)
if: github.event_name != 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: "SFA-${{ needs.calculate_version.outputs.version }}-universal.apk"
path: 'dist/release/*-universal.apk'
publish_android:
name: Publish Android
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
@@ -364,12 +324,12 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.23
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
ndk-version: r28-beta2
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
@@ -462,7 +422,7 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.23
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
@@ -502,19 +462,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"
@@ -590,10 +550,10 @@ jobs:
cd "${{ matrix.archive }}"
zip -r SFM.dSYMs.zip dSYMs
popd
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"
mkdir -p dist/release
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
- name: Upload image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
@@ -614,6 +574,12 @@ jobs:
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
install-only: true
- name: Cache ghr
uses: actions/cache@v4
id: cache-ghr
@@ -638,17 +604,26 @@ 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
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Replace builds
if: ${{ env.PUBLISHED != 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace -p 5 "v${VERSION}" dist
ghr --replace -p 5 "v${VERSION}" dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -28,11 +28,10 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.23
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: --timeout=30m
install-mode: binary
verify: false
install-mode: binary

View File

@@ -1,63 +1,13 @@
name: Build Linux Packages
name: Release to Linux repository
on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
release:
types:
- published
jobs:
calculate_version:
name: Calculate version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --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
@@ -66,120 +16,23 @@ jobs:
- 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
go-version: ^1.23
- name: Extract signing key
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
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Publish release
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
args: release -f .goreleaser.fury.yaml --clean
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
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:
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/
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}

View File

@@ -48,7 +48,7 @@ nfpms:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: "config|noreplace"
type: config
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service

View File

@@ -52,7 +52,7 @@ builds:
env:
- CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go1.20.14
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
targets:
- windows_amd64_v1
- windows_386
@@ -95,12 +95,10 @@ archives:
builds:
- main
- android
formats:
- tar.gz
format: tar.gz
format_overrides:
- goos: windows
formats:
- zip
format: zip
wrap_in_directory: true
files:
- LICENSE
@@ -130,7 +128,7 @@ nfpms:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: "config|noreplace"
type: config
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.23-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

View File

@@ -10,7 +10,7 @@ GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
@@ -28,7 +28,7 @@ ci_build:
go build $(MAIN_PARAMS) $(MAIN)
generate_completions:
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
@@ -247,4 +247,4 @@ clean:
update:
git fetch
git reset FETCH_HEAD --hard
git clean -fdx
git clean -fdx

View File

@@ -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) *SavedBinary
SaveRuleSet(tag string, set *SavedBinary) error
LoadRuleSet(tag string) *SavedRuleSet
SaveRuleSet(tag string, set *SavedRuleSet) error
}
type SavedBinary struct {
type SavedRuleSet struct {
Content []byte
LastUpdated time.Time
LastEtag string
}
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
if err != nil {
@@ -70,7 +70,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
return buffer.Bytes(), nil
}
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)

View File

@@ -53,11 +53,10 @@ type InboundContext struct {
// sniffer
Protocol string
Domain string
Client string
SniffContext any
PacketSniffError error
Protocol string
Domain string
Client string
SniffContext any
// cache

View File

@@ -25,7 +25,6 @@ type NetworkManager interface {
PackageManager() tun.PackageManager
WIFIState() WIFIState
ResetNetwork()
UpdateWIFIState()
}
type NetworkOptions struct {

View File

@@ -38,7 +38,7 @@ type Router interface {
ClearDNSCache()
Rules() []Rule
AppendTracker(tracker ConnectionTracker)
SetTracker(tracker ConnectionTracker)
ResetNetwork()
}

19
box.go
View File

@@ -165,15 +165,7 @@ func New(options Options) (*Box, error) {
} else {
tag = F.ToString(i)
}
endpointCtx := ctx
if tag != "" {
// TODO: remove this
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = endpointManager.Create(
endpointCtx,
err = endpointManager.Create(ctx,
router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag,
@@ -191,8 +183,7 @@ 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,
@@ -257,7 +248,7 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "create clash-server")
}
router.AppendTracker(clashServer)
router.SetTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
services = append(services, clashServer)
}
@@ -267,7 +258,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create v2ray-server")
}
if v2rayServer.StatsService() != nil {
router.AppendTracker(v2rayServer.StatsService())
router.SetTracker(v2rayServer.StatsService())
services = append(services, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
@@ -408,7 +399,7 @@ func (s *Box) Close() error {
close(s.done)
}
err := common.Close(
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.network,
s.inbound, s.outbound, s.router, s.connection, s.network,
)
for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -5,7 +5,6 @@ import (
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/sagernet/asc-go/asc"
@@ -195,10 +194,6 @@ 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
}
}

View File

@@ -48,7 +48,7 @@ func FindSDK() {
}
func findNDK() bool {
const fixedVersion = "28.0.13004108"
const fixedVersion = "28.0.12674087"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath

View File

@@ -5,49 +5,40 @@ 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 (
flagRunInCI bool
flagRunNightly bool
)
var nightly bool
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
}
func main() {
flag.Parse()
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 nightly {
version, err := build_shared.ReadTagVersionRev()
if err != nil {
log.Fatal(err)
}
var versionStr string
if version.PreReleaseIdentifier != "" {
versionStr = version.VersionString() + "-nightly"
} else {
version.Patch++
versionStr = version.VersionString() + "-nightly"
}
err = setGitHubEnv("version", versionStr)
if err != nil {
log.Fatal(err)
}
} else {
tag, err := build_shared.ReadTag()
if err != nil {
log.Error(err)
os.Stdout.WriteString("unknown\n")
} else {
os.Stdout.WriteString(versionStr + "\n")
os.Stdout.WriteString(tag + "\n")
}
}
}

View File

@@ -13,14 +13,10 @@ import (
"github.com/sagernet/sing/common"
)
var (
flagRunInCI bool
flagRunNightly bool
)
var flagRunInCI bool
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
@@ -50,23 +46,21 @@ 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] {

View File

@@ -30,7 +30,7 @@ func init() {
}
func generateTLSKeyPair(serverName string) error {
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
if err != nil {
return err
}

View File

@@ -61,15 +61,14 @@ func upgradeRuleSet(sourcePath string) error {
log.Info("already up-to-date")
return nil
}
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
plainRuleSet, err := plainRuleSetCompat.Upgrade()
if err != nil {
return err
}
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(plainRuleSetCompat)
err = encoder.Encode(plainRuleSet)
if err != nil {
return E.Cause(err, "encode config")
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"os"
"github.com/sagernet/sing-box/common/settings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
@@ -57,7 +58,7 @@ func syncTime() error {
return err
}
if commandSyncTimeWrite {
err = ntp.SetSystemTime(response.Time)
err = settings.SetSystemTime(response.Time)
if err != nil {
return E.Cause(err, "write time to system")
}

View File

@@ -18,7 +18,6 @@ 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
}
@@ -32,9 +31,7 @@ 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
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.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 {
@@ -92,7 +89,6 @@ 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
}
@@ -107,9 +103,7 @@ 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
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.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 {
@@ -155,13 +149,10 @@ 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
if defaultInterface == nil || primaryInterface.Index != defaultInterface.Index {
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.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
@@ -170,9 +161,7 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
}
for _, fallbackInterface := range fallbackInterfaces {
perNetListener := listener
if defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index {
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.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

View File

@@ -124,6 +124,14 @@ 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)
@@ -136,7 +144,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()) {
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue
}

View File

@@ -0,0 +1,12 @@
//go:build !(windows || linux || darwin)
package settings
import (
"os"
"time"
)
func SetSystemTime(nowTime time.Time) error {
return os.ErrInvalid
}

View File

@@ -0,0 +1,14 @@
//go:build linux || darwin
package settings
import (
"time"
"golang.org/x/sys/unix"
)
func SetSystemTime(nowTime time.Time) error {
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
return unix.Settimeofday(&timeVal)
}

View File

@@ -0,0 +1,32 @@
package settings
import (
"time"
"unsafe"
"golang.org/x/sys/windows"
)
func SetSystemTime(nowTime time.Time) error {
var systemTime windows.Systemtime
systemTime.Year = uint16(nowTime.Year())
systemTime.Month = uint16(nowTime.Month())
systemTime.Day = uint16(nowTime.Day())
systemTime.Hour = uint16(nowTime.Hour())
systemTime.Minute = uint16(nowTime.Minute())
systemTime.Second = uint16(nowTime.Second())
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
proc := dllKernel32.NewProc("SetSystemTime")
_, _, err := proc.Call(
uintptr(unsafe.Pointer(&systemTime)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return err
}
return nil
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
)
const (
@@ -24,7 +23,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 E.Cause1(ErrNeedMoreData, err)
return err
}
if first != 19 {
@@ -34,7 +33,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
var protocol [19]byte
_, err = reader.Read(protocol[:])
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
return err
}
if string(protocol[:]) != "BitTorrent protocol" {
return os.ErrInvalid
@@ -68,9 +67,7 @@ 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 {

View File

@@ -71,19 +71,3 @@ 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)
}
}

View File

@@ -5,11 +5,14 @@ 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"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/task"
mDNS "github.com/miekg/dns"
)
@@ -18,16 +21,22 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
var length uint16
err := binary.Read(reader, binary.BigEndian, &length)
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
return os.ErrInvalid
}
if length == 0 {
return os.ErrInvalid
}
buffer := buf.NewSize(int(length))
defer buffer.Release()
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
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()
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
return err
}
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
}
@@ -38,6 +47,9 @@ 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
}

View File

@@ -1,23 +0,0 @@
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)
}

View File

@@ -3,12 +3,10 @@ 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"
)
@@ -16,11 +14,7 @@ import (
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
request, err := http.ReadRequest(std_bufio.NewReader(reader))
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err
}
return err
}
metadata.Protocol = C.ProtocolHTTP
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()

View File

@@ -20,6 +20,8 @@ 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()
@@ -306,7 +308,7 @@ find:
metadata.Protocol = C.ProtocolQUIC
metadata.Client = C.ClientChromium
metadata.SniffContext = fragments
return E.Cause1(ErrNeedMoreData, err)
return ErrClientHelloFragmented
}
metadata.Domain = fingerprint.ServerName
for metadata.Client == "" {

View File

@@ -12,26 +12,6 @@ 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")
@@ -40,7 +20,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.ErrNeedMoreData)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)

View File

@@ -8,7 +8,6 @@ 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"
)
@@ -16,7 +15,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 E.Cause1(ErrNeedMoreData, err)
return err
}
if tpktVersion != 0x03 {
return os.ErrInvalid
@@ -25,7 +24,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 E.Cause1(ErrNeedMoreData, err)
return err
}
if tpktReserved != 0x00 {
return os.ErrInvalid
@@ -34,7 +33,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 E.Cause1(ErrNeedMoreData, err)
return err
}
if tpktLength != 19 {
@@ -44,7 +43,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 E.Cause1(ErrNeedMoreData, err)
return err
}
if cotpLength != 14 {
@@ -54,7 +53,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 E.Cause1(ErrNeedMoreData, err)
return err
}
if cotpTpduType != 0xE0 {
return os.ErrInvalid
@@ -62,13 +61,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
err = rw.SkipN(reader, 5)
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
return err
}
var rdpType uint8
err = binary.Read(reader, binary.BigEndian, &rdpType)
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
return err
}
if rdpType != 0x01 {
return os.ErrInvalid
@@ -76,12 +75,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 E.Cause1(ErrNeedMoreData, err)
return err
}
var rdpLength uint8
err = binary.Read(reader, binary.BigEndian, &rdpLength)
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
return err
}
if rdpLength != 8 {
return os.ErrInvalid

View File

@@ -3,14 +3,12 @@ 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"
)
@@ -20,8 +18,6 @@ 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 {
@@ -38,12 +34,12 @@ func Skip(metadata *adapter.InboundContext) bool {
return false
}
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
if timeout == 0 {
timeout = C.ReadPayloadTimeout
}
deadline := time.Now().Add(timeout)
var sniffError error
var errors []error
for i := 0; ; i++ {
err := conn.SetReadDeadline(deadline)
if err != nil {
@@ -57,32 +53,26 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
}
return E.Cause(err, "read payload")
}
sniffError = nil
errors = nil
for _, sniffer := range sniffers {
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
return bytes.NewReader(it.Bytes())
})...)
err = sniffer(ctx, metadata, reader)
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
if err == nil {
return nil
}
sniffError = E.Errors(sniffError, err)
}
if !errors.Is(err, ErrNeedMoreData) {
break
errors = append(errors, err)
}
}
return sniffError
return E.Errors(errors...)
}
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
var sniffError []error
var errors []error
for _, sniffer := range sniffers {
err := sniffer(ctx, metadata, packet)
if err == nil {
return nil
}
sniffError = append(sniffError, err)
errors = append(errors, err)
}
return E.Errors(sniffError...)
return E.Errors(errors...)
}

View File

@@ -5,26 +5,22 @@ 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 {
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 {
scanner := bufio.NewScanner(reader)
if !scanner.Scan() {
return os.ErrInvalid
}
fistLine, _, err := bReader.ReadLine()
if err != nil {
return err
fistLine := scanner.Text()
if !strings.HasPrefix(fistLine, "SSH-2.0-") {
return os.ErrInvalid
}
metadata.Protocol = C.ProtocolSSH
metadata.Client = string(fistLine)[8:]
metadata.Client = fistLine[8:]
return nil
}

View File

@@ -3,13 +3,11 @@ 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 {
@@ -25,9 +23,5 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
metadata.Domain = clientHello.ServerName
return nil
}
if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err
}
return err
}

View File

@@ -11,8 +11,8 @@ import (
"time"
)
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))
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
if err != nil {
return nil, err
}
@@ -23,7 +23,7 @@ func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() ti
return &certificate, err
}
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
if timeFunc == nil {
timeFunc = time.Now
}
@@ -47,11 +47,7 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
},
DNSNames: []string{serverName},
}
if parent == nil {
parent = template
parentKey = key
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
if err != nil {
return
}

View File

@@ -89,20 +89,16 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
tlsConfig.ShortIds = make(map[[8]byte]bool)
if len(options.Reality.ShortID) == 0 {
tlsConfig.ShortIds[[8]byte{0}] = true
} else {
for i, shortIDString := range options.Reality.ShortID {
var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
if err != nil {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
}
if decodedLen > 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
}
tlsConfig.ShortIds[shortID] = true
for i, shortIDString := range options.Reality.ShortID {
var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
if err != nil {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
}
if decodedLen > 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
}
tlsConfig.ShortIds[shortID] = true
}
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions)

View File

@@ -6,7 +6,6 @@ import (
"net"
"os"
"strings"
"time"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -222,12 +221,8 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
key = content
}
if certificate == nil && key == nil && options.Insecure {
timeFunc := ntp.TimeFuncFromContext(ctx)
if timeFunc == nil {
timeFunc = time.Now
}
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(nil, nil, timeFunc, info.ServerName)
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -2,171 +2,7 @@
icon: material/alert-decagram
---
### 1.11.8
* Improve `auto_redirect` **1**
* Fixes and improvements
**1**:
Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks,
see [Tun](/configuration/inbound/tun/#auto_redirect).
_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.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:
* Introducing rule actions **1**
* Improve tun compatibility **3**
* Merge route options to route actions **4**
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5**
* Add multi network dialing **6**
* Add `cache_capacity` DNS option **7**
* Add `override_address` and `override_port` route options **8**
* Upgrade WireGuard outbound to endpoint **9**
* Add UDP GSO support for WireGuard
* Make GSO adaptive **10**
* Add UDP timeout route option **11**
* Add more masquerade options for hysteria2 **12**
* Add `rule-set merge` command
* Add port hopping support for Hysteria2 **13**
* Hysteria2 `ignore_client_bandwidth` behavior update **14**
**1**:
New rule actions replace legacy inbound fields and special outbound fields,
and can be used for pre-matching **2**.
See [Rule](/configuration/route/rule/),
[Rule Action](/configuration/route/rule_action/),
[DNS Rule](/configuration/dns/rule/) and
[DNS Rule Action](/configuration/dns/rule_action/).
For migration, see
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
**2**:
Similar to Surge's pre-matching.
Specifically, new rule actions allow you to reject connections with
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
before connection established to improve tun's compatibility.
See [Rule Action](/configuration/route/rule_action/).
**3**:
When `gvisor` tun stack is enabled, even if the request passes routing,
if the outbound connection establishment fails,
the connection still does not need to be established and a TCP RST is replied.
**4**:
Route options in DNS route actions will no longer be considered deprecated,
see [DNS Route Action](/configuration/dns/rule_action/).
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
see [Route Action](/configuration/route/rule_action/).
**5**:
When using in graphical clients, new routing rule items allow you to match on
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
and [Headless Rule](/configuration/rule-set/headless-rule/).
**6**:
Similar to Surge's strategy.
New options allow you to connect using multiple network interfaces,
prefer or only use one type of interface,
and configure a timeout to fallback to other interfaces.
See [Dial Fields](/configuration/shared/dial/#network_strategy),
[Rule Action](/configuration/route/rule_action/#network_strategy)
and [Route](/configuration/route/#default_network_strategy).
**7**:
See [DNS](/configuration/dns/#cache_capacity).
**8**:
See [Rule Action](/configuration/route/#override_address) and
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
**9**:
The new WireGuard endpoint combines inbound and outbound capabilities,
and the old outbound will be removed in sing-box 1.13.0.
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
**10**:
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
For TUN, GSO has been removed,
see [Deprecated](/deprecated/#gso-option-in-tun).
**11**:
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
**12**:
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
**13**:
See [Hysteria2](/configuration/outbound/hysteria2/).
**14**:
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
### 1.10.7
#### 1.11.0-beta.22
* Fixes and improvements

View File

@@ -7,10 +7,6 @@ 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+

View File

@@ -211,10 +211,6 @@ Set the default route to the Tun.
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
!!! note "Also enable `auto_redirect`"
`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks.
#### iproute2_table_index
!!! question "Since sing-box 1.10.0"
@@ -239,29 +235,22 @@ Linux iproute2 rule start index generated by `auto_route`.
Only supported on Linux with `auto_route` enabled.
Improve TUN routing and performance using nftables.
Automatically configure iptables/nftables to redirect connections.
`auto_redirect` is always recommended on Linux, it provides better routing,
higher performance (better than tproxy),
and avoids conflicts between TUN and Docker bridge networks.
*In Android*
Note that `auto_redirect` also works on Android,
but due to the lack of `nftables` and `ip6tables`,
only simple IPv4 TCP forwarding is performed.
To share your VPN connection over hotspot or repeater on Android,
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
`auto_redirect` also automatically inserts compatibility rules
into the OpenWrt fw4 table, i.e.
it will work on routers without any extra configuration.
*In Linux*:
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
#### auto_redirect_input_mark
!!! question "Since sing-box 1.10.0"
Connection input mark used by `auto_redirect`.
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
`0x2023` is used by default.
@@ -269,7 +258,7 @@ Connection input mark used by `auto_redirect`.
!!! question "Since sing-box 1.10.0"
Connection output mark used by `auto_redirect`.
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
`0x2024` is used by default.
@@ -280,15 +269,17 @@ Enforce strict routing rules when `auto_route` is enabled:
*In Linux*:
* Let unsupported network unreachable
* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
* Make ICMP traffic route to tun instead of upstream interfaces
* Route all connections to tun
It prevents IP address leaks and makes DNS hijacking work on Android.
*In Windows*:
* Let unsupported network unreachable
* prevent DNS leak caused by
* Add firewall rules to prevent DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations.
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
#### route_address
@@ -376,6 +367,8 @@ Exclude custom routes when `auto_route` is enabled.
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
Matched traffic will bypass the sing-box routes.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
=== "Without `auto_redirect` enabled"

View File

@@ -215,10 +215,6 @@ tun 接口的 IPv6 前缀。
VPN 默认优先于 tun。要使 tun 经过 VPN启用 `route.override_android_vpn`
!!! note "也启用 `auto_redirect`"
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy 并避免 TUN 与 Docker 桥接网络冲突。
#### iproute2_table_index
!!! question "自 sing-box 1.10.0 起"
@@ -241,24 +237,23 @@ tun 接口的 IPv6 前缀。
!!! quote ""
仅支持 Linux且需要 `auto_route` 已启用。
仅支持 Linux且需要 `auto_route` 已启用。
通过使用 nftables 改善 TUN 路由和性能
自动配置 iptables/nftables 以重定向连接
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy并避免了 TUN 和 Docker 桥接网络之间的冲突。
*在 Android 中*
请注意,`auto_redirect` 也适用于 Android但由于缺少 `nftables``ip6tables`,仅执行简单的 IPv4 TCP 转发。
若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。
*在 Linux 中*:
`route.default_mark``[dialOptions].routing_mark` 冲突
带有 `auto_redirect ``auto_route` 可以在路由器上按预期工作,**无需干预**
#### auto_redirect_input_mark
!!! question "自 sing-box 1.10.0 起"
`auto_redirect` 使用的连接输入标记。
`route_address_set``route_exclude_address_set` 使用的连接输入标记。
默认使用 `0x2023`
@@ -266,25 +261,29 @@ tun 接口的 IPv6 前缀。
!!! question "自 sing-box 1.10.0 起"
`auto_redirect` 使用的连接输出标记。
`route_address_set``route_exclude_address_set` 使用的连接输出标记。
默认使用 `0x2024`
#### strict_route
启用 `auto_route`,强制执行严格的路由规则
启用 `auto_route` 时执行严格的路由规则
*在 Linux 中*
*在 Linux 中*:
* 使不支持的网络不可达。
* 出于历史遗留原因,当未启用 `strict_route``auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。
* 不支持的网络无法到达
* 使 ICMP 流量路由到 tun 而不是上游接口
* 将所有连接路由到 tun
*在 Windows 中*
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
* 使不支持的网络不可达。
* 阻止 Windows 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) 造成的 DNS 泄露
*在 Windows 中*:
它可能会使某些 Windows 应用程序(如 VirtualBox在某些情况下无法正常工作。
* 添加防火墙规则以阻止 Windows
的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
造成的 DNS 泄露
它可能会使某些应用程序(如 VirtualBox在某些情况下无法正常工作。
#### route_address
@@ -342,6 +341,8 @@ tun 接口的 IPv6 前缀。
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
不匹配的流量将绕过 sing-box 路由。
`route.default_mark``[dialOptions].routing_mark` 冲突。
=== "`auto_redirect` 未启用"

View File

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

View File

@@ -8,7 +8,7 @@ icon: material/delete-clock
### Structure
```json
```json F
{
"type": "block",
"tag": "block"

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "已在 sing-box 1.11.0 废弃"
WireGuard 出站已被用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
WireGuard 出站已被用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
!!! quote "sing-box 1.11.0 中的更改"

View File

@@ -31,45 +31,6 @@ Tag of target outbound.
See `route-options` fields below.
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` reject connections
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
For non-tun connections and already established connections, will just be closed.
#### method
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
#### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
### route-options
```json
@@ -148,6 +109,45 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
| 443 | `quic` |
| 3478 | `stun` |
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` reject connections
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
For non-tun connections and already established connections, will just be closed.
#### method
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
#### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
### sniff
```json

View File

@@ -27,45 +27,6 @@ icon: material/new-box
参阅下方的 `route-options` 字段。
### reject
```json
{
"action": "reject",
"method": "default", // 默认
"no_drop": false
}
```
`reject` 拒绝连接。
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
对于非 tun 连接和已建立的连接,将直接关闭。
#### method
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。
- `drop`: 丢弃数据包。
#### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
## 非最终动作
### route-options
```json
@@ -146,6 +107,45 @@ UDP 连接超时时间。
| 443 | `quic` |
| 3478 | `stun` |
### reject
```json
{
"action": "reject",
"method": "default", // 默认
"no_drop": false
}
```
`reject` 拒绝连接。
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
对于非 tun 连接和已建立的连接,将直接关闭。
#### method
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。
- `drop`: 丢弃数据包。
#### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
## 非最终动作
### sniff
```json

View File

@@ -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 | `chromium` |
| Chromium/Cronet | `chrimium` |
| Safari/Apple Network API | `safari` |
| Firefox / uquic firefox | `firefox` |
| quic-go / uquic chrome | `quic-go` |
| quic-go / uquic chrome | `quic-go` |

View File

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

View File

@@ -74,7 +74,7 @@ icon: material/new-box
==必填==
一组 [无头规则](./headless-rule/).
一组 [无头规则](../headless-rule/).
### 本地或远程字段

View File

@@ -31,11 +31,12 @@ The protocol version, `1` or `2`.
### Application support
| 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) | / |
| 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) | / |
### Protocol details
@@ -49,13 +50,7 @@ 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, but with different address types:
| ATYP | Address type |
|--------|--------------|
| `0x00` | IPv4 Address |
| `0x01` | IPv6 Address |
| `0x02` | Domain Name |
**ATYP / address / port**: Uses the SOCKS address format.
#### Protocol version 2

View File

@@ -8,56 +8,44 @@ icon: material/package
=== ":material-debian: Debian / APT"
```bash
sudo mkdir -p /etc/apt/keyrings &&
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
echo '
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
sudo apt-get update &&
sudo apt-get install sing-box # or sing-box-beta
```
```bash
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
sudo apt-get update
sudo apt-get install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 5"
=== ":material-redhat: Redhat / DNF"
```bash
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
sudo dnf install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 4"
```bash
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
sudo dnf -y install dnf-plugins-core &&
sudo dnf install sing-box # or sing-box-beta
```
```bash
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
sudo dnf install sing-box # or sing-box-beta
```
(This applies to any distribution that uses `dnf` as the package manager: Fedora, CentOS, even OpenSUSE with DNF installed.)
## :material-download-box: Manual Installation
The script download and install the latest package from GitHub releases for deb or rpm based Linux distributions, ArchLinux and OpenWrt.
```shell
curl -fsSL https://sing-box.app/install.sh | sh
```
or latest beta:
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
```
or specific version:
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
```
=== ":material-debian: Debian / DEB"
```bash
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
```
=== ":material-redhat: Redhat / RPM"
```bash
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
```
(This applies to any distribution that uses `rpm` and `systemd`. Because of how `rpm` defines dependencies, if it installs, it probably works.)
=== ":simple-archlinux: Archlinux / PKG"
```bash
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
```
## :material-book-lock-open: Managed Installation

View File

@@ -9,35 +9,22 @@ icon: material/package
=== ":material-debian: Debian / APT"
```bash
sudo mkdir -p /etc/apt/keyrings &&
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
echo '
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
sudo apt-get update &&
sudo apt-get install sing-box # or sing-box-beta
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
sudo apt-get update
sudo apt-get install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 5"
=== ":material-redhat: Redhat / DNF"
```bash
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
sudo dnf install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 4"
```bash
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
sudo dnf -y install dnf-plugins-core &&
sudo dnf install sing-box # or sing-box-beta
```
```bash
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
sudo dnf install sing-box # or sing-box-beta
```
(这适用于任何使用 `dnf` 作为包管理器的发行版Fedora、CentOS甚至安装了 DNF 的 OpenSUSE。
## :material-download-box: 手动安装

View File

@@ -1,97 +0,0 @@
#!/bin/sh
download_beta=false
download_version=""
for arg in "$@"; do
if [[ "$arg" == "--beta" ]]; then
download_beta=true
elif [[ "$arg" == "--version" ]]; then
download_version=true
elif [[ "$download_version" == 'true' ]]; then
download_version="$arg"
else
echo "Unknown argument: $arg"
echo "Usage: $0 [--beta] [--version <version>]"
exit 1
fi
done
if [[ $(command -v dpkg) ]]; then
os="linux"
arch=$(dpkg --print-architecture)
package_suffix=".deb"
package_install="dpkg -i"
elif [[ $(command -v dnf) ]]; then
os="linux"
arch=$(uname -m)
package_suffix=".rpm"
package_install="dnf install -y"
elif [[ $(command -v rpm) ]]; then
os="linux"
arch=$(uname -m)
package_suffix=".rpm"
package_install="rpm -i"
elif [[ $(command -v pacman) ]]; then
os="linux"
arch=$(uname -m)
package_suffix=".pkg.tar.zst"
package_install="pacman -U --noconfirm"
elif [[ $(command -v opkg) ]]; then
os="openwrt"
source /etc/os-release
arch="$OPENWRT_ARCH"
package_suffix=".ipk"
package_install="opkg update && opkg install -y"
else
echo "Missing supported package manager."
exit 1
fi
if [[ -z "$download_version" ]]; then
if [[ "$download_beta" != 'true' ]]; then
if [[ -n "$GITHUB_TOKEN" ]]; then
latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest)
else
latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases/latest)
fi
curl_exit_status=$?
if [[ $curl_exit_status -ne 0 ]]; then
echo "$latest_release"
exit $?
fi
download_version=$(echo "$latest_release" | grep tag_name | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//')
else
if [[ -n "$GITHUB_TOKEN" ]]; then
latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases)
else
latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases)
fi
curl_exit_status=$?
if [[ $? -ne 0 ]]; then
echo "$latest_release"
exit $?
fi
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//')
fi
fi
package_name="sing-box_${download_version}_${os}_${arch}${package_suffix}"
package_url="https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}"
echo "Downloading $package_url"
if [[ -n "$GITHUB_TOKEN" ]]; then
curl --fail-with-body -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url"
else
curl --fail-with-body -Lo "$package_name" "$package_url"
fi
if [[ $? -ne 0 ]]; then
exit $?
fi
if [[ $(command -v sudo) ]]; then
package_install="sudo $package_install"
fi
echo "$package_install $package_name" && $package_install "$package_name" && rm "$package_name"

View File

@@ -108,7 +108,7 @@ Inbound fields are deprecated and can be replaced by rule actions.
!!! info "References"
[Listen Fields](/configuration/shared/listen/) /
[Listen Fields](/configuration/inbound/listen/) /
[Rule](/configuration/route/rule/) /
[Rule Action](/configuration/route/rule_action/) /
[DNS Rule](/configuration/dns/rule/) /

View File

@@ -284,8 +284,8 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
})
}
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {
var savedSet adapter.SavedBinary
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet {
var savedSet adapter.SavedRuleSet
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.SavedBinary {
return &savedSet
}
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error {
return c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := c.createBucket(t, bucketRuleSet)
if err != nil {

View File

@@ -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(Dir(s.externalUI))))
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.externalUI))))
})
}
return s, nil

View File

@@ -1,18 +0,0 @@
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
}

View File

@@ -41,6 +41,7 @@ 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)
@@ -52,7 +53,6 @@ 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(response.Body, s.externalUI)
err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
if err != nil {
removeAllInDirectory(s.externalUI)
}
return err
}
func (s *Server) downloadZIP(body io.Reader, output string) error {
tempFile, err := filemanager.CreateTemp(s.ctx, "external-ui.zip")
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
tempFile, err := filemanager.CreateTemp(s.ctx, name)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/binary"
"net"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
)
@@ -17,7 +18,19 @@ func (c *CommandClient) ServiceReload() error {
if err != nil {
return err
}
return readError(conn)
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
}
func (s *CommandServer) handleServiceReload(conn net.Conn) error {
@@ -42,7 +55,19 @@ func (c *CommandClient) ServiceClose() error {
if err != nil {
return err
}
return readError(conn)
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
}
func (s *CommandServer) handleServiceClose(conn net.Conn) error {

View File

@@ -66,6 +66,10 @@ func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions op
return nil, os.ErrInvalid
}
func (s *platformInterfaceStub) UpdateRouteOptions(options *tun.Options, platformInterface option.TunPlatformOptions) error {
return os.ErrInvalid
}
func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool {
return true
}

View File

@@ -56,12 +56,7 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
if sFixAndroidStack {
done := make(chan struct{})
go func() {
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
close(done)
}()
<-done
go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
} else {
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
}

View File

@@ -9,6 +9,7 @@ type PlatformInterface interface {
UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int32) error
OpenTun(options TunOptions) (int32, error)
UpdateRouteOptions(options TunOptions) error
WriteLog(message string)
UseProcFS() bool
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)

View File

@@ -13,6 +13,7 @@ type Interface interface {
UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int) error
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
Interfaces() ([]adapter.NetworkInterface, error)
UnderNetworkExtension() bool

View File

@@ -40,7 +40,7 @@ type BoxService struct {
clashServer adapter.ClashServer
pauseManager pause.Manager
iOSPauseFields
servicePauseFields
}
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
@@ -174,6 +174,20 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
return tun.New(*options)
}
func (w *platformInterfaceWrapper) UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error {
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
return E.New("android: unsupported uid options")
}
if len(options.IncludeAndroidUser) > 0 {
return E.New("android: unsupported android_user option")
}
routeRanges, err := options.BuildAutoRouteRanges(true)
if err != nil {
return err
}
return w.iif.UpdateRouteOptions(&tunOptions{options, routeRanges, platformOptions})
}
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
return &platformDefaultInterfaceMonitor{
platformInterfaceWrapper: w,

View File

@@ -1,39 +1,33 @@
package libbox
import (
"sync"
"time"
C "github.com/sagernet/sing-box/constant"
)
type iOSPauseFields struct {
endPauseTimer *time.Timer
type servicePauseFields struct {
pauseAccess sync.Mutex
pauseTimer *time.Timer
}
func (s *BoxService) Pause() {
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.pauseAccess.Lock()
defer s.pauseAccess.Unlock()
if s.pauseTimer != nil {
s.pauseTimer.Stop()
}
s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork)
}
func (s *BoxService) Wake() {
if !C.IsIos {
s.pauseManager.DeviceWake()
s.instance.Router().ResetNetwork()
s.pauseAccess.Lock()
defer s.pauseAccess.Unlock()
if s.pauseTimer != nil {
s.pauseTimer.Stop()
}
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()
}

View File

@@ -7,13 +7,11 @@ 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.",
}

View File

@@ -4,7 +4,6 @@ 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
View File

@@ -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.2.1
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/render v1.0.3
github.com/gofrs/uuid/v5 v5.3.2
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
github.com/gofrs/uuid/v5 v5.3.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
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-20241231083714-66613d49c422
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
github.com/mholt/acmez v1.2.0
github.com/miekg/dns v1.1.63
github.com/miekg/dns v1.1.62
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.49.0-beta.1
github.com/sagernet/quic-go v0.48.2-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.6
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 v0.6.0-beta.11
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-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.0
github.com/sagernet/sing-tun v0.6.4
github.com/sagernet/sing-vmess 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/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.10.0
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.32.0
golang.org/x/crypto v0.31.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/mod v0.20.0
golang.org/x/net v0.34.0
golang.org/x/sys v0.30.0
golang.org/x/net v0.31.0
golang.org/x/sys v0.28.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

72
go.sum
View File

@@ -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.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
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/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.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
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/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-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
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/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-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
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/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
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/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,31 +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.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/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/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.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 v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE=
github.com/sagernet/sing v0.6.6/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 v0.6.0-beta.11 h1:jWCNlZI1Vdj8lQeBrjRZIQfNwlqMk0ZRqMJuPfTJupI=
github.com/sagernet/sing v0.6.0-beta.11/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-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 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.4 h1:3Iew6UtAf1+mucVeHKNhAEQI5xmq3CUCbGptUbjebts=
github.com/sagernet/sing-tun v0.6.4/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/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/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=
@@ -154,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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/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=
@@ -174,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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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/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.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
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/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=
@@ -193,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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
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=

View File

@@ -20,16 +20,12 @@ type ID struct {
}
func ContextWithNewID(ctx context.Context) context.Context {
return ContextWithID(ctx, ID{
return context.WithValue(ctx, (*idKey)(nil), 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

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
)
@@ -31,7 +30,7 @@ func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) erro
return err
}
o.RawMessage = content
return checkOptions(o)
return nil
}
type LogOptions struct {
@@ -43,52 +42,3 @@ 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
}

View File

@@ -12,7 +12,6 @@ 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"
@@ -81,7 +80,7 @@ func (i *Inbound) Close() error {
}
func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil)
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.Socksaddr{}, nil)
}
func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
@@ -94,7 +93,7 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
case 2:
destination.Addr = i.overrideDestination.Addr
case 3:
destination.Port = i.overrideDestination.Port
destination.Port = metadata.Destination.Port
}
metadata.Destination = destination
if i.overrideOption != 0 {
@@ -105,6 +104,7 @@ 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,11 +123,8 @@ 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
if i.overrideOption != 0 {
conn = bufio.NewDestinationNATPacketConn(bufio.NewNetPacketConn(conn), i.listener.UDPAddr(), destination)
}
metadata.OriginDestination = i.listener.UDPAddr()
i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -19,7 +19,6 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
@@ -28,7 +27,10 @@ func RegisterURLTest(registry *outbound.Registry) {
outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest)
}
var _ adapter.OutboundGroup = (*URLTest)(nil)
var (
_ adapter.OutboundGroup = (*URLTest)(nil)
_ adapter.InterfaceUpdateListener = (*URLTest)(nil)
)
type URLTest struct {
outbound.Adapter
@@ -170,12 +172,15 @@ 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
outbound adapter.OutboundManager
pause pause.Manager
pauseCallback *list.Element[pause.Callback]
outboundManager adapter.OutboundManager
logger log.Logger
outbounds []adapter.Outbound
link string
@@ -184,15 +189,17 @@ 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) {
@@ -217,7 +224,7 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage
}
return &URLTestGroup{
ctx: ctx,
outbound: outboundManager,
outboundManager: outboundManager,
logger: logger,
outbounds: outbounds,
link: link,
@@ -226,15 +233,13 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage
idleTimeout: idleTimeout,
history: history,
close: make(chan struct{}),
pause: service.FromContext[pause.Manager](ctx),
pauseManager: 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)
@@ -244,25 +249,24 @@ 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
}
@@ -326,11 +330,10 @@ 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)
}
}
@@ -363,7 +366,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
continue
}
checked[realTag] = true
p, loaded := g.outbound.Outbound(realTag)
p, loaded := g.outboundManager.Outbound(realTag)
if !loaded {
continue
}
@@ -395,16 +398,12 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
func (g *URLTestGroup) performUpdateCheck() {
var updated bool
if outbound, exists := g.Select(N.NetworkTCP); outbound != nil && (g.selectedOutboundTCP == nil || (exists && outbound != g.selectedOutboundTCP)) {
if g.selectedOutboundTCP != nil {
updated = true
}
g.selectedOutboundTCP = outbound
updated = true
}
if outbound, exists := g.Select(N.NetworkUDP); outbound != nil && (g.selectedOutboundUDP == nil || (exists && outbound != g.selectedOutboundUDP)) {
if g.selectedOutboundUDP != nil {
updated = true
}
g.selectedOutboundUDP = outbound
updated = true
}
if updated {
g.interruptGroup.Interrupt(g.interruptExternalConnections)

View File

@@ -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(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}

View File

@@ -245,7 +245,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if err != nil {
return nil, E.Cause(err, "initialize auto-redirect")
}
if !C.IsAndroid {
if !C.IsAndroid && (len(inbound.routeRuleSet) > 0 || len(inbound.routeExcludeRuleSet) > 0) {
inbound.tunOptions.AutoRedirectMarkMode = true
err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
if err != nil {
@@ -305,18 +305,16 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
if t.tunOptions.Name == "" {
t.tunOptions.Name = tun.CalculateInterfaceName("")
}
if t.platformInterface == nil {
if t.platformInterface == nil || runtime.GOOS != "android" {
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
for _, routeRuleSet := range t.routeRuleSet {
ipSets := routeRuleSet.ExtractIPSet()
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 {
@@ -324,11 +322,9 @@ 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 (
@@ -425,7 +421,41 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) {
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
t.autoRedirect.UpdateRouteAddressSet()
if t.autoRedirect != nil {
t.autoRedirect.UpdateRouteAddressSet()
} else {
tunOptions := t.tunOptions
for _, ipSet := range t.routeAddressSet {
for _, prefix := range ipSet.Prefixes() {
if prefix.Addr().Is4() {
tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix)
} else {
tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix)
}
}
}
for _, ipSet := range t.routeExcludeAddressSet {
for _, prefix := range ipSet.Prefixes() {
if prefix.Addr().Is4() {
tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix)
} else {
tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix)
}
}
}
if t.platformInterface != nil {
err := t.platformInterface.UpdateRouteOptions(&tunOptions, t.platformOptions)
if err != nil {
t.logger.Error("update route addresses: ", err)
}
} else {
err := t.tunIf.UpdateRouteOptions(tunOptions)
if err != nil {
t.logger.Error("update route addresses: ", err)
}
}
t.logger.Info("updated route addresses")
}
t.routeAddressSet = nil
t.routeExcludeAddressSet = nil
}

View File

@@ -26,6 +26,11 @@ func RegisterEndpoint(registry *endpoint.Registry) {
endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)
}
var (
_ adapter.Endpoint = (*Endpoint)(nil)
_ adapter.InterfaceUpdateListener = (*Endpoint)(nil)
)
type Endpoint struct {
endpoint.Adapter
ctx context.Context
@@ -113,6 +118,11 @@ func (w *Endpoint) Close() error {
return w.endpoint.Close()
}
func (w *Endpoint) InterfaceUpdated() {
w.endpoint.BindUpdate()
return
}
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error {
return w.router.PreMatch(adapter.InboundContext{
Inbound: w.Tag(),

View File

@@ -25,6 +25,11 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound)
}
var (
_ adapter.Endpoint = (*Endpoint)(nil)
_ adapter.InterfaceUpdateListener = (*Endpoint)(nil)
)
type Outbound struct {
outbound.Adapter
ctx context.Context
@@ -119,6 +124,11 @@ func (o *Outbound) Close() error {
return o.endpoint.Close()
}
func (o *Outbound) InterfaceUpdated() {
o.endpoint.BindUpdate()
return
}
func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch network {
case N.NetworkTCP:

13
release/config/openwrt.init Executable file → Normal file
View File

@@ -1,27 +1,26 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=99
PROG="/usr/bin/sing-box"
start_service() {
config_load "sing-box"
local enabled config_file working_directory
local log_stderr
local log_stdout log_stderr
config_get_bool enabled "main" "enabled" "0"
[ "$enabled" -eq "1" ] || return 0
config_get config_file "main" "conffile" "/etc/sing-box/config.json"
config_get working_directory "main" "workdir" "/usr/share/sing-box"
config_get_bool log_stdout "main" "log_stdout" "1"
config_get_bool log_stderr "main" "log_stderr" "1"
procd_open_instance
procd_set_param command "$PROG" run -c "$config_file" -D "$working_directory"
procd_set_param file "$config_file"
procd_swet_param command "$PROG" run -c "$conffile" -D "$workdir"
procd_set_param file "$conffile"
procd_set_param stderr "$log_stderr"
procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000"
sprocd_set_param limits nofile="1000000 1000000"
procd_set_param respawn
procd_close_instance
@@ -29,4 +28,4 @@ start_service() {
service_triggers() {
procd_add_reload_trigger "sing-box"
}
}

View File

@@ -1 +0,0 @@
/etc/sing-box/

View File

@@ -1,4 +0,0 @@
#!/bin/sh
[ -s ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_prerm $0 $@

View File

@@ -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_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
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
ExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure

View File

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

View File

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

View File

@@ -2,11 +2,9 @@ package route
import (
"context"
"errors"
"io"
"net"
"net/netip"
"os"
"sync"
"sync/atomic"
"time"
@@ -15,7 +13,6 @@ 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"
@@ -152,17 +149,16 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
} else {
originDestination = metadata.Destination
}
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
} else if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
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)
}
}
} else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination {
remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination)
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
}
var udpTimeout time.Duration
if metadata.UDPTimeout > 0 {
@@ -193,16 +189,14 @@ 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 net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
var (
sourceReader io.Reader = source
destinationWriter io.Writer = destination
)
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
var readCounters, writeCounters []N.CountFunc
for {
sourceReader, readCounters = N.UnwrapCountReader(sourceReader, readCounters)
destinationWriter, writeCounters = N.UnwrapCountWriter(destinationWriter, writeCounters)
if cachedSrc, isCached := sourceReader.(N.CachedReader); isCached {
source, readCounters = N.UnwrapCountReader(source, readCounters)
destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters)
if cachedSrc, isCached := source.(N.CachedReader); isCached {
cachedBuffer := cachedSrc.ReadCached()
if cachedBuffer != nil {
dataLen := cachedBuffer.Len()
@@ -212,7 +206,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn,
if done.Swap(true) {
onClose(err)
}
common.Close(source, destination)
common.Close(originSource, originDestination)
if !direction {
m.logger.ErrorContext(ctx, "connection upload payload: ", err)
} else {
@@ -231,35 +225,20 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn,
}
break
}
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)
_, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
if err != nil {
common.Close(source, destination)
common.Close(originDestination)
} else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex {
err = duplexDst.CloseWrite()
if err != nil {
common.Close(source, destination)
common.Close(originSource, originDestination)
}
} else {
destination.Close()
common.Close(originDestination)
}
if done.Swap(true) {
onClose(err)
common.Close(source, destination)
common.Close(originSource, originDestination)
}
if !direction {
if err == nil {
@@ -280,42 +259,16 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn,
}
}
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 err == nil {
m.logger.DebugContext(ctx, "packet upload finished")
} else if E.IsClosedOrCanceled(err) {
if E.IsClosedOrCanceled(err) {
m.logger.TraceContext(ctx, "packet upload closed")
} else {
m.logger.DebugContext(ctx, "packet upload closed: ", err)
}
} else {
if err == nil {
m.logger.DebugContext(ctx, "packet download finished")
} else if E.IsClosedOrCanceled(err) {
if E.IsClosedOrCanceled(err) {
m.logger.TraceContext(ctx, "packet download closed")
} else {
m.logger.DebugContext(ctx, "packet download closed: ", err)

View File

@@ -354,18 +354,6 @@ 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()
@@ -426,7 +414,15 @@ func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interfa
}
}
r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", "))
r.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)
}
}
}
if !r.started {
return

View File

@@ -33,18 +33,7 @@ import (
// Deprecated: use RouteConnectionEx instead.
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
done := make(chan interface{})
err := r.routeConnection(ctx, conn, metadata, N.OnceClose(func(it error) {
close(done)
}))
if err != nil {
return err
}
select {
case <-done:
case <-r.ctx.Done():
}
return nil
return r.routeConnection(ctx, conn, metadata, nil)
}
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
@@ -60,6 +49,10 @@ 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 {
@@ -136,8 +129,8 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
for _, buffer := range buffers {
conn = bufio.NewCachedConn(conn, buffer)
}
for _, tracker := range r.trackers {
conn = tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
if r.tracker != nil {
conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
}
if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler {
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
@@ -148,10 +141,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
}
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
done := make(chan interface{})
err := r.routePacketConnection(ctx, conn, metadata, N.OnceClose(func(it error) {
close(done)
}))
err := r.routePacketConnection(ctx, conn, metadata, nil)
if err != nil {
conn.Close()
if E.IsClosedOrCanceled(err) {
@@ -160,10 +150,6 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
r.logger.ErrorContext(ctx, err)
}
}
select {
case <-done:
case <-r.ctx.Done():
}
return nil
}
@@ -182,6 +168,9 @@ 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 {
@@ -251,8 +240,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination)
N.PutPacketBuffer(buffer)
}
for _, tracker := range r.trackers {
conn = tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
if r.tracker != nil {
conn = r.tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
}
if metadata.FakeIP {
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
@@ -351,7 +340,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, nil)
}, inputConn, inputPacketConn)
if newErr != nil {
fatalErr = newErr
return
@@ -451,7 +440,7 @@ match:
switch action := currentRule.Action().(type) {
case *rule.RuleActionSniff:
if !preMatch {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn)
if newErr != nil {
fatalErr = newErr
return
@@ -482,21 +471,32 @@ 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, inputBuffers []*buf.Buffer,
inputConn net.Conn, inputPacketConn N.PacketConn,
) (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 metadata.Protocol != "" {
r.logger.DebugContext(ctx, "duplicate sniff skipped")
return
}
if inputConn != nil {
} else if inputConn != nil {
sniffBuffer := buf.NewPacket()
var streamSniffers []sniff.StreamSniffer
if len(action.StreamSniffers) > 0 {
@@ -515,7 +515,6 @@ func (r *Router) actionSniff(
ctx,
metadata,
inputConn,
inputBuffers,
sniffBuffer,
action.Timeout,
streamSniffers...,
@@ -542,10 +541,6 @@ 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()
@@ -577,7 +572,10 @@ func (r *Router) actionSniff(
return
}
} else {
if len(packetBuffers) > 0 || metadata.PacketSniffError != nil {
if !metadata.Destination.Addr.IsGlobalUnicast() {
metadata.Destination = destination
}
if len(packetBuffers) > 0 {
err = sniff.PeekPacket(
ctx,
metadata,
@@ -610,9 +608,7 @@ func (r *Router) actionSniff(
Destination: destination,
}
packetBuffers = append(packetBuffers, packetBuffer)
metadata.PacketSniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols
if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(packetBuffers) == 0 {
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue
}

View File

@@ -64,7 +64,7 @@ type Router struct {
fakeIPStore adapter.FakeIPStore
processSearcher process.Searcher
pauseManager pause.Manager
trackers []adapter.ConnectionTracker
tracker adapter.ConnectionTracker
platformInterface platform.Interface
needWIFIState bool
started bool
@@ -484,13 +484,6 @@ 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
}
@@ -511,8 +504,8 @@ func (r *Router) Rules() []adapter.Rule {
return r.rules
}
func (r *Router) AppendTracker(tracker adapter.ConnectionTracker) {
r.trackers = append(r.trackers, tracker)
func (r *Router) SetTracker(tracker adapter.ConnectionTracker) {
r.tracker = tracker
}
func (r *Router) ResetNetwork() {

View File

@@ -162,24 +162,6 @@ 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")
}
@@ -279,9 +261,6 @@ 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()

View File

@@ -59,7 +59,7 @@ func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultH
}
func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool {
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0
}
func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {

View File

@@ -5,7 +5,6 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -27,16 +26,14 @@ 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
callbackAccess sync.Mutex
callbacks list.List[adapter.RuleSetUpdateCallback]
refs atomic.Int32
ctx context.Context
logger logger.Logger
tag string
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
fileFormat string
watcher *fswatch.Watcher
refs atomic.Int32
}
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
@@ -55,12 +52,13 @@ func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.R
return nil, err
}
} else {
filePath := filemanager.BasePath(ctx, options.LocalOptions.Path)
filePath, _ = filepath.Abs(filePath)
err := ruleSet.reloadFile(filePath)
err := ruleSet.reloadFile(filemanager.BasePath(ctx, options.LocalOptions.Path))
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) {
@@ -143,12 +141,6 @@ 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
}
@@ -181,15 +173,10 @@ func (s *LocalRuleSet) Cleanup() {
}
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
s.callbackAccess.Lock()
defer s.callbackAccess.Unlock()
return s.callbacks.PushBack(callback)
return nil
}
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 {

View File

@@ -33,23 +33,23 @@ import (
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
type RemoteRuleSet struct {
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
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
}
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,
outbound: service.FromContext[adapter.OutboundManager](ctx),
logger: logger,
options: options,
updateInterval: updateInterval,
pauseManager: service.FromContext[pause.Manager](ctx),
ctx: ctx,
cancel: cancel,
outboundManager: 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.outbound.Outbound(s.options.RemoteOptions.DownloadDetour)
outbound, loaded := s.outboundManager.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.outbound.Default()
dialer = s.outboundManager.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.fetch(ctx, startContext)
err := s.fetchOnce(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.fetch(s.ctx, nil)
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 {
@@ -211,21 +211,18 @@ func (s *RemoteRuleSet) loopUpdate() {
case <-s.ctx.Done():
return
case <-s.updateTicker.C:
s.updateOnce()
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
}
}
}
}
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 {
func (s *RemoteRuleSet) fetchOnce(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 {
@@ -289,7 +286,7 @@ func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPSta
}
s.lastUpdated = time.Now()
if s.cacheFile != nil {
err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedBinary{
err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{
LastUpdated: s.lastUpdated,
Content: content,
LastEtag: s.lastEtag,
@@ -304,10 +301,8 @@ func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPSta
func (s *RemoteRuleSet) Close() error {
s.rules = nil
s.updateTicker.Stop()
s.cancel()
if s.updateTicker != nil {
s.updateTicker.Stop()
}
return nil
}

View File

@@ -62,6 +62,10 @@ func isProcessDNSRule(rule option.DefaultDNSRule) bool {
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
}
func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool {
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0
}
func notPrivateNode(code string) bool {
return code != "private"
}

View File

@@ -13,7 +13,7 @@ require (
github.com/docker/go-connections v0.5.0
github.com/gofrs/uuid/v5 v5.3.0
github.com/sagernet/quic-go v0.48.2-beta.1
github.com/sagernet/sing v0.6.0-beta.12
github.com/sagernet/sing v0.6.0-beta.10
github.com/sagernet/sing-dns v0.4.0-beta.2
github.com/sagernet/sing-quic v0.4.0-beta.4
github.com/sagernet/sing-shadowsocks v0.2.7

View File

@@ -146,8 +146,8 @@ github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
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 v0.6.0-beta.10 h1:DqeP9fcXd3x9i6uJMwAlIaCFmeLBDNS3I6LuttBEP/Y=
github.com/sagernet/sing v0.6.0-beta.10/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=

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