mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-08 11:44:13 +08:00
Compare commits
153 Commits
v1.12.0-be
...
dev-next
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 | ||
![]() |
cb68a40c43 | ||
![]() |
023218e6e7 | ||
![]() |
2a24b94b8d | ||
![]() |
c6531cf184 | ||
![]() |
d4fa0ed349 |
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,17 +1,22 @@
|
||||
-s dir
|
||||
--name sing-box
|
||||
--category net
|
||||
--license GPLv3-or-later
|
||||
--license GPL-3.0-or-later
|
||||
--description "The universal proxy platform."
|
||||
--url "https://sing-box.sagernet.org/"
|
||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||
--no-deb-generate-changes
|
||||
--config-files /etc/sing-box/config.json
|
||||
--after-install release/config/sing-box.postinst
|
||||
|
||||
release/config/config.json=/etc/sing-box/config.json
|
||||
|
||||
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
||||
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
||||
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||
|
||||
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
|
28
.github/deb2ipk.sh
vendored
Executable file
28
.github/deb2ipk.sh
vendored
Executable file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
PROJECT=$(dirname "$0")/../..
|
||||
TMP_PATH=`mktemp -d`
|
||||
cp $2 $TMP_PATH
|
||||
pushd $TMP_PATH
|
||||
|
||||
DEB_NAME=`ls *.deb`
|
||||
ar x $DEB_NAME
|
||||
|
||||
mkdir control
|
||||
pushd control
|
||||
tar xf ../control.tar.gz
|
||||
rm md5sums
|
||||
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
|
||||
cat control
|
||||
tar czf ../control.tar.gz ./*
|
||||
popd
|
||||
|
||||
DEB_NAME=${DEB_NAME%.deb}
|
||||
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
|
||||
popd
|
||||
|
||||
cp $TMP_PATH/$DEB_NAME.ipk $3
|
||||
rm -r $TMP_PATH
|
99
.github/workflows/build.yml
vendored
99
.github/workflows/build.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@ -68,31 +68,38 @@ jobs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ linux, windows, darwin, android ]
|
||||
arch: [ "386", amd64, arm64 ]
|
||||
legacy_go: [ false ]
|
||||
include:
|
||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
||||
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
||||
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
||||
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
||||
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||
|
||||
- { os: windows, arch: "386", legacy_go: true }
|
||||
- { os: windows, arch: amd64 }
|
||||
- { os: windows, arch: amd64, legacy_go: true }
|
||||
- { os: windows, arch: "386" }
|
||||
- { os: windows, arch: "386", legacy_go: true }
|
||||
- { os: windows, arch: arm64 }
|
||||
|
||||
- { os: darwin, arch: amd64 }
|
||||
- { os: darwin, arch: arm64 }
|
||||
|
||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||
exclude:
|
||||
- { os: darwin, arch: "386" }
|
||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
@ -102,7 +109,7 @@ jobs:
|
||||
if: ${{ ! matrix.legacy_go }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Cache Legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
id: cache-legacy-go
|
||||
@ -133,7 +140,7 @@ jobs:
|
||||
- name: Set build tags
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api,with_tailscale'
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
if: matrix.os != 'android'
|
||||
@ -147,7 +154,10 @@ jobs:
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: ${{ matrix.os }}
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
GO386: ${{ matrix.go386 }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GOMIPS: ${{ matrix.gomips }}
|
||||
GOMIPS64: ${{ matrix.gomips }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build Android
|
||||
if: matrix.os == 'android'
|
||||
@ -167,12 +177,17 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set name
|
||||
run: |-
|
||||
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
|
||||
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
|
||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
|
||||
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
||||
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
|
||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
||||
DIR_NAME="${DIR_NAME}-legacy"
|
||||
fi
|
||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||
@ -181,10 +196,12 @@ jobs:
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y debsigs
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t deb \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.deb" \
|
||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
||||
--architecture ${{ matrix.debian }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||
@ -199,9 +216,10 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t rpm \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.rpm" \
|
||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
||||
--architecture ${{ matrix.rpm }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
cat > $HOME/.rpmmacros <<EOF
|
||||
@ -217,12 +235,29 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libarchive-tools
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t pacman \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||
--architecture ${{ matrix.pacman }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
- name: Package OpenWrt
|
||||
if: matrix.openwrt != ''
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .fpm_openwrt .fpm
|
||||
fpm -t deb \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/openwrt.deb" \
|
||||
--architecture all \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
for architecture in ${{ matrix.openwrt }}; do
|
||||
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
||||
done
|
||||
rm "dist/openwrt.deb"
|
||||
- name: Archive
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
@ -242,7 +277,7 @@ jobs:
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
path: "dist"
|
||||
build_android:
|
||||
name: Build Android
|
||||
@ -259,7 +294,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@ -339,7 +374,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@ -437,7 +472,7 @@ jobs:
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
|
11
.github/workflows/linux.yml
vendored
11
.github/workflows/linux.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.2
|
||||
go-version: ^1.24.3
|
||||
- name: Setup Android NDK
|
||||
if: matrix.os == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
@ -80,10 +80,7 @@ jobs:
|
||||
- name: Set build tags
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
||||
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
||||
TAGS="${TAGS},with_ech"
|
||||
fi
|
||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
run: |
|
||||
@ -120,6 +117,7 @@ jobs:
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get install -y debsigs
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t deb \
|
||||
--name "${NAME}" \
|
||||
-v "$PKG_VERSION" \
|
||||
@ -138,6 +136,7 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t rpm \
|
||||
--name "${NAME}" \
|
||||
-v "$PKG_VERSION" \
|
||||
|
@ -21,14 +21,13 @@ linters-settings:
|
||||
- -SA1003
|
||||
|
||||
run:
|
||||
go: "1.24"
|
||||
go: "1.23"
|
||||
build-tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
|
||||
|
@ -15,7 +15,6 @@ builds:
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
@ -56,6 +55,12 @@ nfpms:
|
||||
dst: /usr/lib/systemd/system/sing-box.service
|
||||
- src: release/config/sing-box@.service
|
||||
dst: /usr/lib/systemd/system/sing-box@.service
|
||||
- src: release/config/sing-box.sysusers
|
||||
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||
- src: release/config/sing-box.rules
|
||||
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||
- src: release/config/sing-box-split-dns.xml
|
||||
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||
|
||||
- src: release/completions/sing-box.bash
|
||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||
|
@ -17,7 +17,6 @@ builds:
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
@ -47,7 +46,6 @@ builds:
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
@ -138,6 +136,12 @@ nfpms:
|
||||
dst: /usr/lib/systemd/system/sing-box.service
|
||||
- src: release/config/sing-box@.service
|
||||
dst: /usr/lib/systemd/system/sing-box@.service
|
||||
- src: release/config/sing-box.sysusers
|
||||
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||
- src: release/config/sing-box.rules
|
||||
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||
- src: release/config/sing-box-split-dns.xml
|
||||
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||
|
||||
- src: release/completions/sing-box.bash
|
||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||
|
@ -13,7 +13,7 @@ RUN set -ex \
|
||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||
&& go build -v -trimpath -tags \
|
||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api,with_tailscale" \
|
||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
|
||||
-o /go/bin/sing-box \
|
||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||
./cmd/sing-box
|
||||
|
9
Makefile
9
Makefile
@ -1,14 +1,13 @@
|
||||
NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
||||
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
|
||||
|
||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
||||
|
||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||
MAIN = ./cmd/sing-box
|
||||
PREFIX ?= $(shell go env GOPATH)
|
||||
|
||||
@ -24,7 +23,7 @@ ci_build:
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
generate_completions:
|
||||
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
||||
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
|
||||
|
||||
install:
|
||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||
|
@ -7,7 +7,9 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
@ -31,11 +33,30 @@ type DNSClient interface {
|
||||
}
|
||||
|
||||
type DNSQueryOptions struct {
|
||||
Transport DNSTransport
|
||||
Strategy C.DomainStrategy
|
||||
DisableCache bool
|
||||
RewriteTTL *uint32
|
||||
ClientSubnet netip.Prefix
|
||||
Transport DNSTransport
|
||||
Strategy C.DomainStrategy
|
||||
LookupStrategy C.DomainStrategy
|
||||
DisableCache bool
|
||||
RewriteTTL *uint32
|
||||
ClientSubnet netip.Prefix
|
||||
}
|
||||
|
||||
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
||||
if options == nil {
|
||||
return &DNSQueryOptions{}, nil
|
||||
}
|
||||
transportManager := service.FromContext[DNSTransportManager](ctx)
|
||||
transport, loaded := transportManager.Transport(options.Server)
|
||||
if !loaded {
|
||||
return nil, E.New("domain resolver not found: " + options.Server)
|
||||
}
|
||||
return &DNSQueryOptions{
|
||||
Transport: transport,
|
||||
Strategy: C.DomainStrategy(options.Strategy),
|
||||
DisableCache: options.DisableCache,
|
||||
RewriteTTL: options.RewriteTTL,
|
||||
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type RDRCStore interface {
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type FakeIPStore interface {
|
||||
Service
|
||||
SimpleLifecycle
|
||||
Contains(address netip.Addr) bool
|
||||
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
||||
Lookup(address netip.Addr) (string, bool)
|
||||
|
@ -74,6 +74,7 @@ type InboundContext struct {
|
||||
UDPTimeout time.Duration
|
||||
TLSFragment bool
|
||||
TLSFragmentFallbackDelay time.Duration
|
||||
TLSRecordFragment bool
|
||||
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
|
@ -37,13 +37,14 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
|
||||
|
||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started && m.stage >= stage {
|
||||
panic("already started")
|
||||
}
|
||||
m.started = true
|
||||
m.stage = stage
|
||||
for _, inbound := range m.inbounds {
|
||||
inbounds := m.inbounds
|
||||
m.access.Unlock()
|
||||
for _, inbound := range inbounds {
|
||||
err := adapter.LegacyStart(inbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
|
@ -2,6 +2,11 @@ package adapter
|
||||
|
||||
import E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
type SimpleLifecycle interface {
|
||||
Start() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type StartStage uint8
|
||||
|
||||
const (
|
||||
|
@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error {
|
||||
}
|
||||
|
||||
type lifecycleServiceWrapper struct {
|
||||
Service
|
||||
SimpleLifecycle
|
||||
name string
|
||||
}
|
||||
|
||||
func NewLifecycleService(service Service, name string) LifecycleService {
|
||||
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
|
||||
return &lifecycleServiceWrapper{
|
||||
Service: service,
|
||||
name: name,
|
||||
SimpleLifecycle: service,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
|
||||
}
|
||||
|
||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||
return LegacyStart(l.Service, stage)
|
||||
return LegacyStart(l.SimpleLifecycle, stage)
|
||||
}
|
||||
|
||||
func (l *lifecycleServiceWrapper) Close() error {
|
||||
return l.Service.Close()
|
||||
return l.SimpleLifecycle.Close()
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ type HeadlessRule interface {
|
||||
|
||||
type Rule interface {
|
||||
HeadlessRule
|
||||
Service
|
||||
SimpleLifecycle
|
||||
Type() string
|
||||
Action() RuleAction
|
||||
}
|
||||
|
@ -1,6 +1,27 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Start() error
|
||||
Close() error
|
||||
Lifecycle
|
||||
Type() string
|
||||
Tag() string
|
||||
}
|
||||
|
||||
type ServiceRegistry interface {
|
||||
option.ServiceOptionsRegistry
|
||||
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
|
||||
}
|
||||
|
||||
type ServiceManager interface {
|
||||
Lifecycle
|
||||
Services() []Service
|
||||
Get(tag string) (Service, bool)
|
||||
Remove(tag string) error
|
||||
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
|
||||
}
|
||||
|
21
adapter/service/adapter.go
Normal file
21
adapter/service/adapter.go
Normal file
@ -0,0 +1,21 @@
|
||||
package service
|
||||
|
||||
type Adapter struct {
|
||||
serviceType string
|
||||
serviceTag string
|
||||
}
|
||||
|
||||
func NewAdapter(serviceType string, serviceTag string) Adapter {
|
||||
return Adapter{
|
||||
serviceType: serviceType,
|
||||
serviceTag: serviceTag,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.serviceType
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.serviceTag
|
||||
}
|
144
adapter/service/manager.go
Normal file
144
adapter/service/manager.go
Normal file
@ -0,0 +1,144 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var _ adapter.ServiceManager = (*Manager)(nil)
|
||||
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.ServiceRegistry
|
||||
access sync.Mutex
|
||||
started bool
|
||||
stage adapter.StartStage
|
||||
services []adapter.Service
|
||||
serviceByTag map[string]adapter.Service
|
||||
}
|
||||
|
||||
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
serviceByTag: make(map[string]adapter.Service),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
m.access.Lock()
|
||||
if m.started && m.stage >= stage {
|
||||
panic("already started")
|
||||
}
|
||||
m.started = true
|
||||
m.stage = stage
|
||||
services := m.services
|
||||
m.access.Unlock()
|
||||
for _, service := range services {
|
||||
err := adapter.LegacyStart(service, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if !m.started {
|
||||
return nil
|
||||
}
|
||||
m.started = false
|
||||
services := m.services
|
||||
m.services = nil
|
||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||
var err error
|
||||
for _, service := range services {
|
||||
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
|
||||
err = E.Append(err, service.Close(), func(err error) error {
|
||||
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Services() []adapter.Service {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
return m.services
|
||||
}
|
||||
|
||||
func (m *Manager) Get(tag string) (adapter.Service, bool) {
|
||||
m.access.Lock()
|
||||
service, found := m.serviceByTag[tag]
|
||||
m.access.Unlock()
|
||||
return service, found
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
m.access.Lock()
|
||||
service, found := m.serviceByTag[tag]
|
||||
if !found {
|
||||
m.access.Unlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
delete(m.serviceByTag, tag)
|
||||
index := common.Index(m.services, func(it adapter.Service) bool {
|
||||
return it == service
|
||||
})
|
||||
if index == -1 {
|
||||
panic("invalid service index")
|
||||
}
|
||||
m.services = append(m.services[:index], m.services[index+1:]...)
|
||||
started := m.started
|
||||
m.access.Unlock()
|
||||
if started {
|
||||
return service.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
|
||||
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started {
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(service, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
||||
if m.started {
|
||||
err = existsService.Close()
|
||||
if err != nil {
|
||||
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
|
||||
}
|
||||
}
|
||||
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
|
||||
return it == existsService
|
||||
})
|
||||
if existsIndex == -1 {
|
||||
panic("invalid service index")
|
||||
}
|
||||
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
|
||||
}
|
||||
m.services = append(m.services, service)
|
||||
m.serviceByTag[tag] = service
|
||||
return nil
|
||||
}
|
72
adapter/service/registry.go
Normal file
72
adapter/service/registry.go
Normal file
@ -0,0 +1,72 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
|
||||
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
||||
})
|
||||
}
|
||||
|
||||
var _ adapter.ServiceRegistry = (*Registry)(nil)
|
||||
|
||||
type (
|
||||
optionsConstructorFunc func() any
|
||||
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
access sync.Mutex
|
||||
optionsType map[string]optionsConstructorFunc
|
||||
constructor map[string]constructorFunc
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
optionsType: make(map[string]optionsConstructorFunc),
|
||||
constructor: make(map[string]constructorFunc),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
return optionsConstructor(), true
|
||||
}
|
||||
|
||||
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
constructor, loaded := m.constructor[outboundType]
|
||||
if !loaded {
|
||||
return nil, E.New("outbound type not found: " + outboundType)
|
||||
}
|
||||
return constructor(ctx, logger, tag, options)
|
||||
}
|
||||
|
||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.optionsType[outboundType] = optionsConstructor
|
||||
m.constructor[outboundType] = constructor
|
||||
}
|
18
adapter/ssm.go
Normal file
18
adapter/ssm.go
Normal file
@ -0,0 +1,18 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ManagedSSMServer interface {
|
||||
Inbound
|
||||
SetTracker(tracker SSMTracker)
|
||||
UpdateUsers(users []string, uPSKs []string) error
|
||||
}
|
||||
|
||||
type SSMTracker interface {
|
||||
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
|
||||
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
|
||||
}
|
@ -3,6 +3,6 @@ package adapter
|
||||
import "time"
|
||||
|
||||
type TimeService interface {
|
||||
Service
|
||||
SimpleLifecycle
|
||||
TimeFunc() func() time.Time
|
||||
}
|
||||
|
123
box.go
123
box.go
@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||
"github.com/sagernet/sing-box/common/certificate"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
@ -34,22 +35,23 @@ import (
|
||||
"github.com/sagernet/sing/service/pause"
|
||||
)
|
||||
|
||||
var _ adapter.Service = (*Box)(nil)
|
||||
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
||||
|
||||
type Box struct {
|
||||
createdAt time.Time
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
network *route.NetworkManager
|
||||
endpoint *endpoint.Manager
|
||||
inbound *inbound.Manager
|
||||
outbound *outbound.Manager
|
||||
dnsTransport *dns.TransportManager
|
||||
dnsRouter *dns.Router
|
||||
connection *route.ConnectionManager
|
||||
router *route.Router
|
||||
services []adapter.LifecycleService
|
||||
done chan struct{}
|
||||
createdAt time.Time
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
network *route.NetworkManager
|
||||
endpoint *endpoint.Manager
|
||||
inbound *inbound.Manager
|
||||
outbound *outbound.Manager
|
||||
service *boxService.Manager
|
||||
dnsTransport *dns.TransportManager
|
||||
dnsRouter *dns.Router
|
||||
connection *route.ConnectionManager
|
||||
router *route.Router
|
||||
internalService []adapter.LifecycleService
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
@ -64,6 +66,7 @@ func Context(
|
||||
outboundRegistry adapter.OutboundRegistry,
|
||||
endpointRegistry adapter.EndpointRegistry,
|
||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||
serviceRegistry adapter.ServiceRegistry,
|
||||
) context.Context {
|
||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||
@ -84,6 +87,10 @@ func Context(
|
||||
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||
}
|
||||
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@ -99,6 +106,7 @@ func New(options Options) (*Box, error) {
|
||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||
|
||||
if endpointRegistry == nil {
|
||||
return nil, E.New("missing endpoint registry in context")
|
||||
@ -109,6 +117,12 @@ func New(options Options) (*Box, error) {
|
||||
if outboundRegistry == nil {
|
||||
return nil, E.New("missing outbound registry in context")
|
||||
}
|
||||
if dnsTransportRegistry == nil {
|
||||
return nil, E.New("missing DNS transport registry in context")
|
||||
}
|
||||
if serviceRegistry == nil {
|
||||
return nil, E.New("missing service registry in context")
|
||||
}
|
||||
|
||||
ctx = pause.WithDefaultManager(ctx)
|
||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||
@ -142,7 +156,7 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "create log factory")
|
||||
}
|
||||
|
||||
var services []adapter.LifecycleService
|
||||
var internalServices []adapter.LifecycleService
|
||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||
len(certificateOptions.Certificate) > 0 ||
|
||||
@ -153,7 +167,7 @@ func New(options Options) (*Box, error) {
|
||||
return nil, err
|
||||
}
|
||||
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||
services = append(services, certificateStore)
|
||||
internalServices = append(internalServices, certificateStore)
|
||||
}
|
||||
|
||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||
@ -162,10 +176,12 @@ func New(options Options) (*Box, error) {
|
||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||
@ -280,6 +296,24 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||
}
|
||||
}
|
||||
for i, serviceOptions := range options.Services {
|
||||
var tag string
|
||||
if serviceOptions.Tag != "" {
|
||||
tag = serviceOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = serviceManager.Create(
|
||||
ctx,
|
||||
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
serviceOptions.Type,
|
||||
serviceOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||
}
|
||||
}
|
||||
outboundManager.Initialize(common.Must1(
|
||||
direct.NewOutbound(
|
||||
ctx,
|
||||
@ -305,7 +339,7 @@ func New(options Options) (*Box, error) {
|
||||
if needCacheFile {
|
||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||
services = append(services, cacheFile)
|
||||
internalServices = append(internalServices, cacheFile)
|
||||
}
|
||||
if needClashAPI {
|
||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||
@ -316,7 +350,7 @@ func New(options Options) (*Box, error) {
|
||||
}
|
||||
router.AppendTracker(clashServer)
|
||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||
services = append(services, clashServer)
|
||||
internalServices = append(internalServices, clashServer)
|
||||
}
|
||||
if needV2RayAPI {
|
||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||
@ -325,7 +359,7 @@ func New(options Options) (*Box, error) {
|
||||
}
|
||||
if v2rayServer.StatsService() != nil {
|
||||
router.AppendTracker(v2rayServer.StatsService())
|
||||
services = append(services, v2rayServer)
|
||||
internalServices = append(internalServices, v2rayServer)
|
||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||
}
|
||||
}
|
||||
@ -343,22 +377,23 @@ func New(options Options) (*Box, error) {
|
||||
WriteToSystem: ntpOptions.WriteToSystem,
|
||||
})
|
||||
timeService.TimeService = ntpService
|
||||
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||
}
|
||||
return &Box{
|
||||
network: networkManager,
|
||||
endpoint: endpointManager,
|
||||
inbound: inboundManager,
|
||||
outbound: outboundManager,
|
||||
dnsTransport: dnsTransportManager,
|
||||
dnsRouter: dnsRouter,
|
||||
connection: connectionManager,
|
||||
router: router,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
services: services,
|
||||
done: make(chan struct{}),
|
||||
network: networkManager,
|
||||
endpoint: endpointManager,
|
||||
inbound: inboundManager,
|
||||
outbound: outboundManager,
|
||||
dnsTransport: dnsTransportManager,
|
||||
service: serviceManager,
|
||||
dnsRouter: dnsRouter,
|
||||
connection: connectionManager,
|
||||
router: router,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
internalService: internalServices,
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -408,11 +443,11 @@ func (s *Box) preStart() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "start logger")
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -428,31 +463,27 @@ func (s *Box) start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.inbound.Start(adapter.StartStateStart)
|
||||
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
|
||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -469,7 +500,7 @@ func (s *Box) Close() error {
|
||||
err := common.Close(
|
||||
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||
)
|
||||
for _, lifecycleService := range s.services {
|
||||
for _, lifecycleService := range s.internalService {
|
||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", lifecycleService.Name())
|
||||
})
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 8354b78e5d002d636827cfeed6ed5df8ea057452
|
||||
Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0
|
@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
tag := tagVersion.VersionString()
|
||||
client := createClient(10 * time.Minute)
|
||||
client := createClient(20 * time.Minute)
|
||||
|
||||
log.Info(tag, " list build IDs")
|
||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||
@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
build := builds.Data[0]
|
||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
|
||||
log.Info(string(platform), " ", tag, " waiting for process")
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
|
@ -59,8 +59,8 @@ func init() {
|
||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||
|
||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||
memcTags = append(memcTags, "with_tailscale")
|
||||
debugTags = append(debugTags, "debug")
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@ -68,6 +67,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||
configPaths = append(configPaths, "config.json")
|
||||
}
|
||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
||||
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
if flagRuleSetMatchFormat == "" {
|
||||
switch filepath.Ext(sourcePath) {
|
||||
case ".json":
|
||||
flagRuleSetMatchFormat = C.RuleSetFormatSource
|
||||
case ".srs":
|
||||
flagRuleSetMatchFormat = C.RuleSetFormatBinary
|
||||
}
|
||||
}
|
||||
var ruleSet option.PlainRuleSetCompat
|
||||
switch flagRuleSetMatchFormat {
|
||||
case C.RuleSetFormatSource:
|
||||
|
@ -7,7 +7,8 @@ import (
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/utls"
|
||||
|
||||
"github.com/metacubex/utls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -24,8 +25,8 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
|
||||
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
||||
func utlsReadRecord(c *tls.Conn) error
|
||||
|
||||
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
|
||||
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
||||
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
||||
|
@ -66,23 +66,19 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||
}
|
||||
if options.BindInterface != "" {
|
||||
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
||||
}
|
||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
if options.RoutingMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||
}
|
||||
if networkManager != nil {
|
||||
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
|
||||
if autoRedirectOutputMark > 0 {
|
||||
if options.RoutingMark > 0 {
|
||||
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
|
||||
}
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
if !C.IsLinux {
|
||||
return nil, E.New("`routing_mark` is only supported on Linux")
|
||||
}
|
||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||
}
|
||||
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
||||
if disableDefaultBind || options.TCPFastOpen {
|
||||
@ -101,10 +97,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
if platformInterface != nil {
|
||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||
@ -116,6 +108,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
}
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
@ -127,8 +123,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
}
|
||||
}
|
||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||
}
|
||||
}
|
||||
if options.ReuseAddr {
|
||||
@ -210,6 +206,22 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefault bool) control.Func {
|
||||
if networkManager == nil {
|
||||
return control.RoutingMark(mark)
|
||||
}
|
||||
return func(network, address string, conn syscall.RawConn) error {
|
||||
if networkManager.AutoRedirectOutputMark() != 0 {
|
||||
if isDefault {
|
||||
return E.New("`route.default_mark` is conflict with `tun.auto_redirect`")
|
||||
} else {
|
||||
return E.New("`routing_mark` is conflict with `tun.auto_redirect`")
|
||||
}
|
||||
}
|
||||
return control.RoutingMark(mark)(network, address, conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||
if !address.IsValid() {
|
||||
return nil, E.New("invalid address")
|
||||
@ -335,7 +347,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||
return d.udpListener.ListenPacket(context.Background(), network, address)
|
||||
udpListener := d.udpListener
|
||||
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
for _, wgControlFn := range WgControlFns {
|
||||
err := wgControlFn(network, address, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return udpListener.ListenPacket(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
|
@ -83,6 +83,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||
//nolint:staticcheck
|
||||
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
||||
}
|
||||
server = dialOptions.DomainResolver.Server
|
||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||
@ -95,22 +96,31 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||
} else if options.DirectResolver {
|
||||
return nil, E.New("missing domain resolver for domain server address")
|
||||
} else if defaultOptions.DomainResolver != "" {
|
||||
dnsQueryOptions = defaultOptions.DomainResolveOptions
|
||||
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
|
||||
if !loaded {
|
||||
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
|
||||
}
|
||||
dnsQueryOptions.Transport = transport
|
||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||
} else {
|
||||
transports := dnsTransport.Transports()
|
||||
if len(transports) < 2 {
|
||||
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||
} else if options.NewDialer {
|
||||
return nil, E.New("missing domain resolver for domain server address")
|
||||
} else if !options.DirectOutbound {
|
||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||
if defaultOptions.DomainResolver != "" {
|
||||
dnsQueryOptions = defaultOptions.DomainResolveOptions
|
||||
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
|
||||
if !loaded {
|
||||
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
|
||||
}
|
||||
dnsQueryOptions.Transport = transport
|
||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||
} else {
|
||||
transports := dnsTransport.Transports()
|
||||
if len(transports) < 2 {
|
||||
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||
} else if options.NewDialer {
|
||||
return nil, E.New("missing domain resolver for domain server address")
|
||||
} else if !options.DirectOutbound {
|
||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||
}
|
||||
}
|
||||
if
|
||||
//nolint:staticcheck
|
||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||
//nolint:staticcheck
|
||||
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
||||
}
|
||||
}
|
||||
dialer = NewResolveDialer(
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@ -76,10 +75,11 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||
return c.conn.Write(b)
|
||||
default:
|
||||
}
|
||||
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||
if err != nil {
|
||||
c.conn = nil
|
||||
c.err = E.Cause(err, "dial tcp fast open")
|
||||
c.err = err
|
||||
} else {
|
||||
c.conn = conn
|
||||
}
|
||||
n = len(b)
|
||||
close(c.create)
|
||||
|
@ -1,158 +0,0 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var defaultSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
var memorysSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kb": KiByte,
|
||||
"mb": MiByte,
|
||||
"gb": GiByte,
|
||||
"tb": TiByte,
|
||||
"pb": PiByte,
|
||||
"eb": EiByte,
|
||||
"": Byte,
|
||||
"k": KiByte,
|
||||
"m": MiByte,
|
||||
"g": GiByte,
|
||||
"t": TiByte,
|
||||
"p": PiByte,
|
||||
"e": EiByte,
|
||||
}
|
||||
|
||||
var (
|
||||
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
)
|
||||
|
||||
func Bytes(s uint64) string {
|
||||
return humanateBytes(s, 1000, defaultSizes)
|
||||
}
|
||||
|
||||
func MemoryBytes(s uint64) string {
|
||||
return humanateBytes(s, 1024, defaultSizes)
|
||||
}
|
||||
|
||||
func IBytes(s uint64) string {
|
||||
return humanateBytes(s, 1024, iSizes)
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
return parseBytes0(s, defaultSizeTable)
|
||||
}
|
||||
|
||||
func ParseMemoryBytes(s string) (uint64, error) {
|
||||
return parseBytes0(s, memorysSizeTable)
|
||||
}
|
||||
|
||||
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := sizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
@ -32,6 +32,7 @@ type Listener struct {
|
||||
disablePacketOutput bool
|
||||
setSystemProxy bool
|
||||
systemProxySOCKS bool
|
||||
tproxy bool
|
||||
|
||||
tcpListener net.Listener
|
||||
systemProxy settings.SystemProxy
|
||||
@ -54,6 +55,7 @@ type Options struct {
|
||||
DisablePacketOutput bool
|
||||
SetSystemProxy bool
|
||||
SystemProxySOCKS bool
|
||||
TProxy bool
|
||||
}
|
||||
|
||||
func New(
|
||||
@ -71,6 +73,7 @@ func New(
|
||||
disablePacketOutput: options.DisablePacketOutput,
|
||||
setSystemProxy: options.SetSystemProxy,
|
||||
systemProxySOCKS: options.SystemProxySOCKS,
|
||||
tproxy: options.TProxy,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,18 @@ package listener
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/redir"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
@ -23,6 +27,15 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||
var err error
|
||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||
var listenConfig net.ListenConfig
|
||||
if l.listenOptions.BindInterface != "" {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||
}
|
||||
if l.listenOptions.RoutingMark != 0 {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||
}
|
||||
if l.listenOptions.ReuseAddr {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||
}
|
||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||
if keepIdle == 0 {
|
||||
@ -40,6 +53,13 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||
}
|
||||
setMultiPathTCP(&listenConfig)
|
||||
}
|
||||
if l.tproxy {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), false)
|
||||
})
|
||||
})
|
||||
}
|
||||
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
||||
if l.listenOptions.TCPFastOpen {
|
||||
var tfoConfig tfo.ListenConfig
|
||||
|
@ -5,17 +5,30 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/redir"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||
var lc net.ListenConfig
|
||||
var listenConfig net.ListenConfig
|
||||
if l.listenOptions.BindInterface != "" {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||
}
|
||||
if l.listenOptions.RoutingMark != 0 {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||
}
|
||||
if l.listenOptions.ReuseAddr {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||
}
|
||||
var udpFragment bool
|
||||
if l.listenOptions.UDPFragment != nil {
|
||||
udpFragment = *l.listenOptions.UDPFragment
|
||||
@ -23,10 +36,17 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
udpFragment = l.listenOptions.UDPFragmentDefault
|
||||
}
|
||||
if !udpFragment {
|
||||
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())
|
||||
}
|
||||
if l.tproxy {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), true)
|
||||
})
|
||||
})
|
||||
}
|
||||
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||
return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -37,8 +57,32 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
return udpConn, err
|
||||
}
|
||||
|
||||
func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
|
||||
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
|
||||
if l.listenOptions.BindInterface != "" {
|
||||
dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||
}
|
||||
if l.listenOptions.RoutingMark != 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||
}
|
||||
if l.listenOptions.ReuseAddr {
|
||||
dialer.Control = control.Append(dialer.Control, control.ReuseAddr())
|
||||
}
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
|
||||
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||
if l.listenOptions.BindInterface != "" {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||
}
|
||||
if l.listenOptions.RoutingMark != 0 {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||
}
|
||||
if l.listenOptions.ReuseAddr {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||
}
|
||||
return listenConfig.ListenPacket(ctx, network, address)
|
||||
})
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
||||
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||
@ -20,11 +20,13 @@ func TProxy(fd uintptr, isIPv6 bool) error {
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
||||
}
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
||||
if isUDP {
|
||||
if err == nil {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
if err == nil && isIPv6 {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/sagernet/sing/common/control"
|
||||
)
|
||||
|
||||
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
|
@ -31,13 +31,18 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
const header = "BitTorrent protocol"
|
||||
var protocol [19]byte
|
||||
_, err = reader.Read(protocol[:])
|
||||
var n int
|
||||
n, err = reader.Read(protocol[:])
|
||||
if string(protocol[:n]) != header[:n] {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if string(protocol[:]) != "BitTorrent protocol" {
|
||||
return os.ErrInvalid
|
||||
if n < 19 {
|
||||
return ErrNeedMoreData
|
||||
}
|
||||
|
||||
metadata.Protocol = C.ProtocolBitTorrent
|
||||
|
@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffIncompleteBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("13426974546f7272656e74")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("13426974546f7272656e75")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffUTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -20,22 +20,36 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if length == 0 {
|
||||
if length < 12 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
buffer := buf.NewSize(int(length))
|
||||
defer buffer.Release()
|
||||
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||
var n int
|
||||
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||
packet := buffer.Bytes()
|
||||
if n > 2 && packet[2]&0x80 != 0 { // QR
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
|
||||
return os.ErrInvalid
|
||||
}
|
||||
for i := 6; i < 10; i++ {
|
||||
// ANCOUNT, NSCOUNT
|
||||
if n > i && packet[i] != 0 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
|
||||
return DomainNameQuery(readCtx, metadata, packet)
|
||||
}
|
||||
|
||||
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||
var msg mDNS.Msg
|
||||
err := msg.Unpack(packet)
|
||||
if err != nil {
|
||||
if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
|
||||
return err
|
||||
}
|
||||
metadata.Protocol = C.ProtocolDNS
|
||||
|
@ -1,6 +1,7 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
@ -21,3 +22,32 @@ func TestSniffDNS(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||
}
|
||||
|
||||
func TestSniffStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||
}
|
||||
|
||||
func TestSniffIncompleteStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000001000000000000")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000000000000000000")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
||||
}
|
||||
sniffError = E.Errors(sniffError, err)
|
||||
}
|
||||
if !errors.Is(err, ErrNeedMoreData) {
|
||||
if !errors.Is(sniffError, ErrNeedMoreData) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,11 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
const sshPrefix = "SSH-2.0-"
|
||||
bReader := bufio.NewReader(reader)
|
||||
prefix, err := bReader.Peek(len(sshPrefix))
|
||||
if string(prefix[:]) != sshPrefix[:len(prefix)] {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
} else if string(prefix) != sshPrefix {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
fistLine, _, err := bReader.ReadLine()
|
||||
if err != nil {
|
||||
|
@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) {
|
||||
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
||||
require.Equal(t, "dropbear", metadata.Client)
|
||||
}
|
||||
|
||||
func TestSniffIncompleteSSH(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("5353482d322e30")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotSSH(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("5353482d322e31")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func (w *acmeWrapper) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||
var acmeServer string
|
||||
switch options.Provider {
|
||||
case "", "letsencrypt":
|
||||
|
@ -11,6 +11,6 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ type (
|
||||
STDConfig = tls.Config
|
||||
STDConn = tls.Conn
|
||||
ConnectionState = tls.ConnectionState
|
||||
CurveID = tls.CurveID
|
||||
)
|
||||
|
||||
func ParseTLSVersion(version string) (uint16, error) {
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
@ -46,7 +48,10 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
|
||||
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||
return &STDClientConfig{tlsConfig}, nil
|
||||
} else {
|
||||
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil
|
||||
return &STDECHClientConfig{
|
||||
STDClientConfig: STDClientConfig{tlsConfig},
|
||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,11 +104,28 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||
|
||||
type STDECHClientConfig struct {
|
||||
STDClientConfig
|
||||
dnsRouter adapter.DNSRouter
|
||||
access sync.Mutex
|
||||
dnsRouter adapter.DNSRouter
|
||||
lastTTL time.Duration
|
||||
lastUpdate time.Time
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||
message := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
RecursionDesired: true,
|
||||
@ -133,6 +155,8 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode ECH config")
|
||||
}
|
||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
||||
s.lastUpdate = time.Now()
|
||||
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||
break match
|
||||
}
|
||||
@ -143,19 +167,11 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
||||
return nil, E.New("no ECH config found in DNS records")
|
||||
}
|
||||
}
|
||||
tlsConn, err := s.Client(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
return s.Client(conn)
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) Clone() Config {
|
||||
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter}
|
||||
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||
}
|
||||
|
||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||
|
5
common/tls/ech_tag_stub.go
Normal file
5
common/tls/ech_tag_stub.go
Normal file
@ -0,0 +1,5 @@
|
||||
//go:build with_ech
|
||||
|
||||
package tls
|
||||
|
||||
var _ int = "Due to the migration to stdlib, the separate `with_ech` build tag has been deprecated and is no longer needed, please update your build configuration."
|
@ -29,12 +29,13 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
utls "github.com/sagernet/utls"
|
||||
|
||||
utls "github.com/metacubex/utls"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
@ -114,6 +115,22 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, extension := range uConn.Extensions {
|
||||
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
|
||||
ce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool {
|
||||
return curveID != utls.X25519MLKEM768
|
||||
})
|
||||
}
|
||||
if ks, ok := extension.(*utls.KeyShareExtension); ok {
|
||||
ks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool {
|
||||
return share.Group != utls.X25519MLKEM768
|
||||
})
|
||||
}
|
||||
}
|
||||
err = uConn.BuildHandshakeState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(uConfig.NextProtos) > 0 {
|
||||
for _, extension := range uConn.Extensions {
|
||||
@ -148,9 +165,13 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdheKey := uConn.HandshakeState.State13.EcdheKey
|
||||
keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys
|
||||
if keyShareKeys == nil {
|
||||
return nil, E.New("nil KeyShareKeys")
|
||||
}
|
||||
ecdheKey := keyShareKeys.Ecdhe
|
||||
if ecdheKey == nil {
|
||||
return nil, E.New("nil ecdhe_key")
|
||||
return nil, E.New("nil ecdheKey")
|
||||
}
|
||||
authKey, err := ecdheKey.ECDH(publicKey)
|
||||
if err != nil {
|
||||
@ -214,10 +235,6 @@ func realityClientFallback(ctx context.Context, uConn net.Conn, serverName strin
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||
e.uClient.config.SessionIDGenerator = generator
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) Clone() Config {
|
||||
return &RealityClientConfig{
|
||||
e.ctx,
|
||||
|
@ -1,4 +1,4 @@
|
||||
//go:build with_reality_server
|
||||
//go:build with_utls
|
||||
|
||||
package tls
|
||||
|
||||
@ -7,28 +7,29 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/reality"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
|
||||
utls "github.com/metacubex/utls"
|
||||
)
|
||||
|
||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||
|
||||
type RealityServerConfig struct {
|
||||
config *reality.Config
|
||||
config *utls.RealityConfig
|
||||
}
|
||||
|
||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
||||
var tlsConfig reality.Config
|
||||
var tlsConfig utls.RealityConfig
|
||||
|
||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||
return nil, E.New("acme is unavailable in reality")
|
||||
@ -74,6 +75,11 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
||||
}
|
||||
|
||||
tlsConfig.SessionTicketsDisabled = true
|
||||
tlsConfig.Log = func(format string, v ...any) {
|
||||
if logger != nil {
|
||||
logger.Trace(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
tlsConfig.Type = N.NetworkTCP
|
||||
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
||||
|
||||
@ -89,16 +95,20 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
||||
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
||||
|
||||
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
||||
for i, shortIDString := range options.Reality.ShortID {
|
||||
var shortID [8]byte
|
||||
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
|
||||
if len(options.Reality.ShortID) == 0 {
|
||||
tlsConfig.ShortIds[[8]byte{0}] = true
|
||||
} else {
|
||||
for i, shortIDString := range options.Reality.ShortID {
|
||||
var shortID [8]byte
|
||||
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
|
||||
}
|
||||
if decodedLen > 8 {
|
||||
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
|
||||
}
|
||||
tlsConfig.ShortIds[shortID] = true
|
||||
}
|
||||
if decodedLen > 8 {
|
||||
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
|
||||
}
|
||||
tlsConfig.ShortIds[shortID] = true
|
||||
}
|
||||
|
||||
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
||||
@ -109,10 +119,6 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
||||
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
}
|
||||
|
||||
if debug.Enabled {
|
||||
tlsConfig.Show = true
|
||||
}
|
||||
|
||||
return &RealityServerConfig{&tlsConfig}, nil
|
||||
}
|
||||
|
||||
@ -153,7 +159,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
||||
tlsConn, err := reality.Server(ctx, conn, c.config)
|
||||
tlsConn, err := utls.RealityServer(ctx, conn, c.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -169,7 +175,7 @@ func (c *RealityServerConfig) Clone() Config {
|
||||
var _ Conn = (*realityConnWrapper)(nil)
|
||||
|
||||
type realityConnWrapper struct {
|
||||
*reality.Conn
|
||||
*utls.Conn
|
||||
}
|
||||
|
||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||
|
@ -1,15 +1,5 @@
|
||||
//go:build !with_reality_server
|
||||
//go:build with_reality_server
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
||||
}
|
||||
var _ int = "The separate `with_reality_server` build tag has been merged into `with_utls` and is no longer needed, please update your build configuration."
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@ -21,7 +22,7 @@ var errInsecureUnused = E.New("tls: insecure unused")
|
||||
type STDServerConfig struct {
|
||||
config *tls.Config
|
||||
logger log.Logger
|
||||
acmeService adapter.Service
|
||||
acmeService adapter.SimpleLifecycle
|
||||
certificate []byte
|
||||
key []byte
|
||||
certificatePath string
|
||||
@ -164,7 +165,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
return nil, nil
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
var acmeService adapter.Service
|
||||
var acmeService adapter.SimpleLifecycle
|
||||
var err error
|
||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||
//nolint:staticcheck
|
||||
@ -233,8 +234,12 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
key = content
|
||||
}
|
||||
if certificate == nil && key == nil && options.Insecure {
|
||||
timeFunc := ntp.TimeFuncFromContext(ctx)
|
||||
if timeFunc == nil {
|
||||
timeFunc = time.Now
|
||||
}
|
||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||
return GenerateKeyPair(nil, nil, timeFunc, info.ServerName)
|
||||
}
|
||||
} else {
|
||||
if certificate == nil {
|
||||
|
@ -16,8 +16,8 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
utls "github.com/sagernet/utls"
|
||||
|
||||
utls "github.com/metacubex/utls"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
|
@ -5,6 +5,7 @@ package tls
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
@ -14,5 +15,9 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
||||
}
|
||||
|
||||
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
||||
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
|
||||
}
|
||||
|
||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package tf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
@ -17,17 +19,19 @@ type Conn struct {
|
||||
tcpConn *net.TCPConn
|
||||
ctx context.Context
|
||||
firstPacketWritten bool
|
||||
splitRecord bool
|
||||
fallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
tcpConn: tcpConn,
|
||||
ctx: ctx,
|
||||
splitRecord: splitRecord,
|
||||
fallbackDelay: fallbackDelay,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
@ -37,10 +41,12 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
}()
|
||||
serverName := indexTLSServerName(b)
|
||||
if serverName != nil {
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(true)
|
||||
if err != nil {
|
||||
return
|
||||
if !c.splitRecord {
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
splits := strings.Split(serverName.ServerName, ".")
|
||||
@ -61,16 +67,25 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
currentIndex++
|
||||
}
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
for i := 0; i <= len(splitIndexes); i++ {
|
||||
var payload []byte
|
||||
if i == 0 {
|
||||
payload = b[:splitIndexes[i]]
|
||||
if c.splitRecord {
|
||||
payload = payload[recordLayerHeaderLen:]
|
||||
}
|
||||
} else if i == len(splitIndexes) {
|
||||
payload = b[splitIndexes[i-1]:]
|
||||
} else {
|
||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||
}
|
||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
||||
if c.splitRecord {
|
||||
payloadLen := uint16(len(payload))
|
||||
buffer.Write(b[:3])
|
||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
||||
buffer.Write(payload)
|
||||
} else if c.tcpConn != nil && i != len(splitIndexes) {
|
||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||
if err != nil {
|
||||
return
|
||||
@ -82,11 +97,18 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(false)
|
||||
if c.splitRecord {
|
||||
_, err = c.Conn.Write(buffer.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
32
common/tlsfragment/conn_test.go
Normal file
32
common/tlsfragment/conn_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package tf_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
tf "github.com/sagernet/sing-box/common/tlsfragment"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTLSFragment(t *testing.T) {
|
||||
t.Parallel()
|
||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||
require.NoError(t, err)
|
||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
|
||||
ServerName: "www.cloudflare.com",
|
||||
})
|
||||
require.NoError(t, tlsConn.Handshake())
|
||||
}
|
||||
|
||||
func TestTLSRecordFragment(t *testing.T) {
|
||||
t.Parallel()
|
||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||
require.NoError(t, err)
|
||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
|
||||
ServerName: "www.cloudflare.com",
|
||||
})
|
||||
require.NoError(t, tlsConn.Handshake())
|
||||
}
|
@ -25,6 +25,9 @@ const (
|
||||
TypeTUIC = "tuic"
|
||||
TypeHysteria2 = "hysteria2"
|
||||
TypeTailscale = "tailscale"
|
||||
TypeDERP = "derp"
|
||||
TypeResolved = "resolved"
|
||||
TypeSSMAPI = "ssm-api"
|
||||
)
|
||||
|
||||
const (
|
||||
|
6
debug.go
6
debug.go
@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||
if options.MemoryLimit.Value() != 0 {
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
||||
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
||||
runtime.ReadMemStats(&memStats)
|
||||
|
||||
var memObject badjson.JSONObject
|
||||
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||
memObject.Put("rss", rusageMaxRSS())
|
||||
|
||||
|
@ -34,6 +34,7 @@ type Client struct {
|
||||
disableCache bool
|
||||
disableExpire bool
|
||||
independentCache bool
|
||||
clientSubnet netip.Prefix
|
||||
rdrc adapter.RDRCStore
|
||||
initRDRCFunc func() adapter.RDRCStore
|
||||
logger logger.ContextLogger
|
||||
@ -47,6 +48,7 @@ type ClientOptions struct {
|
||||
DisableExpire bool
|
||||
IndependentCache bool
|
||||
CacheCapacity uint32
|
||||
ClientSubnet netip.Prefix
|
||||
RDRC func() adapter.RDRCStore
|
||||
Logger logger.ContextLogger
|
||||
}
|
||||
@ -57,6 +59,7 @@ func NewClient(options ClientOptions) *Client {
|
||||
disableCache: options.DisableCache,
|
||||
disableExpire: options.DisableExpire,
|
||||
independentCache: options.IndependentCache,
|
||||
clientSubnet: options.ClientSubnet,
|
||||
initRDRCFunc: options.RDRC,
|
||||
logger: options.Logger,
|
||||
}
|
||||
@ -104,8 +107,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
return &responseMessage, nil
|
||||
}
|
||||
question := message.Question[0]
|
||||
if options.ClientSubnet.IsValid() {
|
||||
message = SetClientSubnet(message, options.ClientSubnet, true)
|
||||
clientSubnet := options.ClientSubnet
|
||||
if !clientSubnet.IsValid() {
|
||||
clientSubnet = c.clientSubnet
|
||||
}
|
||||
if clientSubnet.IsValid() {
|
||||
message = SetClientSubnet(message, clientSubnet)
|
||||
}
|
||||
isSimpleRequest := len(message.Question) == 1 &&
|
||||
len(message.Ns) == 0 &&
|
||||
@ -232,10 +239,20 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
record.Header().Ttl = timeToLive
|
||||
}
|
||||
}
|
||||
response.Id = messageId
|
||||
if !disableCache {
|
||||
c.storeCache(transport, question, response, timeToLive)
|
||||
}
|
||||
response.Id = messageId
|
||||
requestEDNSOpt := message.IsEdns0()
|
||||
responseEDNSOpt := response.IsEdns0()
|
||||
if responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) {
|
||||
response.Extra = common.Filter(response.Extra, func(it dns.RR) bool {
|
||||
return it.Header().Rrtype != dns.TypeOPT
|
||||
})
|
||||
if requestEDNSOpt != nil {
|
||||
response.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do())
|
||||
}
|
||||
}
|
||||
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||
return response, err
|
||||
}
|
||||
@ -243,9 +260,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
domain = FqdnToDomain(domain)
|
||||
dnsName := dns.Fqdn(domain)
|
||||
if options.Strategy == C.DomainStrategyIPv4Only {
|
||||
var strategy C.DomainStrategy
|
||||
if options.LookupStrategy != C.DomainStrategyAsIS {
|
||||
strategy = options.LookupStrategy
|
||||
} else {
|
||||
strategy = options.Strategy
|
||||
}
|
||||
if strategy == C.DomainStrategyIPv4Only {
|
||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
||||
} else if options.Strategy == C.DomainStrategyIPv6Only {
|
||||
} else if strategy == C.DomainStrategyIPv6Only {
|
||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
||||
}
|
||||
var response4 []netip.Addr
|
||||
@ -271,7 +294,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
||||
if len(response4) == 0 && len(response6) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return sortAddresses(response4, response6, options.Strategy), nil
|
||||
return sortAddresses(response4, response6, strategy), nil
|
||||
}
|
||||
|
||||
func (c *Client) ClearCache() {
|
||||
@ -483,7 +506,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
||||
}
|
||||
|
||||
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
||||
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
||||
if response.Rcode != dns.RcodeSuccess {
|
||||
return nil, RcodeError(response.Rcode)
|
||||
}
|
||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||
@ -527,12 +550,26 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
|
||||
return value, loaded
|
||||
}
|
||||
|
||||
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
||||
return &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: message.Id,
|
||||
Rcode: rcode,
|
||||
Response: true,
|
||||
},
|
||||
Question: message.Question,
|
||||
}
|
||||
}
|
||||
|
||||
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
|
||||
response := dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: id,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Response: true,
|
||||
Id: id,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionDesired: true,
|
||||
RecursionAvailable: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{question},
|
||||
}
|
||||
@ -565,9 +602,12 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
|
||||
func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg {
|
||||
response := dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: id,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Response: true,
|
||||
Id: id,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionDesired: true,
|
||||
RecursionAvailable: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{question},
|
||||
Answer: []dns.RR{
|
||||
@ -588,9 +628,12 @@ func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToL
|
||||
func FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg {
|
||||
response := dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: id,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Response: true,
|
||||
Id: id,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionDesired: true,
|
||||
RecursionAvailable: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{question},
|
||||
Answer: []dns.RR{
|
||||
@ -611,9 +654,12 @@ func FixedResponseTXT(id uint16, question dns.Question, records []string, timeTo
|
||||
func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg {
|
||||
response := dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: id,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Response: true,
|
||||
Id: id,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionDesired: true,
|
||||
RecursionAvailable: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Question: []dns.Question{question},
|
||||
}
|
||||
|
@ -6,7 +6,11 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg {
|
||||
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg {
|
||||
return setClientSubnet(message, clientSubnet, true)
|
||||
}
|
||||
|
||||
func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {
|
||||
var (
|
||||
optRecord *dns.OPT
|
||||
subnetOption *dns.EDNS0_SUBNET
|
||||
@ -19,9 +23,6 @@ findExists:
|
||||
var isEDNS0Subnet bool
|
||||
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
||||
if isEDNS0Subnet {
|
||||
if !override {
|
||||
return message
|
||||
}
|
||||
break findExists
|
||||
}
|
||||
}
|
||||
@ -37,14 +38,14 @@ findExists:
|
||||
},
|
||||
}
|
||||
message.Extra = append(message.Extra, optRecord)
|
||||
} else {
|
||||
message = message.Copy()
|
||||
} else if clone {
|
||||
return setClientSubnet(message.Copy(), clientSubnet, false)
|
||||
}
|
||||
if subnetOption == nil {
|
||||
subnetOption = new(dns.EDNS0_SUBNET)
|
||||
subnetOption.Code = dns.EDNS0SUBNET
|
||||
optRecord.Option = append(optRecord.Option, subnetOption)
|
||||
}
|
||||
subnetOption.Code = dns.EDNS0SUBNET
|
||||
if clientSubnet.Addr().Is4() {
|
||||
subnetOption.Family = 1
|
||||
} else {
|
||||
|
@ -55,6 +55,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
||||
RDRC: func() adapter.RDRCStore {
|
||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||
if cacheFile == nil {
|
||||
@ -258,7 +259,14 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
return FixedResponse(message.Id, message.Question[0], nil, 0), nil
|
||||
return &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: message.Id,
|
||||
Rcode: mDNS.RcodeRefused,
|
||||
Response: true,
|
||||
},
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
}, nil
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
@ -285,7 +293,12 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
} else if errors.Is(err, ErrResponseRejected) {
|
||||
rejected = true
|
||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||
/*} else if responseCheck!= nil && errors.Is(err, RcodeError(mDNS.RcodeNameError)) {
|
||||
rejected = true
|
||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||
*/
|
||||
} else if len(message.Question) > 0 {
|
||||
rejected = true
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||
@ -323,6 +336,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
err error
|
||||
)
|
||||
printResult := func() {
|
||||
if err == nil && len(responseAddrs) == 0 {
|
||||
err = E.New("empty result")
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrResponseRejectedCached) {
|
||||
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||
@ -331,15 +347,15 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||
}
|
||||
} else if len(responseAddrs) == 0 {
|
||||
r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
||||
err = RcodeNameError
|
||||
}
|
||||
if err != nil {
|
||||
err = E.Cause(err, "lookup ", domain)
|
||||
}
|
||||
}
|
||||
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
||||
if cached {
|
||||
if len(responseAddrs) == 0 {
|
||||
return nil, RcodeNameError
|
||||
return nil, E.New("lookup ", domain, ": empty result (cached)")
|
||||
}
|
||||
return responseAddrs, nil
|
||||
}
|
||||
|
@ -96,6 +96,9 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 443
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return NewHTTPSRaw(
|
||||
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
||||
logger,
|
||||
|
@ -3,6 +3,7 @@ package local
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@ -35,6 +36,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
||||
}
|
||||
return &Transport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||
ctx: ctx,
|
||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||
dialer: transportDialer,
|
||||
}, nil
|
||||
@ -57,7 +59,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||
}
|
||||
}
|
||||
systemConfig := getSystemDNSConfig()
|
||||
systemConfig := getSystemDNSConfig(t.ctx)
|
||||
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
||||
} else {
|
||||
@ -89,8 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
||||
startRacer := func(ctx context.Context, fqdn string) {
|
||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||
if err == nil {
|
||||
addresses, _ := dns.MessageToAddresses(response)
|
||||
if len(addresses) == 0 {
|
||||
var addresses []netip.Addr
|
||||
addresses, err = dns.MessageToAddresses(response)
|
||||
if err == nil && len(addresses) == 0 {
|
||||
err = E.New(fqdn, ": empty result")
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -23,19 +24,21 @@ type resolverConfig struct {
|
||||
|
||||
var resolvConf resolverConfig
|
||||
|
||||
func getSystemDNSConfig() *dnsConfig {
|
||||
resolvConf.tryUpdate("/etc/resolv.conf")
|
||||
func getSystemDNSConfig(ctx context.Context) *dnsConfig {
|
||||
resolvConf.tryUpdate(ctx, "/etc/resolv.conf")
|
||||
return resolvConf.dnsConfig.Load()
|
||||
}
|
||||
|
||||
func (conf *resolverConfig) init() {
|
||||
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf"))
|
||||
func (conf *resolverConfig) init(ctx context.Context) {
|
||||
conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf"))
|
||||
conf.lastChecked = time.Now()
|
||||
conf.ch = make(chan struct{}, 1)
|
||||
}
|
||||
|
||||
func (conf *resolverConfig) tryUpdate(name string) {
|
||||
conf.initOnce.Do(conf.init)
|
||||
func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {
|
||||
conf.initOnce.Do(func() {
|
||||
conf.init(ctx)
|
||||
})
|
||||
|
||||
if conf.dnsConfig.Load().noReload {
|
||||
return
|
||||
@ -59,7 +62,7 @@ func (conf *resolverConfig) tryUpdate(name string) {
|
||||
return
|
||||
}
|
||||
}
|
||||
dnsConf := dnsReadConfig(name)
|
||||
dnsConf := dnsReadConfig(ctx, name)
|
||||
conf.dnsConfig.Store(dnsConf)
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ package local
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@ -18,7 +19,7 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func dnsReadConfig(_ string) *dnsConfig {
|
||||
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||
if C.res_init() != 0 {
|
||||
return &dnsConfig{
|
||||
servers: defaultNS,
|
||||
|
@ -4,6 +4,7 @@ package local
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
@ -13,7 +14,7 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func dnsReadConfig(name string) *dnsConfig {
|
||||
func dnsReadConfig(_ context.Context, name string) *dnsConfig {
|
||||
conf := &dnsConfig{
|
||||
ndots: 1,
|
||||
timeout: 5 * time.Second,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
@ -8,10 +9,13 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func dnsReadConfig(_ string) *dnsConfig {
|
||||
func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
|
||||
conf := &dnsConfig{
|
||||
ndots: 1,
|
||||
timeout: 5 * time.Second,
|
||||
@ -22,35 +26,35 @@ func dnsReadConfig(_ string) *dnsConfig {
|
||||
conf.servers = defaultNS
|
||||
}
|
||||
}()
|
||||
aas, err := adapterAddresses()
|
||||
addresses, err := adapterAddresses()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, aa := range aas {
|
||||
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
|
||||
if aa.OperStatus != windows.IfOperStatusUp {
|
||||
var dnsAddresses []struct {
|
||||
ifName string
|
||||
netip.Addr
|
||||
}
|
||||
for _, address := range addresses {
|
||||
if address.OperStatus != windows.IfOperStatusUp {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only take interfaces which have at least one gateway
|
||||
if aa.FirstGatewayAddress == nil {
|
||||
if address.IfType == windows.IF_TYPE_TUNNEL {
|
||||
continue
|
||||
}
|
||||
|
||||
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
|
||||
sa, err := dns.Address.Sockaddr.Sockaddr()
|
||||
if address.FirstGatewayAddress == nil {
|
||||
continue
|
||||
}
|
||||
for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {
|
||||
rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var ip netip.Addr
|
||||
switch sa := sa.(type) {
|
||||
var dnsServerAddr netip.Addr
|
||||
switch sockaddr := rawSockaddr.(type) {
|
||||
case *syscall.SockaddrInet4:
|
||||
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]})
|
||||
dnsServerAddr = netip.AddrFrom4(sockaddr.Addr)
|
||||
case *syscall.SockaddrInet6:
|
||||
var addr16 [16]byte
|
||||
copy(addr16[:], sa.Addr[:])
|
||||
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
|
||||
if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {
|
||||
// fec0/10 IPv6 addresses are site local anycast DNS
|
||||
// addresses Microsoft sets by default if no other
|
||||
// IPv6 DNS address is set. Site local anycast is
|
||||
@ -58,14 +62,27 @@ func dnsReadConfig(_ string) *dnsConfig {
|
||||
// https://datatracker.ietf.org/doc/html/rfc3879
|
||||
continue
|
||||
}
|
||||
ip = netip.AddrFrom16(addr16)
|
||||
dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
|
||||
default:
|
||||
// Unexpected type.
|
||||
continue
|
||||
}
|
||||
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53"))
|
||||
dnsAddresses = append(dnsAddresses, struct {
|
||||
ifName string
|
||||
netip.Addr
|
||||
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
|
||||
}
|
||||
}
|
||||
var myInterface string
|
||||
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
|
||||
myInterface = networkManager.InterfaceMonitor().MyInterface()
|
||||
}
|
||||
for _, address := range dnsAddresses {
|
||||
if address.ifName == myInterface {
|
||||
continue
|
||||
}
|
||||
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,9 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 443
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return &HTTP3Transport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
||||
logger: logger,
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
sQUIC "github.com/sagernet/sing-quic"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@ -58,6 +59,9 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 853
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return &Transport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
||||
ctx: ctx,
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@ -40,6 +41,9 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 53
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return &TCPTransport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
||||
dialer: transportDialer,
|
||||
|
@ -57,13 +57,20 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 853
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return NewTLSRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), transportDialer, serverAddr, tlsConfig), nil
|
||||
}
|
||||
|
||||
func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr, tlsConfig tls.Config) *TLSTransport {
|
||||
return &TLSTransport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
|
||||
TransportAdapter: adapter,
|
||||
logger: logger,
|
||||
dialer: transportDialer,
|
||||
dialer: dialer,
|
||||
serverAddr: serverAddr,
|
||||
tlsConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TLSTransport) Start(stage adapter.StartStage) error {
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@ -47,6 +48,9 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
||||
if serverAddr.Port == 0 {
|
||||
serverAddr.Port = 53
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return nil, E.New("invalid server address: ", serverAddr)
|
||||
}
|
||||
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
|
||||
}
|
||||
|
||||
@ -117,7 +121,7 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
||||
conn.access.Unlock()
|
||||
defer func() {
|
||||
conn.access.Lock()
|
||||
delete(conn.callbacks, messageId)
|
||||
delete(conn.callbacks, exMessage.Id)
|
||||
conn.access.Unlock()
|
||||
}()
|
||||
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
||||
|
@ -2,7 +2,134 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.12.0-beta.2
|
||||
#### 1.12.0-beta.22
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-beta.21
|
||||
|
||||
* Fix missing `home` option for DERP service **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
You can now choose what the DERP home page shows, just like with derper's `-home` flag.
|
||||
|
||||
See [DERP](/configuration/service/derp/#home).
|
||||
|
||||
### 1.11.13
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-beta.17
|
||||
|
||||
* Update quic-go to v0.52.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-beta.15
|
||||
|
||||
* Add DERP service **1**
|
||||
* Add Resolved service and DNS server **2**
|
||||
* Add SSM API service **3**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
||||
|
||||
See [DERP Service](/configuration/service/derp/).
|
||||
|
||||
**2**:
|
||||
|
||||
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
||||
(e.g. NetworkManager) and provide DNS resolution.
|
||||
|
||||
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
|
||||
|
||||
**3**:
|
||||
|
||||
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
||||
|
||||
See [SSM API Service](/configuration/service/ssm-api/).
|
||||
|
||||
### 1.11.11
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-beta.13
|
||||
|
||||
* Add TLS record fragment route options **1**
|
||||
* Add missing `accept_routes` option for Tailscale **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Route Action](/configuration/route/rule_action/#tls_record_fragment).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Tailscale](/configuration/endpoint/tailscale/#accept_routes).
|
||||
|
||||
#### 1.12.0-beta.10
|
||||
|
||||
* Add control options for listeners **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen/).
|
||||
|
||||
### 1.11.10
|
||||
|
||||
* Undeprecate the `block` outbound **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Since we don’t have a replacement for using the `block` outbound in selectors yet,
|
||||
we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future.
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-beta.9
|
||||
|
||||
* Update quic-go to v0.51.0
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.9
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-beta.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.8
|
||||
|
||||
* Improve `auto_redirect` **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks,
|
||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-beta.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -10,7 +137,8 @@ icon: material/alert-decagram
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-beta.1
|
||||
|
||||
@ -25,7 +153,8 @@ see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-alpha.19
|
||||
|
||||
@ -65,7 +194,8 @@ See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
#### 1.12.0-alpha.13
|
||||
|
||||
@ -136,7 +266,8 @@ For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and pa
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
|
||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration
|
||||
process._
|
||||
|
||||
#### 1.12.0-alpha.5
|
||||
|
||||
|
@ -9,6 +9,10 @@ and the data generated by the software is always on your device.
|
||||
|
||||
## Android
|
||||
|
||||
The broad package (App) visibility (QUERY_ALL_PACKAGES) permission
|
||||
is used to provide per-application proxy features for VPN,
|
||||
sing-box will not collect your app list.
|
||||
|
||||
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
|
||||
sing-box uses the location permission in the background
|
||||
to get information about the connected Wi-Fi network to make them work.
|
||||
|
@ -1,7 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-decagram: [servers](#servers)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
@ -1,7 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-decagram: [servers](#servers)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
@ -81,7 +81,7 @@ Will overrides `dns.client_subnet`.
|
||||
|
||||
#### method
|
||||
|
||||
- `default`: Reply with NXDOMAIN.
|
||||
- `default`: Reply with REFUSED.
|
||||
- `drop`: Drop the request.
|
||||
|
||||
`default` will be used by default.
|
||||
|
@ -81,7 +81,7 @@ icon: material/new-box
|
||||
|
||||
#### method
|
||||
|
||||
- `default`: 返回 NXDOMAIN。
|
||||
- `default`: 返回 REFUSED。
|
||||
- `drop`: 丢弃请求。
|
||||
|
||||
默认使用 `defualt`。
|
||||
|
@ -27,19 +27,21 @@ icon: material/alert-decagram
|
||||
|
||||
The type of the DNS server.
|
||||
|
||||
| Type | Format |
|
||||
|-----------------|-----------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `predefined` | [Predefined](./predefined/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| Type | Format |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| `resolved` | [Resolved](./resolved/) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
@ -27,19 +27,21 @@ icon: material/alert-decagram
|
||||
|
||||
DNS 服务器的类型。
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-----------------|-----------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `predefined` | [Predefined](./predefined/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| 类型 | 格式 |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
| `udp` | [UDP](./udp/) |
|
||||
| `tls` | [TLS](./tls/) |
|
||||
| `quic` | [QUIC](./quic/) |
|
||||
| `https` | [HTTPS](./https/) |
|
||||
| `h3` | [HTTP/3](./http3/) |
|
||||
| `dhcp` | [DHCP](./dhcp/) |
|
||||
| `fakeip` | [Fake IP](./fakeip/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
| `resolved` | [Resolved](./resolved/) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
84
docs/configuration/dns/server/resolved.md
Normal file
84
docs/configuration/dns/server/resolved.md
Normal file
@ -0,0 +1,84 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Resolved
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "resolved",
|
||||
"tag": "",
|
||||
|
||||
"service": "resolved",
|
||||
"accept_default_resolvers": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Fields
|
||||
|
||||
#### service
|
||||
|
||||
==Required==
|
||||
|
||||
The tag of the [Resolved Service](/configuration/service/resolved).
|
||||
|
||||
#### accept_default_resolvers
|
||||
|
||||
Indicates whether the default DNS resolvers should be accepted for fallback queries in addition to matching domains.
|
||||
|
||||
Specifically, default DNS resolvers are DNS servers that have `SetLinkDefaultRoute` or `SetLinkDomains ~.` set.
|
||||
|
||||
If not enabled, `NXDOMAIN` will be returned for requests that do not match search or match domains.
|
||||
|
||||
### Examples
|
||||
|
||||
=== "Split DNS only"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "local",
|
||||
"tag": "local"
|
||||
},
|
||||
{
|
||||
"type": "resolved",
|
||||
"tag": "resolved",
|
||||
"service": "resolved"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"ip_accept_any": true,
|
||||
"server": "resolved"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Use as global DNS"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "resolved",
|
||||
"service": "resolved",
|
||||
"accept_default_resolvers": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
@ -30,13 +30,13 @@ icon: material/new-box
|
||||
|
||||
==Required==
|
||||
|
||||
The tag of the Tailscale endpoint.
|
||||
The tag of the [Tailscale Endpoint](/configuration/endpoint/tailscale).
|
||||
|
||||
#### accept_default_resolvers
|
||||
|
||||
Indicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。
|
||||
|
||||
if not enabled, NXDOMAIN will be returned for non-Tailscale domain queries.
|
||||
if not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries.
|
||||
|
||||
### Examples
|
||||
|
||||
|
@ -6,7 +6,7 @@ icon: material/new-box
|
||||
|
||||
# Endpoint
|
||||
|
||||
Endpoint is protocols that has both inbound and outbound behavior.
|
||||
An endpoint is a protocol with inbound and outbound behavior.
|
||||
|
||||
### Structure
|
||||
|
||||
|
@ -15,6 +15,7 @@ icon: material/new-box
|
||||
"control_url": "",
|
||||
"ephemeral": false,
|
||||
"hostname": "",
|
||||
"accept_routes": false,
|
||||
"exit_node": "",
|
||||
"exit_node_allow_lan_access": false,
|
||||
"advertise_routes": [],
|
||||
@ -62,6 +63,10 @@ System hostname is used by default.
|
||||
|
||||
Example: `localhost`
|
||||
|
||||
#### accept_routes
|
||||
|
||||
Indicates whether the node should accept routes advertised by other nodes.
|
||||
|
||||
#### exit_node
|
||||
|
||||
The exit node name or IP address to use.
|
||||
|
@ -42,16 +42,18 @@ AnyTLS padding scheme line array.
|
||||
|
||||
Default padding scheme:
|
||||
|
||||
```
|
||||
stop=8
|
||||
0=30-30
|
||||
1=100-400
|
||||
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||
3=9-9,500-1000
|
||||
4=500-1000
|
||||
5=500-1000
|
||||
6=500-1000
|
||||
7=500-1000
|
||||
```json
|
||||
[
|
||||
"stop=8",
|
||||
"0=30-30",
|
||||
"1=100-400",
|
||||
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
|
||||
"3=9-9,500-1000",
|
||||
"4=500-1000",
|
||||
"5=500-1000",
|
||||
"6=500-1000",
|
||||
"7=500-1000"
|
||||
]
|
||||
```
|
||||
|
||||
#### tls
|
||||
|
@ -42,16 +42,18 @@ AnyTLS 填充方案行数组。
|
||||
|
||||
默认填充方案:
|
||||
|
||||
```
|
||||
stop=8
|
||||
0=30-30
|
||||
1=100-400
|
||||
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||
3=9-9,500-1000
|
||||
4=500-1000
|
||||
5=500-1000
|
||||
6=500-1000
|
||||
7=500-1000
|
||||
```json
|
||||
[
|
||||
"stop=8",
|
||||
"0=30-30",
|
||||
"1=100-400",
|
||||
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
|
||||
"3=9-9,500-1000",
|
||||
"4=500-1000",
|
||||
"5=500-1000",
|
||||
"6=500-1000",
|
||||
"7=500-1000"
|
||||
]
|
||||
```
|
||||
|
||||
#### tls
|
||||
|
@ -213,7 +213,7 @@ Set the default route to the Tun.
|
||||
|
||||
!!! note "Also enable `auto_redirect`"
|
||||
|
||||
`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts with Docker bridge networks.
|
||||
`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks.
|
||||
|
||||
#### iproute2_table_index
|
||||
|
||||
@ -239,20 +239,21 @@ Linux iproute2 rule start index generated by `auto_route`.
|
||||
|
||||
Only supported on Linux with `auto_route` enabled.
|
||||
|
||||
Automatically configure iptables/nftables to redirect connections.
|
||||
Improve TUN routing and performance using nftables.
|
||||
|
||||
Auto redirect is always recommended on Linux, it provides better routing,
|
||||
`auto_redirect` is always recommended on Linux, it provides better routing,
|
||||
higher performance (better than tproxy),
|
||||
and avoids conflicts with Docker bridge networks.
|
||||
and avoids conflicts between TUN and Docker bridge networks.
|
||||
|
||||
*In Android*:
|
||||
|
||||
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||
Note that `auto_redirect` also works on Android,
|
||||
but due to the lack of `nftables` and `ip6tables`,
|
||||
only simple IPv4 TCP forwarding is performed.
|
||||
To share your VPN connection over hotspot or repeater on Android,
|
||||
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||
|
||||
*In Linux*:
|
||||
|
||||
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
|
||||
`auto_redirect` also automatically inserts compatibility rules
|
||||
into the OpenWrt fw4 table, i.e.
|
||||
it will work on routers without any extra configuration.
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
|
||||
@ -279,17 +280,15 @@ Enforce strict routing rules when `auto_route` is enabled:
|
||||
*In Linux*:
|
||||
|
||||
* Let unsupported network unreachable
|
||||
* Make ICMP traffic route to tun instead of upstream interfaces
|
||||
* Route all connections to tun
|
||||
|
||||
It prevents IP address leaks and makes DNS hijacking work on Android.
|
||||
* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
|
||||
|
||||
*In Windows*:
|
||||
|
||||
* Add firewall rules to prevent DNS leak caused by
|
||||
* Let unsupported network unreachable
|
||||
* prevent DNS leak caused by
|
||||
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
||||
|
||||
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
||||
It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations.
|
||||
|
||||
#### route_address
|
||||
|
||||
|
@ -217,7 +217,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! note "也启用 `auto_redirect`"
|
||||
|
||||
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy), 并避免与 Docker 桥接网络冲突。
|
||||
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy), 并避免 TUN 与 Docker 桥接网络冲突。
|
||||
|
||||
#### iproute2_table_index
|
||||
|
||||
@ -243,17 +243,14 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
仅支持 Linux,且需要 `auto_route` 已启用。
|
||||
|
||||
自动配置 iptables/nftables 以重定向连接。
|
||||
通过使用 nftables 改善 TUN 路由和性能。
|
||||
|
||||
在 Linux 上始终推荐使用 auto redirect,它提供更好的路由, 更高的性能(优于 tproxy), 并避免与 Docker 桥接网络冲突。
|
||||
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy),并避免了 TUN 和 Docker 桥接网络之间的冲突。
|
||||
|
||||
*在 Android 中*:
|
||||
请注意,`auto_redirect` 也适用于 Android,但由于缺少 `nftables` 和 `ip6tables`,仅执行简单的 IPv4 TCP 转发。
|
||||
若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
||||
|
||||
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
||||
|
||||
*在 Linux 中*:
|
||||
|
||||
带有 `auto_redirect` 的 `auto_route` 在路由器上**无需干预**即可按预期工作。
|
||||
`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。
|
||||
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
|
||||
@ -261,7 +258,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
`auto_redriect` 使用的连接输入标记。
|
||||
`auto_redirect` 使用的连接输入标记。
|
||||
|
||||
默认使用 `0x2023`。
|
||||
|
||||
@ -269,29 +266,25 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
`auto_redriect` 使用的连接输出标记。
|
||||
`auto_redirect` 使用的连接输出标记。
|
||||
|
||||
默认使用 `0x2024`。
|
||||
|
||||
#### strict_route
|
||||
|
||||
启用 `auto_route` 时执行严格的路由规则。
|
||||
当启用 `auto_route` 时,强制执行严格的路由规则:
|
||||
|
||||
*在 Linux 中*:
|
||||
*在 Linux 中*:
|
||||
|
||||
* 让不支持的网络无法到达
|
||||
* 使 ICMP 流量路由到 tun 而不是上游接口
|
||||
* 将所有连接路由到 tun
|
||||
* 使不支持的网络不可达。
|
||||
* 出于历史遗留原因,当未启用 `strict_route` 或 `auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。
|
||||
|
||||
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
|
||||
*在 Windows 中*:
|
||||
|
||||
*在 Windows 中*:
|
||||
* 使不支持的网络不可达。
|
||||
* 阻止 Windows 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) 造成的 DNS 泄露
|
||||
|
||||
* 添加防火墙规则以阻止 Windows
|
||||
的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
||||
造成的 DNS 泄露
|
||||
|
||||
它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
||||
它可能会使某些 Windows 应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
||||
|
||||
#### route_address
|
||||
|
||||
@ -405,11 +398,11 @@ UDP NAT 过期时间。
|
||||
|
||||
TCP/IP 栈。
|
||||
|
||||
| 栈 | 描述 |
|
||||
|--------|------------------------------------------------------------------|
|
||||
| system | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||
| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||
| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||
| 栈 | 描述 |
|
||||
|----------|-------------------------------------------------------------------------------------------------------|
|
||||
| `system` | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||
| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||
| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||
|
||||
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
|
||||
|
||||
|
@ -14,6 +14,7 @@ sing-box uses JSON for configuration files.
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
"services": [],
|
||||
"experimental": {}
|
||||
}
|
||||
```
|
||||
@ -30,6 +31,7 @@ sing-box uses JSON for configuration files.
|
||||
| `inbounds` | [Inbound](./inbound/) |
|
||||
| `outbounds` | [Outbound](./outbound/) |
|
||||
| `route` | [Route](./route/) |
|
||||
| `services` | [Service](./service/) |
|
||||
| `experimental` | [Experimental](./experimental/) |
|
||||
|
||||
### Check
|
||||
|
@ -14,6 +14,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
"services": [],
|
||||
"experimental": {}
|
||||
}
|
||||
```
|
||||
@ -30,6 +31,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
| `inbounds` | [入站](./inbound/) |
|
||||
| `outbounds` | [出站](./outbound/) |
|
||||
| `route` | [路由](./route/) |
|
||||
| `services` | [服务](./service/) |
|
||||
| `experimental` | [实验性](./experimental/) |
|
||||
|
||||
### 检查
|
||||
|
@ -2,10 +2,6 @@
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
|
@ -2,10 +2,6 @@
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||
|
||||
`block` 出站关闭所有传入请求。
|
||||
|
||||
### 结构
|
||||
|
@ -6,6 +6,7 @@ icon: material/new-box
|
||||
|
||||
:material-plus: [tls_fragment](#tls_fragment)
|
||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
||||
:material-plus: [resolve.disable_cache](#disable_cache)
|
||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
||||
@ -91,7 +92,8 @@ Not available when `method` is set to drop.
|
||||
"udp_connect": false,
|
||||
"udp_timeout": "",
|
||||
"tls_fragment": false,
|
||||
"tls_fragment_fallback_delay": ""
|
||||
"tls_fragment_fallback_delay": "",
|
||||
"tls_record_fragment": ""
|
||||
}
|
||||
```
|
||||
|
||||
@ -164,13 +166,19 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
|
||||
|
||||
Fragment TLS handshakes to bypass firewalls.
|
||||
|
||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
|
||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
||||
and should not be used to circumvent real censorship.
|
||||
|
||||
Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked.
|
||||
Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.
|
||||
|
||||
On Linux, Apple platforms, (administrator privileges required) Windows, the wait time can be automatically detected, otherwise it will fall back to waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
||||
On Linux, Apple platforms, (administrator privileges required) Windows,
|
||||
the wait time can be automatically detected, otherwise it will fall back to
|
||||
waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
||||
|
||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, because the target is considered to be local or behind a transparent proxy.
|
||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
||||
because the target is considered to be local or behind a transparent proxy.
|
||||
|
||||
Conflict with `tls_record_fragment`.
|
||||
|
||||
#### tls_fragment_fallback_delay
|
||||
|
||||
@ -180,6 +188,17 @@ The fallback value used when TLS segmentation cannot automatically determine the
|
||||
|
||||
`500ms` is used by default.
|
||||
|
||||
#### tls_record_fragment
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
||||
|
||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
||||
and should not be used to circumvent real censorship.
|
||||
|
||||
Conflict with `tls_fragment`.
|
||||
|
||||
### sniff
|
||||
|
||||
```json
|
||||
|
@ -6,6 +6,10 @@ icon: material/new-box
|
||||
|
||||
:material-plus: [tls_fragment](#tls_fragment)
|
||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
||||
:material-plus: [resolve.disable_cache](#disable_cache)
|
||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
||||
|
||||
## 最终动作
|
||||
|
||||
@ -159,12 +163,15 @@ UDP 连接超时时间。
|
||||
|
||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||
|
||||
由于它不是为性能设计的,不应被应用于所有连接,而仅应用于已知被阻止的服务器名称。
|
||||
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
|
||||
|
||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
|
||||
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
||||
|
||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||
|
||||
与 `tls_record_fragment` 冲突。
|
||||
|
||||
#### tls_fragment_fallback_delay
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
@ -173,6 +180,16 @@ UDP 连接超时时间。
|
||||
|
||||
默认使用 `500ms`。
|
||||
|
||||
#### tls_record_fragment
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
||||
|
||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||
|
||||
与 `tls_fragment` 冲突。
|
||||
|
||||
### sniff
|
||||
|
||||
```json
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
| QUIC 客户端 | 类型 |
|
||||
|:------------------------:|:----------:|
|
||||
| Chromium/Cronet | `chrimium` |
|
||||
| Chromium/Cronet | `chromium` |
|
||||
| Safari/Apple Network API | `safari` |
|
||||
| Firefox / uquic firefox | `firefox` |
|
||||
| quic-go / uquic chrome | `quic-go` |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user