Compare commits

...

81 Commits

Author SHA1 Message Date
世界
49498f6439 Bump version 2025-04-18 08:54:40 +08:00
世界
144a890c71 release: Add openwrt packages 2025-04-18 08:54:40 +08:00
世界
afb4993445 Fix urltest outbound 2025-04-18 08:54:40 +08:00
世界
4c9455b944 Fix wireguard endpoint 2025-04-18 08:54:40 +08:00
世界
5fdc051a08 Fix override_port in direct inbound 2025-04-16 17:04:13 +08:00
世界
cb68a40c43 documentation: Update actual behaviors of auto_redirect and strict_route 2025-04-12 13:06:16 +08:00
纳西妲 · Nahida
023218e6e7 Fix build will fail when use space to split each tag 2025-04-12 13:06:16 +08:00
世界
2a24b94b8d Minor fixes 2025-04-12 13:06:15 +08:00
世界
c6531cf184 Fix NTP service 2025-04-12 13:06:15 +08:00
世界
d4fa0ed349 Improve auto redirect 2025-04-12 13:06:10 +08:00
世界
10874d2dc4 Bump version 2025-04-08 14:34:09 +08:00
Fei1Yang
5adaf1ac75 Mark config file as noreplace for rpm 2025-04-08 14:21:08 +08:00
世界
9668ea69b8 Fix windows process searcher 2025-04-08 14:16:27 +08:00
testing
ae9bc7acf1 documentation: Fix typo
Signed-off-by: testing <58134720+testing765@users.noreply.github.com>
2025-04-08 14:16:23 +08:00
世界
594ee480a2 option: Fix listable 2025-04-08 14:16:23 +08:00
世界
a15b5a2463 Fix no_drop not work 2025-04-08 14:16:23 +08:00
Mahdi
991e755789 Fix conn copy 2025-04-08 14:16:22 +08:00
世界
97d41ffde8 Improve pause management 2025-04-08 14:16:22 +08:00
世界
24af0766ac Fix uTP sniffer 2025-04-08 14:16:22 +08:00
世界
af17eaa537 Improve sniffer 2025-04-08 14:16:22 +08:00
世界
3adc10a797 Fix hysteria2 close 2025-04-08 14:16:22 +08:00
xchacha20-poly1305
5eeef6b28e Fix multiple trackers 2025-04-08 14:16:22 +08:00
世界
f4c29840c3 Fix DNS sniffer 2025-03-31 20:45:04 +08:00
世界
47fc3ebda4 Add duplicate tag check 2025-03-29 23:10:22 +08:00
世界
9774a659b0 Fix DoQ / truncate DNS message 2025-03-29 17:41:22 +08:00
世界
2e4a6de4e7 release: Fix read tag 2025-03-27 20:30:57 +08:00
世界
a530e424e9 Bump version 2025-03-27 18:17:39 +08:00
世界
0bfd487ee9 Fix udpnat2 handler again 2025-03-27 18:17:39 +08:00
世界
6aae834493 release: Fix workflow 2025-03-27 18:17:39 +08:00
世界
f56131f38e Make linter happy 2025-03-24 20:38:42 +08:00
世界
273a11d550 Fix crash on udpnat2 handler 2025-03-24 18:14:32 +08:00
世界
ae8ce75e41 Fix websocket crash 2025-03-24 17:44:14 +08:00
世界
d6d94b689f release: Replace goreleaser build with scripts 2025-03-24 13:48:37 +08:00
世界
30d785f1ee release: Use fake goreleaser key 2025-03-21 22:25:51 +08:00
世界
db5ec3cdfc Fix connectionCopyEarly 2025-03-21 10:51:16 +08:00
世界
9aca54d039 Fix socks5 UDP 2025-03-16 14:46:44 +08:00
世界
d55d5009c2 Fix processing multiple sniffs 2025-03-16 09:21:54 +08:00
世界
4f3ee61104 Fix copy early conn 2025-03-15 08:09:04 +08:00
世界
96eb98c00a Fix httpupgrade crash 2025-03-14 17:17:28 +08:00
世界
68ce9577c6 Fix context in v2ray http transports 2025-03-14 17:07:17 +08:00
世界
3ae036e997 Downgrade goreleaser to stable since nfpm fixed 2025-03-13 18:53:19 +08:00
世界
5da2d1d470 release: Fix goreleaser version 2025-03-12 16:15:50 +08:00
世界
8e2baf40f1 Bump version 2025-03-11 20:18:34 +08:00
世界
c24c40dfee platform: Fix android start 2025-03-11 20:18:34 +08:00
世界
32e52ce1ed Fix udp nat for fakeip 2025-03-11 19:09:27 +08:00
世界
ed46438359 release: Use nightly goreleaser to fix rpm bug 2025-03-11 13:29:08 +08:00
世界
0b5490d5a3 Fix resolve domain for WireGuard 2025-03-11 12:02:25 +08:00
Tal Rasha
2d73ef511d Fix grpclite memory leak
Co-authored-by: talrasha007 <talrasha007@gmail.om>
2025-03-10 14:48:02 +08:00
Mahdi
63e6c85f6f Fix shadowsocks UoT 2025-03-10 14:47:59 +08:00
世界
8946a6d2d0 release: Use latest goreleaser 2025-03-09 15:27:04 +08:00
世界
d3132645fb documentation: Fix description of the UoT protocol 2025-03-09 15:26:42 +08:00
世界
373f158fe0 Fix download external ui with query params 2025-03-09 15:26:36 +08:00
世界
ce36835fab Fix override destination 2025-03-09 15:25:06 +08:00
世界
619fa671d7 Skip binding to the default interface as it will fail on some Android devices 2025-02-26 07:25:35 +08:00
世界
eb07c7a79e Bump version 2025-02-24 07:27:55 +08:00
Gavin Luo
7eb3535094 release: Fix systemd permissions 2025-02-24 07:27:55 +08:00
世界
93b68312cf platform: Add update WIFI state func 2025-02-23 08:35:30 +08:00
世界
97ce666e43 Fix http.FileServer short write 2025-02-23 08:35:30 +08:00
世界
4000e1e66d release: Fix update android version 2025-02-23 08:35:30 +08:00
世界
270740e859 Fix crash on route address set update 2025-02-23 08:35:30 +08:00
世界
6cad142cfe Bump Go to go1.24 2025-02-23 08:35:30 +08:00
世界
093013687c Fix sniff QUIC hidden in three or more packets 2025-02-18 18:14:59 +08:00
世界
ff31c469a0 Override version 2025-02-11 15:55:15 +08:00
世界
fbe390268c Bump version 2025-02-11 01:32:14 +08:00
世界
07ac01dcb7 platform: Update NDK to r28 2025-02-11 01:32:14 +08:00
ReleTor
badfdb62cd documentation: Fixes 2025-02-11 01:32:14 +08:00
printfer
986a410b30 documentation: Fix migration links 2025-02-11 01:32:14 +08:00
世界
9db2d58545 Fix override address 2025-02-11 01:32:14 +08:00
世界
4eed46ac59 Fix respond ICMP echo 2025-02-10 15:12:10 +08:00
世界
abc38d1dab Fix udpnat2 crash 2025-02-10 15:11:26 +08:00
世界
8d6c4f1289 release: Skip testflight when another build in review 2025-02-06 12:02:47 +08:00
世界
a2d40eb8b8 Fix override UDP destination 2025-02-06 11:20:35 +08:00
世界
17b502bb4b Update dependencies 2025-02-06 09:08:52 +08:00
世界
a0d4421085 Update quic-go to v0.49.0 2025-02-06 08:50:21 +08:00
世界
0d443072d1 Fix panic in auto-redirect initialize 2025-02-06 08:49:25 +08:00
世界
c9fb99b799 Fix missing ENOTCONN in IsClosed check 2025-02-06 08:48:49 +08:00
世界
92d245ad04 Bump version 2025-02-05 09:59:52 +08:00
世界
0908627297 Fix crash on remote rule-set stop 2025-02-05 08:58:10 +08:00
世界
7f79458b4f Minor updates 2025-02-01 19:49:33 +08:00
世界
9b4c11ba95 Fix rule-set not closed 2025-02-01 19:49:33 +08:00
世界
27c31eac5d Fix local rule-set not updated 2025-02-01 19:42:21 +08:00
92 changed files with 1427 additions and 693 deletions

30
.fpm_openwrt Normal file
View File

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

21
.fpm_systemd Normal file
View File

@@ -0,0 +1,21 @@
-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 Executable file
View File

