mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
58 Commits
v1.12.0-be
...
dev-mitm-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cca8893c9 | ||
|
|
58fdae33bb | ||
|
|
29ad1e3888 | ||
|
|
799a005ead | ||
|
|
0d5737dded | ||
|
|
6b6f02ea0f | ||
|
|
6c0785eb50 | ||
|
|
74f9bec2ba | ||
|
|
405560622a | ||
|
|
b5740dae0e | ||
|
|
4c8b868553 | ||
|
|
967940cfcc | ||
|
|
236e0765a5 | ||
|
|
fcc5ec8a5d | ||
|
|
3012c47695 | ||
|
|
2bc58c5f11 | ||
|
|
d5e174c2a8 | ||
|
|
5f5d5f8b3d | ||
|
|
87f122b2ab | ||
|
|
c7cc28d61f | ||
|
|
ba8b85f0d7 | ||
|
|
3419df90e2 | ||
|
|
3348c65c9b | ||
|
|
c9d6848eae | ||
|
|
9615f48327 | ||
|
|
1823bbde91 | ||
|
|
3834c1f5cd | ||
|
|
fc3424af15 | ||
|
|
f031d759ae | ||
|
|
e1fc66f072 | ||
|
|
00a0911252 | ||
|
|
e16c021308 | ||
|
|
9b5d9588ab | ||
|
|
7a1fb6b825 | ||
|
|
fa64180e49 | ||
|
|
9319e58a61 | ||
|
|
f2f50e7f79 | ||
|
|
3ea305b882 | ||
|
|
1c4bcefa8f | ||
|
|
e5d12c9b72 | ||
|
|
6a75487b34 | ||
|
|
fa4ed37d23 | ||
|
|
219cc6a944 | ||
|
|
0ff9e3833e | ||
|
|
a71b583a1a | ||
|
|
1462208d68 | ||
|
|
16babfc8ac | ||
|
|
a35c5d0fae | ||
|
|
259dae4399 | ||
|
|
96dabc048f | ||
|
|
833dbe7cb8 | ||
|
|
b810f34c30 | ||
|
|
e0a2e9412b | ||
|
|
f43974dd8c | ||
|
|
6bfad0200e | ||
|
|
045fb72420 | ||
|
|
ab4ea6ed68 | ||
|
|
cf72196a1a |
@@ -1,13 +1,11 @@
|
|||||||
-s dir
|
-s dir
|
||||||
--name sing-box
|
--name sing-box
|
||||||
--category net
|
--category net
|
||||||
--license GPL-3.0-or-later
|
--license GPLv3-or-later
|
||||||
--description "The universal proxy platform."
|
--description "The universal proxy platform."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
--no-deb-generate-changes
|
|
||||||
--config-files /etc/sing-box/config.json
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
30
.fpm_openwrt
30
.fpm_openwrt
@@ -1,30 +0,0 @@
|
|||||||
-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
|
|
||||||
28
.github/deb2ipk.sh
vendored
28
.github/deb2ipk.sh
vendored
@@ -1,28 +0,0 @@
|
|||||||
#!/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
|
|
||||||
134
.github/workflows/build.yml
vendored
134
.github/workflows/build.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.3
|
go-version: ^1.24
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
- name: Calculate version
|
- name: Calculate version
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/read_tag --ci --nightly
|
go run -v ./cmd/internal/read_tag --nightly
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
id: outputs
|
id: outputs
|
||||||
run: |-
|
run: |-
|
||||||
@@ -68,38 +68,31 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
os: [ linux, windows, darwin, android ]
|
||||||
|
arch: [ "386", amd64, arm64 ]
|
||||||
|
legacy_go: [ false ]
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||||
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||||
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||||
- { 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: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||||
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
- { 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: mipsle, debian: mipsel, rpm: mipsel }
|
||||||
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
|
||||||
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
|
||||||
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
|
||||||
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
|
||||||
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
|
|
||||||
- { 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: "386", legacy_go: true }
|
||||||
- { os: windows, arch: arm64 }
|
- { os: windows, arch: amd64, legacy_go: true }
|
||||||
|
|
||||||
- { os: darwin, arch: amd64 }
|
|
||||||
- { os: darwin, arch: arm64 }
|
|
||||||
|
|
||||||
|
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||||
|
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
exclude:
|
||||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
- { os: darwin, arch: "386" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
@@ -109,7 +102,7 @@ jobs:
|
|||||||
if: ${{ ! matrix.legacy_go }}
|
if: ${{ ! matrix.legacy_go }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.3
|
go-version: ^1.24
|
||||||
- name: Cache Legacy Go
|
- name: Cache Legacy Go
|
||||||
if: matrix.require_legacy_go
|
if: matrix.require_legacy_go
|
||||||
id: cache-legacy-go
|
id: cache-legacy-go
|
||||||
@@ -140,7 +133,10 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
||||||
|
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
||||||
|
TAGS="${TAGS},with_ech"
|
||||||
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.os != 'android'
|
if: matrix.os != 'android'
|
||||||
@@ -154,10 +150,7 @@ jobs:
|
|||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
GOOS: ${{ matrix.os }}
|
GOOS: ${{ matrix.os }}
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
GO386: ${{ matrix.go386 }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build Android
|
- name: Build Android
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
@@ -177,31 +170,21 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set name
|
- name: Set name
|
||||||
run: |-
|
run: |-
|
||||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
|
||||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
|
||||||
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
|
||||||
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
|
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
|
||||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
|
||||||
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-legacy"
|
|
||||||
fi
|
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
|
||||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Package DEB
|
- name: Package DEB
|
||||||
if: matrix.debian != ''
|
if: matrix.debian != ''
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
-v "$PKG_VERSION" \
|
-v "${{ needs.calculate_version.outputs.version }}" \
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
-p "dist/${PKG_NAME}.deb" \
|
||||||
--architecture ${{ matrix.debian }} \
|
--architecture ${{ matrix.debian }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
@@ -216,10 +199,9 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
-v "$PKG_VERSION" \
|
-v "${{ needs.calculate_version.outputs.version }}" \
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
-p "dist/${PKG_NAME}.rpm" \
|
||||||
--architecture ${{ matrix.rpm }} \
|
--architecture ${{ matrix.rpm }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
@@ -235,37 +217,20 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libarchive-tools
|
sudo apt-get install -y libarchive-tools
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t pacman \
|
fpm -t pacman \
|
||||||
-v "$PKG_VERSION" \
|
-v "${{ needs.calculate_version.outputs.version }}" \
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
||||||
--architecture ${{ matrix.pacman }} \
|
--architecture ${{ matrix.pacman }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
- name: Package OpenWrt
|
|
||||||
if: matrix.openwrt != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
cp .fpm_openwrt .fpm
|
|
||||||
fpm -t deb \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/openwrt.deb" \
|
|
||||||
--architecture all \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
for architecture in ${{ matrix.openwrt }}; do
|
|
||||||
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
|
||||||
done
|
|
||||||
rm "dist/openwrt.deb"
|
|
||||||
- name: Archive
|
- name: Archive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
cd dist
|
cd dist
|
||||||
mkdir -p "${DIR_NAME}"
|
mkdir -p "${DIR_NAME}"
|
||||||
cp ../LICENSE "${DIR_NAME}"
|
cp ../LICENSE "${DIR_NAME}"
|
||||||
if [ '${{ matrix.os }}' = 'windows' ]; then
|
if [ '${{ matrix.os }}' = 'windoes' ]; then
|
||||||
cp sing-box "${DIR_NAME}/sing-box.exe"
|
cp sing-box.exe "${DIR_NAME}"
|
||||||
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
||||||
else
|
else
|
||||||
cp sing-box "${DIR_NAME}"
|
cp sing-box "${DIR_NAME}"
|
||||||
@@ -277,7 +242,7 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
@@ -294,7 +259,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.3
|
go-version: ^1.24
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -351,9 +316,9 @@ jobs:
|
|||||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
- name: Prepare upload
|
- name: Prepare upload
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p dist
|
mkdir -p dist/release
|
||||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
||||||
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
|
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -374,7 +339,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.3
|
go-version: ^1.24
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -472,7 +437,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.3
|
go-version: ^1.24
|
||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -549,13 +514,10 @@ jobs:
|
|||||||
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||||
- name: Update version
|
|
||||||
if: matrix.if && matrix.name != 'iOS'
|
|
||||||
run: |-
|
|
||||||
go run -v ./cmd/internal/update_apple_version --ci
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_apple_version --ci
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
xcodebuild archive \
|
xcodebuild archive \
|
||||||
-scheme "${{ matrix.scheme }}" \
|
-scheme "${{ matrix.scheme }}" \
|
||||||
@@ -604,9 +566,9 @@ jobs:
|
|||||||
zip -r SFM.dSYMs.zip dSYMs
|
zip -r SFM.dSYMs.zip dSYMs
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p dist
|
mkdir -p dist/release
|
||||||
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
|
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
|
||||||
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
|
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
- name: Upload image
|
- name: Upload image
|
||||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.3
|
go-version: ^1.24
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
32
.github/workflows/linux.yml
vendored
32
.github/workflows/linux.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.3
|
go-version: ^1.24
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Calculate version
|
- name: Calculate version
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/read_tag --ci --nightly
|
go run -v ./cmd/internal/read_tag --nightly
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
id: outputs
|
id: outputs
|
||||||
run: |-
|
run: |-
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.3
|
go-version: ^1.24
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -80,7 +80,10 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
||||||
|
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
||||||
|
TAGS="${TAGS},with_ech"
|
||||||
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
@@ -106,24 +109,17 @@ jobs:
|
|||||||
if: contains(needs.calculate_version.outputs.version, '-')
|
if: contains(needs.calculate_version.outputs.version, '-')
|
||||||
run: |-
|
run: |-
|
||||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
- name: Set version
|
|
||||||
run: |-
|
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
|
||||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Package DEB
|
- name: Package DEB
|
||||||
if: matrix.debian != ''
|
if: matrix.debian != ''
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
--name "${NAME}" \
|
-v "${{ needs.calculate_version.outputs.version }}" \
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
||||||
--architecture ${{ matrix.debian }} \
|
--architecture ${{ matrix.debian }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/${NAME}
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||||
rm -rf $HOME/.gnupg
|
rm -rf $HOME/.gnupg
|
||||||
@@ -136,13 +132,11 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
--name "${NAME}" \
|
-v "${{ needs.calculate_version.outputs.version }}" \
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
||||||
--architecture ${{ matrix.rpm }} \
|
--architecture ${{ matrix.rpm }} \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/${NAME}
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||||
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||||
@@ -181,4 +175,6 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Publish packages
|
- name: Publish packages
|
||||||
run: |-
|
run: |-
|
||||||
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
wget -O fury-cli.deb https://github.com/gemfury/cli/releases/download/v0.23.0/fury-cli_0.23.0_linux_amd64.deb
|
||||||
|
sudo dpkg -i fury-cli.deb
|
||||||
|
fury migrate dist --as=sagernet --api-token ${{ secrets.FURY_TOKEN }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
/.idea/
|
/.idea/
|
||||||
/vendor/
|
/vendor/
|
||||||
/*.json
|
/*.json
|
||||||
|
/*.js
|
||||||
/*.srs
|
/*.srs
|
||||||
/*.db
|
/*.db
|
||||||
/site/
|
/site/
|
||||||
|
|||||||
@@ -21,15 +21,17 @@ linters-settings:
|
|||||||
- -SA1003
|
- -SA1003
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.23"
|
go: "1.24"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_script
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-dirs:
|
exclude-dirs:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
@@ -49,7 +50,7 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: "config|noreplace"
|
type: config
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
|
- with_script
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOTOOLCHAIN=local
|
- GOTOOLCHAIN=local
|
||||||
@@ -46,9 +48,11 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
|
- with_script
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
||||||
@@ -130,7 +134,7 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: "config|noreplace"
|
type: config
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ RUN set -ex \
|
|||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags \
|
&& go build -v -trimpath -tags \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -1,14 +1,14 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_clash_api,with_quic,with_utls,with_tailscale
|
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale,with_script
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ ci_build:
|
|||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
|
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -226,8 +226,8 @@ lib:
|
|||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.5
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.5
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import (
|
|||||||
type CertificateStore interface {
|
type CertificateStore interface {
|
||||||
LifecycleService
|
LifecycleService
|
||||||
Pool() *x509.CertPool
|
Pool() *x509.CertPool
|
||||||
|
TLSDecryptionEnabled() bool
|
||||||
|
TLSDecryptionCertificate() *x509.Certificate
|
||||||
|
TLSDecryptionPrivateKey() any
|
||||||
}
|
}
|
||||||
|
|
||||||
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
|
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ type CacheFile interface {
|
|||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedBinary
|
LoadRuleSet(tag string) *SavedBinary
|
||||||
SaveRuleSet(tag string, set *SavedBinary) error
|
SaveRuleSet(tag string, set *SavedBinary) error
|
||||||
|
LoadScript(tag string) *SavedBinary
|
||||||
|
SaveScript(tag string, script *SavedBinary) error
|
||||||
|
SurgePersistentStoreRead(key string) string
|
||||||
|
SurgePersistentStoreWrite(key string, value string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedBinary struct {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -58,6 +60,8 @@ type InboundContext struct {
|
|||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
PacketSniffError error
|
PacketSniffError error
|
||||||
|
HTTPRequest *http.Request
|
||||||
|
ClientHello *tls.ClientHelloInfo
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
@@ -74,7 +78,7 @@ type InboundContext struct {
|
|||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
TLSFragment bool
|
TLSFragment bool
|
||||||
TLSFragmentFallbackDelay time.Duration
|
TLSFragmentFallbackDelay time.Duration
|
||||||
TLSRecordFragment bool
|
MITM *option.MITMRouteOptions
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import E "github.com/sagernet/sing/common/exceptions"
|
import (
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
type StartStage uint8
|
type StartStage uint8
|
||||||
|
|
||||||
@@ -45,6 +47,9 @@ type LifecycleService interface {
|
|||||||
|
|
||||||
func Start(stage StartStage, services ...Lifecycle) error {
|
func Start(stage StartStage, services ...Lifecycle) error {
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
|
if service == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
13
adapter/mitm.go
Normal file
13
adapter/mitm.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MITMEngine interface {
|
||||||
|
Lifecycle
|
||||||
|
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ type Router interface {
|
|||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
AppendTracker(tracker ConnectionTracker)
|
SetTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
54
adapter/script.go
Normal file
54
adapter/script.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ScriptManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Scripts() []Script
|
||||||
|
Script(name string) (Script, bool)
|
||||||
|
SurgeCache() *SurgeInMemoryCache
|
||||||
|
}
|
||||||
|
|
||||||
|
type SurgeInMemoryCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Script interface {
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||||
|
PostStart() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SurgeScript interface {
|
||||||
|
Script
|
||||||
|
ExecuteGeneric(ctx context.Context, scriptType string, timeout time.Duration, arguments []string) error
|
||||||
|
ExecuteHTTPRequest(ctx context.Context, timeout time.Duration, request *http.Request, body []byte, binaryBody bool, arguments []string) (*HTTPRequestScriptResult, error)
|
||||||
|
ExecuteHTTPResponse(ctx context.Context, timeout time.Duration, request *http.Request, response *http.Response, body []byte, binaryBody bool, arguments []string) (*HTTPResponseScriptResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPRequestScriptResult struct {
|
||||||
|
URL string
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
Response *HTTPRequestScriptResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPRequestScriptResponse struct {
|
||||||
|
Status int
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPResponseScriptResult struct {
|
||||||
|
Status int
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
55
box.go
55
box.go
@@ -23,9 +23,11 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/mitm"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
|
"github.com/sagernet/sing-box/script"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
@@ -48,6 +50,8 @@ type Box struct {
|
|||||||
dnsRouter *dns.Router
|
dnsRouter *dns.Router
|
||||||
connection *route.ConnectionManager
|
connection *route.ConnectionManager
|
||||||
router *route.Router
|
router *route.Router
|
||||||
|
script *script.Manager
|
||||||
|
mitm adapter.MITMEngine //*mitm.Engine
|
||||||
services []adapter.LifecycleService
|
services []adapter.LifecycleService
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
@@ -143,18 +147,12 @@ func New(options Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var services []adapter.LifecycleService
|
var services []adapter.LifecycleService
|
||||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), common.PtrValueOrDefault(options.Certificate))
|
||||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
if err != nil {
|
||||||
len(certificateOptions.Certificate) > 0 ||
|
return nil, err
|
||||||
len(certificateOptions.CertificatePath) > 0 ||
|
|
||||||
len(certificateOptions.CertificateDirectoryPath) > 0 {
|
|
||||||
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
|
||||||
services = append(services, certificateStore)
|
|
||||||
}
|
}
|
||||||
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
|
services = append(services, certificateStore)
|
||||||
|
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||||
@@ -173,7 +171,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection"))
|
||||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
||||||
service.MustRegister[adapter.Router](ctx, router)
|
service.MustRegister[adapter.Router](ctx, router)
|
||||||
@@ -181,8 +179,8 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize router")
|
return nil, E.Cause(err, "initialize router")
|
||||||
}
|
}
|
||||||
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
|
||||||
var timeService *tls.TimeServiceWrapper
|
var timeService *tls.TimeServiceWrapper
|
||||||
|
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||||
if ntpOptions.Enabled {
|
if ntpOptions.Enabled {
|
||||||
timeService = new(tls.TimeServiceWrapper)
|
timeService = new(tls.TimeServiceWrapper)
|
||||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
@@ -296,6 +294,11 @@ func New(options Options) (*Box, error) {
|
|||||||
"local",
|
"local",
|
||||||
option.LocalDNSServerOptions{},
|
option.LocalDNSServerOptions{},
|
||||||
)))
|
)))
|
||||||
|
scriptManager, err := script.NewManager(ctx, logFactory, options.Scripts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize script manager")
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.ScriptManager](ctx, scriptManager)
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -314,7 +317,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash-server")
|
return nil, E.Cause(err, "create clash-server")
|
||||||
}
|
}
|
||||||
router.AppendTracker(clashServer)
|
router.SetTracker(clashServer)
|
||||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||||
services = append(services, clashServer)
|
services = append(services, clashServer)
|
||||||
}
|
}
|
||||||
@@ -324,7 +327,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create v2ray-server")
|
return nil, E.Cause(err, "create v2ray-server")
|
||||||
}
|
}
|
||||||
if v2rayServer.StatsService() != nil {
|
if v2rayServer.StatsService() != nil {
|
||||||
router.AppendTracker(v2rayServer.StatsService())
|
router.SetTracker(v2rayServer.StatsService())
|
||||||
services = append(services, v2rayServer)
|
services = append(services, v2rayServer)
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
}
|
}
|
||||||
@@ -345,6 +348,16 @@ func New(options Options) (*Box, error) {
|
|||||||
timeService.TimeService = ntpService
|
timeService.TimeService = ntpService
|
||||||
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
|
mitmOptions := common.PtrValueOrDefault(options.MITM)
|
||||||
|
var mitmEngine adapter.MITMEngine
|
||||||
|
if mitmOptions.Enabled {
|
||||||
|
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create MITM engine")
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.MITMEngine](ctx, engine)
|
||||||
|
mitmEngine = engine
|
||||||
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
@@ -354,6 +367,8 @@ func New(options Options) (*Box, error) {
|
|||||||
dnsRouter: dnsRouter,
|
dnsRouter: dnsRouter,
|
||||||
connection: connectionManager,
|
connection: connectionManager,
|
||||||
router: router,
|
router: router,
|
||||||
|
script: scriptManager,
|
||||||
|
mitm: mitmEngine,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
@@ -412,11 +427,11 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.script, s.mitm, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.script, s.mitm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -440,7 +455,7 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -448,7 +463,7 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -467,7 +482,7 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.services {
|
for _, lifecycleService := range s.services {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
|
|||||||
Submodule clients/android updated: 9dd336679d...aefe3c0290
@@ -59,7 +59,7 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_script")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
|
|||||||
@@ -5,49 +5,40 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
"github.com/sagernet/sing-box/common/badversion"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var nightly bool
|
||||||
flagRunInCI bool
|
|
||||||
flagRunNightly bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
||||||
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
var (
|
if nightly {
|
||||||
versionStr string
|
version, err := build_shared.ReadTagVersionRev()
|
||||||
err error
|
|
||||||
)
|
|
||||||
if flagRunNightly {
|
|
||||||
var version badversion.Version
|
|
||||||
version, err = build_shared.ReadTagVersion()
|
|
||||||
if err == nil {
|
|
||||||
versionStr = version.String()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
versionStr, err = build_shared.ReadTag()
|
|
||||||
}
|
|
||||||
if flagRunInCI {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
var versionStr string
|
||||||
|
if version.PreReleaseIdentifier != "" {
|
||||||
|
versionStr = version.VersionString() + "-nightly"
|
||||||
|
} else {
|
||||||
|
version.Patch++
|
||||||
|
versionStr = version.VersionString() + "-nightly"
|
||||||
|
}
|
||||||
err = setGitHubEnv("version", versionStr)
|
err = setGitHubEnv("version", versionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
os.Stdout.WriteString("unknown\n")
|
os.Stdout.WriteString("unknown\n")
|
||||||
} else {
|
} else {
|
||||||
os.Stdout.WriteString(versionStr + "\n")
|
os.Stdout.WriteString(tag + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
121
cmd/sing-box/cmd_generate_ca.go
Normal file
121
cmd/sing-box/cmd_generate_ca.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagGenerateCAName string
|
||||||
|
flagGenerateCAPKCS12Password string
|
||||||
|
flagGenerateOutput string
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGenerateCAKeyPair = &cobra.Command{
|
||||||
|
Use: "ca-keypair",
|
||||||
|
Short: "Generate CA key pair",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateCAKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateCAName, "name", "n", "", "Set custom CA name")
|
||||||
|
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateCAPKCS12Password, "p12-password", "p", "", "Set custom PKCS12 password")
|
||||||
|
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateOutput, "output", "o", ".", "Set output directory")
|
||||||
|
commandGenerate.AddCommand(commandGenerateCAKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCAKeyPair() error {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
spkiASN1, err := x509.MarshalPKIXPublicKey(privateKey.Public())
|
||||||
|
var spki struct {
|
||||||
|
Algorithm pkix.AlgorithmIdentifier
|
||||||
|
SubjectPublicKey asn1.BitString
|
||||||
|
}
|
||||||
|
_, err = asn1.Unmarshal(spkiASN1, &spki)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
skid := sha1.Sum(spki.SubjectPublicKey.Bytes)
|
||||||
|
var caName string
|
||||||
|
if flagGenerateCAName != "" {
|
||||||
|
caName = flagGenerateCAName
|
||||||
|
} else {
|
||||||
|
caName = "sing-box Generated CA " + strings.ToUpper(hex.EncodeToString(skid[:4]))
|
||||||
|
}
|
||||||
|
caTpl := &x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{caName},
|
||||||
|
CommonName: caName,
|
||||||
|
},
|
||||||
|
SubjectKeyId: skid[:],
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
KeyUsage: x509.KeyUsageCertSign,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
MaxPathLenZero: true,
|
||||||
|
}
|
||||||
|
publicDer, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, privateKey.Public(), privateKey)
|
||||||
|
var caPassword string
|
||||||
|
if flagGenerateCAPKCS12Password != "" {
|
||||||
|
caPassword = flagGenerateCAPKCS12Password
|
||||||
|
} else {
|
||||||
|
caPassword = strings.ToUpper(hex.EncodeToString(skid[:4]))
|
||||||
|
}
|
||||||
|
caTpl.Raw = publicDer
|
||||||
|
p12Bytes, err := pkcs12.Modern.Encode(privateKey, caTpl, nil, caPassword)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
privateDer, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".pem"), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
|
||||||
|
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".private.pem"), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer}), 0o644)
|
||||||
|
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".crt"), publicDer, 0o644)
|
||||||
|
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".p12"), p12Bytes, 0o644)
|
||||||
|
var tlsDecryptionOptions option.TLSDecryptionOptions
|
||||||
|
tlsDecryptionOptions.Enabled = true
|
||||||
|
tlsDecryptionOptions.KeyPair = base64.StdEncoding.EncodeToString(p12Bytes)
|
||||||
|
tlsDecryptionOptions.KeyPairPassword = caPassword
|
||||||
|
var certificateOptions option.CertificateOptions
|
||||||
|
certificateOptions.TLSDecryption = &tlsDecryptionOptions
|
||||||
|
encoder := json.NewEncoder(os.Stdout)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
return encoder.Encode(certificateOptions)
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
@@ -57,14 +56,6 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read rule-set")
|
return E.Cause(err, "read rule-set")
|
||||||
}
|
}
|
||||||
if flagRuleSetMatchFormat == "" {
|
|
||||||
switch filepath.Ext(sourcePath) {
|
|
||||||
case ".json":
|
|
||||||
flagRuleSetMatchFormat = C.RuleSetFormatSource
|
|
||||||
case ".srs":
|
|
||||||
flagRuleSetMatchFormat = C.RuleSetFormatBinary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ruleSet option.PlainRuleSetCompat
|
var ruleSet option.PlainRuleSetCompat
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
case C.RuleSetFormatSource:
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,36 +12,5 @@ var commandTools = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
|
|
||||||
mainCommand.AddCommand(commandTools)
|
mainCommand.AddCommand(commandTools)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPreStartedClient() (*box.Box, error) {
|
|
||||||
options, err := readConfigAndMerge()
|
|
||||||
if err != nil {
|
|
||||||
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create service")
|
|
||||||
}
|
|
||||||
err = instance.PreStart()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "start service")
|
|
||||||
}
|
|
||||||
return instance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
|
|
||||||
if outboundTag == "" {
|
|
||||||
return instance.Outbound().Default(), nil
|
|
||||||
} else {
|
|
||||||
outbound, loaded := instance.Outbound().Outbound(outboundTag)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound not found: ", outboundTag)
|
|
||||||
}
|
|
||||||
return outbound, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"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"
|
|
||||||
"github.com/sagernet/sing/common/task"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandConnectFlagNetwork string
|
|
||||||
|
|
||||||
var commandConnect = &cobra.Command{
|
|
||||||
Use: "connect <address>",
|
|
||||||
Short: "Connect to an address",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := connect(args[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
|
|
||||||
commandTools.AddCommand(commandConnect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect(address string) error {
|
|
||||||
switch N.NetworkName(commandConnectFlagNetwork) {
|
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
|
||||||
default:
|
|
||||||
return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)
|
|
||||||
}
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "connect to server")
|
|
||||||
}
|
|
||||||
var group task.Group
|
|
||||||
group.Append("upload", func(ctx context.Context) error {
|
|
||||||
return common.Error(bufio.Copy(conn, os.Stdin))
|
|
||||||
})
|
|
||||||
group.Append("download", func(ctx context.Context) error {
|
|
||||||
return common.Error(bufio.Copy(os.Stdout, conn))
|
|
||||||
})
|
|
||||||
group.Cleanup(func() {
|
|
||||||
conn.Close()
|
|
||||||
})
|
|
||||||
err = group.Run(context.Background())
|
|
||||||
if E.IsClosed(err) {
|
|
||||||
log.Info(err)
|
|
||||||
} else {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandFetch = &cobra.Command{
|
|
||||||
Use: "fetch",
|
|
||||||
Short: "Fetch an URL",
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := fetch(args)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandTools.AddCommand(commandFetch)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
httpClient *http.Client
|
|
||||||
http3Client *http.Client
|
|
||||||
)
|
|
||||||
|
|
||||||
func fetch(args []string) error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
if C.WithQUIC {
|
|
||||||
err = initializeHTTP3Client(instance)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer http3Client.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
for _, urlString := range args {
|
|
||||||
var parsedURL *url.URL
|
|
||||||
parsedURL, err = url.Parse(urlString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch parsedURL.Scheme {
|
|
||||||
case "":
|
|
||||||
parsedURL.Scheme = "http"
|
|
||||||
fallthrough
|
|
||||||
case "http", "https":
|
|
||||||
err = fetchHTTP(httpClient, parsedURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "http3":
|
|
||||||
if !C.WithQUIC {
|
|
||||||
return C.ErrQUICNotIncluded
|
|
||||||
}
|
|
||||||
parsedURL.Scheme = "https"
|
|
||||||
err = fetchHTTP(http3Client, parsedURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return E.New("unsupported scheme: ", parsedURL.Scheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
|
|
||||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
request.Header.Add("User-Agent", "curl/7.88.0")
|
|
||||||
response, err := httpClient.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = bufio.Copy(os.Stdout, response.Body)
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
//go:build with_quic
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
|
||||||
"github.com/sagernet/quic-go/http3"
|
|
||||||
box "github.com/sagernet/sing-box"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initializeHTTP3Client(instance *box.Box) error {
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
http3Client = &http.Client{
|
|
||||||
Transport: &http3.Transport{
|
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
|
||||||
destination := M.ParseSocksaddr(addr)
|
|
||||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
|
||||||
if dErr != nil {
|
|
||||||
return nil, dErr
|
|
||||||
}
|
|
||||||
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//go:build !with_quic
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
box "github.com/sagernet/sing-box"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initializeHTTP3Client(instance *box.Box) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchHTTP3(parsedURL *url.URL) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
108
cmd/sing-box/cmd_tools_install_ca.go
Normal file
108
cmd/sing-box/cmd_tools_install_ca.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/shell"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandInstallCACertificate = &cobra.Command{
|
||||||
|
Use: "install-ca <path to certificate>",
|
||||||
|
Short: "Install CA certificate to system",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := installCACertificate(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandTools.AddCommand(commandInstallCACertificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installCACertificate(path string) error {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
return shell.Exec("powershell", "-Command", "Import-Certificate -FilePath \""+path+"\" -CertStoreLocation Cert:\\LocalMachine\\Root").Attach().Run()
|
||||||
|
case "darwin":
|
||||||
|
return shell.Exec("sudo", "security", "add-trusted-cert", "-d", "-r", "trustRoot", "-k", "/Library/Keychains/System.keychain", path).Attach().Run()
|
||||||
|
case "linux":
|
||||||
|
updateCertPath, updateCertPathNotFoundErr := exec.LookPath("update-ca-certificates")
|
||||||
|
if updateCertPathNotFoundErr == nil {
|
||||||
|
publicDer, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll("/usr/local/share/ca-certificates", 0o755)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
log.Info("Try running with sudo")
|
||||||
|
return shell.Exec("sudo", os.Args...).Attach().Run()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileName := filepath.Base(updateCertPath)
|
||||||
|
if !strings.HasSuffix(fileName, ".crt") {
|
||||||
|
fileName = fileName + ".crt"
|
||||||
|
}
|
||||||
|
filePath, _ := filepath.Abs(filepath.Join("/usr/local/share/ca-certificates", fileName))
|
||||||
|
err = os.WriteFile(filePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
log.Info("Try running with sudo")
|
||||||
|
return shell.Exec("sudo", os.Args...).Attach().Run()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("certificate written to " + filePath + "\n")
|
||||||
|
err = shell.Exec(updateCertPath).Attach().Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("certificate installed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
updateTrustPath, updateTrustPathNotFoundErr := exec.LookPath("update-ca-trust")
|
||||||
|
if updateTrustPathNotFoundErr == nil {
|
||||||
|
publicDer, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileName := filepath.Base(updateTrustPath)
|
||||||
|
fileExt := filepath.Ext(path)
|
||||||
|
if fileExt != "" {
|
||||||
|
fileName = fileName[:len(fileName)-len(fileExt)]
|
||||||
|
}
|
||||||
|
filePath, _ := filepath.Abs(filepath.Join("/etc/pki/ca-trust/source/anchors/", fileName+".pem"))
|
||||||
|
err = os.WriteFile(filePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
log.Info("Try running with sudo")
|
||||||
|
return shell.Exec("sudo", os.Args...).Attach().Run()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("certificate written to " + filePath + "\n")
|
||||||
|
err = shell.Exec(updateTrustPath, "extract").Attach().Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("certificate installed")
|
||||||
|
}
|
||||||
|
return E.New("update-ca-certificates or update-ca-trust not found")
|
||||||
|
default:
|
||||||
|
return E.New("unsupported operating system: ", runtime.GOOS)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -39,20 +40,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func syncTime() error {
|
func syncTime() error {
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
||||||
if serverAddress.Port == 0 {
|
if serverAddress.Port == 0 {
|
||||||
serverAddress.Port = 123
|
serverAddress.Port = 123
|
||||||
}
|
}
|
||||||
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
|
response, err := ntp.Exchange(context.Background(), N.SystemDialer, serverAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import (
|
|||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/utls"
|
||||||
"github.com/metacubex/utls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -25,8 +24,8 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
|
||||||
func utlsReadRecord(c *tls.Conn) error
|
func utlsReadRecord(c *tls.Conn) error
|
||||||
|
|
||||||
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
|
||||||
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package certificate
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -16,6 +17,8 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.CertificateStore = (*Store)(nil)
|
var _ adapter.CertificateStore = (*Store)(nil)
|
||||||
@@ -27,6 +30,9 @@ type Store struct {
|
|||||||
certificatePaths []string
|
certificatePaths []string
|
||||||
certificateDirectoryPaths []string
|
certificateDirectoryPaths []string
|
||||||
watcher *fswatch.Watcher
|
watcher *fswatch.Watcher
|
||||||
|
tlsDecryptionEnabled bool
|
||||||
|
tlsDecryptionPrivateKey any
|
||||||
|
tlsDecryptionCertificate *x509.Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
|
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
|
||||||
@@ -90,6 +96,19 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initializing certificate store")
|
return nil, E.Cause(err, "initializing certificate store")
|
||||||
}
|
}
|
||||||
|
if options.TLSDecryption != nil && options.TLSDecryption.Enabled {
|
||||||
|
pfxBytes, err := base64.StdEncoding.DecodeString(options.TLSDecryption.KeyPair)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode key pair base64 bytes")
|
||||||
|
}
|
||||||
|
privateKey, certificate, err := pkcs12.Decode(pfxBytes, options.TLSDecryption.KeyPairPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode key pair")
|
||||||
|
}
|
||||||
|
store.tlsDecryptionEnabled = true
|
||||||
|
store.tlsDecryptionPrivateKey = privateKey
|
||||||
|
store.tlsDecryptionCertificate = certificate
|
||||||
|
}
|
||||||
return store, nil
|
return store, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,3 +202,15 @@ func isSameDirSymlink(f fs.DirEntry, dir string) bool {
|
|||||||
target, err := os.Readlink(filepath.Join(dir, f.Name()))
|
target, err := os.Readlink(filepath.Join(dir, f.Name()))
|
||||||
return err == nil && !strings.Contains(target, "/")
|
return err == nil && !strings.Contains(target, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) TLSDecryptionEnabled() bool {
|
||||||
|
return s.tlsDecryptionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) TLSDecryptionCertificate() *x509.Certificate {
|
||||||
|
return s.tlsDecryptionCertificate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) TLSDecryptionPrivateKey() any {
|
||||||
|
return s.tlsDecryptionPrivateKey
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,19 +66,23 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||||
}
|
}
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
|
||||||
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
|
||||||
}
|
|
||||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark > 0 {
|
if options.RoutingMark > 0 {
|
||||||
if !C.IsLinux {
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||||
return nil, E.New("`routing_mark` is only supported on Linux")
|
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))
|
||||||
}
|
}
|
||||||
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
|
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
||||||
if disableDefaultBind || options.TCPFastOpen {
|
if disableDefaultBind || options.TCPFastOpen {
|
||||||
@@ -123,8 +127,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
@@ -206,22 +210,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
}, nil
|
}, 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) {
|
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
@@ -347,17 +335,7 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||||
udpListener := d.udpListener
|
return d.udpListener.ListenPacket(context.Background(), network, address)
|
||||||
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
|
|
||||||
for _, wgControlFn := range WgControlFns {
|
|
||||||
err := wgControlFn(network, address, conn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return udpListener.ListenPacket(context.Background(), network, address)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ type Options struct {
|
|||||||
ResolverOnDetour bool
|
ResolverOnDetour bool
|
||||||
NewDialer bool
|
NewDialer bool
|
||||||
LegacyDNSDialer bool
|
LegacyDNSDialer bool
|
||||||
DirectOutbound bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge with NewWithOptions
|
// TODO: merge with NewWithOptions
|
||||||
@@ -83,7 +82,6 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||||
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
|
||||||
}
|
}
|
||||||
server = dialOptions.DomainResolver.Server
|
server = dialOptions.DomainResolver.Server
|
||||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||||
@@ -96,31 +94,22 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
} else if options.DirectResolver {
|
} else if options.DirectResolver {
|
||||||
return nil, E.New("missing domain resolver for domain server address")
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
} else {
|
} else if defaultOptions.DomainResolver != "" {
|
||||||
if defaultOptions.DomainResolver != "" {
|
dnsQueryOptions = defaultOptions.DomainResolveOptions
|
||||||
dnsQueryOptions = defaultOptions.DomainResolveOptions
|
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
|
||||||
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
|
if !loaded {
|
||||||
if !loaded {
|
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
|
||||||
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
|
dnsQueryOptions.Transport = transport
|
||||||
//nolint:staticcheck
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
} else if options.NewDialer {
|
||||||
//nolint:staticcheck
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
} else {
|
||||||
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
transports := dnsTransport.Transports()
|
||||||
|
if len(transports) < 2 {
|
||||||
|
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||||
|
} else {
|
||||||
|
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialer = NewResolveDialer(
|
dialer = NewResolveDialer(
|
||||||
|
|||||||
158
common/humanize/bytes.go
Normal file
158
common/humanize/bytes.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -8,11 +8,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
"github.com/metacubex/tfo-go"
|
"github.com/metacubex/tfo-go"
|
||||||
)
|
)
|
||||||
@@ -25,15 +23,6 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
var err error
|
var err error
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
var listenConfig net.ListenConfig
|
var listenConfig net.ListenConfig
|
||||||
if l.listenOptions.BindInterface != "" {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
|
||||||
}
|
|
||||||
if l.listenOptions.RoutingMark != 0 {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
|
||||||
}
|
|
||||||
if l.listenOptions.ReuseAddr {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
|
||||||
}
|
|
||||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
|
|||||||
@@ -6,27 +6,16 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
var listenConfig net.ListenConfig
|
var lc net.ListenConfig
|
||||||
if l.listenOptions.BindInterface != "" {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
|
||||||
}
|
|
||||||
if l.listenOptions.RoutingMark != 0 {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
|
||||||
}
|
|
||||||
if l.listenOptions.ReuseAddr {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
|
||||||
}
|
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if l.listenOptions.UDPFragment != nil {
|
if l.listenOptions.UDPFragment != nil {
|
||||||
udpFragment = *l.listenOptions.UDPFragment
|
udpFragment = *l.listenOptions.UDPFragment
|
||||||
@@ -34,10 +23,10 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
udpFragment = l.listenOptions.UDPFragmentDefault
|
udpFragment = l.listenOptions.UDPFragmentDefault
|
||||||
}
|
}
|
||||||
if !udpFragment {
|
if !udpFragment {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())
|
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
||||||
}
|
}
|
||||||
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||||
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -48,32 +37,8 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
return udpConn, err
|
return udpConn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
|
|
||||||
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
|
|
||||||
if l.listenOptions.BindInterface != "" {
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
|
||||||
}
|
|
||||||
if l.listenOptions.RoutingMark != 0 {
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
|
||||||
}
|
|
||||||
if l.listenOptions.ReuseAddr {
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.ReuseAddr())
|
|
||||||
}
|
|
||||||
return dialer.DialContext(ctx, network, address)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
|
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
|
||||||
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||||
if l.listenOptions.BindInterface != "" {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
|
||||||
}
|
|
||||||
if l.listenOptions.RoutingMark != 0 {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
|
||||||
}
|
|
||||||
if l.listenOptions.ReuseAddr {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
|
||||||
}
|
|
||||||
return listenConfig.ListenPacket(ctx, network, address)
|
return listenConfig.ListenPacket(ctx, network, address)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -24,25 +23,20 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
|||||||
var first byte
|
var first byte
|
||||||
err := binary.Read(reader, binary.BigEndian, &first)
|
err := binary.Read(reader, binary.BigEndian, &first)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if first != 19 {
|
if first != 19 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = "BitTorrent protocol"
|
|
||||||
var protocol [19]byte
|
var protocol [19]byte
|
||||||
var n int
|
_, err = reader.Read(protocol[:])
|
||||||
n, err = reader.Read(protocol[:])
|
|
||||||
if string(protocol[:n]) != header[:n] {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
if n < 19 {
|
if string(protocol[:]) != "BitTorrent protocol" {
|
||||||
return ErrNeedMoreData
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.Protocol = C.ProtocolBitTorrent
|
metadata.Protocol = C.ProtocolBitTorrent
|
||||||
@@ -73,9 +67,7 @@ func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if extension > 0x04 {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
var length byte
|
var length byte
|
||||||
err = binary.Read(reader, binary.BigEndian, &length)
|
err = binary.Read(reader, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -32,27 +32,6 @@ func TestSniffBittorrent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSniffIncompleteBittorrent(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
pkt, err := hex.DecodeString("13426974546f7272656e74")
|
|
||||||
require.NoError(t, err)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffNotBittorrent(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
pkt, err := hex.DecodeString("13426974546f7272656e75")
|
|
||||||
require.NoError(t, err)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
|
||||||
require.NotEmpty(t, err)
|
|
||||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffUTP(t *testing.T) {
|
func TestSniffUTP(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -92,19 +71,3 @@ func TestSniffUDPTracker(t *testing.T) {
|
|||||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSniffNotUTP(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
packets := []string{
|
|
||||||
"0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
}
|
|
||||||
for _, pkt := range packets {
|
|
||||||
pkt, err := hex.DecodeString(pkt)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.UTP(context.TODO(), &metadata, pkt)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -18,40 +21,35 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
|||||||
var length uint16
|
var length uint16
|
||||||
err := binary.Read(reader, binary.BigEndian, &length)
|
err := binary.Read(reader, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
if length < 12 {
|
if length == 0 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
buffer := buf.NewSize(int(length))
|
buffer := buf.NewSize(int(length))
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
var n int
|
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
|
||||||
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
var readTask task.Group
|
||||||
packet := buffer.Bytes()
|
readTask.Append0(func(ctx context.Context) error {
|
||||||
if n > 2 && packet[2]&0x80 != 0 { // QR
|
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen()))
|
||||||
return os.ErrInvalid
|
})
|
||||||
}
|
err = readTask.Run(readCtx)
|
||||||
if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
|
cancel()
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
for i := 6; i < 10; i++ {
|
|
||||||
// ANCOUNT, NSCOUNT
|
|
||||||
if n > i && packet[i] != 0 {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
return DomainNameQuery(readCtx, metadata, packet)
|
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||||
var msg mDNS.Msg
|
var msg mDNS.Msg
|
||||||
err := msg.Unpack(packet)
|
err := msg.Unpack(packet)
|
||||||
if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
metadata.Protocol = C.ProtocolDNS
|
metadata.Protocol = C.ProtocolDNS
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package sniff_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/sniff"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSniffDNS(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
query, err := hex.DecodeString("740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
|
||||||
require.NoError(t, err)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.DomainNameQuery(context.TODO(), &metadata, query)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -3,12 +3,10 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
std_bufio "bufio"
|
std_bufio "bufio"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/protocol/http"
|
"github.com/sagernet/sing/protocol/http"
|
||||||
)
|
)
|
||||||
@@ -16,13 +14,10 @@ import (
|
|||||||
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
request, err := http.ReadRequest(std_bufio.NewReader(reader))
|
request, err := http.ReadRequest(std_bufio.NewReader(reader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
return err
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolHTTP
|
metadata.Protocol = C.ProtocolHTTP
|
||||||
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
||||||
|
metadata.HTTPRequest = request
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import (
|
|||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection")
|
||||||
|
|
||||||
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||||
reader := bytes.NewReader(packet)
|
reader := bytes.NewReader(packet)
|
||||||
typeByte, err := reader.ReadByte()
|
typeByte, err := reader.ReadByte()
|
||||||
@@ -306,7 +308,7 @@ find:
|
|||||||
metadata.Protocol = C.ProtocolQUIC
|
metadata.Protocol = C.ProtocolQUIC
|
||||||
metadata.Client = C.ClientChromium
|
metadata.Client = C.ClientChromium
|
||||||
metadata.SniffContext = fragments
|
metadata.SniffContext = fragments
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return ErrClientHelloFragmented
|
||||||
}
|
}
|
||||||
metadata.Domain = fingerprint.ServerName
|
metadata.Domain = fingerprint.ServerName
|
||||||
for metadata.Client == "" {
|
for metadata.Client == "" {
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ func TestSniffQUICChromeNew(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
||||||
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
||||||
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
|
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
@@ -40,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
||||||
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktVersion uint8
|
var tpktVersion uint8
|
||||||
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
|
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
if tpktVersion != 0x03 {
|
if tpktVersion != 0x03 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -25,7 +24,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktReserved uint8
|
var tpktReserved uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
|
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
if tpktReserved != 0x00 {
|
if tpktReserved != 0x00 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -34,7 +33,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktLength uint16
|
var tpktLength uint16
|
||||||
err = binary.Read(reader, binary.BigEndian, &tpktLength)
|
err = binary.Read(reader, binary.BigEndian, &tpktLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tpktLength != 19 {
|
if tpktLength != 19 {
|
||||||
@@ -44,7 +43,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var cotpLength uint8
|
var cotpLength uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &cotpLength)
|
err = binary.Read(reader, binary.BigEndian, &cotpLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cotpLength != 14 {
|
if cotpLength != 14 {
|
||||||
@@ -54,7 +53,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var cotpTpduType uint8
|
var cotpTpduType uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
|
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
if cotpTpduType != 0xE0 {
|
if cotpTpduType != 0xE0 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -62,13 +61,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
|
|
||||||
err = rw.SkipN(reader, 5)
|
err = rw.SkipN(reader, 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var rdpType uint8
|
var rdpType uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpType)
|
err = binary.Read(reader, binary.BigEndian, &rdpType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
if rdpType != 0x01 {
|
if rdpType != 0x01 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -76,12 +75,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var rdpFlags uint8
|
var rdpFlags uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
|
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
var rdpLength uint8
|
var rdpLength uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpLength)
|
err = binary.Read(reader, binary.BigEndian, &rdpLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return err
|
||||||
}
|
}
|
||||||
if rdpLength != 8 {
|
if rdpLength != 8 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,8 +19,6 @@ type (
|
|||||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNeedMoreData = E.New("need more data")
|
|
||||||
|
|
||||||
func Skip(metadata *adapter.InboundContext) bool {
|
func Skip(metadata *adapter.InboundContext) bool {
|
||||||
// skip server first protocols
|
// skip server first protocols
|
||||||
switch metadata.Destination.Port {
|
switch metadata.Destination.Port {
|
||||||
@@ -43,7 +40,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
timeout = C.ReadPayloadTimeout
|
timeout = C.ReadPayloadTimeout
|
||||||
}
|
}
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
var sniffError error
|
var errors []error
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
err := conn.SetReadDeadline(deadline)
|
err := conn.SetReadDeadline(deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -57,7 +54,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
}
|
}
|
||||||
return E.Cause(err, "read payload")
|
return E.Cause(err, "read payload")
|
||||||
}
|
}
|
||||||
sniffError = nil
|
errors = nil
|
||||||
for _, sniffer := range sniffers {
|
for _, sniffer := range sniffers {
|
||||||
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
|
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
|
||||||
return bytes.NewReader(it.Bytes())
|
return bytes.NewReader(it.Bytes())
|
||||||
@@ -66,23 +63,20 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sniffError = E.Errors(sniffError, err)
|
errors = append(errors, err)
|
||||||
}
|
|
||||||
if !errors.Is(sniffError, ErrNeedMoreData) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sniffError
|
return E.Errors(errors...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
|
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
|
||||||
var sniffError []error
|
var errors []error
|
||||||
for _, sniffer := range sniffers {
|
for _, sniffer := range sniffers {
|
||||||
err := sniffer(ctx, metadata, packet)
|
err := sniffer(ctx, metadata, packet)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sniffError = append(sniffError, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
return E.Errors(sniffError...)
|
return E.Errors(errors...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,27 +5,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
const sshPrefix = "SSH-2.0-"
|
scanner := bufio.NewScanner(reader)
|
||||||
bReader := bufio.NewReader(reader)
|
if !scanner.Scan() {
|
||||||
prefix, err := bReader.Peek(len(sshPrefix))
|
|
||||||
if string(prefix[:]) != sshPrefix[:len(prefix)] {
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
if err != nil {
|
fistLine := scanner.Text()
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
if !strings.HasPrefix(fistLine, "SSH-2.0-") {
|
||||||
}
|
return os.ErrInvalid
|
||||||
fistLine, _, err := bReader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolSSH
|
metadata.Protocol = C.ProtocolSSH
|
||||||
metadata.Client = string(fistLine)[8:]
|
metadata.Client = fistLine[8:]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,24 +24,3 @@ func TestSniffSSH(t *testing.T) {
|
|||||||
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
||||||
require.Equal(t, "dropbear", metadata.Client)
|
require.Equal(t, "dropbear", metadata.Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSniffIncompleteSSH(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
pkt, err := hex.DecodeString("5353482d322e30")
|
|
||||||
require.NoError(t, err)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffNotSSH(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
pkt, err := hex.DecodeString("5353482d322e31")
|
|
||||||
require.NoError(t, err)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
|
||||||
require.NotEmpty(t, err)
|
|
||||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
@@ -23,11 +21,8 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
|
|||||||
if clientHello != nil {
|
if clientHello != nil {
|
||||||
metadata.Protocol = C.ProtocolTLS
|
metadata.Protocol = C.ProtocolTLS
|
||||||
metadata.Domain = clientHello.ServerName
|
metadata.Domain = clientHello.ServerName
|
||||||
|
metadata.ClientHello = clientHello
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
return err
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type (
|
|||||||
STDConfig = tls.Config
|
STDConfig = tls.Config
|
||||||
STDConn = tls.Conn
|
STDConn = tls.Conn
|
||||||
ConnectionState = tls.ConnectionState
|
ConnectionState = tls.ConnectionState
|
||||||
CurveID = tls.CurveID
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseTLSVersion(version string) (uint16, error) {
|
func ParseTLSVersion(version string) (uint16, error) {
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
@@ -48,10 +46,7 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
|
|||||||
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return &STDClientConfig{tlsConfig}, nil
|
return &STDClientConfig{tlsConfig}, nil
|
||||||
} else {
|
} else {
|
||||||
return &STDECHClientConfig{
|
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil
|
||||||
STDClientConfig: STDClientConfig{tlsConfig},
|
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,28 +99,11 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
|||||||
|
|
||||||
type STDECHClientConfig struct {
|
type STDECHClientConfig struct {
|
||||||
STDClientConfig
|
STDClientConfig
|
||||||
access sync.Mutex
|
dnsRouter adapter.DNSRouter
|
||||||
dnsRouter adapter.DNSRouter
|
|
||||||
lastTTL time.Duration
|
|
||||||
lastUpdate time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
|
||||||
s.access.Lock()
|
|
||||||
defer s.access.Unlock()
|
|
||||||
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
@@ -145,7 +123,6 @@ func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Con
|
|||||||
if response.Rcode != mDNS.RcodeSuccess {
|
if response.Rcode != mDNS.RcodeSuccess {
|
||||||
return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
|
return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
|
||||||
}
|
}
|
||||||
match:
|
|
||||||
for _, rr := range response.Answer {
|
for _, rr := range response.Answer {
|
||||||
switch resource := rr.(type) {
|
switch resource := rr.(type) {
|
||||||
case *mDNS.HTTPS:
|
case *mDNS.HTTPS:
|
||||||
@@ -155,23 +132,26 @@ func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Con
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode ECH config")
|
return nil, E.Cause(err, "decode ECH config")
|
||||||
}
|
}
|
||||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
|
||||||
s.lastUpdate = time.Now()
|
|
||||||
s.config.EncryptedClientHelloConfigList = echConfigList
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
break match
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
return nil, E.New("no ECH config found in DNS records")
|
||||||
return nil, E.New("no ECH config found in DNS records")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return s.Client(conn)
|
tlsConn, err := s.Client(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDECHClientConfig) Clone() Config {
|
func (s *STDECHClientConfig) Clone() Config {
|
||||||
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
//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."
|
|
||||||
@@ -8,7 +8,10 @@ import (
|
|||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||||
@@ -35,17 +38,30 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
template := &x509.Certificate{
|
var template *x509.Certificate
|
||||||
SerialNumber: serialNumber,
|
if serverAddress := M.ParseAddr(serverName); serverAddress.IsValid() {
|
||||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
template = &x509.Certificate{
|
||||||
NotAfter: expire,
|
SerialNumber: serialNumber,
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
IPAddresses: []net.IP{serverAddress.AsSlice()},
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||||
BasicConstraintsValid: true,
|
NotAfter: expire,
|
||||||
Subject: pkix.Name{
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
CommonName: serverName,
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
},
|
BasicConstraintsValid: true,
|
||||||
DNSNames: []string{serverName},
|
}
|
||||||
|
} else {
|
||||||
|
template = &x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||||
|
NotAfter: expire,
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: serverName,
|
||||||
|
},
|
||||||
|
DNSNames: []string{serverName},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
parent = template
|
parent = template
|
||||||
|
|||||||
@@ -29,13 +29,12 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
utls "github.com/metacubex/utls"
|
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@@ -115,22 +114,6 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, extension := range uConn.Extensions {
|
|
||||||
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
|
|
||||||
ce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool {
|
|
||||||
return curveID != utls.X25519MLKEM768
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if ks, ok := extension.(*utls.KeyShareExtension); ok {
|
|
||||||
ks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool {
|
|
||||||
return share.Group != utls.X25519MLKEM768
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = uConn.BuildHandshakeState()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(uConfig.NextProtos) > 0 {
|
if len(uConfig.NextProtos) > 0 {
|
||||||
for _, extension := range uConn.Extensions {
|
for _, extension := range uConn.Extensions {
|
||||||
@@ -165,13 +148,9 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys
|
ecdheKey := uConn.HandshakeState.State13.EcdheKey
|
||||||
if keyShareKeys == nil {
|
|
||||||
return nil, E.New("nil KeyShareKeys")
|
|
||||||
}
|
|
||||||
ecdheKey := keyShareKeys.Ecdhe
|
|
||||||
if ecdheKey == nil {
|
if ecdheKey == nil {
|
||||||
return nil, E.New("nil ecdheKey")
|
return nil, E.New("nil ecdhe_key")
|
||||||
}
|
}
|
||||||
authKey, err := ecdheKey.ECDH(publicKey)
|
authKey, err := ecdheKey.ECDH(publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -235,6 +214,10 @@ func realityClientFallback(ctx context.Context, uConn net.Conn, serverName strin
|
|||||||
response.Body.Close()
|
response.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
|
e.uClient.config.SessionIDGenerator = generator
|
||||||
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
func (e *RealityClientConfig) Clone() Config {
|
||||||
return &RealityClientConfig{
|
return &RealityClientConfig{
|
||||||
e.ctx,
|
e.ctx,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build with_utls
|
//go:build with_reality_server
|
||||||
|
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
@@ -7,29 +7,28 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/reality"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
utls "github.com/metacubex/utls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||||
|
|
||||||
type RealityServerConfig struct {
|
type RealityServerConfig struct {
|
||||||
config *utls.RealityConfig
|
config *reality.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
||||||
var tlsConfig utls.RealityConfig
|
var tlsConfig reality.Config
|
||||||
|
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
return nil, E.New("acme is unavailable in reality")
|
return nil, E.New("acme is unavailable in reality")
|
||||||
@@ -75,11 +74,6 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig.SessionTicketsDisabled = true
|
tlsConfig.SessionTicketsDisabled = true
|
||||||
tlsConfig.Log = func(format string, v ...any) {
|
|
||||||
if logger != nil {
|
|
||||||
logger.Trace(fmt.Sprintf(format, v...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tlsConfig.Type = N.NetworkTCP
|
tlsConfig.Type = N.NetworkTCP
|
||||||
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
||||||
|
|
||||||
@@ -95,20 +89,16 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
||||||
|
|
||||||
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
||||||
if len(options.Reality.ShortID) == 0 {
|
for i, shortIDString := range options.Reality.ShortID {
|
||||||
tlsConfig.ShortIds[[8]byte{0}] = true
|
var shortID [8]byte
|
||||||
} else {
|
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
||||||
for i, shortIDString := range options.Reality.ShortID {
|
if err != nil {
|
||||||
var shortID [8]byte
|
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
|
||||||
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
|
|
||||||
}
|
|
||||||
if decodedLen > 8 {
|
|
||||||
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
|
|
||||||
}
|
|
||||||
tlsConfig.ShortIds[shortID] = true
|
|
||||||
}
|
}
|
||||||
|
if decodedLen > 8 {
|
||||||
|
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
|
||||||
|
}
|
||||||
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
||||||
@@ -119,6 +109,10 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if debug.Enabled {
|
||||||
|
tlsConfig.Show = true
|
||||||
|
}
|
||||||
|
|
||||||
return &RealityServerConfig{&tlsConfig}, nil
|
return &RealityServerConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +153,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
||||||
tlsConn, err := utls.RealityServer(ctx, conn, c.config)
|
tlsConn, err := reality.Server(ctx, conn, c.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -175,7 +169,7 @@ func (c *RealityServerConfig) Clone() Config {
|
|||||||
var _ Conn = (*realityConnWrapper)(nil)
|
var _ Conn = (*realityConnWrapper)(nil)
|
||||||
|
|
||||||
type realityConnWrapper struct {
|
type realityConnWrapper struct {
|
||||||
*utls.Conn
|
*reality.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
//go:build with_reality_server
|
//go:build !with_reality_server
|
||||||
|
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
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."
|
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`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -234,12 +233,8 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
key = content
|
key = content
|
||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
timeFunc := ntp.TimeFuncFromContext(ctx)
|
|
||||||
if timeFunc == nil {
|
|
||||||
timeFunc = time.Now
|
|
||||||
}
|
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(nil, nil, timeFunc, info.ServerName)
|
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
utls "github.com/metacubex/utls"
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@@ -15,9 +14,5 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
|
return nil, E.New(`uTLS, which is required by reality client 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,9 +1,7 @@
|
|||||||
package tf
|
package tf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -19,19 +17,17 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
splitRecord bool
|
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
tcpConn: tcpConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
splitRecord: splitRecord,
|
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||||
@@ -41,12 +37,10 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}()
|
}()
|
||||||
serverName := indexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if !c.splitRecord {
|
if c.tcpConn != nil {
|
||||||
if c.tcpConn != nil {
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
if err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splits := strings.Split(serverName.ServerName, ".")
|
splits := strings.Split(serverName.ServerName, ".")
|
||||||
@@ -67,25 +61,16 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
currentIndex++
|
currentIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var buffer bytes.Buffer
|
|
||||||
for i := 0; i <= len(splitIndexes); i++ {
|
for i := 0; i <= len(splitIndexes); i++ {
|
||||||
var payload []byte
|
var payload []byte
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
payload = b[:splitIndexes[i]]
|
payload = b[:splitIndexes[i]]
|
||||||
if c.splitRecord {
|
|
||||||
payload = payload[recordLayerHeaderLen:]
|
|
||||||
}
|
|
||||||
} else if i == len(splitIndexes) {
|
} else if i == len(splitIndexes) {
|
||||||
payload = b[splitIndexes[i-1]:]
|
payload = b[splitIndexes[i-1]:]
|
||||||
} else {
|
} else {
|
||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||||
}
|
}
|
||||||
if c.splitRecord {
|
if c.tcpConn != nil && i != len(splitIndexes) {
|
||||||
payloadLen := uint16(len(payload))
|
|
||||||
buffer.Write(b[:3])
|
|
||||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
|
||||||
buffer.Write(payload)
|
|
||||||
} else if c.tcpConn != nil && i != len(splitIndexes) {
|
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -97,18 +82,11 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.splitRecord {
|
if c.tcpConn != nil {
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if c.tcpConn != nil {
|
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
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())
|
|
||||||
}
|
|
||||||
7
constant/script.go
Normal file
7
constant/script.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScriptTypeSurge = "surge"
|
||||||
|
ScriptSourceTypeLocal = "local"
|
||||||
|
ScriptSourceTypeRemote = "remote"
|
||||||
|
)
|
||||||
6
debug.go
6
debug.go
@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
if options.TraceBack != "" {
|
if options.TraceBack != "" {
|
||||||
debug.SetTraceback(options.TraceBack)
|
debug.SetTraceback(options.TraceBack)
|
||||||
}
|
}
|
||||||
if options.MemoryLimit.Value() != 0 {
|
if options.MemoryLimit != 0 {
|
||||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
||||||
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/byteformats"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
|||||||
runtime.ReadMemStats(&memStats)
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
var memObject badjson.JSONObject
|
var memObject badjson.JSONObject
|
||||||
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
|
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
||||||
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
|
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
||||||
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
memObject.Put("rss", rusageMaxRSS())
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
if options.ClientSubnet.IsValid() {
|
if options.ClientSubnet.IsValid() {
|
||||||
message = SetClientSubnet(message, options.ClientSubnet)
|
message = SetClientSubnet(message, options.ClientSubnet, true)
|
||||||
}
|
}
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
@@ -232,20 +232,10 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
record.Header().Ttl = timeToLive
|
record.Header().Ttl = timeToLive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
response.Id = messageId
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
c.storeCache(transport, question, response, timeToLive)
|
c.storeCache(transport, question, response, timeToLive)
|
||||||
}
|
}
|
||||||
response.Id = messageId
|
|
||||||
requestEDNSOpt := message.IsEdns0()
|
|
||||||
responseEDNSOpt := response.IsEdns0()
|
|
||||||
if responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) {
|
|
||||||
response.Extra = common.Filter(response.Extra, func(it dns.RR) bool {
|
|
||||||
return it.Header().Rrtype != dns.TypeOPT
|
|
||||||
})
|
|
||||||
if requestEDNSOpt != nil {
|
|
||||||
response.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
@@ -493,7 +483,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
||||||
return nil, RcodeError(response.Rcode)
|
return nil, RcodeError(response.Rcode)
|
||||||
}
|
}
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf
|
|||||||
}
|
}
|
||||||
responseLen := response.Len()
|
responseLen := response.Len()
|
||||||
if responseLen > maxLen {
|
if responseLen > maxLen {
|
||||||
copyResponse := *response
|
|
||||||
response = ©Response
|
|
||||||
response.Truncate(maxLen)
|
response.Truncate(maxLen)
|
||||||
}
|
}
|
||||||
buffer := buf.NewSize(headroom*2 + 1 + responseLen)
|
buffer := buf.NewSize(headroom*2 + 1 + responseLen)
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg {
|
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg {
|
||||||
return setClientSubnet(message, clientSubnet, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {
|
|
||||||
var (
|
var (
|
||||||
optRecord *dns.OPT
|
optRecord *dns.OPT
|
||||||
subnetOption *dns.EDNS0_SUBNET
|
subnetOption *dns.EDNS0_SUBNET
|
||||||
@@ -23,6 +19,9 @@ findExists:
|
|||||||
var isEDNS0Subnet bool
|
var isEDNS0Subnet bool
|
||||||
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
||||||
if isEDNS0Subnet {
|
if isEDNS0Subnet {
|
||||||
|
if !override {
|
||||||
|
return message
|
||||||
|
}
|
||||||
break findExists
|
break findExists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,14 +37,14 @@ findExists:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
message.Extra = append(message.Extra, optRecord)
|
message.Extra = append(message.Extra, optRecord)
|
||||||
} else if clone {
|
} else {
|
||||||
return setClientSubnet(message.Copy(), clientSubnet, false)
|
message = message.Copy()
|
||||||
}
|
}
|
||||||
if subnetOption == nil {
|
if subnetOption == nil {
|
||||||
subnetOption = new(dns.EDNS0_SUBNET)
|
subnetOption = new(dns.EDNS0_SUBNET)
|
||||||
subnetOption.Code = dns.EDNS0SUBNET
|
|
||||||
optRecord.Option = append(optRecord.Option, subnetOption)
|
optRecord.Option = append(optRecord.Option, subnetOption)
|
||||||
}
|
}
|
||||||
|
subnetOption.Code = dns.EDNS0SUBNET
|
||||||
if clientSubnet.Addr().Is4() {
|
if clientSubnet.Addr().Is4() {
|
||||||
subnetOption.Family = 1
|
subnetOption.Family = 1
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -258,14 +258,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
switch action.Method {
|
switch action.Method {
|
||||||
case C.RuleActionRejectMethodDefault:
|
case C.RuleActionRejectMethodDefault:
|
||||||
return &mDNS.Msg{
|
return FixedResponse(message.Id, message.Question[0], nil, 0), nil
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: mDNS.RcodeRefused,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{message.Question[0]},
|
|
||||||
}, nil
|
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
return nil, tun.ErrDrop
|
||||||
}
|
}
|
||||||
@@ -330,9 +323,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
printResult := func() {
|
printResult := func() {
|
||||||
if err == nil && len(responseAddrs) == 0 {
|
|
||||||
err = E.New("empty result")
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrResponseRejectedCached) {
|
if errors.Is(err, ErrResponseRejectedCached) {
|
||||||
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||||
@@ -341,15 +331,15 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||||
}
|
}
|
||||||
}
|
} else if len(responseAddrs) == 0 {
|
||||||
if err != nil {
|
r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
||||||
err = E.Cause(err, "lookup ", domain)
|
err = RcodeNameError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
||||||
if cached {
|
if cached {
|
||||||
if len(responseAddrs) == 0 {
|
if len(responseAddrs) == 0 {
|
||||||
return nil, E.New("lookup ", domain, ": empty result (cached)")
|
return nil, RcodeNameError
|
||||||
}
|
}
|
||||||
return responseAddrs, nil
|
return responseAddrs, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,9 +96,6 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 443
|
serverAddr.Port = 443
|
||||||
}
|
}
|
||||||
if !serverAddr.IsValid() {
|
|
||||||
return nil, E.New("invalid server address: ", serverAddr)
|
|
||||||
}
|
|
||||||
return NewHTTPSRaw(
|
return NewHTTPSRaw(
|
||||||
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
||||||
logger,
|
logger,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package local
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/netip"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -36,7 +35,6 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
}
|
}
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||||
ctx: ctx,
|
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -59,7 +57,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
systemConfig := getSystemDNSConfig(t.ctx)
|
systemConfig := getSystemDNSConfig()
|
||||||
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||||
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
||||||
} else {
|
} else {
|
||||||
@@ -91,9 +89,8 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
|||||||
startRacer := func(ctx context.Context, fqdn string) {
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var addresses []netip.Addr
|
addresses, _ := dns.MessageToAddresses(response)
|
||||||
addresses, err = dns.MessageToAddresses(response)
|
if len(addresses) == 0 {
|
||||||
if err == nil && len(addresses) == 0 {
|
|
||||||
err = E.New(fqdn, ": empty result")
|
err = E.New(fqdn, ": empty result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -24,21 +23,19 @@ type resolverConfig struct {
|
|||||||
|
|
||||||
var resolvConf resolverConfig
|
var resolvConf resolverConfig
|
||||||
|
|
||||||
func getSystemDNSConfig(ctx context.Context) *dnsConfig {
|
func getSystemDNSConfig() *dnsConfig {
|
||||||
resolvConf.tryUpdate(ctx, "/etc/resolv.conf")
|
resolvConf.tryUpdate("/etc/resolv.conf")
|
||||||
return resolvConf.dnsConfig.Load()
|
return resolvConf.dnsConfig.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *resolverConfig) init(ctx context.Context) {
|
func (conf *resolverConfig) init() {
|
||||||
conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf"))
|
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf"))
|
||||||
conf.lastChecked = time.Now()
|
conf.lastChecked = time.Now()
|
||||||
conf.ch = make(chan struct{}, 1)
|
conf.ch = make(chan struct{}, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {
|
func (conf *resolverConfig) tryUpdate(name string) {
|
||||||
conf.initOnce.Do(func() {
|
conf.initOnce.Do(conf.init)
|
||||||
conf.init(ctx)
|
|
||||||
})
|
|
||||||
|
|
||||||
if conf.dnsConfig.Load().noReload {
|
if conf.dnsConfig.Load().noReload {
|
||||||
return
|
return
|
||||||
@@ -62,7 +59,7 @@ func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dnsConf := dnsReadConfig(ctx, name)
|
dnsConf := dnsReadConfig(name)
|
||||||
conf.dnsConfig.Store(dnsConf)
|
conf.dnsConfig.Store(dnsConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ package local
|
|||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -19,7 +18,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
func dnsReadConfig(_ string) *dnsConfig {
|
||||||
if C.res_init() != 0 {
|
if C.res_init() != 0 {
|
||||||
return &dnsConfig{
|
return &dnsConfig{
|
||||||
servers: defaultNS,
|
servers: defaultNS,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ context.Context, name string) *dnsConfig {
|
func dnsReadConfig(name string) *dnsConfig {
|
||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -9,13 +8,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
|
func dnsReadConfig(_ string) *dnsConfig {
|
||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
@@ -26,35 +22,35 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
|
|||||||
conf.servers = defaultNS
|
conf.servers = defaultNS
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
addresses, err := adapterAddresses()
|
aas, err := adapterAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var dnsAddresses []struct {
|
|
||||||
ifName string
|
for _, aa := range aas {
|
||||||
netip.Addr
|
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
|
||||||
}
|
if aa.OperStatus != windows.IfOperStatusUp {
|
||||||
for _, address := range addresses {
|
|
||||||
if address.OperStatus != windows.IfOperStatusUp {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if address.IfType == windows.IF_TYPE_TUNNEL {
|
|
||||||
|
// Only take interfaces which have at least one gateway
|
||||||
|
if aa.FirstGatewayAddress == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if address.FirstGatewayAddress == nil {
|
|
||||||
continue
|
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
|
||||||
}
|
sa, err := dns.Address.Sockaddr.Sockaddr()
|
||||||
for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {
|
|
||||||
rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var dnsServerAddr netip.Addr
|
var ip netip.Addr
|
||||||
switch sockaddr := rawSockaddr.(type) {
|
switch sa := sa.(type) {
|
||||||
case *syscall.SockaddrInet4:
|
case *syscall.SockaddrInet4:
|
||||||
dnsServerAddr = netip.AddrFrom4(sockaddr.Addr)
|
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]})
|
||||||
case *syscall.SockaddrInet6:
|
case *syscall.SockaddrInet6:
|
||||||
if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {
|
var addr16 [16]byte
|
||||||
|
copy(addr16[:], sa.Addr[:])
|
||||||
|
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
|
||||||
// fec0/10 IPv6 addresses are site local anycast DNS
|
// fec0/10 IPv6 addresses are site local anycast DNS
|
||||||
// addresses Microsoft sets by default if no other
|
// addresses Microsoft sets by default if no other
|
||||||
// IPv6 DNS address is set. Site local anycast is
|
// IPv6 DNS address is set. Site local anycast is
|
||||||
@@ -62,27 +58,14 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
|
|||||||
// https://datatracker.ietf.org/doc/html/rfc3879
|
// https://datatracker.ietf.org/doc/html/rfc3879
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
|
ip = netip.AddrFrom16(addr16)
|
||||||
default:
|
default:
|
||||||
// Unexpected type.
|
// Unexpected type.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dnsAddresses = append(dnsAddresses, struct {
|
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53"))
|
||||||
ifName string
|
|
||||||
netip.Addr
|
|
||||||
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var myInterface string
|
|
||||||
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
|
|
||||||
myInterface = networkManager.InterfaceMonitor().MyInterface()
|
|
||||||
}
|
|
||||||
for _, address := range dnsAddresses {
|
|
||||||
if address.ifName == myInterface {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
|
|
||||||
}
|
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,9 +92,6 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 443
|
serverAddr.Port = 443
|
||||||
}
|
}
|
||||||
if !serverAddr.IsValid() {
|
|
||||||
return nil, E.New("invalid server address: ", serverAddr)
|
|
||||||
}
|
|
||||||
return &HTTP3Transport{
|
return &HTTP3Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
sQUIC "github.com/sagernet/sing-quic"
|
sQUIC "github.com/sagernet/sing-quic"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -59,9 +58,6 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 853
|
serverAddr.Port = 853
|
||||||
}
|
}
|
||||||
if !serverAddr.IsValid() {
|
|
||||||
return nil, E.New("invalid server address: ", serverAddr)
|
|
||||||
}
|
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -144,12 +140,12 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer stream.Close()
|
||||||
|
defer stream.CancelRead(0)
|
||||||
err = transport.WriteMessage(stream, 0, message)
|
err = transport.WriteMessage(stream, 0, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stream.Close()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
stream.Close()
|
|
||||||
return transport.ReadMessage(stream)
|
return transport.ReadMessage(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@@ -41,9 +40,6 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 53
|
serverAddr.Port = 53
|
||||||
}
|
}
|
||||||
if !serverAddr.IsValid() {
|
|
||||||
return nil, E.New("invalid server address: ", serverAddr)
|
|
||||||
}
|
|
||||||
return &TCPTransport{
|
return &TCPTransport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
|
|||||||
@@ -57,9 +57,6 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 853
|
serverAddr.Port = 853
|
||||||
}
|
}
|
||||||
if !serverAddr.IsValid() {
|
|
||||||
return nil, E.New("invalid server address: ", serverAddr)
|
|
||||||
}
|
|
||||||
return &TLSTransport{
|
return &TLSTransport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -48,9 +47,6 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 53
|
serverAddr.Port = 53
|
||||||
}
|
}
|
||||||
if !serverAddr.IsValid() {
|
|
||||||
return nil, E.New("invalid server address: ", serverAddr)
|
|
||||||
}
|
|
||||||
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
|
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +117,7 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
|||||||
conn.access.Unlock()
|
conn.access.Unlock()
|
||||||
defer func() {
|
defer func() {
|
||||||
conn.access.Lock()
|
conn.access.Lock()
|
||||||
delete(conn.callbacks, exMessage.Id)
|
delete(conn.callbacks, messageId)
|
||||||
conn.access.Unlock()
|
conn.access.Unlock()
|
||||||
}()
|
}()
|
||||||
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
||||||
@@ -216,8 +212,8 @@ type dnsConnection struct {
|
|||||||
|
|
||||||
func (c *dnsConnection) Close(err error) {
|
func (c *dnsConnection) Close(err error) {
|
||||||
c.closeOnce.Do(func() {
|
c.closeOnce.Do(func() {
|
||||||
c.err = err
|
|
||||||
close(c.done)
|
close(c.done)
|
||||||
|
c.err = err
|
||||||
})
|
})
|
||||||
c.Conn.Close()
|
c.Conn.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,104 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.12.0-beta.14
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 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
|
|
||||||
|
|
||||||
### 1.11.7
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
|
||||||
violated the rules (TestFlight users are not affected)._
|
|
||||||
|
|
||||||
#### 1.12.0-beta.1
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
|
||||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
|
||||||
|
|
||||||
### 1.11.6
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
|
||||||
violated the rules (TestFlight users are not affected)._
|
|
||||||
|
|
||||||
#### 1.12.0-alpha.19
|
#### 1.12.0-alpha.19
|
||||||
|
|
||||||
* Update gVisor to 20250319.0
|
* Update gVisor to 20250319.0
|
||||||
@@ -138,8 +40,7 @@ See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
_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)._
|
||||||
violated the rules (TestFlight users are not affected)._
|
|
||||||
|
|
||||||
#### 1.12.0-alpha.13
|
#### 1.12.0-alpha.13
|
||||||
|
|
||||||
@@ -210,8 +111,7 @@ For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and pa
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration
|
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
|
||||||
process._
|
|
||||||
|
|
||||||
#### 1.12.0-alpha.5
|
#### 1.12.0-alpha.5
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ and the data generated by the software is always on your device.
|
|||||||
|
|
||||||
## Android
|
## Android
|
||||||
|
|
||||||
The broad package (App) visibility (QUERY_ALL_PACKAGES) permission
|
|
||||||
is used to provide per-application proxy features for VPN,
|
|
||||||
sing-box will not collect your app list.
|
|
||||||
|
|
||||||
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
|
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
|
||||||
sing-box uses the location permission in the background
|
sing-box uses the location permission in the background
|
||||||
to get information about the connected Wi-Fi network to make them work.
|
to get information about the connected Wi-Fi network to make them work.
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ Will overrides `dns.client_subnet`.
|
|||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
- `default`: Reply with REFUSED.
|
- `default`: Reply with NXDOMAIN.
|
||||||
- `drop`: Drop the request.
|
- `drop`: Drop the request.
|
||||||
|
|
||||||
`default` will be used by default.
|
`default` will be used by default.
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
- `default`: 返回 REFUSED。
|
- `default`: 返回 NXDOMAIN。
|
||||||
- `drop`: 丢弃请求。
|
- `drop`: 丢弃请求。
|
||||||
|
|
||||||
默认使用 `defualt`。
|
默认使用 `defualt`。
|
||||||
|
|||||||
@@ -27,20 +27,19 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
The type of the DNS server.
|
The type of the DNS server.
|
||||||
|
|
||||||
| Type | Format |
|
| Type | Format |
|
||||||
|-----------------|---------------------------|
|
|-----------------|-----------------------------|
|
||||||
| empty (default) | [Legacy](./legacy/) |
|
| empty (default) | [Legacy](./legacy/) |
|
||||||
| `local` | [Local](./local/) |
|
| `tcp` | [TCP](./tcp/) |
|
||||||
| `hosts` | [Hosts](./hosts/) |
|
| `udp` | [UDP](./udp/) |
|
||||||
| `tcp` | [TCP](./tcp/) |
|
| `tls` | [TLS](./tls/) |
|
||||||
| `udp` | [UDP](./udp/) |
|
| `https` | [HTTPS](./https/) |
|
||||||
| `tls` | [TLS](./tls/) |
|
| `quic` | [QUIC](./quic/) |
|
||||||
| `quic` | [QUIC](./quic/) |
|
| `h3` | [HTTP/3](./http3/) |
|
||||||
| `https` | [HTTPS](./https/) |
|
| `predefined` | [Predefined](./predefined/) |
|
||||||
| `h3` | [HTTP/3](./http3/) |
|
| `dhcp` | [DHCP](./dhcp/) |
|
||||||
| `dhcp` | [DHCP](./dhcp/) |
|
| `fakeip` | [Fake IP](./fakeip/) |
|
||||||
| `fakeip` | [Fake IP](./fakeip/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -27,20 +27,19 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
DNS 服务器的类型。
|
DNS 服务器的类型。
|
||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|-----------------|---------------------------|
|
|-----------------|-----------------------------|
|
||||||
| empty (default) | [Legacy](./legacy/) |
|
| empty (default) | [Legacy](./legacy/) |
|
||||||
| `local` | [Local](./local/) |
|
| `tcp` | [TCP](./tcp/) |
|
||||||
| `hosts` | [Hosts](./hosts/) |
|
| `udp` | [UDP](./udp/) |
|
||||||
| `tcp` | [TCP](./tcp/) |
|
| `tls` | [TLS](./tls/) |
|
||||||
| `udp` | [UDP](./udp/) |
|
| `https` | [HTTPS](./https/) |
|
||||||
| `tls` | [TLS](./tls/) |
|
| `quic` | [QUIC](./quic/) |
|
||||||
| `quic` | [QUIC](./quic/) |
|
| `h3` | [HTTP/3](./http3/) |
|
||||||
| `https` | [HTTPS](./https/) |
|
| `predefined` | [Predefined](./predefined/) |
|
||||||
| `h3` | [HTTP/3](./http3/) |
|
| `dhcp` | [DHCP](./dhcp/) |
|
||||||
| `dhcp` | [DHCP](./dhcp/) |
|
| `fakeip` | [Fake IP](./fakeip/) |
|
||||||
| `fakeip` | [Fake IP](./fakeip/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ icon: material/new-box
|
|||||||
"control_url": "",
|
"control_url": "",
|
||||||
"ephemeral": false,
|
"ephemeral": false,
|
||||||
"hostname": "",
|
"hostname": "",
|
||||||
"accept_routes": false,
|
|
||||||
"exit_node": "",
|
"exit_node": "",
|
||||||
"exit_node_allow_lan_access": false,
|
"exit_node_allow_lan_access": false,
|
||||||
"advertise_routes": [],
|
"advertise_routes": [],
|
||||||
@@ -63,10 +62,6 @@ System hostname is used by default.
|
|||||||
|
|
||||||
Example: `localhost`
|
Example: `localhost`
|
||||||
|
|
||||||
#### accept_routes
|
|
||||||
|
|
||||||
Indicates whether the node should accept routes advertised by other nodes.
|
|
||||||
|
|
||||||
#### exit_node
|
#### exit_node
|
||||||
|
|
||||||
The exit node name or IP address to use.
|
The exit node name or IP address to use.
|
||||||
|
|||||||
@@ -42,18 +42,16 @@ AnyTLS padding scheme line array.
|
|||||||
|
|
||||||
Default padding scheme:
|
Default padding scheme:
|
||||||
|
|
||||||
```json
|
```
|
||||||
[
|
stop=8
|
||||||
"stop=8",
|
0=34-120
|
||||||
"0=30-30",
|
1=100-400
|
||||||
"1=100-400",
|
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
||||||
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
|
3=500-1000
|
||||||
"3=9-9,500-1000",
|
4=500-1000
|
||||||
"4=500-1000",
|
5=500-1000
|
||||||
"5=500-1000",
|
6=500-1000
|
||||||
"6=500-1000",
|
7=500-1000
|
||||||
"7=500-1000"
|
|
||||||
]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|||||||
@@ -42,18 +42,16 @@ AnyTLS 填充方案行数组。
|
|||||||
|
|
||||||
默认填充方案:
|
默认填充方案:
|
||||||
|
|
||||||
```json
|
```
|
||||||
[
|
stop=8
|
||||||
"stop=8",
|
0=34-120
|
||||||
"0=30-30",
|
1=100-400
|
||||||
"1=100-400",
|
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
||||||
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
|
3=500-1000
|
||||||
"3=9-9,500-1000",
|
4=500-1000
|
||||||
"4=500-1000",
|
5=500-1000
|
||||||
"5=500-1000",
|
6=500-1000
|
||||||
"6=500-1000",
|
7=500-1000
|
||||||
"7=500-1000"
|
|
||||||
]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|||||||
@@ -211,10 +211,6 @@ Set the default route to the Tun.
|
|||||||
|
|
||||||
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
|
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
|
||||||
|
|
||||||
!!! note "Also enable `auto_redirect`"
|
|
||||||
|
|
||||||
`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks.
|
|
||||||
|
|
||||||
#### iproute2_table_index
|
#### iproute2_table_index
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
@@ -239,29 +235,22 @@ Linux iproute2 rule start index generated by `auto_route`.
|
|||||||
|
|
||||||
Only supported on Linux with `auto_route` enabled.
|
Only supported on Linux with `auto_route` enabled.
|
||||||
|
|
||||||
Improve TUN routing and performance using nftables.
|
Automatically configure iptables/nftables to redirect connections.
|
||||||
|
|
||||||
`auto_redirect` is always recommended on Linux, it provides better routing,
|
*In Android*:
|
||||||
higher performance (better than tproxy),
|
|
||||||
and avoids conflicts between TUN and Docker bridge networks.
|
|
||||||
|
|
||||||
Note that `auto_redirect` also works on Android,
|
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||||
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).
|
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||||
|
|
||||||
`auto_redirect` also automatically inserts compatibility rules
|
*In Linux*:
|
||||||
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`.
|
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
Connection input mark used by `auto_redirect`.
|
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
|
||||||
|
|
||||||
`0x2023` is used by default.
|
`0x2023` is used by default.
|
||||||
|
|
||||||
@@ -269,7 +258,7 @@ Connection input mark used by `auto_redirect`.
|
|||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
Connection output mark used by `auto_redirect`.
|
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
|
||||||
|
|
||||||
`0x2024` is used by default.
|
`0x2024` is used by default.
|
||||||
|
|
||||||
@@ -280,15 +269,17 @@ Enforce strict routing rules when `auto_route` is enabled:
|
|||||||
*In Linux*:
|
*In Linux*:
|
||||||
|
|
||||||
* Let unsupported network unreachable
|
* Let unsupported network unreachable
|
||||||
* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
|
* 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.
|
||||||
|
|
||||||
*In Windows*:
|
*In Windows*:
|
||||||
|
|
||||||
* Let unsupported network unreachable
|
* Add firewall rules to prevent DNS leak caused by
|
||||||
* prevent DNS leak caused by
|
|
||||||
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
||||||
|
|
||||||
It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations.
|
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
||||||
|
|
||||||
#### route_address
|
#### route_address
|
||||||
|
|
||||||
@@ -377,6 +368,8 @@ Exclude custom routes when `auto_route` is enabled.
|
|||||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||||
Matched traffic will bypass the sing-box routes.
|
Matched traffic will bypass the sing-box routes.
|
||||||
|
|
||||||
|
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||||
|
|
||||||
=== "Without `auto_redirect` enabled"
|
=== "Without `auto_redirect` enabled"
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|||||||
@@ -215,10 +215,6 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。
|
VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。
|
||||||
|
|
||||||
!!! note "也启用 `auto_redirect`"
|
|
||||||
|
|
||||||
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy), 并避免 TUN 与 Docker 桥接网络冲突。
|
|
||||||
|
|
||||||
#### iproute2_table_index
|
#### iproute2_table_index
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
@@ -243,22 +239,21 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
仅支持 Linux,且需要 `auto_route` 已启用。
|
仅支持 Linux,且需要 `auto_route` 已启用。
|
||||||
|
|
||||||
通过使用 nftables 改善 TUN 路由和性能。
|
自动配置 iptables/nftables 以重定向连接。
|
||||||
|
|
||||||
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy),并避免了 TUN 和 Docker 桥接网络之间的冲突。
|
*在 Android 中*:
|
||||||
|
|
||||||
请注意,`auto_redirect` 也适用于 Android,但由于缺少 `nftables` 和 `ip6tables`,仅执行简单的 IPv4 TCP 转发。
|
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
||||||
若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
|
||||||
|
|
||||||
`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。
|
*在 Linux 中*:
|
||||||
|
|
||||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
带有 `auto_redirect `的 `auto_route` 可以在路由器上按预期工作,**无需干预**。
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
`auto_redirect` 使用的连接输入标记。
|
`route_address_set` 和 `route_exclude_address_set` 使用的连接输入标记。
|
||||||
|
|
||||||
默认使用 `0x2023`。
|
默认使用 `0x2023`。
|
||||||
|
|
||||||
@@ -266,25 +261,29 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
`auto_redirect` 使用的连接输出标记。
|
`route_address_set` 和 `route_exclude_address_set` 使用的连接输出标记。
|
||||||
|
|
||||||
默认使用 `0x2024`。
|
默认使用 `0x2024`。
|
||||||
|
|
||||||
#### strict_route
|
#### strict_route
|
||||||
|
|
||||||
当启用 `auto_route` 时,强制执行严格的路由规则:
|
启用 `auto_route` 时执行严格的路由规则。
|
||||||
|
|
||||||
*在 Linux 中*:
|
*在 Linux 中*:
|
||||||
|
|
||||||
* 使不支持的网络不可达。
|
* 让不支持的网络无法到达
|
||||||
* 出于历史遗留原因,当未启用 `strict_route` 或 `auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。
|
* 使 ICMP 流量路由到 tun 而不是上游接口
|
||||||
|
* 将所有连接路由到 tun
|
||||||
|
|
||||||
*在 Windows 中*:
|
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
|
||||||
|
|
||||||
* 使不支持的网络不可达。
|
*在 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 应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
* 添加防火墙规则以阻止 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)在某些情况下无法正常工作。
|
||||||
|
|
||||||
#### route_address
|
#### route_address
|
||||||
|
|
||||||
@@ -343,6 +342,8 @@ tun 接口的 IPv6 前缀。
|
|||||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||||
不匹配的流量将绕过 sing-box 路由。
|
不匹配的流量将绕过 sing-box 路由。
|
||||||
|
|
||||||
|
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||||
|
|
||||||
=== "`auto_redirect` 未启用"
|
=== "`auto_redirect` 未启用"
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
icon: material/delete-clock
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.11.0"
|
||||||
|
|
||||||
|
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
icon: material/delete-clock
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||||
|
|
||||||
|
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||||
|
|
||||||
`block` 出站关闭所有传入请求。
|
`block` 出站关闭所有传入请求。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
|
||||||
:material-plus: [resolve.disable_cache](#disable_cache)
|
:material-plus: [resolve.disable_cache](#disable_cache)
|
||||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
||||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
:material-plus: [resolve.client_subnet](#client_subnet)
|
||||||
@@ -92,8 +91,7 @@ Not available when `method` is set to drop.
|
|||||||
"udp_connect": false,
|
"udp_connect": false,
|
||||||
"udp_timeout": "",
|
"udp_timeout": "",
|
||||||
"tls_fragment": false,
|
"tls_fragment": false,
|
||||||
"tls_fragment_fallback_delay": "",
|
"tls_fragment_fallback_delay": ""
|
||||||
"tls_record_fragment": ""
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -166,19 +164,13 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
|
|||||||
|
|
||||||
Fragment TLS handshakes to bypass firewalls.
|
Fragment TLS handshakes to bypass firewalls.
|
||||||
|
|
||||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
|
||||||
and should not be used to circumvent real censorship.
|
|
||||||
|
|
||||||
Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.
|
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.
|
||||||
|
|
||||||
On Linux, Apple platforms, (administrator privileges required) Windows,
|
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`.
|
||||||
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,
|
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.
|
||||||
because the target is considered to be local or behind a transparent proxy.
|
|
||||||
|
|
||||||
Conflict with `tls_record_fragment`.
|
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
@@ -188,17 +180,6 @@ The fallback value used when TLS segmentation cannot automatically determine the
|
|||||||
|
|
||||||
`500ms` is used by default.
|
`500ms` is used by default.
|
||||||
|
|
||||||
#### tls_record_fragment
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
|
||||||
|
|
||||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
|
||||||
and should not be used to circumvent real censorship.
|
|
||||||
|
|
||||||
Conflict with `tls_fragment`.
|
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
|
||||||
:material-plus: [resolve.disable_cache](#disable_cache)
|
|
||||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
|
||||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
|
||||||
|
|
||||||
## 最终动作
|
## 最终动作
|
||||||
|
|
||||||
@@ -163,15 +159,12 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||||
|
|
||||||
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
|
由于它不是为性能设计的,不应被应用于所有连接,而仅应用于已知被阻止的服务器名称。
|
||||||
|
|
||||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
|
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
||||||
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
|
||||||
|
|
||||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||||
|
|
||||||
与 `tls_record_fragment` 冲突。
|
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
@@ -180,16 +173,6 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
默认使用 `500ms`。
|
默认使用 `500ms`。
|
||||||
|
|
||||||
#### tls_record_fragment
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
|
||||||
|
|
||||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
|
||||||
|
|
||||||
与 `tls_fragment` 冲突。
|
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
|
|||||||
|
|
||||||
| QUIC Client | Type |
|
| QUIC Client | Type |
|
||||||
|:------------------------:|:----------:|
|
|:------------------------:|:----------:|
|
||||||
| Chromium/Cronet | `chromium` |
|
| Chromium/Cronet | `chrimium` |
|
||||||
| Safari/Apple Network API | `safari` |
|
| Safari/Apple Network API | `safari` |
|
||||||
| Firefox / uquic firefox | `firefox` |
|
| Firefox / uquic firefox | `firefox` |
|
||||||
| quic-go / uquic chrome | `quic-go` |
|
| quic-go / uquic chrome | `quic-go` |
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
| QUIC 客户端 | 类型 |
|
| QUIC 客户端 | 类型 |
|
||||||
|:------------------------:|:----------:|
|
|:------------------------:|:----------:|
|
||||||
| Chromium/Cronet | `chromium` |
|
| Chromium/Cronet | `chrimium` |
|
||||||
| Safari/Apple Network API | `safari` |
|
| Safari/Apple Network API | `safari` |
|
||||||
| Firefox / uquic firefox | `firefox` |
|
| Firefox / uquic firefox | `firefox` |
|
||||||
| quic-go / uquic chrome | `quic-go` |
|
| quic-go / uquic chrome | `quic-go` |
|
||||||
@@ -80,8 +80,6 @@ List of [Headless Rule](./headless-rule/).
|
|||||||
|
|
||||||
Format of rule-set file, `source` or `binary`.
|
Format of rule-set file, `source` or `binary`.
|
||||||
|
|
||||||
Optional when `path` or `url` uses `json` or `srs` as extension.
|
|
||||||
|
|
||||||
### Local Fields
|
### Local Fields
|
||||||
|
|
||||||
#### path
|
#### path
|
||||||
|
|||||||
@@ -80,8 +80,6 @@
|
|||||||
|
|
||||||
规则集格式, `source` 或 `binary`。
|
规则集格式, `source` 或 `binary`。
|
||||||
|
|
||||||
当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。
|
|
||||||
|
|
||||||
### 本地字段
|
### 本地字段
|
||||||
|
|
||||||
#### path
|
#### path
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user