mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-02 06:37:25 +03:00
Compare commits
148 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3cc010880 | ||
|
|
1920c191be | ||
|
|
e0ac459204 | ||
|
|
09fb897805 | ||
|
|
a1b3d891a3 | ||
|
|
d866a40469 | ||
|
|
45cd04b07e | ||
|
|
2cf0528c4d | ||
|
|
905a2ded93 | ||
|
|
cb3c0829c5 | ||
|
|
1a8f6e053d | ||
|
|
99a09a6ce5 | ||
|
|
01b4c7fcdd | ||
|
|
fe89f946c1 | ||
|
|
6c17c7a8f5 | ||
|
|
ea067e5478 | ||
|
|
75af9a824e | ||
|
|
a5d4a42119 | ||
|
|
9821fbc3e3 | ||
|
|
c0408ad1de | ||
|
|
6b0e861afa | ||
|
|
e32d686d6c | ||
|
|
844308e128 | ||
|
|
93c14db281 | ||
|
|
b893a27dfc | ||
|
|
d39960fa23 | ||
|
|
ba0badd4bf | ||
|
|
cfbb5d63d5 | ||
|
|
8447a3edfe | ||
|
|
1a9747a531 | ||
|
|
583ecbea3b | ||
|
|
bb6c8535a5 | ||
|
|
10d90e4acc | ||
|
|
e625012219 | ||
|
|
670863fd5b | ||
|
|
f7cf87142f | ||
|
|
2597a68a01 | ||
|
|
7354332daa | ||
|
|
a0d382fc4e | ||
|
|
a6da8b6654 | ||
|
|
7385616cca | ||
|
|
4b6784b446 | ||
|
|
68579bb93b | ||
|
|
6aace7b1b7 | ||
|
|
148234b742 | ||
|
|
97b7a451be | ||
|
|
73b67e0b48 | ||
|
|
88b4d04d59 | ||
|
|
d1ec6c6dd2 | ||
|
|
523825336a | ||
|
|
032565a026 | ||
|
|
aeea24ae30 | ||
|
|
af22549f1a | ||
|
|
57b17ceb4b | ||
|
|
3dd308e7c3 | ||
|
|
7f75195d86 | ||
|
|
2fe4cad905 | ||
|
|
f55eb75a53 | ||
|
|
5ffb5b6ad2 | ||
|
|
a1d5931759 | ||
|
|
9e68e909cb | ||
|
|
117e8b76cc | ||
|
|
d2f83bfd50 | ||
|
|
eaef13febe | ||
|
|
0110c69dc9 | ||
|
|
fb2f5af1fb | ||
|
|
1553923118 | ||
|
|
0ada49489d | ||
|
|
95d5ca9393 | ||
|
|
6cebbb4590 | ||
|
|
0ef81bb5ef | ||
|
|
0d30a1df9d | ||
|
|
563499d2f9 | ||
|
|
f10c0c1c8d | ||
|
|
428074d88b | ||
|
|
fa18832ad2 | ||
|
|
87bce2de29 | ||
|
|
f5020554e4 | ||
|
|
31f3623b8a | ||
|
|
bb42657177 | ||
|
|
f19ff7eca7 | ||
|
|
8e45133f2e | ||
|
|
63df88675f | ||
|
|
0423244298 | ||
|
|
a5f1af9587 | ||
|
|
112817c1a4 | ||
|
|
6e91de51f1 | ||
|
|
efc5c542fb | ||
|
|
f1b569c7d1 | ||
|
|
a752197d5e | ||
|
|
65517d4513 | ||
|
|
ccf4fa4d3a | ||
|
|
18dbb823a1 | ||
|
|
4ec058e91a | ||
|
|
6eed06b2c2 | ||
|
|
dd209cc9d5 | ||
|
|
b0c0a6b07d | ||
|
|
951a8fabbf | ||
|
|
928298b528 | ||
|
|
5b84fa0137 | ||
|
|
2bb85ac8a1 | ||
|
|
43a9016c83 | ||
|
|
255068fd40 | ||
|
|
098a00b025 | ||
|
|
dba0b5276b | ||
|
|
78ae935468 | ||
|
|
3ea5f76470 | ||
|
|
b4d294c05e | ||
|
|
83cf5f5c6a | ||
|
|
e7b3a8eebe | ||
|
|
ee3a42a67e | ||
|
|
50227c0f5f | ||
|
|
bc5eb1e1a5 | ||
|
|
995267a042 | ||
|
|
41226a6075 | ||
|
|
81d32181ce | ||
|
|
c5ecca3938 | ||
|
|
900888731c | ||
|
|
13e648e4b1 | ||
|
|
aff12ff671 | ||
|
|
101fb88255 | ||
|
|
8b489354e4 | ||
|
|
7dea6eb7a6 | ||
|
|
af1bfe4e3e | ||
|
|
d574e9eb52 | ||
|
|
2d7df1e1f2 | ||
|
|
1c0ffcf5b1 | ||
|
|
348cc39975 | ||
|
|
987899f94a | ||
|
|
d8b2d5142f | ||
|
|
134802d1ee | ||
|
|
e5e81b4de1 | ||
|
|
300c961efa | ||
|
|
7c7f512405 | ||
|
|
03e8d029c2 | ||
|
|
787b5f1931 | ||
|
|
56a7624618 | ||
|
|
3a84acf122 | ||
|
|
f600e02e47 | ||
|
|
e6d19de58a | ||
|
|
f2bbf6b2aa | ||
|
|
c54d50fd36 | ||
|
|
6a051054db | ||
|
|
49498f6439 | ||
|
|
144a890c71 | ||
|
|
afb4993445 | ||
|
|
4c9455b944 | ||
|
|
5fdc051a08 |
30
.fpm_openwrt
Normal file
30
.fpm_openwrt
Normal 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
|
||||
@@ -1,12 +1,14 @@
|
||||
-s dir
|
||||
--name sing-box
|
||||
--category net
|
||||
--license GPLv3-or-later
|
||||
--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
|
||||
--after-install release/config/sing-box.postinst
|
||||
|
||||
release/config/config.json=/etc/sing-box/config.json
|
||||
|
||||
28
.github/deb2ipk.sh
vendored
Executable file
28
.github/deb2ipk.sh
vendored
Executable 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
|
||||
99
.github/workflows/build.yml
vendored
99
.github/workflows/build.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@@ -68,31 +68,38 @@ jobs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ linux, windows, darwin, android ]
|
||||
arch: [ "386", amd64, arm64 ]
|
||||
legacy_go: [ false ]
|
||||
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: 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 }
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||
- { 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: "386", legacy_go: true }
|
||||
- { 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: 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: arm, ndk: "armv7a-linux-androideabi21" }
|
||||
exclude:
|
||||
- { os: darwin, arch: "386" }
|
||||
- { 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
|
||||
@@ -102,7 +109,7 @@ jobs:
|
||||
if: ${{ ! matrix.legacy_go }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Cache Legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
id: cache-legacy-go
|
||||
@@ -133,7 +140,7 @@ jobs:
|
||||
- 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,with_tailscale'
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
if: matrix.os != 'android'
|
||||
@@ -147,7 +154,10 @@ jobs:
|
||||
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 }}
|
||||
- name: Build Android
|
||||
if: matrix.os == 'android'
|
||||
@@ -167,12 +177,17 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set name
|
||||
run: |-
|
||||
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
|
||||
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
|
||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
|
||||
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
||||
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}"
|
||||
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||
@@ -181,10 +196,12 @@ jobs:
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y debsigs
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t deb \
|
||||
-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 }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||
@@ -199,9 +216,10 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t rpm \
|
||||
-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 }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
cat > $HOME/.rpmmacros <<EOF
|
||||
@@ -217,12 +235,29 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libarchive-tools
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t pacman \
|
||||
-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 }} \
|
||||
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
|
||||
@@ -242,7 +277,7 @@ jobs:
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
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
|
||||
@@ -259,7 +294,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@@ -339,7 +374,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@@ -437,7 +472,7 @@ jobs:
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
|
||||
11
.github/workflows/linux.yml
vendored
11
.github/workflows/linux.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
if: matrix.os == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
@@ -80,10 +80,7 @@ jobs:
|
||||
- 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
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
run: |
|
||||
@@ -120,6 +117,7 @@ jobs:
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get install -y debsigs
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t deb \
|
||||
--name "${NAME}" \
|
||||
-v "$PKG_VERSION" \
|
||||
@@ -138,6 +136,7 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t rpm \
|
||||
--name "${NAME}" \
|
||||
-v "$PKG_VERSION" \
|
||||
|
||||
@@ -21,14 +21,13 @@ linters-settings:
|
||||
- -SA1003
|
||||
|
||||
run:
|
||||
go: "1.24"
|
||||
go: "1.23"
|
||||
build-tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ builds:
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
|
||||
@@ -17,7 +17,6 @@ builds:
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
@@ -47,7 +46,6 @@ builds:
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
|
||||
@@ -13,7 +13,7 @@ RUN set -ex \
|
||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||
&& go build -v -trimpath -tags \
|
||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api,with_tailscale" \
|
||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
|
||||
-o /go/bin/sing-box \
|
||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||
./cmd/sing-box
|
||||
|
||||
5
Makefile
5
Makefile
@@ -1,11 +1,10 @@
|
||||
NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
||||
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
|
||||
|
||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||
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 github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
||||
|
||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||
|
||||
@@ -74,6 +74,7 @@ type InboundContext struct {
|
||||
UDPTimeout time.Duration
|
||||
TLSFragment bool
|
||||
TLSFragmentFallbackDelay time.Duration
|
||||
TLSRecordFragment bool
|
||||
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
|
||||
Submodule clients/android updated: 8354b78e5d...320170a107
@@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
tag := tagVersion.VersionString()
|
||||
client := createClient(10 * time.Minute)
|
||||
client := createClient(20 * time.Minute)
|
||||
|
||||
log.Info(tag, " list build IDs")
|
||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||
@@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
build := builds.Data[0]
|
||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
|
||||
log.Info(string(platform), " ", tag, " waiting for process")
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
|
||||
@@ -59,8 +59,8 @@ func init() {
|
||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||
|
||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||
memcTags = append(memcTags, "with_tailscale")
|
||||
debugTags = append(debugTags, "debug")
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@@ -68,6 +67,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||
configPaths = append(configPaths, "config.json")
|
||||
}
|
||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry(), include.ServiceRegistry())
|
||||
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
@@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
if flagRuleSetMatchFormat == "" {
|
||||
switch filepath.Ext(sourcePath) {
|
||||
case ".json":
|
||||
flagRuleSetMatchFormat = C.RuleSetFormatSource
|
||||
case ".srs":
|
||||
flagRuleSetMatchFormat = C.RuleSetFormatBinary
|
||||
}
|
||||
}
|
||||
var ruleSet option.PlainRuleSetCompat
|
||||
switch flagRuleSetMatchFormat {
|
||||
case C.RuleSetFormatSource:
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/utls"
|
||||
|
||||
"github.com/metacubex/utls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -24,8 +25,8 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
|
||||
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
||||
func utlsReadRecord(c *tls.Conn) error
|
||||
|
||||
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
|
||||
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
||||
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
||||
|
||||
@@ -66,11 +66,17 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||
}
|
||||
if options.BindInterface != "" {
|
||||
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
||||
}
|
||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
if options.RoutingMark > 0 {
|
||||
if !C.IsLinux {
|
||||
return nil, E.New("`routing_mark` is only supported on Linux")
|
||||
}
|
||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||
}
|
||||
@@ -91,10 +97,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
if platformInterface != nil {
|
||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||
@@ -106,6 +108,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
}
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
@@ -341,7 +347,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||
return d.udpListener.ListenPacket(context.Background(), network, address)
|
||||
udpListener := d.udpListener
|
||||
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
for _, wgControlFn := range WgControlFns {
|
||||
err := wgControlFn(network, address, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return udpListener.ListenPacket(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
|
||||
@@ -83,6 +83,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||
//nolint:staticcheck
|
||||
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
||||
}
|
||||
server = dialOptions.DomainResolver.Server
|
||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||
@@ -95,22 +96,31 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||
} else if options.DirectResolver {
|
||||
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 {
|
||||
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 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 {
|
||||
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(
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@@ -76,10 +75,11 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||
return c.conn.Write(b)
|
||||
default:
|
||||
}
|
||||
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||
if err != nil {
|
||||
c.conn = nil
|
||||
c.err = E.Cause(err, "dial tcp fast open")
|
||||
c.err = err
|
||||
} else {
|
||||
c.conn = conn
|
||||
}
|
||||
n = len(b)
|
||||
close(c.create)
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var defaultSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
var memorysSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kb": KiByte,
|
||||
"mb": MiByte,
|
||||
"gb": GiByte,
|
||||
"tb": TiByte,
|
||||
"pb": PiByte,
|
||||
"eb": EiByte,
|
||||
"": Byte,
|
||||
"k": KiByte,
|
||||
"m": MiByte,
|
||||
"g": GiByte,
|
||||
"t": TiByte,
|
||||
"p": PiByte,
|
||||
"e": EiByte,
|
||||
}
|
||||
|
||||
var (
|
||||
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
)
|
||||
|
||||
func Bytes(s uint64) string {
|
||||
return humanateBytes(s, 1000, defaultSizes)
|
||||
}
|
||||
|
||||
func MemoryBytes(s uint64) string {
|
||||
return humanateBytes(s, 1024, defaultSizes)
|
||||
}
|
||||
|
||||
func IBytes(s uint64) string {
|
||||
return humanateBytes(s, 1024, iSizes)
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
return parseBytes0(s, defaultSizeTable)
|
||||
}
|
||||
|
||||
func ParseMemoryBytes(s string) (uint64, error) {
|
||||
return parseBytes0(s, memorysSizeTable)
|
||||
}
|
||||
|
||||
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := sizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
@@ -32,6 +32,7 @@ type Listener struct {
|
||||
disablePacketOutput bool
|
||||
setSystemProxy bool
|
||||
systemProxySOCKS bool
|
||||
tproxy bool
|
||||
|
||||
tcpListener net.Listener
|
||||
systemProxy settings.SystemProxy
|
||||
@@ -54,6 +55,7 @@ type Options struct {
|
||||
DisablePacketOutput bool
|
||||
SetSystemProxy bool
|
||||
SystemProxySOCKS bool
|
||||
TProxy bool
|
||||
}
|
||||
|
||||
func New(
|
||||
@@ -71,6 +73,7 @@ func New(
|
||||
disablePacketOutput: options.DisablePacketOutput,
|
||||
setSystemProxy: options.SetSystemProxy,
|
||||
systemProxySOCKS: options.SystemProxySOCKS,
|
||||
tproxy: options.TProxy,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,18 @@ package listener
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/redir"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
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/service"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
@@ -23,6 +27,15 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||
var err error
|
||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||
var listenConfig net.ListenConfig
|
||||
if l.listenOptions.BindInterface != "" {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||
}
|
||||
if l.listenOptions.RoutingMark != 0 {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||
}
|
||||
if l.listenOptions.ReuseAddr {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||
}
|
||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||
if keepIdle == 0 {
|
||||
@@ -40,6 +53,13 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||
}
|
||||
setMultiPathTCP(&listenConfig)
|
||||
}
|
||||
if l.tproxy {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), false)
|
||||
})
|
||||
})
|
||||
}
|
||||
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
||||
if l.listenOptions.TCPFastOpen {
|
||||
var tfoConfig tfo.ListenConfig
|
||||
|
||||
@@ -5,17 +5,30 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/redir"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
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/service"
|
||||
)
|
||||
|
||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||
var lc net.ListenConfig
|
||||
var listenConfig net.ListenConfig
|
||||
if l.listenOptions.BindInterface != "" {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||
}
|
||||
if l.listenOptions.RoutingMark != 0 {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||
}
|
||||
if l.listenOptions.ReuseAddr {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||
}
|
||||
var udpFragment bool
|
||||
if l.listenOptions.UDPFragment != nil {
|
||||
udpFragment = *l.listenOptions.UDPFragment
|
||||
@@ -23,10 +36,17 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
udpFragment = l.listenOptions.UDPFragmentDefault
|
||||
}
|
||||
if !udpFragment {
|
||||
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())
|
||||
}
|
||||
if l.tproxy {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), true)
|
||||
})
|
||||
})
|
||||
}
|
||||
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||
return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -37,8 +57,32 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
return udpConn, err
|
||||
}
|
||||
|
||||
func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
|
||||
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
|
||||
if l.listenOptions.BindInterface != "" {
|
||||
dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||
}
|
||||
if l.listenOptions.RoutingMark != 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||
}
|
||||
if l.listenOptions.ReuseAddr {
|
||||
dialer.Control = control.Append(dialer.Control, control.ReuseAddr())
|
||||
}
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
|
||||
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||
if l.listenOptions.BindInterface != "" {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||
}
|
||||
if l.listenOptions.RoutingMark != 0 {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||
}
|
||||
if l.listenOptions.ReuseAddr {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||
}
|
||||
return listenConfig.ListenPacket(ctx, network, address)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
||||
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||
@@ -20,11 +20,13 @@ func TProxy(fd uintptr, isIPv6 bool) error {
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
||||
}
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
||||
if isUDP {
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/sagernet/sing/common/control"
|
||||
)
|
||||
|
||||
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
|
||||
@@ -31,13 +31,18 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
const header = "BitTorrent protocol"
|
||||
var protocol [19]byte
|
||||
_, err = reader.Read(protocol[:])
|
||||
var n int
|
||||
n, err = reader.Read(protocol[:])
|
||||
if string(protocol[:n]) != header[:n] {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if string(protocol[:]) != "BitTorrent protocol" {
|
||||
return os.ErrInvalid
|
||||
if n < 19 {
|
||||
return ErrNeedMoreData
|
||||
}
|
||||
|
||||
metadata.Protocol = C.ProtocolBitTorrent
|
||||
|
||||
@@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffIncompleteBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("13426974546f7272656e74")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("13426974546f7272656e75")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffUTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -20,22 +20,36 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if length == 0 {
|
||||
if length < 12 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
buffer := buf.NewSize(int(length))
|
||||
defer buffer.Release()
|
||||
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||
var n int
|
||||
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||
packet := buffer.Bytes()
|
||||
if n > 2 && packet[2]&0x80 != 0 { // QR
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
|
||||
return os.ErrInvalid
|
||||
}
|
||||
for i := 6; i < 10; i++ {
|
||||
// ANCOUNT, NSCOUNT
|
||||
if n > i && packet[i] != 0 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
|
||||
return DomainNameQuery(readCtx, metadata, packet)
|
||||
}
|
||||
|
||||
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||
var msg mDNS.Msg
|
||||
err := msg.Unpack(packet)
|
||||
if err != nil {
|
||||
if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
|
||||
return err
|
||||
}
|
||||
metadata.Protocol = C.ProtocolDNS
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
@@ -21,3 +22,32 @@ func TestSniffDNS(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||
}
|
||||
|
||||
func TestSniffStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||
}
|
||||
|
||||
func TestSniffIncompleteStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000001000000000000")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000000000000000000")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
||||
}
|
||||
sniffError = E.Errors(sniffError, err)
|
||||
}
|
||||
if !errors.Is(err, ErrNeedMoreData) {
|
||||
if !errors.Is(sniffError, ErrNeedMoreData) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
const sshPrefix = "SSH-2.0-"
|
||||
bReader := bufio.NewReader(reader)
|
||||
prefix, err := bReader.Peek(len(sshPrefix))
|
||||
if string(prefix[:]) != sshPrefix[:len(prefix)] {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
} else if string(prefix) != sshPrefix {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
fistLine, _, err := bReader.ReadLine()
|
||||
if err != nil {
|
||||
|
||||
@@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) {
|
||||
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
||||
require.Equal(t, "dropbear", metadata.Client)
|
||||
}
|
||||
|
||||
func TestSniffIncompleteSSH(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("5353482d322e30")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotSSH(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("5353482d322e31")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type (
|
||||
STDConfig = tls.Config
|
||||
STDConn = tls.Conn
|
||||
ConnectionState = tls.ConnectionState
|
||||
CurveID = tls.CurveID
|
||||
)
|
||||
|
||||
func ParseTLSVersion(version string) (uint16, error) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
@@ -46,7 +48,10 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
|
||||
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||
return &STDClientConfig{tlsConfig}, nil
|
||||
} else {
|
||||
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil
|
||||
return &STDECHClientConfig{
|
||||
STDClientConfig: STDClientConfig{tlsConfig},
|
||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,11 +104,28 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||
|
||||
type STDECHClientConfig struct {
|
||||
STDClientConfig
|
||||
dnsRouter adapter.DNSRouter
|
||||
access sync.Mutex
|
||||
dnsRouter adapter.DNSRouter
|
||||
lastTTL time.Duration
|
||||
lastUpdate time.Time
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||
message := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
RecursionDesired: true,
|
||||
@@ -133,6 +155,8 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode ECH config")
|
||||
}
|
||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
||||
s.lastUpdate = time.Now()
|
||||
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||
break match
|
||||
}
|
||||
@@ -143,19 +167,11 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
||||
return nil, E.New("no ECH config found in DNS records")
|
||||
}
|
||||
}
|
||||
tlsConn, err := s.Client(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
return s.Client(conn)
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) Clone() Config {
|
||||
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter}
|
||||
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||
}
|
||||
|
||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||
|
||||
5
common/tls/ech_tag_stub.go
Normal file
5
common/tls/ech_tag_stub.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build with_ech
|
||||
|
||||
package tls
|
||||
|
||||
var _ int = "Due to the migration to stdlib, the separate `with_ech` build tag has been deprecated and is no longer needed, please update your build configuration."
|
||||
@@ -29,12 +29,13 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
utls "github.com/sagernet/utls"
|
||||
|
||||
utls "github.com/metacubex/utls"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
@@ -114,6 +115,22 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, extension := range uConn.Extensions {
|
||||
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
|
||||
ce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool {
|
||||
return curveID != utls.X25519MLKEM768
|
||||
})
|
||||
}
|
||||
if ks, ok := extension.(*utls.KeyShareExtension); ok {
|
||||
ks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool {
|
||||
return share.Group != utls.X25519MLKEM768
|
||||
})
|
||||
}
|
||||
}
|
||||
err = uConn.BuildHandshakeState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(uConfig.NextProtos) > 0 {
|
||||
for _, extension := range uConn.Extensions {
|
||||
@@ -148,9 +165,13 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdheKey := uConn.HandshakeState.State13.EcdheKey
|
||||
keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys
|
||||
if keyShareKeys == nil {
|
||||
return nil, E.New("nil KeyShareKeys")
|
||||
}
|
||||
ecdheKey := keyShareKeys.Ecdhe
|
||||
if ecdheKey == nil {
|
||||
return nil, E.New("nil ecdhe_key")
|
||||
return nil, E.New("nil ecdheKey")
|
||||
}
|
||||
authKey, err := ecdheKey.ECDH(publicKey)
|
||||
if err != nil {
|
||||
@@ -214,10 +235,6 @@ func realityClientFallback(ctx context.Context, uConn net.Conn, serverName strin
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||
e.uClient.config.SessionIDGenerator = generator
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) Clone() Config {
|
||||
return &RealityClientConfig{
|
||||
e.ctx,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build with_reality_server
|
||||
//go:build with_utls
|
||||
|
||||
package tls
|
||||
|
||||
@@ -7,28 +7,29 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/reality"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
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/ntp"
|
||||
|
||||
utls "github.com/metacubex/utls"
|
||||
)
|
||||
|
||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||
|
||||
type RealityServerConfig struct {
|
||||
config *reality.Config
|
||||
config *utls.RealityConfig
|
||||
}
|
||||
|
||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
||||
var tlsConfig reality.Config
|
||||
var tlsConfig utls.RealityConfig
|
||||
|
||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||
return nil, E.New("acme is unavailable in reality")
|
||||
@@ -74,6 +75,11 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
||||
}
|
||||
|
||||
tlsConfig.SessionTicketsDisabled = true
|
||||
tlsConfig.Log = func(format string, v ...any) {
|
||||
if logger != nil {
|
||||
logger.Trace(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
tlsConfig.Type = N.NetworkTCP
|
||||
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
||||
|
||||
@@ -113,10 +119,6 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
||||
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
}
|
||||
|
||||
if debug.Enabled {
|
||||
tlsConfig.Show = true
|
||||
}
|
||||
|
||||
return &RealityServerConfig{&tlsConfig}, nil
|
||||
}
|
||||
|
||||
@@ -157,7 +159,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
||||
tlsConn, err := reality.Server(ctx, conn, c.config)
|
||||
tlsConn, err := utls.RealityServer(ctx, conn, c.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -173,7 +175,7 @@ func (c *RealityServerConfig) Clone() Config {
|
||||
var _ Conn = (*realityConnWrapper)(nil)
|
||||
|
||||
type realityConnWrapper struct {
|
||||
*reality.Conn
|
||||
*utls.Conn
|
||||
}
|
||||
|
||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
//go:build !with_reality_server
|
||||
//go:build with_reality_server
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
||||
}
|
||||
var _ int = "The separate `with_reality_server` build tag has been merged into `with_utls` and is no longer needed, please update your build configuration."
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
utls "github.com/sagernet/utls"
|
||||
|
||||
utls "github.com/metacubex/utls"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package tls
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
@@ -14,5 +15,9 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
||||
}
|
||||
|
||||
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
||||
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
|
||||
}
|
||||
|
||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package tf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
@@ -17,17 +19,19 @@ type Conn struct {
|
||||
tcpConn *net.TCPConn
|
||||
ctx context.Context
|
||||
firstPacketWritten bool
|
||||
splitRecord bool
|
||||
fallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
tcpConn: tcpConn,
|
||||
ctx: ctx,
|
||||
splitRecord: splitRecord,
|
||||
fallbackDelay: fallbackDelay,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
@@ -37,10 +41,12 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
}()
|
||||
serverName := indexTLSServerName(b)
|
||||
if serverName != nil {
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(true)
|
||||
if err != nil {
|
||||
return
|
||||
if !c.splitRecord {
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
splits := strings.Split(serverName.ServerName, ".")
|
||||
@@ -61,16 +67,25 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
currentIndex++
|
||||
}
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
for i := 0; i <= len(splitIndexes); i++ {
|
||||
var payload []byte
|
||||
if i == 0 {
|
||||
payload = b[:splitIndexes[i]]
|
||||
if c.splitRecord {
|
||||
payload = payload[recordLayerHeaderLen:]
|
||||
}
|
||||
} else if i == len(splitIndexes) {
|
||||
payload = b[splitIndexes[i-1]:]
|
||||
} else {
|
||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||
}
|
||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
||||
if c.splitRecord {
|
||||
payloadLen := uint16(len(payload))
|
||||
buffer.Write(b[:3])
|
||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
||||
buffer.Write(payload)
|
||||
} else if c.tcpConn != nil && i != len(splitIndexes) {
|
||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -82,11 +97,18 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(false)
|
||||
if c.splitRecord {
|
||||
_, err = c.Conn.Write(buffer.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
32
common/tlsfragment/conn_test.go
Normal file
32
common/tlsfragment/conn_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package tf_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
tf "github.com/sagernet/sing-box/common/tlsfragment"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTLSFragment(t *testing.T) {
|
||||
t.Parallel()
|
||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||
require.NoError(t, err)
|
||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
|
||||
ServerName: "www.cloudflare.com",
|
||||
})
|
||||
require.NoError(t, tlsConn.Handshake())
|
||||
}
|
||||
|
||||
func TestTLSRecordFragment(t *testing.T) {
|
||||
t.Parallel()
|
||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||
require.NoError(t, err)
|
||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
|
||||
ServerName: "www.cloudflare.com",
|
||||
})
|
||||
require.NoError(t, tlsConn.Handshake())
|
||||
}
|
||||
@@ -26,7 +26,6 @@ const (
|
||||
TypeHysteria2 = "hysteria2"
|
||||
TypeTailscale = "tailscale"
|
||||
TypeDERP = "derp"
|
||||
TypeDERPSTUN = "derp-stun"
|
||||
TypeResolved = "resolved"
|
||||
TypeSSMAPI = "ssm-api"
|
||||
)
|
||||
|
||||
6
debug.go
6
debug.go
@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||
if options.MemoryLimit.Value() != 0 {
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
||||
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
@@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
||||
runtime.ReadMemStats(&memStats)
|
||||
|
||||
var memObject badjson.JSONObject
|
||||
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||
memObject.Put("rss", rusageMaxRSS())
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ type Client struct {
|
||||
disableCache bool
|
||||
disableExpire bool
|
||||
independentCache bool
|
||||
clientSubnet netip.Prefix
|
||||
rdrc adapter.RDRCStore
|
||||
initRDRCFunc func() adapter.RDRCStore
|
||||
logger logger.ContextLogger
|
||||
@@ -47,6 +48,7 @@ type ClientOptions struct {
|
||||
DisableExpire bool
|
||||
IndependentCache bool
|
||||
CacheCapacity uint32
|
||||
ClientSubnet netip.Prefix
|
||||
RDRC func() adapter.RDRCStore
|
||||
Logger logger.ContextLogger
|
||||
}
|
||||
@@ -57,6 +59,7 @@ func NewClient(options ClientOptions) *Client {
|
||||
disableCache: options.DisableCache,
|
||||
disableExpire: options.DisableExpire,
|
||||
independentCache: options.IndependentCache,
|
||||
clientSubnet: options.ClientSubnet,
|
||||
initRDRCFunc: options.RDRC,
|
||||
logger: options.Logger,
|
||||
}
|
||||
@@ -104,8 +107,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
return &responseMessage, nil
|
||||
}
|
||||
question := message.Question[0]
|
||||
if options.ClientSubnet.IsValid() {
|
||||
message = SetClientSubnet(message, options.ClientSubnet, true)
|
||||
clientSubnet := options.ClientSubnet
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = c.clientSubnet
|
||||
}
|
||||
if clientSubnet.IsValid() {
|
||||
message = SetClientSubnet(message, clientSubnet)
|
||||
}
|
||||
isSimpleRequest := len(message.Question) == 1 &&
|
||||
len(message.Ns) == 0 &&
|
||||
@@ -232,10 +239,20 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
record.Header().Ttl = timeToLive
|
||||
}
|
||||
}
|
||||
response.Id = messageId
|
||||
if !disableCache {
|
||||
c.storeCache(transport, question, response, timeToLive)
|
||||
}
|
||||
response.Id = messageId
|
||||
requestEDNSOpt := message.IsEdns0()
|
||||
responseEDNSOpt := response.IsEdns0()
|
||||
if responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) {
|
||||
response.Extra = common.Filter(response.Extra, func(it dns.RR) bool {
|
||||
return it.Header().Rrtype != dns.TypeOPT
|
||||
})
|
||||
if requestEDNSOpt != nil {
|
||||
response.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do())
|
||||
}
|
||||
}
|
||||
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||
return response, err
|
||||
}
|
||||
@@ -489,7 +506,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
||||
}
|
||||
|
||||
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
||||
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
||||
if response.Rcode != dns.RcodeSuccess {
|
||||
return nil, RcodeError(response.Rcode)
|
||||
}
|
||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||
|
||||
@@ -6,7 +6,11 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg {
|
||||
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg {
|
||||
return setClientSubnet(message, clientSubnet, true)
|
||||
}
|
||||
|
||||
func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {
|
||||
var (
|
||||
optRecord *dns.OPT
|
||||
subnetOption *dns.EDNS0_SUBNET
|
||||
@@ -19,9 +23,6 @@ findExists:
|
||||
var isEDNS0Subnet bool
|
||||
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
||||
if isEDNS0Subnet {
|
||||
if !override {
|
||||
return message
|
||||
}
|
||||
break findExists
|
||||
}
|
||||
}
|
||||
@@ -37,14 +38,14 @@ findExists:
|
||||
},
|
||||
}
|
||||
message.Extra = append(message.Extra, optRecord)
|
||||
} else {
|
||||
message = message.Copy()
|
||||
} else if clone {
|
||||
return setClientSubnet(message.Copy(), clientSubnet, false)
|
||||
}
|
||||
if subnetOption == nil {
|
||||
subnetOption = new(dns.EDNS0_SUBNET)
|
||||
subnetOption.Code = dns.EDNS0SUBNET
|
||||
optRecord.Option = append(optRecord.Option, subnetOption)
|
||||
}
|
||||
subnetOption.Code = dns.EDNS0SUBNET
|
||||
if clientSubnet.Addr().Is4() {
|
||||
subnetOption.Family = 1
|
||||
} else {
|
||||
|
||||
@@ -55,6 +55,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
||||
RDRC: func() adapter.RDRCStore {
|
||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||
if cacheFile == nil {
|
||||
@@ -258,7 +259,14 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
return FixedResponse(message.Id, message.Question[0], nil, 0), nil
|
||||
return &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: message.Id,
|
||||
Rcode: mDNS.RcodeRefused,
|
||||
Response: true,
|
||||
},
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
}, nil
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
@@ -328,6 +336,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
err error
|
||||
)
|
||||
printResult := func() {
|
||||
if err == nil && len(responseAddrs) == 0 {
|
||||
err = E.New("empty result")
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrResponseRejectedCached) {
|
||||
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||
@@ -336,15 +347,15 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||
}
|
||||
} else if len(responseAddrs) == 0 {
|
||||
r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
||||
err = RcodeNameError
|
||||
}
|
||||
if err != nil {
|
||||
err = E.Cause(err, "lookup ", domain)
|
||||
}
|
||||
}
|
||||
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
||||
if cached {
|
||||
if len(responseAddrs) == 0 {
|
||||
return nil, RcodeNameError
|
||||
return nil, E.New("lookup ", domain, ": empty result (cached)")
|
||||
}
|
||||
return responseAddrs, nil
|
||||
}
|
||||
|
||||
@@ -96,6 +96,9 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 443
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return NewHTTPSRaw(
|
||||
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
||||
logger,
|
||||
|
||||
@@ -3,6 +3,7 @@ package local
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -90,8 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
||||
startRacer := func(ctx context.Context, fqdn string) {
|
||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||
if err == nil {
|
||||
addresses, _ := dns.MessageToAddresses(response)
|
||||
if len(addresses) == 0 {
|
||||
var addresses []netip.Addr
|
||||
addresses, err = dns.MessageToAddresses(response)
|
||||
if err == nil && len(addresses) == 0 {
|
||||
err = E.New(fqdn, ": empty result")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,9 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 443
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return &HTTP3Transport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
||||
logger: logger,
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
sQUIC "github.com/sagernet/sing-quic"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -58,6 +59,9 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 853
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return &Transport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
||||
ctx: ctx,
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@@ -40,6 +41,9 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 53
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return &TCPTransport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
||||
dialer: transportDialer,
|
||||
|
||||
@@ -57,6 +57,9 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 853
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return NewTLSRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), transportDialer, serverAddr, tlsConfig), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -47,6 +48,9 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 53
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
|
||||
}
|
||||
|
||||
@@ -117,7 +121,7 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
||||
conn.access.Unlock()
|
||||
defer func() {
|
||||
conn.access.Lock()
|
||||
delete(conn.callbacks, messageId)
|
||||
delete(conn.callbacks, exMessage.Id)
|
||||
conn.access.Unlock()
|
||||
}()
|
||||
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
||||
|
||||
@@ -2,6 +2,133 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.12.0-beta.22
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-beta.21
|
||||
|
||||
* Fix missing `home` option for DERP service **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
You can now choose what the DERP home page shows, just like with derper's `-home` flag.
|
||||
|
||||
See [DERP](/configuration/service/derp/#home).
|
||||
|
||||
### 1.11.13
|
||||
|
||||
* 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.12.0-beta.17
|
||||
|
||||
* Update quic-go to v0.52.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-beta.15
|
||||
|
||||
* Add DERP service **1**
|
||||
* Add Resolved service and DNS server **2**
|
||||
* Add SSM API service **3**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
||||
|
||||
See [DERP Service](/configuration/service/derp/).
|
||||
|
||||
**2**:
|
||||
|
||||
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
||||
(e.g. NetworkManager) and provide DNS resolution.
|
||||
|
||||
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
|
||||
|
||||
**3**:
|
||||
|
||||
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
||||
|
||||
See [SSM API Service](/configuration/service/ssm-api/).
|
||||
|
||||
### 1.11.11
|
||||
|
||||
* 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.12.0-beta.13
|
||||
|
||||
* Add TLS record fragment route options **1**
|
||||
* Add missing `accept_routes` option for Tailscale **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Route Action](/configuration/route/rule_action/#tls_record_fragment).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Tailscale](/configuration/endpoint/tailscale/#accept_routes).
|
||||
|
||||
#### 1.12.0-beta.10
|
||||
|
||||
* Add control options for listeners **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen/).
|
||||
|
||||
### 1.11.10
|
||||
|
||||
* Undeprecate the `block` outbound **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Since we don’t have a replacement for using the `block` outbound in selectors yet,
|
||||
we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future.
|
||||
|
||||
_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.9
|
||||
|
||||
* Update quic-go to v0.51.0
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.9
|
||||
|
||||
* 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.12.0-beta.5
|
||||
|
||||
* 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
|
||||
|
||||
* Fixes and improvements
|
||||
@@ -10,7 +137,8 @@ icon: material/alert-decagram
|
||||
|
||||
* 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)._
|
||||
_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.1
|
||||
|
||||
@@ -25,7 +153,8 @@ see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
* 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)._
|
||||
_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-alpha.19
|
||||
|
||||
@@ -65,7 +194,8 @@ See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
||||
|
||||
* 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)._
|
||||
_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-alpha.13
|
||||
|
||||
@@ -136,7 +266,8 @@ For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and pa
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
|
||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration
|
||||
process._
|
||||
|
||||
#### 1.12.0-alpha.5
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ and the data generated by the software is always on your device.
|
||||
|
||||
## 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,
|
||||
sing-box uses the location permission in the background
|
||||
to get information about the connected Wi-Fi network to make them work.
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-decagram: [servers](#servers)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-decagram: [servers](#servers)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
||||
@@ -81,7 +81,7 @@ Will overrides `dns.client_subnet`.
|
||||
|
||||
#### method
|
||||
|
||||
- `default`: Reply with NXDOMAIN.
|
||||
- `default`: Reply with REFUSED.
|
||||
- `drop`: Drop the request.
|
||||
|
||||
`default` will be used by default.
|
||||
|
||||
@@ -81,7 +81,7 @@ icon: material/new-box
|
||||
|
||||
#### method
|
||||
|
||||
- `default`: 返回 NXDOMAIN。
|
||||
- `default`: 返回 REFUSED。
|
||||
- `drop`: 丢弃请求。
|
||||
|
||||
默认使用 `defualt`。
|
||||
|
||||
@@ -27,19 +27,21 @@ icon: material/alert-decagram
|
||||
|
||||
The type of the DNS server.
|
||||
|
||||
| Type | Format |
|
||||
|-----------------|-----------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `predefined` | [Predefined](./predefined/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| Type | Format |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| `resolved` | [Resolved](./resolved/) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
||||
@@ -27,19 +27,21 @@ icon: material/alert-decagram
|
||||
|
||||
DNS 服务器的类型。
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-----------------|-----------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `predefined` | [Predefined](./predefined/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| 类型 | 格式 |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| `resolved` | [Resolved](./resolved/) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
||||
84
docs/configuration/dns/server/resolved.md
Normal file
84
docs/configuration/dns/server/resolved.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Resolved
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "resolved",
|
||||
"tag": "",
|
||||
|
||||
"service": "resolved",
|
||||
"accept_default_resolvers": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Fields
|
||||
|
||||
#### service
|
||||
|
||||
==Required==
|
||||
|
||||
The tag of the [Resolved Service](/configuration/service/resolved).
|
||||
|
||||
#### accept_default_resolvers
|
||||
|
||||
Indicates whether the default DNS resolvers should be accepted for fallback queries in addition to matching domains.
|
||||
|
||||
Specifically, default DNS resolvers are DNS servers that have `SetLinkDefaultRoute` or `SetLinkDomains ~.` set.
|
||||
|
||||
If not enabled, `NXDOMAIN` will be returned for requests that do not match search or match domains.
|
||||
|
||||
### Examples
|
||||
|
||||
=== "Split DNS only"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "local",
|
||||
"tag": "local"
|
||||
},
|
||||
{
|
||||
"type": "resolved",
|
||||
"tag": "resolved",
|
||||
"service": "resolved"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"ip_accept_any": true,
|
||||
"server": "resolved"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Use as global DNS"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "resolved",
|
||||
"service": "resolved",
|
||||
"accept_default_resolvers": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -30,13 +30,13 @@ icon: material/new-box
|
||||
|
||||
==Required==
|
||||
|
||||
The tag of the Tailscale endpoint.
|
||||
The tag of the [Tailscale Endpoint](/configuration/endpoint/tailscale).
|
||||
|
||||
#### accept_default_resolvers
|
||||
|
||||
Indicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。
|
||||
|
||||
if not enabled, NXDOMAIN will be returned for non-Tailscale domain queries.
|
||||
if not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries.
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -80,4 +80,4 @@ if not enabled, NXDOMAIN will be returned for non-Tailscale domain queries.
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -6,7 +6,7 @@ icon: material/new-box
|
||||
|
||||
# Endpoint
|
||||
|
||||
Endpoint is protocols that has both inbound and outbound behavior.
|
||||
An endpoint is a protocol with inbound and outbound behavior.
|
||||
|
||||
### Structure
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ icon: material/new-box
|
||||
"control_url": "",
|
||||
"ephemeral": false,
|
||||
"hostname": "",
|
||||
"accept_routes": false,
|
||||
"exit_node": "",
|
||||
"exit_node_allow_lan_access": false,
|
||||
"advertise_routes": [],
|
||||
@@ -62,6 +63,10 @@ System hostname is used by default.
|
||||
|
||||
Example: `localhost`
|
||||
|
||||
#### accept_routes
|
||||
|
||||
Indicates whether the node should accept routes advertised by other nodes.
|
||||
|
||||
#### exit_node
|
||||
|
||||
The exit node name or IP address to use.
|
||||
|
||||
@@ -42,16 +42,18 @@ AnyTLS padding scheme line array.
|
||||
|
||||
Default padding scheme:
|
||||
|
||||
```
|
||||
stop=8
|
||||
0=30-30
|
||||
1=100-400
|
||||
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||
3=9-9,500-1000
|
||||
4=500-1000
|
||||
5=500-1000
|
||||
6=500-1000
|
||||
7=500-1000
|
||||
```json
|
||||
[
|
||||
"stop=8",
|
||||
"0=30-30",
|
||||
"1=100-400",
|
||||
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
|
||||
"3=9-9,500-1000",
|
||||
"4=500-1000",
|
||||
"5=500-1000",
|
||||
"6=500-1000",
|
||||
"7=500-1000"
|
||||
]
|
||||
```
|
||||
|
||||
#### tls
|
||||
|
||||
@@ -42,16 +42,18 @@ AnyTLS 填充方案行数组。
|
||||
|
||||
默认填充方案:
|
||||
|
||||
```
|
||||
stop=8
|
||||
0=30-30
|
||||
1=100-400
|
||||
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||
3=9-9,500-1000
|
||||
4=500-1000
|
||||
5=500-1000
|
||||
6=500-1000
|
||||
7=500-1000
|
||||
```json
|
||||
[
|
||||
"stop=8",
|
||||
"0=30-30",
|
||||
"1=100-400",
|
||||
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
|
||||
"3=9-9,500-1000",
|
||||
"4=500-1000",
|
||||
"5=500-1000",
|
||||
"6=500-1000",
|
||||
"7=500-1000"
|
||||
]
|
||||
```
|
||||
|
||||
#### tls
|
||||
|
||||
@@ -398,11 +398,11 @@ UDP NAT 过期时间。
|
||||
|
||||
TCP/IP 栈。
|
||||
|
||||
| 栈 | 描述 |
|
||||
|--------|------------------------------------------------------------------|
|
||||
| system | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||
| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||
| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||
| 栈 | 描述 |
|
||||
|----------|-------------------------------------------------------------------------------------------------------|
|
||||
| `system` | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||
| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||
| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||
|
||||
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ sing-box uses JSON for configuration files.
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
"services": [],
|
||||
"experimental": {}
|
||||
}
|
||||
```
|
||||
@@ -30,6 +31,7 @@ sing-box uses JSON for configuration files.
|
||||
| `inbounds` | [Inbound](./inbound/) |
|
||||
| `outbounds` | [Outbound](./outbound/) |
|
||||
| `route` | [Route](./route/) |
|
||||
| `services` | [Service](./service/) |
|
||||
| `experimental` | [Experimental](./experimental/) |
|
||||
|
||||
### Check
|
||||
|
||||
@@ -14,6 +14,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
"services": [],
|
||||
"experimental": {}
|
||||
}
|
||||
```
|
||||
@@ -30,6 +31,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
| `inbounds` | [入站](./inbound/) |
|
||||
| `outbounds` | [出站](./outbound/) |
|
||||
| `route` | [路由](./route/) |
|
||||
| `services` | [服务](./service/) |
|
||||
| `experimental` | [实验性](./experimental/) |
|
||||
|
||||
### 检查
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||
|
||||
`block` 出站关闭所有传入请求。
|
||||
|
||||
### 结构
|
||||
|
||||
@@ -6,6 +6,7 @@ icon: material/new-box
|
||||
|
||||
:material-plus: [tls_fragment](#tls_fragment)
|
||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
||||
:material-plus: [resolve.disable_cache](#disable_cache)
|
||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
||||
@@ -91,7 +92,8 @@ Not available when `method` is set to drop.
|
||||
"udp_connect": false,
|
||||
"udp_timeout": "",
|
||||
"tls_fragment": false,
|
||||
"tls_fragment_fallback_delay": ""
|
||||
"tls_fragment_fallback_delay": "",
|
||||
"tls_record_fragment": ""
|
||||
}
|
||||
```
|
||||
|
||||
@@ -164,13 +166,19 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
|
||||
|
||||
Fragment TLS handshakes to bypass firewalls.
|
||||
|
||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
|
||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
||||
and should not be used to circumvent real censorship.
|
||||
|
||||
Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked.
|
||||
Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.
|
||||
|
||||
On Linux, Apple platforms, (administrator privileges required) Windows, the wait time can be automatically detected, otherwise it will fall back to waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
||||
On Linux, Apple platforms, (administrator privileges required) Windows,
|
||||
the wait time can be automatically detected, otherwise it will fall back to
|
||||
waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
||||
|
||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, because the target is considered to be local or behind a transparent proxy.
|
||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
||||
because the target is considered to be local or behind a transparent proxy.
|
||||
|
||||
Conflict with `tls_record_fragment`.
|
||||
|
||||
#### tls_fragment_fallback_delay
|
||||
|
||||
@@ -180,6 +188,17 @@ The fallback value used when TLS segmentation cannot automatically determine the
|
||||
|
||||
`500ms` is used by default.
|
||||
|
||||
#### tls_record_fragment
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
||||
|
||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
||||
and should not be used to circumvent real censorship.
|
||||
|
||||
Conflict with `tls_fragment`.
|
||||
|
||||
### sniff
|
||||
|
||||
```json
|
||||
|
||||
@@ -5,7 +5,11 @@ icon: material/new-box
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [tls_fragment](#tls_fragment)
|
||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
||||
:material-plus: [resolve.disable_cache](#disable_cache)
|
||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
||||
|
||||
## 最终动作
|
||||
|
||||
@@ -159,12 +163,15 @@ UDP 连接超时时间。
|
||||
|
||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||
|
||||
由于它不是为性能设计的,不应被应用于所有连接,而仅应用于已知被阻止的服务器名称。
|
||||
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
|
||||
|
||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
|
||||
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
||||
|
||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||
|
||||
与 `tls_record_fragment` 冲突。
|
||||
|
||||
#### tls_fragment_fallback_delay
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
@@ -173,6 +180,16 @@ UDP 连接超时时间。
|
||||
|
||||
默认使用 `500ms`。
|
||||
|
||||
#### tls_record_fragment
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
||||
|
||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||
|
||||
与 `tls_fragment` 冲突。
|
||||
|
||||
### sniff
|
||||
|
||||
```json
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
| QUIC 客户端 | 类型 |
|
||||
|:------------------------:|:----------:|
|
||||
| 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` |
|
||||
|
||||
@@ -80,6 +80,8 @@ List of [Headless Rule](./headless-rule/).
|
||||
|
||||
Format of rule-set file, `source` or `binary`.
|
||||
|
||||
Optional when `path` or `url` uses `json` or `srs` as extension.
|
||||
|
||||
### Local Fields
|
||||
|
||||
#### path
|
||||
|
||||
@@ -80,6 +80,8 @@
|
||||
|
||||
规则集格式, `source` 或 `binary`。
|
||||
|
||||
当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。
|
||||
|
||||
### 本地字段
|
||||
|
||||
#### path
|
||||
|
||||
135
docs/configuration/service/derp.md
Normal file
135
docs/configuration/service/derp.md
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# DERP
|
||||
|
||||
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "derp",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"tls": {},
|
||||
"config_path": "",
|
||||
"verify_client_endpoint": [],
|
||||
"verify_client_url": [],
|
||||
"home": "",
|
||||
"mesh_with": [],
|
||||
"mesh_psk": "",
|
||||
"mesh_psk_file": "",
|
||||
"stun": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||
|
||||
### Fields
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### config_path
|
||||
|
||||
==Required==
|
||||
|
||||
Derper configuration file path.
|
||||
|
||||
Example: `derper.key`
|
||||
|
||||
#### verify_client_endpoint
|
||||
|
||||
Tailscale endpoints tags to verify clients.
|
||||
|
||||
#### verify_client_url
|
||||
|
||||
URL to verify clients.
|
||||
|
||||
Object format:
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "https://my-headscale.com/verify",
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
|
||||
Setting Array value to a string `__URL__` is equivalent to configuring:
|
||||
|
||||
```json
|
||||
{ "url": __URL__ }
|
||||
```
|
||||
|
||||
#### home
|
||||
|
||||
What to serve at the root path. It may be left empty (the default, for a default homepage), `blank` for a blank page, or a URL to redirect to
|
||||
|
||||
#### mesh_with
|
||||
|
||||
Mesh with other DERP servers.
|
||||
|
||||
Object format:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": "",
|
||||
"server_port": "",
|
||||
"host": "",
|
||||
"tls": {},
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
|
||||
Object fields:
|
||||
|
||||
- `server`: **Required** DERP server address.
|
||||
- `server_port`: **Required** DERP server port.
|
||||
- `host`: Custom DERP hostname.
|
||||
- `tls`: [TLS](/configuration/shared/tls/#outbound)
|
||||
- `Dial Fields`: [Dial Fields](/configuration/shared/dial/)
|
||||
|
||||
#### mesh_psk
|
||||
|
||||
Pre-shared key for DERP mesh.
|
||||
|
||||
#### mesh_psk_file
|
||||
|
||||
Pre-shared key file for DERP mesh.
|
||||
|
||||
#### stun
|
||||
|
||||
STUN server listen options.
|
||||
|
||||
Object format:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
|
||||
... // Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
Object fields:
|
||||
|
||||
- `enabled`: **Required** Enable STUN server.
|
||||
- `listen`: **Required** STUN server listen address, default to `::`.
|
||||
- `listen_port`: **Required** STUN server listen port, default to `3478`.
|
||||
- `other Listen Fields`: [Listen Fields](/configuration/shared/listen/)
|
||||
|
||||
Setting `stun` value to a number `__PORT__` is equivalent to configuring:
|
||||
|
||||
```json
|
||||
{ "enabled": true, "listen_port": __PORT__ }
|
||||
```
|
||||
32
docs/configuration/service/index.md
Normal file
32
docs/configuration/service/index.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Service
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"services": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Type | Format |
|
||||
|------------|------------------------|
|
||||
| `derp` | [DERP](./derp) |
|
||||
| `resolved` | [Resolved](./resolved) |
|
||||
| `ssm-api` | [SSM API](./ssm-api) |
|
||||
|
||||
#### tag
|
||||
|
||||
The tag of the endpoint.
|
||||
44
docs/configuration/service/resolved.md
Normal file
44
docs/configuration/service/resolved.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Resolved
|
||||
|
||||
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
||||
(e.g. NetworkManager) and provide DNS resolution.
|
||||
|
||||
See also: [Resolved DNS Server](/configuration/dns/server/resolved/)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "resolved",
|
||||
|
||||
... // Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||
|
||||
### Fields
|
||||
|
||||
#### listen
|
||||
|
||||
==Required==
|
||||
|
||||
Listen address.
|
||||
|
||||
`127.0.0.53` will be used by default.
|
||||
|
||||
#### listen_port
|
||||
|
||||
==Required==
|
||||
|
||||
Listen port.
|
||||
|
||||
`53` will be used by default.
|
||||
52
docs/configuration/service/ssm-api.md
Normal file
52
docs/configuration/service/ssm-api.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# SSM API
|
||||
|
||||
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
||||
|
||||
See https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ssm-api",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"servers": {},
|
||||
"tls": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||
|
||||
### Fields
|
||||
|
||||
#### servers
|
||||
|
||||
==Required==
|
||||
|
||||
A mapping Object from HTTP endpoints to [Shadowsocks Inbound](/configuration/inbound/shadowsocks) tags.
|
||||
|
||||
Selected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"/": "ss-in"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
@@ -25,11 +25,12 @@ icon: material/new-box
|
||||
"inet6_bind_address": "",
|
||||
"routing_mark": 0,
|
||||
"reuse_addr": false,
|
||||
"netns": "",
|
||||
"connect_timeout": "",
|
||||
"tcp_fast_open": false,
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"netns": "",
|
||||
|
||||
"domain_resolver": "", // or {}
|
||||
"network_strategy": "",
|
||||
"network_type": [],
|
||||
@@ -37,6 +38,7 @@ icon: material/new-box
|
||||
"fallback_delay": "",
|
||||
|
||||
// Deprecated
|
||||
|
||||
"domain_strategy": ""
|
||||
}
|
||||
```
|
||||
@@ -73,10 +75,22 @@ The IPv6 address to bind to.
|
||||
|
||||
Set netfilter routing mark.
|
||||
|
||||
Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported.
|
||||
|
||||
#### reuse_addr
|
||||
|
||||
Reuse listener address.
|
||||
|
||||
#### netns
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux.
|
||||
|
||||
Set network namespace, name or path.
|
||||
|
||||
#### connect_timeout
|
||||
|
||||
Connect timeout, in golang's Duration format.
|
||||
@@ -102,16 +116,6 @@ Enable TCP Multi Path.
|
||||
|
||||
Enable UDP fragmentation.
|
||||
|
||||
#### netns
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux.
|
||||
|
||||
Set network namespace, name or path.
|
||||
|
||||
#### domain_resolver
|
||||
|
||||
!!! warning ""
|
||||
@@ -206,7 +210,7 @@ Only take effect when `domain_strategy` or `network_strategy` is set.
|
||||
|
||||
!!! 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`.
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@ icon: material/new-box
|
||||
"inet6_bind_address": "",
|
||||
"routing_mark": 0,
|
||||
"reuse_addr": false,
|
||||
"netns": "",
|
||||
"connect_timeout": "",
|
||||
"tcp_fast_open": false,
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"netns": "",
|
||||
"domain_resolver": "", // 或 {}
|
||||
"network_strategy": "",
|
||||
"network_type": [],
|
||||
@@ -74,10 +74,22 @@ icon: material/new-box
|
||||
|
||||
设置 netfilter 路由标记。
|
||||
|
||||
支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。
|
||||
|
||||
#### reuse_addr
|
||||
|
||||
重用监听地址。
|
||||
|
||||
#### netns
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
设置网络命名空间,名称或路径。
|
||||
|
||||
#### connect_timeout
|
||||
|
||||
连接超时,采用 golang 的 Duration 格式。
|
||||
@@ -101,16 +113,6 @@ icon: material/new-box
|
||||
|
||||
启用 UDP 分段。
|
||||
|
||||
#### netns
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
设置网络命名空间,名称或路径。
|
||||
|
||||
#### domain_resolver
|
||||
|
||||
!!! warning ""
|
||||
@@ -194,6 +196,10 @@ icon: material/new-box
|
||||
|
||||
#### 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`。
|
||||
|
||||
如果设置,域名将在请求发出之前解析为 IP。
|
||||
|
||||
@@ -4,7 +4,10 @@ icon: material/new-box
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [netns](#netns)
|
||||
:material-plus: [netns](#netns)
|
||||
:material-plus: [bind_interface](#bind_interface)
|
||||
:material-plus: [routing_mark](#routing_mark)
|
||||
:material-plus: [reuse_addr](#reuse_addr)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
@@ -20,12 +23,18 @@ icon: material/new-box
|
||||
{
|
||||
"listen": "",
|
||||
"listen_port": 0,
|
||||
"bind_interface": "",
|
||||
"routing_mark": 0,
|
||||
"reuse_addr": false,
|
||||
"netns": "",
|
||||
"tcp_fast_open": false,
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"udp_timeout": "",
|
||||
"netns": "",
|
||||
"detour": "",
|
||||
|
||||
// Deprecated
|
||||
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"sniff_timeout": "",
|
||||
@@ -36,15 +45,6 @@ icon: material/new-box
|
||||
|
||||
### Fields
|
||||
|
||||
| Field | Available Context |
|
||||
|--------------------------------|---------------------------------------------------------|
|
||||
| `listen` | Needs to listen on TCP or UDP. |
|
||||
| `listen_port` | Needs to listen on TCP or UDP. |
|
||||
| `tcp_fast_open` | Needs to listen on TCP. |
|
||||
| `tcp_multi_path` | Needs to listen on TCP. |
|
||||
| `udp_timeout` | Needs to assemble UDP connections. |
|
||||
| `udp_disable_domain_unmapping` | Needs to listen on UDP and accept domain UDP addresses. |
|
||||
|
||||
#### listen
|
||||
|
||||
==Required==
|
||||
@@ -55,6 +55,40 @@ Listen address.
|
||||
|
||||
Listen port.
|
||||
|
||||
#### bind_interface
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
The network interface to bind to.
|
||||
|
||||
#### routing_mark
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux.
|
||||
|
||||
Set netfilter routing mark.
|
||||
|
||||
Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported.
|
||||
|
||||
#### reuse_addr
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Reuse listener address.
|
||||
|
||||
#### netns
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux.
|
||||
|
||||
Set network namespace, name or path.
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
Enable TCP Fast Open.
|
||||
@@ -77,16 +111,6 @@ UDP NAT expiration time.
|
||||
|
||||
`5m` will be used by default.
|
||||
|
||||
#### netns
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux.
|
||||
|
||||
Set network namespace, name or path.
|
||||
|
||||
#### detour
|
||||
|
||||
If set, connections will be forwarded to the specified inbound.
|
||||
|
||||
@@ -4,7 +4,10 @@ icon: material/new-box
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [netns](#netns)
|
||||
:material-plus: [netns](#netns)
|
||||
:material-plus: [bind_interface](#bind_interface)
|
||||
:material-plus: [routing_mark](#routing_mark)
|
||||
:material-plus: [reuse_addr](#reuse_addr)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
@@ -20,12 +23,18 @@ icon: material/new-box
|
||||
{
|
||||
"listen": "",
|
||||
"listen_port": 0,
|
||||
"bind_interface": "",
|
||||
"routing_mark": 0,
|
||||
"reuse_addr": false,
|
||||
"netns": "",
|
||||
"tcp_fast_open": false,
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"udp_timeout": "",
|
||||
"netns": "",
|
||||
"detour": "",
|
||||
|
||||
// 废弃的
|
||||
|
||||
"sniff": false,
|
||||
"sniff_override_destination": false,
|
||||
"sniff_timeout": "",
|
||||
@@ -34,16 +43,6 @@ icon: material/new-box
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
| 字段 | 可用上下文 |
|
||||
|------------------|-----------------|
|
||||
| `listen` | 需要监听 TCP 或 UDP。 |
|
||||
| `listen_port` | 需要监听 TCP 或 UDP。 |
|
||||
| `tcp_fast_open` | 需要监听 TCP。 |
|
||||
| `tcp_multi_path` | 需要监听 TCP。 |
|
||||
| `udp_timeout` | 需要组装 UDP 连接。 |
|
||||
|
|
||||
|
||||
### 字段
|
||||
|
||||
#### listen
|
||||
@@ -56,6 +55,40 @@ icon: material/new-box
|
||||
|
||||
监听端口。
|
||||
|
||||
#### bind_interface
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
要绑定到的网络接口。
|
||||
|
||||
#### routing_mark
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
设置 netfilter 路由标记。
|
||||
|
||||
支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。
|
||||
|
||||
#### reuse_addr
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
重用监听地址。
|
||||
|
||||
#### netns
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
设置网络命名空间,名称或路径。
|
||||
|
||||
#### tcp_fast_open
|
||||
|
||||
启用 TCP Fast Open。
|
||||
@@ -78,16 +111,6 @@ UDP NAT 过期时间。
|
||||
|
||||
默认使用 `5m`。
|
||||
|
||||
#### netns
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux。
|
||||
|
||||
设置网络命名空间,名称或路径。
|
||||
|
||||
#### detour
|
||||
|
||||
如果设置,连接将被转发到指定的入站。
|
||||
|
||||
@@ -52,7 +52,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
||||
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). |
|
||||
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). |
|
||||
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
|
||||
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||
|
||||
@@ -56,7 +56,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
||||
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). |
|
||||
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). |
|
||||
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
|
||||
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
||||
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||
|
||||
@@ -9,43 +9,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
|
||||
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 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
|
||||
```
|
||||
(This applies to any distribution that uses `dnf` as the package manager: Fedora, CentOS, even OpenSUSE with DNF installed.)
|
||||
|
||||
## :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
|
||||
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
|
||||
```
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / RPM"
|
||||
or latest beta:
|
||||
|
||||
```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.)
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
|
||||
```
|
||||
|
||||
=== ":simple-archlinux: Archlinux / PKG"
|
||||
or specific version:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
|
||||
```
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
|
||||
```
|
||||
|
||||
## :material-book-lock-open: Managed Installation
|
||||
|
||||
|
||||
@@ -9,43 +9,55 @@ 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 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
|
||||
```
|
||||
(这适用于任何使用 `dnf` 作为包管理器的发行版:Fedora、CentOS,甚至安装了 DNF 的 OpenSUSE。)
|
||||
|
||||
## :material-download-box: 手动安装
|
||||
|
||||
=== ":material-debian: Debian / DEB"
|
||||
该脚本从 GitHub 发布中下载并安装最新的软件包,适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
|
||||
```
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / RPM"
|
||||
或最新测试版:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
||||
```
|
||||
(这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。)
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
|
||||
```
|
||||
|
||||
=== ":simple-archlinux: Archlinux / PKG"
|
||||
或指定版本:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
|
||||
```
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
|
||||
```
|
||||
|
||||
## :material-book-lock-open: 托管安装
|
||||
|
||||
|
||||
116
docs/installation/tools/install.sh
Executable file
116
docs/installation/tools/install.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/bin/sh
|
||||
|
||||
download_beta=false
|
||||
download_version=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--beta)
|
||||
download_beta=true
|
||||
shift
|
||||
;;
|
||||
--version)
|
||||
shift
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Missing argument for --version"
|
||||
echo "Usage: $0 [--beta] [--version <version>]"
|
||||
exit 1
|
||||
fi
|
||||
download_version="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1"
|
||||
echo "Usage: $0 [--beta] [--version <version>]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if command -v pacman >/dev/null 2>&1; then
|
||||
os="linux"
|
||||
arch=$(uname -m)
|
||||
package_suffix=".pkg.tar.zst"
|
||||
package_install="pacman -U --noconfirm"
|
||||
elif command -v dpkg >/dev/null 2>&1; then
|
||||
os="linux"
|
||||
arch=$(dpkg --print-architecture)
|
||||
package_suffix=".deb"
|
||||
package_install="dpkg -i"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
os="linux"
|
||||
arch=$(uname -m)
|
||||
package_suffix=".rpm"
|
||||
package_install="dnf install -y"
|
||||
elif command -v rpm >/dev/null 2>&1; then
|
||||
os="linux"
|
||||
arch=$(uname -m)
|
||||
package_suffix=".rpm"
|
||||
package_install="rpm -i"
|
||||
elif command -v opkg >/dev/null 2>&1; then
|
||||
os="openwrt"
|
||||
. /etc/os-release
|
||||
arch="$OPENWRT_ARCH"
|
||||
package_suffix=".ipk"
|
||||
package_install="opkg update && opkg install"
|
||||
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 -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest)
|
||||
else
|
||||
latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest)
|
||||
fi
|
||||
curl_exit_status=$?
|
||||
if [ $curl_exit_status -ne 0 ]; then
|
||||
exit $curl_exit_status
|
||||
fi
|
||||
if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then
|
||||
echo "$latest_release"
|
||||
exit 1
|
||||
fi
|
||||
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g')
|
||||
else
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases)
|
||||
else
|
||||
latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases)
|
||||
fi
|
||||
curl_exit_status=$?
|
||||
if [ $curl_exit_status -ne 0 ]; then
|
||||
exit $curl_exit_status
|
||||
fi
|
||||
if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then
|
||||
echo "$latest_release"
|
||||
exit 1
|
||||
fi
|
||||
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g')
|
||||
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 -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url"
|
||||
else
|
||||
curl --fail -Lo "$package_name" "$package_url"
|
||||
fi
|
||||
|
||||
curl_exit_status=$?
|
||||
if [ $curl_exit_status -ne 0 ]; then
|
||||
exit $curl_exit_status
|
||||
fi
|
||||
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
package_install="sudo $package_install"
|
||||
fi
|
||||
|
||||
echo "$package_install $package_name"
|
||||
sh -c "$package_install \"$package_name\""
|
||||
rm -f "$package_name"
|
||||
@@ -292,7 +292,7 @@ DNS servers are refactored for better performance and scalability.
|
||||
}
|
||||
],
|
||||
"fakeip": {
|
||||
"enable": true,
|
||||
"enabled": true,
|
||||
"inet4_range": "198.18.0.0/15",
|
||||
"inet6_range": "fc00::/18"
|
||||
}
|
||||
@@ -516,13 +516,13 @@ DNS servers are refactored for better performance and scalability.
|
||||
The legacy outbound DNS rules are deprecated and can be replaced by new domain resolver options.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
|
||||
[DNS rule](/configuration/dns/rule/#outbound) /
|
||||
[Dial Fields](/configuration/shared/dial/#domain_resolver) /
|
||||
[Route](/configuration/route/#domain_resolver)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -556,7 +556,8 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
|
||||
"dns": {
|
||||
"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
|
||||
|
||||
### Migrate legacy special outbounds to rule actions
|
||||
|
||||
@@ -16,7 +16,7 @@ DNS 服务器已经重构。
|
||||
=== "Local"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -28,9 +28,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -46,7 +46,7 @@ DNS 服务器已经重构。
|
||||
=== "TCP"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -58,9 +58,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -77,7 +77,7 @@ DNS 服务器已经重构。
|
||||
=== "UDP"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -89,9 +89,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -108,7 +108,7 @@ DNS 服务器已经重构。
|
||||
=== "TLS"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -120,9 +120,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -139,7 +139,7 @@ DNS 服务器已经重构。
|
||||
=== "HTTPS"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -151,9 +151,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -170,7 +170,7 @@ DNS 服务器已经重构。
|
||||
=== "QUIC"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -182,9 +182,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -201,7 +201,7 @@ DNS 服务器已经重构。
|
||||
=== "HTTP3"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -213,9 +213,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -232,7 +232,7 @@ DNS 服务器已经重构。
|
||||
=== "DHCP"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -247,9 +247,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -269,7 +269,7 @@ DNS 服务器已经重构。
|
||||
=== "FakeIP"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -292,16 +292,16 @@ DNS 服务器已经重构。
|
||||
}
|
||||
],
|
||||
"fakeip": {
|
||||
"enable": true,
|
||||
"enabled": true,
|
||||
"inet4_range": "198.18.0.0/15",
|
||||
"inet6_range": "fc00::/18"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -333,7 +333,7 @@ DNS 服务器已经重构。
|
||||
=== "RCode"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -345,9 +345,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -368,7 +368,7 @@ DNS 服务器已经重构。
|
||||
=== "带有域名地址的服务器"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -385,9 +385,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -410,7 +410,7 @@ DNS 服务器已经重构。
|
||||
=== "带有域策略的服务器"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -434,9 +434,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -466,7 +466,7 @@ DNS 服务器已经重构。
|
||||
=== "带有客户端子网的服务器"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -483,9 +483,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -556,7 +556,8 @@ DNS 服务器已经重构。
|
||||
"dns": {
|
||||
"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
|
||||
|
||||
### 迁移旧的特殊出站到规则动作
|
||||
@@ -601,7 +653,7 @@ DNS 服务器已经重构。
|
||||
=== "Block"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
@@ -614,7 +666,7 @@ DNS 服务器已经重构。
|
||||
"rules": [
|
||||
{
|
||||
...,
|
||||
|
||||
|
||||
"outbound": "block"
|
||||
}
|
||||
]
|
||||
@@ -623,14 +675,14 @@ DNS 服务器已经重构。
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
...,
|
||||
|
||||
|
||||
"action": "reject"
|
||||
}
|
||||
]
|
||||
@@ -641,13 +693,13 @@ DNS 服务器已经重构。
|
||||
=== "DNS"
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"inbound": [
|
||||
{
|
||||
...,
|
||||
|
||||
|
||||
"sniff": true
|
||||
}
|
||||
],
|
||||
@@ -667,9 +719,9 @@ DNS 服务器已经重构。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"route": {
|
||||
@@ -1133,4 +1185,4 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
@@ -12,14 +13,23 @@ import (
|
||||
"github.com/sagernet/ws/wsutil"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
// API created by Clash.Meta
|
||||
|
||||
func (s *Server) setupMetaAPI(r chi.Router) {
|
||||
if s.logDebug {
|
||||
r := chi.NewRouter()
|
||||
r.Put("/gc", func(w http.ResponseWriter, r *http.Request) {
|
||||
debug.FreeOSMemory()
|
||||
})
|
||||
r.Mount("/", middleware.Profiler())
|
||||
}
|
||||
r.Get("/memory", memory(s.trafficManager))
|
||||
r.Mount("/group", groupRouter(s))
|
||||
r.Mount("/upgrade", upgradeRouter(s))
|
||||
}
|
||||
|
||||
type Memory struct {
|
||||
|
||||
36
experimental/clashapi/api_meta_upgrade.go
Normal file
36
experimental/clashapi/api_meta_upgrade.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package clashapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func upgradeRouter(server *Server) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/ui", updateExternalUI(server))
|
||||
return r
|
||||
}
|
||||
|
||||
func updateExternalUI(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if server.externalUI == "" {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, newError("external UI not enabled"))
|
||||
return
|
||||
}
|
||||
server.logger.Info("upgrading external UI")
|
||||
err := server.downloadExternalUI()
|
||||
if err != nil {
|
||||
server.logger.Error(E.Cause(err, "upgrade external ui"))
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
server.logger.Info("updated external UI")
|
||||
render.JSON(w, r, render.M{"status": "ok"})
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,8 @@ type Server struct {
|
||||
httpServer *http.Server
|
||||
trafficManager *trafficontrol.Manager
|
||||
urlTestHistory adapter.URLTestHistoryStorage
|
||||
logDebug bool
|
||||
|
||||
mode string
|
||||
modeList []string
|
||||
modeUpdateHook chan<- struct{}
|
||||
@@ -74,6 +76,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
||||
Handler: chiRouter,
|
||||
},
|
||||
trafficManager: trafficManager,
|
||||
logDebug: logFactory.Level() >= log.LevelDebug,
|
||||
modeList: options.ModeList,
|
||||
externalController: options.ExternalController != "",
|
||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||
|
||||
@@ -161,6 +161,7 @@ var OptionLegacyDNSFakeIPOptions = Note{
|
||||
Description: "legacy DNS fakeip options",
|
||||
DeprecatedVersion: "1.12.0",
|
||||
ScheduledVersion: "1.14.0",
|
||||
EnvName: "LEGACY_DNS_FAKEIP_OPTIONS",
|
||||
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",
|
||||
DeprecatedVersion: "1.12.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",
|
||||
}
|
||||
|
||||
@@ -177,6 +179,7 @@ var OptionMissingDomainResolver = Note{
|
||||
Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields",
|
||||
DeprecatedVersion: "1.12.0",
|
||||
ScheduledVersion: "1.14.0",
|
||||
EnvName: "MISSING_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",
|
||||
DeprecatedVersion: "1.12.0",
|
||||
ScheduledVersion: "1.13.0",
|
||||
EnvName: "LEGACY_ECH_OPTIONS",
|
||||
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{
|
||||
OptionBadMatchSource,
|
||||
OptionGEOIP,
|
||||
@@ -204,4 +217,5 @@ var Options = []Note{
|
||||
OptionOutboundDNSRuleItem,
|
||||
OptionMissingDomainResolver,
|
||||
OptionLegacyECHOptions,
|
||||
OptionLegacyDomainStrategyOptions,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user