@@ -0,0 +1,28 @@
#!/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.23
go-version: ^1.24
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --nightly
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
@@ -69,141 +69,206 @@ jobs:
strategy:
matrix:
include:
- name: linux_386
goos: linux
goarch: 386
- name: linux_amd64
goos: linux
goarch: amd64
- name: linux_arm64
goos: linux
goarch: arm64
- name: linux_arm
goos: linux
goarch: arm
goarm: 6
- name: linux_arm_v7
goos: linux
goarch: arm
goarm: 7
- name: linux_s390x
goos: linux
goarch: s390x
- name: linux_riscv64
goos: linux
goarch: riscv64
- name: linux_mips64le
goos: linux
goarch: mips64le
- name: windows_amd64
goos: windows
goarch: amd64
require_legacy_go: true
- name: windows_386
goos: windows
goarch: 386
require_legacy_go: true
- name: windows_arm64
goos: windows
goarch: arm64
- name: darwin_arm64
goos: darwin
goarch: arm64
- name: darwin_amd64
goos: darwin
goarch: amd64
require_legacy_go: true
- name: android_arm64
goos: android
goarch: arm64
- name: android_arm
goos: android
goarch: arm
goarm: 7
- name: android_amd64
goos: android
goarch: amd64
- name: android_386
goos: android
goarch: 386
- { 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" }
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
if: matrix.legacy_go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Cache legacy Go
if: matrix.require_legacy_go
id: cache-legacy-go
uses: actions/cache@v4
go-version: ~1.20
- name: Setup Go
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5
with:
path: |
~/go/go1.20.14
key: go120
- name: Setup legacy Go
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |-
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
tar -xzf go1.20.14.linux-amd64.tar.gz
mv go $HOME/go/go1.20.14
go-version: ^1.24
- name: Setup Android NDK
if: matrix.goos == 'android'
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta2
ndk-version: r28
local-cache: true
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: 2.5.1
install-only: true
- name: Extract signing key
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
TAGS="${TAGS},with_ech"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
if: matrix.goos != 'android'
run: |-
goreleaser release --clean --split
if: matrix.os != 'android'
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOPATH: ${{ env.HOME }}/go
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
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.goos == 'android'
run: |-
if: matrix.os == 'android'
run: |
set -xeuo pipefail
go install -v ./cmd/internal/build
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
export CC='${{ matrix.ndk }}-clang'
export CXX="${CC}++"
mkdir -p dist
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
BUILD_GOOS: ${{ matrix.goos }}
BUILD_GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
CGO_ENABLED: "1"
BUILD_GOOS: ${{ matrix.os }}
BUILD_GOARCH: ${{ matrix.arch }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Set name
run: |-
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
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.name }}
path: 'dist'
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
build_android:
name: Build Android
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
@@ -219,12 +284,12 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
go-version: ^1.24
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta3
ndk-version: r28
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
@@ -256,9 +321,16 @@ jobs:
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Build
- name: Update version
if: github.event_name == 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci
- name: Update nightly version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci --nightly
- name: Build
run: |-
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
@@ -268,13 +340,11 @@ jobs:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare upload
if: github.event_name == 'workflow_dispatch'
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
mkdir -p dist
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
- name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: binary-android-apks
@@ -294,12 +364,12 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
go-version: ^1.24
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: r28-beta3
ndk-version: r28
- name: Setup OpenJDK
run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
@@ -392,7 +462,7 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.23
go-version: ^1.24
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
@@ -432,19 +502,19 @@ jobs:
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILES_PATH"
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
xcrun notarytool store-credentials "notarytool-password" \
--key $ASC_KEY_PATH \
--key-id $ASC_KEY_ID \
--issuer $ASC_KEY_ISSUER_ID
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
@@ -520,10 +590,10 @@ jobs:
cd "${{ matrix.archive }}"
zip -r SFM.dSYMs.zip dSYMs
popd
mkdir -p dist/release
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
mkdir -p dist
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
- name: Upload image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
@@ -544,12 +614,6 @@ jobs:
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: 2.5.1
install-only: true
- name: Cache ghr
uses: actions/cache@v4
id: cache-ghr
@@ -574,26 +638,17 @@ jobs:
with:
path: dist
merge-multiple: true
- name: Merge builds
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
run: |-
goreleaser continue --merge --skip publish
mkdir -p dist/release
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: Upload builds
if: ${{ env.PUBLISHED == 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Replace builds
if: ${{ env.PUBLISHED != 'false' }}
run: |-
export PATH="$PATH:$HOME/go/bin"
ghr --replace -p 5 "v${VERSION}" dist/release
ghr --replace -p 5 "v${VERSION}" dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@@ -1,13 +1,22 @@
name: Release to Linux repository
name: Build Linux Packages
on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
release:
types:
- published
jobs:
build:
calculate_version:
name: Calculate version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@@ -16,23 +25,161 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.23
- name: Extract signing key
go-version: ^1.24
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
build:
name: Build binary
runs-on: ubuntu-latest
needs:
- calculate_version
strategy:
matrix:
include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: "386", debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
local-cache: true
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
TAGS="${TAGS},with_ech"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set mtime
run: |-
TZ=UTC touch -t '197001010000' dist/sing-box
- name: Set name
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
run: |-
echo "NAME=sing-box" >> "$GITHUB_ENV"
- name: Set beta name
if: contains(needs.calculate_version.outputs.version, '-')
run: |-
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
- name: Set version
run: |-
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}"
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
- name: Package DEB
if: matrix.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y debsigs
fpm -t deb \
--name "${NAME}" \
-v "$PKG_VERSION" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Publish release
uses: goreleaser/goreleaser-action@v6
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
fpm -t rpm \
--name "${NAME}" \
-v "$PKG_VERSION" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
distribution: goreleaser-pro
version: latest
args: release -f .goreleaser.fury.yaml --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
upload:
name: Upload builds
runs-on: ubuntu-latest
needs:
- calculate_version
- build
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Publish packages
run: |-
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/

View File

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

View File

@@ -95,10 +95,12 @@ archives:
builds:
- main
- android
format: tar.gz
formats:
- tar.gz
format_overrides:
- goos: windows
format: zip
formats:
- zip
wrap_in_directory: true
files:
- LICENSE
@@ -128,7 +130,7 @@ nfpms:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
type: "config|noreplace"
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box

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

View File

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

View File

@@ -25,6 +25,7 @@ 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
SetTracker(tracker ConnectionTracker)
AppendTracker(tracker ConnectionTracker)
ResetNetwork()
}

17
box.go
View File

