Compare commits

..

81 Commits

Author SHA1 Message Date
世界
230ac25f2c
documentation: Bump version 2025-04-19 15:40:31 +08:00
reletor
c2749ce307
documentation: Minor fixes 2025-04-19 15:40:31 +08:00
caelansar
bc216caef0
Fix callback deletion in UDP transport 2025-04-19 15:40:31 +08:00
世界
0e9a38e369
documentation: Try to make the play review happy 2025-04-19 11:48:08 +08:00
世界
7869374b29
Fix missing handling of legacy domain_strategy options 2025-04-19 11:48:08 +08:00
世界
9f3bb97c97
Improve local DNS server 2025-04-19 11:48:08 +08:00
anytls
867194c929
Update anytls
Co-authored-by: anytls <anytls>
2025-04-19 11:48:07 +08:00
世界
acdfa70031
Fix DNS dialer 2025-04-19 11:48:07 +08:00
世界
56ef2e52d1
release: Skip override version for iOS 2025-04-19 11:48:07 +08:00
iikira
e356515217
Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-04-19 11:48:07 +08:00
ReleTor
7d9058cf10
Fix fetch ECH configs 2025-04-19 11:48:06 +08:00
世界
1df4a1fa37
release: Update Go to 1.24.2 2025-04-19 11:48:06 +08:00
世界
d6f83f3003
Allow direct outbounds without domain_resolver 2025-04-19 11:48:06 +08:00
世界
782baad82b
Fix Tailscale dialer 2025-04-19 11:48:06 +08:00
dyhkwong
674699db1f
Fix DNS over QUIC stream close 2025-04-19 11:48:05 +08:00
anytls
17d7211abf
Update anytls
Co-authored-by: anytls <anytls>
2025-04-19 11:48:05 +08:00
Rambling2076
dc9196d766
Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-04-19 11:48:05 +08:00
世界
ef2d2d4f9e
Fail when default DNS server not found 2025-04-19 11:48:05 +08:00
世界
e601393a69
Update gVisor to 20250319.0 2025-04-19 11:48:04 +08:00
世界
d5d7320666
release: Do not build tailscale on iOS and tvOS 2025-04-19 11:48:04 +08:00
世界
09f8b985c4
Explicitly reject detour to empty direct outbounds 2025-04-19 11:48:04 +08:00
世界
0afaae0108
Add netns support 2025-04-19 11:48:04 +08:00
世界
4138f33aff
Add wildcard name support for predefined records 2025-04-19 11:48:03 +08:00
世界
796e8250c0
Remove map usage in options 2025-04-19 11:48:03 +08:00
世界
cb727c8c4c
Fix unhandled DNS loop 2025-04-19 11:48:03 +08:00
世界
fa741f9d7e
Add wildcard-sni support for shadow-tls inbound 2025-04-19 11:48:02 +08:00
世界
34b95d856d
Fix Tailscale DNS 2025-04-19 11:48:02 +08:00
k9982874
766386fdd7
Add ntp protocol sniffing 2025-04-19 11:48:02 +08:00
世界
c6387b8b8c
option: Fix marshal legacy DNS options 2025-04-19 11:48:02 +08:00
世界
69305beb7c
Make domain_resolver optional when only one DNS server is configured 2025-04-19 11:48:01 +08:00
世界
c013a0c253
Fix DNS lookup context pollution 2025-04-19 11:48:01 +08:00
世界
f5cd08c044
Fix http3 DNS server connecting to wrong address 2025-04-19 11:48:01 +08:00
Restia-Ashbell
0d29f02e02
documentation: Fix typo 2025-04-19 11:48:00 +08:00
anytls
cc259db0bf
Update sing-anytls
Co-authored-by: anytls <anytls>
2025-04-19 11:48:00 +08:00
k9982874
fbe618199f
Fix hosts DNS server 2025-04-19 11:48:00 +08:00
世界
75160c15b7
Fix UDP DNS server crash 2025-04-19 11:48:00 +08:00
世界
a455127831
documentation: Fix missing ip_accept_any DNS rule option 2025-04-19 11:48:00 +08:00
世界
5420d786b5
Fix anytls dialer usage 2025-04-19 11:47:59 +08:00
世界
04fb124795
Move predefined DNS server to rule action 2025-04-19 11:47:59 +08:00
世界
05d27f5ca5
Fix domain resolver on direct outbound 2025-04-19 11:47:59 +08:00
Zephyruso
2e2efbea8e
Fix missing AnyTLS display name 2025-04-19 11:47:58 +08:00
anytls
d3e8c67aad
Update sing-anytls
Co-authored-by: anytls <anytls>
2025-04-19 11:47:58 +08:00
Estel
2fb54b8e7a
documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-04-19 11:47:58 +08:00
TargetLocked
661a437ef3
Fix parsing legacy DNS options 2025-04-19 11:47:58 +08:00
世界
3ecf86e4d7
Fix DNS fallback 2025-04-19 11:47:57 +08:00
世界
bc9e67394b
documentation: Fix missing hosts DNS server 2025-04-19 11:47:57 +08:00
anytls
b2900682fc
Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-04-19 11:47:57 +08:00
ReleTor
37d16c652f
documentation: Minor fixes 2025-04-19 11:47:57 +08:00
libtry486
af6f7c07d7
documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-04-19 11:47:56 +08:00
Alireza Ahmadi
fcc52a4b72
Fix Outbound deadlock 2025-04-19 11:47:56 +08:00
世界
d0d988f3f8
documentation: Fix AnyTLS doc 2025-04-19 11:47:55 +08:00
anytls
ac7b1c538e
Add AnyTLS protocol 2025-04-19 11:47:55 +08:00
世界
b497b67927
Migrate to stdlib ECH support 2025-04-19 11:47:55 +08:00
世界
3d73094d3b
Add fallback local DNS server for iOS 2025-04-19 11:47:54 +08:00
世界
4ac74f1af2
Get darwin local DNS server from libresolv 2025-04-19 11:47:54 +08:00
世界
6177bab519
Improve resolve action 2025-04-19 11:47:54 +08:00
世界
7a6d09709d
Fix toolchain version 2025-04-19 11:47:54 +08:00
世界
bdf073483e
Add back port hopping to hysteria 1 2025-04-19 11:47:53 +08:00
世界
3161c1121d
Update dependencies 2025-04-19 11:47:53 +08:00
xchacha20-poly1305
442ba0c3f5
Remove single quotes of raw Moziila certs 2025-04-19 11:47:53 +08:00
世界
a453fdaaf4
Add Tailscale endpoint 2025-04-19 11:47:53 +08:00
世界
d0f52be0de
Build legacy binaries with latest Go 2025-04-19 11:47:52 +08:00
世界
d51692d5c0
documentation: Remove outdated icons 2025-04-19 11:47:52 +08:00
世界
bd8503dff2
documentation: Certificate store 2025-04-19 11:47:51 +08:00
世界
283b0c5958
documentation: TLS fragment 2025-04-19 11:47:51 +08:00
世界
38ace76b5a
documentation: Outbound domain resolver 2025-04-19 11:47:51 +08:00
世界
e17ae9ec28
documentation: Refactor DNS 2025-04-19 11:47:51 +08:00
世界
380222c978
Add certificate store 2025-04-19 11:47:50 +08:00
世界
cac5f0410b
Add TLS fragment support 2025-04-19 11:47:50 +08:00
世界
233b475248
refactor: Outbound domain resolver 2025-04-19 11:47:50 +08:00
世界
a4dea6485c
refactor: DNS 2025-04-19 11:47:50 +08:00
世界
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
43 changed files with 699 additions and 297 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

