mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-08 19:54:12 +08:00
Compare commits
196 Commits
v1.12.0-al
...
dev-next
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3cc010880 | ||
![]() |
1920c191be | ||
![]() |
e0ac459204 | ||
![]() |
09fb897805 | ||
![]() |
a1b3d891a3 | ||
![]() |
d866a40469 | ||
![]() |
45cd04b07e | ||
![]() |
2cf0528c4d | ||
![]() |
905a2ded93 | ||
![]() |
cb3c0829c5 | ||
![]() |
1a8f6e053d | ||
![]() |
99a09a6ce5 | ||
![]() |
01b4c7fcdd | ||
![]() |
fe89f946c1 | ||
![]() |
6c17c7a8f5 | ||
![]() |
ea067e5478 | ||
![]() |
75af9a824e | ||
![]() |
a5d4a42119 | ||
![]() |
9821fbc3e3 | ||
![]() |
c0408ad1de | ||
![]() |
6b0e861afa | ||
![]() |
e32d686d6c | ||
![]() |
844308e128 | ||
![]() |
93c14db281 | ||
![]() |
b893a27dfc | ||
![]() |
d39960fa23 | ||
![]() |
ba0badd4bf | ||
![]() |
cfbb5d63d5 | ||
![]() |
8447a3edfe | ||
![]() |
1a9747a531 | ||
![]() |
583ecbea3b | ||
![]() |
bb6c8535a5 | ||
![]() |
10d90e4acc | ||
![]() |
e625012219 | ||
![]() |
670863fd5b | ||
![]() |
f7cf87142f | ||
![]() |
2597a68a01 | ||
![]() |
7354332daa | ||
![]() |
a0d382fc4e | ||
![]() |
a6da8b6654 | ||
![]() |
7385616cca | ||
![]() |
4b6784b446 | ||
![]() |
68579bb93b | ||
![]() |
6aace7b1b7 | ||
![]() |
148234b742 | ||
![]() |
97b7a451be | ||
![]() |
73b67e0b48 | ||
![]() |
88b4d04d59 | ||
![]() |
d1ec6c6dd2 | ||
![]() |
523825336a | ||
![]() |
032565a026 | ||
![]() |
aeea24ae30 | ||
![]() |
af22549f1a | ||
![]() |
57b17ceb4b | ||
![]() |
3dd308e7c3 | ||
![]() |
7f75195d86 | ||
![]() |
2fe4cad905 | ||
![]() |
f55eb75a53 | ||
![]() |
5ffb5b6ad2 | ||
![]() |
a1d5931759 | ||
![]() |
9e68e909cb | ||
![]() |
117e8b76cc | ||
![]() |
d2f83bfd50 | ||
![]() |
eaef13febe | ||
![]() |
0110c69dc9 | ||
![]() |
fb2f5af1fb | ||
![]() |
1553923118 | ||
![]() |
0ada49489d | ||
![]() |
95d5ca9393 | ||
![]() |
6cebbb4590 | ||
![]() |
0ef81bb5ef | ||
![]() |
0d30a1df9d | ||
![]() |
563499d2f9 | ||
![]() |
f10c0c1c8d | ||
![]() |
428074d88b | ||
![]() |
fa18832ad2 | ||
![]() |
87bce2de29 | ||
![]() |
f5020554e4 | ||
![]() |
31f3623b8a | ||
![]() |
bb42657177 | ||
![]() |
f19ff7eca7 | ||
![]() |
8e45133f2e | ||
![]() |
63df88675f | ||
![]() |
0423244298 | ||
![]() |
a5f1af9587 | ||
![]() |
112817c1a4 | ||
![]() |
6e91de51f1 | ||
![]() |
efc5c542fb | ||
![]() |
f1b569c7d1 | ||
![]() |
a752197d5e | ||
![]() |
65517d4513 | ||
![]() |
ccf4fa4d3a | ||
![]() |
18dbb823a1 | ||
![]() |
4ec058e91a | ||
![]() |
6eed06b2c2 | ||
![]() |
dd209cc9d5 | ||
![]() |
b0c0a6b07d | ||
![]() |
951a8fabbf | ||
![]() |
928298b528 | ||
![]() |
5b84fa0137 | ||
![]() |
2bb85ac8a1 | ||
![]() |
43a9016c83 | ||
![]() |
255068fd40 | ||
![]() |
098a00b025 | ||
![]() |
dba0b5276b | ||
![]() |
78ae935468 | ||
![]() |
3ea5f76470 | ||
![]() |
b4d294c05e | ||
![]() |
83cf5f5c6a | ||
![]() |
e7b3a8eebe | ||
![]() |
ee3a42a67e | ||
![]() |
50227c0f5f | ||
![]() |
bc5eb1e1a5 | ||
![]() |
995267a042 | ||
![]() |
41226a6075 | ||
![]() |
81d32181ce | ||
![]() |
c5ecca3938 | ||
![]() |
900888731c | ||
![]() |
13e648e4b1 | ||
![]() |
aff12ff671 | ||
![]() |
101fb88255 | ||
![]() |
8b489354e4 | ||
![]() |
7dea6eb7a6 | ||
![]() |
af1bfe4e3e | ||
![]() |
d574e9eb52 | ||
![]() |
2d7df1e1f2 | ||
![]() |
1c0ffcf5b1 | ||
![]() |
348cc39975 | ||
![]() |
987899f94a | ||
![]() |
d8b2d5142f | ||
![]() |
134802d1ee | ||
![]() |
e5e81b4de1 | ||
![]() |
300c961efa | ||
![]() |
7c7f512405 | ||
![]() |
03e8d029c2 | ||
![]() |
787b5f1931 | ||
![]() |
56a7624618 | ||
![]() |
3a84acf122 | ||
![]() |
f600e02e47 | ||
![]() |
e6d19de58a | ||
![]() |
f2bbf6b2aa | ||
![]() |
c54d50fd36 | ||
![]() |
6a051054db | ||
![]() |
49498f6439 | ||
![]() |
144a890c71 | ||
![]() |
afb4993445 | ||
![]() |
4c9455b944 | ||
![]() |
5fdc051a08 | ||
![]() |
cb68a40c43 | ||
![]() |
023218e6e7 | ||
![]() |
2a24b94b8d | ||
![]() |
c6531cf184 | ||
![]() |
d4fa0ed349 | ||
![]() |
10874d2dc4 | ||
![]() |
5adaf1ac75 | ||
![]() |
9668ea69b8 | ||
![]() |
ae9bc7acf1 | ||
![]() |
594ee480a2 | ||
![]() |
a15b5a2463 | ||
![]() |
991e755789 | ||
![]() |
97d41ffde8 | ||
![]() |
24af0766ac | ||
![]() |
af17eaa537 | ||
![]() |
3adc10a797 | ||
![]() |
5eeef6b28e | ||
![]() |
f4c29840c3 | ||
![]() |
47fc3ebda4 | ||
![]() |
9774a659b0 | ||
![]() |
2e4a6de4e7 | ||
![]() |
a530e424e9 | ||
![]() |
0bfd487ee9 | ||
![]() |
6aae834493 | ||
![]() |
f56131f38e | ||
![]() |
273a11d550 | ||
![]() |
ae8ce75e41 | ||
![]() |
d6d94b689f | ||
![]() |
30d785f1ee | ||
![]() |
db5ec3cdfc | ||
![]() |
9aca54d039 | ||
![]() |
d55d5009c2 | ||
![]() |
4f3ee61104 | ||
![]() |
96eb98c00a | ||
![]() |
68ce9577c6 | ||
![]() |
3ae036e997 | ||
![]() |
5da2d1d470 | ||
![]() |
8e2baf40f1 | ||
![]() |
c24c40dfee | ||
![]() |
32e52ce1ed | ||
![]() |
ed46438359 | ||
![]() |
0b5490d5a3 | ||
![]() |
2d73ef511d | ||
![]() |
63e6c85f6f | ||
![]() |
8946a6d2d0 | ||
![]() |
d3132645fb | ||
![]() |
373f158fe0 | ||
![]() |
ce36835fab |
30
.fpm_openwrt
Normal file
30
.fpm_openwrt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
-s dir
|
||||||
|
--name sing-box
|
||||||
|
--category net
|
||||||
|
--license GPL-3.0-or-later
|
||||||
|
--description "The universal proxy platform."
|
||||||
|
--url "https://sing-box.sagernet.org/"
|
||||||
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
|
--no-deb-generate-changes
|
||||||
|
|
||||||
|
--config-files /etc/config/sing-box
|
||||||
|
--config-files /etc/sing-box/config.json
|
||||||
|
|
||||||
|
--depends ca-bundle
|
||||||
|
--depends kmod-inet-diag
|
||||||
|
--depends kmod-tun
|
||||||
|
--depends firewall4
|
||||||
|
|
||||||
|
--before-remove release/config/openwrt.prerm
|
||||||
|
|
||||||
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
|
release/config/openwrt.conf=/etc/config/sing-box
|
||||||
|
release/config/openwrt.init=/etc/init.d/sing-box
|
||||||
|
release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box
|
||||||
|
|
||||||
|
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
25
.fpm_systemd
Normal file
25
.fpm_systemd
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
-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>"
|
||||||
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
|
--no-deb-generate-changes
|
||||||
|
--config-files /etc/sing-box/config.json
|
||||||
|
--after-install release/config/sing-box.postinst
|
||||||
|
|
||||||
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
|
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||||
|
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||||
|
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
||||||
|
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
28
.github/deb2ipk.sh
vendored
Executable file
28
.github/deb2ipk.sh
vendored
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
PROJECT=$(dirname "$0")/../..
|
||||||
|
TMP_PATH=`mktemp -d`
|
||||||
|
cp $2 $TMP_PATH
|
||||||
|
pushd $TMP_PATH
|
||||||
|
|
||||||
|
DEB_NAME=`ls *.deb`
|
||||||
|
ar x $DEB_NAME
|
||||||
|
|
||||||
|
mkdir control
|
||||||
|
pushd control
|
||||||
|
tar xf ../control.tar.gz
|
||||||
|
rm md5sums
|
||||||
|
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
|
||||||
|
cat control
|
||||||
|
tar czf ../control.tar.gz ./*
|
||||||
|
popd
|
||||||
|
|
||||||
|
DEB_NAME=${DEB_NAME%.deb}
|
||||||
|
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
|
||||||
|
popd
|
||||||
|
|
||||||
|
cp $TMP_PATH/$DEB_NAME.ipk $3
|
||||||
|
rm -r $TMP_PATH
|
7
.github/setup_legacy_go.sh
vendored
7
.github/setup_legacy_go.sh
vendored
@ -1,10 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.23.6"
|
VERSION="1.23.6"
|
||||||
|
|
||||||
|
mkdir -p $HOME/go
|
||||||
|
cd $HOME/go
|
||||||
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||||
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||||
mv go $HOME/go/go_legacy
|
mv go go_legacy
|
||||||
cd $HOME/go/go_legacy
|
cd go_legacy
|
||||||
|
|
||||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
# this patch file only works on golang1.23.x
|
# this patch file only works on golang1.23.x
|
||||||
|
319
.github/workflows/build.yml
vendored
319
.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
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@ -55,7 +55,7 @@ jobs:
|
|||||||
- name: Calculate version
|
- name: Calculate version
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/read_tag --nightly
|
go run -v ./cmd/internal/read_tag --ci --nightly
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
id: outputs
|
id: outputs
|
||||||
run: |-
|
run: |-
|
||||||
@ -69,72 +69,48 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: linux_386
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||||
goos: linux
|
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||||
goarch: 386
|
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
||||||
- name: linux_amd64
|
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
goos: linux
|
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
||||||
goarch: amd64
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
||||||
- name: linux_arm64
|
- { 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" }
|
||||||
goos: linux
|
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
||||||
goarch: arm64
|
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||||
- name: linux_arm
|
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||||
goos: linux
|
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
||||||
goarch: arm
|
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
||||||
goarm: 6
|
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
||||||
- name: linux_arm_v7
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
goos: linux
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
goarch: arm
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||||
goarm: 7
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
- name: linux_s390x
|
|
||||||
goos: linux
|
- { os: windows, arch: amd64 }
|
||||||
goarch: s390x
|
- { os: windows, arch: amd64, legacy_go: true }
|
||||||
- name: linux_riscv64
|
- { os: windows, arch: "386" }
|
||||||
goos: linux
|
- { os: windows, arch: "386", legacy_go: true }
|
||||||
goarch: riscv64
|
- { os: windows, arch: arm64 }
|
||||||
- name: linux_mips64le
|
|
||||||
goos: linux
|
- { os: darwin, arch: amd64 }
|
||||||
goarch: mips64le
|
- { os: darwin, arch: arm64 }
|
||||||
- name: windows_amd64
|
|
||||||
goos: windows
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||||
goarch: amd64
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
require_legacy_go: true
|
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||||
- name: windows_386
|
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||||
goos: windows
|
|
||||||
goarch: 386
|
|
||||||
require_legacy_go: true
|
|
||||||
- name: windows_arm64
|
|
||||||
goos: windows
|
|
||||||
goarch: arm64
|
|
||||||
- name: darwin_arm64
|
|
||||||
goos: darwin
|
|
||||||
goarch: arm64
|
|
||||||
- name: darwin_amd64
|
|
||||||
goos: darwin
|
|
||||||
goarch: amd64
|
|
||||||
- name: android_arm64
|
|
||||||
goos: android
|
|
||||||
goarch: arm64
|
|
||||||
- name: android_arm
|
|
||||||
goos: android
|
|
||||||
goarch: arm
|
|
||||||
goarm: 7
|
|
||||||
- name: android_amd64
|
|
||||||
goos: android
|
|
||||||
goarch: amd64
|
|
||||||
- name: android_386
|
|
||||||
goos: android
|
|
||||||
goarch: 386
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: ${{ ! matrix.legacy_go }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.3
|
||||||
- name: Cache legacy Go
|
- name: Cache Legacy Go
|
||||||
if: matrix.require_legacy_go
|
if: matrix.require_legacy_go
|
||||||
id: cache-legacy-go
|
id: cache-legacy-go
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@ -142,64 +118,167 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
~/go/go_legacy
|
~/go/go_legacy
|
||||||
key: go_legacy_1236
|
key: go_legacy_1236
|
||||||
- name: Setup legacy Go
|
- name: Setup Legacy Go
|
||||||
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
run: bash .github/setup_legacy_go.sh
|
run: |-
|
||||||
|
.github/setup_legacy_go.sh
|
||||||
|
- name: Setup Legacy Go 2
|
||||||
|
if: matrix.legacy_go
|
||||||
|
run: |-
|
||||||
|
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
||||||
|
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.goos == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28
|
||||||
local-cache: true
|
local-cache: true
|
||||||
- name: Setup Goreleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
with:
|
|
||||||
distribution: goreleaser-pro
|
|
||||||
version: '~> v2'
|
|
||||||
install-only: true
|
|
||||||
- name: Extract signing key
|
|
||||||
run: |-
|
|
||||||
mkdir -p $HOME/.gnupg
|
|
||||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
|
||||||
EOF
|
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Set build tags
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||||
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.goos != 'android'
|
if: matrix.os != 'android'
|
||||||
run: |-
|
run: |
|
||||||
goreleaser release --clean --split
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
CGO_ENABLED: "0"
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOOS: ${{ matrix.os }}
|
||||||
GOPATH: ${{ env.HOME }}/go
|
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 }}
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
|
||||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
|
||||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
|
||||||
- name: Build Android
|
- name: Build Android
|
||||||
if: matrix.goos == 'android'
|
if: matrix.os == 'android'
|
||||||
run: |-
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
go install -v ./cmd/internal/build
|
go install -v ./cmd/internal/build
|
||||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
|
export CC='${{ matrix.ndk }}-clang'
|
||||||
|
export CXX="${CC}++"
|
||||||
|
mkdir -p dist
|
||||||
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
BUILD_GOOS: ${{ matrix.goos }}
|
CGO_ENABLED: "1"
|
||||||
BUILD_GOARCH: ${{ matrix.goarch }}
|
BUILD_GOOS: ${{ matrix.os }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
BUILD_GOARCH: ${{ matrix.arch }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
- name: Set name
|
||||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
run: |-
|
||||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
||||||
|
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
||||||
|
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||||
|
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||||
|
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-legacy"
|
||||||
|
fi
|
||||||
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Package DEB
|
||||||
|
if: matrix.debian != ''
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y debsigs
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t deb \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
||||||
|
--architecture ${{ matrix.debian }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
|
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||||
|
rm -rf $HOME/.gnupg
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
||||||
|
- name: Package RPM
|
||||||
|
if: matrix.rpm != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t rpm \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
||||||
|
--architecture ${{ matrix.rpm }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
|
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||||
|
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
EOF
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
rpmsign --addsign dist/*.rpm
|
||||||
|
- name: Package Pacman
|
||||||
|
if: matrix.pacman != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libarchive-tools
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t pacman \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||||
|
--architecture ${{ matrix.pacman }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
- name: Package OpenWrt
|
||||||
|
if: matrix.openwrt != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
cp .fpm_openwrt .fpm
|
||||||
|
fpm -t deb \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/openwrt.deb" \
|
||||||
|
--architecture all \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
for architecture in ${{ matrix.openwrt }}; do
|
||||||
|
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
||||||
|
done
|
||||||
|
rm "dist/openwrt.deb"
|
||||||
|
- name: Archive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd dist
|
||||||
|
mkdir -p "${DIR_NAME}"
|
||||||
|
cp ../LICENSE "${DIR_NAME}"
|
||||||
|
if [ '${{ matrix.os }}' = 'windows' ]; then
|
||||||
|
cp sing-box "${DIR_NAME}/sing-box.exe"
|
||||||
|
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
||||||
|
else
|
||||||
|
cp sing-box "${DIR_NAME}"
|
||||||
|
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
||||||
|
fi
|
||||||
|
rm -r "${DIR_NAME}"
|
||||||
|
- name: Cleanup
|
||||||
|
run: rm dist/sing-box
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.name }}
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||||
path: 'dist'
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||||
@ -215,7 +294,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@ -271,13 +350,11 @@ jobs:
|
|||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
- name: Prepare upload
|
- name: Prepare upload
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p dist/release
|
mkdir -p dist
|
||||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
||||||
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
|
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-android-apks
|
name: binary-android-apks
|
||||||
@ -297,7 +374,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@ -395,7 +472,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.3
|
||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
@ -472,10 +549,13 @@ jobs:
|
|||||||
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||||
|
- name: Update version
|
||||||
|
if: matrix.if && matrix.name != 'iOS'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_apple_version --ci
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/update_apple_version --ci
|
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
xcodebuild archive \
|
xcodebuild archive \
|
||||||
-scheme "${{ matrix.scheme }}" \
|
-scheme "${{ matrix.scheme }}" \
|
||||||
@ -524,9 +604,9 @@ jobs:
|
|||||||
zip -r SFM.dSYMs.zip dSYMs
|
zip -r SFM.dSYMs.zip dSYMs
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p dist/release
|
mkdir -p dist
|
||||||
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
|
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
|
||||||
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
|
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
- name: Upload image
|
- name: Upload image
|
||||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@ -547,12 +627,6 @@ jobs:
|
|||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Goreleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
with:
|
|
||||||
distribution: goreleaser-pro
|
|
||||||
version: 2.5.1
|
|
||||||
install-only: true
|
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
id: cache-ghr
|
id: cache-ghr
|
||||||
@ -577,26 +651,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Merge builds
|
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
|
||||||
run: |-
|
|
||||||
goreleaser continue --merge --skip publish
|
|
||||||
mkdir -p dist/release
|
|
||||||
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
|
||||||
- name: Upload builds
|
- name: Upload builds
|
||||||
if: ${{ env.PUBLISHED == 'false' }}
|
if: ${{ env.PUBLISHED == 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Replace builds
|
- name: Replace builds
|
||||||
if: ${{ env.PUBLISHED != 'false' }}
|
if: ${{ env.PUBLISHED != 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace -p 5 "v${VERSION}" dist/release
|
ghr --replace -p 5 "v${VERSION}" dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
3
.github/workflows/lint.yml
vendored
3
.github/workflows/lint.yml
vendored
@ -28,10 +28,11 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.3
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
|
verify: false
|
||||||
|
182
.github/workflows/linux.yml
vendored
182
.github/workflows/linux.yml
vendored
@ -1,13 +1,22 @@
|
|||||||
name: Release to Linux repository
|
name: Build Linux Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version name"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
calculate_version:
|
||||||
|
name: Calculate version
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
@ -16,23 +25,160 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.3
|
||||||
- name: Extract signing key
|
- name: Check input version
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p $HOME/.gnupg
|
echo "version=${{ inputs.version }}"
|
||||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Calculate version
|
||||||
|
if: github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/read_tag --ci --nightly
|
||||||
|
- name: Set outputs
|
||||||
|
id: outputs
|
||||||
|
run: |-
|
||||||
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
build:
|
||||||
|
name: Build binary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||||
|
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||||
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||||
|
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||||
|
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||||
|
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
|
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||||
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||||
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.24.3
|
||||||
|
- name: Setup Android NDK
|
||||||
|
if: matrix.os == 'android'
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28
|
||||||
|
local-cache: true
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Set build tags
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||||
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: ${{ matrix.os }}
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Set mtime
|
||||||
|
run: |-
|
||||||
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
|
- name: Set name
|
||||||
|
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||||
|
- name: Set beta name
|
||||||
|
if: contains(needs.calculate_version.outputs.version, '-')
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
|
- name: Set version
|
||||||
|
run: |-
|
||||||
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Package DEB
|
||||||
|
if: matrix.debian != ''
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get install -y debsigs
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t deb \
|
||||||
|
--name "${NAME}" \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
||||||
|
--architecture ${{ matrix.debian }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
|
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||||
|
rm -rf $HOME/.gnupg
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
${{ secrets.GPG_KEY }}
|
${{ secrets.GPG_KEY }}
|
||||||
EOF
|
EOF
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
||||||
- name: Publish release
|
- name: Package RPM
|
||||||
uses: goreleaser/goreleaser-action@v6
|
if: matrix.rpm != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
cp .fpm_systemd .fpm
|
||||||
|
fpm -t rpm \
|
||||||
|
--name "${NAME}" \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
||||||
|
--architecture ${{ matrix.rpm }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
|
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||||
|
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
EOF
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
rpmsign --addsign dist/*.rpm
|
||||||
|
- name: Cleanup
|
||||||
|
run: rm dist/sing-box
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||||
version: latest
|
path: "dist"
|
||||||
args: release -f .goreleaser.fury.yaml --clean
|
upload:
|
||||||
env:
|
name: Upload builds
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
runs-on: ubuntu-latest
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
needs:
|
||||||
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
- calculate_version
|
||||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
- build
|
||||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Download builds
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Publish packages
|
||||||
|
run: |-
|
||||||
|
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
||||||
|
@ -21,14 +21,13 @@ linters-settings:
|
|||||||
- -SA1003
|
- -SA1003
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.24"
|
go: "1.23"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
@ -50,12 +49,18 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: "config|noreplace"
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
- src: release/config/sing-box.sysusers
|
||||||
|
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||||
|
- src: release/config/sing-box.rules
|
||||||
|
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
- src: release/config/sing-box-split-dns.xml
|
||||||
|
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
- src: release/completions/sing-box.bash
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
@ -17,7 +17,6 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
@ -47,7 +46,6 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
@ -126,18 +124,24 @@ nfpms:
|
|||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
- archlinux
|
||||||
# - apk
|
# - apk
|
||||||
# - ipk
|
# - ipk
|
||||||
priority: extra
|
priority: extra
|
||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: "config|noreplace"
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
- src: release/config/sing-box.sysusers
|
||||||
|
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||||
|
- src: release/config/sing-box.rules
|
||||||
|
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
- src: release/config/sing-box-split-dns.xml
|
||||||
|
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
- src: release/completions/sing-box.bash
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
@ -13,7 +13,7 @@ RUN set -ex \
|
|||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags \
|
&& go build -v -trimpath -tags \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
20
Makefile
20
Makefile
@ -1,16 +1,13 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
|
||||||
TAGS_GO123 = with_tailscale
|
|
||||||
TAGS ?= $(TAGS_GO120),$(TAGS_GO123)
|
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
|
||||||
@ -20,18 +17,13 @@ build:
|
|||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
ci_build_go120:
|
|
||||||
export GOTOOLCHAIN=local && \
|
|
||||||
go build $(PARAMS) $(MAIN) && \
|
|
||||||
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
|
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(PARAMS) $(MAIN) && \
|
go build $(PARAMS) $(MAIN) && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
@ -233,8 +225,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.4
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
@ -7,7 +7,9 @@ import (
|
|||||||
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-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@ -33,11 +35,30 @@ type DNSClient interface {
|
|||||||
type DNSQueryOptions struct {
|
type DNSQueryOptions struct {
|
||||||
Transport DNSTransport
|
Transport DNSTransport
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
|
LookupStrategy C.DomainStrategy
|
||||||
DisableCache bool
|
DisableCache bool
|
||||||
RewriteTTL *uint32
|
RewriteTTL *uint32
|
||||||
ClientSubnet netip.Prefix
|
ClientSubnet netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
||||||
|
if options == nil {
|
||||||
|
return &DNSQueryOptions{}, nil
|
||||||
|
}
|
||||||
|
transportManager := service.FromContext[DNSTransportManager](ctx)
|
||||||
|
transport, loaded := transportManager.Transport(options.Server)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("domain resolver not found: " + options.Server)
|
||||||
|
}
|
||||||
|
return &DNSQueryOptions{
|
||||||
|
Transport: transport,
|
||||||
|
Strategy: C.DomainStrategy(options.Strategy),
|
||||||
|
DisableCache: options.DisableCache,
|
||||||
|
RewriteTTL: options.RewriteTTL,
|
||||||
|
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type RDRCStore interface {
|
type RDRCStore interface {
|
||||||
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
|
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
|
||||||
SaveRDRC(transportName string, qName string, qType uint16) error
|
SaveRDRC(transportName string, qName string, qType uint16) error
|
||||||
@ -45,10 +66,10 @@ type RDRCStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSTransport interface {
|
type DNSTransport interface {
|
||||||
|
Lifecycle
|
||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
Reset()
|
|
||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FakeIPStore interface {
|
type FakeIPStore interface {
|
||||||
Service
|
SimpleLifecycle
|
||||||
Contains(address netip.Addr) bool
|
Contains(address netip.Addr) bool
|
||||||
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
||||||
Lookup(address netip.Addr) (string, bool)
|
Lookup(address netip.Addr) (string, bool)
|
||||||
|
@ -57,6 +57,7 @@ type InboundContext struct {
|
|||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
|
PacketSniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ type InboundContext struct {
|
|||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
TLSFragment bool
|
TLSFragment bool
|
||||||
TLSFragmentFallbackDelay time.Duration
|
TLSFragmentFallbackDelay time.Duration
|
||||||
|
TLSRecordFragment bool
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
|
@ -37,13 +37,14 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
|
|||||||
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
for _, inbound := range m.inbounds {
|
inbounds := m.inbounds
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, inbound := range inbounds {
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
@ -2,6 +2,11 @@ package adapter
|
|||||||
|
|
||||||
import E "github.com/sagernet/sing/common/exceptions"
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
type SimpleLifecycle interface {
|
||||||
|
Start() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
type StartStage uint8
|
type StartStage uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -28,13 +28,13 @@ func LegacyStart(starter any, stage StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lifecycleServiceWrapper struct {
|
type lifecycleServiceWrapper struct {
|
||||||
Service
|
SimpleLifecycle
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLifecycleService(service Service, name string) LifecycleService {
|
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
|
||||||
return &lifecycleServiceWrapper{
|
return &lifecycleServiceWrapper{
|
||||||
Service: service,
|
SimpleLifecycle: service,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||||
return LegacyStart(l.Service, stage)
|
return LegacyStart(l.SimpleLifecycle, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Close() error {
|
func (l *lifecycleServiceWrapper) Close() error {
|
||||||
return l.Service.Close()
|
return l.SimpleLifecycle.Close()
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ type Router interface {
|
|||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
SetTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ type HeadlessRule interface {
|
|||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
Service
|
SimpleLifecycle
|
||||||
Type() string
|
Type() string
|
||||||
Action() RuleAction
|
Action() RuleAction
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,27 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Start() error
|
Lifecycle
|
||||||
Close() error
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceRegistry interface {
|
||||||
|
option.ServiceOptionsRegistry
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Services() []Service
|
||||||
|
Get(tag string) (Service, bool)
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
|
||||||
}
|
}
|
||||||
|
21
adapter/service/adapter.go
Normal file
21
adapter/service/adapter.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
serviceType string
|
||||||
|
serviceTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(serviceType string, serviceTag string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
serviceType: serviceType,
|
||||||
|
serviceTag: serviceTag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.serviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.serviceTag
|
||||||
|
}
|
144
adapter/service/manager.go
Normal file
144
adapter/service/manager.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.ServiceManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.ServiceRegistry
|
||||||
|
access sync.Mutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
services []adapter.Service
|
||||||
|
serviceByTag map[string]adapter.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
serviceByTag: make(map[string]adapter.Service),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
services := m.services
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, service := range services {
|
||||||
|
err := adapter.LegacyStart(service, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if !m.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
services := m.services
|
||||||
|
m.services = nil
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for _, service := range services {
|
||||||
|
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
err = E.Append(err, service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Services() []adapter.Service {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.services
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Get(tag string) (adapter.Service, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
service, found := m.serviceByTag[tag]
|
||||||
|
m.access.Unlock()
|
||||||
|
return service, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
service, found := m.serviceByTag[tag]
|
||||||
|
if !found {
|
||||||
|
m.access.Unlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.serviceByTag, tag)
|
||||||
|
index := common.Index(m.services, func(it adapter.Service) bool {
|
||||||
|
return it == service
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid service index")
|
||||||
|
}
|
||||||
|
m.services = append(m.services[:index], m.services[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
m.access.Unlock()
|
||||||
|
if started {
|
||||||
|
return service.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
|
||||||
|
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(service, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = existsService.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
|
||||||
|
return it == existsService
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid service index")
|
||||||
|
}
|
||||||
|
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.services = append(m.services, service)
|
||||||
|
m.serviceByTag[tag] = service
|
||||||
|
return nil
|
||||||
|
}
|
72
adapter/service/registry.go
Normal file
72
adapter/service/registry.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.ServiceRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructor map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructor: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
constructor, loaded := m.constructor[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.optionsType[outboundType] = optionsConstructor
|
||||||
|
m.constructor[outboundType] = constructor
|
||||||
|
}
|
18
adapter/ssm.go
Normal file
18
adapter/ssm.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ManagedSSMServer interface {
|
||||||
|
Inbound
|
||||||
|
SetTracker(tracker SSMTracker)
|
||||||
|
UpdateUsers(users []string, uPSKs []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSMTracker interface {
|
||||||
|
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
|
||||||
|
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
|
||||||
|
}
|
@ -3,6 +3,6 @@ package adapter
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type TimeService interface {
|
type TimeService interface {
|
||||||
Service
|
SimpleLifecycle
|
||||||
TimeFunc() func() time.Time
|
TimeFunc() func() time.Time
|
||||||
}
|
}
|
||||||
|
88
box.go
88
box.go
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
"github.com/sagernet/sing-box/common/certificate"
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
"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"
|
||||||
@ -34,7 +35,7 @@ import (
|
|||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Service = (*Box)(nil)
|
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
@ -44,11 +45,12 @@ type Box struct {
|
|||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
|
service *boxService.Manager
|
||||||
dnsTransport *dns.TransportManager
|
dnsTransport *dns.TransportManager
|
||||||
dnsRouter *dns.Router
|
dnsRouter *dns.Router
|
||||||
connection *route.ConnectionManager
|
connection *route.ConnectionManager
|
||||||
router *route.Router
|
router *route.Router
|
||||||
services []adapter.LifecycleService
|
internalService []adapter.LifecycleService
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +66,7 @@ func Context(
|
|||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
|
serviceRegistry adapter.ServiceRegistry,
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@ -84,6 +87,10 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
}
|
}
|
||||||
|
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
||||||
|
}
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +106,7 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
|
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@ -109,6 +117,12 @@ func New(options Options) (*Box, error) {
|
|||||||
if outboundRegistry == nil {
|
if outboundRegistry == nil {
|
||||||
return nil, E.New("missing outbound registry in context")
|
return nil, E.New("missing outbound registry in context")
|
||||||
}
|
}
|
||||||
|
if dnsTransportRegistry == nil {
|
||||||
|
return nil, E.New("missing DNS transport registry in context")
|
||||||
|
}
|
||||||
|
if serviceRegistry == nil {
|
||||||
|
return nil, E.New("missing service registry in context")
|
||||||
|
}
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@ -142,7 +156,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
|
||||||
var services []adapter.LifecycleService
|
var internalServices []adapter.LifecycleService
|
||||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||||
len(certificateOptions.Certificate) > 0 ||
|
len(certificateOptions.Certificate) > 0 ||
|
||||||
@ -153,7 +167,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
services = append(services, certificateStore)
|
internalServices = append(internalServices, certificateStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
@ -162,10 +176,12 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
|
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
@ -216,8 +232,15 @@ func New(options Options) (*Box, error) {
|
|||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
|
endpointCtx := ctx
|
||||||
|
if tag != "" {
|
||||||
|
// TODO: remove this
|
||||||
|
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
|
||||||
|
Outbound: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
err = endpointManager.Create(
|
err = endpointManager.Create(
|
||||||
ctx,
|
endpointCtx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
@ -273,6 +296,24 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i, serviceOptions := range options.Services {
|
||||||
|
var tag string
|
||||||
|
if serviceOptions.Tag != "" {
|
||||||
|
tag = serviceOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = serviceManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
serviceOptions.Type,
|
||||||
|
serviceOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
outboundManager.Initialize(common.Must1(
|
outboundManager.Initialize(common.Must1(
|
||||||
direct.NewOutbound(
|
direct.NewOutbound(
|
||||||
ctx,
|
ctx,
|
||||||
@ -298,7 +339,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
services = append(services, cacheFile)
|
internalServices = append(internalServices, cacheFile)
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
@ -307,9 +348,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash-server")
|
return nil, E.Cause(err, "create clash-server")
|
||||||
}
|
}
|
||||||
router.SetTracker(clashServer)
|
router.AppendTracker(clashServer)
|
||||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||||
services = append(services, clashServer)
|
internalServices = append(internalServices, clashServer)
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
@ -317,8 +358,8 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create v2ray-server")
|
return nil, E.Cause(err, "create v2ray-server")
|
||||||
}
|
}
|
||||||
if v2rayServer.StatsService() != nil {
|
if v2rayServer.StatsService() != nil {
|
||||||
router.SetTracker(v2rayServer.StatsService())
|
router.AppendTracker(v2rayServer.StatsService())
|
||||||
services = append(services, v2rayServer)
|
internalServices = append(internalServices, v2rayServer)
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,7 +377,7 @@ func New(options Options) (*Box, error) {
|
|||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
})
|
})
|
||||||
timeService.TimeService = ntpService
|
timeService.TimeService = ntpService
|
||||||
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
@ -344,13 +385,14 @@ func New(options Options) (*Box, error) {
|
|||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
|
service: serviceManager,
|
||||||
dnsRouter: dnsRouter,
|
dnsRouter: dnsRouter,
|
||||||
connection: connectionManager,
|
connection: connectionManager,
|
||||||
router: router,
|
router: router,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
services: services,
|
internalService: internalServices,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -401,11 +443,11 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -421,31 +463,27 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.inbound.Start(adapter.StartStateStart)
|
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -462,7 +500,7 @@ func (s *Box) Close() error {
|
|||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.services {
|
for _, lifecycleService := range s.internalService {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
})
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 0576fd75a67e56048c29d00ef539b4f8f05aec2a
|
Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0
|
@ -1 +1 @@
|
|||||||
Subproject commit a828bb3a93b57d0c1b13d74246f0675c5244466d
|
Subproject commit ae5818ee5a24af965dc91f80bffa16e1e6c109c1
|
@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
client := createClient(10 * time.Minute)
|
client := createClient(20 * time.Minute)
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
build := builds.Data[0]
|
||||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
|
@ -45,6 +45,7 @@ var (
|
|||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
iosTags []string
|
iosTags []string
|
||||||
|
memcTags []string
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,8 +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_tailscale")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||||
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,18 +101,19 @@ func buildAndroid() {
|
|||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-tags")
|
tags := append(sharedTags, memcTags...)
|
||||||
if !debugEnabled {
|
if debugEnabled {
|
||||||
args = append(args, strings.Join(sharedTags, ","))
|
tags = append(tags, debugTags...)
|
||||||
} else {
|
|
||||||
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = append(args, "-tags", strings.Join(tags, ","))
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||||
@ -148,7 +151,9 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
@ -156,12 +161,11 @@ func buildApple() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, iosTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
args = append(args, "-tags")
|
if debugEnabled {
|
||||||
if !debugEnabled {
|
tags = append(tags, debugTags...)
|
||||||
args = append(args, strings.Join(tags, ","))
|
|
||||||
} else {
|
|
||||||
args = append(args, strings.Join(append(tags, debugTags...), ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = append(args, "-tags", strings.Join(tags, ","))
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||||
|
@ -5,40 +5,49 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
|
"github.com/sagernet/sing-box/common/badversion"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nightly bool
|
var (
|
||||||
|
flagRunInCI bool
|
||||||
|
flagRunNightly bool
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
|
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if nightly {
|
var (
|
||||||
version, err := build_shared.ReadTagVersionRev()
|
versionStr string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if flagRunNightly {
|
||||||
|
var version badversion.Version
|
||||||
|
version, err = build_shared.ReadTagVersion()
|
||||||
|
if err == nil {
|
||||||
|
versionStr = version.String()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
versionStr, err = build_shared.ReadTag()
|
||||||
|
}
|
||||||
|
if flagRunInCI {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
var versionStr string
|
|
||||||
if version.PreReleaseIdentifier != "" {
|
|
||||||
versionStr = version.VersionString() + "-nightly"
|
|
||||||
} else {
|
|
||||||
version.Patch++
|
|
||||||
versionStr = version.VersionString() + "-nightly"
|
|
||||||
}
|
|
||||||
err = setGitHubEnv("version", versionStr)
|
err = setGitHubEnv("version", versionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tag, err := build_shared.ReadTag()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
os.Stdout.WriteString("unknown\n")
|
os.Stdout.WriteString("unknown\n")
|
||||||
} else {
|
} else {
|
||||||
os.Stdout.WriteString(tag + "\n")
|
os.Stdout.WriteString(versionStr + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@ -68,6 +67,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
||||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read rule-set")
|
return E.Cause(err, "read rule-set")
|
||||||
}
|
}
|
||||||
|
if flagRuleSetMatchFormat == "" {
|
||||||
|
switch filepath.Ext(sourcePath) {
|
||||||
|
case ".json":
|
||||||
|
flagRuleSetMatchFormat = C.RuleSetFormatSource
|
||||||
|
case ".srs":
|
||||||
|
flagRuleSetMatchFormat = C.RuleSetFormatBinary
|
||||||
|
}
|
||||||
|
}
|
||||||
var ruleSet option.PlainRuleSetCompat
|
var ruleSet option.PlainRuleSetCompat
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
case C.RuleSetFormatSource:
|
||||||
|
@ -7,7 +7,8 @@ import (
|
|||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/utls"
|
|
||||||
|
"github.com/metacubex/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -24,8 +25,8 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
|
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
||||||
func utlsReadRecord(c *tls.Conn) error
|
func utlsReadRecord(c *tls.Conn) error
|
||||||
|
|
||||||
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
|
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
||||||
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@ -35,6 +36,7 @@ type DefaultDialer struct {
|
|||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
|
netns string
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
networkStrategy *C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
defaultNetworkStrategy bool
|
defaultNetworkStrategy bool
|
||||||
@ -64,23 +66,19 @@ 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 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
if !C.IsLinux {
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
return nil, E.New("`routing_mark` is only supported on Linux")
|
||||||
}
|
|
||||||
if networkManager != nil {
|
|
||||||
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
|
|
||||||
if autoRedirectOutputMark > 0 {
|
|
||||||
if options.RoutingMark > 0 {
|
|
||||||
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
|
|
||||||
}
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
|
||||||
}
|
}
|
||||||
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
}
|
}
|
||||||
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
||||||
if disableDefaultBind || options.TCPFastOpen {
|
if disableDefaultBind || options.TCPFastOpen {
|
||||||
@ -99,10 +97,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else if networkManager.AutoDetectInterface() {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
if networkStrategy == nil {
|
|
||||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
|
||||||
defaultNetworkStrategy = true
|
|
||||||
}
|
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
@ -114,6 +108,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
}
|
}
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
bindFunc := networkManager.ProtectFunc()
|
bindFunc := networkManager.ProtectFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
@ -125,8 +123,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
@ -198,6 +196,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpListener: listener,
|
udpListener: listener,
|
||||||
udpAddr4: udpAddr4,
|
udpAddr4: udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6: udpAddr6,
|
||||||
|
netns: options.NetNs,
|
||||||
networkManager: networkManager,
|
networkManager: networkManager,
|
||||||
networkStrategy: networkStrategy,
|
networkStrategy: networkStrategy,
|
||||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||||
@ -207,6 +206,22 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefault bool) control.Func {
|
||||||
|
if networkManager == nil {
|
||||||
|
return control.RoutingMark(mark)
|
||||||
|
}
|
||||||
|
return func(network, address string, conn syscall.RawConn) error {
|
||||||
|
if networkManager.AutoRedirectOutputMark() != 0 {
|
||||||
|
if isDefault {
|
||||||
|
return E.New("`route.default_mark` is conflict with `tun.auto_redirect`")
|
||||||
|
} else {
|
||||||
|
return E.New("`routing_mark` is conflict with `tun.auto_redirect`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return control.RoutingMark(mark)(network, address, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
@ -214,19 +229,21 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
return nil, E.New("domain not resolved")
|
return nil, E.New("domain not resolved")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
|
return trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
return d.udpDialer4.DialContext(ctx, network, address.String())
|
||||||
} else {
|
} else {
|
||||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
return d.udpDialer6.DialContext(ctx, network, address.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
return DialSlowContext(&d.dialer4, ctx, network, address)
|
||||||
} else {
|
} else {
|
||||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
return DialSlowContext(&d.dialer6, ctx, network, address)
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||||
}
|
}
|
||||||
@ -282,13 +299,15 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
|
return trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
||||||
if destination.IsIPv6() {
|
if destination.IsIPv6() {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
|
||||||
} else {
|
} else {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||||
}
|
}
|
||||||
@ -328,7 +347,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||||
return d.udpListener.ListenPacket(context.Background(), network, address)
|
udpListener := d.udpListener
|
||||||
|
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
|
for _, wgControlFn := range WgControlFns {
|
||||||
|
err := wgControlFn(network, address, conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return udpListener.ListenPacket(context.Background(), network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
|
@ -6,26 +6,39 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DirectDialer interface {
|
||||||
|
IsEmpty() bool
|
||||||
|
}
|
||||||
|
|
||||||
type DetourDialer struct {
|
type DetourDialer struct {
|
||||||
outboundManager adapter.OutboundManager
|
outboundManager adapter.OutboundManager
|
||||||
detour string
|
detour string
|
||||||
|
legacyDNSDialer bool
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
initErr error
|
initErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer {
|
func NewDetour(outboundManager adapter.OutboundManager, detour string, legacyDNSDialer bool) N.Dialer {
|
||||||
return &DetourDialer{outboundManager: outboundManager, detour: detour}
|
return &DetourDialer{
|
||||||
|
outboundManager: outboundManager,
|
||||||
|
detour: detour,
|
||||||
|
legacyDNSDialer: legacyDNSDialer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Start() error {
|
func InitializeDetour(dialer N.Dialer) error {
|
||||||
_, err := d.Dialer()
|
detourDialer, isDetour := common.Cast[*DetourDialer](dialer)
|
||||||
return err
|
if !isDetour {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return common.Error(detourDialer.Dialer())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
||||||
@ -34,11 +47,20 @@ func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) init() {
|
func (d *DetourDialer) init() {
|
||||||
var loaded bool
|
dialer, loaded := d.outboundManager.Outbound(d.detour)
|
||||||
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
|
|
||||||
if !loaded {
|
if !loaded {
|
||||||
d.initErr = E.New("outbound detour not found: ", d.detour)
|
d.initErr = E.New("outbound detour not found: ", d.detour)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if !d.legacyDNSDialer {
|
||||||
|
if directDialer, isDirect := dialer.(DirectDialer); isDirect {
|
||||||
|
if directDialer.IsEmpty() {
|
||||||
|
d.initErr = E.New("detour to an empty direct outbound makes no sense")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.dialer = dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
@ -23,6 +23,8 @@ type Options struct {
|
|||||||
DirectResolver bool
|
DirectResolver bool
|
||||||
ResolverOnDetour bool
|
ResolverOnDetour bool
|
||||||
NewDialer bool
|
NewDialer bool
|
||||||
|
LegacyDNSDialer bool
|
||||||
|
DirectOutbound bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge with NewWithOptions
|
// TODO: merge with NewWithOptions
|
||||||
@ -45,14 +47,14 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
if outboundManager == nil {
|
if outboundManager == nil {
|
||||||
return nil, E.New("missing outbound manager")
|
return nil, E.New("missing outbound manager")
|
||||||
}
|
}
|
||||||
dialer = NewDetour(outboundManager, dialOptions.Detour)
|
dialer = NewDetour(outboundManager, dialOptions.Detour, options.LegacyDNSDialer)
|
||||||
} else {
|
} else {
|
||||||
dialer, err = NewDefault(options.Context, dialOptions)
|
dialer, err = NewDefault(options.Context, dialOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) {
|
if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour || dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "") {
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](options.Context)
|
networkManager := service.FromContext[adapter.NetworkManager](options.Context)
|
||||||
dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context)
|
dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context)
|
||||||
var defaultOptions adapter.NetworkOptions
|
var defaultOptions adapter.NetworkOptions
|
||||||
@ -81,6 +83,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||||
|
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
||||||
}
|
}
|
||||||
server = dialOptions.DomainResolver.Server
|
server = dialOptions.DomainResolver.Server
|
||||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||||
@ -93,7 +96,8 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
} else if options.DirectResolver {
|
} else if options.DirectResolver {
|
||||||
return nil, E.New("missing domain resolver for domain server address")
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
} else if defaultOptions.DomainResolver != "" {
|
} else {
|
||||||
|
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 {
|
||||||
@ -101,11 +105,24 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
}
|
}
|
||||||
dnsQueryOptions.Transport = transport
|
dnsQueryOptions.Transport = transport
|
||||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
|
} else {
|
||||||
|
transports := dnsTransport.Transports()
|
||||||
|
if len(transports) < 2 {
|
||||||
|
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||||
} else if options.NewDialer {
|
} else if options.NewDialer {
|
||||||
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 !options.DirectOutbound {
|
||||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if
|
||||||
|
//nolint:staticcheck
|
||||||
|
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||||
|
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
dialer = NewResolveDialer(
|
dialer = NewResolveDialer(
|
||||||
options.Context,
|
options.Context,
|
||||||
dialer,
|
dialer,
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@ -76,10 +75,11 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
return c.conn.Write(b)
|
return c.conn.Write(b)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.conn = nil
|
c.err = err
|
||||||
c.err = E.Cause(err, "dial tcp fast open")
|
} else {
|
||||||
|
c.conn = conn
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
|
@ -1,158 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IEC Sizes.
|
|
||||||
// kibis of bits
|
|
||||||
const (
|
|
||||||
Byte = 1 << (iota * 10)
|
|
||||||
KiByte
|
|
||||||
MiByte
|
|
||||||
GiByte
|
|
||||||
TiByte
|
|
||||||
PiByte
|
|
||||||
EiByte
|
|
||||||
)
|
|
||||||
|
|
||||||
// SI Sizes.
|
|
||||||
const (
|
|
||||||
IByte = 1
|
|
||||||
KByte = IByte * 1000
|
|
||||||
MByte = KByte * 1000
|
|
||||||
GByte = MByte * 1000
|
|
||||||
TByte = GByte * 1000
|
|
||||||
PByte = TByte * 1000
|
|
||||||
EByte = PByte * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultSizeTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"kib": KiByte,
|
|
||||||
"kb": KByte,
|
|
||||||
"mib": MiByte,
|
|
||||||
"mb": MByte,
|
|
||||||
"gib": GiByte,
|
|
||||||
"gb": GByte,
|
|
||||||
"tib": TiByte,
|
|
||||||
"tb": TByte,
|
|
||||||
"pib": PiByte,
|
|
||||||
"pb": PByte,
|
|
||||||
"eib": EiByte,
|
|
||||||
"eb": EByte,
|
|
||||||
// Without suffix
|
|
||||||
"": Byte,
|
|
||||||
"ki": KiByte,
|
|
||||||
"k": KByte,
|
|
||||||
"mi": MiByte,
|
|
||||||
"m": MByte,
|
|
||||||
"gi": GiByte,
|
|
||||||
"g": GByte,
|
|
||||||
"ti": TiByte,
|
|
||||||
"t": TByte,
|
|
||||||
"pi": PiByte,
|
|
||||||
"p": PByte,
|
|
||||||
"ei": EiByte,
|
|
||||||
"e": EByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var memorysSizeTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"kb": KiByte,
|
|
||||||
"mb": MiByte,
|
|
||||||
"gb": GiByte,
|
|
||||||
"tb": TiByte,
|
|
||||||
"pb": PiByte,
|
|
||||||
"eb": EiByte,
|
|
||||||
"": Byte,
|
|
||||||
"k": KiByte,
|
|
||||||
"m": MiByte,
|
|
||||||
"g": GiByte,
|
|
||||||
"t": TiByte,
|
|
||||||
"p": PiByte,
|
|
||||||
"e": EiByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
|
||||||
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func Bytes(s uint64) string {
|
|
||||||
return humanateBytes(s, 1000, defaultSizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MemoryBytes(s uint64) string {
|
|
||||||
return humanateBytes(s, 1024, defaultSizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IBytes(s uint64) string {
|
|
||||||
return humanateBytes(s, 1024, iSizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logn(n, b float64) float64 {
|
|
||||||
return math.Log(n) / math.Log(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
|
||||||
if s < 10 {
|
|
||||||
return fmt.Sprintf("%d B", s)
|
|
||||||
}
|
|
||||||
e := math.Floor(logn(float64(s), base))
|
|
||||||
suffix := sizes[int(e)]
|
|
||||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
|
||||||
f := "%.0f %s"
|
|
||||||
if val < 10 {
|
|
||||||
f = "%.1f %s"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(f, val, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseBytes(s string) (uint64, error) {
|
|
||||||
return parseBytes0(s, defaultSizeTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseMemoryBytes(s string) (uint64, error) {
|
|
||||||
return parseBytes0(s, memorysSizeTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
|
|
||||||
lastDigit := 0
|
|
||||||
hasComma := false
|
|
||||||
for _, r := range s {
|
|
||||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if r == ',' {
|
|
||||||
hasComma = true
|
|
||||||
}
|
|
||||||
lastDigit++
|
|
||||||
}
|
|
||||||
|
|
||||||
num := s[:lastDigit]
|
|
||||||
if hasComma {
|
|
||||||
num = strings.Replace(num, ",", "", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := strconv.ParseFloat(num, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
|
||||||
if m, ok := sizeTable[extra]; ok {
|
|
||||||
f *= float64(m)
|
|
||||||
if f >= math.MaxUint64 {
|
|
||||||
return 0, fmt.Errorf("too large: %v", s)
|
|
||||||
}
|
|
||||||
return uint64(f), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
|
||||||
}
|
|
@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -14,6 +16,8 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
@ -28,6 +32,7 @@ type Listener struct {
|
|||||||
disablePacketOutput bool
|
disablePacketOutput bool
|
||||||
setSystemProxy bool
|
setSystemProxy bool
|
||||||
systemProxySOCKS bool
|
systemProxySOCKS bool
|
||||||
|
tproxy bool
|
||||||
|
|
||||||
tcpListener net.Listener
|
tcpListener net.Listener
|
||||||
systemProxy settings.SystemProxy
|
systemProxy settings.SystemProxy
|
||||||
@ -50,6 +55,7 @@ type Options struct {
|
|||||||
DisablePacketOutput bool
|
DisablePacketOutput bool
|
||||||
SetSystemProxy bool
|
SetSystemProxy bool
|
||||||
SystemProxySOCKS bool
|
SystemProxySOCKS bool
|
||||||
|
TProxy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@ -67,6 +73,7 @@ func New(
|
|||||||
disablePacketOutput: options.DisablePacketOutput,
|
disablePacketOutput: options.DisablePacketOutput,
|
||||||
setSystemProxy: options.SetSystemProxy,
|
setSystemProxy: options.SetSystemProxy,
|
||||||
systemProxySOCKS: options.SystemProxySOCKS,
|
systemProxySOCKS: options.SystemProxySOCKS,
|
||||||
|
tproxy: options.TProxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,3 +142,30 @@ func (l *Listener) UDPConn() *net.UDPConn {
|
|||||||
func (l *Listener) ListenOptions() option.ListenOptions {
|
func (l *Listener) ListenOptions() option.ListenOptions {
|
||||||
return l.listenOptions
|
return l.listenOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (T, error) {
|
||||||
|
if nameOrPath != "" {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
currentNs, err := netns.Get()
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "get current netns")
|
||||||
|
}
|
||||||
|
defer netns.Set(currentNs)
|
||||||
|
var targetNs netns.NsHandle
|
||||||
|
if strings.HasPrefix(nameOrPath, "/") {
|
||||||
|
targetNs, err = netns.GetFromPath(nameOrPath)
|
||||||
|
} else {
|
||||||
|
targetNs, err = netns.GetFromName(nameOrPath)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "get netns ", nameOrPath)
|
||||||
|
}
|
||||||
|
defer targetNs.Close()
|
||||||
|
err = netns.Set(targetNs)
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "set netns to ", nameOrPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return block()
|
||||||
|
}
|
||||||
|
@ -3,23 +3,39 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) ListenTCP() (net.Listener, error) {
|
func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
||||||
|
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
||||||
|
}
|
||||||
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 tcpListener net.Listener
|
|
||||||
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 {
|
||||||
@ -37,20 +53,26 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
}
|
}
|
||||||
setMultiPathTCP(&listenConfig)
|
setMultiPathTCP(&listenConfig)
|
||||||
}
|
}
|
||||||
|
if l.tproxy {
|
||||||
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
|
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
||||||
if l.listenOptions.TCPFastOpen {
|
if l.listenOptions.TCPFastOpen {
|
||||||
var tfoConfig tfo.ListenConfig
|
var tfoConfig tfo.ListenConfig
|
||||||
tfoConfig.ListenConfig = listenConfig
|
tfoConfig.ListenConfig = listenConfig
|
||||||
tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
return tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||||
} else {
|
} else {
|
||||||
tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
return listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if err == nil {
|
|
||||||
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
||||||
}
|
|
||||||
//nolint:staticcheck
|
|
||||||
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
|
||||||
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
|
||||||
}
|
|
||||||
l.tcpListener = tcpListener
|
l.tcpListener = tcpListener
|
||||||
return tcpListener, err
|
return tcpListener, err
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,34 @@
|
|||||||
package listener
|
package listener
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/redir"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
var lc net.ListenConfig
|
var listenConfig net.ListenConfig
|
||||||
|
if l.listenOptions.BindInterface != "" {
|
||||||
|
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
||||||
|
}
|
||||||
|
if l.listenOptions.RoutingMark != 0 {
|
||||||
|
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
||||||
|
}
|
||||||
|
if l.listenOptions.ReuseAddr {
|
||||||
|
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||||
|
}
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if l.listenOptions.UDPFragment != nil {
|
if l.listenOptions.UDPFragment != nil {
|
||||||
udpFragment = *l.listenOptions.UDPFragment
|
udpFragment = *l.listenOptions.UDPFragment
|
||||||
@ -22,9 +36,18 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
udpFragment = l.listenOptions.UDPFragmentDefault
|
udpFragment = l.listenOptions.UDPFragmentDefault
|
||||||
}
|
}
|
||||||
if !udpFragment {
|
if !udpFragment {
|
||||||
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())
|
||||||
}
|
}
|
||||||
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
if l.tproxy {
|
||||||
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
|
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||||
|
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -34,6 +57,36 @@ 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) {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Listener) UDPAddr() M.Socksaddr {
|
func (l *Listener) UDPAddr() M.Socksaddr {
|
||||||
return l.udpAddr
|
return l.udpAddr
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TProxy(fd uintptr, isIPv6 bool) error {
|
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
||||||
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||||
@ -20,12 +20,14 @@ func TProxy(fd uintptr, isIPv6 bool) error {
|
|||||||
if err == nil && isIPv6 {
|
if err == nil && isIPv6 {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
||||||
}
|
}
|
||||||
|
if 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) error {
|
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -23,21 +24,26 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
|||||||
var first byte
|
var first byte
|
||||||
err := binary.Read(reader, binary.BigEndian, &first)
|
err := binary.Read(reader, binary.BigEndian, &first)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if first != 19 {
|
if first != 19 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const header = "BitTorrent protocol"
|
||||||
var protocol [19]byte
|
var protocol [19]byte
|
||||||
_, err = reader.Read(protocol[:])
|
var n int
|
||||||
if err != nil {
|
n, err = reader.Read(protocol[:])
|
||||||
return err
|
if string(protocol[:n]) != header[:n] {
|
||||||
}
|
|
||||||
if string(protocol[:]) != "BitTorrent protocol" {
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
|
}
|
||||||
|
if n < 19 {
|
||||||
|
return ErrNeedMoreData
|
||||||
|
}
|
||||||
|
|
||||||
metadata.Protocol = C.ProtocolBitTorrent
|
metadata.Protocol = C.ProtocolBitTorrent
|
||||||
return nil
|
return nil
|
||||||
@ -67,7 +73,9 @@ func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if extension > 0x04 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
var length byte
|
var length byte
|
||||||
err = binary.Read(reader, binary.BigEndian, &length)
|
err = binary.Read(reader, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSniffIncompleteBittorrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pkt, err := hex.DecodeString("13426974546f7272656e74")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||||
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffNotBittorrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pkt, err := hex.DecodeString("13426974546f7272656e75")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||||
|
require.NotEmpty(t, err)
|
||||||
|
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSniffUTP(t *testing.T) {
|
func TestSniffUTP(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@ -71,3 +92,19 @@ func TestSniffUDPTracker(t *testing.T) {
|
|||||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSniffNotUTP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
packets := []string{
|
||||||
|
"0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
}
|
||||||
|
for _, pkt := range packets {
|
||||||
|
pkt, err := hex.DecodeString(pkt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.UTP(context.TODO(), &metadata, pkt)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,14 +5,11 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/task"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@ -21,35 +18,40 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
|||||||
var length uint16
|
var length uint16
|
||||||
err := binary.Read(reader, binary.BigEndian, &length)
|
err := binary.Read(reader, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return os.ErrInvalid
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if length == 0 {
|
if length < 12 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
buffer := buf.NewSize(int(length))
|
buffer := buf.NewSize(int(length))
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
|
var n int
|
||||||
var readTask task.Group
|
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||||
readTask.Append0(func(ctx context.Context) error {
|
packet := buffer.Bytes()
|
||||||
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen()))
|
if n > 2 && packet[2]&0x80 != 0 { // QR
|
||||||
})
|
return os.ErrInvalid
|
||||||
err = readTask.Run(readCtx)
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
|
if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
for i := 6; i < 10; i++ {
|
||||||
|
// ANCOUNT, NSCOUNT
|
||||||
|
if n > i && packet[i] != 0 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
|
}
|
||||||
|
return DomainNameQuery(readCtx, metadata, packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||||
var msg mDNS.Msg
|
var msg mDNS.Msg
|
||||||
err := msg.Unpack(packet)
|
err := msg.Unpack(packet)
|
||||||
if err != nil {
|
if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
metadata.Protocol = C.ProtocolDNS
|
metadata.Protocol = C.ProtocolDNS
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
53
common/sniff/dns_test.go
Normal file
53
common/sniff/dns_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffDNS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
query, err := hex.DecodeString("740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.DomainNameQuery(context.TODO(), &metadata, query)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffStreamDNS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffIncompleteStreamDNS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
query, err := hex.DecodeString("001e740701000001000000000000")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||||
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffNotStreamDNS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
query, err := hex.DecodeString("001e740701000000000000000000")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||||
|
require.NotEmpty(t, err)
|
||||||
|
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
|
}
|
@ -3,10 +3,12 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
std_bufio "bufio"
|
std_bufio "bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/protocol/http"
|
"github.com/sagernet/sing/protocol/http"
|
||||||
)
|
)
|
||||||
@ -14,8 +16,12 @@ import (
|
|||||||
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
request, err := http.ReadRequest(std_bufio.NewReader(reader))
|
request, err := http.ReadRequest(std_bufio.NewReader(reader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
metadata.Protocol = C.ProtocolHTTP
|
metadata.Protocol = C.ProtocolHTTP
|
||||||
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
||||||
return nil
|
return nil
|
||||||
|
58
common/sniff/ntp.go
Normal file
58
common/sniff/ntp.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NTP(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||||
|
// NTP packets must be at least 48 bytes long (standard NTP header size).
|
||||||
|
pLen := len(packet)
|
||||||
|
if pLen < 48 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
// Check the LI (Leap Indicator) and Version Number (VN) in the first byte.
|
||||||
|
// We'll primarily focus on ensuring the version is valid for NTP.
|
||||||
|
// Many NTP versions are used, but let's check for generally accepted ones (3 & 4 for IPv4, plus potential extensions/customizations)
|
||||||
|
firstByte := packet[0]
|
||||||
|
li := (firstByte >> 6) & 0x03 // Extract LI
|
||||||
|
vn := (firstByte >> 3) & 0x07 // Extract VN
|
||||||
|
mode := firstByte & 0x07 // Extract Mode
|
||||||
|
|
||||||
|
// Leap Indicator should be a valid value (0-3).
|
||||||
|
if li > 3 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version Check (common NTP versions are 3 and 4)
|
||||||
|
if vn != 3 && vn != 4 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the Mode field for a client request (Mode 3). This validates it *is* a request.
|
||||||
|
if mode != 3 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Root Delay and Root Dispersion. While not strictly *required* for a request,
|
||||||
|
// we can check if they appear to be reasonable values (not excessively large).
|
||||||
|
rootDelay := binary.BigEndian.Uint32(packet[4:8])
|
||||||
|
rootDispersion := binary.BigEndian.Uint32(packet[8:12])
|
||||||
|
|
||||||
|
// Check for unreasonably large root delay and dispersion. NTP RFC specifies max values of approximately 16 seconds.
|
||||||
|
// Convert to milliseconds for easy comparison. Each unit is 1/2^16 seconds.
|
||||||
|
if float64(rootDelay)/65536.0 > 16.0 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
if float64(rootDispersion)/65536.0 > 16.0 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.Protocol = C.ProtocolNTP
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
33
common/sniff/ntp_test.go
Normal file
33
common/sniff/ntp_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffNTP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
packet, err := hex.DecodeString("1b0006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.NTP(context.Background(), &metadata, packet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, metadata.Protocol, C.ProtocolNTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffNTPFailed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
packet, err := hex.DecodeString("400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.NTP(context.Background(), &metadata, packet)
|
||||||
|
require.ErrorIs(t, err, os.ErrInvalid)
|
||||||
|
}
|
@ -20,8 +20,6 @@ import (
|
|||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection")
|
|
||||||
|
|
||||||
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||||
reader := bytes.NewReader(packet)
|
reader := bytes.NewReader(packet)
|
||||||
typeByte, err := reader.ReadByte()
|
typeByte, err := reader.ReadByte()
|
||||||
@ -308,7 +306,7 @@ find:
|
|||||||
metadata.Protocol = C.ProtocolQUIC
|
metadata.Protocol = C.ProtocolQUIC
|
||||||
metadata.Client = C.ClientChromium
|
metadata.Client = C.ClientChromium
|
||||||
metadata.SniffContext = fragments
|
metadata.SniffContext = fragments
|
||||||
return ErrClientHelloFragmented
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
metadata.Domain = fingerprint.ServerName
|
metadata.Domain = fingerprint.ServerName
|
||||||
for metadata.Client == "" {
|
for metadata.Client == "" {
|
||||||
|
@ -20,11 +20,11 @@ func TestSniffQUICChromeNew(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
|
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
@ -40,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktVersion uint8
|
var tpktVersion uint8
|
||||||
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
|
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if tpktVersion != 0x03 {
|
if tpktVersion != 0x03 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@ -24,7 +25,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktReserved uint8
|
var tpktReserved uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
|
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if tpktReserved != 0x00 {
|
if tpktReserved != 0x00 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@ -33,7 +34,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktLength uint16
|
var tpktLength uint16
|
||||||
err = binary.Read(reader, binary.BigEndian, &tpktLength)
|
err = binary.Read(reader, binary.BigEndian, &tpktLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tpktLength != 19 {
|
if tpktLength != 19 {
|
||||||
@ -43,7 +44,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var cotpLength uint8
|
var cotpLength uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &cotpLength)
|
err = binary.Read(reader, binary.BigEndian, &cotpLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cotpLength != 14 {
|
if cotpLength != 14 {
|
||||||
@ -53,7 +54,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var cotpTpduType uint8
|
var cotpTpduType uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
|
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if cotpTpduType != 0xE0 {
|
if cotpTpduType != 0xE0 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@ -61,13 +62,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
|
|
||||||
err = rw.SkipN(reader, 5)
|
err = rw.SkipN(reader, 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rdpType uint8
|
var rdpType uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpType)
|
err = binary.Read(reader, binary.BigEndian, &rdpType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if rdpType != 0x01 {
|
if rdpType != 0x01 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@ -75,12 +76,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var rdpFlags uint8
|
var rdpFlags uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
|
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
var rdpLength uint8
|
var rdpLength uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpLength)
|
err = binary.Read(reader, binary.BigEndian, &rdpLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if rdpLength != 8 {
|
if rdpLength != 8 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
|
@ -3,12 +3,14 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@ -18,6 +20,8 @@ type (
|
|||||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNeedMoreData = E.New("need more data")
|
||||||
|
|
||||||
func Skip(metadata *adapter.InboundContext) bool {
|
func Skip(metadata *adapter.InboundContext) bool {
|
||||||
// skip server first protocols
|
// skip server first protocols
|
||||||
switch metadata.Destination.Port {
|
switch metadata.Destination.Port {
|
||||||
@ -34,12 +38,12 @@ func Skip(metadata *adapter.InboundContext) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
|
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = C.ReadPayloadTimeout
|
timeout = C.ReadPayloadTimeout
|
||||||
}
|
}
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
var errors []error
|
var sniffError error
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
err := conn.SetReadDeadline(deadline)
|
err := conn.SetReadDeadline(deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -53,26 +57,32 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
}
|
}
|
||||||
return E.Cause(err, "read payload")
|
return E.Cause(err, "read payload")
|
||||||
}
|
}
|
||||||
errors = nil
|
sniffError = nil
|
||||||
for _, sniffer := range sniffers {
|
for _, sniffer := range sniffers {
|
||||||
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
|
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
|
||||||
|
return bytes.NewReader(it.Bytes())
|
||||||
|
})...)
|
||||||
|
err = sniffer(ctx, metadata, reader)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errors = append(errors, err)
|
sniffError = E.Errors(sniffError, err)
|
||||||
|
}
|
||||||
|
if !errors.Is(sniffError, ErrNeedMoreData) {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return E.Errors(errors...)
|
return sniffError
|
||||||
}
|
}
|
||||||
|
|
||||||
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
|
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
|
||||||
var errors []error
|
var sniffError []error
|
||||||
for _, sniffer := range sniffers {
|
for _, sniffer := range sniffers {
|
||||||
err := sniffer(ctx, metadata, packet)
|
err := sniffer(ctx, metadata, packet)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errors = append(errors, err)
|
sniffError = append(sniffError, err)
|
||||||
}
|
}
|
||||||
return E.Errors(errors...)
|
return E.Errors(sniffError...)
|
||||||
}
|
}
|
||||||
|
@ -5,22 +5,27 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
scanner := bufio.NewScanner(reader)
|
const sshPrefix = "SSH-2.0-"
|
||||||
if !scanner.Scan() {
|
bReader := bufio.NewReader(reader)
|
||||||
|
prefix, err := bReader.Peek(len(sshPrefix))
|
||||||
|
if string(prefix[:]) != sshPrefix[:len(prefix)] {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
fistLine := scanner.Text()
|
if err != nil {
|
||||||
if !strings.HasPrefix(fistLine, "SSH-2.0-") {
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
return os.ErrInvalid
|
}
|
||||||
|
fistLine, _, err := bReader.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolSSH
|
metadata.Protocol = C.ProtocolSSH
|
||||||
metadata.Client = fistLine[8:]
|
metadata.Client = string(fistLine)[8:]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) {
|
|||||||
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
||||||
require.Equal(t, "dropbear", metadata.Client)
|
require.Equal(t, "dropbear", metadata.Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSniffIncompleteSSH(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pkt, err := hex.DecodeString("5353482d322e30")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||||
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffNotSSH(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pkt, err := hex.DecodeString("5353482d322e31")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||||
|
require.NotEmpty(t, err)
|
||||||
|
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
|
}
|
||||||
|
@ -3,11 +3,13 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
@ -23,5 +25,9 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
|
|||||||
metadata.Domain = clientHello.ServerName
|
metadata.Domain = clientHello.ServerName
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
|
} else {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case "", "letsencrypt":
|
case "", "letsencrypt":
|
||||||
|
@ -11,6 +11,6 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ type (
|
|||||||
STDConfig = tls.Config
|
STDConfig = tls.Config
|
||||||
STDConn = tls.Conn
|
STDConn = tls.Conn
|
||||||
ConnectionState = tls.ConnectionState
|
ConnectionState = tls.ConnectionState
|
||||||
|
CurveID = tls.CurveID
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseTLSVersion(version string) (uint16, error) {
|
func ParseTLSVersion(version string) (uint16, error) {
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
@ -46,7 +48,10 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
|
|||||||
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return &STDClientConfig{tlsConfig}, nil
|
return &STDClientConfig{tlsConfig}, nil
|
||||||
} else {
|
} else {
|
||||||
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil
|
return &STDECHClientConfig{
|
||||||
|
STDClientConfig: STDClientConfig{tlsConfig},
|
||||||
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,11 +104,28 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
|||||||
|
|
||||||
type STDECHClientConfig struct {
|
type STDECHClientConfig struct {
|
||||||
STDClientConfig
|
STDClientConfig
|
||||||
|
access sync.Mutex
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
|
lastTTL time.Duration
|
||||||
|
lastUpdate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tlsConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
|
s.access.Lock()
|
||||||
|
defer s.access.Unlock()
|
||||||
|
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
@ -123,6 +145,7 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
|||||||
if response.Rcode != mDNS.RcodeSuccess {
|
if response.Rcode != mDNS.RcodeSuccess {
|
||||||
return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
|
return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
|
||||||
}
|
}
|
||||||
|
match:
|
||||||
for _, rr := range response.Answer {
|
for _, rr := range response.Answer {
|
||||||
switch resource := rr.(type) {
|
switch resource := rr.(type) {
|
||||||
case *mDNS.HTTPS:
|
case *mDNS.HTTPS:
|
||||||
@ -132,26 +155,23 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode ECH config")
|
return nil, E.Cause(err, "decode ECH config")
|
||||||
}
|
}
|
||||||
|
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
||||||
|
s.lastUpdate = time.Now()
|
||||||
s.config.EncryptedClientHelloConfigList = echConfigList
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
|
break match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
tlsConn, err := s.Client(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
return s.Client(conn)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDECHClientConfig) Clone() Config {
|
func (s *STDECHClientConfig) Clone() Config {
|
||||||
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter}
|
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
5
common/tls/ech_tag_stub.go
Normal file
5
common/tls/ech_tag_stub.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//go:build with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
var _ int = "Due to the migration to stdlib, the separate `with_ech` build tag has been deprecated and is no longer needed, please update your build configuration."
|
@ -29,12 +29,13 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
|
|
||||||
|
utls "github.com/metacubex/utls"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@ -114,6 +115,22 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, extension := range uConn.Extensions {
|
||||||
|
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
|
||||||
|
ce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool {
|
||||||
|
return curveID != utls.X25519MLKEM768
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if ks, ok := extension.(*utls.KeyShareExtension); ok {
|
||||||
|
ks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool {
|
||||||
|
return share.Group != utls.X25519MLKEM768
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = uConn.BuildHandshakeState()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if len(uConfig.NextProtos) > 0 {
|
if len(uConfig.NextProtos) > 0 {
|
||||||
for _, extension := range uConn.Extensions {
|
for _, extension := range uConn.Extensions {
|
||||||
@ -148,9 +165,13 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ecdheKey := uConn.HandshakeState.State13.EcdheKey
|
keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys
|
||||||
|
if keyShareKeys == nil {
|
||||||
|
return nil, E.New("nil KeyShareKeys")
|
||||||
|
}
|
||||||
|
ecdheKey := keyShareKeys.Ecdhe
|
||||||
if ecdheKey == nil {
|
if ecdheKey == nil {
|
||||||
return nil, E.New("nil ecdhe_key")
|
return nil, E.New("nil ecdheKey")
|
||||||
}
|
}
|
||||||
authKey, err := ecdheKey.ECDH(publicKey)
|
authKey, err := ecdheKey.ECDH(publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -214,10 +235,6 @@ func realityClientFallback(ctx context.Context, uConn net.Conn, serverName strin
|
|||||||
response.Body.Close()
|
response.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
|
||||||
e.uClient.config.SessionIDGenerator = generator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
func (e *RealityClientConfig) Clone() Config {
|
||||||
return &RealityClientConfig{
|
return &RealityClientConfig{
|
||||||
e.ctx,
|
e.ctx,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//go:build with_reality_server
|
//go:build with_utls
|
||||||
|
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
@ -7,28 +7,29 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/reality"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/debug"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
|
utls "github.com/metacubex/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||||
|
|
||||||
type RealityServerConfig struct {
|
type RealityServerConfig struct {
|
||||||
config *reality.Config
|
config *utls.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
||||||
var tlsConfig reality.Config
|
var tlsConfig utls.RealityConfig
|
||||||
|
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
return nil, E.New("acme is unavailable in reality")
|
return nil, E.New("acme is unavailable in reality")
|
||||||
@ -74,6 +75,11 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig.SessionTicketsDisabled = true
|
tlsConfig.SessionTicketsDisabled = true
|
||||||
|
tlsConfig.Log = func(format string, v ...any) {
|
||||||
|
if logger != nil {
|
||||||
|
logger.Trace(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
tlsConfig.Type = N.NetworkTCP
|
tlsConfig.Type = N.NetworkTCP
|
||||||
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
||||||
|
|
||||||
@ -89,6 +95,9 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
||||||
|
|
||||||
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
||||||
|
if len(options.Reality.ShortID) == 0 {
|
||||||
|
tlsConfig.ShortIds[[8]byte{0}] = true
|
||||||
|
} else {
|
||||||
for i, shortIDString := range options.Reality.ShortID {
|
for i, shortIDString := range options.Reality.ShortID {
|
||||||
var shortID [8]byte
|
var shortID [8]byte
|
||||||
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
||||||
@ -100,6 +109,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
}
|
}
|
||||||
tlsConfig.ShortIds[shortID] = true
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -109,10 +119,6 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug.Enabled {
|
|
||||||
tlsConfig.Show = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RealityServerConfig{&tlsConfig}, nil
|
return &RealityServerConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +159,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
||||||
tlsConn, err := reality.Server(ctx, conn, c.config)
|
tlsConn, err := utls.RealityServer(ctx, conn, c.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -169,7 +175,7 @@ func (c *RealityServerConfig) Clone() Config {
|
|||||||
var _ Conn = (*realityConnWrapper)(nil)
|
var _ Conn = (*realityConnWrapper)(nil)
|
||||||
|
|
||||||
type realityConnWrapper struct {
|
type realityConnWrapper struct {
|
||||||
*reality.Conn
|
*utls.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||||
|
@ -1,15 +1,5 @@
|
|||||||
//go:build !with_reality_server
|
//go:build with_reality_server
|
||||||
|
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
var _ int = "The separate `with_reality_server` build tag has been merged into `with_utls` and is no longer needed, please update your build configuration."
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
|
||||||
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
|
||||||
}
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -21,7 +22,7 @@ var errInsecureUnused = E.New("tls: insecure unused")
|
|||||||
type STDServerConfig struct {
|
type STDServerConfig struct {
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
acmeService adapter.Service
|
acmeService adapter.SimpleLifecycle
|
||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
certificatePath string
|
certificatePath string
|
||||||
@ -164,7 +165,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
var acmeService adapter.Service
|
var acmeService adapter.SimpleLifecycle
|
||||||
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
|
||||||
@ -233,8 +234,12 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
key = content
|
key = content
|
||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
|
timeFunc := ntp.TimeFuncFromContext(ctx)
|
||||||
|
if timeFunc == nil {
|
||||||
|
timeFunc = time.Now
|
||||||
|
}
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
return GenerateKeyPair(nil, nil, timeFunc, info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
@ -16,8 +16,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
|
|
||||||
|
utls "github.com/metacubex/utls"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@ -14,5 +15,9 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
|
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package tf
|
package tf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@ -17,17 +19,19 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
|
splitRecord bool
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
tcpConn: tcpConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
splitRecord: splitRecord,
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||||
@ -37,12 +41,14 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}()
|
}()
|
||||||
serverName := indexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
|
if !c.splitRecord {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
splits := strings.Split(serverName.ServerName, ".")
|
splits := strings.Split(serverName.ServerName, ".")
|
||||||
currentIndex := serverName.Index
|
currentIndex := serverName.Index
|
||||||
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
|
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
|
||||||
@ -61,16 +67,25 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
currentIndex++
|
currentIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var buffer bytes.Buffer
|
||||||
for i := 0; i <= len(splitIndexes); i++ {
|
for i := 0; i <= len(splitIndexes); i++ {
|
||||||
var payload []byte
|
var payload []byte
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
payload = b[:splitIndexes[i]]
|
payload = b[:splitIndexes[i]]
|
||||||
|
if c.splitRecord {
|
||||||
|
payload = payload[recordLayerHeaderLen:]
|
||||||
|
}
|
||||||
} else if i == len(splitIndexes) {
|
} else if i == len(splitIndexes) {
|
||||||
payload = b[splitIndexes[i-1]:]
|
payload = b[splitIndexes[i-1]:]
|
||||||
} else {
|
} else {
|
||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||||
}
|
}
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
if c.splitRecord {
|
||||||
|
payloadLen := uint16(len(payload))
|
||||||
|
buffer.Write(b[:3])
|
||||||
|
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
||||||
|
buffer.Write(payload)
|
||||||
|
} else if c.tcpConn != nil && i != len(splitIndexes) {
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -82,12 +97,19 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if c.splitRecord {
|
||||||
|
_, err = c.Conn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
common/tlsfragment/conn_test.go
Normal file
32
common/tlsfragment/conn_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package tf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
tf "github.com/sagernet/sing-box/common/tlsfragment"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTLSFragment(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
|
require.NoError(t, err)
|
||||||
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
|
||||||
|
ServerName: "www.cloudflare.com",
|
||||||
|
})
|
||||||
|
require.NoError(t, tlsConn.Handshake())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTLSRecordFragment(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
|
require.NoError(t, err)
|
||||||
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
|
||||||
|
ServerName: "www.cloudflare.com",
|
||||||
|
})
|
||||||
|
require.NoError(t, tlsConn.Handshake())
|
||||||
|
}
|
@ -25,6 +25,9 @@ const (
|
|||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
|
TypeDERP = "derp"
|
||||||
|
TypeResolved = "resolved"
|
||||||
|
TypeSSMAPI = "ssm-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
6
debug.go
6
debug.go
@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
if options.TraceBack != "" {
|
if options.TraceBack != "" {
|
||||||
debug.SetTraceback(options.TraceBack)
|
debug.SetTraceback(options.TraceBack)
|
||||||
}
|
}
|
||||||
if options.MemoryLimit != 0 {
|
if options.MemoryLimit.Value() != 0 {
|
||||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
||||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/byteformats"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
|||||||
runtime.ReadMemStats(&memStats)
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
var memObject badjson.JSONObject
|
var memObject badjson.JSONObject
|
||||||
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
|
||||||
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
|
||||||
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
memObject.Put("rss", rusageMaxRSS())
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ type Client struct {
|
|||||||
disableCache bool
|
disableCache bool
|
||||||
disableExpire bool
|
disableExpire bool
|
||||||
independentCache bool
|
independentCache bool
|
||||||
|
clientSubnet netip.Prefix
|
||||||
rdrc adapter.RDRCStore
|
rdrc adapter.RDRCStore
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
@ -47,6 +48,7 @@ type ClientOptions struct {
|
|||||||
DisableExpire bool
|
DisableExpire bool
|
||||||
IndependentCache bool
|
IndependentCache bool
|
||||||
CacheCapacity uint32
|
CacheCapacity uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
RDRC func() adapter.RDRCStore
|
RDRC func() adapter.RDRCStore
|
||||||
Logger logger.ContextLogger
|
Logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
@ -57,6 +59,7 @@ func NewClient(options ClientOptions) *Client {
|
|||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
disableExpire: options.DisableExpire,
|
disableExpire: options.DisableExpire,
|
||||||
independentCache: options.IndependentCache,
|
independentCache: options.IndependentCache,
|
||||||
|
clientSubnet: options.ClientSubnet,
|
||||||
initRDRCFunc: options.RDRC,
|
initRDRCFunc: options.RDRC,
|
||||||
logger: options.Logger,
|
logger: options.Logger,
|
||||||
}
|
}
|
||||||
@ -104,8 +107,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return &responseMessage, nil
|
return &responseMessage, nil
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
if options.ClientSubnet.IsValid() {
|
clientSubnet := options.ClientSubnet
|
||||||
message = SetClientSubnet(message, options.ClientSubnet, true)
|
if !clientSubnet.IsValid() {
|
||||||
|
clientSubnet = c.clientSubnet
|
||||||
|
}
|
||||||
|
if clientSubnet.IsValid() {
|
||||||
|
message = SetClientSubnet(message, clientSubnet)
|
||||||
}
|
}
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
@ -232,10 +239,20 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
record.Header().Ttl = timeToLive
|
record.Header().Ttl = timeToLive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response.Id = messageId
|
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
c.storeCache(transport, question, response, timeToLive)
|
c.storeCache(transport, question, response, timeToLive)
|
||||||
}
|
}
|
||||||
|
response.Id = messageId
|
||||||
|
requestEDNSOpt := message.IsEdns0()
|
||||||
|
responseEDNSOpt := response.IsEdns0()
|
||||||
|
if responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) {
|
||||||
|
response.Extra = common.Filter(response.Extra, func(it dns.RR) bool {
|
||||||
|
return it.Header().Rrtype != dns.TypeOPT
|
||||||
|
})
|
||||||
|
if requestEDNSOpt != nil {
|
||||||
|
response.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do())
|
||||||
|
}
|
||||||
|
}
|
||||||
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
logExchangedResponse(c.logger, ctx, response, timeToLive)
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
@ -243,9 +260,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
domain = FqdnToDomain(domain)
|
domain = FqdnToDomain(domain)
|
||||||
dnsName := dns.Fqdn(domain)
|
dnsName := dns.Fqdn(domain)
|
||||||
if options.Strategy == C.DomainStrategyIPv4Only {
|
var strategy C.DomainStrategy
|
||||||
|
if options.LookupStrategy != C.DomainStrategyAsIS {
|
||||||
|
strategy = options.LookupStrategy
|
||||||
|
} else {
|
||||||
|
strategy = options.Strategy
|
||||||
|
}
|
||||||
|
if strategy == C.DomainStrategyIPv4Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
||||||
} else if options.Strategy == C.DomainStrategyIPv6Only {
|
} else if strategy == C.DomainStrategyIPv6Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
||||||
}
|
}
|
||||||
var response4 []netip.Addr
|
var response4 []netip.Addr
|
||||||
@ -271,7 +294,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
if len(response4) == 0 && len(response6) == 0 {
|
if len(response4) == 0 && len(response6) == 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return sortAddresses(response4, response6, options.Strategy), nil
|
return sortAddresses(response4, response6, strategy), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ClearCache() {
|
func (c *Client) ClearCache() {
|
||||||
@ -483,7 +506,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
||||||
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
if response.Rcode != dns.RcodeSuccess {
|
||||||
return nil, RcodeError(response.Rcode)
|
return nil, RcodeError(response.Rcode)
|
||||||
}
|
}
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
@ -527,17 +550,31 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
|
|||||||
return value, loaded
|
return value, loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
||||||
|
return &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: rcode,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: message.Question,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
|
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
Response: true,
|
Response: true,
|
||||||
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
for _, address := range addresses {
|
for _, address := range addresses {
|
||||||
if address.Is4() {
|
if address.Is4() && question.Qtype == dns.TypeA {
|
||||||
response.Answer = append(response.Answer, &dns.A{
|
response.Answer = append(response.Answer, &dns.A{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: question.Name,
|
Name: question.Name,
|
||||||
@ -547,7 +584,7 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
|
|||||||
},
|
},
|
||||||
A: address.AsSlice(),
|
A: address.AsSlice(),
|
||||||
})
|
})
|
||||||
} else {
|
} else if address.Is6() && question.Qtype == dns.TypeAAAA {
|
||||||
response.Answer = append(response.Answer, &dns.AAAA{
|
response.Answer = append(response.Answer, &dns.AAAA{
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: question.Name,
|
Name: question.Name,
|
||||||
@ -566,8 +603,11 @@ func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToL
|
|||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
Response: true,
|
Response: true,
|
||||||
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
Answer: []dns.RR{
|
Answer: []dns.RR{
|
||||||
@ -589,8 +629,11 @@ func FixedResponseTXT(id uint16, question dns.Question, records []string, timeTo
|
|||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
Response: true,
|
Response: true,
|
||||||
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
Answer: []dns.RR{
|
Answer: []dns.RR{
|
||||||
@ -612,8 +655,11 @@ func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeTo
|
|||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
Response: true,
|
Response: true,
|
||||||
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf
|
|||||||
}
|
}
|
||||||
responseLen := response.Len()
|
responseLen := response.Len()
|
||||||
if responseLen > maxLen {
|
if responseLen > maxLen {
|
||||||
|
copyResponse := *response
|
||||||
|
response = ©Response
|
||||||
response.Truncate(maxLen)
|
response.Truncate(maxLen)
|
||||||
}
|
}
|
||||||
buffer := buf.NewSize(headroom*2 + 1 + responseLen)
|
buffer := buf.NewSize(headroom*2 + 1 + responseLen)
|
||||||
|
@ -6,7 +6,11 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg {
|
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg {
|
||||||
|
return setClientSubnet(message, clientSubnet, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {
|
||||||
var (
|
var (
|
||||||
optRecord *dns.OPT
|
optRecord *dns.OPT
|
||||||
subnetOption *dns.EDNS0_SUBNET
|
subnetOption *dns.EDNS0_SUBNET
|
||||||
@ -19,9 +23,6 @@ findExists:
|
|||||||
var isEDNS0Subnet bool
|
var isEDNS0Subnet bool
|
||||||
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
||||||
if isEDNS0Subnet {
|
if isEDNS0Subnet {
|
||||||
if !override {
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
break findExists
|
break findExists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,14 +38,14 @@ findExists:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
message.Extra = append(message.Extra, optRecord)
|
message.Extra = append(message.Extra, optRecord)
|
||||||
} else {
|
} else if clone {
|
||||||
message = message.Copy()
|
return setClientSubnet(message.Copy(), clientSubnet, false)
|
||||||
}
|
}
|
||||||
if subnetOption == nil {
|
if subnetOption == nil {
|
||||||
subnetOption = new(dns.EDNS0_SUBNET)
|
subnetOption = new(dns.EDNS0_SUBNET)
|
||||||
|
subnetOption.Code = dns.EDNS0SUBNET
|
||||||
optRecord.Option = append(optRecord.Option, subnetOption)
|
optRecord.Option = append(optRecord.Option, subnetOption)
|
||||||
}
|
}
|
||||||
subnetOption.Code = dns.EDNS0SUBNET
|
|
||||||
if clientSubnet.Addr().Is4() {
|
if clientSubnet.Addr().Is4() {
|
||||||
subnetOption.Family = 1
|
subnetOption.Family = 1
|
||||||
} else {
|
} else {
|
||||||
|
@ -55,6 +55,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||||
|
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
||||||
RDRC: func() adapter.RDRCStore {
|
RDRC: func() adapter.RDRCStore {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
if cacheFile == nil {
|
if cacheFile == nil {
|
||||||
@ -258,25 +259,19 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
switch action.Method {
|
switch action.Method {
|
||||||
case C.RuleActionRejectMethodDefault:
|
case C.RuleActionRejectMethodDefault:
|
||||||
return FixedResponse(message.Id, message.Question[0], nil, 0), nil
|
return &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: mDNS.RcodeRefused,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{message.Question[0]},
|
||||||
|
}, nil
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
return nil, tun.ErrDrop
|
||||||
}
|
}
|
||||||
case *R.RuleActionPredefined:
|
case *R.RuleActionPredefined:
|
||||||
return &mDNS.Msg{
|
return action.Response(message), nil
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Response: true,
|
|
||||||
Authoritative: true,
|
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: action.Rcode,
|
|
||||||
},
|
|
||||||
Question: message.Question,
|
|
||||||
Answer: action.Answer,
|
|
||||||
Ns: action.Ns,
|
|
||||||
Extra: action.Extra,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var responseCheck func(responseAddrs []netip.Addr) bool
|
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||||
@ -298,7 +293,12 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
} else if errors.Is(err, ErrResponseRejected) {
|
} else if errors.Is(err, ErrResponseRejected) {
|
||||||
rejected = true
|
rejected = true
|
||||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||||
|
/*} else if responseCheck!= nil && errors.Is(err, RcodeError(mDNS.RcodeNameError)) {
|
||||||
|
rejected = true
|
||||||
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||||
|
*/
|
||||||
} else if len(message.Question) > 0 {
|
} else if len(message.Question) > 0 {
|
||||||
|
rejected = true
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||||
@ -336,6 +336,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
printResult := func() {
|
printResult := func() {
|
||||||
|
if err == nil && len(responseAddrs) == 0 {
|
||||||
|
err = E.New("empty result")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrResponseRejectedCached) {
|
if errors.Is(err, ErrResponseRejectedCached) {
|
||||||
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||||
@ -344,15 +347,15 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||||
}
|
}
|
||||||
} else if len(responseAddrs) == 0 {
|
}
|
||||||
r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
if err != nil {
|
||||||
err = RcodeNameError
|
err = E.Cause(err, "lookup ", domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
||||||
if cached {
|
if cached {
|
||||||
if len(responseAddrs) == 0 {
|
if len(responseAddrs) == 0 {
|
||||||
return nil, RcodeNameError
|
return nil, E.New("lookup ", domain, ": empty result (cached)")
|
||||||
}
|
}
|
||||||
return responseAddrs, nil
|
return responseAddrs, nil
|
||||||
}
|
}
|
||||||
@ -383,7 +386,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
ruleIndex = -1
|
ruleIndex = -1
|
||||||
for {
|
for {
|
||||||
dnsCtx := adapter.OverrideContext(ctx)
|
dnsCtx := adapter.OverrideContext(ctx)
|
||||||
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &options)
|
dnsOptions := options
|
||||||
|
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &dnsOptions)
|
||||||
if rule != nil {
|
if rule != nil {
|
||||||
switch action := rule.Action().(type) {
|
switch action := rule.Action().(type) {
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
@ -416,10 +420,10 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
return rule.MatchAddressLimit(metadata)
|
return rule.MatchAddressLimit(metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.Strategy == C.DomainStrategyAsIS {
|
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
||||||
options.Strategy = r.defaultDomainStrategy
|
dnsOptions.Strategy = r.defaultDomainStrategy
|
||||||
}
|
}
|
||||||
responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, options, responseCheck)
|
responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, dnsOptions, responseCheck)
|
||||||
if responseCheck == nil || err == nil {
|
if responseCheck == nil || err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -461,6 +465,6 @@ func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) {
|
|||||||
func (r *Router) ResetNetwork() {
|
func (r *Router) ResetNetwork() {
|
||||||
r.ClearCache()
|
r.ClearCache()
|
||||||
for _, transport := range r.transport.Transports() {
|
for _, transport := range r.transport.Transports() {
|
||||||
transport.Reset()
|
transport.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
|
|||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
for _, transport := range t.transports {
|
for _, transport := range t.transports {
|
||||||
transport.Reset()
|
transport.Close()
|
||||||
}
|
}
|
||||||
if t.interfaceCallback != nil {
|
if t.interfaceCallback != nil {
|
||||||
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
||||||
@ -89,12 +89,6 @@ func (t *Transport) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
|
||||||
for _, transport := range t.transports {
|
|
||||||
transport.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
err := t.fetchServers()
|
err := t.fetchServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -252,7 +246,7 @@ func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.So
|
|||||||
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
||||||
}
|
}
|
||||||
for _, transport := range t.transports {
|
for _, transport := range t.transports {
|
||||||
transport.Reset()
|
transport.Close()
|
||||||
}
|
}
|
||||||
t.transports = transports
|
t.transports = transports
|
||||||
return nil
|
return nil
|
||||||
|
@ -2,6 +2,7 @@ package hosts
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -23,10 +24,14 @@ var _ adapter.DNSTransport = (*Transport)(nil)
|
|||||||
type Transport struct {
|
type Transport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
files []*File
|
files []*File
|
||||||
|
predefined map[string][]netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
var files []*File
|
var (
|
||||||
|
files []*File
|
||||||
|
predefined = make(map[string][]netip.Addr)
|
||||||
|
)
|
||||||
if len(options.Path) == 0 {
|
if len(options.Path) == 0 {
|
||||||
files = append(files, NewFile(DefaultPath))
|
files = append(files, NewFile(DefaultPath))
|
||||||
} else {
|
} else {
|
||||||
@ -34,19 +39,33 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
files = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path))))
|
files = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if options.Predefined != nil {
|
||||||
|
for _, entry := range options.Predefined.Entries() {
|
||||||
|
predefined[mDNS.CanonicalName(entry.Key)] = entry.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil),
|
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil),
|
||||||
files: files,
|
files: files,
|
||||||
|
predefined: predefined,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
domain := mDNS.CanonicalName(question.Name)
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
if addresses, ok := t.predefined[domain]; ok {
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
for _, file := range t.files {
|
for _, file := range t.files {
|
||||||
addresses := file.Lookup(domain)
|
addresses := file.Lookup(domain)
|
||||||
if len(addresses) > 0 {
|
if len(addresses) > 0 {
|
||||||
|
@ -34,7 +34,7 @@ func (f *File) Lookup(name string) []netip.Addr {
|
|||||||
f.access.Lock()
|
f.access.Lock()
|
||||||
defer f.access.Unlock()
|
defer f.access.Unlock()
|
||||||
f.update()
|
f.update()
|
||||||
return f.byName[name]
|
return f.byName[dns.CanonicalName(name)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) update() {
|
func (f *File) update() {
|
||||||
|
@ -11,6 +11,6 @@ import (
|
|||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost."))
|
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost"))
|
||||||
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
|
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost"))
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
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"
|
||||||
@ -91,10 +92,13 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
serverAddr := options.ServerOptions.Build()
|
serverAddr := options.DNSServerAddressOptions.Build()
|
||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 443
|
serverAddr.Port = 443
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return NewHTTPSRaw(
|
return NewHTTPSRaw(
|
||||||
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
|
||||||
logger,
|
logger,
|
||||||
@ -149,9 +153,17 @@ func NewHTTPSRaw(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Reset() {
|
func (t *HTTPSTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dialer.InitializeDetour(t.dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTPSTransport) Close() error {
|
||||||
t.transport.CloseIdleConnections()
|
t.transport.CloseIdleConnections()
|
||||||
t.transport = t.transport.Clone()
|
t.transport = t.transport.Clone()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
@ -3,6 +3,7 @@ package local
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -35,12 +36,18 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
}
|
}
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||||
|
ctx: ctx,
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
@ -52,7 +59,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
systemConfig := getSystemDNSConfig()
|
systemConfig := getSystemDNSConfig(t.ctx)
|
||||||
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||||
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
||||||
} else {
|
} else {
|
||||||
@ -84,8 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
|||||||
startRacer := func(ctx context.Context, fqdn string) {
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
addresses, _ := dns.MessageToAddresses(response)
|
var addresses []netip.Addr
|
||||||
if len(addresses) == 0 {
|
addresses, err = dns.MessageToAddresses(response)
|
||||||
|
if err == nil && len(addresses) == 0 {
|
||||||
err = E.New(fqdn, ": empty result")
|
err = E.New(fqdn, ": empty result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@ -23,19 +24,21 @@ type resolverConfig struct {
|
|||||||
|
|
||||||
var resolvConf resolverConfig
|
var resolvConf resolverConfig
|
||||||
|
|
||||||
func getSystemDNSConfig() *dnsConfig {
|
func getSystemDNSConfig(ctx context.Context) *dnsConfig {
|
||||||
resolvConf.tryUpdate("/etc/resolv.conf")
|
resolvConf.tryUpdate(ctx, "/etc/resolv.conf")
|
||||||
return resolvConf.dnsConfig.Load()
|
return resolvConf.dnsConfig.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *resolverConfig) init() {
|
func (conf *resolverConfig) init(ctx context.Context) {
|
||||||
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf"))
|
conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf"))
|
||||||
conf.lastChecked = time.Now()
|
conf.lastChecked = time.Now()
|
||||||
conf.ch = make(chan struct{}, 1)
|
conf.ch = make(chan struct{}, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *resolverConfig) tryUpdate(name string) {
|
func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {
|
||||||
conf.initOnce.Do(conf.init)
|
conf.initOnce.Do(func() {
|
||||||
|
conf.init(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
if conf.dnsConfig.Load().noReload {
|
if conf.dnsConfig.Load().noReload {
|
||||||
return
|
return
|
||||||
@ -59,7 +62,7 @@ func (conf *resolverConfig) tryUpdate(name string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dnsConf := dnsReadConfig(name)
|
dnsConf := dnsReadConfig(ctx, name)
|
||||||
conf.dnsConfig.Store(dnsConf)
|
conf.dnsConfig.Store(dnsConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ package local
|
|||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -18,7 +19,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ string) *dnsConfig {
|
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||||
if C.res_init() != 0 {
|
if C.res_init() != 0 {
|
||||||
return &dnsConfig{
|
return &dnsConfig{
|
||||||
servers: defaultNS,
|
servers: defaultNS,
|
||||||
|
@ -4,6 +4,7 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@ -13,7 +14,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(name string) *dnsConfig {
|
func dnsReadConfig(_ context.Context, name string) *dnsConfig {
|
||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@ -8,10 +9,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ string) *dnsConfig {
|
func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
|
||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
@ -22,35 +26,35 @@ func dnsReadConfig(_ string) *dnsConfig {
|
|||||||
conf.servers = defaultNS
|
conf.servers = defaultNS
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
aas, err := adapterAddresses()
|
addresses, err := adapterAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
var dnsAddresses []struct {
|
||||||
for _, aa := range aas {
|
ifName string
|
||||||
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
|
netip.Addr
|
||||||
if aa.OperStatus != windows.IfOperStatusUp {
|
}
|
||||||
|
for _, address := range addresses {
|
||||||
|
if address.OperStatus != windows.IfOperStatusUp {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if address.IfType == windows.IF_TYPE_TUNNEL {
|
||||||
// Only take interfaces which have at least one gateway
|
|
||||||
if aa.FirstGatewayAddress == nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if address.FirstGatewayAddress == nil {
|
||||||
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
|
continue
|
||||||
sa, err := dns.Address.Sockaddr.Sockaddr()
|
}
|
||||||
|
for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {
|
||||||
|
rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var ip netip.Addr
|
var dnsServerAddr netip.Addr
|
||||||
switch sa := sa.(type) {
|
switch sockaddr := rawSockaddr.(type) {
|
||||||
case *syscall.SockaddrInet4:
|
case *syscall.SockaddrInet4:
|
||||||
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]})
|
dnsServerAddr = netip.AddrFrom4(sockaddr.Addr)
|
||||||
case *syscall.SockaddrInet6:
|
case *syscall.SockaddrInet6:
|
||||||
var addr16 [16]byte
|
if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {
|
||||||
copy(addr16[:], sa.Addr[:])
|
|
||||||
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
|
|
||||||
// fec0/10 IPv6 addresses are site local anycast DNS
|
// fec0/10 IPv6 addresses are site local anycast DNS
|
||||||
// addresses Microsoft sets by default if no other
|
// addresses Microsoft sets by default if no other
|
||||||
// IPv6 DNS address is set. Site local anycast is
|
// IPv6 DNS address is set. Site local anycast is
|
||||||
@ -58,14 +62,27 @@ func dnsReadConfig(_ string) *dnsConfig {
|
|||||||
// https://datatracker.ietf.org/doc/html/rfc3879
|
// https://datatracker.ietf.org/doc/html/rfc3879
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ip = netip.AddrFrom16(addr16)
|
dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
|
||||||
default:
|
default:
|
||||||
// Unexpected type.
|
// Unexpected type.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53"))
|
dnsAddresses = append(dnsAddresses, struct {
|
||||||
|
ifName string
|
||||||
|
netip.Addr
|
||||||
|
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var myInterface string
|
||||||
|
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
|
||||||
|
myInterface = networkManager.InterfaceMonitor().MyInterface()
|
||||||
|
}
|
||||||
|
for _, address := range dnsAddresses {
|
||||||
|
if address.ifName == myInterface {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
|
||||||
|
}
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ import (
|
|||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||||
|
|
||||||
@ -89,10 +88,13 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
serverAddr := options.ServerOptions.Build()
|
serverAddr := options.DNSServerAddressOptions.Build()
|
||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 443
|
serverAddr.Port = 443
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return &HTTP3Transport{
|
return &HTTP3Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
@ -101,8 +103,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
headers: headers,
|
headers: headers,
|
||||||
transport: &http3.Transport{
|
transport: &http3.Transport{
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
destinationAddr := M.ParseSocksaddr(addr)
|
conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, serverAddr)
|
||||||
conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, destinationAddr)
|
|
||||||
if dialErr != nil {
|
if dialErr != nil {
|
||||||
return nil, dialErr
|
return nil, dialErr
|
||||||
}
|
}
|
||||||
@ -113,8 +114,12 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTP3Transport) Reset() {
|
func (t *HTTP3Transport) Start(stage adapter.StartStage) error {
|
||||||
t.transport.Close()
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTP3Transport) Close() error {
|
||||||
|
return t.transport.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
sQUIC "github.com/sagernet/sing-quic"
|
sQUIC "github.com/sagernet/sing-quic"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@ -54,10 +55,13 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{"doq"})
|
tlsConfig.SetNextProtos([]string{"doq"})
|
||||||
}
|
}
|
||||||
serverAddr := options.ServerOptions.Build()
|
serverAddr := options.DNSServerAddressOptions.Build()
|
||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 853
|
serverAddr.Port = 853
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@ -68,13 +72,18 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
t.access.Lock()
|
t.access.Lock()
|
||||||
defer t.access.Unlock()
|
defer t.access.Unlock()
|
||||||
connection := t.connection
|
connection := t.connection
|
||||||
if connection != nil {
|
if connection != nil {
|
||||||
connection.CloseWithError(0, "")
|
connection.CloseWithError(0, "")
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
@ -135,12 +144,12 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer stream.Close()
|
|
||||||
defer stream.CancelRead(0)
|
|
||||||
err = transport.WriteMessage(stream, 0, message)
|
err = transport.WriteMessage(stream, 0, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
stream.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
stream.Close()
|
||||||
return transport.ReadMessage(stream)
|
return transport.ReadMessage(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,12 +6,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
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/log"
|
"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"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@ -35,10 +37,13 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
serverAddr := options.ServerOptions.Build()
|
serverAddr := options.DNSServerAddressOptions.Build()
|
||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 53
|
serverAddr.Port = 53
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return &TCPTransport{
|
return &TCPTransport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
@ -46,7 +51,15 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPTransport) Reset() {
|
func (t *TCPTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dialer.InitializeDetour(t.dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCPTransport) Close() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
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"
|
||||||
@ -52,26 +53,41 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
serverAddr := options.ServerOptions.Build()
|
serverAddr := options.DNSServerAddressOptions.Build()
|
||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 853
|
serverAddr.Port = 853
|
||||||
}
|
}
|
||||||
return &TLSTransport{
|
if !serverAddr.IsValid() {
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
logger: logger,
|
}
|
||||||
dialer: transportDialer,
|
return NewTLSRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), transportDialer, serverAddr, tlsConfig), nil
|
||||||
serverAddr: serverAddr,
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TLSTransport) Reset() {
|
func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr, tlsConfig tls.Config) *TLSTransport {
|
||||||
|
return &TLSTransport{
|
||||||
|
TransportAdapter: adapter,
|
||||||
|
logger: logger,
|
||||||
|
dialer: dialer,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dialer.InitializeDetour(t.dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSTransport) Close() error {
|
||||||
t.access.Lock()
|
t.access.Lock()
|
||||||
defer t.access.Unlock()
|
defer t.access.Unlock()
|
||||||
for connection := t.connections.Front(); connection != nil; connection = connection.Next() {
|
for connection := t.connections.Front(); connection != nil; connection = connection.Next() {
|
||||||
connection.Value.Close()
|
connection.Value.Close()
|
||||||
}
|
}
|
||||||
t.connections.Init()
|
t.connections.Init()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
@ -7,11 +7,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
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/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@ -42,10 +44,13 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
serverAddr := options.ServerOptions.Build()
|
serverAddr := options.DNSServerAddressOptions.Build()
|
||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 53
|
serverAddr.Port = 53
|
||||||
}
|
}
|
||||||
|
if !serverAddr.IsValid() {
|
||||||
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
|
}
|
||||||
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
|
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +69,19 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPTransport) Reset() {
|
func (t *UDPTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dialer.InitializeDetour(t.dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *UDPTransport) Close() error {
|
||||||
t.access.Lock()
|
t.access.Lock()
|
||||||
defer t.access.Unlock()
|
defer t.access.Unlock()
|
||||||
close(t.done)
|
close(t.done)
|
||||||
t.done = make(chan struct{})
|
t.done = make(chan struct{})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
@ -108,15 +121,8 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
|||||||
conn.access.Unlock()
|
conn.access.Unlock()
|
||||||
defer func() {
|
defer func() {
|
||||||
conn.access.Lock()
|
conn.access.Lock()
|
||||||
delete(conn.callbacks, messageId)
|
delete(conn.callbacks, exMessage.Id)
|
||||||
conn.access.Unlock()
|
conn.access.Unlock()
|
||||||
callback.access.Lock()
|
|
||||||
select {
|
|
||||||
case <-callback.done:
|
|
||||||
default:
|
|
||||||
close(callback.done)
|
|
||||||
}
|
|
||||||
callback.access.Unlock()
|
|
||||||
}()
|
}()
|
||||||
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -210,8 +216,8 @@ type dnsConnection struct {
|
|||||||
|
|
||||||
func (c *dnsConnection) Close(err error) {
|
func (c *dnsConnection) Close(err error) {
|
||||||
c.closeOnce.Do(func() {
|
c.closeOnce.Do(func() {
|
||||||
close(c.done)
|
|
||||||
c.err = err
|
c.err = err
|
||||||
|
close(c.done)
|
||||||
})
|
})
|
||||||
c.Conn.Close()
|
c.Conn.Close()
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
|
|||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options.DialerOptions,
|
Options: options.DialerOptions,
|
||||||
DirectResolver: true,
|
DirectResolver: true,
|
||||||
|
LegacyDNSDialer: options.Legacy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,6 +48,7 @@ func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions)
|
|||||||
Options: options.DialerOptions,
|
Options: options.DialerOptions,
|
||||||
RemoteIsDomain: options.ServerIsDomain(),
|
RemoteIsDomain: options.ServerIsDomain(),
|
||||||
DirectResolver: true,
|
DirectResolver: true,
|
||||||
|
LegacyDNSDialer: options.Legacy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,9 @@ func (m *TransportManager) Start(stage adapter.StartStage) error {
|
|||||||
transports := m.transports
|
transports := m.transports
|
||||||
m.access.Unlock()
|
m.access.Unlock()
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
|
if m.defaultTag != "" && m.defaultTransport == nil {
|
||||||
|
return E.New("default DNS server not found: ", m.defaultTag)
|
||||||
|
}
|
||||||
return m.startTransports(m.transports)
|
return m.startTransports(m.transports)
|
||||||
} else {
|
} else {
|
||||||
for _, outbound := range transports {
|
for _, outbound := range transports {
|
||||||
@ -225,7 +228,7 @@ func (m *TransportManager) Remove(tag string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if started {
|
if started {
|
||||||
transport.Reset()
|
transport.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,201 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 1.12.0-beta.22
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.12.0-beta.21
|
||||||
|
|
||||||
|
* Fix missing `home` option for DERP service **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
You can now choose what the DERP home page shows, just like with derper's `-home` flag.
|
||||||
|
|
||||||
|
See [DERP](/configuration/service/derp/#home).
|
||||||
|
|
||||||
|
### 1.11.13
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-beta.17
|
||||||
|
|
||||||
|
* Update quic-go to v0.52.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.12.0-beta.15
|
||||||
|
|
||||||
|
* Add DERP service **1**
|
||||||
|
* Add Resolved service and DNS server **2**
|
||||||
|
* Add SSM API service **3**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
||||||
|
|
||||||
|
See [DERP Service](/configuration/service/derp/).
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
||||||
|
(e.g. NetworkManager) and provide DNS resolution.
|
||||||
|
|
||||||
|
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
|
||||||
|
|
||||||
|
**3**:
|
||||||
|
|
||||||
|
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
||||||
|
|
||||||
|
See [SSM API Service](/configuration/service/ssm-api/).
|
||||||
|
|
||||||
|
### 1.11.11
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-beta.13
|
||||||
|
|
||||||
|
* Add TLS record fragment route options **1**
|
||||||
|
* Add missing `accept_routes` option for Tailscale **2**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Route Action](/configuration/route/rule_action/#tls_record_fragment).
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
See [Tailscale](/configuration/endpoint/tailscale/#accept_routes).
|
||||||
|
|
||||||
|
#### 1.12.0-beta.10
|
||||||
|
|
||||||
|
* Add control options for listeners **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen/).
|
||||||
|
|
||||||
|
### 1.11.10
|
||||||
|
|
||||||
|
* Undeprecate the `block` outbound **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Since we don’t have a replacement for using the `block` outbound in selectors yet,
|
||||||
|
we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future.
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-beta.9
|
||||||
|
|
||||||
|
* Update quic-go to v0.51.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.11.9
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-beta.5
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.11.8
|
||||||
|
|
||||||
|
* Improve `auto_redirect` **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks,
|
||||||
|
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-beta.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.11.7
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-beta.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
||||||
|
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||||
|
|
||||||
|
### 1.11.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-alpha.19
|
||||||
|
|
||||||
|
* Update gVisor to 20250319.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.12.0-alpha.18
|
||||||
|
|
||||||
|
* Add wildcard SNI support for ShadowTLS inbound **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).
|
||||||
|
|
||||||
|
#### 1.12.0-alpha.17
|
||||||
|
|
||||||
|
* Add NTP sniffer **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Protocol Sniff](/configuration/route/sniff/).
|
||||||
|
|
||||||
|
#### 1.12.0-alpha.16
|
||||||
|
|
||||||
|
* Update `domain_resolver` behavior **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
`route.default_domain_resolver` or `outbound.domain_resolver` is now optional when only one DNS server is configured.
|
||||||
|
|
||||||
|
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
||||||
|
|
||||||
|
### 1.11.5
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
#### 1.12.0-alpha.13
|
#### 1.12.0-alpha.13
|
||||||
|
|
||||||
* Move `predefined` DNS server to DNS rule action **1**
|
* Move `predefined` DNS server to DNS rule action **1**
|
||||||
@ -71,7 +266,8 @@ For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and pa
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
|
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration
|
||||||
|
process._
|
||||||
|
|
||||||
#### 1.12.0-alpha.5
|
#### 1.12.0-alpha.5
|
||||||
|
|
||||||
|
@ -7,6 +7,10 @@ icon: material/apple
|
|||||||
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||||
platform-specific function implementation, such as TUN transparent proxy implementation.
|
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||||
|
|
||||||
|
!!! failure ""
|
||||||
|
|
||||||
|
We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected).
|
||||||
|
|
||||||
## :material-graph: Requirements
|
## :material-graph: Requirements
|
||||||
|
|
||||||
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||||
|
@ -9,6 +9,10 @@ and the data generated by the software is always on your device.
|
|||||||
|
|
||||||
## Android
|
## Android
|
||||||
|
|
||||||
|
The broad package (App) visibility (QUERY_ALL_PACKAGES) permission
|
||||||
|
is used to provide per-application proxy features for VPN,
|
||||||
|
sing-box will not collect your app list.
|
||||||
|
|
||||||
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
|
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
|
||||||
sing-box uses the location permission in the background
|
sing-box uses the location permission in the background
|
||||||
to get information about the connected Wi-Fi network to make them work.
|
to get information about the connected Wi-Fi network to make them work.
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
|
:material-decagram: [servers](#servers)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
:material-plus: [cache_capacity](#cache_capacity)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user