@@ -165,7 +165,15 @@ func New(options Options) (*Box, error) {
} else {
tag = F.ToString(i)
}
err = endpointManager.Create(ctx,
endpointCtx := ctx
if tag != "" {
// TODO: remove this
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = endpointManager.Create(
endpointCtx,
router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag,
@@ -183,7 +191,8 @@ func New(options Options) (*Box, error) {
} else {
tag = F.ToString(i)
}
err = inboundManager.Create(ctx,
err = inboundManager.Create(
ctx,
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
tag,
@@ -248,7 +257,7 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "create clash-server")
}
router.SetTracker(clashServer)
router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
services = append(services, clashServer)
}
@@ -258,7 +267,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create v2ray-server")
}
if v2rayServer.StatsService() != nil {
router.SetTracker(v2rayServer.StatsService())
router.AppendTracker(v2rayServer.StatsService())
services = append(services, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}

View File

@@ -5,6 +5,7 @@ import (
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/sagernet/asc-go/asc"
@@ -194,6 +195,10 @@ func publishTestflight(ctx context.Context) error {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil {
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
log.Error(err)
break
}
return err
}
}

View File

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

View File

@@ -5,40 +5,49 @@ import (
"os"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing-box/log"
)
var nightly bool
var (
flagRunInCI bool
flagRunNightly bool
)
func init() {
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
flag.Parse()
if nightly {
version, err := build_shared.ReadTagVersionRev()
var (
versionStr string
err error
)
if flagRunNightly {
var version badversion.Version
version, err = build_shared.ReadTagVersion()
if err == nil {
versionStr = version.String()
}
} else {
versionStr, err = build_shared.ReadTag()
}
if flagRunInCI {
if err != nil {
log.Fatal(err)
}
var versionStr string
if version.PreReleaseIdentifier != "" {
versionStr = version.VersionString() + "-nightly"
} else {
version.Patch++
versionStr = version.VersionString() + "-nightly"
}
err = setGitHubEnv("version", versionStr)
if err != nil {
log.Fatal(err)
}
} else {
tag, err := build_shared.ReadTag()
if err != nil {
log.Error(err)
os.Stdout.WriteString("unknown\n")
} else {
os.Stdout.WriteString(tag + "\n")
os.Stdout.WriteString(versionStr + "\n")
}
}
}

View File

@@ -13,10 +13,14 @@ import (
"github.com/sagernet/sing/common"
)
var flagRunInCI bool
var (
flagRunInCI bool
flagRunNightly bool
)
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
@@ -46,21 +50,23 @@ func main() {
switch propPair[0] {
case "VERSION_NAME":
if propPair[1] != newVersion {
log.Info("updated version from ", propPair[1], " to ", newVersion)
versionUpdated = true
propPair[1] = newVersion
log.Info("updated version to ", newVersion)
}
case "GO_VERSION":
if propPair[1] != runtime.Version() {
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
goVersionUpdated = true
propPair[1] = runtime.Version()
log.Info("updated Go version to ", runtime.Version())
}
}
}
if !(versionUpdated || goVersionUpdated) {
log.Info("version not changed")
return
} else if flagRunInCI && !flagRunNightly {
log.Fatal("version changed, commit changes first.")
}
for _, propPair := range propsList {
switch propPair[0] {

View File

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

View File

@@ -18,6 +18,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface")
}
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -31,7 +32,9 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr)
if err != nil {
select {
@@ -89,6 +92,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface")
}
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@@ -103,7 +107,9 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr)
if err != nil {
select {
@@ -149,10 +155,13 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, E.New("no available network interface")
}
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
var errors []error
for _, primaryInterface := range primaryInterfaces {
perNetListener := listener
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
if defaultInterface == nil || primaryInterface.Index != defaultInterface.Index {
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
}
conn, err := perNetListener.ListenPacket(ctx, network, addr)
if err == nil {
return conn, nil
@@ -161,7 +170,9 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
}
for _, fallbackInterface := range fallbackInterfaces {
perNetListener := listener
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
if defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index {
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
}
conn, err := perNetListener.ListenPacket(ctx, network, addr)
if err == nil {
return conn, nil

View File

@@ -124,14 +124,6 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
for i := 0; i < n; i++ {
row := b[4+itemSize*i : 4+itemSize*(i+1)]
if s.tcpState >= 0 {
tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
// MIB_TCP_STATE_ESTAB, only check established connections for TCP
if tcpState != 5 {
continue
}
}
// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
// this field can be illustrated as follows depends on different machine endianess:
// little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB)
@@ -144,7 +136,7 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
if ip != srcIP && (!srcIP.IsUnspecified()) {
continue
}

View File

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

View File

@@ -71,3 +71,19 @@ func TestSniffUDPTracker(t *testing.T) {
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
}
}
func TestSniffNotUTP(t *testing.T) {
t.Parallel()
packets := []string{
"0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
}
for _, pkt := range packets {
pkt, err := hex.DecodeString(pkt)
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.UTP(context.TODO(), &metadata, pkt)
require.Error(t, err)
}
}

View File

@@ -5,14 +5,11 @@ import (
"encoding/binary"
"io"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/task"
E "github.com/sagernet/sing/common/exceptions"
mDNS "github.com/miekg/dns"
)
@@ -21,22 +18,16 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
var length uint16
err := binary.Read(reader, binary.BigEndian, &length)
if err != nil {
return os.ErrInvalid
return E.Cause1(ErrNeedMoreData, err)
}
if length == 0 {
return os.ErrInvalid
}
buffer := buf.NewSize(int(length))
defer buffer.Release()
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
var readTask task.Group
readTask.Append0(func(ctx context.Context) error {
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen()))
})
err = readTask.Run(readCtx)
cancel()
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
}
@@ -47,9 +38,6 @@ func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, pack
if err != nil {
return err
}
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
return os.ErrInvalid
}
metadata.Protocol = C.ProtocolDNS
return nil
}

23
common/sniff/dns_test.go Normal file
View File

@@ -0,0 +1,23 @@
package sniff_test
import (
"context"
"encoding/hex"
"testing"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/stretchr/testify/require"
)
func TestSniffDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("740701000001000000000000012a06676f6f676c6503636f6d0000010001")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.DomainNameQuery(context.TODO(), &metadata, query)
require.NoError(t, err)
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
}

View File

@@ -3,10 +3,12 @@ package sniff
import (
std_bufio "bufio"
"context"
"errors"
"io"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/http"
)
@@ -14,7 +16,11 @@ import (
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
request, err := http.ReadRequest(std_bufio.NewReader(reader))
if err != nil {
return err
if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err
}
}
metadata.Protocol = C.ProtocolHTTP
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()

View File

@@ -20,8 +20,6 @@ import (
"golang.org/x/crypto/hkdf"
)
var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection")
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
reader := bytes.NewReader(packet)
typeByte, err := reader.ReadByte()
@@ -308,7 +306,7 @@ find:
metadata.Protocol = C.ProtocolQUIC
metadata.Client = C.ClientChromium
metadata.SniffContext = fragments
return ErrClientHelloFragmented
return E.Cause1(ErrNeedMoreData, err)
}
metadata.Domain = fingerprint.ServerName
for metadata.Client == "" {

View File

@@ -12,6 +12,26 @@ import (
"github.com/stretchr/testify/require"
)
func TestSniffQUICChromeNew(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("ca0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489ad89c322f75f9a383c90d126a0b21104cb519c2bb32e6a134e86896452e942b26c519b8c7ac9e4c99fae5e1f65cf08fb98443b30e4567932e8fb0789820d8f33037b59ac8113530258c9467dfb52489396dae01f099d28b234efa107fa411f2a1ffa2abe74988e03d662d4296024e95ce0fe1671724937157f77b84990478a2d4060676cf0827b4e8c600654111750414dafa0cccb332f3020c2922a015f445df5edc9c7d2d1ceea9fddcc9ff821c9183aa39a70da20fcc057579e1051c1c899148d6cf9d08b4919822082d040d1ce03ca4f216be6cb7ef03db6df0993ef1ccce5c8c648980554f41704526e1809d2545739f5872e75ec797db1c99f5682e2eda9363cb32aa367b7b363c782ddbacf874183cc15c8a2db068dd4093eebdd096ad33832a7939deb0a872279744f5a56dc001ba62fac973bf680f3b362bdd336add4dd102f462b773bf70bfce1921070a802a92025273a177186d1a643081b42175eb789ccddadb71033ef4feacbf6fd282ab622cf61669d73cda559e411c6ccdd8f003443b6933b7729b7a357aa4aa2fba0f365f829a4d497afb5dc2648a53bc9f3e786d955069d0a4781088a5463747dfe9958ea19ea444eae947ec6a67640955f710f93640084f3fbb8ad259b68dbc0ee0b7fab2d81bffd83ed8a6d33522dbfef43bec0a0fb4bdf1cb712dc4ced0680c0687fa240fd157baa232b1c84e14adce6421cf9270f9b3972f98fc67b344b8a4f1fb551e26f7f76d484ed9f8197f231dc5d9a44cc0ddce73d7f810a620851f4e97eb5037ab5135d7c3be5b80cc32d19910b8387aca64c93c02dc3e35238b78e6aff470722078982e58802844932b6041446bfdcc97ba640cbb86721bcd0f40f27b77aa6287ce5674ec1720134b9302875482c3269787e004b9edb483d44f326eef38c0e83cb46af96488c2e696bc2524567fb29c1e8edcd5a73615496d172d46a9d29e0505c0018b7bbb00165eca0389e09c4b1d73b6cc4a2f735a720650134a2e98e8105e20695cf231b92586237dfe0f99c897414e51c21627496276535f07abb53fb2b554376fe520fa45a3e944fd91dfe7a72aead08842b6b63d8edf861fb911954c83bd9a896eb9da4af5eff646455069d747facd4e77c254096843bff7c3e9031dbdf8dc37ea45f1122922fcbc322ec1378f3c7c1af0da62e1052e6210f1b23073f93a82d90e14cb20bc4501d487a1c848674d57a7c269b13590b3a99d8b8b4f6d0dfbd1d2cbbe7a32c0d5c84ae7ec438b0b19f3862d8fabaa828d06c7e3c6967405cd56a1ae90f38633e2ee0e3ecfca3df399fe12f029e0860a1a30da010300d0c94f0bf56091d00011488c1429928b21c739ebf50ba8be91116315d3173f6d2c56735722478c4d74392ba84d1727036b3d64e8c2263b0f33cb8086be587ca6b3940259c06afa2683868856529303ae12e91d7ca874568be7f2bfaa0656dfab0ed31ed90eaea10fb7f3433ec59a334abe6211d547fa0c825ac45d3691e749d15432008de83e9f6d98f368359137ae803d9189b3386f800c7c0cf4b615d1983cf82d9981a8105b60a80fe66c9b0d439b5ba153dd19e9e7483a01cf3b02b4597540b38e658d4eb8455e030b2bf2690bdd78c23f16fe5")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.NoError(t, err)
require.Equal(t, "www.google.com", metadata.Domain)
}
func TestSniffQUICChromium(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("c30000000108f40d654cc09b27f5000044d08a94548e57e43cc5483f129986187c432d58d46674830442988f869566a6e31e2ae37c9f7acbf61cc81621594fab0b3dfdc1635460b32389563dc8e74006315661cd22694114612973c1c45910621713a48b375854f095e8a77ccf3afa64e972f0f7f7002f50e0b014b1b146ea47c07fb20b73ad5587872b51a0b3fafdf1c4cf4fe6f8b112142392efa25d993abe2f42582be145148bdfe12edcd96c3655b65a4781b093e5594ba8e3ae5320f12e8314fc3ca374128cc43381046c322b964681ed4395c813b28534505118201459665a44b8f0abead877de322e9040631d20b05f15b81fa7ff785d4041aecc37c7e2ccdc5d1532787ce566517e8985fd5c200dbfd1e67bc255efaba94cfc07bb52fea4a90887413b134f2715b5643542aa897c6116486f428d82da64d2a2c1e1bdd40bd592558901a554b003d6966ac5a7b8b9413eddbf6ef21f28386c74981e3ce1d724c341e95494907626659692720c81114ca4acea35a14c402cfa3dc2228446e78dc1b81fa4325cf7e314a9cad6a6bdff33b3351dcba74eb15fae67f1227283aa4cdd64bcadf8f19358333f8549b596f4350297b5c65274565869d497398339947b9d3d064e5b06d39d34b436d8a41c1a3880de10bd26c3b1c5b4e2a49b0d4d07b8d90cd9e92bc611564d19ea8ec33099e92033caf21f5307dbeaa4708b99eb313bff99e2081ac25fd12d6a72e8335e0724f6718fe023cd0ad0d6e6a6309f09c9c391eec2bc08e9c3210a043c08e1759f354c121f6517fff4d6e20711a871e41285d48d930352fddffb92c96ba57df045ce99f8bfdfa8edc0969ce68a51e9fbb4f54b956d9df74a9e4af27ed2b27839bce1cffeca8333c0aaee81a570217442f9029ba8fedb84a2cf4be4d910982d891ea00e816c7fb98e8020e896a9c6fdd9106611da0a99dde18df1b7a8f6327acb1eed9ad93314451e48cb0dfb9571728521ca3db2ac0968159d5622556a55d51a422d11995b650949aaefc5d24c16080446dfc4fbc10353f9f93ce161ab513367bb89ab83988e0630b689e174e27bcfcc31996ee7b0bca909e251b82d69a28fee5a5d662e127508cd19dbbe5097b7d5b62a49203d66764197a527e472e2627e44a93d44177dace9d60e7d0e03305ddf4cfe47cdf2362e14de79ef46a6763ce696cd7854a48d9419a0817507a4713ffd4977b906d4f2b5fb6dbe1bd15bc505d5fea582190bf531a45d5ee026da8918547fd5105f15e5d061c7b0cf80a34990366ed8e91e13c2f0d85e5dad537298808d193cf54b7eaac33f10051f74cb6b75e52f81618c36f03d86aef613ba237a1a793ba1539938a38f62ccaf7bd5f6c5e0ce53cde4012fcf2b758214a0422d2faaa798e86e19d7481b42df2b36a73d287ff28c20cce01ce598771fec16a8f1f00305c06010126013a6c1de9f589b4e79d693717cd88ad1c42a2d99fa96617ba0bc6365b68e21a70ebc447904aa27979e1514433cfd83bfec09f137c747d47582cb63eb28f873fb94cf7a59ff764ddfbb687d79a58bb10f85949269f7f72c611a5e0fbb52adfa298ff060ec2eb7216fd7302ea8fb07798cbb3be25cb53ac8161aac2b5bbcfbcfb01c113d28bd1cb0333fb89ac82a95930f7abded0a2f5a623cc6a1f62bf3f38ef1b81c1e50a634f657dbb6770e4af45879e2fb1e00c742e7b52205c8015b5c0f5b1e40186ff9aa7288ab3e01a51fb87761f9bc6837082af109b39cc9f620")
@@ -20,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)

View File

@@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
)
@@ -15,7 +16,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktVersion uint8
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if tpktVersion != 0x03 {
return os.ErrInvalid
@@ -24,7 +25,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktReserved uint8
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if tpktReserved != 0x00 {
return os.ErrInvalid
@@ -33,7 +34,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktLength uint16
err = binary.Read(reader, binary.BigEndian, &tpktLength)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if tpktLength != 19 {
@@ -43,7 +44,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var cotpLength uint8
err = binary.Read(reader, binary.BigEndian, &cotpLength)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if cotpLength != 14 {
@@ -53,7 +54,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var cotpTpduType uint8
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if cotpTpduType != 0xE0 {
return os.ErrInvalid
@@ -61,13 +62,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
err = rw.SkipN(reader, 5)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
var rdpType uint8
err = binary.Read(reader, binary.BigEndian, &rdpType)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if rdpType != 0x01 {
return os.ErrInvalid
@@ -75,12 +76,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var rdpFlags uint8
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
var rdpLength uint8
err = binary.Read(reader, binary.BigEndian, &rdpLength)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if rdpLength != 8 {
return os.ErrInvalid

View File

@@ -3,12 +3,14 @@ package sniff
import (
"bytes"
"context"
"errors"
"io"
"net"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
)
@@ -18,6 +20,8 @@ type (
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
)
var ErrNeedMoreData = E.New("need more data")
func Skip(metadata *adapter.InboundContext) bool {
// skip server first protocols
switch metadata.Destination.Port {
@@ -34,12 +38,12 @@ func Skip(metadata *adapter.InboundContext) bool {
return false
}
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
if timeout == 0 {
timeout = C.ReadPayloadTimeout
}
deadline := time.Now().Add(timeout)
var errors []error
var sniffError error
for i := 0; ; i++ {
err := conn.SetReadDeadline(deadline)
if err != nil {
@@ -53,26 +57,32 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
}
return E.Cause(err, "read payload")
}
errors = nil
sniffError = nil
for _, sniffer := range sniffers {
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
return bytes.NewReader(it.Bytes())
})...)
err = sniffer(ctx, metadata, reader)
if err == nil {
return nil
}
errors = append(errors, err)
sniffError = E.Errors(sniffError, err)
}
if !errors.Is(err, ErrNeedMoreData) {
break
}
}
return E.Errors(errors...)
return sniffError
}
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
var errors []error
var sniffError []error
for _, sniffer := range sniffers {
err := sniffer(ctx, metadata, packet)
if err == nil {
return nil
}
errors = append(errors, err)
sniffError = append(sniffError, err)
}
return E.Errors(errors...)
return E.Errors(sniffError...)
}

View File

@@ -5,22 +5,26 @@ import (
"context"
"io"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
)
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
scanner := bufio.NewScanner(reader)
if !scanner.Scan() {
const sshPrefix = "SSH-2.0-"
bReader := bufio.NewReader(reader)
prefix, err := bReader.Peek(len(sshPrefix))
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
} else if string(prefix) != sshPrefix {
return os.ErrInvalid
}
fistLine := scanner.Text()
if !strings.HasPrefix(fistLine, "SSH-2.0-") {
return os.ErrInvalid
fistLine, _, err := bReader.ReadLine()
if err != nil {
return err
}
metadata.Protocol = C.ProtocolSSH
metadata.Client = fistLine[8:]
metadata.Client = string(fistLine)[8:]
return nil
}

View File

@@ -3,11 +3,13 @@ package sniff
import (
"context"
"crypto/tls"
"errors"
"io"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
)
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
@@ -23,5 +25,9 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
metadata.Domain = clientHello.ServerName
return nil
}
return err
if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err
}
}