View File

@ -1,11 +1,12 @@
-s dir -s dir
--name sing-box --name sing-box
--category net --category net
--license GPLv3-or-later --license GPL-3.0-or-later
--description "The universal proxy platform." --description "The universal proxy platform."
--url "https://sing-box.sagernet.org/" --url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>" --maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues" --deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
--no-deb-generate-changes
--config-files /etc/sing-box/config.json --config-files /etc/sing-box/config.json
release/config/config.json=/etc/sing-box/config.json release/config/config.json=/etc/sing-box/config.json

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

@ -68,31 +68,38 @@ jobs:
- calculate_version - calculate_version
strategy: strategy:
matrix: matrix:
os: [ linux, windows, darwin, android ]
arch: [ "386", amd64, arm64 ]
legacy_go: [ false ]
include: include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 } - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
- { os: linux, arch: "386", debian: i386, rpm: i386 } - { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl } - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl } - { 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: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 } - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el } - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel } - { 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: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le } - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 } - { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 } - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- { os: windows, arch: "386", legacy_go: true } - { os: windows, arch: amd64 }
- { os: windows, arch: amd64, legacy_go: true } - { 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: arm64 }
- { os: android, arch: "386", ndk: "i686-linux-android21" }
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" } - { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" } - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
exclude: - { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- { os: darwin, arch: "386" } - { os: android, arch: "386", ndk: "i686-linux-android21" }
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@ -147,7 +154,10 @@ jobs:
CGO_ENABLED: "0" CGO_ENABLED: "0"
GOOS: ${{ matrix.os }} GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }} GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }} GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build Android - name: Build Android
if: matrix.os == 'android' if: matrix.os == 'android'
@ -167,14 +177,19 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name - name: Set name
run: |- run: |-
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true) DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true) if [[ -n "${{ matrix.goarm }}" ]]; then
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}" DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}" 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}" echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
PKG_VERSION="${{ needs.calculate_version.outputs.version }}" PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}" PKG_VERSION="${PKG_VERSION//-/\~}-1"
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
- name: Package DEB - name: Package DEB
if: matrix.debian != '' if: matrix.debian != ''
@ -182,9 +197,10 @@ jobs:
set -xeuo pipefail set -xeuo pipefail
sudo gem install fpm sudo gem install fpm
sudo apt-get install -y debsigs sudo apt-get install -y debsigs
cp .fpm_systemd .fpm
fpm -t deb \ fpm -t deb \
-v "$PKG_VERSION" \ -v "$PKG_VERSION" \
-p "dist/${PKG_NAME}.deb" \ -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
--architecture ${{ matrix.debian }} \ --architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff' curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
@ -199,9 +215,10 @@ jobs:
run: |- run: |-
set -xeuo pipefail set -xeuo pipefail
sudo gem install fpm sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \ fpm -t rpm \
-v "$PKG_VERSION" \ -v "$PKG_VERSION" \
-p "dist/${PKG_NAME}.rpm" \ -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
--architecture ${{ matrix.rpm }} \ --architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF cat > $HOME/.rpmmacros <<EOF
@ -218,11 +235,27 @@ jobs:
set -xeuo pipefail set -xeuo pipefail
sudo gem install fpm sudo gem install fpm
sudo apt-get install -y libarchive-tools sudo apt-get install -y libarchive-tools
cp .fpm_systemd .fpm
fpm -t pacman \ fpm -t pacman \
-v "$PKG_VERSION" \ -v "$PKG_VERSION" \
-p "dist/${PKG_NAME}.pkg.tar.zst" \ -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
--architecture ${{ matrix.pacman }} \ --architecture ${{ matrix.pacman }} \
dist/sing-box=/usr/bin/sing-box 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 - name: Archive
run: | run: |
set -xeuo pipefail set -xeuo pipefail
@ -242,7 +275,7 @@ jobs:
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }} 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" path: "dist"
build_android: build_android:
name: Build Android name: Build Android

