mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-02 14:47:27 +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
|
-s dir
|
||||||
--name sing-box
|
--name sing-box
|
||||||
--category net
|
--category net
|
||||||
--license GPLv3-or-later
|
--license GPL-3.0-or-later
|
||||||
--description "The universal proxy platform."
|
--description "The universal proxy platform."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
|
--no-deb-generate-changes
|
||||||
--config-files /etc/sing-box/config.json
|
--config-files /etc/sing-box/config.json
|
||||||
|
--after-install release/config/sing-box.postinst
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
28
.github/deb2ipk.sh
vendored
Executable file
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
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.2
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -68,31 +68,38 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ linux, windows, darwin, android ]
|
|
||||||
arch: [ "386", amd64, arm64 ]
|
|
||||||
legacy_go: [ false ]
|
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
||||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
||||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
||||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
||||||
|
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
||||||
|
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||||
|
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||||
|
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
||||||
|
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
||||||
|
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
|
|
||||||
- { os: windows, arch: "386", legacy_go: true }
|
- { os: windows, arch: amd64 }
|
||||||
- { os: windows, arch: amd64, legacy_go: true }
|
- { os: windows, arch: amd64, legacy_go: true }
|
||||||
|
- { os: windows, arch: "386" }
|
||||||
|
- { os: windows, arch: "386", legacy_go: true }
|
||||||
|
- { os: windows, arch: arm64 }
|
||||||
|
|
||||||
|
- { os: darwin, arch: amd64 }
|
||||||
|
- { os: darwin, arch: arm64 }
|
||||||
|
|
||||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
|
||||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
|
||||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
exclude:
|
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||||
- { os: darwin, arch: "386" }
|
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
@@ -102,7 +109,7 @@ jobs:
|
|||||||
if: ${{ ! matrix.legacy_go }}
|
if: ${{ ! matrix.legacy_go }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.2
|
go-version: ^1.24.3
|
||||||
- name: Cache Legacy Go
|
- name: Cache Legacy Go
|
||||||
if: matrix.require_legacy_go
|
if: matrix.require_legacy_go
|
||||||
id: cache-legacy-go
|
id: cache-legacy-go
|
||||||
@@ -133,7 +140,7 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
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}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.os != 'android'
|
if: matrix.os != 'android'
|
||||||
@@ -147,7 +154,10 @@ jobs:
|
|||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
GOOS: ${{ matrix.os }}
|
GOOS: ${{ matrix.os }}
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GO386: ${{ matrix.go386 }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GOMIPS: ${{ matrix.gomips }}
|
||||||
|
GOMIPS64: ${{ matrix.gomips }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build Android
|
- name: Build Android
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
@@ -167,12 +177,17 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set name
|
- name: Set name
|
||||||
run: |-
|
run: |-
|
||||||
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
|
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
||||||
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
|
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
|
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
||||||
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||||
|
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||||
|
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-legacy"
|
||||||
|
fi
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
@@ -181,10 +196,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
|
sudo apt-get update
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.deb" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
||||||
--architecture ${{ matrix.debian }} \
|
--architecture ${{ matrix.debian }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
@@ -199,9 +216,10 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.rpm" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
||||||
--architecture ${{ matrix.rpm }} \
|
--architecture ${{ matrix.rpm }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
@@ -217,12 +235,29 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
|
sudo apt-get update
|
||||||
sudo apt-get install -y libarchive-tools
|
sudo apt-get install -y libarchive-tools
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t pacman \
|
fpm -t pacman \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||||
--architecture ${{ matrix.pacman }} \
|
--architecture ${{ matrix.pacman }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
- name: Package OpenWrt
|
||||||
|
if: matrix.openwrt != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
cp .fpm_openwrt .fpm
|
||||||
|
fpm -t deb \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/openwrt.deb" \
|
||||||
|
--architecture all \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
for architecture in ${{ matrix.openwrt }}; do
|
||||||
|
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
||||||
|
done
|
||||||
|
rm "dist/openwrt.deb"
|
||||||
- name: Archive
|
- name: Archive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
@@ -242,7 +277,7 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
@@ -259,7 +294,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.2
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -339,7 +374,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.2
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -437,7 +472,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.2
|
go-version: ^1.24.3
|
||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.2
|
go-version: ^1.24.3
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
11
.github/workflows/linux.yml
vendored
11
.github/workflows/linux.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.2
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.2
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -80,10 +80,7 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||||
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
|
||||||
TAGS="${TAGS},with_ech"
|
|
||||||
fi
|
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
@@ -120,6 +117,7 @@ jobs:
|
|||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
--name "${NAME}" \
|
--name "${NAME}" \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
@@ -138,6 +136,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
--name "${NAME}" \
|
--name "${NAME}" \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
|
|||||||
@@ -21,14 +21,13 @@ linters-settings:
|
|||||||
- -SA1003
|
- -SA1003
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.24"
|
go: "1.23"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
@@ -47,7 +46,6 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ RUN set -ex \
|
|||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags \
|
&& 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 \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|||||||
5
Makefile
5
Makefile
@@ -1,11 +1,10 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
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 ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
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="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ type InboundContext struct {
|
|||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
TLSFragment bool
|
TLSFragment bool
|
||||||
TLSFragmentFallbackDelay time.Duration
|
TLSFragmentFallbackDelay time.Duration
|
||||||
|
TLSRecordFragment bool
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
|
|||||||
Submodule clients/android updated: 8354b78e5d...320170a107
@@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
client := createClient(10 * time.Minute)
|
client := createClient(20 * time.Minute)
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
@@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
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")
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
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)
|
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")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -68,6 +67,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
||||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry(), include.ServiceRegistry())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
@@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read rule-set")
|
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
|
var ruleSet option.PlainRuleSetCompat
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
case C.RuleSetFormatSource:
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import (
|
|||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/utls"
|
|
||||||
|
"github.com/metacubex/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
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
|
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
|
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
||||||
|
|||||||
@@ -66,11 +66,17 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||||
}
|
}
|
||||||
if options.BindInterface != "" {
|
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)
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark > 0 {
|
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))
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
listener.Control = control.Append(listener.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() {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
if networkStrategy == nil {
|
|
||||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
|
||||||
defaultNetworkStrategy = true
|
|
||||||
}
|
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
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 {
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
}
|
}
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
bindFunc := networkManager.ProtectFunc()
|
bindFunc := networkManager.ProtectFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.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) {
|
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) {
|
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) {
|
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||||
|
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
||||||
}
|
}
|
||||||
server = dialOptions.DomainResolver.Server
|
server = dialOptions.DomainResolver.Server
|
||||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||||
@@ -95,22 +96,31 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
} else if options.DirectResolver {
|
} else if options.DirectResolver {
|
||||||
return nil, E.New("missing domain resolver for domain server address")
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
} else if defaultOptions.DomainResolver != "" {
|
|
||||||
dnsQueryOptions = defaultOptions.DomainResolveOptions
|
|
||||||
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
|
|
||||||
}
|
|
||||||
dnsQueryOptions.Transport = transport
|
|
||||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
|
||||||
} else {
|
} else {
|
||||||
transports := dnsTransport.Transports()
|
if defaultOptions.DomainResolver != "" {
|
||||||
if len(transports) < 2 {
|
dnsQueryOptions = defaultOptions.DomainResolveOptions
|
||||||
dnsQueryOptions.Transport = dnsTransport.Default()
|
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
|
||||||
} else if options.NewDialer {
|
if !loaded {
|
||||||
return nil, E.New("missing domain resolver for domain server address")
|
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
|
||||||
} else if !options.DirectOutbound {
|
}
|
||||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
dnsQueryOptions.Transport = transport
|
||||||
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
|
} else {
|
||||||
|
transports := dnsTransport.Transports()
|
||||||
|
if len(transports) < 2 {
|
||||||
|
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||||
|
} else if options.NewDialer {
|
||||||
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
|
} else if !options.DirectOutbound {
|
||||||
|
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if
|
||||||
|
//nolint:staticcheck
|
||||||
|
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||||
|
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialer = NewResolveDialer(
|
dialer = NewResolveDialer(
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
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)
|
return c.conn.Write(b)
|
||||||
default:
|
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 {
|
if err != nil {
|
||||||
c.conn = nil
|
c.err = err
|
||||||
c.err = E.Cause(err, "dial tcp fast open")
|
} else {
|
||||||
|
c.conn = conn
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
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
|
disablePacketOutput bool
|
||||||
setSystemProxy bool
|
setSystemProxy bool
|
||||||
systemProxySOCKS bool
|
systemProxySOCKS bool
|
||||||
|
tproxy bool
|
||||||
|
|
||||||
tcpListener net.Listener
|
tcpListener net.Listener
|
||||||
systemProxy settings.SystemProxy
|
systemProxy settings.SystemProxy
|
||||||
@@ -54,6 +55,7 @@ type Options struct {
|
|||||||
DisablePacketOutput bool
|
DisablePacketOutput bool
|
||||||
SetSystemProxy bool
|
SetSystemProxy bool
|
||||||
SystemProxySOCKS bool
|
SystemProxySOCKS bool
|
||||||
|
TProxy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@@ -71,6 +73,7 @@ func New(
|
|||||||
disablePacketOutput: options.DisablePacketOutput,
|
disablePacketOutput: options.DisablePacketOutput,
|
||||||
setSystemProxy: options.SetSystemProxy,
|
setSystemProxy: options.SetSystemProxy,
|
||||||
systemProxySOCKS: options.SystemProxySOCKS,
|
systemProxySOCKS: options.SystemProxySOCKS,
|
||||||
|
tproxy: options.TProxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,18 @@ package listener
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/redir"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
"github.com/metacubex/tfo-go"
|
"github.com/metacubex/tfo-go"
|
||||||
)
|
)
|
||||||
@@ -23,6 +27,15 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
var err error
|
var err error
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
var listenConfig 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())
|
||||||
|
}
|
||||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
@@ -40,6 +53,13 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
}
|
}
|
||||||
setMultiPathTCP(&listenConfig)
|
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) {
|
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
||||||
if l.listenOptions.TCPFastOpen {
|
if l.listenOptions.TCPFastOpen {
|
||||||
var tfoConfig tfo.ListenConfig
|
var tfoConfig tfo.ListenConfig
|
||||||
|
|||||||
@@ -5,17 +5,30 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"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/buf"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
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)
|
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
|
var udpFragment bool
|
||||||
if l.listenOptions.UDPFragment != nil {
|
if l.listenOptions.UDPFragment != nil {
|
||||||
udpFragment = *l.listenOptions.UDPFragment
|
udpFragment = *l.listenOptions.UDPFragment
|
||||||
@@ -23,10 +36,17 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
udpFragment = l.listenOptions.UDPFragmentDefault
|
udpFragment = l.listenOptions.UDPFragmentDefault
|
||||||
}
|
}
|
||||||
if !udpFragment {
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -37,8 +57,32 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
return udpConn, err
|
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) {
|
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) {
|
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)
|
return listenConfig.ListenPacket(ctx, network, address)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"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)
|
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
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 {
|
if err == nil && isIPv6 {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if isUDP {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 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 err == nil && isIPv6 {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/control"
|
"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
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,13 +31,18 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const header = "BitTorrent protocol"
|
||||||
var protocol [19]byte
|
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 {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if string(protocol[:]) != "BitTorrent protocol" {
|
if n < 19 {
|
||||||
return os.ErrInvalid
|
return ErrNeedMoreData
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.Protocol = C.ProtocolBitTorrent
|
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) {
|
func TestSniffUTP(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -20,22 +20,36 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if length == 0 {
|
if length < 12 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
buffer := buf.NewSize(int(length))
|
buffer := buf.NewSize(int(length))
|
||||||
defer buffer.Release()
|
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 {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
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 {
|
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||||
var msg mDNS.Msg
|
var msg mDNS.Msg
|
||||||
err := msg.Unpack(packet)
|
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
|
return err
|
||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolDNS
|
metadata.Protocol = C.ProtocolDNS
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package sniff_test
|
package sniff_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -21,3 +22,32 @@ func TestSniffDNS(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
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)
|
sniffError = E.Errors(sniffError, err)
|
||||||
}
|
}
|
||||||
if !errors.Is(err, ErrNeedMoreData) {
|
if !errors.Is(sniffError, ErrNeedMoreData) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
const sshPrefix = "SSH-2.0-"
|
const sshPrefix = "SSH-2.0-"
|
||||||
bReader := bufio.NewReader(reader)
|
bReader := bufio.NewReader(reader)
|
||||||
prefix, err := bReader.Peek(len(sshPrefix))
|
prefix, err := bReader.Peek(len(sshPrefix))
|
||||||
|
if string(prefix[:]) != sshPrefix[:len(prefix)] {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
} else if string(prefix) != sshPrefix {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
}
|
||||||
fistLine, _, err := bReader.ReadLine()
|
fistLine, _, err := bReader.ReadLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) {
|
|||||||
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
||||||
require.Equal(t, "dropbear", metadata.Client)
|
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
|
STDConfig = tls.Config
|
||||||
STDConn = tls.Conn
|
STDConn = tls.Conn
|
||||||
ConnectionState = tls.ConnectionState
|
ConnectionState = tls.ConnectionState
|
||||||
|
CurveID = tls.CurveID
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseTLSVersion(version string) (uint16, error) {
|
func ParseTLSVersion(version string) (uint16, error) {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
@@ -46,7 +48,10 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
|
|||||||
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return &STDClientConfig{tlsConfig}, nil
|
return &STDClientConfig{tlsConfig}, nil
|
||||||
} else {
|
} 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 {
|
type STDECHClientConfig struct {
|
||||||
STDClientConfig
|
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) {
|
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{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
@@ -133,6 +155,8 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode ECH config")
|
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
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
break match
|
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")
|
return nil, E.New("no ECH config found in DNS records")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsConn, err := s.Client(conn)
|
return s.Client(conn)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDECHClientConfig) Clone() Config {
|
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) {
|
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/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
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/crypto/hkdf"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@@ -114,6 +115,22 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if len(uConfig.NextProtos) > 0 {
|
||||||
for _, extension := range uConn.Extensions {
|
for _, extension := range uConn.Extensions {
|
||||||
@@ -148,9 +165,13 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if ecdheKey == nil {
|
||||||
return nil, E.New("nil ecdhe_key")
|
return nil, E.New("nil ecdheKey")
|
||||||
}
|
}
|
||||||
authKey, err := ecdheKey.ECDH(publicKey)
|
authKey, err := ecdheKey.ECDH(publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -214,10 +235,6 @@ func realityClientFallback(ctx context.Context, uConn net.Conn, serverName strin
|
|||||||
response.Body.Close()
|
response.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
|
||||||
e.uClient.config.SessionIDGenerator = generator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
func (e *RealityClientConfig) Clone() Config {
|
||||||
return &RealityClientConfig{
|
return &RealityClientConfig{
|
||||||
e.ctx,
|
e.ctx,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build with_reality_server
|
//go:build with_utls
|
||||||
|
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
@@ -7,28 +7,29 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/reality"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/debug"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
|
utls "github.com/metacubex/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||||
|
|
||||||
type RealityServerConfig struct {
|
type RealityServerConfig struct {
|
||||||
config *reality.Config
|
config *utls.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
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 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
return nil, E.New("acme is unavailable in reality")
|
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.SessionTicketsDisabled = true
|
||||||
|
tlsConfig.Log = func(format string, v ...any) {
|
||||||
|
if logger != nil {
|
||||||
|
logger.Trace(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
tlsConfig.Type = N.NetworkTCP
|
tlsConfig.Type = N.NetworkTCP
|
||||||
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
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))
|
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug.Enabled {
|
|
||||||
tlsConfig.Show = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RealityServerConfig{&tlsConfig}, nil
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -173,7 +175,7 @@ func (c *RealityServerConfig) Clone() Config {
|
|||||||
var _ Conn = (*realityConnWrapper)(nil)
|
var _ Conn = (*realityConnWrapper)(nil)
|
||||||
|
|
||||||
type realityConnWrapper struct {
|
type realityConnWrapper struct {
|
||||||
*reality.Conn
|
*utls.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
//go:build !with_reality_server
|
//go:build with_reality_server
|
||||||
|
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
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."
|
||||||
"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`)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
|
|
||||||
|
utls "github.com/metacubex/utls"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
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) {
|
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
|
package tf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -17,17 +19,19 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
|
splitRecord bool
|
||||||
fallbackDelay time.Duration
|
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)
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
tcpConn: tcpConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
splitRecord: splitRecord,
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
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)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if c.tcpConn != nil {
|
if !c.splitRecord {
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
if c.tcpConn != nil {
|
||||||
if err != nil {
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
return
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splits := strings.Split(serverName.ServerName, ".")
|
splits := strings.Split(serverName.ServerName, ".")
|
||||||
@@ -61,16 +67,25 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
currentIndex++
|
currentIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var buffer bytes.Buffer
|
||||||
for i := 0; i <= len(splitIndexes); i++ {
|
for i := 0; i <= len(splitIndexes); i++ {
|
||||||
var payload []byte
|
var payload []byte
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
payload = b[:splitIndexes[i]]
|
payload = b[:splitIndexes[i]]
|
||||||
|
if c.splitRecord {
|
||||||
|
payload = payload[recordLayerHeaderLen:]
|
||||||
|
}
|
||||||
} else if i == len(splitIndexes) {
|
} else if i == len(splitIndexes) {
|
||||||
payload = b[splitIndexes[i-1]:]
|
payload = b[splitIndexes[i-1]:]
|
||||||
} else {
|
} else {
|
||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
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)
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -82,11 +97,18 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.tcpConn != nil {
|
if c.splitRecord {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
_, err = c.Conn.Write(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if c.tcpConn != nil {
|
||||||
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return len(b), nil
|
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"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
TypeDERP = "derp"
|
TypeDERP = "derp"
|
||||||
TypeDERPSTUN = "derp-stun"
|
|
||||||
TypeResolved = "resolved"
|
TypeResolved = "resolved"
|
||||||
TypeSSMAPI = "ssm-api"
|
TypeSSMAPI = "ssm-api"
|
||||||
)
|
)
|
||||||
|
|||||||
6
debug.go
6
debug.go
@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
if options.TraceBack != "" {
|
if options.TraceBack != "" {
|
||||||
debug.SetTraceback(options.TraceBack)
|
debug.SetTraceback(options.TraceBack)
|
||||||
}
|
}
|
||||||
if options.MemoryLimit != 0 {
|
if options.MemoryLimit.Value() != 0 {
|
||||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
||||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/byteformats"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
|||||||
runtime.ReadMemStats(&memStats)
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
var memObject badjson.JSONObject
|
var memObject badjson.JSONObject
|
||||||
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
|
||||||
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
|
||||||
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
memObject.Put("rss", rusageMaxRSS())
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type Client struct {
|
|||||||
disableCache bool
|
disableCache bool
|
||||||
disableExpire bool
|
disableExpire bool
|
||||||
independentCache bool
|
independentCache bool
|
||||||
|
clientSubnet netip.Prefix
|
||||||
rdrc adapter.RDRCStore
|
rdrc adapter.RDRCStore
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
@@ -47,6 +48,7 @@ type ClientOptions struct {
|
|||||||
DisableExpire bool
|
DisableExpire bool
|
||||||
IndependentCache bool
|
IndependentCache bool
|
||||||
CacheCapacity uint32
|
CacheCapacity uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
RDRC func() adapter.RDRCStore
|
RDRC func() adapter.RDRCStore
|
||||||
Logger logger.ContextLogger
|
Logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,7 @@ func NewClient(options ClientOptions) *Client {
|
|||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
disableExpire: options.DisableExpire,
|
disableExpire: options.DisableExpire,
|
||||||
independentCache: options.IndependentCache,
|
independentCache: options.IndependentCache,
|
||||||
|
clientSubnet: options.ClientSubnet,
|
||||||
initRDRCFunc: options.RDRC,
|
initRDRCFunc: options.RDRC,
|
||||||
logger: options.Logger,
|
logger: options.Logger,
|
||||||
}
|
}
|
||||||
@@ -104,8 +107,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return &responseMessage, nil
|
return &responseMessage, nil
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
if options.ClientSubnet.IsValid() {
|
clientSubnet := options.ClientSubnet
|
||||||
message = SetClientSubnet(message, options.ClientSubnet, true)
|
if !clientSubnet.IsValid() {
|
||||||
|
clientSubnet = c.clientSubnet
|
||||||
|
}
|
||||||
|
if clientSubnet.IsValid() {
|
||||||
|
message = SetClientSubnet(message, clientSubnet)
|
||||||
}
|
}
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
@@ -232,10 +239,20 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
record.Header().Ttl = timeToLive
|
record.Header().Ttl = timeToLive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response.Id = messageId
|
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
c.storeCache(transport, question, response, timeToLive)
|
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)
|
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||||
return response, err
|
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) {
|
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)
|
return nil, RcodeError(response.Rcode)
|
||||||
}
|
}
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"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 (
|
var (
|
||||||
optRecord *dns.OPT
|
optRecord *dns.OPT
|
||||||
subnetOption *dns.EDNS0_SUBNET
|
subnetOption *dns.EDNS0_SUBNET
|
||||||
@@ -19,9 +23,6 @@ findExists:
|
|||||||
var isEDNS0Subnet bool
|
var isEDNS0Subnet bool
|
||||||
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
||||||
if isEDNS0Subnet {
|
if isEDNS0Subnet {
|
||||||
if !override {
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
break findExists
|
break findExists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,14 +38,14 @@ findExists:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
message.Extra = append(message.Extra, optRecord)
|
message.Extra = append(message.Extra, optRecord)
|
||||||
} else {
|
} else if clone {
|
||||||
message = message.Copy()
|
return setClientSubnet(message.Copy(), clientSubnet, false)
|
||||||
}
|
}
|
||||||
if subnetOption == nil {
|
if subnetOption == nil {
|
||||||
subnetOption = new(dns.EDNS0_SUBNET)
|
subnetOption = new(dns.EDNS0_SUBNET)
|
||||||
|
subnetOption.Code = dns.EDNS0SUBNET
|
||||||
optRecord.Option = append(optRecord.Option, subnetOption)
|
optRecord.Option = append(optRecord.Option, subnetOption)
|
||||||
}
|
}
|
||||||
subnetOption.Code = dns.EDNS0SUBNET
|
|
||||||
if clientSubnet.Addr().Is4() {
|
if clientSubnet.Addr().Is4() {
|
||||||
subnetOption.Family = 1
|
subnetOption.Family = 1
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||||
|
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
||||||
RDRC: func() adapter.RDRCStore {
|
RDRC: func() adapter.RDRCStore {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
if cacheFile == nil {
|
if cacheFile == nil {
|
||||||
@@ -258,7 +259,14 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
switch action.Method {
|
switch action.Method {
|
||||||
case C.RuleActionRejectMethodDefault:
|
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:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
return nil, tun.ErrDrop
|
||||||
}
|
}
|
||||||
@@ -328,6 +336,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
printResult := func() {
|
printResult := func() {
|
||||||
|
if err == nil && len(responseAddrs) == 0 {
|
||||||
|
err = E.New("empty result")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrResponseRejectedCached) {
|
if errors.Is(err, ErrResponseRejectedCached) {
|
||||||
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
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 {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
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")
|
if err != nil {
|
||||||
err = RcodeNameError
|
err = E.Cause(err, "lookup ", domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
||||||
if cached {
|
if cached {
|
||||||
if len(responseAddrs) == 0 {
|
if len(responseAddrs) == 0 {
|
||||||
return nil, RcodeNameError
|
return nil, E.New("lookup ", domain, ": empty result (cached)")
|
||||||
}
|
}
|
||||||
return responseAddrs, nil
|
return responseAddrs, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 443
|
serverAddr.Port = 443
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return NewHTTPSRaw(
|
return NewHTTPSRaw(
|
||||||
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
||||||
logger,
|
logger,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package local
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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) {
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
addresses, _ := dns.MessageToAddresses(response)
|
var addresses []netip.Addr
|
||||||
if len(addresses) == 0 {
|
addresses, err = dns.MessageToAddresses(response)
|
||||||
|
if err == nil && len(addresses) == 0 {
|
||||||
err = E.New(fqdn, ": empty result")
|
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 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 443
|
serverAddr.Port = 443
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return &HTTP3Transport{
|
return &HTTP3Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
sQUIC "github.com/sagernet/sing-quic"
|
sQUIC "github.com/sagernet/sing-quic"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
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 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 853
|
serverAddr.Port = 853
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
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 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 53
|
serverAddr.Port = 53
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return &TCPTransport{
|
return &TCPTransport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 853
|
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
|
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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
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 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 53
|
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
|
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()
|
conn.access.Unlock()
|
||||||
defer func() {
|
defer func() {
|
||||||
conn.access.Lock()
|
conn.access.Lock()
|
||||||
delete(conn.callbacks, messageId)
|
delete(conn.callbacks, exMessage.Id)
|
||||||
conn.access.Unlock()
|
conn.access.Unlock()
|
||||||
}()
|
}()
|
||||||
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
||||||
|
|||||||
@@ -2,6 +2,133 @@
|
|||||||
icon: material/alert-decagram
|
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
|
#### 1.12.0-beta.3
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@@ -10,7 +137,8 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
* Fixes and improvements
|
* 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
|
#### 1.12.0-beta.1
|
||||||
|
|
||||||
@@ -25,7 +153,8 @@ see [Tun](/configuration/inbound/tun/#auto_redirect).
|
|||||||
|
|
||||||
* Fixes and improvements
|
* 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
|
#### 1.12.0-alpha.19
|
||||||
|
|
||||||
@@ -65,7 +194,8 @@ See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
|||||||
|
|
||||||
* Fixes and improvements
|
* 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
|
#### 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
|
* 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
|
#### 1.12.0-alpha.5
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ and the data generated by the software is always on your device.
|
|||||||
|
|
||||||
## Android
|
## Android
|
||||||
|
|
||||||
|
The broad package (App) visibility (QUERY_ALL_PACKAGES) permission
|
||||||
|
is used to provide per-application proxy features for VPN,
|
||||||
|
sing-box will not collect your app list.
|
||||||
|
|
||||||
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
|
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
|
||||||
sing-box uses the location permission in the background
|
sing-box uses the location permission in the background
|
||||||
to get information about the connected Wi-Fi network to make them work.
|
to get information about the connected Wi-Fi network to make them work.
|
||||||
|
|||||||
@@ -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"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
: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 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
:material-plus: [cache_capacity](#cache_capacity)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ Will overrides `dns.client_subnet`.
|
|||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
- `default`: Reply with NXDOMAIN.
|
- `default`: Reply with REFUSED.
|
||||||
- `drop`: Drop the request.
|
- `drop`: Drop the request.
|
||||||
|
|
||||||
`default` will be used by default.
|
`default` will be used by default.
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
- `default`: 返回 NXDOMAIN。
|
- `default`: 返回 REFUSED。
|
||||||
- `drop`: 丢弃请求。
|
- `drop`: 丢弃请求。
|
||||||
|
|
||||||
默认使用 `defualt`。
|
默认使用 `defualt`。
|
||||||
|
|||||||
@@ -27,19 +27,21 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
The type of the DNS server.
|
The type of the DNS server.
|
||||||
|
|
||||||
| Type | Format |
|
| Type | Format |
|
||||||
|-----------------|-----------------------------|
|
|-----------------|---------------------------|
|
||||||
| empty (default) | [Legacy](./legacy/) |
|
| empty (default) | [Legacy](./legacy/) |
|
||||||
| `tcp` | [TCP](./tcp/) |
|
| `local` | [Local](./local/) |
|
||||||
| `udp` | [UDP](./udp/) |
|
| `hosts` | [Hosts](./hosts/) |
|
||||||
| `tls` | [TLS](./tls/) |
|
| `tcp` | [TCP](./tcp/) |
|
||||||
| `https` | [HTTPS](./https/) |
|
| `udp` | [UDP](./udp/) |
|
||||||
| `quic` | [QUIC](./quic/) |
|
| `tls` | [TLS](./tls/) |
|
||||||
| `h3` | [HTTP/3](./http3/) |
|
| `quic` | [QUIC](./quic/) |
|
||||||
| `predefined` | [Predefined](./predefined/) |
|
| `https` | [HTTPS](./https/) |
|
||||||
| `dhcp` | [DHCP](./dhcp/) |
|
| `h3` | [HTTP/3](./http3/) |
|
||||||
| `fakeip` | [Fake IP](./fakeip/) |
|
| `dhcp` | [DHCP](./dhcp/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `fakeip` | [Fake IP](./fakeip/) |
|
||||||
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
|
| `resolved` | [Resolved](./resolved/) |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -27,19 +27,21 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
DNS 服务器的类型。
|
DNS 服务器的类型。
|
||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|-----------------|-----------------------------|
|
|-----------------|---------------------------|
|
||||||
| empty (default) | [Legacy](./legacy/) |
|
| empty (default) | [Legacy](./legacy/) |
|
||||||
| `tcp` | [TCP](./tcp/) |
|
| `local` | [Local](./local/) |
|
||||||
| `udp` | [UDP](./udp/) |
|
| `hosts` | [Hosts](./hosts/) |
|
||||||
| `tls` | [TLS](./tls/) |
|
| `tcp` | [TCP](./tcp/) |
|
||||||
| `https` | [HTTPS](./https/) |
|
| `udp` | [UDP](./udp/) |
|
||||||
| `quic` | [QUIC](./quic/) |
|
| `tls` | [TLS](./tls/) |
|
||||||
| `h3` | [HTTP/3](./http3/) |
|
| `quic` | [QUIC](./quic/) |
|
||||||
| `predefined` | [Predefined](./predefined/) |
|
| `https` | [HTTPS](./https/) |
|
||||||
| `dhcp` | [DHCP](./dhcp/) |
|
| `h3` | [HTTP/3](./http3/) |
|
||||||
| `fakeip` | [Fake IP](./fakeip/) |
|
| `dhcp` | [DHCP](./dhcp/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `fakeip` | [Fake IP](./fakeip/) |
|
||||||
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
|
| `resolved` | [Resolved](./resolved/) |
|
||||||
|
|
||||||
#### tag
|
#### 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==
|
==Required==
|
||||||
|
|
||||||
The tag of the Tailscale endpoint.
|
The tag of the [Tailscale Endpoint](/configuration/endpoint/tailscale).
|
||||||
|
|
||||||
#### accept_default_resolvers
|
#### accept_default_resolvers
|
||||||
|
|
||||||
Indicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。
|
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
|
### 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
|
||||||
|
|
||||||
Endpoint is protocols that has both inbound and outbound behavior.
|
An endpoint is a protocol with inbound and outbound behavior.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ icon: material/new-box
|
|||||||
"control_url": "",
|
"control_url": "",
|
||||||
"ephemeral": false,
|
"ephemeral": false,
|
||||||
"hostname": "",
|
"hostname": "",
|
||||||
|
"accept_routes": false,
|
||||||
"exit_node": "",
|
"exit_node": "",
|
||||||
"exit_node_allow_lan_access": false,
|
"exit_node_allow_lan_access": false,
|
||||||
"advertise_routes": [],
|
"advertise_routes": [],
|
||||||
@@ -62,6 +63,10 @@ System hostname is used by default.
|
|||||||
|
|
||||||
Example: `localhost`
|
Example: `localhost`
|
||||||
|
|
||||||
|
#### accept_routes
|
||||||
|
|
||||||
|
Indicates whether the node should accept routes advertised by other nodes.
|
||||||
|
|
||||||
#### exit_node
|
#### exit_node
|
||||||
|
|
||||||
The exit node name or IP address to use.
|
The exit node name or IP address to use.
|
||||||
|
|||||||
@@ -42,16 +42,18 @@ AnyTLS padding scheme line array.
|
|||||||
|
|
||||||
Default padding scheme:
|
Default padding scheme:
|
||||||
|
|
||||||
```
|
```json
|
||||||
stop=8
|
[
|
||||||
0=30-30
|
"stop=8",
|
||||||
1=100-400
|
"0=30-30",
|
||||||
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
"1=100-400",
|
||||||
3=9-9,500-1000
|
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
|
||||||
4=500-1000
|
"3=9-9,500-1000",
|
||||||
5=500-1000
|
"4=500-1000",
|
||||||
6=500-1000
|
"5=500-1000",
|
||||||
7=500-1000
|
"6=500-1000",
|
||||||
|
"7=500-1000"
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|||||||
@@ -42,16 +42,18 @@ AnyTLS 填充方案行数组。
|
|||||||
|
|
||||||
默认填充方案:
|
默认填充方案:
|
||||||
|
|
||||||
```
|
```json
|
||||||
stop=8
|
[
|
||||||
0=30-30
|
"stop=8",
|
||||||
1=100-400
|
"0=30-30",
|
||||||
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
"1=100-400",
|
||||||
3=9-9,500-1000
|
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
|
||||||
4=500-1000
|
"3=9-9,500-1000",
|
||||||
5=500-1000
|
"4=500-1000",
|
||||||
6=500-1000
|
"5=500-1000",
|
||||||
7=500-1000
|
"6=500-1000",
|
||||||
|
"7=500-1000"
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|||||||
@@ -398,11 +398,11 @@ UDP NAT 过期时间。
|
|||||||
|
|
||||||
TCP/IP 栈。
|
TCP/IP 栈。
|
||||||
|
|
||||||
| 栈 | 描述 |
|
| 栈 | 描述 |
|
||||||
|--------|------------------------------------------------------------------|
|
|----------|-------------------------------------------------------------------------------------------------------|
|
||||||
| system | 基于系统网络栈执行 L3 到 L4 转换 |
|
| `system` | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||||
| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||||
| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||||
|
|
||||||
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
|
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ sing-box uses JSON for configuration files.
|
|||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
|
"services": [],
|
||||||
"experimental": {}
|
"experimental": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -30,6 +31,7 @@ sing-box uses JSON for configuration files.
|
|||||||
| `inbounds` | [Inbound](./inbound/) |
|
| `inbounds` | [Inbound](./inbound/) |
|
||||||
| `outbounds` | [Outbound](./outbound/) |
|
| `outbounds` | [Outbound](./outbound/) |
|
||||||
| `route` | [Route](./route/) |
|
| `route` | [Route](./route/) |
|
||||||
|
| `services` | [Service](./service/) |
|
||||||
| `experimental` | [Experimental](./experimental/) |
|
| `experimental` | [Experimental](./experimental/) |
|
||||||
|
|
||||||
### Check
|
### Check
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
|
"services": [],
|
||||||
"experimental": {}
|
"experimental": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -30,6 +31,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
| `inbounds` | [入站](./inbound/) |
|
| `inbounds` | [入站](./inbound/) |
|
||||||
| `outbounds` | [出站](./outbound/) |
|
| `outbounds` | [出站](./outbound/) |
|
||||||
| `route` | [路由](./route/) |
|
| `route` | [路由](./route/) |
|
||||||
|
| `services` | [服务](./service/) |
|
||||||
| `experimental` | [实验性](./experimental/) |
|
| `experimental` | [实验性](./experimental/) |
|
||||||
|
|
||||||
### 检查
|
### 检查
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/delete-clock
|
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
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/delete-clock
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
`block` 出站关闭所有传入请求。
|
`block` 出站关闭所有传入请求。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
: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.disable_cache](#disable_cache)
|
||||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
||||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
:material-plus: [resolve.client_subnet](#client_subnet)
|
||||||
@@ -91,7 +92,8 @@ Not available when `method` is set to drop.
|
|||||||
"udp_connect": false,
|
"udp_connect": false,
|
||||||
"udp_timeout": "",
|
"udp_timeout": "",
|
||||||
"tls_fragment": false,
|
"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.
|
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
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
@@ -180,6 +188,17 @@ The fallback value used when TLS segmentation cannot automatically determine the
|
|||||||
|
|
||||||
`500ms` is used by default.
|
`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
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ icon: material/new-box
|
|||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
: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 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||||
|
|
||||||
|
与 `tls_record_fragment` 冲突。
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
@@ -173,6 +180,16 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
默认使用 `500ms`。
|
默认使用 `500ms`。
|
||||||
|
|
||||||
|
#### tls_record_fragment
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
|
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
||||||
|
|
||||||
|
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||||
|
|
||||||
|
与 `tls_fragment` 冲突。
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
| QUIC 客户端 | 类型 |
|
| QUIC 客户端 | 类型 |
|
||||||
|:------------------------:|:----------:|
|
|:------------------------:|:----------:|
|
||||||
| Chromium/Cronet | `chrimium` |
|
| Chromium/Cronet | `chromium` |
|
||||||
| Safari/Apple Network API | `safari` |
|
| Safari/Apple Network API | `safari` |
|
||||||
| Firefox / uquic firefox | `firefox` |
|
| Firefox / uquic firefox | `firefox` |
|
||||||
| quic-go / uquic chrome | `quic-go` |
|
| quic-go / uquic chrome | `quic-go` |
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ List of [Headless Rule](./headless-rule/).
|
|||||||
|
|
||||||
Format of rule-set file, `source` or `binary`.
|
Format of rule-set file, `source` or `binary`.
|
||||||
|
|
||||||
|
Optional when `path` or `url` uses `json` or `srs` as extension.
|
||||||
|
|
||||||
### Local Fields
|
### Local Fields
|
||||||
|
|
||||||
#### path
|
#### path
|
||||||
|
|||||||
@@ -80,6 +80,8 @@
|
|||||||
|
|
||||||
规则集格式, `source` 或 `binary`。
|
规则集格式, `source` 或 `binary`。
|
||||||
|
|
||||||
|
当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。
|
||||||
|
|
||||||
### 本地字段
|
### 本地字段
|
||||||
|
|
||||||
#### path
|
#### 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": "",
|
"inet6_bind_address": "",
|
||||||
"routing_mark": 0,
|
"routing_mark": 0,
|
||||||
"reuse_addr": false,
|
"reuse_addr": false,
|
||||||
|
"netns": "",
|
||||||
"connect_timeout": "",
|
"connect_timeout": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"netns": "",
|
|
||||||
"domain_resolver": "", // or {}
|
"domain_resolver": "", // or {}
|
||||||
"network_strategy": "",
|
"network_strategy": "",
|
||||||
"network_type": [],
|
"network_type": [],
|
||||||
@@ -37,6 +38,7 @@ icon: material/new-box
|
|||||||
"fallback_delay": "",
|
"fallback_delay": "",
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
|
||||||
"domain_strategy": ""
|
"domain_strategy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -73,10 +75,22 @@ The IPv6 address to bind to.
|
|||||||
|
|
||||||
Set netfilter routing mark.
|
Set netfilter routing mark.
|
||||||
|
|
||||||
|
Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported.
|
||||||
|
|
||||||
#### reuse_addr
|
#### reuse_addr
|
||||||
|
|
||||||
Reuse listener address.
|
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
|
||||||
|
|
||||||
Connect timeout, in golang's Duration format.
|
Connect timeout, in golang's Duration format.
|
||||||
@@ -102,16 +116,6 @@ Enable TCP Multi Path.
|
|||||||
|
|
||||||
Enable UDP fragmentation.
|
Enable UDP fragmentation.
|
||||||
|
|
||||||
#### netns
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux.
|
|
||||||
|
|
||||||
Set network namespace, name or path.
|
|
||||||
|
|
||||||
#### domain_resolver
|
#### domain_resolver
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
@@ -206,7 +210,7 @@ Only take effect when `domain_strategy` or `network_strategy` is set.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.12.0"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
`domain_strategy` is merged to [domain_resolver](#domain_resolver) in sing-box 1.12.0.
|
`domain_strategy` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver).
|
||||||
|
|
||||||
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
|
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ icon: material/new-box
|
|||||||
"inet6_bind_address": "",
|
"inet6_bind_address": "",
|
||||||
"routing_mark": 0,
|
"routing_mark": 0,
|
||||||
"reuse_addr": false,
|
"reuse_addr": false,
|
||||||
|
"netns": "",
|
||||||
"connect_timeout": "",
|
"connect_timeout": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"netns": "",
|
|
||||||
"domain_resolver": "", // 或 {}
|
"domain_resolver": "", // 或 {}
|
||||||
"network_strategy": "",
|
"network_strategy": "",
|
||||||
"network_type": [],
|
"network_type": [],
|
||||||
@@ -74,10 +74,22 @@ icon: material/new-box
|
|||||||
|
|
||||||
设置 netfilter 路由标记。
|
设置 netfilter 路由标记。
|
||||||
|
|
||||||
|
支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。
|
||||||
|
|
||||||
#### reuse_addr
|
#### reuse_addr
|
||||||
|
|
||||||
重用监听地址。
|
重用监听地址。
|
||||||
|
|
||||||
|
#### netns
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux。
|
||||||
|
|
||||||
|
设置网络命名空间,名称或路径。
|
||||||
|
|
||||||
#### connect_timeout
|
#### connect_timeout
|
||||||
|
|
||||||
连接超时,采用 golang 的 Duration 格式。
|
连接超时,采用 golang 的 Duration 格式。
|
||||||
@@ -101,16 +113,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
启用 UDP 分段。
|
启用 UDP 分段。
|
||||||
|
|
||||||
#### netns
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux。
|
|
||||||
|
|
||||||
设置网络命名空间,名称或路径。
|
|
||||||
|
|
||||||
#### domain_resolver
|
#### domain_resolver
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
@@ -194,6 +196,10 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### domain_strategy
|
#### domain_strategy
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||||
|
|
||||||
|
`domain_strategy` 已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver)。
|
||||||
|
|
||||||
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
|
|
||||||
如果设置,域名将在请求发出之前解析为 IP。
|
如果设置,域名将在请求发出之前解析为 IP。
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! 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"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
@@ -20,12 +23,18 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"listen": "",
|
"listen": "",
|
||||||
"listen_port": 0,
|
"listen_port": 0,
|
||||||
|
"bind_interface": "",
|
||||||
|
"routing_mark": 0,
|
||||||
|
"reuse_addr": false,
|
||||||
|
"netns": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"udp_timeout": "",
|
"udp_timeout": "",
|
||||||
"netns": "",
|
|
||||||
"detour": "",
|
"detour": "",
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
|
||||||
"sniff": false,
|
"sniff": false,
|
||||||
"sniff_override_destination": false,
|
"sniff_override_destination": false,
|
||||||
"sniff_timeout": "",
|
"sniff_timeout": "",
|
||||||
@@ -36,15 +45,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
### Fields
|
### 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
|
#### listen
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
@@ -55,6 +55,40 @@ Listen address.
|
|||||||
|
|
||||||
Listen port.
|
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
|
#### tcp_fast_open
|
||||||
|
|
||||||
Enable TCP Fast Open.
|
Enable TCP Fast Open.
|
||||||
@@ -77,16 +111,6 @@ UDP NAT expiration time.
|
|||||||
|
|
||||||
`5m` will be used by default.
|
`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
|
#### detour
|
||||||
|
|
||||||
If set, connections will be forwarded to the specified inbound.
|
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"
|
!!! 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 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
@@ -20,12 +23,18 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"listen": "",
|
"listen": "",
|
||||||
"listen_port": 0,
|
"listen_port": 0,
|
||||||
|
"bind_interface": "",
|
||||||
|
"routing_mark": 0,
|
||||||
|
"reuse_addr": false,
|
||||||
|
"netns": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"udp_timeout": "",
|
"udp_timeout": "",
|
||||||
"netns": "",
|
|
||||||
"detour": "",
|
"detour": "",
|
||||||
|
|
||||||
|
// 废弃的
|
||||||
|
|
||||||
"sniff": false,
|
"sniff": false,
|
||||||
"sniff_override_destination": false,
|
"sniff_override_destination": false,
|
||||||
"sniff_timeout": "",
|
"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
|
#### 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
|
||||||
|
|
||||||
启用 TCP Fast Open。
|
启用 TCP Fast Open。
|
||||||
@@ -78,16 +111,6 @@ UDP NAT 过期时间。
|
|||||||
|
|
||||||
默认使用 `5m`。
|
默认使用 `5m`。
|
||||||
|
|
||||||
#### netns
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux。
|
|
||||||
|
|
||||||
设置网络命名空间,名称或路径。
|
|
||||||
|
|
||||||
#### detour
|
#### 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_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_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_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_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_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). |
|
| `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_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_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_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_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_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). |
|
| `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"
|
=== ":material-debian: Debian / APT"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
|
sudo mkdir -p /etc/apt/keyrings &&
|
||||||
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
|
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
|
||||||
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
|
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
|
||||||
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
|
echo '
|
||||||
sudo apt-get update
|
Types: deb
|
||||||
sudo apt-get install sing-box # or sing-box-beta
|
URIs: https://deb.sagernet.org/
|
||||||
|
Suites: *
|
||||||
|
Components: *
|
||||||
|
Enabled: yes
|
||||||
|
Signed-By: /etc/apt/keyrings/sagernet.asc
|
||||||
|
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
|
||||||
|
sudo apt-get update &&
|
||||||
|
sudo apt-get install sing-box # or sing-box-beta
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-redhat: Redhat / DNF"
|
=== ":material-redhat: Redhat / DNF 5"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dnf -y install dnf-plugins-core
|
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
|
||||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
sudo dnf install sing-box # or sing-box-beta
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":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
|
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-download-box: Manual Installation
|
||||||
|
|
||||||
=== ":material-debian: Debian / DEB"
|
The script download and install the latest package from GitHub releases
|
||||||
|
for deb or rpm based Linux distributions, ArchLinux and OpenWrt.
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
|
curl -fsSL https://sing-box.app/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-redhat: Redhat / RPM"
|
or latest beta:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
|
||||||
```
|
```
|
||||||
(This applies to any distribution that uses `rpm` and `systemd`. Because of how `rpm` defines dependencies, if it installs, it probably works.)
|
|
||||||
|
|
||||||
=== ":simple-archlinux: Archlinux / PKG"
|
or specific version:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
|
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
|
||||||
```
|
```
|
||||||
|
|
||||||
## :material-book-lock-open: Managed Installation
|
## :material-book-lock-open: Managed Installation
|
||||||
|
|
||||||
|
|||||||
@@ -9,43 +9,55 @@ icon: material/package
|
|||||||
=== ":material-debian: Debian / APT"
|
=== ":material-debian: Debian / APT"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
|
sudo mkdir -p /etc/apt/keyrings &&
|
||||||
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
|
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
|
||||||
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
|
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
|
||||||
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
|
echo '
|
||||||
sudo apt-get update
|
Types: deb
|
||||||
sudo apt-get install sing-box # or sing-box-beta
|
URIs: https://deb.sagernet.org/
|
||||||
|
Suites: *
|
||||||
|
Components: *
|
||||||
|
Enabled: yes
|
||||||
|
Signed-By: /etc/apt/keyrings/sagernet.asc
|
||||||
|
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
|
||||||
|
sudo apt-get update &&
|
||||||
|
sudo apt-get install sing-box # or sing-box-beta
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-redhat: Redhat / DNF"
|
=== ":material-redhat: Redhat / DNF 5"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dnf -y install dnf-plugins-core
|
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
|
||||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
sudo dnf install sing-box # or sing-box-beta
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":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
|
sudo dnf install sing-box # or sing-box-beta
|
||||||
```
|
```
|
||||||
(这适用于任何使用 `dnf` 作为包管理器的发行版:Fedora、CentOS,甚至安装了 DNF 的 OpenSUSE。)
|
|
||||||
|
|
||||||
## :material-download-box: 手动安装
|
## :material-download-box: 手动安装
|
||||||
|
|
||||||
=== ":material-debian: Debian / DEB"
|
该脚本从 GitHub 发布中下载并安装最新的软件包,适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
|
curl -fsSL https://sing-box.app/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-redhat: Redhat / RPM"
|
或最新测试版:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
|
||||||
```
|
```
|
||||||
(这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。)
|
|
||||||
|
|
||||||
=== ":simple-archlinux: Archlinux / PKG"
|
或指定版本:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
|
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
|
||||||
```
|
```
|
||||||
|
|
||||||
## :material-book-lock-open: 托管安装
|
## :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": {
|
"fakeip": {
|
||||||
"enable": true,
|
"enabled": true,
|
||||||
"inet4_range": "198.18.0.0/15",
|
"inet4_range": "198.18.0.0/15",
|
||||||
"inet6_range": "fc00::/18"
|
"inet6_range": "fc00::/18"
|
||||||
}
|
}
|
||||||
@@ -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.
|
The legacy outbound DNS rules are deprecated and can be replaced by new domain resolver options.
|
||||||
|
|
||||||
!!! info "References"
|
!!! info "References"
|
||||||
|
|
||||||
[DNS rule](/configuration/dns/rule/#outbound) /
|
[DNS rule](/configuration/dns/rule/#outbound) /
|
||||||
[Dial Fields](/configuration/shared/dial/#domain_resolver) /
|
[Dial Fields](/configuration/shared/dial/#domain_resolver) /
|
||||||
[Route](/configuration/route/#domain_resolver)
|
[Route](/configuration/route/#domain_resolver)
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
=== ":material-card-remove: Deprecated"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -556,7 +556,8 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
|
|||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"type": "local"
|
"type": "local",
|
||||||
|
"tag": "local"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -586,6 +587,58 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Migrate outbound domain strategy option to domain resolver
|
||||||
|
|
||||||
|
!!! info "References"
|
||||||
|
|
||||||
|
[Dial Fields](/configuration/shared/dial/#domain_strategy)
|
||||||
|
|
||||||
|
The `domain_strategy` option in Dial Fields has been deprecated and can be replaced with the new domain resolver option.
|
||||||
|
|
||||||
|
Note that due to the use of Dial Fields by some of the new DNS servers introduced in sing-box 1.12,
|
||||||
|
some people mistakenly believe that `domain_strategy` is the same feature as in the legacy DNS servers.
|
||||||
|
|
||||||
|
=== ":material-card-remove: Deprecated"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "socks",
|
||||||
|
"server": "example.org",
|
||||||
|
"server_port": 2080,
|
||||||
|
"domain_strategy": "prefer_ipv4",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-card-multiple: New"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "local",
|
||||||
|
"tag": "local"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "socks",
|
||||||
|
"server": "example.org",
|
||||||
|
"server_port": 2080,
|
||||||
|
"domain_resolver": {
|
||||||
|
"server": "local",
|
||||||
|
"strategy": "prefer_ipv4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 1.11.0
|
## 1.11.0
|
||||||
|
|
||||||
### Migrate legacy special outbounds to rule actions
|
### Migrate legacy special outbounds to rule actions
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ DNS 服务器已经重构。
|
|||||||
=== "Local"
|
=== "Local"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -28,9 +28,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -46,7 +46,7 @@ DNS 服务器已经重构。
|
|||||||
=== "TCP"
|
=== "TCP"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -58,9 +58,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -77,7 +77,7 @@ DNS 服务器已经重构。
|
|||||||
=== "UDP"
|
=== "UDP"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -89,9 +89,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -108,7 +108,7 @@ DNS 服务器已经重构。
|
|||||||
=== "TLS"
|
=== "TLS"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -120,9 +120,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -139,7 +139,7 @@ DNS 服务器已经重构。
|
|||||||
=== "HTTPS"
|
=== "HTTPS"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -151,9 +151,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -170,7 +170,7 @@ DNS 服务器已经重构。
|
|||||||
=== "QUIC"
|
=== "QUIC"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -182,9 +182,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -201,7 +201,7 @@ DNS 服务器已经重构。
|
|||||||
=== "HTTP3"
|
=== "HTTP3"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -213,9 +213,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -232,7 +232,7 @@ DNS 服务器已经重构。
|
|||||||
=== "DHCP"
|
=== "DHCP"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -247,9 +247,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -269,7 +269,7 @@ DNS 服务器已经重构。
|
|||||||
=== "FakeIP"
|
=== "FakeIP"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -292,16 +292,16 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"fakeip": {
|
"fakeip": {
|
||||||
"enable": true,
|
"enabled": true,
|
||||||
"inet4_range": "198.18.0.0/15",
|
"inet4_range": "198.18.0.0/15",
|
||||||
"inet6_range": "fc00::/18"
|
"inet6_range": "fc00::/18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -333,7 +333,7 @@ DNS 服务器已经重构。
|
|||||||
=== "RCode"
|
=== "RCode"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -345,9 +345,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -368,7 +368,7 @@ DNS 服务器已经重构。
|
|||||||
=== "带有域名地址的服务器"
|
=== "带有域名地址的服务器"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -385,9 +385,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -410,7 +410,7 @@ DNS 服务器已经重构。
|
|||||||
=== "带有域策略的服务器"
|
=== "带有域策略的服务器"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -434,9 +434,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -466,7 +466,7 @@ DNS 服务器已经重构。
|
|||||||
=== "带有客户端子网的服务器"
|
=== "带有客户端子网的服务器"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -483,9 +483,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
@@ -556,7 +556,8 @@ DNS 服务器已经重构。
|
|||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"type": "local"
|
"type": "local",
|
||||||
|
"tag": "local"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -586,6 +587,57 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 迁移出站域名策略选项到域名解析器
|
||||||
|
|
||||||
|
拨号字段中的 `domain_strategy` 选项已被弃用,可以用新的域名解析器选项替代。
|
||||||
|
|
||||||
|
请注意,由于 sing-box 1.12 中引入的一些新 DNS 服务器使用了拨号字段,一些人错误地认为 `domain_strategy` 与旧 DNS 服务器中的功能相同。
|
||||||
|
|
||||||
|
!!! info "参考"
|
||||||
|
|
||||||
|
[拨号字段](/configuration/shared/dial/#domain_strategy)
|
||||||
|
|
||||||
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "socks",
|
||||||
|
"server": "example.org",
|
||||||
|
"server_port": 2080,
|
||||||
|
"domain_strategy": "prefer_ipv4",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "local",
|
||||||
|
"tag": "local"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "socks",
|
||||||
|
"server": "example.org",
|
||||||
|
"server_port": 2080,
|
||||||
|
"domain_resolver": {
|
||||||
|
"server": "local",
|
||||||
|
"strategy": "prefer_ipv4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 1.11.0
|
## 1.11.0
|
||||||
|
|
||||||
### 迁移旧的特殊出站到规则动作
|
### 迁移旧的特殊出站到规则动作
|
||||||
@@ -601,7 +653,7 @@ DNS 服务器已经重构。
|
|||||||
=== "Block"
|
=== "Block"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@@ -614,7 +666,7 @@ DNS 服务器已经重构。
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
...,
|
...,
|
||||||
|
|
||||||
"outbound": "block"
|
"outbound": "block"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -623,14 +675,14 @@ DNS 服务器已经重构。
|
|||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
...,
|
...,
|
||||||
|
|
||||||
"action": "reject"
|
"action": "reject"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -641,13 +693,13 @@ DNS 服务器已经重构。
|
|||||||
=== "DNS"
|
=== "DNS"
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"inbound": [
|
"inbound": [
|
||||||
{
|
{
|
||||||
...,
|
...,
|
||||||
|
|
||||||
"sniff": true
|
"sniff": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -667,9 +719,9 @@ DNS 服务器已经重构。
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"route": {
|
"route": {
|
||||||
@@ -1133,4 +1185,4 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
@@ -12,14 +13,23 @@ import (
|
|||||||
"github.com/sagernet/ws/wsutil"
|
"github.com/sagernet/ws/wsutil"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
// API created by Clash.Meta
|
// API created by Clash.Meta
|
||||||
|
|
||||||
func (s *Server) setupMetaAPI(r chi.Router) {
|
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.Get("/memory", memory(s.trafficManager))
|
||||||
r.Mount("/group", groupRouter(s))
|
r.Mount("/group", groupRouter(s))
|
||||||
|
r.Mount("/upgrade", upgradeRouter(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Memory struct {
|
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
|
httpServer *http.Server
|
||||||
trafficManager *trafficontrol.Manager
|
trafficManager *trafficontrol.Manager
|
||||||
urlTestHistory adapter.URLTestHistoryStorage
|
urlTestHistory adapter.URLTestHistoryStorage
|
||||||
|
logDebug bool
|
||||||
|
|
||||||
mode string
|
mode string
|
||||||
modeList []string
|
modeList []string
|
||||||
modeUpdateHook chan<- struct{}
|
modeUpdateHook chan<- struct{}
|
||||||
@@ -74,6 +76,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
|||||||
Handler: chiRouter,
|
Handler: chiRouter,
|
||||||
},
|
},
|
||||||
trafficManager: trafficManager,
|
trafficManager: trafficManager,
|
||||||
|
logDebug: logFactory.Level() >= log.LevelDebug,
|
||||||
modeList: options.ModeList,
|
modeList: options.ModeList,
|
||||||
externalController: options.ExternalController != "",
|
externalController: options.ExternalController != "",
|
||||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ var OptionLegacyDNSFakeIPOptions = Note{
|
|||||||
Description: "legacy DNS fakeip options",
|
Description: "legacy DNS fakeip options",
|
||||||
DeprecatedVersion: "1.12.0",
|
DeprecatedVersion: "1.12.0",
|
||||||
ScheduledVersion: "1.14.0",
|
ScheduledVersion: "1.14.0",
|
||||||
|
EnvName: "LEGACY_DNS_FAKEIP_OPTIONS",
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +170,7 @@ var OptionOutboundDNSRuleItem = Note{
|
|||||||
Description: "outbound DNS rule item",
|
Description: "outbound DNS rule item",
|
||||||
DeprecatedVersion: "1.12.0",
|
DeprecatedVersion: "1.12.0",
|
||||||
ScheduledVersion: "1.14.0",
|
ScheduledVersion: "1.14.0",
|
||||||
|
EnvName: "OUTBOUND_DNS_RULE_ITEM",
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver",
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +179,7 @@ var OptionMissingDomainResolver = Note{
|
|||||||
Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields",
|
Description: "missing `route.default_domain_resolver` or `domain_resolver` in dial fields",
|
||||||
DeprecatedVersion: "1.12.0",
|
DeprecatedVersion: "1.12.0",
|
||||||
ScheduledVersion: "1.14.0",
|
ScheduledVersion: "1.14.0",
|
||||||
|
EnvName: "MISSING_DOMAIN_RESOLVER",
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver",
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,9 +188,19 @@ var OptionLegacyECHOptions = Note{
|
|||||||
Description: "legacy ECH options",
|
Description: "legacy ECH options",
|
||||||
DeprecatedVersion: "1.12.0",
|
DeprecatedVersion: "1.12.0",
|
||||||
ScheduledVersion: "1.13.0",
|
ScheduledVersion: "1.13.0",
|
||||||
|
EnvName: "LEGACY_ECH_OPTIONS",
|
||||||
MigrationLink: "https://sing-box.sagernet.org/deprecated/#legacy-ech-fields",
|
MigrationLink: "https://sing-box.sagernet.org/deprecated/#legacy-ech-fields",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var OptionLegacyDomainStrategyOptions = Note{
|
||||||
|
Name: "legacy-domain-strategy-options",
|
||||||
|
Description: "legacy domain strategy options",
|
||||||
|
DeprecatedVersion: "1.12.0",
|
||||||
|
ScheduledVersion: "1.14.0",
|
||||||
|
EnvName: "LEGACY_DOMAIN_STRATEGY_OPTIONS",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-domain-strategy-options",
|
||||||
|
}
|
||||||
|
|
||||||
var Options = []Note{
|
var Options = []Note{
|
||||||
OptionBadMatchSource,
|
OptionBadMatchSource,
|
||||||
OptionGEOIP,
|
OptionGEOIP,
|
||||||
@@ -204,4 +217,5 @@ var Options = []Note{
|
|||||||
OptionOutboundDNSRuleItem,
|
OptionOutboundDNSRuleItem,
|
||||||
OptionMissingDomainResolver,
|
OptionMissingDomainResolver,
|
||||||
OptionLegacyECHOptions,
|
OptionLegacyECHOptions,
|
||||||
|
OptionLegacyDomainStrategyOptions,
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user