View File

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

View File

@@ -89,16 +89,20 @@ 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)
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 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
}
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,6 +6,7 @@ import (
"net"
"os"
"strings"
"time"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -221,8 +222,12 @@ 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 GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
return GenerateKeyPair(nil, nil, timeFunc, info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -2,6 +2,50 @@
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:

View File

@@ -7,6 +7,10 @@ icon: material/apple
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
platform-specific function implementation, such as TUN transparent proxy implementation.
!!! failure ""
We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected).
## :material-graph: Requirements
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+

View File

@@ -211,6 +211,10 @@ 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"
@@ -235,22 +239,29 @@ Linux iproute2 rule start index generated by `auto_route`.
Only supported on Linux with `auto_route` enabled.
Automatically configure iptables/nftables to redirect connections.
Improve TUN routing and performance using nftables.
*In Android*
`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.
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
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,
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
*In Linux*:
`auto_redirect` also automatically inserts compatibility rules
into the OpenWrt fw4 table, i.e.
it will work on routers without any extra configuration.
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
#### auto_redirect_input_mark
!!! question "Since sing-box 1.10.0"
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
Connection input mark used by `auto_redirect`.
`0x2023` is used by default.
@@ -258,7 +269,7 @@ Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`
!!! question "Since sing-box 1.10.0"
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
Connection output mark used by `auto_redirect`.
`0x2024` is used by default.
@@ -269,17 +280,15 @@ Enforce strict routing rules when `auto_route` is enabled:
*In Linux*:
* Let unsupported network unreachable
* 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.
* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
*In Windows*:
* Add firewall rules to prevent DNS leak caused by
* Let unsupported network unreachable
* 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 applications (such as VirtualBox) from working properly in certain situations.
It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations.
#### route_address
@@ -367,8 +376,6 @@ 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,6 +215,10 @@ 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 起"
@@ -237,23 +241,24 @@ tun 接口的 IPv6 前缀。
!!! quote ""
仅支持 Linux且需要 `auto_route` 已启用。
仅支持 Linux且需要 `auto_route` 已启用。
自动配置 iptables/nftables 以重定向连接
通过使用 nftables 改善 TUN 路由和性能
*在 Android 中*
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy并避免了 TUN 和 Docker 桥接网络之间的冲突。
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
请注意,`auto_redirect` 也适用于 Android但由于缺少 `nftables``ip6tables`,仅执行简单的 IPv4 TCP 转发。
若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
*在 Linux 中*:
`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。
带有 `auto_redirect ``auto_route` 可以在路由器上按预期工作,**无需干预**
`route.default_mark``[dialOptions].routing_mark` 冲突
#### auto_redirect_input_mark
!!! question "自 sing-box 1.10.0 起"
`route_address_set``route_exclude_address_set` 使用的连接输入标记。
`auto_redirect` 使用的连接输入标记。
默认使用 `0x2023`
@@ -261,29 +266,25 @@ tun 接口的 IPv6 前缀。
!!! question "自 sing-box 1.10.0 起"
`route_address_set``route_exclude_address_set` 使用的连接输出标记。
`auto_redirect` 使用的连接输出标记。
默认使用 `0x2024`
#### strict_route
启用 `auto_route` 时执行严格的路由规则
启用 `auto_route`,强制执行严格的路由规则
*在 Linux 中*:
*在 Linux 中*
* 不支持的网络无法到达
* 使 ICMP 流量路由到 tun 而不是上游接口
* 将所有连接路由到 tun
* 使不支持的网络不可达。
* 出于历史遗留原因,当未启用 `strict_route``auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
*在 Windows 中*
*在 Windows 中*:
* 使不支持的网络不可达。
* 阻止 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
的 [普通多宿主 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在某些情况下无法正常工作。
它可能会使某些 Windows 应用程序(如 VirtualBox在某些情况下无法正常工作。
#### route_address
@@ -341,8 +342,6 @@ tun 接口的 IPv6 前缀。
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
不匹配的流量将绕过 sing-box 路由。
`route.default_mark``[dialOptions].routing_mark` 冲突。
=== "`auto_redirect` 未启用"

View File

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

View File

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

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

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,12 +31,11 @@ The protocol version, `1` or `2`.
### Application support
| Project | UoT v1 | UoT v2 |
|--------------|----------------------|-------------------------------------------------------------------------------------------------------------------|
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
| Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) |
| Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) |
| Shadowrocket | v2.2.12 (2022/08/13) | / |
| Project | UoT v1 | UoT v2 |
|--------------|----------------------|----------------------|
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) |
| Shadowrocket | v2.2.12 (2022/08/13) | / |
### Protocol details
@@ -50,7 +49,13 @@ The client requests the magic address to the upper layer proxy protocol to indic
|------|----------|-------|--------|----------|
| u8 | variable | u16be | u16be | variable |
**ATYP / address / port**: Uses the SOCKS address format.
**ATYP / address / port**: Uses the SOCKS address format, but with different address types:
| ATYP | Address type |
|--------|--------------|
| `0x00` | IPv4 Address |
| `0x01` | IPv6 Address |
| `0x02` | Domain Name |
#### Protocol version 2