View File

@ -8,7 +8,7 @@ GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag) 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=" 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 MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH) PREFIX ?= $(shell go env GOPATH)
@ -24,7 +24,7 @@ ci_build:
go build $(MAIN_PARAMS) $(MAIN) go build $(MAIN_PARAMS) $(MAIN)
generate_completions: generate_completions:
go run -v --tags $(TAGS),generate,generate_completions $(MAIN) go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
install: install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)

@ -1 +1 @@
Subproject commit 8354b78e5d002d636827cfeed6ed5df8ea057452 Subproject commit 55f31c29bb68895ce544e0dfbf852b4b3e32b530

View File

@ -83,6 +83,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) { dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck //nolint:staticcheck
strategy = C.DomainStrategy(dialOptions.DomainStrategy) strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
} }
server = dialOptions.DomainResolver.Server server = dialOptions.DomainResolver.Server
dnsQueryOptions = adapter.DNSQueryOptions{ dnsQueryOptions = adapter.DNSQueryOptions{
@ -95,22 +96,31 @@ func NewWithOptions(options Options) (N.Dialer, error) {
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay) resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else if options.DirectResolver { } else if options.DirectResolver {
return nil, E.New("missing domain resolver for domain server address") return nil, E.New("missing domain resolver for domain server address")
} else if defaultOptions.DomainResolver != "" {
dnsQueryOptions = defaultOptions.DomainResolveOptions
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
if !loaded {
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
}
dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else { } else {
transports := dnsTransport.Transports() if defaultOptions.DomainResolver != "" {
if len(transports) < 2 { dnsQueryOptions = defaultOptions.DomainResolveOptions
dnsQueryOptions.Transport = dnsTransport.Default() transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
} else if options.NewDialer { if !loaded {
return nil, E.New("missing domain resolver for domain server address") return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
} else if !options.DirectOutbound { }
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver) dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else {
transports := dnsTransport.Transports()
if len(transports) < 2 {
dnsQueryOptions.Transport = dnsTransport.Default()
} else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address")
} else if !options.DirectOutbound {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
}
}
if
//nolint:staticcheck
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
} }
} }
dialer = NewResolveDialer( dialer = NewResolveDialer(

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.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
tlsConfig.ShortIds = make(map[[8]byte]bool) tlsConfig.ShortIds = make(map[[8]byte]bool)
for i, shortIDString := range options.Reality.ShortID { if len(options.Reality.ShortID) == 0 {
var shortID [8]byte tlsConfig.ShortIds[[8]byte{0}] = true
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString)) } else {
if err != nil { for i, shortIDString := range options.Reality.ShortID {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString) 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, options.Reality.Handshake.ServerIsDomain()) handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())

View File

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

View File

@ -35,6 +35,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
} }
return &Transport{ return &Transport{
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
ctx: ctx,
hosts: hosts.NewFile(hosts.DefaultPath), hosts: hosts.NewFile(hosts.DefaultPath),
dialer: transportDialer, dialer: transportDialer,
}, nil }, nil
@ -57,7 +58,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
} }
} }
systemConfig := getSystemDNSConfig() systemConfig := getSystemDNSConfig(t.ctx)
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) { if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
return t.exchangeSingleRequest(ctx, systemConfig, message, domain) return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
} else { } else {

View File

@ -1,6 +1,7 @@
package local package local
import ( import (
"context"
"os" "os"
"runtime" "runtime"
"strings" "strings"
@ -23,19 +24,21 @@ type resolverConfig struct {
var resolvConf resolverConfig var resolvConf resolverConfig
func getSystemDNSConfig() *dnsConfig { func getSystemDNSConfig(ctx context.Context) *dnsConfig {
resolvConf.tryUpdate("/etc/resolv.conf") resolvConf.tryUpdate(ctx, "/etc/resolv.conf")
return resolvConf.dnsConfig.Load() return resolvConf.dnsConfig.Load()
} }
func (conf *resolverConfig) init() { func (conf *resolverConfig) init(ctx context.Context) {
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf")) conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf"))
conf.lastChecked = time.Now() conf.lastChecked = time.Now()
conf.ch = make(chan struct{}, 1) conf.ch = make(chan struct{}, 1)
} }
func (conf *resolverConfig) tryUpdate(name string) { func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {
conf.initOnce.Do(conf.init) conf.initOnce.Do(func() {
conf.init(ctx)
})
if conf.dnsConfig.Load().noReload { if conf.dnsConfig.Load().noReload {
return return
@ -59,7 +62,7 @@ func (conf *resolverConfig) tryUpdate(name string) {
return return
} }
} }
dnsConf := dnsReadConfig(name) dnsConf := dnsReadConfig(ctx, name)
conf.dnsConfig.Store(dnsConf) conf.dnsConfig.Store(dnsConf)
} }

View File

@ -11,6 +11,7 @@ package local
import "C" import "C"
import ( import (
"context"
"time" "time"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -18,7 +19,7 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func dnsReadConfig(_ string) *dnsConfig { func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
if C.res_init() != 0 { if C.res_init() != 0 {
return &dnsConfig{ return &dnsConfig{
servers: defaultNS, servers: defaultNS,

View File

@ -4,6 +4,7 @@ package local
import ( import (
"bufio" "bufio"
"context"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@ -13,7 +14,7 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func dnsReadConfig(name string) *dnsConfig { func dnsReadConfig(_ context.Context, name string) *dnsConfig {
conf := &dnsConfig{ conf := &dnsConfig{
ndots: 1, ndots: 1,
timeout: 5 * time.Second, timeout: 5 * time.Second,

View File

@ -1,6 +1,7 @@
package local package local
import ( import (
"context"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@ -8,10 +9,13 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/service"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
func dnsReadConfig(_ string) *dnsConfig { func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
conf := &dnsConfig{ conf := &dnsConfig{
ndots: 1, ndots: 1,
timeout: 5 * time.Second, timeout: 5 * time.Second,
@ -22,35 +26,35 @@ func dnsReadConfig(_ string) *dnsConfig {
conf.servers = defaultNS conf.servers = defaultNS
} }
}() }()
aas, err := adapterAddresses() addresses, err := adapterAddresses()
if err != nil { if err != nil {
return nil return nil
} }
var dnsAddresses []struct {
for _, aa := range aas { ifName string
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs. netip.Addr
if aa.OperStatus != windows.IfOperStatusUp { }
for _, address := range addresses {
if address.OperStatus != windows.IfOperStatusUp {
continue continue
} }
if address.IfType == windows.IF_TYPE_TUNNEL {
// Only take interfaces which have at least one gateway
if aa.FirstGatewayAddress == nil {
continue continue
} }
if address.FirstGatewayAddress == nil {
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next { continue
sa, err := dns.Address.Sockaddr.Sockaddr() }
for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {
rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()
if err != nil { if err != nil {
continue continue
} }
var ip netip.Addr var dnsServerAddr netip.Addr
switch sa := sa.(type) { switch sockaddr := rawSockaddr.(type) {
case *syscall.SockaddrInet4: case *syscall.SockaddrInet4:
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]}) dnsServerAddr = netip.AddrFrom4(sockaddr.Addr)
case *syscall.SockaddrInet6: case *syscall.SockaddrInet6:
var addr16 [16]byte if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {
copy(addr16[:], sa.Addr[:])
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
// fec0/10 IPv6 addresses are site local anycast DNS // fec0/10 IPv6 addresses are site local anycast DNS
// addresses Microsoft sets by default if no other // addresses Microsoft sets by default if no other
// IPv6 DNS address is set. Site local anycast is // IPv6 DNS address is set. Site local anycast is
@ -58,14 +62,27 @@ func dnsReadConfig(_ string) *dnsConfig {
// https://datatracker.ietf.org/doc/html/rfc3879 // https://datatracker.ietf.org/doc/html/rfc3879
continue continue
} }
ip = netip.AddrFrom16(addr16) dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
default: default:
// Unexpected type. // Unexpected type.
continue continue
} }
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53")) dnsAddresses = append(dnsAddresses, struct {
ifName string
netip.Addr
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
} }
} }
var myInterface string
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
myInterface = networkManager.InterfaceMonitor().MyInterface()
}
for _, address := range dnsAddresses {
if address.ifName == myInterface {
continue
}
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
}
return conf return conf
} }

View File

@ -117,7 +117,7 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
conn.access.Unlock() conn.access.Unlock()
defer func() { defer func() {
conn.access.Lock() conn.access.Lock()
delete(conn.callbacks, messageId) delete(conn.callbacks, exMessage.Id)
conn.access.Unlock() conn.access.Unlock()
}() }()
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())

View File

@ -2,6 +2,22 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.12.0-beta.4
* Fixes and improvements
### 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.12.0-beta.3 #### 1.12.0-beta.3
* Fixes and improvements * Fixes and improvements

View File

@ -9,6 +9,10 @@ and the data generated by the software is always on your device.
## Android ## Android
The broad package (App) visibility (QUERY_ALL_PACKAGES) permission
is used to provide per-application proxy features for VPN,
sing-box will not collect your app list.
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules, If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
sing-box uses the location permission in the background sing-box uses the location permission in the background
to get information about the connected Wi-Fi network to make them work. to get information about the connected Wi-Fi network to make them work.

View File

@ -27,19 +27,20 @@ icon: material/alert-decagram
The type of the DNS server. The type of the DNS server.
| Type | Format | | Type | Format |
|-----------------|-----------------------------| |-----------------|---------------------------|
| empty (default) | [Legacy](./legacy/) | | empty (default) | [Legacy](./legacy/) |
| `tcp` | [TCP](./tcp/) | | `local` | [Local](./local/) |
| `udp` | [UDP](./udp/) | | `hosts` | [Hosts](./hosts/) |
| `tls` | [TLS](./tls/) | | `tcp` | [TCP](./tcp/) |
| `https` | [HTTPS](./https/) | | `udp` | [UDP](./udp/) |
| `quic` | [QUIC](./quic/) | | `tls` | [TLS](./tls/) |
| `h3` | [HTTP/3](./http3/) | | `quic` | [QUIC](./quic/) |
| `predefined` | [Predefined](./predefined/) | | `https` | [HTTPS](./https/) |
| `dhcp` | [DHCP](./dhcp/) | | `h3` | [HTTP/3](./http3/) |
| `fakeip` | [Fake IP](./fakeip/) | | `dhcp` | [DHCP](./dhcp/) |
| `tailscale` | [Tailscale](./tailscale/) | | `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
#### tag #### tag

View File

@ -27,19 +27,20 @@ icon: material/alert-decagram
DNS 服务器的类型。 DNS 服务器的类型。
| 类型 | 格式 | | 类型 | 格式 |
|-----------------|-----------------------------| |-----------------|---------------------------|
| empty (default) | [Legacy](./legacy/) | | empty (default) | [Legacy](./legacy/) |
| `tcp` | [TCP](./tcp/) | | `local` | [Local](./local/) |
| `udp` | [UDP](./udp/) | | `hosts` | [Hosts](./hosts/) |
| `tls` | [TLS](./tls/) | | `tcp` | [TCP](./tcp/) |
| `https` | [HTTPS](./https/) | | `udp` | [UDP](./udp/) |
| `quic` | [QUIC](./quic/) | | `tls` | [TLS](./tls/) |
| `h3` | [HTTP/3](./http3/) | | `quic` | [QUIC](./quic/) |
| `predefined` | [Predefined](./predefined/) | | `https` | [HTTPS](./https/) |
| `dhcp` | [DHCP](./dhcp/) | | `h3` | [HTTP/3](./http3/) |
| `fakeip` | [Fake IP](./fakeip/) | | `dhcp` | [DHCP](./dhcp/) |
| `tailscale` | [Tailscale](./tailscale/) | | `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
#### tag #### tag

View File

@ -213,7 +213,7 @@ Set the default route to the Tun.
!!! note "Also enable `auto_redirect`" !!! note "Also enable `auto_redirect`"
`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts with Docker bridge networks. `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 #### iproute2_table_index
@ -239,20 +239,21 @@ Linux iproute2 rule start index generated by `auto_route`.
Only supported on Linux with `auto_route` enabled. Only supported on Linux with `auto_route` enabled.
Automatically configure iptables/nftables to redirect connections. Improve TUN routing and performance using nftables.
Auto redirect is always recommended on Linux, it provides better routing, `auto_redirect` is always recommended on Linux, it provides better routing,
higher performance (better than tproxy), higher performance (better than tproxy),
and avoids conflicts with Docker bridge networks. and avoids conflicts between TUN and Docker bridge networks.
*In Android* Note that `auto_redirect` also works on Android,
but due to the lack of `nftables` and `ip6tables`,
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater, 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). use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
*In Linux*: `auto_redirect` also automatically inserts compatibility rules
into the OpenWrt fw4 table, i.e.
`auto_route` with `auto_redirect` works as expected on routers **without intervention**. it will work on routers without any extra configuration.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`. Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
@ -279,17 +280,15 @@ Enforce strict routing rules when `auto_route` is enabled:
*In Linux*: *In Linux*:
* Let unsupported network unreachable * Let unsupported network unreachable
* Make ICMP traffic route to tun instead of upstream interfaces * For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
* Route all connections to tun
It prevents IP address leaks and makes DNS hijacking work on Android.
*In Windows*: *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) 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 #### route_address

View File

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

View File

@ -26,7 +26,7 @@
| QUIC 客户端 | 类型 | | QUIC 客户端 | 类型 |
|:------------------------:|:----------:| |:------------------------:|:----------:|
| Chromium/Cronet | `chrimium` | | Chromium/Cronet | `chromium` |
| Safari/Apple Network API | `safari` | | Safari/Apple Network API | `safari` |
| Firefox / uquic firefox | `firefox` | | Firefox / uquic firefox | `firefox` |
| quic-go / uquic chrome | `quic-go` | | quic-go / uquic chrome | `quic-go` |

View File

@ -206,7 +206,7 @@ Only take effect when `domain_strategy` or `network_strategy` is set.
!!! failure "Deprecated in sing-box 1.12.0" !!! failure "Deprecated in sing-box 1.12.0"
`domain_strategy` is merged to [domain_resolver](#domain_resolver) in sing-box 1.12.0. `domain_strategy` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver).
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`. Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.

View File

@ -194,6 +194,10 @@ icon: material/new-box
#### domain_strategy #### domain_strategy
!!! failure "已在 sing-box 1.12.0 废弃"
`domain_strategy` 已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver)。
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only` 可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
如果设置,域名将在请求发出之前解析为 IP。 如果设置,域名将在请求发出之前解析为 IP。

View File

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

View File

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

@ -292,7 +292,7 @@ DNS servers are refactored for better performance and scalability.
} }
], ],
"fakeip": { "fakeip": {
"enable": true, "enabled": true,
"inet4_range": "198.18.0.0/15", "inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18" "inet6_range": "fc00::/18"
} }
@ -556,7 +556,8 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
"dns": { "dns": {
"servers": [ "servers": [
{ {
"type": "local" "type": "local",
"tag": "local"
} }
] ]
}, },
@ -586,6 +587,58 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
} }
``` ```
### Migrate outbound domain strategy option to domain resolver
!!! info "References"
[Dial Fields](/configuration/shared/dial/#domain_strategy)
The `domain_strategy` option in Dial Fields has been deprecated and can be replaced with the new domain resolver option.
Note that due to the use of Dial Fields by some of the new DNS servers introduced in sing-box 1.12,
some people mistakenly believe that `domain_strategy` is the same feature as in the legacy DNS servers.
=== ":material-card-remove: Deprecated"
```json
{
"outbounds": [
{
"type": "socks",
"server": "example.org",
"server_port": 2080,
"domain_strategy": "prefer_ipv4",
}
]
}
```
=== ":material-card-multiple: New"
```json
{
"dns": {
"servers": [
{
"type": "local",
"tag": "local"
}
]
},
"outbounds": [
{
"type": "socks",
"server": "example.org",
"server_port": 2080,
"domain_resolver": {
"server": "local",
"strategy": "prefer_ipv4"
}
}
]
}
```
## 1.11.0 ## 1.11.0
### Migrate legacy special outbounds to rule actions ### Migrate legacy special outbounds to rule actions

View File

@ -292,7 +292,7 @@ DNS 服务器已经重构。
} }
], ],
"fakeip": { "fakeip": {
"enable": true, "enabled": true,
"inet4_range": "198.18.0.0/15", "inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18" "inet6_range": "fc00::/18"
} }
@ -556,7 +556,8 @@ DNS 服务器已经重构。
"dns": { "dns": {
"servers": [ "servers": [
{ {
"type": "local" "type": "local",
"tag": "local"
} }
] ]
}, },
@ -586,6 +587,57 @@ DNS 服务器已经重构。
} }
``` ```
### 迁移出站域名策略选项到域名解析器
拨号字段中的 `domain_strategy` 选项已被弃用,可以用新的域名解析器选项替代。
请注意,由于 sing-box 1.12 中引入的一些新 DNS 服务器使用了拨号字段,一些人错误地认为 `domain_strategy` 与旧 DNS 服务器中的功能相同。
!!! info "参考"
[拨号字段](/configuration/shared/dial/#domain_strategy)
=== ":material-card-remove: 弃用的"
```json
{
"outbounds": [
{
"type": "socks",
"server": "example.org",
"server_port": 2080,
"domain_strategy": "prefer_ipv4",
}
]
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
"servers": [
{
"type": "local",
"tag": "local"
}
]
},
"outbounds": [
{
"type": "socks",
"server": "example.org",
"server_port": 2080,
"domain_resolver": {
"server": "local",
"strategy": "prefer_ipv4"
}
}
]
}
```
## 1.11.0 ## 1.11.0
### 迁移旧的特殊出站到规则动作 ### 迁移旧的特殊出站到规则动作

View File

@ -161,6 +161,7 @@ var OptionLegacyDNSFakeIPOptions = Note{
Description: "legacy DNS fakeip options", Description: "legacy DNS fakeip options",
DeprecatedVersion: "1.12.0", DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.14.0", ScheduledVersion: "1.14.0",
EnvName: "LEGACY_DNS_FAKEIP_OPTIONS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats", MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
} }
@ -169,6 +170,7 @@ var OptionOutboundDNSRuleItem = Note{
Description: "outbound DNS rule item", Description: "outbound DNS rule item",
DeprecatedVersion: "1.12.0", DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.14.0", ScheduledVersion: "1.14.0",
EnvName: "OUTBOUND_DNS_RULE_ITEM",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver",
} }
@ -177,6 +179,7 @@ var OptionMissingDomainResolver = Note{
Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields", Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields",
DeprecatedVersion: "1.12.0", DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.14.0", ScheduledVersion: "1.14.0",
EnvName: "MISSING_DOMAIN_RESOLVER",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver",
} }
@ -185,9 +188,19 @@ var OptionLegacyECHOptions = Note{
Description: "legacy ECH options", Description: "legacy ECH options",
DeprecatedVersion: "1.12.0", DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.13.0", ScheduledVersion: "1.13.0",
EnvName: "LEGACY_ECH_OPTIONS",
MigrationLink: "https://sing-box.sagernet.org/deprecated/#legacy-ech-fields", MigrationLink: "https://sing-box.sagernet.org/deprecated/#legacy-ech-fields",
} }
var OptionLegacyDomainStrategyOptions = Note{
Name: "legacy-domain-strategy-options",
Description: "legacy domain strategy options",
DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.14.0",
EnvName: "LEGACY_DOMAIN_STRATEGY_OPTIONS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-domain-strategy-options",
}
var Options = []Note{ var Options = []Note{
OptionBadMatchSource, OptionBadMatchSource,
OptionGEOIP, OptionGEOIP,
@ -204,4 +217,5 @@ var Options = []Note{
OptionOutboundDNSRuleItem, OptionOutboundDNSRuleItem,
OptionMissingDomainResolver, OptionMissingDomainResolver,
OptionLegacyECHOptions, OptionLegacyECHOptions,
OptionLegacyDomainStrategyOptions,
} }

View File

@ -116,6 +116,10 @@ func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network str
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error {
return nil
}
type interfaceMonitorStub struct{} type interfaceMonitorStub struct{}
func (s *interfaceMonitorStub) Start() error { func (s *interfaceMonitorStub) Start() error {
@ -145,8 +149,11 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
} }
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error { func (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) {
return nil }
func (s *interfaceMonitorStub) MyInterface() string {
return ""
} }
func FormatConfig(configContent string) (*StringBox, error) { func FormatConfig(configContent string) (*StringBox, error) {

View File

@ -15,9 +15,10 @@ var (
type platformDefaultInterfaceMonitor struct { type platformDefaultInterfaceMonitor struct {
*platformInterfaceWrapper *platformInterfaceWrapper
element *list.Element[tun.NetworkUpdateCallback] logger logger.Logger
callbacks list.List[tun.DefaultInterfaceUpdateCallback] element *list.Element[tun.NetworkUpdateCallback]
logger logger.Logger callbacks list.List[tun.DefaultInterfaceUpdateCallback]
myInterface string
} }
func (m *platformDefaultInterfaceMonitor) Start() error { func (m *platformDefaultInterfaceMonitor) Start() error {
@ -102,3 +103,15 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s
callback(newInterface, 0) callback(newInterface, 0)
} }
} }
func (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) {
m.defaultInterfaceAccess.Lock()
defer m.defaultInterfaceAccess.Unlock()
m.myInterface = interfaceName
}
func (m *platformDefaultInterfaceMonitor) MyInterface() string {
m.defaultInterfaceAccess.Lock()
defer m.defaultInterfaceAccess.Unlock()
return m.myInterface
}

View File

@ -164,6 +164,7 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
if err != nil { if err != nil {
return nil, E.Cause(err, "query tun name") return nil, E.Cause(err, "query tun name")
} }
options.InterfaceMonitor.RegisterMyInterface(options.Name)
dupFd, err := dup(int(tunFd)) dupFd, err := dup(int(tunFd))
if err != nil { if err != nil {
return nil, E.Cause(err, "dup tun file descriptor") return nil, E.Cause(err, "dup tun file descriptor")

2
go.mod
View File

@ -32,7 +32,7 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056
github.com/sagernet/sing-tun v0.6.3-0.20250409030157-219c612399be github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a
github.com/sagernet/sing-vmess v0.2.0 github.com/sagernet/sing-vmess v0.2.0
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/tailscale v1.80.3-mod.2 github.com/sagernet/tailscale v1.80.3-mod.2

4
go.sum
View File

@ -190,8 +190,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQAHhSXqAfxAw1wDG/QWbdpGH5Na3k8qUynqWnEA= github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQAHhSXqAfxAw1wDG/QWbdpGH5Na3k8qUynqWnEA=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056/go.mod h1:HyacBPIFiKihJQR8LQp56FM4hBtd/7MZXnRxxQIOPsc= github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056/go.mod h1:HyacBPIFiKihJQR8LQp56FM4hBtd/7MZXnRxxQIOPsc=
github.com/sagernet/sing-tun v0.6.3-0.20250409030157-219c612399be h1:E/JnQ1DEz5XIH6VVk3dIMD7aIX5o79jPkbyw57tWlJs= github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a h1:2aLxZFD2HPCLrnFGpH+KBuPqMOk0cuaDE2dgEvANuMk=
github.com/sagernet/sing-tun v0.6.3-0.20250409030157-219c612399be/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI= github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI=
github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= github.com/sagernet/sing-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 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=

View File

@ -94,7 +94,7 @@ func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata a
case 2: case 2:
destination.Addr = i.overrideDestination.Addr destination.Addr = i.overrideDestination.Addr
case 3: case 3:
destination.Port = metadata.Destination.Port destination.Port = i.overrideDestination.Port
} }
metadata.Destination = destination metadata.Destination = destination
if i.overrideOption != 0 { if i.overrideOption != 0 {

View File

@ -396,12 +396,16 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
func (g *URLTestGroup) performUpdateCheck() { func (g *URLTestGroup) performUpdateCheck() {
var updated bool var updated bool
if outbound, exists := g.Select(N.NetworkTCP); outbound != nil && (g.selectedOutboundTCP == nil || (exists && outbound != g.selectedOutboundTCP)) { 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 g.selectedOutboundTCP = outbound
updated = true
} }
if outbound, exists := g.Select(N.NetworkUDP); outbound != nil && (g.selectedOutboundUDP == nil || (exists && outbound != g.selectedOutboundUDP)) { 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 g.selectedOutboundUDP = outbound
updated = true
} }
if updated { if updated {
g.interruptGroup.Interrupt(g.interruptExternalConnections) g.interruptGroup.Interrupt(g.interruptExternalConnections)

View File

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

View File

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

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

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

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

@ -150,7 +150,7 @@ func (e *Endpoint) Start(resolve bool) error {
connectAddr netip.AddrPort connectAddr netip.AddrPort
reserved [3]uint8 reserved [3]uint8
) )
if len(e.peers) == 1 { if len(e.peers) == 1 && e.peers[0].endpoint.IsValid() {
isConnect = true isConnect = true
connectAddr = e.peers[0].endpoint connectAddr = e.peers[0].endpoint
reserved = e.peers[0].reserved reserved = e.peers[0].reserved
@ -208,10 +208,6 @@ func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
return e.tunDevice.ListenPacket(ctx, destination) return e.tunDevice.ListenPacket(ctx, destination)
} }
func (e *Endpoint) BindUpdate() error {
return e.device.BindUpdate()
}
func (e *Endpoint) Close() error { func (e *Endpoint) Close() error {
if e.device != nil { if e.device != nil {
e.device.Close() e.device.Close()