mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-08 17:23:20 +03:00
Compare commits
72 Commits
v1.12.1-ex
...
dev-wip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
847c80a1fe | ||
|
|
40be0627f5 | ||
|
|
ae3b943b93 | ||
|
|
7da9b40c66 | ||
|
|
48772992fa | ||
|
|
a66772fa14 | ||
|
|
3b49ed8a71 | ||
|
|
1c34ee26dc | ||
|
|
0ca4d8f327 | ||
|
|
7e69892694 | ||
|
|
0494541057 | ||
|
|
b55d5914e0 | ||
|
|
584fd40d59 | ||
|
|
3c595f205b | ||
|
|
f75bd13383 | ||
|
|
99effc44f8 | ||
|
|
7abe17cea0 | ||
|
|
04f0ae6c00 | ||
|
|
8f79ee6919 | ||
|
|
29150fc0f7 | ||
|
|
30ac4ca62c | ||
|
|
819f14eca0 | ||
|
|
24df55660d | ||
|
|
02943cefb3 | ||
|
|
9f5a403532 | ||
|
|
9bcab743fc | ||
|
|
af47fc2543 | ||
|
|
07160909d7 | ||
|
|
74c37a6f9c | ||
|
|
0dd11223ef | ||
|
|
f635cdd3ef | ||
|
|
dccf5c56fb | ||
|
|
12141bb74a | ||
|
|
657bd4725f | ||
|
|
c50c8815f0 | ||
|
|
3026b37734 | ||
|
|
ecbd8f55f6 | ||
|
|
743b00f748 | ||
|
|
0249fc5e67 | ||
|
|
51e63cbeba | ||
|
|
b6a9e0ce0c | ||
|
|
3114db2450 | ||
|
|
ac29d4ca9c | ||
|
|
70bb012989 | ||
|
|
c6090a32dc | ||
|
|
e7d0227c90 | ||
|
|
6bbab76cc7 | ||
|
|
0af1236d8f | ||
|
|
488e26aebc | ||
|
|
8a2a65133a | ||
|
|
5e6cc89c65 | ||
|
|
3145df4b6c | ||
|
|
4b2f455e1e | ||
|
|
02619e3dfa | ||
|
|
00082ab6c8 | ||
|
|
ce26077d83 | ||
|
|
089e13451b | ||
|
|
d3184a7997 | ||
|
|
65f5767180 | ||
|
|
c594f33dc4 | ||
|
|
e65a9d7380 | ||
|
|
dd304bebfc | ||
|
|
6a6c6a5ba5 | ||
|
|
b99dc4609d | ||
|
|
e59dab657e | ||
|
|
908174ddf6 | ||
|
|
34c48fa421 | ||
|
|
1fe5b8fd9d | ||
|
|
4bef6ea0e8 | ||
|
|
941049d21b | ||
|
|
bdc1724975 | ||
|
|
942511ad6d |
@@ -1,14 +1,12 @@
|
|||||||
-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
|
--config-files /etc/sing-box/config.json
|
||||||
--after-install release/config/sing-box.postinst
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
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
|
|
||||||
105
.github/workflows/build.yml
vendored
105
.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.6
|
go-version: ^1.24.2
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
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.6
|
go-version: ^1.24.2
|
||||||
- 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,7 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_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,with_tailscale'
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.os != 'android'
|
if: matrix.os != 'android'
|
||||||
@@ -154,10 +147,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,17 +167,12 @@ 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}"
|
||||||
|
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
@@ -196,12 +181,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/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 "$PKG_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,29 +217,12 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libarchive-tools
|
sudo apt-get install -y libarchive-tools
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t pacman \
|
fpm -t pacman \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/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
|
||||||
@@ -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.6
|
go-version: ^1.24.2
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -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.6
|
go-version: ^1.24.2
|
||||||
- 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,15 +437,15 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.2
|
||||||
- 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: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Setup Xcode beta
|
- name: Setup Xcode beta
|
||||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -615,7 +580,7 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
|
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
|
|||||||
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.6
|
go-version: ^1.24.2
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
11
.github/workflows/linux.yml
vendored
11
.github/workflows/linux.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.2
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.2
|
||||||
- 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,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
|
||||||
run: |
|
run: |
|
||||||
@@ -117,7 +120,6 @@ jobs:
|
|||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get install -y debsigs
|
sudo apt-get install -y debsigs
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t deb \
|
fpm -t deb \
|
||||||
--name "${NAME}" \
|
--name "${NAME}" \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
@@ -136,7 +138,6 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
--name "${NAME}" \
|
--name "${NAME}" \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -17,6 +17,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
|
||||||
@@ -46,6 +47,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
|
||||||
|
|||||||
@@ -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,with_tailscale" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|||||||
29
Makefile
29
Makefile
@@ -1,10 +1,11 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
|
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale
|
||||||
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
||||||
|
|
||||||
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 github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
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)"
|
||||||
@@ -108,16 +109,6 @@ upload_ios_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_ios_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
|
||||||
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
|
||||||
|
|
||||||
upload_ios_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
release_ios: build_ios upload_ios_app_store
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
@@ -185,16 +176,6 @@ upload_tvos_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_tvos_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
|
||||||
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
|
||||||
|
|
||||||
upload_tvos_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
@@ -245,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.7
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.7
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -1,20 +1,12 @@
|
|||||||
# sing-box-extended
|
# sing-box
|
||||||
|
|
||||||
Sing-box with extended features.
|
The universal proxy platform.
|
||||||
|
|
||||||
## Features
|
[](https://repology.org/project/sing-box/versions)
|
||||||
|
|
||||||
* Amnezia
|
## Documentation
|
||||||
* WARP
|
|
||||||
* Tunneling
|
|
||||||
* Mieru
|
|
||||||
* XHTTP
|
|
||||||
* SDNS (DNSCrypt)
|
|
||||||
* Unified delay
|
|
||||||
|
|
||||||
## Examples
|
https://sing-box.sagernet.org
|
||||||
|
|
||||||
https://github.com/shtorm-7/sing-box-extended/tree/extended/examples
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -36,4 +28,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
In addition, no derivative work may use the name or imply association
|
||||||
with this application without prior consent.
|
with this application without prior consent.
|
||||||
```
|
```
|
||||||
@@ -35,6 +35,7 @@ func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Ma
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
@@ -42,12 +43,9 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
m.stage = stage
|
m.stage = stage
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
// started with outbound manager
|
// started with outbound manager
|
||||||
m.access.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
endpoints := m.endpoints
|
for _, endpoint := range m.endpoints {
|
||||||
m.access.Unlock()
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
err := adapter.LegacyStart(endpoint, stage)
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ type CacheFile interface {
|
|||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
StoreWARPConfig() bool
|
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
@@ -54,8 +52,6 @@ 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
|
||||||
LoadWARPConfig(tag string) *SavedBinary
|
|
||||||
SaveWARPConfig(tag string, set *SavedBinary) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedBinary struct {
|
||||||
|
|||||||
@@ -42,24 +42,22 @@ type InboundManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion uint8
|
IPVersion uint8
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
TunnelSource string
|
User string
|
||||||
TunnelDestination string
|
Outbound string
|
||||||
User string
|
|
||||||
Outbound string
|
|
||||||
|
|
||||||
// sniffer
|
// sniffer
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SniffError error
|
PacketSniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
@@ -76,7 +74,6 @@ type InboundContext struct {
|
|||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
TLSFragment bool
|
TLSFragment bool
|
||||||
TLSFragmentFallbackDelay time.Duration
|
TLSFragmentFallbackDelay time.Duration
|
||||||
TLSRecordFragment bool
|
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
|
|||||||
6
box.go
6
box.go
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
@@ -140,9 +139,6 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
|
||||||
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
|
||||||
}
|
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
@@ -502,7 +498,7 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.internalService {
|
for _, lifecycleService := range s.internalService {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
|
|||||||
Submodule clients/android updated: 6db8e06e8d...8354b78e5d
Submodule clients/apple updated: c5734677bd...ae5818ee5a
@@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
client := createClient(20 * time.Minute)
|
client := createClient(10 * time.Minute)
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
@@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
build := builds.Data[0]
|
||||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
|
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
@@ -177,7 +177,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
||||||
log.Info("waiting for process")
|
log.Info("waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -16,17 +16,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
withTailscale bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -46,9 +44,8 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
iosTags []string
|
||||||
memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,10 +59,9 @@ 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", "with_conntrack")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
||||||
darwinTags = append(darwinTags, "with_dhcp")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,8 +108,6 @@ func buildAndroid() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-ldflags", "-checklinkname=0")
|
|
||||||
|
|
||||||
tags := append(sharedTags, memcTags...)
|
tags := append(sharedTags, memcTags...)
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
@@ -157,10 +151,7 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
}
|
|
||||||
if !withTailscale {
|
|
||||||
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@@ -169,10 +160,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
if withTailscale {
|
|
||||||
tags = append(tags, memcTags...)
|
|
||||||
}
|
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
|
||||||
|
|
||||||
var iperf3Path string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := main0()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main0() error {
|
|
||||||
err := shell.Exec("sudo", "ls").Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
results, err := runTests()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoder := json.NewEncoder(os.Stdout)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
return encoder.Encode(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTests() ([]TestResult, error) {
|
|
||||||
boxPaths := []string{
|
|
||||||
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
|
|
||||||
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
|
||||||
"./sing-box",
|
|
||||||
}
|
|
||||||
stacks := []string{
|
|
||||||
"gvisor",
|
|
||||||
"system",
|
|
||||||
}
|
|
||||||
mtus := []int{
|
|
||||||
1500,
|
|
||||||
4064,
|
|
||||||
// 16384,
|
|
||||||
// 32768,
|
|
||||||
// 49152,
|
|
||||||
65535,
|
|
||||||
}
|
|
||||||
flagList := [][]string{
|
|
||||||
{},
|
|
||||||
}
|
|
||||||
var results []TestResult
|
|
||||||
for _, boxPath := range boxPaths {
|
|
||||||
for _, stack := range stacks {
|
|
||||||
for _, mtu := range mtus {
|
|
||||||
if strings.HasPrefix(boxPath, ".") {
|
|
||||||
for _, flags := range flagList {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestResult struct {
|
|
||||||
BoxPath string `json:"box_path"`
|
|
||||||
Stack string `json:"stack"`
|
|
||||||
MTU int `json:"mtu"`
|
|
||||||
Flags []string `json:"flags"`
|
|
||||||
MultiThread bool `json:"multi_thread"`
|
|
||||||
UploadSpeed string `json:"upload_speed"`
|
|
||||||
DownloadSpeed string `json:"download_speed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
|
|
||||||
testAddress := netip.MustParseAddr("1.1.1.1")
|
|
||||||
testConfig := option.Options{
|
|
||||||
Inbounds: []option.Inbound{
|
|
||||||
{
|
|
||||||
Type: C.TypeTun,
|
|
||||||
Options: &option.TunInboundOptions{
|
|
||||||
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
|
|
||||||
AutoRoute: true,
|
|
||||||
MTU: uint32(mtu),
|
|
||||||
Stack: stackName,
|
|
||||||
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Route: &option.RouteOptions{
|
|
||||||
Rules: []option.Rule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultRule{
|
|
||||||
RawDefaultRule: option.RawDefaultRule{
|
|
||||||
IPCIDR: []string{testAddress.String()},
|
|
||||||
},
|
|
||||||
RuleAction: option.RuleAction{
|
|
||||||
Action: C.RuleActionTypeRouteOptions,
|
|
||||||
RouteOptionsOptions: option.RouteOptionsActionOptions{
|
|
||||||
OverrideAddress: "127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AutoDetectInterface: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ctx := include.Context(context.Background())
|
|
||||||
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(tempConfig.Name())
|
|
||||||
encoder := json.NewEncoderContext(ctx, tempConfig)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
err = encoder.Encode(testConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "encode test config")
|
|
||||||
}
|
|
||||||
tempConfig.Close()
|
|
||||||
var sudoArgs []string
|
|
||||||
if len(flags) > 0 {
|
|
||||||
sudoArgs = append(sudoArgs, "env")
|
|
||||||
for _, flag := range flags {
|
|
||||||
sudoArgs = append(sudoArgs, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
|
||||||
boxProcess := shell.Exec("sudo", sudoArgs...)
|
|
||||||
boxProcess.Stdout = &stderrWriter{}
|
|
||||||
boxProcess.Stderr = io.Discard
|
|
||||||
err = boxProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if C.IsDarwin {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3-darwin")
|
|
||||||
} else {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverProcess := shell.Exec(iperf3Path, "-s")
|
|
||||||
serverProcess.Stdout = io.Discard
|
|
||||||
serverProcess.Stderr = io.Discard
|
|
||||||
err = serverProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "start iperf3 server")
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
args := []string{"-c", testAddress.String()}
|
|
||||||
if multiThread {
|
|
||||||
args = append(args, "-P", "10")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadProcess := shell.Exec(iperf3Path, args...)
|
|
||||||
output, err := uploadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
|
|
||||||
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
|
|
||||||
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
|
|
||||||
|
|
||||||
result = &TestResult{
|
|
||||||
BoxPath: boxPath,
|
|
||||||
Stack: stackName,
|
|
||||||
MTU: mtu,
|
|
||||||
Flags: flags,
|
|
||||||
MultiThread: multiThread,
|
|
||||||
UploadSpeed: uploadResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
|
|
||||||
output, err = downloadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
|
|
||||||
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
|
|
||||||
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
|
|
||||||
|
|
||||||
result.DownloadSpeed = downloadResult
|
|
||||||
|
|
||||||
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
|
|
||||||
if len(flags) > 0 {
|
|
||||||
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
|
|
||||||
}
|
|
||||||
if multiThread {
|
|
||||||
printArgs = append(printArgs, "(-P 10)")
|
|
||||||
}
|
|
||||||
fmt.Println(printArgs...)
|
|
||||||
err = boxProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = serverProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
boxProcess.Cmd.Wait()
|
|
||||||
close(boxDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
serverDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
serverProcess.Process.Wait()
|
|
||||||
close(serverDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-boxDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
boxProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("box process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-serverDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
serverProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("server process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type stderrWriter struct{}
|
|
||||||
|
|
||||||
func (w *stderrWriter) Write(p []byte) (n int, err error) {
|
|
||||||
return os.Stderr.Write(p)
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -67,5 +68,6 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||||
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry(), include.ServiceRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
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"
|
||||||
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
var rules []option.HeadlessRule
|
var rules []option.HeadlessRule
|
||||||
switch flagRuleSetConvertType {
|
switch flagRuleSetConvertType {
|
||||||
case "adguard":
|
case "adguard":
|
||||||
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
rules, err = adguard.Convert(reader)
|
||||||
case "":
|
case "":
|
||||||
return E.New("source type is required")
|
return E.New("source type is required")
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.AdGuardDomain) > 0
|
|
||||||
}) {
|
|
||||||
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if hasRule(rule.LogicalOptions.Rules, cond) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,10 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
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/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
|
|||||||
isImportant bool
|
isImportant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
|
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
var (
|
var (
|
||||||
ruleLines []agdguardRuleLine
|
ruleLines []agdguardRuleLine
|
||||||
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
|
|||||||
parseLine:
|
parseLine:
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ruleLine := scanner.Text()
|
ruleLine := scanner.Text()
|
||||||
if ruleLine == "" {
|
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originRuleLine := ruleLine
|
originRuleLine := ruleLine
|
||||||
@@ -96,7 +92,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if !ignored {
|
if !ignored {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||||
continue parseLine
|
continue parseLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,35 +120,27 @@ parseLine:
|
|||||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||||
if ignoreIPCIDRRegexp(ruleLine) {
|
if ignoreIPCIDRRegexp(ruleLine) {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isRegexp = true
|
isRegexp = true
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(ruleLine, "://") {
|
if strings.Contains(ruleLine, "://") {
|
||||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||||
isSuffix = true
|
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "/") {
|
if strings.Contains(ruleLine, "/") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
if strings.Contains(ruleLine, "##") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
|
if strings.Contains(ruleLine, "#$#") {
|
||||||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
|
|
||||||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
|
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(ruleLine, "~") {
|
|
||||||
ignoredLines++
|
|
||||||
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var domainCheck string
|
var domainCheck string
|
||||||
@@ -163,7 +151,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if ruleLine == "" {
|
if ruleLine == "" {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||||
@@ -171,13 +159,13 @@ parseLine:
|
|||||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||||
if ipErr == nil {
|
if ipErr == nil {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||||
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||||
}
|
}
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
continue
|
continue
|
||||||
@@ -295,112 +283,10 @@ parseLine:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ignoredLines > 0 {
|
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||||
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
|
||||||
}
|
|
||||||
return []option.HeadlessRule{currentRule}, nil
|
return []option.HeadlessRule{currentRule}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
|
||||||
|
|
||||||
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
|
||||||
if len(rules) != 1 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule := rules[0]
|
|
||||||
var (
|
|
||||||
importantDomain []string
|
|
||||||
importantDomainRegex []string
|
|
||||||
importantExcludeDomain []string
|
|
||||||
importantExcludeDomainRegex []string
|
|
||||||
domain []string
|
|
||||||
domainRegex []string
|
|
||||||
excludeDomain []string
|
|
||||||
excludeDomainRegex []string
|
|
||||||
)
|
|
||||||
parse:
|
|
||||||
for {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
|
||||||
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule = rule.LogicalOptions.Rules[1]
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
domain = rule.DefaultOptions.AdGuardDomain
|
|
||||||
domainRegex = rule.DefaultOptions.DomainRegex
|
|
||||||
if len(domain)+len(domainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
break parse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var output bytes.Buffer
|
|
||||||
for _, ruleLine := range importantDomain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantDomainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
return output.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||||
ruleLine = ruleLine[12:]
|
ruleLine = ruleLine[12:]
|
||||||
@@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
|||||||
ruleLine = ruleLine[13:]
|
ruleLine = ruleLine[13:]
|
||||||
} else if strings.HasPrefix(ruleLine, "^") {
|
} else if strings.HasPrefix(ruleLine, "^") {
|
||||||
ruleLine = ruleLine[1:]
|
ruleLine = ruleLine[1:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
||||||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
return parseErr == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
||||||
@@ -454,5 +342,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
|
|||||||
for len(ruleParts) < 4 {
|
for len(ruleParts) < 4 {
|
||||||
ruleParts = append(ruleParts, 0)
|
ruleParts = append(ruleParts, 0)
|
||||||
}
|
}
|
||||||
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
|
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
|
||||||
}
|
}
|
||||||
@@ -7,15 +7,13 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
func TestConverter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ruleString := `||sagernet.org^$important
|
rules, err := Convert(strings.NewReader(`
|
||||||
@@|sing-box.sagernet.org^$important
|
|
||||||
||example.org^
|
||example.org^
|
||||||
|example.com^
|
|example.com^
|
||||||
example.net^
|
example.net^
|
||||||
@@ -23,9 +21,10 @@ example.net^
|
|||||||
||example.edu.tw^
|
||example.edu.tw^
|
||||||
|example.gov
|
|example.gov
|
||||||
example.arpa
|
example.arpa
|
||||||
@@|sagernet.example.org^
|
@@|sagernet.example.org|
|
||||||
`
|
||sagernet.org^$important
|
||||||
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
@@|sing-box.sagernet.org^$important
|
||||||
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -76,18 +75,15 @@ example.arpa
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}), domain)
|
}), domain)
|
||||||
}
|
}
|
||||||
ruleFromOptions, err := FromOptions(rules)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, ruleString, string(ruleFromOptions))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
::1 localhost #[IPv6]
|
::1 localhost #[IPv6]
|
||||||
0.0.0.0 google.com
|
0.0.0.0 google.com
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimpleHosts(t *testing.T) {
|
func TestSimpleHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
example.com
|
example.com
|
||||||
www.example.org
|
www.example.org
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CloudflareApi struct {
|
|
||||||
client http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloudflareApi(opts ...CloudflareApiOption) *CloudflareApi {
|
|
||||||
api := &CloudflareApi{http.Client{Timeout: 30 * time.Second}}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(api)
|
|
||||||
}
|
|
||||||
return api
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) {
|
|
||||||
request, err := http.NewRequest("POST", "https://api.cloudflareclient.com/v0i1909051800/reg", strings.NewReader(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"{\"install_id\":\"\",\"tos\":\"%s\",\"key\":\"%s\",\"fcm_token\":\"\",\"type\":\"ios\",\"locale\":\"en_US\"}",
|
|
||||||
time.Now().Format("2006-01-02T15:04:05.000Z"),
|
|
||||||
publicKey,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response, err := api.client.Do(request.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("status code is not 200")
|
|
||||||
}
|
|
||||||
content, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
profile := new(CloudflareProfile)
|
|
||||||
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *CloudflareApi) GetProfile(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) {
|
|
||||||
request, err := http.NewRequest("GET", "https://api.cloudflareclient.com/v0i1909051800/reg/"+id, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
request.Header.Set("Authorization", "Bearer "+authToken)
|
|
||||||
response, err := api.client.Do(request.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("status code is not 200")
|
|
||||||
}
|
|
||||||
content, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
profile := new(CloudflareProfile)
|
|
||||||
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile)
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CloudflareApiOption func(api *CloudflareApi)
|
|
||||||
|
|
||||||
func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption {
|
|
||||||
return func(api *CloudflareApi) {
|
|
||||||
api.client.Transport = &http.Transport{
|
|
||||||
DialContext: dialContext,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type CloudflareProfile struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
Account struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
AccountType string `json:"account_type"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
Updated time.Time `json:"updated"`
|
|
||||||
PremiumData int `json:"premium_data"`
|
|
||||||
Quota int `json:"quota"`
|
|
||||||
Usage int `json:"usage"`
|
|
||||||
WARPPlus bool `json:"warp_plus"`
|
|
||||||
ReferralCount int `json:"referral_count"`
|
|
||||||
ReferralRenewalCountdown int `json:"referral_renewal_countdown"`
|
|
||||||
Role string `json:"role"`
|
|
||||||
License string `json:"license"`
|
|
||||||
TTL time.Time `json:"ttl"`
|
|
||||||
} `json:"account"`
|
|
||||||
Config struct {
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
Interface struct {
|
|
||||||
Addresses struct {
|
|
||||||
V4 string `json:"v4"`
|
|
||||||
V6 string `json:"v6"`
|
|
||||||
} `json:"addresses"`
|
|
||||||
} `json:"interface"`
|
|
||||||
Peers []struct {
|
|
||||||
PublicKey string `json:"public_key"`
|
|
||||||
Endpoint struct {
|
|
||||||
V4 string `json:"v4"`
|
|
||||||
V6 string `json:"v6"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Ports []int `json:"ports"`
|
|
||||||
} `json:"endpoint"`
|
|
||||||
} `json:"peers"`
|
|
||||||
Services struct {
|
|
||||||
HTTPProxy string `json:"http_proxy"`
|
|
||||||
} `json:"services"`
|
|
||||||
Metrics struct {
|
|
||||||
Ping int `json:"ping"`
|
|
||||||
Report int `json:"report"`
|
|
||||||
} `json:"metrics"`
|
|
||||||
} `json:"config"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
WARPEnabled bool `json:"warp_enabled"`
|
|
||||||
WaitlistEnabled bool `json:"waitlist_enabled"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
Updated time.Time `json:"updated"`
|
|
||||||
Tos time.Time `json:"tos"`
|
|
||||||
Place int `json:"place"`
|
|
||||||
Locale string `json:"locale"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
InstallID string `json:"install_id"`
|
|
||||||
FcmToken string `json:"fcm_token"`
|
|
||||||
Policy struct {
|
|
||||||
TunnelProtocol string `json:"tunnel_protocol"`
|
|
||||||
} `json:"policy"`
|
|
||||||
}
|
|
||||||
@@ -66,17 +66,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||||
}
|
}
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
|
||||||
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
|
||||||
}
|
|
||||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark > 0 {
|
if options.RoutingMark > 0 {
|
||||||
if !C.IsLinux {
|
|
||||||
return nil, E.New("`routing_mark` is only supported on Linux")
|
|
||||||
}
|
|
||||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
}
|
}
|
||||||
@@ -97,6 +91,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else if networkManager.AutoDetectInterface() {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
@@ -108,10 +106,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
}
|
}
|
||||||
if networkStrategy == nil {
|
|
||||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
|
||||||
defaultNetworkStrategy = true
|
|
||||||
}
|
|
||||||
bindFunc := networkManager.ProtectFunc()
|
bindFunc := networkManager.ProtectFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
@@ -347,17 +341,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) {
|
||||||
|
|||||||
@@ -83,7 +83,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 +95,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 {
|
|
||||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if
|
dnsQueryOptions.Transport = transport
|
||||||
//nolint:staticcheck
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
} else {
|
||||||
//nolint:staticcheck
|
transports := dnsTransport.Transports()
|
||||||
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
if len(transports) < 2 {
|
||||||
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialer = NewResolveDialer(
|
dialer = NewResolveDialer(
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@@ -24,9 +26,7 @@ type slowOpenConn struct {
|
|||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
done chan struct{}
|
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
closeOnce sync.Once
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,6 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
network: network,
|
network: network,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
create: make(chan struct{}),
|
create: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +55,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.ctx.Done():
|
||||||
return 0, os.ErrClosed
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.conn.Read(b)
|
return c.conn.Read(b)
|
||||||
@@ -75,15 +74,12 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Write(b)
|
return c.conn.Write(b)
|
||||||
case <-c.done:
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.err = err
|
c.conn = nil
|
||||||
} else {
|
c.err = E.Cause(err, "dial tcp fast open")
|
||||||
c.conn = conn
|
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@@ -91,13 +87,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
c.closeOnce.Do(func() {
|
return common.Close(c.conn)
|
||||||
close(c.done)
|
|
||||||
if c.conn != nil {
|
|
||||||
c.conn.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
@@ -162,8 +152,8 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.ctx.Done():
|
||||||
return 0, c.err
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn)
|
return bufio.Copy(w, c.conn)
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package interrupt
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,32 +73,3 @@ func (c *PacketConn) WriterReplaceable() bool {
|
|||||||
func (c *PacketConn) Upstream() any {
|
func (c *PacketConn) Upstream() any {
|
||||||
return c.PacketConn
|
return c.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
type SingPacketConn struct {
|
|
||||||
N.PacketConn
|
|
||||||
group *Group
|
|
||||||
element *list.Element[*groupConnItem]
|
|
||||||
}
|
|
||||||
|
|
||||||
/*func (c *SingPacketConn) MarkAsInternal() {
|
|
||||||
c.element.Value.internal = true
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func (c *SingPacketConn) Close() error {
|
|
||||||
c.group.access.Lock()
|
|
||||||
defer c.group.access.Unlock()
|
|
||||||
c.group.connections.Remove(c.element)
|
|
||||||
return c.PacketConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SingPacketConn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SingPacketConn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SingPacketConn) Upstream() any {
|
|
||||||
return c.PacketConn
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,13 +36,6 @@ func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketCo
|
|||||||
return &PacketConn{PacketConn: conn, group: g, element: item}
|
return &PacketConn{PacketConn: conn, group: g, element: item}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool) N.PacketConn {
|
|
||||||
g.access.Lock()
|
|
||||||
defer g.access.Unlock()
|
|
||||||
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
|
|
||||||
return &SingPacketConn{PacketConn: conn, group: g, element: item}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
||||||
g.access.Lock()
|
g.access.Lock()
|
||||||
defer g.access.Unlock()
|
defer g.access.Unlock()
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ type Listener struct {
|
|||||||
disablePacketOutput bool
|
disablePacketOutput bool
|
||||||
setSystemProxy bool
|
setSystemProxy bool
|
||||||
systemProxySOCKS bool
|
systemProxySOCKS bool
|
||||||
tproxy bool
|
|
||||||
|
|
||||||
tcpListener net.Listener
|
tcpListener net.Listener
|
||||||
systemProxy settings.SystemProxy
|
systemProxy settings.SystemProxy
|
||||||
@@ -55,7 +54,6 @@ type Options struct {
|
|||||||
DisablePacketOutput bool
|
DisablePacketOutput bool
|
||||||
SetSystemProxy bool
|
SetSystemProxy bool
|
||||||
SystemProxySOCKS bool
|
SystemProxySOCKS bool
|
||||||
TProxy bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@@ -73,7 +71,6 @@ func New(
|
|||||||
disablePacketOutput: options.DisablePacketOutput,
|
disablePacketOutput: options.DisablePacketOutput,
|
||||||
setSystemProxy: options.SetSystemProxy,
|
setSystemProxy: options.SetSystemProxy,
|
||||||
systemProxySOCKS: options.SystemProxySOCKS,
|
systemProxySOCKS: options.SystemProxySOCKS,
|
||||||
tproxy: options.TProxy,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,14 @@ package listener
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/redir"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
"github.com/metacubex/tfo-go"
|
"github.com/metacubex/tfo-go"
|
||||||
)
|
)
|
||||||
@@ -27,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 {
|
||||||
@@ -53,13 +40,6 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
}
|
}
|
||||||
setMultiPathTCP(&listenConfig)
|
setMultiPathTCP(&listenConfig)
|
||||||
}
|
}
|
||||||
if l.tproxy {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
|
||||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
||||||
if l.listenOptions.TCPFastOpen {
|
if l.listenOptions.TCPFastOpen {
|
||||||
var tfoConfig tfo.ListenConfig
|
var tfoConfig tfo.ListenConfig
|
||||||
|
|||||||
@@ -5,30 +5,17 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/redir"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
var 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
|
||||||
@@ -36,17 +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())
|
||||||
}
|
|
||||||
if l.tproxy {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
|
||||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||||
return 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
|
||||||
@@ -57,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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -164,8 +120,9 @@ func (l *Listener) loopUDPOut() {
|
|||||||
if l.shutdown.Load() && E.IsClosed(err) {
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case <-l.packetOutboundClosed:
|
case <-l.packetOutboundClosed:
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
// rup8(sizeof(xtcpcb_n))
|
// rup8(sizeof(xtcpcb_n))
|
||||||
itemSize += 208
|
itemSize += 208
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackUDPProcess string
|
|
||||||
// skip the first xinpgen(24 bytes) block
|
// skip the first xinpgen(24 bytes) block
|
||||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||||
// offset of xinpcb_n and xsocket_n
|
// offset of xinpcb_n and xsocket_n
|
||||||
@@ -92,34 +90,24 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
flag := buf[inp+44]
|
flag := buf[inp+44]
|
||||||
|
|
||||||
var srcIP netip.Addr
|
var srcIP netip.Addr
|
||||||
srcIsIPv4 := false
|
|
||||||
switch {
|
switch {
|
||||||
case flag&0x1 > 0 && isIPv4:
|
case flag&0x1 > 0 && isIPv4:
|
||||||
// ipv4
|
// ipv4
|
||||||
srcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))
|
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
||||||
srcIsIPv4 = true
|
|
||||||
case flag&0x2 > 0 && !isIPv4:
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
// ipv6
|
// ipv6
|
||||||
srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
|
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip == srcIP {
|
if ip != srcIP {
|
||||||
// xsocket_n.so_last_pid
|
continue
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
|
||||||
return getExecPathFromPID(pid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// udp packet connection may be not equal with srcIP
|
// xsocket_n.so_last_pid
|
||||||
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
return getExecPathFromPID(pid)
|
||||||
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
|
||||||
return fallbackUDPProcess, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ErrNotFound
|
return "", ErrNotFound
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||||
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||||
@@ -20,13 +20,11 @@ func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
|||||||
if err == nil && isIPv6 {
|
if err == nil && isIPv6 {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
||||||
}
|
}
|
||||||
if isUDP {
|
if err == nil {
|
||||||
if err == nil {
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
}
|
||||||
}
|
if err == nil && isIPv6 {
|
||||||
if err == nil && isIPv6 {
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,18 +31,13 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = "BitTorrent protocol"
|
|
||||||
var protocol [19]byte
|
var protocol [19]byte
|
||||||
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 E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if n < 19 {
|
if string(protocol[:]) != "BitTorrent protocol" {
|
||||||
return ErrNeedMoreData
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.Protocol = C.ProtocolBitTorrent
|
metadata.Protocol = C.ProtocolBitTorrent
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -20,36 +20,22 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if length < 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
|
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||||
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
|
||||||
packet := buffer.Bytes()
|
|
||||||
if n > 2 && packet[2]&0x80 != 0 { // QR
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
for i := 6; i < 10; i++ {
|
|
||||||
// ANCOUNT, NSCOUNT
|
|
||||||
if n > i && packet[i] != 0 {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
return DomainNameQuery(readCtx, metadata, 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
|
||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolDNS
|
metadata.Protocol = C.ProtocolDNS
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package sniff_test
|
package sniff_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -22,32 +21,3 @@ func TestSniffDNS(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSniffStreamDNS(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
|
||||||
require.NoError(t, err)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffIncompleteStreamDNS(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
query, err := hex.DecodeString("001e740701000001000000000000")
|
|
||||||
require.NoError(t, err)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffNotStreamDNS(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
query, err := hex.DecodeString("001e740701000000000000000000")
|
|
||||||
require.NoError(t, err)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
|
||||||
require.NotEmpty(t, err)
|
|
||||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
}
|
}
|
||||||
sniffError = E.Errors(sniffError, err)
|
sniffError = E.Errors(sniffError, err)
|
||||||
}
|
}
|
||||||
if !errors.Is(sniffError, ErrNeedMoreData) {
|
if !errors.Is(err, ErrNeedMoreData) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
const sshPrefix = "SSH-2.0-"
|
const sshPrefix = "SSH-2.0-"
|
||||||
bReader := bufio.NewReader(reader)
|
bReader := bufio.NewReader(reader)
|
||||||
prefix, err := bReader.Peek(len(sshPrefix))
|
prefix, err := bReader.Peek(len(sshPrefix))
|
||||||
if string(prefix[:]) != sshPrefix[:len(prefix)] {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
|
} else if string(prefix) != sshPrefix {
|
||||||
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
fistLine, _, err := bReader.ReadLine()
|
fistLine, _, err := bReader.ReadLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -24,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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -215,15 +215,16 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
case ruleItemWIFIBSSID:
|
case ruleItemWIFIBSSID:
|
||||||
rule.WIFIBSSID, err = readRuleItemString(reader)
|
rule.WIFIBSSID, err = readRuleItemString(reader)
|
||||||
case ruleItemAdGuardDomain:
|
case ruleItemAdGuardDomain:
|
||||||
|
if recover {
|
||||||
|
err = E.New("unable to decompile binary AdGuard rules to rule-set")
|
||||||
|
return
|
||||||
|
}
|
||||||
var matcher *domain.AdGuardMatcher
|
var matcher *domain.AdGuardMatcher
|
||||||
matcher, err = domain.ReadAdGuardMatcher(reader)
|
matcher, err = domain.ReadAdGuardMatcher(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rule.AdGuardDomainMatcher = matcher
|
rule.AdGuardDomainMatcher = matcher
|
||||||
if recover {
|
|
||||||
rule.AdGuardDomain = matcher.Dump()
|
|
||||||
}
|
|
||||||
case ruleItemNetworkType:
|
case ruleItemNetworkType:
|
||||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||||
case ruleItemNetworkIsExpensive:
|
case ruleItemNetworkIsExpensive:
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"os"
|
||||||
"strings"
|
"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"
|
||||||
"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/logger"
|
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
@@ -37,38 +37,7 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type acmeLogWriter struct {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(logLine, "error: "):
|
|
||||||
w.logger.Error(logLine[7:])
|
|
||||||
case strings.HasPrefix(logLine, "warn: "):
|
|
||||||
w.logger.Warn(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "info: "):
|
|
||||||
w.logger.Info(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "debug: "):
|
|
||||||
w.logger.Debug(logLine[7:])
|
|
||||||
default:
|
|
||||||
w.logger.Debug(logLine)
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encoderConfig() zapcore.EncoderConfig {
|
|
||||||
config := zap.NewProductionEncoderConfig()
|
|
||||||
config.TimeKey = zapcore.OmitKey
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case "", "letsencrypt":
|
case "", "letsencrypt":
|
||||||
@@ -89,15 +58,14 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
zapLogger := zap.New(zapcore.NewCore(
|
|
||||||
zapcore.NewConsoleEncoder(encoderConfig()),
|
|
||||||
&acmeLogWriter{logger: logger},
|
|
||||||
zap.DebugLevel,
|
|
||||||
))
|
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
DefaultServerName: options.DefaultServerName,
|
DefaultServerName: options.DefaultServerName,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Logger: zapLogger,
|
Logger: zap.New(zapcore.NewCore(
|
||||||
|
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
os.Stderr,
|
||||||
|
zap.InfoLevel,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
acmeConfig := certmagic.ACMEIssuer{
|
acmeConfig := certmagic.ACMEIssuer{
|
||||||
CA: acmeServer,
|
CA: acmeServer,
|
||||||
@@ -107,7 +75,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
Logger: zapLogger,
|
Logger: config.Logger,
|
||||||
}
|
}
|
||||||
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
||||||
var solver certmagic.DNS01Solver
|
var solver certmagic.DNS01Solver
|
||||||
@@ -135,7 +103,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
},
|
},
|
||||||
Logger: zapLogger,
|
|
||||||
})
|
})
|
||||||
config = certmagic.New(cache, *config)
|
config = certmagic.New(cache, *config)
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ 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"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -25,7 +23,7 @@ import (
|
|||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
var echConfig []byte
|
var echConfig []byte
|
||||||
if len(options.ECH.Config) > 0 {
|
if len(options.ECH.Config) > 0 {
|
||||||
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
||||||
@@ -45,13 +43,10 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
|
|||||||
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
return nil, E.New("invalid ECH configs pem")
|
return nil, E.New("invalid ECH configs pem")
|
||||||
}
|
}
|
||||||
clientConfig.SetECHConfigList(block.Bytes)
|
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return clientConfig, nil
|
return &STDClientConfig{tlsConfig}, nil
|
||||||
} else {
|
} else {
|
||||||
return &ECHClientConfig{
|
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil
|
||||||
ECHCapableConfig: clientConfig,
|
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,37 +97,20 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type STDECHClientConfig struct {
|
||||||
ECHCapableConfig
|
STDClientConfig
|
||||||
access sync.Mutex
|
dnsRouter adapter.DNSRouter
|
||||||
dnsRouter adapter.DNSRouter
|
|
||||||
lastTTL time.Duration
|
|
||||||
lastUpdate time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) 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 *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
|
||||||
s.access.Lock()
|
|
||||||
defer s.access.Unlock()
|
|
||||||
if len(s.ECHConfigList()) == 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,
|
||||||
},
|
},
|
||||||
Question: []mDNS.Question{
|
Question: []mDNS.Question{
|
||||||
{
|
{
|
||||||
Name: mDNS.Fqdn(s.ServerName()),
|
Name: mDNS.Fqdn(s.config.ServerName),
|
||||||
Qtype: mDNS.TypeHTTPS,
|
Qtype: mDNS.TypeHTTPS,
|
||||||
Qclass: mDNS.ClassINET,
|
Qclass: mDNS.ClassINET,
|
||||||
},
|
},
|
||||||
@@ -155,23 +133,29 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode ECH config")
|
return nil, E.Cause(err, "decode ECH config")
|
||||||
}
|
}
|
||||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
s.lastUpdate = time.Now()
|
|
||||||
s.SetECHConfigList(echConfigList)
|
|
||||||
break match
|
break match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(s.ECHConfigList()) == 0 {
|
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 *ECHClientConfig) Clone() Config {
|
func (s *STDECHClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), 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) {
|
||||||
|
|||||||
155
common/tls/ech_keygen.go
Normal file
155
common/tls/ech_keygen.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
|
||||||
|
cipherSuites := []echCipherSuite{
|
||||||
|
{
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_AES128GCM,
|
||||||
|
}, {
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_ChaCha20Poly1305,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
keyConfig := []myECHKeyConfig{
|
||||||
|
{id: 0, kem: hpke.KEM_X25519_HKDF_SHA256},
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var configBuffer bytes.Buffer
|
||||||
|
var totalLen uint16
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
totalLen += uint16(len(keyPair.rawConf))
|
||||||
|
}
|
||||||
|
binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
configBuffer.Write(keyPair.rawConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyBuffer bytes.Buffer
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
keyBuffer.Write(keyPair.rawKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
|
||||||
|
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type echKeyConfigPair struct {
|
||||||
|
id uint8
|
||||||
|
rawKey []byte
|
||||||
|
conf myECHKeyConfig
|
||||||
|
rawConf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type echCipherSuite struct {
|
||||||
|
kdf hpke.KDF
|
||||||
|
aead hpke.AEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
type myECHKeyConfig struct {
|
||||||
|
id uint8
|
||||||
|
kem hpke.KEM
|
||||||
|
seed []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
|
||||||
|
be := binary.BigEndian
|
||||||
|
// prepare for future update
|
||||||
|
if version != 0xfe0d {
|
||||||
|
return nil, E.New("unsupported ECH version", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
suiteBuf := make([]byte, 0, len(suite)*4+2)
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
|
||||||
|
for _, s := range suite {
|
||||||
|
if !s.kdf.IsValid() || !s.aead.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE cipher suite")
|
||||||
|
}
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf))
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead))
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := []echKeyConfigPair{}
|
||||||
|
for _, c := range conf {
|
||||||
|
pair := echKeyConfigPair{}
|
||||||
|
pair.id = c.id
|
||||||
|
pair.conf = c
|
||||||
|
|
||||||
|
if !c.kem.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE KEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
kpGenerator := c.kem.Scheme().GenerateKeyPair
|
||||||
|
if len(c.seed) > 0 {
|
||||||
|
kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) {
|
||||||
|
pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed)
|
||||||
|
return pub, sec, nil
|
||||||
|
}
|
||||||
|
if len(c.seed) < c.kem.Scheme().PrivateKeySize() {
|
||||||
|
return nil, E.New("HPKE KEM seed too short")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, sec, err := kpGenerator()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "generate ECH config key pair")
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
b = be.AppendUint16(b, version)
|
||||||
|
b = be.AppendUint16(b, 0) // length field
|
||||||
|
// contents
|
||||||
|
// key config
|
||||||
|
b = append(b, c.id)
|
||||||
|
b = be.AppendUint16(b, uint16(c.kem))
|
||||||
|
pubBuf, err := pub.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH public key")
|
||||||
|
}
|
||||||
|
b = be.AppendUint16(b, uint16(len(pubBuf)))
|
||||||
|
b = append(b, pubBuf...)
|
||||||
|
|
||||||
|
b = append(b, suiteBuf...)
|
||||||
|
// end key config
|
||||||
|
// max name len, not supported
|
||||||
|
b = append(b, 0)
|
||||||
|
// server name
|
||||||
|
b = append(b, byte(len(serverName)))
|
||||||
|
b = append(b, []byte(serverName)...)
|
||||||
|
// extensions, not supported
|
||||||
|
b = be.AppendUint16(b, 0)
|
||||||
|
|
||||||
|
be.PutUint16(b[2:], uint16(len(b)-4))
|
||||||
|
|
||||||
|
pair.rawConf = b
|
||||||
|
|
||||||
|
secBuf, err := sec.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH private key")
|
||||||
|
}
|
||||||
|
sk := []byte{}
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
|
sk = append(sk, secBuf...)
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(b)))
|
||||||
|
sk = append(sk, b...)
|
||||||
|
pair.rawKey = sk
|
||||||
|
|
||||||
|
pairs = append(pairs, pair)
|
||||||
|
}
|
||||||
|
return pairs, nil
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdh"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/pem"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ECHCapableConfig interface {
|
|
||||||
Config
|
|
||||||
ECHConfigList() []byte
|
|
||||||
SetECHConfigList([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {
|
|
||||||
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configBuilder := cryptobyte.NewBuilder(nil)
|
|
||||||
configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echConfig)
|
|
||||||
})
|
|
||||||
configBytes, err := configBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
keyBuilder := cryptobyte.NewBuilder(nil)
|
|
||||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echKey.Bytes())
|
|
||||||
})
|
|
||||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echConfig)
|
|
||||||
})
|
|
||||||
keyBytes, err := keyBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes}))
|
|
||||||
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) {
|
|
||||||
const extensionEncryptedClientHello = 0xfe0d
|
|
||||||
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
|
||||||
const KDF_HKDF_SHA256 = 0x0001
|
|
||||||
builder := cryptobyte.NewBuilder(nil)
|
|
||||||
builder.AddUint16(extensionEncryptedClientHello)
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddUint8(id)
|
|
||||||
|
|
||||||
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(pubKey)
|
|
||||||
})
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
const (
|
|
||||||
AEAD_AES_128_GCM = 0x0001
|
|
||||||
AEAD_AES_256_GCM = 0x0002
|
|
||||||
AEAD_ChaCha20Poly1305 = 0x0003
|
|
||||||
)
|
|
||||||
for _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} {
|
|
||||||
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
|
|
||||||
builder.AddUint16(aeadID)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
builder.AddUint8(maxNameLen)
|
|
||||||
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes([]byte(publicName))
|
|
||||||
})
|
|
||||||
builder.AddUint16(0) // extensions
|
|
||||||
})
|
|
||||||
return builder.Bytes()
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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."
|
|
||||||
@@ -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"
|
||||||
)
|
)
|
||||||
@@ -74,7 +73,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
|||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID}, nil
|
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
@@ -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()
|
||||||
|
|
||||||
@@ -119,6 +113,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 +157,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 +173,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`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,60 +7,43 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
ctx context.Context
|
config *tls.Config
|
||||||
config *tls.Config
|
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) ServerName() string {
|
func (s *STDClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return s.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetServerName(serverName string) {
|
func (s *STDClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
s.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) NextProtos() []string {
|
func (s *STDClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return s.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetNextProtos(nextProto []string) {
|
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
|
||||||
c.config.NextProtos = nextProto
|
s.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Config() (*STDConfig, error) {
|
func (s *STDClientConfig) Config() (*STDConfig, error) {
|
||||||
return c.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return tls.Client(conn, s.config), nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return tls.Client(conn, c.config), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
@@ -77,7 +60,9 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
@@ -86,16 +71,12 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
tlsConfig.InsecureSkipVerify = true
|
tlsConfig.InsecureSkipVerify = true
|
||||||
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
||||||
verifyOptions := x509.VerifyOptions{
|
verifyOptions := x509.VerifyOptions{
|
||||||
Roots: tlsConfig.RootCAs,
|
|
||||||
DNSName: serverName,
|
DNSName: serverName,
|
||||||
Intermediates: x509.NewCertPool(),
|
Intermediates: x509.NewCertPool(),
|
||||||
}
|
}
|
||||||
for _, cert := range state.PeerCertificates[1:] {
|
for _, cert := range state.PeerCertificates[1:] {
|
||||||
verifyOptions.Intermediates.AddCert(cert)
|
verifyOptions.Intermediates.AddCert(cert)
|
||||||
}
|
}
|
||||||
if tlsConfig.Time != nil {
|
|
||||||
verifyOptions.CurrentTime = tlsConfig.Time()
|
|
||||||
}
|
|
||||||
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -146,10 +127,8 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return parseECHClientConfig(ctx, stdConfig, options)
|
return parseECHClientConfig(ctx, options, &tlsConfig)
|
||||||
} else {
|
|
||||||
return stdConfig, nil
|
|
||||||
}
|
}
|
||||||
|
return &STDClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,10 @@ type TimeServiceWrapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
||||||
return func() time.Time {
|
if w.TimeService == nil {
|
||||||
if w.TimeService != nil {
|
return nil
|
||||||
return w.TimeService.TimeFunc()()
|
|
||||||
} else {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return w.TimeService.TimeFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TimeServiceWrapper) Upstream() any {
|
func (w *TimeServiceWrapper) Upstream() any {
|
||||||
|
|||||||
@@ -8,77 +8,62 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UTLSClientConfig struct {
|
type UTLSClientConfig struct {
|
||||||
ctx context.Context
|
config *utls.Config
|
||||||
config *utls.Config
|
id utls.ClientHelloID
|
||||||
id utls.ClientHelloID
|
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ServerName() string {
|
func (e *UTLSClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return e.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetServerName(serverName string) {
|
func (e *UTLSClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
e.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) NextProtos() []string {
|
func (e *UTLSClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return e.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
||||||
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
||||||
nextProto = append(nextProto, "http/1.1")
|
nextProto = append(nextProto, "http/1.1")
|
||||||
}
|
}
|
||||||
c.config.NextProtos = nextProto
|
e.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Config() (*STDConfig, error) {
|
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
||||||
return nil, E.New("unsupported usage for uTLS")
|
return nil, E.New("unsupported usage for uTLS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
c.config.SessionIDGenerator = generator
|
e.config.SessionIDGenerator = generator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Clone() Config {
|
func (e *UTLSClientConfig) Clone() Config {
|
||||||
return &UTLSClientConfig{
|
return &UTLSClientConfig{
|
||||||
c.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment,
|
config: e.config.Clone(),
|
||||||
|
id: e.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
type utlsConnWrapper struct {
|
||||||
*utls.UConn
|
*utls.UConn
|
||||||
}
|
}
|
||||||
@@ -131,12 +116,14 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
|||||||
return c.UConn.HandshakeContext(ctx)
|
return c.UConn.HandshakeContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
serverName = serverAddress
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
|
serverName = serverAddress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if serverName == "" && !options.Insecure {
|
if serverName == "" && !options.Insecure {
|
||||||
return nil, E.New("missing server_name or insecure=true")
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
@@ -145,16 +132,15 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
tlsConfig.InsecureSkipVerify = options.Insecure
|
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||||
} else if options.DisableSNI {
|
} else if options.DisableSNI {
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
return nil, E.New("disable_sni is unsupported in uTLS")
|
||||||
return nil, E.New("disable_sni is unsupported in reality")
|
|
||||||
}
|
|
||||||
tlsConfig.InsecureServerNameToVerify = serverName
|
|
||||||
}
|
}
|
||||||
if len(options.ALPN) > 0 {
|
if len(options.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = options.ALPN
|
tlsConfig.NextProtos = options.ALPN
|
||||||
@@ -206,15 +192,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
|
||||||
return nil, E.New("Reality is conflict with ECH")
|
|
||||||
}
|
|
||||||
return parseECHClientConfig(ctx, uConfig, options)
|
|
||||||
} else {
|
|
||||||
return uConfig, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -242,7 +220,7 @@ func init() {
|
|||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq", "chrome_pq_psk":
|
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "chrome", "":
|
case "chrome", "":
|
||||||
return utls.HelloChrome_Auto, nil
|
return utls.HelloChrome_Auto, nil
|
||||||
|
|||||||
@@ -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,15 +1,12 @@
|
|||||||
package tf
|
package tf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
@@ -20,24 +17,17 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
splitPacket bool
|
|
||||||
splitRecord bool
|
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||||
if fallbackDelay == 0 {
|
|
||||||
fallbackDelay = C.TLSFragmentFallbackDelay
|
|
||||||
}
|
|
||||||
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,
|
||||||
splitPacket: splitPacket,
|
|
||||||
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) {
|
||||||
@@ -45,14 +35,12 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
c.firstPacketWritten = true
|
c.firstPacketWritten = true
|
||||||
}()
|
}()
|
||||||
serverName := IndexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if c.splitPacket {
|
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, ".")
|
||||||
@@ -73,50 +61,26 @@ 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) {
|
||||||
if c.splitPacket {
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
buffer.Reset()
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
payloadLen := uint16(len(payload))
|
} else {
|
||||||
buffer.Write(b[:3])
|
_, err = c.Conn.Write(payload)
|
||||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
if err != nil {
|
||||||
buffer.Write(payload)
|
return
|
||||||
if c.splitPacket {
|
|
||||||
payload = buffer.Bytes()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.splitPacket {
|
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = c.Conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.splitRecord && !c.splitPacket {
|
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
|
|||||||
@@ -1,42 +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(), true, 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(), false, true, 0), &tls.Config{
|
|
||||||
ServerName: "www.cloudflare.com",
|
|
||||||
})
|
|
||||||
require.NoError(t, tlsConn.Handshake())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLS2Fragment(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, true, 0), &tls.Config{
|
|
||||||
ServerName: "www.cloudflare.com",
|
|
||||||
})
|
|
||||||
require.NoError(t, tlsConn.Handshake())
|
|
||||||
}
|
|
||||||
@@ -22,13 +22,13 @@ const (
|
|||||||
tls13 uint16 = 0x0304
|
tls13 uint16 = 0x0304
|
||||||
)
|
)
|
||||||
|
|
||||||
type MyServerName struct {
|
type myServerName struct {
|
||||||
Index int
|
Index int
|
||||||
Length int
|
Length int
|
||||||
ServerName string
|
ServerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func IndexTLSServerName(payload []byte) *MyServerName {
|
func indexTLSServerName(payload []byte) *myServerName {
|
||||||
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -36,48 +36,47 @@ func IndexTLSServerName(payload []byte) *MyServerName {
|
|||||||
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen:])
|
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
|
||||||
if serverName == nil {
|
if serverName == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName.Index += recordLayerHeaderLen
|
serverName.Length += recordLayerHeaderLen
|
||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
||||||
if len(handshake) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if handshake[0] != handshakeType {
|
if hs[0] != handshakeType {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
handshakeLen := uint32(handshake[1])<<16 | uint32(handshake[2])<<8 | uint32(handshake[3])
|
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
||||||
if len(handshake[4:]) != int(handshakeLen) {
|
if len(hs[4:]) != int(handshakeLen) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tlsVersion := uint16(handshake[4])<<8 | uint16(handshake[5])
|
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
||||||
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sessionIDLen := handshake[38]
|
sessionIDLen := hs[38]
|
||||||
currentIndex := handshakeHeaderLen + randomDataLen + sessionIDHeaderLen + int(sessionIDLen)
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
||||||
if len(handshake) < currentIndex {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cipherSuites := handshake[currentIndex:]
|
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
||||||
if len(cipherSuites) < cipherSuiteHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
csLen := uint16(cipherSuites[0])<<8 | uint16(cipherSuites[1])
|
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||||
if len(cipherSuites) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
compressMethodLen := uint16(cipherSuites[cipherSuiteHeaderLen+int(csLen)])
|
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||||
currentIndex += cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||||
if len(handshake) < currentIndex {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName := indexTLSServerNameFromExtensions(handshake[currentIndex:])
|
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
||||||
|
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
|
||||||
if serverName == nil {
|
if serverName == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -85,7 +84,7 @@ func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
|||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
||||||
if len(exs) == 0 {
|
if len(exs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,8 +118,7 @@ func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
|||||||
}
|
}
|
||||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||||
sex = sex[sniExtensionHeaderLen:]
|
sex = sex[sniExtensionHeaderLen:]
|
||||||
|
return &myServerName{
|
||||||
return &MyServerName{
|
|
||||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||||
Length: int(sniLen),
|
Length: int(sniLen),
|
||||||
ServerName: string(sex),
|
ServerName: string(sex),
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
package tf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIndexTLSServerName(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
payload, err := hex.DecodeString("16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100")
|
|
||||||
require.NoError(t, err)
|
|
||||||
serverName := tf.IndexTLSServerName(payload)
|
|
||||||
require.NotNil(t, serverName)
|
|
||||||
require.Equal(t, serverName.ServerName, string(payload[serverName.Index:serverName.Index+serverName.Length]))
|
|
||||||
require.Equal(t, "github.com", serverName.ServerName)
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package urltest
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type contextKeyIsUnifiedDelay struct{}
|
|
||||||
|
|
||||||
func ContextWithIsUnifiedDelay(ctx context.Context) context.Context {
|
|
||||||
return context.WithValue(ctx, contextKeyIsUnifiedDelay{}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsUnifiedDelayFromContext(ctx context.Context) bool {
|
|
||||||
return ctx.Value(contextKeyIsUnifiedDelay{}) != nil
|
|
||||||
}
|
|
||||||
@@ -126,15 +126,6 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if IsUnifiedDelayFromContext(ctx) {
|
|
||||||
second := time.Now()
|
|
||||||
resp, err = client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
start = second
|
|
||||||
}
|
|
||||||
t = uint16(time.Since(start) / time.Millisecond)
|
t = uint16(time.Since(start) / time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,337 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/bytespool"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/net"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Size of a regular buffer.
|
|
||||||
Size = 8192
|
|
||||||
)
|
|
||||||
|
|
||||||
var zero = [Size * 10]byte{0}
|
|
||||||
|
|
||||||
var pool = bytespool.GetPool(Size)
|
|
||||||
|
|
||||||
// ownership represents the data owner of the buffer.
|
|
||||||
type ownership uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
managed ownership = iota
|
|
||||||
unmanaged
|
|
||||||
bytespools
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles
|
|
||||||
// the buffer into an internal buffer pool, in order to recreate a buffer more
|
|
||||||
// quickly.
|
|
||||||
type Buffer struct {
|
|
||||||
v []byte
|
|
||||||
start int32
|
|
||||||
end int32
|
|
||||||
ownership ownership
|
|
||||||
UDP *net.Destination
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a Buffer with 0 length and 8K capacity, managed.
|
|
||||||
func New() *Buffer {
|
|
||||||
buf := pool.Get().([]byte)
|
|
||||||
if cap(buf) >= Size {
|
|
||||||
buf = buf[:Size]
|
|
||||||
} else {
|
|
||||||
buf = make([]byte, Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Buffer{
|
|
||||||
v: buf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExisted creates a standard size Buffer with an existed bytearray, managed.
|
|
||||||
func NewExisted(b []byte) *Buffer {
|
|
||||||
if cap(b) < Size {
|
|
||||||
panic("Invalid buffer")
|
|
||||||
}
|
|
||||||
|
|
||||||
oLen := len(b)
|
|
||||||
if oLen < Size {
|
|
||||||
b = b[:Size]
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Buffer{
|
|
||||||
v: b,
|
|
||||||
end: int32(oLen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromBytes creates a Buffer with an existed bytearray, unmanaged.
|
|
||||||
func FromBytes(b []byte) *Buffer {
|
|
||||||
return &Buffer{
|
|
||||||
v: b,
|
|
||||||
end: int32(len(b)),
|
|
||||||
ownership: unmanaged,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StackNew creates a new Buffer object on stack, managed.
|
|
||||||
// This method is for buffers that is released in the same function.
|
|
||||||
func StackNew() Buffer {
|
|
||||||
buf := pool.Get().([]byte)
|
|
||||||
if cap(buf) >= Size {
|
|
||||||
buf = buf[:Size]
|
|
||||||
} else {
|
|
||||||
buf = make([]byte, Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer{
|
|
||||||
v: buf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's.
|
|
||||||
func NewWithSize(size int32) *Buffer {
|
|
||||||
return &Buffer{
|
|
||||||
v: bytespool.Alloc(size),
|
|
||||||
ownership: bytespools,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release recycles the buffer into an internal buffer pool.
|
|
||||||
func (b *Buffer) Release() {
|
|
||||||
if b == nil || b.v == nil || b.ownership == unmanaged {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p := b.v
|
|
||||||
b.v = nil
|
|
||||||
b.Clear()
|
|
||||||
|
|
||||||
switch b.ownership {
|
|
||||||
case managed:
|
|
||||||
if cap(p) == Size {
|
|
||||||
pool.Put(p)
|
|
||||||
}
|
|
||||||
case bytespools:
|
|
||||||
bytespool.Free(p)
|
|
||||||
}
|
|
||||||
b.UDP = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear clears the content of the buffer, results an empty buffer with
|
|
||||||
// Len() = 0.
|
|
||||||
func (b *Buffer) Clear() {
|
|
||||||
b.start = 0
|
|
||||||
b.end = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Byte returns the bytes at index.
|
|
||||||
func (b *Buffer) Byte(index int32) byte {
|
|
||||||
return b.v[b.start+index]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetByte sets the byte value at index.
|
|
||||||
func (b *Buffer) SetByte(index int32, value byte) {
|
|
||||||
b.v[b.start+index] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the content bytes of this Buffer.
|
|
||||||
func (b *Buffer) Bytes() []byte {
|
|
||||||
return b.v[b.start:b.end]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend increases the buffer size by n bytes, and returns the extended part.
|
|
||||||
// It panics if result size is larger than buf.Size.
|
|
||||||
func (b *Buffer) Extend(n int32) []byte {
|
|
||||||
end := b.end + n
|
|
||||||
if end > int32(len(b.v)) {
|
|
||||||
panic("extending out of bound")
|
|
||||||
}
|
|
||||||
ext := b.v[b.end:end]
|
|
||||||
b.end = end
|
|
||||||
copy(ext, zero[:])
|
|
||||||
return ext
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesRange returns a slice of this buffer with given from and to boundary.
|
|
||||||
func (b *Buffer) BytesRange(from, to int32) []byte {
|
|
||||||
if from < 0 {
|
|
||||||
from += b.Len()
|
|
||||||
}
|
|
||||||
if to < 0 {
|
|
||||||
to += b.Len()
|
|
||||||
}
|
|
||||||
return b.v[b.start+from : b.start+to]
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesFrom returns a slice of this Buffer starting from the given position.
|
|
||||||
func (b *Buffer) BytesFrom(from int32) []byte {
|
|
||||||
if from < 0 {
|
|
||||||
from += b.Len()
|
|
||||||
}
|
|
||||||
return b.v[b.start+from : b.end]
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesTo returns a slice of this Buffer from start to the given position.
|
|
||||||
func (b *Buffer) BytesTo(to int32) []byte {
|
|
||||||
if to < 0 {
|
|
||||||
to += b.Len()
|
|
||||||
}
|
|
||||||
if to < 0 {
|
|
||||||
to = 0
|
|
||||||
}
|
|
||||||
return b.v[b.start : b.start+to]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check makes sure that 0 <= b.start <= b.end.
|
|
||||||
func (b *Buffer) Check() {
|
|
||||||
if b.start < 0 {
|
|
||||||
b.start = 0
|
|
||||||
}
|
|
||||||
if b.end < 0 {
|
|
||||||
b.end = 0
|
|
||||||
}
|
|
||||||
if b.start > b.end {
|
|
||||||
b.start = b.end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize cuts the buffer at the given position.
|
|
||||||
func (b *Buffer) Resize(from, to int32) {
|
|
||||||
oldEnd := b.end
|
|
||||||
if from < 0 {
|
|
||||||
from += b.Len()
|
|
||||||
}
|
|
||||||
if to < 0 {
|
|
||||||
to += b.Len()
|
|
||||||
}
|
|
||||||
if to < from {
|
|
||||||
panic("Invalid slice")
|
|
||||||
}
|
|
||||||
b.end = b.start + to
|
|
||||||
b.start += from
|
|
||||||
b.Check()
|
|
||||||
if b.end > oldEnd {
|
|
||||||
copy(b.v[oldEnd:b.end], zero[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance cuts the buffer at the given position.
|
|
||||||
func (b *Buffer) Advance(from int32) {
|
|
||||||
if from < 0 {
|
|
||||||
from += b.Len()
|
|
||||||
}
|
|
||||||
b.start += from
|
|
||||||
b.Check()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the length of the buffer content.
|
|
||||||
func (b *Buffer) Len() int32 {
|
|
||||||
if b == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return b.end - b.start
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cap returns the capacity of the buffer content.
|
|
||||||
func (b *Buffer) Cap() int32 {
|
|
||||||
if b == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int32(len(b.v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if the buffer is empty.
|
|
||||||
func (b *Buffer) IsEmpty() bool {
|
|
||||||
return b.Len() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFull returns true if the buffer has no more room to grow.
|
|
||||||
func (b *Buffer) IsFull() bool {
|
|
||||||
return b != nil && b.end == int32(len(b.v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements Write method in io.Writer.
|
|
||||||
func (b *Buffer) Write(data []byte) (int, error) {
|
|
||||||
nBytes := copy(b.v[b.end:], data)
|
|
||||||
b.end += int32(nBytes)
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteByte writes a single byte into the buffer.
|
|
||||||
func (b *Buffer) WriteByte(v byte) error {
|
|
||||||
if b.IsFull() {
|
|
||||||
return E.New("buffer full")
|
|
||||||
}
|
|
||||||
b.v[b.end] = v
|
|
||||||
b.end++
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteString implements io.StringWriter.
|
|
||||||
func (b *Buffer) WriteString(s string) (int, error) {
|
|
||||||
return b.Write([]byte(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadByte implements io.ByteReader
|
|
||||||
func (b *Buffer) ReadByte() (byte, error) {
|
|
||||||
if b.start == b.end {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
nb := b.v[b.start]
|
|
||||||
b.start++
|
|
||||||
return nb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadBytes implements bufio.Reader.ReadBytes
|
|
||||||
func (b *Buffer) ReadBytes(length int32) ([]byte, error) {
|
|
||||||
if b.end-b.start < length {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
nb := b.v[b.start : b.start+length]
|
|
||||||
b.start += length
|
|
||||||
return nb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements io.Reader.Read().
|
|
||||||
func (b *Buffer) Read(data []byte) (int, error) {
|
|
||||||
if b.Len() == 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
nBytes := copy(data, b.v[b.start:b.end])
|
|
||||||
if int32(nBytes) == b.Len() {
|
|
||||||
b.Clear()
|
|
||||||
} else {
|
|
||||||
b.start += int32(nBytes)
|
|
||||||
}
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom implements io.ReaderFrom.
|
|
||||||
func (b *Buffer) ReadFrom(reader io.Reader) (int64, error) {
|
|
||||||
n, err := reader.Read(b.v[b.end:])
|
|
||||||
b.end += int32(n)
|
|
||||||
return int64(n), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFullFrom reads exact size of bytes from given reader, or until error occurs.
|
|
||||||
func (b *Buffer) ReadFullFrom(reader io.Reader, size int32) (int64, error) {
|
|
||||||
end := b.end + size
|
|
||||||
if end > int32(len(b.v)) {
|
|
||||||
v := end
|
|
||||||
return 0, E.New("out of bound: ", v)
|
|
||||||
}
|
|
||||||
n, err := io.ReadFull(reader, b.v[b.end:end])
|
|
||||||
b.end += int32(n)
|
|
||||||
return int64(n), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string form of this Buffer.
|
|
||||||
func (b *Buffer) String() string {
|
|
||||||
return string(b.Bytes())
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/errors"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dataHandler func(MultiBuffer)
|
|
||||||
|
|
||||||
type copyHandler struct {
|
|
||||||
onData []dataHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// SizeCounter is for counting bytes copied by Copy().
|
|
||||||
type SizeCounter struct {
|
|
||||||
Size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyOption is an option for copying data.
|
|
||||||
type CopyOption func(*copyHandler)
|
|
||||||
|
|
||||||
// UpdateActivity is a CopyOption to update activity on each data copy operation.
|
|
||||||
func UpdateActivity(timer signal.ActivityUpdater) CopyOption {
|
|
||||||
return func(handler *copyHandler) {
|
|
||||||
handler.onData = append(handler.onData, func(MultiBuffer) {
|
|
||||||
timer.Update()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountSize is a CopyOption that sums the total size of data copied into the given SizeCounter.
|
|
||||||
func CountSize(sc *SizeCounter) CopyOption {
|
|
||||||
return func(handler *copyHandler) {
|
|
||||||
handler.onData = append(handler.onData, func(b MultiBuffer) {
|
|
||||||
sc.Size += int64(b.Len())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type readError struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e readError) Error() string {
|
|
||||||
return e.error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e readError) Unwrap() error {
|
|
||||||
return e.error
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReadError returns true if the error in Copy() comes from reading.
|
|
||||||
func IsReadError(err error) bool {
|
|
||||||
_, ok := err.(readError)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeError struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e writeError) Error() string {
|
|
||||||
return e.error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e writeError) Unwrap() error {
|
|
||||||
return e.error
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWriteError returns true if the error in Copy() comes from writing.
|
|
||||||
func IsWriteError(err error) bool {
|
|
||||||
_, ok := err.(writeError)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyInternal(reader Reader, writer Writer, handler *copyHandler) error {
|
|
||||||
for {
|
|
||||||
buffer, err := reader.ReadMultiBuffer()
|
|
||||||
if !buffer.IsEmpty() {
|
|
||||||
for _, handler := range handler.onData {
|
|
||||||
handler(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
|
|
||||||
return writeError{werr}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return readError{err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy dumps all payload from reader to writer or stops when an error occurs. It returns nil when EOF.
|
|
||||||
func Copy(reader Reader, writer Writer, options ...CopyOption) error {
|
|
||||||
var handler copyHandler
|
|
||||||
for _, option := range options {
|
|
||||||
option(&handler)
|
|
||||||
}
|
|
||||||
err := copyInternal(reader, writer, &handler)
|
|
||||||
if err != nil && errors.Cause(err) != io.EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrNotTimeoutReader = E.New("not a TimeoutReader")
|
|
||||||
|
|
||||||
func CopyOnceTimeout(reader Reader, writer Writer, timeout time.Duration) error {
|
|
||||||
timeoutReader, ok := reader.(TimeoutReader)
|
|
||||||
if !ok {
|
|
||||||
return ErrNotTimeoutReader
|
|
||||||
}
|
|
||||||
mb, err := timeoutReader.ReadMultiBufferTimeout(timeout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writer.WriteMultiBuffer(mb)
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/stat"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/stats"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reader extends io.Reader with MultiBuffer.
|
|
||||||
type Reader interface {
|
|
||||||
// ReadMultiBuffer reads content from underlying reader, and put it into a MultiBuffer.
|
|
||||||
ReadMultiBuffer() (MultiBuffer, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrReadTimeout is an error that happens with IO timeout.
|
|
||||||
var ErrReadTimeout = E.New("IO timeout")
|
|
||||||
|
|
||||||
// TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout.
|
|
||||||
type TimeoutReader interface {
|
|
||||||
ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writer extends io.Writer with MultiBuffer.
|
|
||||||
type Writer interface {
|
|
||||||
// WriteMultiBuffer writes a MultiBuffer into underlying writer.
|
|
||||||
WriteMultiBuffer(MultiBuffer) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAllBytes ensures all bytes are written into the given writer.
|
|
||||||
func WriteAllBytes(writer io.Writer, payload []byte, c stats.Counter) error {
|
|
||||||
wc := 0
|
|
||||||
defer func() {
|
|
||||||
if c != nil {
|
|
||||||
c.Add(int64(wc))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for len(payload) > 0 {
|
|
||||||
n, err := writer.Write(payload)
|
|
||||||
wc += n
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
payload = payload[n:]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPacketReader(reader io.Reader) bool {
|
|
||||||
_, ok := reader.(net.PacketConn)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader creates a new Reader.
|
|
||||||
// The Reader instance doesn't take the ownership of reader.
|
|
||||||
func NewReader(reader io.Reader) Reader {
|
|
||||||
if mr, ok := reader.(Reader); ok {
|
|
||||||
return mr
|
|
||||||
}
|
|
||||||
|
|
||||||
if isPacketReader(reader) {
|
|
||||||
return &PacketReader{
|
|
||||||
Reader: reader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SingleReader{
|
|
||||||
Reader: reader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPacketReader creates a new PacketReader based on the given reader.
|
|
||||||
func NewPacketReader(reader io.Reader) Reader {
|
|
||||||
if mr, ok := reader.(Reader); ok {
|
|
||||||
return mr
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PacketReader{
|
|
||||||
Reader: reader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPacketWriter(writer io.Writer) bool {
|
|
||||||
if _, ok := writer.(net.PacketConn); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the writer doesn't implement syscall.Conn, it is probably not a TCP connection.
|
|
||||||
if _, ok := writer.(syscall.Conn); !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter creates a new Writer.
|
|
||||||
func NewWriter(writer io.Writer) Writer {
|
|
||||||
if mw, ok := writer.(Writer); ok {
|
|
||||||
return mw
|
|
||||||
}
|
|
||||||
|
|
||||||
iConn := writer
|
|
||||||
if statConn, ok := writer.(*stat.CounterConnection); ok {
|
|
||||||
iConn = statConn.Connection
|
|
||||||
}
|
|
||||||
|
|
||||||
if isPacketWriter(iConn) {
|
|
||||||
return &SequentialWriter{
|
|
||||||
Writer: writer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var counter stats.Counter
|
|
||||||
|
|
||||||
if statConn, ok := writer.(*stat.CounterConnection); ok {
|
|
||||||
counter = statConn.WriteCounter
|
|
||||||
}
|
|
||||||
return &BufferToBytesWriter{
|
|
||||||
Writer: iConn,
|
|
||||||
counter: counter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/errors"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/serial"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadAllToBytes reads all content from the reader into a byte array, until EOF.
|
|
||||||
func ReadAllToBytes(reader io.Reader) ([]byte, error) {
|
|
||||||
mb, err := ReadFrom(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if mb.Len() == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
b := make([]byte, mb.Len())
|
|
||||||
mb, _ = SplitBytes(mb, b)
|
|
||||||
ReleaseMulti(mb)
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiBuffer is a list of Buffers. The order of Buffer matters.
|
|
||||||
type MultiBuffer []*Buffer
|
|
||||||
|
|
||||||
// MergeMulti merges content from src to dest, and returns the new address of dest and src
|
|
||||||
func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
|
|
||||||
dest = append(dest, src...)
|
|
||||||
for idx := range src {
|
|
||||||
src[idx] = nil
|
|
||||||
}
|
|
||||||
return dest, src[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
|
|
||||||
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
|
|
||||||
n := len(dest)
|
|
||||||
if n > 0 && !(dest)[n-1].IsFull() {
|
|
||||||
nBytes, _ := (dest)[n-1].Write(src)
|
|
||||||
src = src[nBytes:]
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(src) > 0 {
|
|
||||||
b := New()
|
|
||||||
nBytes, _ := b.Write(src)
|
|
||||||
src = src[nBytes:]
|
|
||||||
dest = append(dest, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseMulti releases all content of the MultiBuffer, and returns an empty MultiBuffer.
|
|
||||||
func ReleaseMulti(mb MultiBuffer) MultiBuffer {
|
|
||||||
for i := range mb {
|
|
||||||
mb[i].Release()
|
|
||||||
mb[i] = nil
|
|
||||||
}
|
|
||||||
return mb[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy copied the beginning part of the MultiBuffer into the given byte array.
|
|
||||||
func (mb MultiBuffer) Copy(b []byte) int {
|
|
||||||
total := 0
|
|
||||||
for _, bb := range mb {
|
|
||||||
nBytes := copy(b[total:], bb.Bytes())
|
|
||||||
total += nBytes
|
|
||||||
if int32(nBytes) < bb.Len() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom reads all content from reader until EOF.
|
|
||||||
func ReadFrom(reader io.Reader) (MultiBuffer, error) {
|
|
||||||
mb := make(MultiBuffer, 0, 16)
|
|
||||||
for {
|
|
||||||
b := New()
|
|
||||||
_, err := b.ReadFullFrom(reader, Size)
|
|
||||||
if b.IsEmpty() {
|
|
||||||
b.Release()
|
|
||||||
} else {
|
|
||||||
mb = append(mb, b)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if errors.Cause(err) == io.EOF || errors.Cause(err) == io.ErrUnexpectedEOF {
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
return mb, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitBytes splits the given amount of bytes from the beginning of the MultiBuffer.
|
|
||||||
// It returns the new address of MultiBuffer leftover, and number of bytes written into the input byte slice.
|
|
||||||
func SplitBytes(mb MultiBuffer, b []byte) (MultiBuffer, int) {
|
|
||||||
totalBytes := 0
|
|
||||||
endIndex := -1
|
|
||||||
for i := range mb {
|
|
||||||
pBuffer := mb[i]
|
|
||||||
nBytes, _ := pBuffer.Read(b)
|
|
||||||
totalBytes += nBytes
|
|
||||||
b = b[nBytes:]
|
|
||||||
if !pBuffer.IsEmpty() {
|
|
||||||
endIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pBuffer.Release()
|
|
||||||
mb[i] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if endIndex == -1 {
|
|
||||||
mb = mb[:0]
|
|
||||||
} else {
|
|
||||||
mb = mb[endIndex:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return mb, totalBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitFirstBytes splits the first buffer from MultiBuffer, and then copy its content into the given slice.
|
|
||||||
func SplitFirstBytes(mb MultiBuffer, p []byte) (MultiBuffer, int) {
|
|
||||||
mb, b := SplitFirst(mb)
|
|
||||||
if b == nil {
|
|
||||||
return mb, 0
|
|
||||||
}
|
|
||||||
n := copy(p, b.Bytes())
|
|
||||||
b.Release()
|
|
||||||
return mb, n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compact returns another MultiBuffer by merging all content of the given one together.
|
|
||||||
func Compact(mb MultiBuffer) MultiBuffer {
|
|
||||||
if len(mb) == 0 {
|
|
||||||
return mb
|
|
||||||
}
|
|
||||||
|
|
||||||
mb2 := make(MultiBuffer, 0, len(mb))
|
|
||||||
last := mb[0]
|
|
||||||
|
|
||||||
for i := 1; i < len(mb); i++ {
|
|
||||||
curr := mb[i]
|
|
||||||
if last.Len()+curr.Len() > Size {
|
|
||||||
mb2 = append(mb2, last)
|
|
||||||
last = curr
|
|
||||||
} else {
|
|
||||||
common.Must2(last.ReadFrom(curr))
|
|
||||||
curr.Release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mb2 = append(mb2, last)
|
|
||||||
return mb2
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitFirst splits the first Buffer from the beginning of the MultiBuffer.
|
|
||||||
func SplitFirst(mb MultiBuffer) (MultiBuffer, *Buffer) {
|
|
||||||
if len(mb) == 0 {
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b := mb[0]
|
|
||||||
mb[0] = nil
|
|
||||||
mb = mb[1:]
|
|
||||||
return mb, b
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitSize splits the beginning of the MultiBuffer into another one, for at most size bytes.
|
|
||||||
func SplitSize(mb MultiBuffer, size int32) (MultiBuffer, MultiBuffer) {
|
|
||||||
if len(mb) == 0 {
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if mb[0].Len() > size {
|
|
||||||
b := New()
|
|
||||||
copy(b.Extend(size), mb[0].BytesTo(size))
|
|
||||||
mb[0].Advance(size)
|
|
||||||
return mb, MultiBuffer{b}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalBytes := int32(0)
|
|
||||||
var r MultiBuffer
|
|
||||||
endIndex := -1
|
|
||||||
for i := range mb {
|
|
||||||
if totalBytes+mb[i].Len() > size {
|
|
||||||
endIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
totalBytes += mb[i].Len()
|
|
||||||
r = append(r, mb[i])
|
|
||||||
mb[i] = nil
|
|
||||||
}
|
|
||||||
if endIndex == -1 {
|
|
||||||
// To reuse mb array
|
|
||||||
mb = mb[:0]
|
|
||||||
} else {
|
|
||||||
mb = mb[endIndex:]
|
|
||||||
}
|
|
||||||
return mb, r
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitMulti splits the beginning of the MultiBuffer into first one, the index i and after into second one
|
|
||||||
func SplitMulti(mb MultiBuffer, i int) (MultiBuffer, MultiBuffer) {
|
|
||||||
mb2 := make(MultiBuffer, 0, len(mb))
|
|
||||||
if i < len(mb) && i >= 0 {
|
|
||||||
mb2 = append(mb2, mb[i:]...)
|
|
||||||
for j := i; j < len(mb); j++ {
|
|
||||||
mb[j] = nil
|
|
||||||
}
|
|
||||||
mb = mb[:i]
|
|
||||||
}
|
|
||||||
return mb, mb2
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer writes all buffers from the MultiBuffer to the Writer one by one, and return error if any, with leftover MultiBuffer.
|
|
||||||
func WriteMultiBuffer(writer io.Writer, mb MultiBuffer) (MultiBuffer, error) {
|
|
||||||
for {
|
|
||||||
mb2, b := SplitFirst(mb)
|
|
||||||
mb = mb2
|
|
||||||
if b == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := writer.Write(b.Bytes())
|
|
||||||
b.Release()
|
|
||||||
if err != nil {
|
|
||||||
return mb, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the total number of bytes in the MultiBuffer.
|
|
||||||
func (mb MultiBuffer) Len() int32 {
|
|
||||||
if mb == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
size := int32(0)
|
|
||||||
for _, b := range mb {
|
|
||||||
size += b.Len()
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if the MultiBuffer has no content.
|
|
||||||
func (mb MultiBuffer) IsEmpty() bool {
|
|
||||||
for _, b := range mb {
|
|
||||||
if !b.IsEmpty() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the content of the MultiBuffer in string.
|
|
||||||
func (mb MultiBuffer) String() string {
|
|
||||||
v := make([]interface{}, len(mb))
|
|
||||||
for i, b := range mb {
|
|
||||||
v[i] = b
|
|
||||||
}
|
|
||||||
return serial.Concat(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiBufferContainer is a ReadWriteCloser wrapper over MultiBuffer.
|
|
||||||
type MultiBufferContainer struct {
|
|
||||||
MultiBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements io.Reader.
|
|
||||||
func (c *MultiBufferContainer) Read(b []byte) (int, error) {
|
|
||||||
if c.MultiBuffer.IsEmpty() {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
mb, nBytes := SplitBytes(c.MultiBuffer, b)
|
|
||||||
c.MultiBuffer = mb
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements Reader.
|
|
||||||
func (c *MultiBufferContainer) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
mb := c.MultiBuffer
|
|
||||||
c.MultiBuffer = nil
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements io.Writer.
|
|
||||||
func (c *MultiBufferContainer) Write(b []byte) (int, error) {
|
|
||||||
c.MultiBuffer = MergeBytes(c.MultiBuffer, b)
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer.
|
|
||||||
func (c *MultiBufferContainer) WriteMultiBuffer(b MultiBuffer) error {
|
|
||||||
mb, _ := MergeMulti(c.MultiBuffer, b)
|
|
||||||
c.MultiBuffer = mb
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closer.
|
|
||||||
func (c *MultiBufferContainer) Close() error {
|
|
||||||
c.MultiBuffer = ReleaseMulti(c.MultiBuffer)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/common/xray/net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EndpointOverrideReader struct {
|
|
||||||
Reader
|
|
||||||
Dest net.Address
|
|
||||||
OriginalDest net.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EndpointOverrideReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
mb, err := r.Reader.ReadMultiBuffer()
|
|
||||||
if err == nil {
|
|
||||||
for _, b := range mb {
|
|
||||||
if b.UDP != nil && b.UDP.Address == r.OriginalDest {
|
|
||||||
b.UDP.Address = r.Dest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mb, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type EndpointOverrideWriter struct {
|
|
||||||
Writer
|
|
||||||
Dest net.Address
|
|
||||||
OriginalDest net.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *EndpointOverrideWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
|
||||||
for _, b := range mb {
|
|
||||||
if b.UDP != nil && b.UDP.Address == w.Dest {
|
|
||||||
b.UDP.Address = w.OriginalDest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w.Writer.WriteMultiBuffer(mb)
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/errors"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readOneUDP(r io.Reader) (*Buffer, error) {
|
|
||||||
b := New()
|
|
||||||
for i := 0; i < 64; i++ {
|
|
||||||
_, err := b.ReadFrom(r)
|
|
||||||
if !b.IsEmpty() {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
b.Release()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Release()
|
|
||||||
return nil, E.New("Reader returns too many empty payloads.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadBuffer reads a Buffer from the given reader.
|
|
||||||
func ReadBuffer(r io.Reader) (*Buffer, error) {
|
|
||||||
b := New()
|
|
||||||
n, err := b.ReadFrom(r)
|
|
||||||
if n > 0 {
|
|
||||||
return b, err
|
|
||||||
}
|
|
||||||
b.Release()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferedReader is a Reader that keeps its internal buffer.
|
|
||||||
type BufferedReader struct {
|
|
||||||
// Reader is the underlying reader to be read from
|
|
||||||
Reader Reader
|
|
||||||
// Buffer is the internal buffer to be read from first
|
|
||||||
Buffer MultiBuffer
|
|
||||||
// Splitter is a function to read bytes from MultiBuffer
|
|
||||||
Splitter func(MultiBuffer, []byte) (MultiBuffer, int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferedBytes returns the number of bytes that is cached in this reader.
|
|
||||||
func (r *BufferedReader) BufferedBytes() int32 {
|
|
||||||
return r.Buffer.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadByte implements io.ByteReader.
|
|
||||||
func (r *BufferedReader) ReadByte() (byte, error) {
|
|
||||||
var b [1]byte
|
|
||||||
_, err := r.Read(b[:])
|
|
||||||
return b[0], err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader.
|
|
||||||
func (r *BufferedReader) Read(b []byte) (int, error) {
|
|
||||||
spliter := r.Splitter
|
|
||||||
if spliter == nil {
|
|
||||||
spliter = SplitBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
if !r.Buffer.IsEmpty() {
|
|
||||||
buffer, nBytes := spliter(r.Buffer, b)
|
|
||||||
r.Buffer = buffer
|
|
||||||
if r.Buffer.IsEmpty() {
|
|
||||||
r.Buffer = nil
|
|
||||||
}
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mb, err := r.Reader.ReadMultiBuffer()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mb, nBytes := spliter(mb, b)
|
|
||||||
if !mb.IsEmpty() {
|
|
||||||
r.Buffer = mb
|
|
||||||
}
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements Reader.
|
|
||||||
func (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
if !r.Buffer.IsEmpty() {
|
|
||||||
mb := r.Buffer
|
|
||||||
r.Buffer = nil
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.Reader.ReadMultiBuffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadAtMost returns a MultiBuffer with at most size.
|
|
||||||
func (r *BufferedReader) ReadAtMost(size int32) (MultiBuffer, error) {
|
|
||||||
if r.Buffer.IsEmpty() {
|
|
||||||
mb, err := r.Reader.ReadMultiBuffer()
|
|
||||||
if mb.IsEmpty() && err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.Buffer = mb
|
|
||||||
}
|
|
||||||
|
|
||||||
rb, mb := SplitSize(r.Buffer, size)
|
|
||||||
r.Buffer = rb
|
|
||||||
if r.Buffer.IsEmpty() {
|
|
||||||
r.Buffer = nil
|
|
||||||
}
|
|
||||||
return mb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) {
|
|
||||||
mbWriter := NewWriter(writer)
|
|
||||||
var sc SizeCounter
|
|
||||||
if r.Buffer != nil {
|
|
||||||
sc.Size = int64(r.Buffer.Len())
|
|
||||||
if err := mbWriter.WriteMultiBuffer(r.Buffer); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
r.Buffer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := Copy(r.Reader, mbWriter, CountSize(&sc))
|
|
||||||
return sc.Size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo implements io.WriterTo.
|
|
||||||
func (r *BufferedReader) WriteTo(writer io.Writer) (int64, error) {
|
|
||||||
nBytes, err := r.writeToInternal(writer)
|
|
||||||
if errors.Cause(err) == io.EOF {
|
|
||||||
return nBytes, nil
|
|
||||||
}
|
|
||||||
return nBytes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt implements common.Interruptible.
|
|
||||||
func (r *BufferedReader) Interrupt() {
|
|
||||||
common.Interrupt(r.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closer.
|
|
||||||
func (r *BufferedReader) Close() error {
|
|
||||||
return common.Close(r.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SingleReader is a Reader that read one Buffer every time.
|
|
||||||
type SingleReader struct {
|
|
||||||
io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements Reader.
|
|
||||||
func (r *SingleReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
b, err := ReadBuffer(r.Reader)
|
|
||||||
return MultiBuffer{b}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PacketReader is a Reader that read one Buffer every time.
|
|
||||||
type PacketReader struct {
|
|
||||||
io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements Reader.
|
|
||||||
func (r *PacketReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|
||||||
b, err := readOneUDP(r.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return MultiBuffer{b}, nil
|
|
||||||
}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
package buf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/errors"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/stats"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BufferToBytesWriter is a Writer that writes alloc.Buffer into underlying writer.
|
|
||||||
type BufferToBytesWriter struct {
|
|
||||||
io.Writer
|
|
||||||
|
|
||||||
counter stats.Counter
|
|
||||||
cache [][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer. This method takes ownership of the given buffer.
|
|
||||||
func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
|
||||||
defer ReleaseMulti(mb)
|
|
||||||
|
|
||||||
size := mb.Len()
|
|
||||||
if size == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mb) == 1 {
|
|
||||||
return WriteAllBytes(w.Writer, mb[0].Bytes(), w.counter)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(w.cache) < len(mb) {
|
|
||||||
w.cache = make([][]byte, 0, len(mb))
|
|
||||||
}
|
|
||||||
|
|
||||||
bs := w.cache
|
|
||||||
for _, b := range mb {
|
|
||||||
bs = append(bs, b.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
for idx := range bs {
|
|
||||||
bs[idx] = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
nb := net.Buffers(bs)
|
|
||||||
wc := int64(0)
|
|
||||||
defer func() {
|
|
||||||
if w.counter != nil {
|
|
||||||
w.counter.Add(wc)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for size > 0 {
|
|
||||||
n, err := nb.WriteTo(w.Writer)
|
|
||||||
wc += n
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
size -= int32(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom implements io.ReaderFrom.
|
|
||||||
func (w *BufferToBytesWriter) ReadFrom(reader io.Reader) (int64, error) {
|
|
||||||
var sc SizeCounter
|
|
||||||
err := Copy(NewReader(reader), w, CountSize(&sc))
|
|
||||||
return sc.Size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferedWriter is a Writer with internal buffer.
|
|
||||||
type BufferedWriter struct {
|
|
||||||
sync.Mutex
|
|
||||||
writer Writer
|
|
||||||
buffer *Buffer
|
|
||||||
buffered bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufferedWriter creates a new BufferedWriter.
|
|
||||||
func NewBufferedWriter(writer Writer) *BufferedWriter {
|
|
||||||
return &BufferedWriter{
|
|
||||||
writer: writer,
|
|
||||||
buffer: New(),
|
|
||||||
buffered: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteByte implements io.ByteWriter.
|
|
||||||
func (w *BufferedWriter) WriteByte(c byte) error {
|
|
||||||
return common.Error2(w.Write([]byte{c}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements io.Writer.
|
|
||||||
func (w *BufferedWriter) Write(b []byte) (int, error) {
|
|
||||||
if len(b) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
if !w.buffered {
|
|
||||||
if writer, ok := w.writer.(io.Writer); ok {
|
|
||||||
return writer.Write(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalBytes := 0
|
|
||||||
for len(b) > 0 {
|
|
||||||
if w.buffer == nil {
|
|
||||||
w.buffer = New()
|
|
||||||
}
|
|
||||||
|
|
||||||
nBytes, err := w.buffer.Write(b)
|
|
||||||
totalBytes += nBytes
|
|
||||||
if err != nil {
|
|
||||||
return totalBytes, err
|
|
||||||
}
|
|
||||||
if !w.buffered || w.buffer.IsFull() {
|
|
||||||
if err := w.flushInternal(); err != nil {
|
|
||||||
return totalBytes, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b = b[nBytes:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer. It takes ownership of the given MultiBuffer.
|
|
||||||
func (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error {
|
|
||||||
if b.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
if !w.buffered {
|
|
||||||
return w.writer.WriteMultiBuffer(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := MultiBufferContainer{
|
|
||||||
MultiBuffer: b,
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
for !reader.MultiBuffer.IsEmpty() {
|
|
||||||
if w.buffer == nil {
|
|
||||||
w.buffer = New()
|
|
||||||
}
|
|
||||||
common.Must2(w.buffer.ReadFrom(&reader))
|
|
||||||
if w.buffer.IsFull() {
|
|
||||||
if err := w.flushInternal(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush flushes buffered content into underlying writer.
|
|
||||||
func (w *BufferedWriter) Flush() error {
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
return w.flushInternal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *BufferedWriter) flushInternal() error {
|
|
||||||
if w.buffer.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b := w.buffer
|
|
||||||
w.buffer = nil
|
|
||||||
|
|
||||||
if writer, ok := w.writer.(io.Writer); ok {
|
|
||||||
err := WriteAllBytes(writer, b.Bytes(), nil)
|
|
||||||
b.Release()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.writer.WriteMultiBuffer(MultiBuffer{b})
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBuffered sets whether the internal buffer is used. If set to false, Flush() will be called to clear the buffer.
|
|
||||||
func (w *BufferedWriter) SetBuffered(f bool) error {
|
|
||||||
w.Lock()
|
|
||||||
defer w.Unlock()
|
|
||||||
|
|
||||||
w.buffered = f
|
|
||||||
if !f {
|
|
||||||
return w.flushInternal()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom implements io.ReaderFrom.
|
|
||||||
func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) {
|
|
||||||
if err := w.SetBuffered(false); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var sc SizeCounter
|
|
||||||
err := Copy(NewReader(reader), w, CountSize(&sc))
|
|
||||||
return sc.Size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closable.
|
|
||||||
func (w *BufferedWriter) Close() error {
|
|
||||||
if err := w.Flush(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return common.Close(w.writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SequentialWriter is a Writer that writes MultiBuffer sequentially into the underlying io.Writer.
|
|
||||||
type SequentialWriter struct {
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements Writer.
|
|
||||||
func (w *SequentialWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
|
||||||
mb, err := WriteMultiBuffer(w.Writer, mb)
|
|
||||||
ReleaseMulti(mb)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type noOpWriter byte
|
|
||||||
|
|
||||||
func (noOpWriter) WriteMultiBuffer(b MultiBuffer) error {
|
|
||||||
ReleaseMulti(b)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noOpWriter) Write(b []byte) (int, error) {
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noOpWriter) ReadFrom(reader io.Reader) (int64, error) {
|
|
||||||
b := New()
|
|
||||||
defer b.Release()
|
|
||||||
|
|
||||||
totalBytes := int64(0)
|
|
||||||
for {
|
|
||||||
b.Clear()
|
|
||||||
_, err := b.ReadFrom(reader)
|
|
||||||
totalBytes += int64(b.Len())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Cause(err) == io.EOF {
|
|
||||||
return totalBytes, nil
|
|
||||||
}
|
|
||||||
return totalBytes, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Discard is a Writer that swallows all contents written in.
|
|
||||||
Discard Writer = noOpWriter(0)
|
|
||||||
|
|
||||||
// DiscardBytes is an io.Writer that swallows all contents written in.
|
|
||||||
DiscardBytes io.Writer = noOpWriter(0)
|
|
||||||
)
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package bytespool
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
func createAllocFunc(size int32) func() interface{} {
|
|
||||||
return func() interface{} {
|
|
||||||
return make([]byte, size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following parameters controls the size of buffer pools.
|
|
||||||
// There are numPools pools. Starting from 2k size, the size of each pool is sizeMulti of the previous one.
|
|
||||||
// Package buf is guaranteed to not use buffers larger than the largest pool.
|
|
||||||
// Other packets may use larger buffers.
|
|
||||||
const (
|
|
||||||
numPools = 4
|
|
||||||
sizeMulti = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
pool [numPools]sync.Pool
|
|
||||||
poolSize [numPools]int32
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
size := int32(2048)
|
|
||||||
for i := 0; i < numPools; i++ {
|
|
||||||
pool[i] = sync.Pool{
|
|
||||||
New: createAllocFunc(size),
|
|
||||||
}
|
|
||||||
poolSize[i] = size
|
|
||||||
size *= sizeMulti
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPool returns a sync.Pool that generates bytes array with at least the given size.
|
|
||||||
// It may return nil if no such pool exists.
|
|
||||||
//
|
|
||||||
// xray:api:stable
|
|
||||||
func GetPool(size int32) *sync.Pool {
|
|
||||||
for idx, ps := range poolSize {
|
|
||||||
if size <= ps {
|
|
||||||
return &pool[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alloc returns a byte slice with at least the given size. Minimum size of returned slice is 2048.
|
|
||||||
//
|
|
||||||
// xray:api:stable
|
|
||||||
func Alloc(size int32) []byte {
|
|
||||||
pool := GetPool(size)
|
|
||||||
if pool != nil {
|
|
||||||
return pool.Get().([]byte)
|
|
||||||
}
|
|
||||||
return make([]byte, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free puts a byte slice into the internal pool.
|
|
||||||
//
|
|
||||||
// xray:api:stable
|
|
||||||
func Free(b []byte) {
|
|
||||||
size := int32(cap(b))
|
|
||||||
b = b[0:cap(b)]
|
|
||||||
for i := numPools - 1; i >= 0; i-- {
|
|
||||||
if size >= poolSize[i] {
|
|
||||||
pool[i].Put(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
// Must panics if err is not nil.
|
|
||||||
func Must(err error) {
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must2 panics if the second parameter is not nil, otherwise returns the first parameter.
|
|
||||||
func Must2(v interface{}, err error) interface{} {
|
|
||||||
Must(err)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error2 returns the err from the 2nd parameter.
|
|
||||||
func Error2(v interface{}, err error) error {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RandBetween(from int64, to int64) int64 {
|
|
||||||
if from == to {
|
|
||||||
return from
|
|
||||||
}
|
|
||||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
|
||||||
return from + bigInt.Int64()
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package errors
|
|
||||||
|
|
||||||
type hasInnerError interface {
|
|
||||||
// Unwrap returns the underlying error of this one.
|
|
||||||
Unwrap() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func Cause(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
L:
|
|
||||||
for {
|
|
||||||
switch inner := err.(type) {
|
|
||||||
case hasInnerError:
|
|
||||||
if inner.Unwrap() == nil {
|
|
||||||
break L
|
|
||||||
}
|
|
||||||
err = inner.Unwrap()
|
|
||||||
default:
|
|
||||||
break L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
// Closable is the interface for objects that can release its resources.
|
|
||||||
//
|
|
||||||
// xray:api:beta
|
|
||||||
type Closable interface {
|
|
||||||
// Close release all resources used by this object, including goroutines.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interruptible is an interface for objects that can be stopped before its completion.
|
|
||||||
//
|
|
||||||
// xray:api:beta
|
|
||||||
type Interruptible interface {
|
|
||||||
Interrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the obj if it is a Closable.
|
|
||||||
//
|
|
||||||
// xray:api:beta
|
|
||||||
func Close(obj interface{}) error {
|
|
||||||
if c, ok := obj.(Closable); ok {
|
|
||||||
return c.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt calls Interrupt() if object implements Interruptible interface, or Close() if the object implements Closable interface.
|
|
||||||
//
|
|
||||||
// xray:api:beta
|
|
||||||
func Interrupt(obj interface{}) error {
|
|
||||||
if c, ok := obj.(Interruptible); ok {
|
|
||||||
c.Interrupt()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return Close(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runnable is the interface for objects that can start to work and stop on demand.
|
|
||||||
type Runnable interface {
|
|
||||||
// Start starts the runnable object. Upon the method returning nil, the object begins to function properly.
|
|
||||||
Start() error
|
|
||||||
|
|
||||||
Closable
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasType is the interface for objects that knows its type.
|
|
||||||
type HasType interface {
|
|
||||||
// Type returns the type of the object.
|
|
||||||
// Usually it returns (*Type)(nil) of the object.
|
|
||||||
Type() interface{}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package badoption
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/crypto"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Range struct {
|
|
||||||
From int32 `json:"from"`
|
|
||||||
To int32 `json:"to"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Range) Build() *Range {
|
|
||||||
return (*Range)(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Range) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(fmt.Sprintf("%d-%d", c.From, c.To))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Range) UnmarshalJSON(content []byte) error {
|
|
||||||
var rangeValue struct {
|
|
||||||
From int32 `json:"from"`
|
|
||||||
To int32 `json:"to"`
|
|
||||||
}
|
|
||||||
var stringValue string
|
|
||||||
err := json.Unmarshal(content, &stringValue)
|
|
||||||
if err == nil {
|
|
||||||
parts := strings.Split(stringValue, "-")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return E.New("invalid length of range parts")
|
|
||||||
}
|
|
||||||
from, err := strconv.ParseInt(parts[0], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
to, err := strconv.ParseInt(parts[1], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rangeValue.From, rangeValue.To = int32(from), int32(to)
|
|
||||||
} else {
|
|
||||||
err := json.Unmarshal(content, &rangeValue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rangeValue.From > rangeValue.To {
|
|
||||||
return E.New("invalid range")
|
|
||||||
}
|
|
||||||
*c = Range{rangeValue.From, rangeValue.To}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Range) Rand() int32 {
|
|
||||||
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// LocalHostIP is a constant value for localhost IP in IPv4.
|
|
||||||
LocalHostIP = IPAddress([]byte{127, 0, 0, 1})
|
|
||||||
|
|
||||||
// AnyIP is a constant value for any IP in IPv4.
|
|
||||||
AnyIP = IPAddress([]byte{0, 0, 0, 0})
|
|
||||||
|
|
||||||
// LocalHostDomain is a constant value for localhost domain.
|
|
||||||
LocalHostDomain = DomainAddress("localhost")
|
|
||||||
|
|
||||||
// LocalHostIPv6 is a constant value for localhost IP in IPv6.
|
|
||||||
LocalHostIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
|
|
||||||
|
|
||||||
// AnyIPv6 is a constant value for any IP in IPv6.
|
|
||||||
AnyIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddressFamily is the type of address.
|
|
||||||
type AddressFamily byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AddressFamilyIPv4 represents address as IPv4
|
|
||||||
AddressFamilyIPv4 = AddressFamily(0)
|
|
||||||
|
|
||||||
// AddressFamilyIPv6 represents address as IPv6
|
|
||||||
AddressFamilyIPv6 = AddressFamily(1)
|
|
||||||
|
|
||||||
// AddressFamilyDomain represents address as Domain
|
|
||||||
AddressFamilyDomain = AddressFamily(2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsIPv4 returns true if current AddressFamily is IPv4.
|
|
||||||
func (af AddressFamily) IsIPv4() bool {
|
|
||||||
return af == AddressFamilyIPv4
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsIPv6 returns true if current AddressFamily is IPv6.
|
|
||||||
func (af AddressFamily) IsIPv6() bool {
|
|
||||||
return af == AddressFamilyIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsIP returns true if current AddressFamily is IPv6 or IPv4.
|
|
||||||
func (af AddressFamily) IsIP() bool {
|
|
||||||
return af == AddressFamilyIPv4 || af == AddressFamilyIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDomain returns true if current AddressFamily is Domain.
|
|
||||||
func (af AddressFamily) IsDomain() bool {
|
|
||||||
return af == AddressFamilyDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address represents a network address to be communicated with. It may be an IP address or domain
|
|
||||||
// address, not both. This interface doesn't resolve IP address for a given domain.
|
|
||||||
type Address interface {
|
|
||||||
IP() net.IP // IP of this Address
|
|
||||||
Domain() string // Domain of this Address
|
|
||||||
Family() AddressFamily
|
|
||||||
|
|
||||||
String() string // String representation of this Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAlphaNum(c byte) bool {
|
|
||||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAddress parses a string into an Address. The return value will be an IPAddress when
|
|
||||||
// the string is in the form of IPv4 or IPv6 address, or a DomainAddress otherwise.
|
|
||||||
func ParseAddress(addr string) Address {
|
|
||||||
// Handle IPv6 address in form as "[2001:4860:0:2001::68]"
|
|
||||||
lenAddr := len(addr)
|
|
||||||
if lenAddr > 0 && addr[0] == '[' && addr[lenAddr-1] == ']' {
|
|
||||||
addr = addr[1 : lenAddr-1]
|
|
||||||
lenAddr -= 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if lenAddr > 0 && (!isAlphaNum(addr[0]) || !isAlphaNum(addr[len(addr)-1])) {
|
|
||||||
addr = strings.TrimSpace(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(addr)
|
|
||||||
if ip != nil {
|
|
||||||
return IPAddress(ip)
|
|
||||||
}
|
|
||||||
return DomainAddress(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes0 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
||||||
|
|
||||||
// IPAddress creates an Address with given IP.
|
|
||||||
func IPAddress(ip []byte) Address {
|
|
||||||
switch len(ip) {
|
|
||||||
case net.IPv4len:
|
|
||||||
var addr ipv4Address = [4]byte{ip[0], ip[1], ip[2], ip[3]}
|
|
||||||
return addr
|
|
||||||
case net.IPv6len:
|
|
||||||
if bytes.Equal(ip[:10], bytes0) && ip[10] == 0xff && ip[11] == 0xff {
|
|
||||||
return IPAddress(ip[12:16])
|
|
||||||
}
|
|
||||||
var addr ipv6Address = [16]byte{
|
|
||||||
ip[0], ip[1], ip[2], ip[3],
|
|
||||||
ip[4], ip[5], ip[6], ip[7],
|
|
||||||
ip[8], ip[9], ip[10], ip[11],
|
|
||||||
ip[12], ip[13], ip[14], ip[15],
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainAddress creates an Address with given domain.
|
|
||||||
// This is an internal function that forcibly converts a string to domain.
|
|
||||||
// It's mainly used in test files and mux.
|
|
||||||
// Unless you have a specific reason, use net.ParseAddress instead,
|
|
||||||
// as this function does not check whether the input is an IP address.
|
|
||||||
// Otherwise, you will get strange results like domain: 1.1.1.1
|
|
||||||
func DomainAddress(domain string) Address {
|
|
||||||
return domainAddress(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipv4Address [4]byte
|
|
||||||
|
|
||||||
func (a ipv4Address) IP() net.IP {
|
|
||||||
return net.IP(a[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ipv4Address) Domain() string {
|
|
||||||
panic("Calling Domain() on an IPv4Address.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ipv4Address) Family() AddressFamily {
|
|
||||||
return AddressFamilyIPv4
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ipv4Address) String() string {
|
|
||||||
return a.IP().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipv6Address [16]byte
|
|
||||||
|
|
||||||
func (a ipv6Address) IP() net.IP {
|
|
||||||
return net.IP(a[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ipv6Address) Domain() string {
|
|
||||||
panic("Calling Domain() on an IPv6Address.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ipv6Address) Family() AddressFamily {
|
|
||||||
return AddressFamilyIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ipv6Address) String() string {
|
|
||||||
return "[" + a.IP().String() + "]"
|
|
||||||
}
|
|
||||||
|
|
||||||
type domainAddress string
|
|
||||||
|
|
||||||
func (domainAddress) IP() net.IP {
|
|
||||||
panic("Calling IP() on a DomainAddress.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a domainAddress) Domain() string {
|
|
||||||
return string(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (domainAddress) Family() AddressFamily {
|
|
||||||
return AddressFamilyDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a domainAddress) String() string {
|
|
||||||
return a.Domain()
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Destination represents a network destination including address and protocol (tcp / udp).
|
|
||||||
type Destination struct {
|
|
||||||
Address Address
|
|
||||||
Port Port
|
|
||||||
Network Network
|
|
||||||
}
|
|
||||||
|
|
||||||
// DestinationFromAddr generates a Destination from a net address.
|
|
||||||
func DestinationFromAddr(addr net.Addr) Destination {
|
|
||||||
switch addr := addr.(type) {
|
|
||||||
case *net.TCPAddr:
|
|
||||||
return TCPDestination(IPAddress(addr.IP), Port(addr.Port))
|
|
||||||
case *net.UDPAddr:
|
|
||||||
return UDPDestination(IPAddress(addr.IP), Port(addr.Port))
|
|
||||||
case *net.UnixAddr:
|
|
||||||
return UnixDestination(DomainAddress(addr.Name))
|
|
||||||
default:
|
|
||||||
panic("Net: Unknown address type.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseDestination converts a destination from its string presentation.
|
|
||||||
func ParseDestination(dest string) (Destination, error) {
|
|
||||||
d := Destination{
|
|
||||||
Address: AnyIP,
|
|
||||||
Port: Port(0),
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(dest, "tcp:") {
|
|
||||||
d.Network = Network_TCP
|
|
||||||
dest = dest[4:]
|
|
||||||
} else if strings.HasPrefix(dest, "udp:") {
|
|
||||||
d.Network = Network_UDP
|
|
||||||
dest = dest[4:]
|
|
||||||
} else if strings.HasPrefix(dest, "unix:") {
|
|
||||||
d = UnixDestination(DomainAddress(dest[5:]))
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
hstr, pstr, err := SplitHostPort(dest)
|
|
||||||
if err != nil {
|
|
||||||
return d, err
|
|
||||||
}
|
|
||||||
if len(hstr) > 0 {
|
|
||||||
d.Address = ParseAddress(hstr)
|
|
||||||
}
|
|
||||||
if len(pstr) > 0 {
|
|
||||||
port, err := PortFromString(pstr)
|
|
||||||
if err != nil {
|
|
||||||
return d, err
|
|
||||||
}
|
|
||||||
d.Port = port
|
|
||||||
}
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPDestination creates a TCP destination with given address
|
|
||||||
func TCPDestination(address Address, port Port) Destination {
|
|
||||||
return Destination{
|
|
||||||
Network: Network_TCP,
|
|
||||||
Address: address,
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDPDestination creates a UDP destination with given address
|
|
||||||
func UDPDestination(address Address, port Port) Destination {
|
|
||||||
return Destination{
|
|
||||||
Network: Network_UDP,
|
|
||||||
Address: address,
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnixDestination creates a Unix destination with given address
|
|
||||||
func UnixDestination(address Address) Destination {
|
|
||||||
return Destination{
|
|
||||||
Network: Network_UNIX,
|
|
||||||
Address: address,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetAddr returns the network address in this Destination in string form.
|
|
||||||
func (d Destination) NetAddr() string {
|
|
||||||
addr := ""
|
|
||||||
if d.Network == Network_TCP || d.Network == Network_UDP {
|
|
||||||
addr = d.Address.String() + ":" + d.Port.String()
|
|
||||||
} else if d.Network == Network_UNIX {
|
|
||||||
addr = d.Address.String()
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawNetAddr converts a net.Addr from its Destination presentation.
|
|
||||||
func (d Destination) RawNetAddr() net.Addr {
|
|
||||||
var addr net.Addr
|
|
||||||
switch d.Network {
|
|
||||||
case Network_TCP:
|
|
||||||
if d.Address.Family().IsIP() {
|
|
||||||
addr = &net.TCPAddr{
|
|
||||||
IP: d.Address.IP(),
|
|
||||||
Port: int(d.Port),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Network_UDP:
|
|
||||||
if d.Address.Family().IsIP() {
|
|
||||||
addr = &net.UDPAddr{
|
|
||||||
IP: d.Address.IP(),
|
|
||||||
Port: int(d.Port),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Network_UNIX:
|
|
||||||
if d.Address.Family().IsDomain() {
|
|
||||||
addr = &net.UnixAddr{
|
|
||||||
Name: d.Address.String(),
|
|
||||||
Net: d.Network.SystemString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the strings form of this Destination.
|
|
||||||
func (d Destination) String() string {
|
|
||||||
prefix := "unknown:"
|
|
||||||
switch d.Network {
|
|
||||||
case Network_TCP:
|
|
||||||
prefix = "tcp:"
|
|
||||||
case Network_UDP:
|
|
||||||
prefix = "udp:"
|
|
||||||
case Network_UNIX:
|
|
||||||
prefix = "unix:"
|
|
||||||
}
|
|
||||||
return prefix + d.NetAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid returns true if this Destination is valid.
|
|
||||||
func (d Destination) IsValid() bool {
|
|
||||||
return d.Network != Network_Unknown
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// defines the maximum time an idle TCP session can survive in the tunnel, so
|
|
||||||
// it should be consistent across HTTP versions and with other transports.
|
|
||||||
const ConnIdleTimeout = 300 * time.Second
|
|
||||||
|
|
||||||
// consistent with quic-go
|
|
||||||
const QuicgoH3KeepAlivePeriod = 10 * time.Second
|
|
||||||
|
|
||||||
// consistent with chrome
|
|
||||||
const ChromeH2KeepAlivePeriod = 45 * time.Second
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
type Network int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
Network_Unknown Network = 0
|
|
||||||
Network_TCP Network = 2
|
|
||||||
Network_UDP Network = 3
|
|
||||||
Network_UNIX Network = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n Network) SystemString() string {
|
|
||||||
switch n {
|
|
||||||
case Network_TCP:
|
|
||||||
return "tcp"
|
|
||||||
case Network_UDP:
|
|
||||||
return "udp"
|
|
||||||
case Network_UNIX:
|
|
||||||
return "unix"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasNetwork returns true if the network list has a certain network.
|
|
||||||
func HasNetwork(list []Network, network Network) bool {
|
|
||||||
for _, value := range list {
|
|
||||||
if value == network {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Port represents a network port in TCP and UDP protocol.
|
|
||||||
type Port uint16
|
|
||||||
|
|
||||||
// PortFromBytes converts a byte array to a Port, assuming bytes are in big endian order.
|
|
||||||
// @unsafe Caller must ensure that the byte array has at least 2 elements.
|
|
||||||
func PortFromBytes(port []byte) Port {
|
|
||||||
return Port(binary.BigEndian.Uint16(port))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PortFromInt converts an integer to a Port.
|
|
||||||
// @error when the integer is not positive or larger then 65535
|
|
||||||
func PortFromInt(val uint32) (Port, error) {
|
|
||||||
if val > 65535 {
|
|
||||||
return Port(0), E.New("invalid port range: ", val)
|
|
||||||
}
|
|
||||||
return Port(val), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PortFromString converts a string to a Port.
|
|
||||||
// @error when the string is not an integer or the integral value is a not a valid Port.
|
|
||||||
func PortFromString(s string) (Port, error) {
|
|
||||||
val, err := strconv.ParseUint(s, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return Port(0), E.New("invalid port range: ", s)
|
|
||||||
}
|
|
||||||
return PortFromInt(uint32(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value return the corresponding uint16 value of a Port.
|
|
||||||
func (p Port) Value() uint16 {
|
|
||||||
return uint16(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string presentation of a Port.
|
|
||||||
func (p Port) String() string {
|
|
||||||
return strconv.Itoa(int(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryPortRange struct {
|
|
||||||
From Port
|
|
||||||
To Port
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r MemoryPortRange) Contains(port Port) bool {
|
|
||||||
return r.From <= port && port <= r.To
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package net
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
// DialTCP is an alias of net.DialTCP.
|
|
||||||
var (
|
|
||||||
DialTCP = net.DialTCP
|
|
||||||
DialUDP = net.DialUDP
|
|
||||||
DialUnix = net.DialUnix
|
|
||||||
Dial = net.Dial
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListenConfig = net.ListenConfig
|
|
||||||
|
|
||||||
var (
|
|
||||||
Listen = net.Listen
|
|
||||||
ListenTCP = net.ListenTCP
|
|
||||||
ListenUDP = net.ListenUDP
|
|
||||||
ListenUnix = net.ListenUnix
|
|
||||||
)
|
|
||||||
|
|
||||||
var LookupIP = net.LookupIP
|
|
||||||
|
|
||||||
var FileConn = net.FileConn
|
|
||||||
|
|
||||||
// ParseIP is an alias of net.ParseIP
|
|
||||||
var ParseIP = net.ParseIP
|
|
||||||
|
|
||||||
var SplitHostPort = net.SplitHostPort
|
|
||||||
|
|
||||||
var CIDRMask = net.CIDRMask
|
|
||||||
|
|
||||||
type (
|
|
||||||
Addr = net.Addr
|
|
||||||
Conn = net.Conn
|
|
||||||
PacketConn = net.PacketConn
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
TCPAddr = net.TCPAddr
|
|
||||||
TCPConn = net.TCPConn
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
UDPAddr = net.UDPAddr
|
|
||||||
UDPConn = net.UDPConn
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
UnixAddr = net.UnixAddr
|
|
||||||
UnixConn = net.UnixConn
|
|
||||||
)
|
|
||||||
|
|
||||||
// IP is an alias for net.IP.
|
|
||||||
type (
|
|
||||||
IP = net.IP
|
|
||||||
IPMask = net.IPMask
|
|
||||||
IPNet = net.IPNet
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IPv4len = net.IPv4len
|
|
||||||
IPv6len = net.IPv6len
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Error = net.Error
|
|
||||||
AddrError = net.AddrError
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Dialer = net.Dialer
|
|
||||||
Listener = net.Listener
|
|
||||||
TCPListener = net.TCPListener
|
|
||||||
UnixListener = net.UnixListener
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ResolveTCPAddr = net.ResolveTCPAddr
|
|
||||||
ResolveUDPAddr = net.ResolveUDPAddr
|
|
||||||
ResolveUnixAddr = net.ResolveUnixAddr
|
|
||||||
)
|
|
||||||
|
|
||||||
type Resolver = net.Resolver
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
package pipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/buf"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
|
||||||
)
|
|
||||||
|
|
||||||
type state byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
open state = iota
|
|
||||||
closed
|
|
||||||
errord
|
|
||||||
)
|
|
||||||
|
|
||||||
type pipeOption struct {
|
|
||||||
limit int32 // maximum buffer size in bytes
|
|
||||||
discardOverflow bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *pipeOption) isFull(curSize int32) bool {
|
|
||||||
return o.limit >= 0 && curSize > o.limit
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipe struct {
|
|
||||||
sync.Mutex
|
|
||||||
data buf.MultiBuffer
|
|
||||||
readSignal *signal.Notifier
|
|
||||||
writeSignal *signal.Notifier
|
|
||||||
done *done.Instance
|
|
||||||
errChan chan error
|
|
||||||
option pipeOption
|
|
||||||
state state
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errBufferFull = errors.New("buffer full")
|
|
||||||
errSlowDown = errors.New("slow down")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *pipe) Len() int32 {
|
|
||||||
data := p.data
|
|
||||||
if data == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return data.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) getState(forRead bool) error {
|
|
||||||
switch p.state {
|
|
||||||
case open:
|
|
||||||
if !forRead && p.option.isFull(p.data.Len()) {
|
|
||||||
return errBufferFull
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case closed:
|
|
||||||
if !forRead {
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
if !p.data.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return io.EOF
|
|
||||||
case errord:
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
default:
|
|
||||||
panic("impossible case")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) readMultiBufferInternal() (buf.MultiBuffer, error) {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
if err := p.getState(true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := p.data
|
|
||||||
p.data = nil
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|
||||||
for {
|
|
||||||
data, err := p.readMultiBufferInternal()
|
|
||||||
if data != nil || err != nil {
|
|
||||||
p.writeSignal.Signal()
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-p.readSignal.Wait():
|
|
||||||
case <-p.done.Wait():
|
|
||||||
case err = <-p.errChan:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {
|
|
||||||
timer := time.NewTimer(d)
|
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
data, err := p.readMultiBufferInternal()
|
|
||||||
if data != nil || err != nil {
|
|
||||||
p.writeSignal.Signal()
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-p.readSignal.Wait():
|
|
||||||
case <-p.done.Wait():
|
|
||||||
case <-timer.C:
|
|
||||||
return nil, buf.ErrReadTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) writeMultiBufferInternal(mb buf.MultiBuffer) error {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
if err := p.getState(false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.data == nil {
|
|
||||||
p.data = mb
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.data, _ = buf.MergeMulti(p.data, mb)
|
|
||||||
return errSlowDown
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
||||||
if mb.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := p.writeMultiBufferInternal(mb)
|
|
||||||
if err == nil {
|
|
||||||
p.readSignal.Signal()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == errSlowDown {
|
|
||||||
p.readSignal.Signal()
|
|
||||||
|
|
||||||
// Yield current goroutine. Hopefully the reading counterpart can pick up the payload.
|
|
||||||
runtime.Gosched()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == errBufferFull && p.option.discardOverflow {
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != errBufferFull {
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
p.readSignal.Signal()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-p.writeSignal.Wait():
|
|
||||||
case <-p.done.Wait():
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pipe) Close() error {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
if p.state == closed || p.state == errord {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.state = closed
|
|
||||||
common.Must(p.done.Close())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt implements common.Interruptible.
|
|
||||||
func (p *pipe) Interrupt() {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
if p.state == closed || p.state == errord {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.state = errord
|
|
||||||
|
|
||||||
if !p.data.IsEmpty() {
|
|
||||||
buf.ReleaseMulti(p.data)
|
|
||||||
p.data = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
common.Must(p.done.Close())
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package pipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal"
|
|
||||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Option for creating new Pipes.
|
|
||||||
type Option func(*pipeOption)
|
|
||||||
|
|
||||||
// WithoutSizeLimit returns an Option for Pipe to have no size limit.
|
|
||||||
func WithoutSizeLimit() Option {
|
|
||||||
return func(opt *pipeOption) {
|
|
||||||
opt.limit = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSizeLimit returns an Option for Pipe to have the given size limit.
|
|
||||||
func WithSizeLimit(limit int32) Option {
|
|
||||||
return func(opt *pipeOption) {
|
|
||||||
opt.limit = limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscardOverflow returns an Option for Pipe to discard writes if full.
|
|
||||||
func DiscardOverflow() Option {
|
|
||||||
return func(opt *pipeOption) {
|
|
||||||
opt.discardOverflow = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Reader and Writer that connects to each other.
|
|
||||||
func New(opts ...Option) (*Reader, *Writer) {
|
|
||||||
p := &pipe{
|
|
||||||
readSignal: signal.NewNotifier(),
|
|
||||||
writeSignal: signal.NewNotifier(),
|
|
||||||
done: done.New(),
|
|
||||||
errChan: make(chan error, 1),
|
|
||||||
option: pipeOption{
|
|
||||||
limit: -1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&(p.option))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Reader{
|
|
||||||
pipe: p,
|
|
||||||
}, &Writer{
|
|
||||||
pipe: p,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package pipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/xray/buf"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reader is a buf.Reader that reads content from a pipe.
|
|
||||||
type Reader struct {
|
|
||||||
pipe *pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBuffer implements buf.Reader.
|
|
||||||
func (r *Reader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|
||||||
return r.pipe.ReadMultiBuffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMultiBufferTimeout reads content from a pipe within the given duration, or returns buf.ErrTimeout otherwise.
|
|
||||||
func (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {
|
|
||||||
return r.pipe.ReadMultiBufferTimeout(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt implements common.Interruptible.
|
|
||||||
func (r *Reader) Interrupt() {
|
|
||||||
r.pipe.Interrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReturnAnError makes ReadMultiBuffer return an error, only once.
|
|
||||||
func (r *Reader) ReturnAnError(err error) {
|
|
||||||
r.pipe.errChan <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recover catches an error set by ReturnAnError, if exists.
|
|
||||||
func (r *Reader) Recover() (err error) {
|
|
||||||
select {
|
|
||||||
case err = <-r.pipe.errChan:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package pipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/common/xray/buf"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Writer is a buf.Writer that writes data into a pipe.
|
|
||||||
type Writer struct {
|
|
||||||
pipe *pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMultiBuffer implements buf.Writer.
|
|
||||||
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
||||||
return w.pipe.WriteMultiBuffer(mb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements io.Closer. After the pipe is closed, writing to the pipe will return io.ErrClosedPipe, while reading will return io.EOF.
|
|
||||||
func (w *Writer) Close() error {
|
|
||||||
return w.pipe.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) Len() int32 {
|
|
||||||
return w.pipe.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt implements common.Interruptible.
|
|
||||||
func (w *Writer) Interrupt() {
|
|
||||||
w.pipe.Interrupt()
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package serial
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadUint16 reads first two bytes from the reader, and then converts them to an uint16 value.
|
|
||||||
func ReadUint16(reader io.Reader) (uint16, error) {
|
|
||||||
var b [2]byte
|
|
||||||
if _, err := io.ReadFull(reader, b[:]); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint16(b[:]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteUint16 writes an uint16 value into writer.
|
|
||||||
func WriteUint16(writer io.Writer, value uint16) (int, error) {
|
|
||||||
var b [2]byte
|
|
||||||
binary.BigEndian.PutUint16(b[:], value)
|
|
||||||
return writer.Write(b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteUint64 writes an uint64 value into writer.
|
|
||||||
func WriteUint64(writer io.Writer, value uint64) (int, error) {
|
|
||||||
var b [8]byte
|
|
||||||
binary.BigEndian.PutUint64(b[:], value)
|
|
||||||
return writer.Write(b[:])
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package serial
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToString serializes an arbitrary value into string.
|
|
||||||
func ToString(v interface{}) string {
|
|
||||||
if v == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
switch value := v.(type) {
|
|
||||||
case string:
|
|
||||||
return value
|
|
||||||
case *string:
|
|
||||||
return *value
|
|
||||||
case fmt.Stringer:
|
|
||||||
return value.String()
|
|
||||||
case error:
|
|
||||||
return value.Error()
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%+v", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concat concatenates all input into a single string.
|
|
||||||
func Concat(v ...interface{}) string {
|
|
||||||
builder := strings.Builder{}
|
|
||||||
for _, value := range v {
|
|
||||||
builder.WriteString(ToString(value))
|
|
||||||
}
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package done
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Instance is a utility for notifications of something being done.
|
|
||||||
type Instance struct {
|
|
||||||
access sync.Mutex
|
|
||||||
c chan struct{}
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new Done.
|
|
||||||
func New() *Instance {
|
|
||||||
return &Instance{
|
|
||||||
c: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done returns true if Close() is called.
|
|
||||||
func (d *Instance) Done() bool {
|
|
||||||
select {
|
|
||||||
case <-d.Wait():
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait returns a channel for waiting for done.
|
|
||||||
func (d *Instance) Wait() <-chan struct{} {
|
|
||||||
return d.c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close marks this Done 'done'. This method may be called multiple times. All calls after first call will have no effect on its status.
|
|
||||||
func (d *Instance) Close() error {
|
|
||||||
d.access.Lock()
|
|
||||||
defer d.access.Unlock()
|
|
||||||
|
|
||||||
if d.closed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
d.closed = true
|
|
||||||
close(d.c)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package signal
|
|
||||||
|
|
||||||
// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously.
|
|
||||||
type Notifier struct {
|
|
||||||
c chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNotifier creates a new Notifier.
|
|
||||||
func NewNotifier() *Notifier {
|
|
||||||
return &Notifier{
|
|
||||||
c: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal signals a change, usually by producer. This method never blocks.
|
|
||||||
func (n *Notifier) Signal() {
|
|
||||||
select {
|
|
||||||
case n.c <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait returns a channel for waiting for changes. The returned channel never gets closed.
|
|
||||||
func (n *Notifier) Wait() <-chan struct{} {
|
|
||||||
return n.c
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user