View File

@@ -8,44 +8,56 @@ icon: material/package
=== ":material-debian: Debian / APT"
```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
```
```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
```
=== ":material-redhat: Redhat / DNF"
=== ":material-redhat: Redhat / DNF 5"
```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.)
```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
```
## :material-download-box: Manual Installation
=== ":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)
```
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-book-lock-open: Managed Installation

View File

@@ -9,22 +9,35 @@ icon: material/package
=== ":material-debian: Debian / APT"
```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
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
```
=== ":material-redhat: Redhat / DNF"
=== ":material-redhat: Redhat / DNF 5"
```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。
```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
```
## :material-download-box: 手动安装

View File

@@ -0,0 +1,97 @@
#!/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/inbound/listen/) /
[Listen Fields](/configuration/shared/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.SavedRuleSet {
var savedSet adapter.SavedRuleSet
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {
var savedSet adapter.SavedBinary
err := c.DB.View(func(t *bbolt.Tx) error {
bucket := c.bucket(t, bucketRuleSet)
if bucket == nil {
@@ -303,7 +303,7 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet {
return &savedSet
}
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error {
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {
return c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := c.createBucket(t, bucketRuleSet)
if err != nil {

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

View File

@@ -0,0 +1,18 @@
package clashapi
import "net/http"
type Dir http.Dir
func (d Dir) Open(name string) (http.File, error) {
file, err := http.Dir(d).Open(name)
if err != nil {
return nil, err
}
return &fileWrapper{file}, nil
}
// workaround for #2345 #2596
type fileWrapper struct {
http.File
}

View File

@@ -41,7 +41,6 @@ func (s *Server) downloadExternalUI() error {
} else {
downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
}
s.logger.Info("downloading external ui")
var detour adapter.Outbound
if s.externalUIDownloadDetour != "" {
outbound, loaded := s.outbound.Outbound(s.externalUIDownloadDetour)
@@ -53,6 +52,7 @@ func (s *Server) downloadExternalUI() error {
outbound := s.outbound.Default()
detour = outbound
}
s.logger.Info("downloading external ui using outbound/", detour.Type(), "[", detour.Tag(), "]")
httpClient := &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
@@ -71,15 +71,15 @@ func (s *Server) downloadExternalUI() error {
if response.StatusCode != http.StatusOK {
return E.New("download external ui failed: ", response.Status)
}
err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
err = s.downloadZIP(response.Body, s.externalUI)
if err != nil {
removeAllInDirectory(s.externalUI)
}
return err
}
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
tempFile, err := filemanager.CreateTemp(s.ctx, name)
func (s *Server) downloadZIP(body io.Reader, output string) error {
tempFile, err := filemanager.CreateTemp(s.ctx, "external-ui.zip")
if err != nil {
return err
}

View File

@@ -4,7 +4,6 @@ import (
"encoding/binary"
"net"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
)
@@ -18,19 +17,7 @@ func (c *CommandClient) ServiceReload() error {
if err != nil {
return err
}
var hasError bool
err = binary.Read(conn, binary.BigEndian, &hasError)
if err != nil {
return err
}
if hasError {
errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return err
}
return E.New(errorMessage)
}
return nil
return readError(conn)
}
func (s *CommandServer) handleServiceReload(conn net.Conn) error {
@@ -55,19 +42,7 @@ func (c *CommandClient) ServiceClose() error {
if err != nil {
return err
}
var hasError bool
err = binary.Read(conn, binary.BigEndian, &hasError)
if err != nil {
return nil
}
if hasError {
errorMessage, err := varbin.ReadValue[string](conn, binary.BigEndian)
if err != nil {
return nil
}
return E.New(errorMessage)
}
return nil
return readError(conn)
}
func (s *CommandServer) handleServiceClose(conn net.Conn) error {

View File

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

View File

@@ -40,7 +40,7 @@ type BoxService struct {
clashServer adapter.ClashServer
pauseManager pause.Manager
servicePauseFields
iOSPauseFields
}
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {

View File

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

View File

@@ -7,11 +7,13 @@ var (
type Locale struct {
// deprecated messages for graphical clients
Locale string
DeprecatedMessage string
DeprecatedMessageNoLink string
}
var defaultLocal = &Locale{
Locale: "en_US",
DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.",
DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.",
}

View File

@@ -4,6 +4,7 @@ var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么
func init() {
localeRegistry["zh_CN"] = &Locale{
Locale: "zh_CN",
DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers,
DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers,
}

34
go.mod
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.1.0
github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/render v1.0.3
github.com/gofrs/uuid/v5 v5.3.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/gofrs/uuid/v5 v5.3.2
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
github.com/libdns/alidns v1.0.3
github.com/libdns/cloudflare v0.1.1
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
github.com/mholt/acmez v1.2.0
github.com/miekg/dns v1.1.62
github.com/miekg/dns v1.1.63
github.com/oschwald/maxminddb-golang v1.12.0
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
@@ -24,30 +24,30 @@ require (
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.4
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.48.2-beta.1
github.com/sagernet/quic-go v0.49.0-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.0-beta.12
github.com/sagernet/sing-dns v0.4.0-beta.2
github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.4.0-beta.4
github.com/sagernet/sing v0.6.6
github.com/sagernet/sing-dns v0.4.1
github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.1
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2
github.com/sagernet/sing-tun v0.6.0-beta.8
github.com/sagernet/sing-vmess v0.2.0-beta.2
github.com/sagernet/sing-shadowtls v0.2.0
github.com/sagernet/sing-tun v0.6.4
github.com/sagernet/sing-vmess v0.2.0
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/utls v1.6.7
github.com/sagernet/wireguard-go v0.0.1-beta.5
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.31.0
golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/mod v0.20.0
golang.org/x/net v0.31.0
golang.org/x/sys v0.28.0
golang.org/x/net v0.34.0
golang.org/x/sys v0.30.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0

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.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -31,8 +31,8 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
@@ -47,8 +47,8 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
@@ -74,12 +74,12 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
@@ -114,29 +114,31 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg=
github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+yfK8DQ8Jk=
github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8WHNsRs71b3Lt1+p/U=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s=
github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY=
github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g=
github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7 h1:ZJauxLmH12Gzv3nucfjsSBQw9UA8t7Sxu8pYHBSP2TU=
github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing 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-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw=
github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs=
github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk=
github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
github.com/sagernet/sing-tun v0.6.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/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
@@ -152,8 +154,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
@@ -172,16 +174,16 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -191,10 +193,10 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=

View File

@@ -20,12 +20,16 @@ type ID struct {
}
func ContextWithNewID(ctx context.Context) context.Context {
return context.WithValue(ctx, (*idKey)(nil), ID{
return ContextWithID(ctx, ID{
ID: rand.Uint32(),
CreatedAt: time.Now(),
})
}
func ContextWithID(ctx context.Context, id ID) context.Context {
return context.WithValue(ctx, (*idKey)(nil), id)
}
func IDFromContext(ctx context.Context) (ID, bool) {
id, loaded := ctx.Value((*idKey)(nil)).(ID)
return id, loaded

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
)
@@ -30,7 +31,7 @@ func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) erro
return err
}
o.RawMessage = content
return nil
return checkOptions(o)
}
type LogOptions struct {
@@ -42,3 +43,52 @@ type LogOptions struct {
}
type StubOptions struct{}
func checkOptions(options *Options) error {
err := checkInbounds(options.Inbounds)
if err != nil {
return err
}
err = checkOutbounds(options.Outbounds, options.Endpoints)
if err != nil {
return err
}
return nil
}
func checkInbounds(inbounds []Inbound) error {
seen := make(map[string]bool)
for _, inbound := range inbounds {
if inbound.Tag == "" {
continue
}
if seen[inbound.Tag] {
return E.New("duplicate inbound tag: ", inbound.Tag)
}
seen[inbound.Tag] = true
}
return nil
}
func checkOutbounds(outbounds []Outbound, endpoints []Endpoint) error {
seen := make(map[string]bool)
for _, outbound := range outbounds {
if outbound.Tag == "" {
continue
}
if seen[outbound.Tag] {
return E.New("duplicate outbound/endpoint tag: ", outbound.Tag)
}
seen[outbound.Tag] = true
}
for _, endpoint := range endpoints {
if endpoint.Tag == "" {
continue
}
if seen[endpoint.Tag] {
return E.New("duplicate outbound/endpoint tag: ", endpoint.Tag)
}
seen[endpoint.Tag] = true
}
return nil
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/udpnat2"
@@ -80,7 +81,7 @@ func (i *Inbound) Close() error {
}
func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.Socksaddr{}, nil)
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil)
}
func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
@@ -93,7 +94,7 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
case 2:
destination.Addr = i.overrideDestination.Addr
case 3:
destination.Port = metadata.Destination.Port
destination.Port = i.overrideDestination.Port
}
metadata.Destination = destination
if i.overrideOption != 0 {
@@ -104,7 +105,6 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
i.logger.InfoContext(ctx, "inbound packet connection from ", source)
i.logger.InfoContext(ctx, "inbound packet connection to ", destination)
var metadata adapter.InboundContext
metadata.Inbound = i.Tag()
metadata.InboundType = i.Type()
@@ -123,8 +123,11 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
destination.Port = i.overrideDestination.Port
default:
}
i.logger.InfoContext(ctx, "inbound packet connection to ", destination)
metadata.Destination = destination
metadata.OriginDestination = i.listener.UDPAddr()
if i.overrideOption != 0 {
conn = bufio.NewDestinationNATPacketConn(bufio.NewNetPacketConn(conn), i.listener.UDPAddr(), destination)
}
i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -19,6 +19,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
@@ -27,10 +28,7 @@ func RegisterURLTest(registry *outbound.Registry) {
outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest)
}
var (
_ adapter.OutboundGroup = (*URLTest)(nil)
_ adapter.InterfaceUpdateListener = (*URLTest)(nil)
)
var _ adapter.OutboundGroup = (*URLTest)(nil)
type URLTest struct {
outbound.Adapter
@@ -172,15 +170,12 @@ func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
}
func (s *URLTest) InterfaceUpdated() {
go s.group.CheckOutbounds(true)
return
}
type URLTestGroup struct {
ctx context.Context
router adapter.Router
outboundManager adapter.OutboundManager
outbound adapter.OutboundManager
pause pause.Manager
pauseCallback *list.Element[pause.Callback]
logger log.Logger
outbounds []adapter.Outbound
link string
@@ -189,17 +184,15 @@ type URLTestGroup struct {
idleTimeout time.Duration
history *urltest.HistoryStorage
checking atomic.Bool
pauseManager pause.Manager
selectedOutboundTCP adapter.Outbound
selectedOutboundUDP adapter.Outbound
interruptGroup *interrupt.Group
interruptExternalConnections bool
access sync.Mutex
ticker *time.Ticker
close chan struct{}
started bool
lastActive atomic.TypedValue[time.Time]
access sync.Mutex
ticker *time.Ticker
close chan struct{}
started bool
lastActive atomic.TypedValue[time.Time]
}
func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) {
@@ -224,7 +217,7 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage
}
return &URLTestGroup{
ctx: ctx,
outboundManager: outboundManager,
outbound: outboundManager,
logger: logger,
outbounds: outbounds,
link: link,
@@ -233,13 +226,15 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage
idleTimeout: idleTimeout,
history: history,
close: make(chan struct{}),
pauseManager: service.FromContext[pause.Manager](ctx),
pause: service.FromContext[pause.Manager](ctx),
interruptGroup: interrupt.NewGroup(),
interruptExternalConnections: interruptExternalConnections,
}, nil
}
func (g *URLTestGroup) PostStart() {
g.access.Lock()
defer g.access.Unlock()
g.started = true
g.lastActive.Store(time.Now())
go g.CheckOutbounds(false)
@@ -249,24 +244,25 @@ func (g *URLTestGroup) Touch() {
if !g.started {
return
}
g.access.Lock()
defer g.access.Unlock()
if g.ticker != nil {
g.lastActive.Store(time.Now())
return
}
g.access.Lock()
defer g.access.Unlock()
if g.ticker != nil {
return
}
g.ticker = time.NewTicker(g.interval)
go g.loopCheck()
g.pauseCallback = pause.RegisterTicker(g.pause, g.ticker, g.interval, nil)
}
func (g *URLTestGroup) Close() error {
g.access.Lock()
defer g.access.Unlock()
if g.ticker == nil {
return nil
}
g.ticker.Stop()
g.pause.UnregisterCallback(g.pauseCallback)
close(g.close)
return nil
}
@@ -330,10 +326,11 @@ func (g *URLTestGroup) loopCheck() {
g.access.Lock()
g.ticker.Stop()
g.ticker = nil
g.pause.UnregisterCallback(g.pauseCallback)
g.pauseCallback = nil
g.access.Unlock()
return
}
g.pauseManager.WaitActive()
g.CheckOutbounds(false)
}
}
@@ -366,7 +363,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
continue
}
checked[realTag] = true
p, loaded := g.outboundManager.Outbound(realTag)
p, loaded := g.outbound.Outbound(realTag)
if !loaded {
continue
}
@@ -398,12 +395,16 @@ 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(router, logger, common.PtrValueOrDefault(options.Multiplex))
inbound.router, err = mux.NewRouterWithOptions(inbound.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 && (len(inbound.routeRuleSet) > 0 || len(inbound.routeExcludeRuleSet) > 0) {
if !C.IsAndroid {
inbound.tunOptions.AutoRedirectMarkMode = true
err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
if err != nil {
@@ -312,9 +312,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
}
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
routeRuleSet.DecRef()
t.routeAddressSet = append(t.routeAddressSet, ipSets...)
if t.autoRedirect != nil {
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
}
}
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
@@ -322,9 +324,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
}
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
routeExcludeRuleSet.DecRef()
t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
if t.autoRedirect != nil {
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
}
}
}
var (

View File

@@ -26,11 +26,6 @@ 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
@@ -118,11 +113,6 @@ 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,11 +25,6 @@ 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
@@ -124,11 +119,6 @@ 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 Normal file → Executable file
View File

@@ -1,26 +1,27 @@
#!/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_stdout log_stderr
local 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_swet_param command "$PROG" run -c "$conffile" -D "$workdir"
procd_set_param file "$conffile"
procd_set_param command "$PROG" run -c "$config_file" -D "$working_directory"
procd_set_param file "$config_file"
procd_set_param stderr "$log_stderr"
procd_set_param limits core="unlimited"
sprocd_set_param limits nofile="1000000 1000000"
procd_set_param limits nofile="1000000 1000000"
procd_set_param respawn
procd_close_instance
@@ -28,4 +29,4 @@ start_service() {
service_triggers() {
procd_add_reload_trigger "sing-box"
}
}

View File

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

4
release/config/openwrt.prerm Executable file
View File

@@ -0,0 +1,4 @@
#!/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_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure

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_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/sing-box -D /var/lib/sing-box-%i -c /etc/sing-box/%i.json run
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure

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_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/local/bin/sing-box -D /var/lib/sing-box -C /usr/local/etc/sing-box run
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure

View File

@@ -2,9 +2,11 @@ package route
import (
"context"
"errors"
"io"
"net"
"net/netip"
"os"
"sync"
"sync/atomic"
"time"
@@ -13,6 +15,7 @@ import (
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions"
@@ -149,16 +152,17 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
} else {
originDestination = metadata.Destination
}
if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
} else if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
if metadata.UDPDisableDomainUnmapping {
remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
} else {
remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
}
}
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
} else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination {
remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination)
}
var udpTimeout time.Duration
if metadata.UDPTimeout > 0 {
@@ -189,14 +193,16 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose)
}
func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader, destination io.Writer, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
originSource := source
originDestination := destination
func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
var (
sourceReader io.Reader = source
destinationWriter io.Writer = destination
)
var readCounters, writeCounters []N.CountFunc
for {
source, readCounters = N.UnwrapCountReader(source, readCounters)
destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters)
if cachedSrc, isCached := source.(N.CachedReader); isCached {
sourceReader, readCounters = N.UnwrapCountReader(sourceReader, readCounters)
destinationWriter, writeCounters = N.UnwrapCountWriter(destinationWriter, writeCounters)
if cachedSrc, isCached := sourceReader.(N.CachedReader); isCached {
cachedBuffer := cachedSrc.ReadCached()
if cachedBuffer != nil {
dataLen := cachedBuffer.Len()
@@ -206,7 +212,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
if done.Swap(true) {
onClose(err)
}
common.Close(originSource, originDestination)
common.Close(source, destination)
if !direction {
m.logger.ErrorContext(ctx, "connection upload payload: ", err)
} else {
@@ -225,9 +231,13 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
}
break
}
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destination); isEarlyConn && earlyConn.NeedHandshake() {
_, err := destination.Write(nil)
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 {
@@ -236,20 +246,20 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
return
}
}
_, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
_, err := bufio.CopyWithCounters(destinationWriter, sourceReader, source, readCounters, writeCounters)
if err != nil {
common.Close(originDestination)
common.Close(source, destination)
} else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex {
err = duplexDst.CloseWrite()
if err != nil {
common.Close(originSource, originDestination)
common.Close(source, destination)
}
} else {
common.Close(originDestination)
destination.Close()
}
if done.Swap(true) {
onClose(err)
common.Close(originSource, originDestination)
common.Close(source, destination)
}
if !direction {
if err == nil {
@@ -270,16 +280,42 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
}
}
func (m *ConnectionManager) connectionCopyEarly(source net.Conn, destination io.Writer) error {
payload := buf.NewPacket()
defer payload.Release()
err := source.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
if err != nil {
if err == os.ErrInvalid {
return common.Error(destination.Write(nil))
}
return err
}
_, err = payload.ReadOnceFrom(source)
if err != nil && !(E.IsTimeout(err) || errors.Is(err, io.EOF)) {
return E.Cause(err, "read payload")
}
_ = source.SetReadDeadline(time.Time{})
_, err = destination.Write(payload.Bytes())
if err != nil {
return E.Cause(err, "write payload")
}
return nil
}
func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
_, err := bufio.CopyPacket(destination, source)
if !direction {
if E.IsClosedOrCanceled(err) {
if err == nil {
m.logger.DebugContext(ctx, "packet upload finished")
} else if E.IsClosedOrCanceled(err) {
m.logger.TraceContext(ctx, "packet upload closed")
} else {
m.logger.DebugContext(ctx, "packet upload closed: ", err)
}
} else {
if E.IsClosedOrCanceled(err) {
if err == nil {
m.logger.DebugContext(ctx, "packet download finished")
} else if E.IsClosedOrCanceled(err) {
m.logger.TraceContext(ctx, "packet download closed")
} else {
m.logger.DebugContext(ctx, "packet download closed: ", err)

View File

@@ -354,6 +354,18 @@ func (r *NetworkManager) WIFIState() adapter.WIFIState {
return r.wifiState
}
func (r *NetworkManager) UpdateWIFIState() {
if r.platformInterface != nil {
state := r.platformInterface.ReadWIFIState()
if state != r.wifiState {
r.wifiState = state
if state.SSID != "" {
r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID)
}
}
}
}
func (r *NetworkManager) ResetNetwork() {
conntrack.Close()
@@ -414,15 +426,7 @@ func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interfa
}
}
r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", "))
if r.platformInterface != nil {
state := r.platformInterface.ReadWIFIState()
if state != r.wifiState {
r.wifiState = state
if state.SSID != "" {
r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID)
}
}
}
r.UpdateWIFIState()
if !r.started {
return

View File

@@ -60,10 +60,6 @@ func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata
}
func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
if r.pauseManager.IsDevicePaused() {
return E.New("reject connection to ", metadata.Destination, " while device paused")
}
//nolint:staticcheck
if metadata.InboundDetour != "" {
if metadata.LastInbound == metadata.InboundDetour {
@@ -140,8 +136,8 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
for _, buffer := range buffers {
conn = bufio.NewCachedConn(conn, buffer)
}
if r.tracker != nil {
conn = r.tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
for _, tracker := range r.trackers {
conn = tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
}
if outboundHandler, isHandler := selectedOutbound.(adapter.ConnectionHandlerEx); isHandler {
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
@@ -186,9 +182,6 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn,
}
func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
if r.pauseManager.IsDevicePaused() {
return E.New("reject packet connection to ", metadata.Destination, " while device paused")
}
//nolint:staticcheck
if metadata.InboundDetour != "" {
if metadata.LastInbound == metadata.InboundDetour {
@@ -258,8 +251,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination)
N.PutPacketBuffer(buffer)
}
if r.tracker != nil {
conn = r.tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
for _, tracker := range r.trackers {
conn = tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
}
if metadata.FakeIP {
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
@@ -358,7 +351,7 @@ func (r *Router) matchRule(
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
}, inputConn, inputPacketConn)
}, inputConn, inputPacketConn, nil)
if newErr != nil {
fatalErr = newErr
return
@@ -458,7 +451,7 @@ match:
switch action := currentRule.Action().(type) {
case *rule.RuleActionSniff:
if !preMatch {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn)
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
if newErr != nil {
fatalErr = newErr
return
@@ -489,32 +482,21 @@ match:
break match
}
}
if !preMatch && inputPacketConn != nil && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() {
var timeout time.Duration
if metadata.InboundType == C.TypeSOCKS {
timeout = C.TCPTimeout
}
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: timeout}, inputConn, inputPacketConn)
if newErr != nil {
fatalErr = newErr
return
}
if newBuffer != nil {
buffers = append(buffers, newBuffer)
} else if len(newPacketBuffers) > 0 {
packetBuffers = append(packetBuffers, newPacketBuffers...)
}
}
return
}
func (r *Router) actionSniff(
ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff,
inputConn net.Conn, inputPacketConn N.PacketConn,
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
if sniff.Skip(metadata) {
r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first")
return
} else if inputConn != nil {
} else if metadata.Protocol != "" {
r.logger.DebugContext(ctx, "duplicate sniff skipped")
return
}
if inputConn != nil {
sniffBuffer := buf.NewPacket()
var streamSniffers []sniff.StreamSniffer
if len(action.StreamSniffers) > 0 {
@@ -533,6 +515,7 @@ func (r *Router) actionSniff(
ctx,
metadata,
inputConn,
inputBuffers,
sniffBuffer,
action.Timeout,
streamSniffers...,
@@ -559,6 +542,10 @@ func (r *Router) actionSniff(
sniffBuffer.Release()
}
} else if inputPacketConn != nil {
if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
return
}
for {
var (
sniffBuffer = buf.NewPacket()
@@ -590,10 +577,7 @@ func (r *Router) actionSniff(
return
}
} else {
if !metadata.Destination.Addr.IsGlobalUnicast() {
metadata.Destination = destination
}
if len(packetBuffers) > 0 {
if len(packetBuffers) > 0 || metadata.PacketSniffError != nil {
err = sniff.PeekPacket(
ctx,
metadata,
@@ -626,7 +610,9 @@ func (r *Router) actionSniff(
Destination: destination,
}
packetBuffers = append(packetBuffers, packetBuffer)
if E.IsMulti(err, sniff.ErrClientHelloFragmented) && len(packetBuffers) == 0 {
metadata.PacketSniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue
}

View File

@@ -64,7 +64,7 @@ type Router struct {
fakeIPStore adapter.FakeIPStore
processSearcher process.Searcher
pauseManager pause.Manager
tracker adapter.ConnectionTracker
trackers []adapter.ConnectionTracker
platformInterface platform.Interface
needWIFIState bool
started bool
@@ -484,6 +484,13 @@ func (r *Router) Close() error {
})
monitor.Finish()
}
for i, ruleSet := range r.ruleSets {
monitor.Start("close rule-set[", i, "]")
err = E.Append(err, ruleSet.Close(), func(err error) error {
return E.Cause(err, "close rule-set[", i, "]")
})
monitor.Finish()
}
return err
}
@@ -504,8 +511,8 @@ func (r *Router) Rules() []adapter.Rule {
return r.rules
}
func (r *Router) SetTracker(tracker adapter.ConnectionTracker) {
r.tracker = tracker
func (r *Router) AppendTracker(tracker adapter.ConnectionTracker) {
r.trackers = append(r.trackers, tracker)
}
func (r *Router) ResetNetwork() {

View File

@@ -162,6 +162,24 @@ func (r *RuleActionRouteOptions) Type() string {
func (r *RuleActionRouteOptions) String() string {
var descriptions []string
if r.OverrideAddress.IsValid() {
descriptions = append(descriptions, F.ToString("override-address=", r.OverrideAddress.AddrString()))
}
if r.OverridePort > 0 {
descriptions = append(descriptions, F.ToString("override-port=", r.OverridePort))
}
if r.NetworkStrategy != nil {
descriptions = append(descriptions, F.ToString("network-strategy=", r.NetworkStrategy))
}
if r.NetworkType != nil {
descriptions = append(descriptions, F.ToString("network-type=", strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), ",")))
}
if r.FallbackNetworkType != nil {
descriptions = append(descriptions, F.ToString("fallback-network-type="+strings.Join(common.Map(r.NetworkType, C.InterfaceType.String), ",")))
}
if r.FallbackDelay > 0 {
descriptions = append(descriptions, F.ToString("fallback-delay=", r.FallbackDelay.String()))
}
if r.UDPDisableDomainUnmapping {
descriptions = append(descriptions, "udp-disable-domain-unmapping")
}
@@ -261,6 +279,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
default:
panic(F.ToString("unknown reject method: ", r.Method))
}
if r.NoDrop {
return returnErr
}
r.dropAccess.Lock()
defer r.dropAccess.Unlock()
timeNow := time.Now()

View File

@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -26,14 +27,16 @@ import (
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
type LocalRuleSet struct {
ctx context.Context
logger logger.Logger
tag string
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
fileFormat string
watcher *fswatch.Watcher
refs atomic.Int32
ctx context.Context
logger logger.Logger
tag string
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
fileFormat string
watcher *fswatch.Watcher
callbackAccess sync.Mutex
callbacks list.List[adapter.RuleSetUpdateCallback]
refs atomic.Int32
}
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
@@ -52,13 +55,12 @@ func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.R
return nil, err
}
} else {
err := ruleSet.reloadFile(filemanager.BasePath(ctx, options.LocalOptions.Path))
filePath := filemanager.BasePath(ctx, options.LocalOptions.Path)
filePath, _ = filepath.Abs(filePath)
err := ruleSet.reloadFile(filePath)
if err != nil {
return nil, err
}
}
if options.Type == C.RuleSetTypeLocal {
filePath, _ := filepath.Abs(options.LocalOptions.Path)
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: []string{filePath},
Callback: func(path string) {
@@ -141,6 +143,12 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
s.rules = rules
s.metadata = metadata
s.callbackAccess.Lock()
callbacks := s.callbacks.Array()
s.callbackAccess.Unlock()
for _, callback := range callbacks {
callback(s)
}
return nil
}
@@ -173,10 +181,15 @@ func (s *LocalRuleSet) Cleanup() {
}
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
return nil
s.callbackAccess.Lock()
defer s.callbackAccess.Unlock()
return s.callbacks.PushBack(callback)
}
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
s.callbackAccess.Lock()
defer s.callbackAccess.Unlock()
s.callbacks.Remove(element)
}
func (s *LocalRuleSet) Close() error {

View File

@@ -33,23 +33,23 @@ import (
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
type RemoteRuleSet struct {
ctx context.Context
cancel context.CancelFunc
outboundManager adapter.OutboundManager
logger logger.ContextLogger
options option.RuleSet
metadata adapter.RuleSetMetadata
updateInterval time.Duration
dialer N.Dialer
rules []adapter.HeadlessRule
lastUpdated time.Time
lastEtag string
updateTicker *time.Ticker
cacheFile adapter.CacheFile
pauseManager pause.Manager
callbackAccess sync.Mutex
callbacks list.List[adapter.RuleSetUpdateCallback]
refs atomic.Int32
ctx context.Context
cancel context.CancelFunc
logger logger.ContextLogger
outbound adapter.OutboundManager
options option.RuleSet
metadata adapter.RuleSetMetadata
updateInterval time.Duration
dialer N.Dialer
rules []adapter.HeadlessRule
lastUpdated time.Time
lastEtag string
updateTicker *time.Ticker
cacheFile adapter.CacheFile
pauseManager pause.Manager
callbackAccess sync.Mutex
callbacks list.List[adapter.RuleSetUpdateCallback]
refs atomic.Int32
}
func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {
@@ -61,13 +61,13 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options
updateInterval = 24 * time.Hour
}
return &RemoteRuleSet{
ctx: ctx,
cancel: cancel,
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
logger: logger,
options: options,
updateInterval: updateInterval,
pauseManager: service.FromContext[pause.Manager](ctx),
ctx: ctx,
cancel: cancel,
outbound: service.FromContext[adapter.OutboundManager](ctx),
logger: logger,
options: options,
updateInterval: updateInterval,
pauseManager: service.FromContext[pause.Manager](ctx),
}
}
@@ -83,13 +83,13 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.
s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx)
var dialer N.Dialer
if s.options.RemoteOptions.DownloadDetour != "" {
outbound, loaded := s.outboundManager.Outbound(s.options.RemoteOptions.DownloadDetour)
outbound, loaded := s.outbound.Outbound(s.options.RemoteOptions.DownloadDetour)
if !loaded {
return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour)
return E.New("download detour not found: ", s.options.RemoteOptions.DownloadDetour)
}
dialer = outbound
} else {
dialer = s.outboundManager.Default()
dialer = s.outbound.Default()
}
s.dialer = dialer
if s.cacheFile != nil {
@@ -103,7 +103,7 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.
}
}
if s.lastUpdated.IsZero() {
err := s.fetchOnce(ctx, startContext)
err := s.fetch(ctx, startContext)
if err != nil {
return E.Cause(err, "initial rule-set: ", s.options.Tag)
}
@@ -198,7 +198,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
func (s *RemoteRuleSet) loopUpdate() {
if time.Since(s.lastUpdated) > s.updateInterval {
err := s.fetchOnce(s.ctx, nil)
err := s.fetch(s.ctx, nil)
if err != nil {
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
} else if s.refs.Load() == 0 {
@@ -211,18 +211,21 @@ func (s *RemoteRuleSet) loopUpdate() {
case <-s.ctx.Done():
return
case <-s.updateTicker.C:
s.pauseManager.WaitActive()
err := s.fetchOnce(s.ctx, nil)
if err != nil {
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
} else if s.refs.Load() == 0 {
s.rules = nil
}
s.updateOnce()
}
}
}
func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTTPStartContext) error {
func (s *RemoteRuleSet) updateOnce() {
err := s.fetch(s.ctx, nil)
if err != nil {
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
} else if s.refs.Load() == 0 {
s.rules = nil
}
}
func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPStartContext) error {
s.logger.Debug("updating rule-set ", s.options.Tag, " from URL: ", s.options.RemoteOptions.URL)
var httpClient *http.Client
if startContext != nil {
@@ -286,7 +289,7 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT
}
s.lastUpdated = time.Now()
if s.cacheFile != nil {
err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{
err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedBinary{
LastUpdated: s.lastUpdated,
Content: content,
LastEtag: s.lastEtag,
@@ -301,8 +304,10 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT
func (s *RemoteRuleSet) Close() error {
s.rules = nil
s.updateTicker.Stop()
s.cancel()
if s.updateTicker != nil {
s.updateTicker.Stop()
}
return nil
}

View File

@@ -21,6 +21,7 @@ import (
var _ net.Conn = (*GunConn)(nil)
type GunConn struct {
rawReader io.Reader
reader *std_bufio.Reader
writer io.Writer
flusher http.Flusher
@@ -31,9 +32,10 @@ type GunConn struct {
func newGunConn(reader io.Reader, writer io.Writer, flusher http.Flusher) *GunConn {
return &GunConn{
reader: std_bufio.NewReader(reader),
writer: writer,
flusher: flusher,
rawReader: reader,
reader: std_bufio.NewReader(reader),
writer: writer,
flusher: flusher,
}
}
@@ -46,6 +48,7 @@ func newLateGunConn(writer io.Writer) *GunConn {
func (c *GunConn) setup(reader io.Reader, err error) {
if reader != nil {
c.rawReader = reader
c.reader = std_bufio.NewReader(reader)
}
c.err = err
@@ -138,7 +141,7 @@ func (c *GunConn) FrontHeadroom() int {
}
func (c *GunConn) Close() error {
return common.Close(c.reader, c.writer)
return common.Close(c.rawReader, c.writer)
}
func (c *GunConn) LocalAddr() net.Addr {

View File

@@ -2,6 +2,7 @@ package v2rayhttp
import (
std_bufio "bufio"
"context"
"io"
"net"
"net/http"
@@ -10,6 +11,7 @@ import (
"sync"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/baderror"
"github.com/sagernet/sing/common/buf"
@@ -255,3 +257,11 @@ func (w *HTTP2ConnWrapper) Close() error {
func (w *HTTP2ConnWrapper) Upstream() any {
return w.ExtendedConn
}
func DupContext(ctx context.Context) context.Context {
id, loaded := log.IDFromContext(ctx)
if !loaded {
return context.Background()
}
return log.ContextWithID(context.Background(), id)
}

View File

@@ -132,7 +132,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if requestBody != nil {
conn = bufio.NewCachedConn(conn, requestBody)
}
s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil)
s.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
} else {
writer.WriteHeader(http.StatusOK)
done := make(chan struct{})

View File

@@ -12,6 +12,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
@@ -37,6 +38,7 @@ type Server struct {
func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
server := &Server{
ctx: ctx,
logger: logger,
tlsConfig: tlsConfig,
handler: handler,
host: options.Host,
@@ -110,7 +112,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed"))
return
}
s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
}
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {

View File

@@ -74,6 +74,10 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
return
}
if header.OpCode.IsControl() {
if header.Length > 128 {
err = wsutil.ErrFrameTooLarge
return
}
err = c.controlHandler(header, c.reader)
if err != nil {
return

View File

@@ -13,6 +13,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
@@ -114,7 +115,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if len(earlyData) > 0 {
conn = bufio.NewCachedConn(conn, buf.As(earlyData))
}
s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil)
s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
}
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {

View File

@@ -30,7 +30,7 @@ type Endpoint struct {
allowedAddress []netip.Prefix
tunDevice Device
device *device.Device
pauseManager pause.Manager
pause pause.Manager
pauseCallback *list.Element[pause.Callback]
}
@@ -150,7 +150,7 @@ func (e *Endpoint) Start(resolve bool) error {
connectAddr netip.AddrPort
reserved [3]uint8
)
if len(e.peers) == 1 {
if len(e.peers) == 1 && e.peers[0].endpoint.IsValid() {
isConnect = true
connectAddr = e.peers[0].endpoint
reserved = e.peers[0].reserved
@@ -187,9 +187,9 @@ func (e *Endpoint) Start(resolve bool) error {
return E.Cause(err, "setup wireguard: \n", ipcConf)
}
e.device = wgDevice
e.pauseManager = service.FromContext[pause.Manager](e.options.Context)
if e.pauseManager != nil {
e.pauseCallback = e.pauseManager.RegisterCallback(e.onPauseUpdated)
e.pause = service.FromContext[pause.Manager](e.options.Context)
if e.pause != nil {
e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)
}
return nil
}
@@ -208,25 +208,21 @@ func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return e.tunDevice.ListenPacket(ctx, destination)
}
func (e *Endpoint) BindUpdate() error {
return e.device.BindUpdate()
}
func (e *Endpoint) Close() error {
if e.device != nil {
e.device.Close()
}
if e.pauseCallback != nil {
e.pauseManager.UnregisterCallback(e.pauseCallback)
e.pause.UnregisterCallback(e.pauseCallback)
}
return nil
}
func (e *Endpoint) onPauseUpdated(event int) {
switch event {
case pause.EventDevicePaused:
case pause.EventDevicePaused, pause.EventNetworkPause:
e.device.Down()
case pause.EventDeviceWake:
case pause.EventDeviceWake, pause.EventNetworkWake:
e.device.Up()
}
}