Compare commits

..

131 Commits

Author SHA1 Message Date
世界
f8de60c3fb documentation: Bump version 2025-05-03 14:13:48 +08:00
世界
2982a0599c Update quic-go to v0.51.0 2025-05-03 14:13:24 +08:00
世界
bc367c3df4 Update utls to v1.7.0 2025-05-03 14:13:24 +08:00
世界
4b98206047 Handle EDNS version downgrade 2025-05-03 10:51:22 +08:00
世界
e5ad739961 Make rule_set.format optional 2025-05-03 10:51:22 +08:00
世界
d7867002c5 documentation: Fix anytls padding scheme description 2025-05-01 12:34:34 +08:00
安容
a8bb3a76e8 Report invalid DNS address early 2025-05-01 12:34:34 +08:00
世界
4bcfbf0ae3 Fix wireguard listen_port 2025-05-01 12:34:33 +08:00
世界
34dd12c7b2 clash-api: Add more meta api 2025-05-01 12:34:33 +08:00
世界
1c8fca95e6 Fix DNS lookup 2025-05-01 12:34:33 +08:00
世界
5a32303f91 Fix tailscale sending unexpected stuff 2025-05-01 12:34:33 +08:00
世界
17f41d83f4 Fix fetch ECH configs 2025-05-01 12:34:32 +08:00
reletor
5e0bc79706 documentation: Minor fixes 2025-05-01 12:34:32 +08:00
caelansar
96a33b10c0 Fix callback deletion in UDP transport 2025-05-01 12:34:32 +08:00
世界
204b174191 documentation: Try to make the play review happy 2025-05-01 12:34:31 +08:00
世界
ad79520c02 Fix missing handling of legacy domain_strategy options 2025-05-01 12:34:31 +08:00
世界
86377d5996 Improve local DNS server 2025-05-01 12:34:31 +08:00
anytls
56887a3a08 Update anytls
Co-authored-by: anytls <anytls>
2025-05-01 12:34:30 +08:00
世界
fcbef874e4 Fix DNS dialer 2025-05-01 12:34:30 +08:00
世界
fdd2d799f8 release: Skip override version for iOS 2025-05-01 12:34:30 +08:00
iikira
fa9dad411e Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-05-01 12:34:29 +08:00
ReleTor
4ec222a2ed Fix fetch ECH configs 2025-05-01 12:34:29 +08:00
世界
9cd7c54750 release: Update Go to 1.24.2 2025-05-01 12:34:29 +08:00
世界
3c70546d5f Allow direct outbounds without domain_resolver 2025-05-01 12:34:28 +08:00
世界
64ee2927d7 Fix Tailscale dialer 2025-05-01 12:34:28 +08:00
dyhkwong
9a3196e320 Fix DNS over QUIC stream close 2025-05-01 12:34:28 +08:00
anytls
046fb6e21c Update anytls
Co-authored-by: anytls <anytls>
2025-05-01 12:34:27 +08:00
Rambling2076
7e21c09f35 Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-05-01 12:34:27 +08:00
世界
ed14bbee92 Fail when default DNS server not found 2025-05-01 12:34:27 +08:00
世界
7a3ef2b07b Update gVisor to 20250319.0 2025-05-01 12:34:26 +08:00
世界
786e734a4b release: Do not build tailscale on iOS and tvOS 2025-05-01 12:34:26 +08:00
世界
aca6e3a1af Explicitly reject detour to empty direct outbounds 2025-05-01 12:34:26 +08:00
世界
046308ac31 Add netns support 2025-05-01 12:33:38 +08:00
世界
312ef4e5f7 Add wildcard name support for predefined records 2025-05-01 12:30:55 +08:00
世界
45299d7305 Remove map usage in options 2025-05-01 12:30:55 +08:00
世界
d807ee5011 Fix unhandled DNS loop 2025-05-01 12:30:54 +08:00
世界
2a4e5ee729 Add wildcard-sni support for shadow-tls inbound 2025-05-01 12:30:54 +08:00
世界
7bdd80f10a Fix Tailscale DNS 2025-05-01 12:30:54 +08:00
k9982874
d19d1a677a Add ntp protocol sniffing 2025-05-01 12:30:54 +08:00
世界
b5e66b9bb9 option: Fix marshal legacy DNS options 2025-05-01 12:30:54 +08:00
世界
e0da41b22d Make domain_resolver optional when only one DNS server is configured 2025-05-01 12:30:54 +08:00
世界
efd08f16b8 Fix DNS lookup context pollution 2025-05-01 12:30:53 +08:00
世界
c65b149103 Fix http3 DNS server connecting to wrong address 2025-05-01 12:30:53 +08:00
Restia-Ashbell
c3b7de77e8 documentation: Fix typo 2025-05-01 12:30:52 +08:00
anytls
56c5f001c1 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-05-01 12:30:52 +08:00
k9982874
63c702febd Fix hosts DNS server 2025-05-01 12:30:52 +08:00
世界
163d689fb0 Fix UDP DNS server crash 2025-05-01 12:30:51 +08:00
世界
9280872c5e documentation: Fix missing ip_accept_any DNS rule option 2025-05-01 12:30:51 +08:00
世界
f504c7bb7c Fix anytls dialer usage 2025-05-01 12:30:51 +08:00
世界
50362ed07b Move predefined DNS server to rule action 2025-05-01 12:30:51 +08:00
世界
6012d1ce44 Fix domain resolver on direct outbound 2025-05-01 12:30:50 +08:00
Zephyruso
110eb69384 Fix missing AnyTLS display name 2025-05-01 12:30:50 +08:00
anytls
4f8f5d47cd Update sing-anytls
Co-authored-by: anytls <anytls>
2025-05-01 12:30:50 +08:00
Estel
357d4a8c9d documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-05-01 12:30:49 +08:00
TargetLocked
029a273531 Fix parsing legacy DNS options 2025-05-01 12:30:49 +08:00
世界
02bc695619 Fix DNS fallback 2025-05-01 12:30:48 +08:00
世界
dffbd6861f documentation: Fix missing hosts DNS server 2025-05-01 12:30:48 +08:00
anytls
9b48db1be4 Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-05-01 12:30:47 +08:00
ReleTor
cb9ada4ae3 documentation: Minor fixes 2025-05-01 12:30:47 +08:00
libtry486
4e8024ea7d documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-05-01 12:30:47 +08:00
Alireza Ahmadi
c953f8c809 Fix Outbound deadlock 2025-05-01 12:30:46 +08:00
世界
db41803a1a documentation: Fix AnyTLS doc 2025-05-01 12:30:46 +08:00
anytls
b202589222 Add AnyTLS protocol 2025-05-01 12:30:46 +08:00
世界
16d7d16919 Migrate to stdlib ECH support 2025-05-01 12:30:45 +08:00
世界
07eacfc580 Add fallback local DNS server for iOS 2025-05-01 12:30:45 +08:00
世界
5a37104770 Get darwin local DNS server from libresolv 2025-05-01 12:30:44 +08:00
世界
9ebda6d366 Improve resolve action 2025-05-01 12:30:43 +08:00
世界
56f66ae026 Fix toolchain version 2025-05-01 12:30:43 +08:00
世界
f45ecb3903 Add back port hopping to hysteria 1 2025-05-01 12:30:43 +08:00
世界
b8c68e1896 Update dependencies 2025-05-01 12:30:43 +08:00
xchacha20-poly1305
389479cb85 Remove single quotes of raw Moziila certs 2025-05-01 12:30:42 +08:00
世界
4f2f890dfd Add Tailscale endpoint 2025-05-01 12:30:41 +08:00
世界
22b31c84e1 Build legacy binaries with latest Go 2025-05-01 12:30:41 +08:00
世界
1e64e92e4e documentation: Remove outdated icons 2025-05-01 12:30:41 +08:00
世界
a0a40898bf documentation: Certificate store 2025-05-01 12:30:40 +08:00
世界
7d521ca78f documentation: TLS fragment 2025-05-01 12:30:40 +08:00
世界
8efb0bab2a documentation: Outbound domain resolver 2025-05-01 12:30:40 +08:00
世界
8beec8dcc3 documentation: Refactor DNS 2025-05-01 12:30:39 +08:00
世界
4be6c8b180 Add certificate store 2025-05-01 12:30:39 +08:00
世界
abff5b3705 Add TLS fragment support 2025-05-01 12:30:38 +08:00
世界
00562b5340 refactor: Outbound domain resolver 2025-05-01 12:30:38 +08:00
世界
ac844a672f refactor: DNS 2025-05-01 12:30:38 +08:00
世界
1a43ac8d30 Fix tproxy writeback 2025-05-01 12:27:07 +08:00
世界
d574e9eb52 Update smux to v1.5.34 2025-04-30 19:39:15 +08:00
世界
2d7df1e1f2 Fix hysteria bytes format 2025-04-29 20:45:19 +08:00
世界
1c0ffcf5b1 Fix counter position in auto redirect dnat rules 2025-04-28 11:20:23 +08:00
世界
348cc39975 Bump version 2025-04-27 21:33:31 +08:00
世界
987899f94a Fix usages of wireguard listener 2025-04-27 21:29:23 +08:00
世界
d8b2d5142f Fix panic on some stupid input 2025-04-25 16:03:58 +08:00
世界
134802d1ee Fix ssh outbound 2025-04-25 16:03:57 +08:00
世界
e5e81b4de1 Fix wireguard listening 2025-04-25 16:03:57 +08:00
世界
300c961efa option: Fix listable again and again 2025-04-25 16:03:57 +08:00
世界
7c7f512405 option: Fix omitempty reject method 2025-04-25 16:03:57 +08:00
世界
03e8d029c2 release: Fix apt-get install 2025-04-25 16:03:57 +08:00
世界
787b5f1931 Fix set wireguard reserved on Linux 2025-04-25 16:03:57 +08:00
世界
56a7624618 Fix vmess working with zero uuids 2025-04-25 16:03:57 +08:00
世界
3a84acf122 Fix hysteria1 server panic 2025-04-25 16:03:57 +08:00
世界
f600e02e47 Fix DNS crash 2025-04-25 16:03:57 +08:00
世界
e6d19de58a Fix overriding address 2025-04-22 14:55:44 +08:00
dyhkwong
f2bbf6b2aa Fix sniffer errors override each others
* Fix sniffer errors override each others

* Do not return ErrNeedMoreData if header is not expected
2025-04-22 14:44:55 +08:00
dyhkwong
c54d50fd36 Fix websocket detour
Signed-off-by: trimgop <20010323+trimgop@users.noreply.github.com>
Co-authored-by: trimgop <20010323+trimgop@users.noreply.github.com>
2025-04-22 14:44:34 +08:00
世界
6a051054db release: Fix packages 2025-04-19 19:12:01 +08:00
世界
49498f6439 Bump version 2025-04-18 08:54:40 +08:00
世界
144a890c71 release: Add openwrt packages 2025-04-18 08:54:40 +08:00
世界
afb4993445 Fix urltest outbound 2025-04-18 08:54:40 +08:00
世界
4c9455b944 Fix wireguard endpoint 2025-04-18 08:54:40 +08:00
世界
5fdc051a08 Fix override_port in direct inbound 2025-04-16 17:04:13 +08:00
世界
cb68a40c43 documentation: Update actual behaviors of auto_redirect and strict_route 2025-04-12 13:06:16 +08:00
纳西妲 · Nahida
023218e6e7 Fix build will fail when use space to split each tag 2025-04-12 13:06:16 +08:00
世界
2a24b94b8d Minor fixes 2025-04-12 13:06:15 +08:00
世界
c6531cf184 Fix NTP service 2025-04-12 13:06:15 +08:00
世界
d4fa0ed349 Improve auto redirect 2025-04-12 13:06:10 +08:00
世界
10874d2dc4 Bump version 2025-04-08 14:34:09 +08:00
Fei1Yang
5adaf1ac75 Mark config file as noreplace for rpm 2025-04-08 14:21:08 +08:00
世界
9668ea69b8 Fix windows process searcher 2025-04-08 14:16:27 +08:00
testing
ae9bc7acf1 documentation: Fix typo
Signed-off-by: testing <58134720+testing765@users.noreply.github.com>
2025-04-08 14:16:23 +08:00
世界
594ee480a2 option: Fix listable 2025-04-08 14:16:23 +08:00
世界
a15b5a2463 Fix no_drop not work 2025-04-08 14:16:23 +08:00
Mahdi
991e755789 Fix conn copy 2025-04-08 14:16:22 +08:00
世界
97d41ffde8 Improve pause management 2025-04-08 14:16:22 +08:00
世界
24af0766ac Fix uTP sniffer 2025-04-08 14:16:22 +08:00
世界
af17eaa537 Improve sniffer 2025-04-08 14:16:22 +08:00
世界
3adc10a797 Fix hysteria2 close 2025-04-08 14:16:22 +08:00
xchacha20-poly1305
5eeef6b28e Fix multiple trackers 2025-04-08 14:16:22 +08:00
世界
f4c29840c3 Fix DNS sniffer 2025-03-31 20:45:04 +08:00
世界
47fc3ebda4 Add duplicate tag check 2025-03-29 23:10:22 +08:00
世界
9774a659b0 Fix DoQ / truncate DNS message 2025-03-29 17:41:22 +08:00
世界
2e4a6de4e7 release: Fix read tag 2025-03-27 20:30:57 +08:00
世界
a530e424e9 Bump version 2025-03-27 18:17:39 +08:00
世界
0bfd487ee9 Fix udpnat2 handler again 2025-03-27 18:17:39 +08:00
世界
6aae834493 release: Fix workflow 2025-03-27 18:17:39 +08:00
193 changed files with 2052 additions and 8375 deletions

30
.fpm_openwrt Normal file
View 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

View File

@@ -1,11 +1,13 @@
-s dir
--name sing-box
--category net
--license GPLv3-or-later
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
--no-deb-generate-changes
--config-files /etc/sing-box/config.json
release/config/config.json=/etc/sing-box/config.json

28
.github/deb2ipk.sh vendored Executable file
View 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

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.2
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -55,7 +55,7 @@ jobs:
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --nightly
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
@@ -68,31 +68,38 @@ jobs:
- calculate_version
strategy:
matrix:
os: [ linux, windows, darwin, android ]
arch: [ "386", amd64, arm64 ]
legacy_go: [ false ]
include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: "386", debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- { os: windows, arch: "386", legacy_go: true }
- { os: windows, arch: amd64 }
- { os: windows, arch: amd64, legacy_go: true }
- { os: windows, arch: "386" }
- { os: windows, arch: "386", legacy_go: true }
- { os: windows, arch: arm64 }
- { os: darwin, arch: amd64 }
- { os: darwin, arch: arm64 }
- { os: android, arch: "386", ndk: "i686-linux-android21" }
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
exclude:
- { os: darwin, arch: "386" }
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- { os: android, arch: "386", ndk: "i686-linux-android21" }
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@@ -102,7 +109,7 @@ jobs:
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.2
- name: Cache Legacy Go
if: matrix.require_legacy_go
id: cache-legacy-go
@@ -133,10 +140,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
TAGS="${TAGS},with_ech"
fi
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
if: matrix.os != 'android'
@@ -150,7 +154,10 @@ jobs:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build Android
if: matrix.os == 'android'
@@ -170,21 +177,31 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name
run: |-
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
if [[ -n "${{ matrix.goarm }}" ]]; then
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
DIR_NAME="${DIR_NAME}-legacy"
fi
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}"
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
- 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 "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${PKG_NAME}.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'
@@ -199,9 +216,10 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${PKG_NAME}.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
@@ -217,20 +235,37 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
sudo apt-get update
sudo apt-get install -y libarchive-tools
cp .fpm_systemd .fpm
fpm -t pacman \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${PKG_NAME}.pkg.tar.zst" \
-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 }}' = 'windoes' ]; then
cp sing-box.exe "${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}"
@@ -242,7 +277,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
build_android:
name: Build Android
@@ -259,7 +294,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.2
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -316,9 +351,9 @@ jobs:
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare upload
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
mkdir -p dist
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
@@ -339,7 +374,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.2
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -437,7 +472,7 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.2
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
@@ -514,10 +549,13 @@ jobs:
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" >> "$GITHUB_ENV"
- name: Update version
if: matrix.if && matrix.name != 'iOS'
run: |-
go run -v ./cmd/internal/update_apple_version --ci
- name: Build
if: matrix.if
run: |-
go run -v ./cmd/internal/update_apple_version --ci
cd clients/apple
xcodebuild archive \
-scheme "${{ matrix.scheme }}" \
@@ -566,9 +604,9 @@ jobs:
zip -r SFM.dSYMs.zip dSYMs
popd
mkdir -p dist/release
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
mkdir -p dist
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
- name: Upload image
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4

View File

@@ -28,7 +28,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.2
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:

View File

@@ -25,7 +25,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.2
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -34,7 +34,7 @@ jobs:
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --nightly
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
@@ -66,7 +66,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.2
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
@@ -80,10 +80,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
TAGS="${TAGS},with_ech"
fi
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
run: |
@@ -109,17 +106,24 @@ jobs:
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 \
-v "${{ needs.calculate_version.outputs.version }}" \
--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/${NAME}
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
@@ -132,11 +136,13 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \
-v "${{ needs.calculate_version.outputs.version }}" \
--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/${NAME}
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 }}
@@ -175,6 +181,4 @@ jobs:
merge-multiple: true
- name: Publish packages
run: |-
wget -O fury-cli.deb https://github.com/gemfury/cli/releases/download/v0.23.0/fury-cli_0.23.0_linux_amd64.deb
sudo dpkg -i fury-cli.deb
fury migrate dist --as=sagernet --api-token ${{ secrets.FURY_TOKEN }}
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
/.idea/
/vendor/
/*.json
/*.js
/*.srs
/*.db
/site/

View File

@@ -28,10 +28,8 @@ run:
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
- with_acme
- with_clash_api
- with_script
issues:
exclude-dirs:

View File

@@ -15,7 +15,6 @@ builds:
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
- with_acme
- with_clash_api
- with_tailscale
@@ -50,7 +49,7 @@ nfpms:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
type: "config|noreplace"
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service

View File

@@ -17,11 +17,9 @@ builds:
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
- with_acme
- with_clash_api
- with_tailscale
- with_script
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
@@ -48,11 +46,9 @@ builds:
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
- with_acme
- with_clash_api
- with_tailscale
- with_script
env:
- CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go_legacy
@@ -134,7 +130,7 @@ nfpms:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
type: "config|noreplace"
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service

View File

@@ -13,7 +13,7 @@ RUN set -ex \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api" \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
-o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box

View File

@@ -1,14 +1,14 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale,with_script
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_clash_api,with_quic,with_utls,with_tailscale
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
@@ -24,7 +24,7 @@ ci_build:
go build $(MAIN_PARAMS) $(MAIN)
generate_completions:
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
@@ -226,8 +226,8 @@ lib:
go run ./cmd/internal/build_libbox -target ios
lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.5
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.5
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
docs:
venv/bin/mkdocs serve
@@ -246,4 +246,4 @@ clean:
update:
git fetch
git reset FETCH_HEAD --hard
git clean -fdx
git clean -fdx

View File

@@ -10,9 +10,6 @@ import (
type CertificateStore interface {
LifecycleService
Pool() *x509.CertPool
TLSDecryptionEnabled() bool
TLSDecryptionCertificate() *x509.Certificate
TLSDecryptionPrivateKey() any
}
func RootPoolFromContext(ctx context.Context) *x509.CertPool {

View File

@@ -52,10 +52,6 @@ type CacheFile interface {
StoreGroupExpand(group string, expand bool) error
LoadRuleSet(tag string) *SavedBinary
SaveRuleSet(tag string, set *SavedBinary) error
LoadScript(tag string) *SavedBinary
SaveScript(tag string, script *SavedBinary) error
SurgePersistentStoreRead(key string) string
SurgePersistentStoreWrite(key string, value string) error
}
type SavedBinary struct {

View File

@@ -2,8 +2,6 @@ package adapter
import (
"context"
"crypto/tls"
"net/http"
"net/netip"
"time"
@@ -60,8 +58,6 @@ type InboundContext struct {
Client string
SniffContext any
PacketSniffError error
HTTPRequest *http.Request
ClientHello *tls.ClientHelloInfo
// cache
@@ -78,7 +74,6 @@ type InboundContext struct {
UDPTimeout time.Duration
TLSFragment bool
TLSFragmentFallbackDelay time.Duration
MITM *option.MITMRouteOptions
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType

View File

@@ -1,8 +1,6 @@
package adapter
import (
E "github.com/sagernet/sing/common/exceptions"
)
import E "github.com/sagernet/sing/common/exceptions"
type StartStage uint8
@@ -47,9 +45,6 @@ type LifecycleService interface {
func Start(stage StartStage, services ...Lifecycle) error {
for _, service := range services {
if service == nil {
continue
}
err := service.Start(stage)
if err != nil {
return err

View File

@@ -1,13 +0,0 @@
package adapter
import (
"context"
"net"
N "github.com/sagernet/sing/common/network"
)
type MITMEngine interface {
Lifecycle
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
}

View File

@@ -24,7 +24,7 @@ type Router interface {
RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool
Rules() []Rule
SetTracker(tracker ConnectionTracker)
AppendTracker(tracker ConnectionTracker)
ResetNetwork()
}

View File

@@ -1,54 +0,0 @@
package adapter
import (
"context"
"net/http"
"sync"
"time"
)
type ScriptManager interface {
Lifecycle
Scripts() []Script
Script(name string) (Script, bool)
SurgeCache() *SurgeInMemoryCache
}
type SurgeInMemoryCache struct {
sync.RWMutex
Data map[string]string
}
type Script interface {
Type() string
Tag() string
StartContext(ctx context.Context, startContext *HTTPStartContext) error
PostStart() error
Close() error
}
type SurgeScript interface {
Script
ExecuteGeneric(ctx context.Context, scriptType string, timeout time.Duration, arguments []string) error
ExecuteHTTPRequest(ctx context.Context, timeout time.Duration, request *http.Request, body []byte, binaryBody bool, arguments []string) (*HTTPRequestScriptResult, error)
ExecuteHTTPResponse(ctx context.Context, timeout time.Duration, request *http.Request, response *http.Response, body []byte, binaryBody bool, arguments []string) (*HTTPResponseScriptResult, error)
}
type HTTPRequestScriptResult struct {
URL string
Headers http.Header
Body []byte
Response *HTTPRequestScriptResponse
}
type HTTPRequestScriptResponse struct {
Status int
Headers http.Header
Body []byte
}
type HTTPResponseScriptResult struct {
Status int
Headers http.Header
Body []byte
}

55
box.go
View File

@@ -23,11 +23,9 @@ import (
"github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/mitm"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route"
"github.com/sagernet/sing-box/script"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
@@ -50,8 +48,6 @@ type Box struct {
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
script *script.Manager
mitm adapter.MITMEngine //*mitm.Engine
services []adapter.LifecycleService
done chan struct{}
}
@@ -147,12 +143,18 @@ func New(options Options) (*Box, error) {
}
var services []adapter.LifecycleService
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), common.PtrValueOrDefault(options.Certificate))
if err != nil {
return nil, err
certificateOptions := common.PtrValueOrDefault(options.Certificate)
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
len(certificateOptions.Certificate) > 0 ||
len(certificateOptions.CertificatePath) > 0 ||
len(certificateOptions.CertificateDirectoryPath) > 0 {
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
if err != nil {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
routeOptions := common.PtrValueOrDefault(options.Route)
dnsOptions := common.PtrValueOrDefault(options.DNS)
@@ -171,7 +173,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize network manager")
}
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection"))
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router)
@@ -179,8 +181,8 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "initialize router")
}
var timeService *tls.TimeServiceWrapper
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
@@ -294,11 +296,6 @@ func New(options Options) (*Box, error) {
"local",
option.LocalDNSServerOptions{},
)))
scriptManager, err := script.NewManager(ctx, logFactory, options.Scripts)
if err != nil {
return nil, E.Cause(err, "initialize script manager")
}
service.MustRegister[adapter.ScriptManager](ctx, scriptManager)
if platformInterface != nil {
err = platformInterface.Initialize(networkManager)
if err != nil {
@@ -317,7 +314,7 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "create clash-server")
}
router.SetTracker(clashServer)
router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
services = append(services, clashServer)
}
@@ -327,7 +324,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create v2ray-server")
}
if v2rayServer.StatsService() != nil {
router.SetTracker(v2rayServer.StatsService())
router.AppendTracker(v2rayServer.StatsService())
services = append(services, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
@@ -348,16 +345,6 @@ func New(options Options) (*Box, error) {
timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
}
mitmOptions := common.PtrValueOrDefault(options.MITM)
var mitmEngine adapter.MITMEngine
if mitmOptions.Enabled {
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
if err != nil {
return nil, E.Cause(err, "create MITM engine")
}
service.MustRegister[adapter.MITMEngine](ctx, engine)
mitmEngine = engine
}
return &Box{
network: networkManager,
endpoint: endpointManager,
@@ -367,8 +354,6 @@ func New(options Options) (*Box, error) {
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
script: scriptManager,
mitm: mitmEngine,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
@@ -427,11 +412,11 @@ func (s *Box) preStart() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.script, s.mitm)
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil {
return err
}
@@ -455,7 +440,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
if err != nil {
return err
}
@@ -463,7 +448,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil {
return err
}
@@ -482,7 +467,7 @@ func (s *Box) Close() error {
close(s.done)
}
err := common.Close(
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, 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 {
err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -59,7 +59,7 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_script")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
memcTags = append(memcTags, "with_tailscale")
debugTags = append(debugTags, "debug")

View File

@@ -5,40 +5,49 @@ import (
"os"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing-box/log"
)
var nightly bool
var (
flagRunInCI bool
flagRunNightly bool
)
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() {
flag.Parse()
if nightly {
version, err := build_shared.ReadTagVersionRev()
var (
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 {
log.Fatal(err)
}
var versionStr string
if version.PreReleaseIdentifier != "" {
versionStr = version.VersionString() + "-nightly"
} else {
version.Patch++
versionStr = version.VersionString() + "-nightly"
}
err = setGitHubEnv("version", versionStr)
if err != nil {
log.Fatal(err)
}
} else {
tag, err := build_shared.ReadTag()
if err != nil {
log.Error(err)
os.Stdout.WriteString("unknown\n")
} else {
os.Stdout.WriteString(tag + "\n")
os.Stdout.WriteString(versionStr + "\n")
}
}
}

View File

@@ -1,121 +0,0 @@
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"math/big"
"os"
"path/filepath"
"strings"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
"software.sslmate.com/src/go-pkcs12"
)
var (
flagGenerateCAName string
flagGenerateCAPKCS12Password string
flagGenerateOutput string
)
var commandGenerateCAKeyPair = &cobra.Command{
Use: "ca-keypair",
Short: "Generate CA key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateCAKeyPair()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateCAName, "name", "n", "", "Set custom CA name")
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateCAPKCS12Password, "p12-password", "p", "", "Set custom PKCS12 password")
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateOutput, "output", "o", ".", "Set output directory")
commandGenerate.AddCommand(commandGenerateCAKeyPair)
}
func generateCAKeyPair() error {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return err
}
spkiASN1, err := x509.MarshalPKIXPublicKey(privateKey.Public())
var spki struct {
Algorithm pkix.AlgorithmIdentifier
SubjectPublicKey asn1.BitString
}
_, err = asn1.Unmarshal(spkiASN1, &spki)
if err != nil {
return err
}
skid := sha1.Sum(spki.SubjectPublicKey.Bytes)
var caName string
if flagGenerateCAName != "" {
caName = flagGenerateCAName
} else {
caName = "sing-box Generated CA " + strings.ToUpper(hex.EncodeToString(skid[:4]))
}
caTpl := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{caName},
CommonName: caName,
},
SubjectKeyId: skid[:],
NotAfter: time.Now().AddDate(10, 0, 0),
NotBefore: time.Now(),
KeyUsage: x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLenZero: true,
}
publicDer, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, privateKey.Public(), privateKey)
var caPassword string
if flagGenerateCAPKCS12Password != "" {
caPassword = flagGenerateCAPKCS12Password
} else {
caPassword = strings.ToUpper(hex.EncodeToString(skid[:4]))
}
caTpl.Raw = publicDer
p12Bytes, err := pkcs12.Modern.Encode(privateKey, caTpl, nil, caPassword)
if err != nil {
return err
}
privateDer, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return err
}
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".pem"), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".private.pem"), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer}), 0o644)
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".crt"), publicDer, 0o644)
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".p12"), p12Bytes, 0o644)
var tlsDecryptionOptions option.TLSDecryptionOptions
tlsDecryptionOptions.Enabled = true
tlsDecryptionOptions.KeyPair = base64.StdEncoding.EncodeToString(p12Bytes)
tlsDecryptionOptions.KeyPairPassword = caPassword
var certificateOptions option.CertificateOptions
certificateOptions.TLSDecryption = &tlsDecryptionOptions
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
return encoder.Encode(certificateOptions)
}

View File

@@ -5,6 +5,7 @@ import (
"context"
"io"
"os"
"path/filepath"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
@@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error {
if err != nil {
return E.Cause(err, "read rule-set")
}
if flagRuleSetMatchFormat == "" {
switch filepath.Ext(sourcePath) {
case ".json":
flagRuleSetMatchFormat = C.RuleSetFormatSource
case ".srs":
flagRuleSetMatchFormat = C.RuleSetFormatBinary
}
}
var ruleSet option.PlainRuleSetCompat
switch flagRuleSetMatchFormat {
case C.RuleSetFormatSource:

View File

@@ -1,6 +1,13 @@
package main
import (
"errors"
"os"
"github.com/sagernet/sing-box"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/spf13/cobra"
)
@@ -12,5 +19,36 @@ var commandTools = &cobra.Command{
}
func init() {
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
mainCommand.AddCommand(commandTools)
}
func createPreStartedClient() (*box.Box, error) {
options, err := readConfigAndMerge()
if err != nil {
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
return nil, err
}
}
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
if err != nil {
return nil, E.Cause(err, "create service")
}
err = instance.PreStart()
if err != nil {
return nil, E.Cause(err, "start service")
}
return instance, nil
}
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
if outboundTag == "" {
return instance.Outbound().Default(), nil
} else {
outbound, loaded := instance.Outbound().Outbound(outboundTag)
if !loaded {
return nil, E.New("outbound not found: ", outboundTag)
}
return outbound, nil
}
}

View File

@@ -0,0 +1,73 @@
package main
import (
"context"
"os"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/task"
"github.com/spf13/cobra"
)
var commandConnectFlagNetwork string
var commandConnect = &cobra.Command{
Use: "connect <address>",
Short: "Connect to an address",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := connect(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
commandTools.AddCommand(commandConnect)
}
func connect(address string) error {
switch N.NetworkName(commandConnectFlagNetwork) {
case N.NetworkTCP, N.NetworkUDP:
default:
return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)
}
instance, err := createPreStartedClient()
if err != nil {
return err
}
defer instance.Close()
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return err
}
conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))
if err != nil {
return E.Cause(err, "connect to server")
}
var group task.Group
group.Append("upload", func(ctx context.Context) error {
return common.Error(bufio.Copy(conn, os.Stdin))
})
group.Append("download", func(ctx context.Context) error {
return common.Error(bufio.Copy(os.Stdout, conn))
})
group.Cleanup(func() {
conn.Close()
})
err = group.Run(context.Background())
if E.IsClosed(err) {
log.Info(err)
} else {
log.Error(err)
}
return nil
}

View File

@@ -0,0 +1,115 @@
package main
import (
"context"
"errors"
"io"
"net"
"net/http"
"net/url"
"os"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/spf13/cobra"
)
var commandFetch = &cobra.Command{
Use: "fetch",
Short: "Fetch an URL",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := fetch(args)
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandTools.AddCommand(commandFetch)
}
var (
httpClient *http.Client
http3Client *http.Client
)
func fetch(args []string) error {
instance, err := createPreStartedClient()
if err != nil {
return err
}
defer instance.Close()
httpClient = &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return nil, err
}
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
},
ForceAttemptHTTP2: true,
},
}
defer httpClient.CloseIdleConnections()
if C.WithQUIC {
err = initializeHTTP3Client(instance)
if err != nil {
return err
}
defer http3Client.CloseIdleConnections()
}
for _, urlString := range args {
var parsedURL *url.URL
parsedURL, err = url.Parse(urlString)
if err != nil {
return err
}
switch parsedURL.Scheme {
case "":
parsedURL.Scheme = "http"
fallthrough
case "http", "https":
err = fetchHTTP(httpClient, parsedURL)
if err != nil {
return err
}
case "http3":
if !C.WithQUIC {
return C.ErrQUICNotIncluded
}
parsedURL.Scheme = "https"
err = fetchHTTP(http3Client, parsedURL)
if err != nil {
return err
}
default:
return E.New("unsupported scheme: ", parsedURL.Scheme)
}
}
return nil
}
func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
request, err := http.NewRequest("GET", parsedURL.String(), nil)
if err != nil {
return err
}
request.Header.Add("User-Agent", "curl/7.88.0")
response, err := httpClient.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
_, err = bufio.Copy(os.Stdout, response.Body)
if errors.Is(err, io.EOF) {
return nil
}
return err
}

View File

@@ -0,0 +1,36 @@
//go:build with_quic
package main
import (
"context"
"crypto/tls"
"net/http"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
box "github.com/sagernet/sing-box"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func initializeHTTP3Client(instance *box.Box) error {
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return err
}
http3Client = &http.Client{
Transport: &http3.Transport{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
destination := M.ParseSocksaddr(addr)
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
if dErr != nil {
return nil, dErr
}
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)
},
},
}
return nil
}

View File

@@ -0,0 +1,18 @@
//go:build !with_quic
package main
import (
"net/url"
"os"
box "github.com/sagernet/sing-box"
)
func initializeHTTP3Client(instance *box.Box) error {
return os.ErrInvalid
}
func fetchHTTP3(parsedURL *url.URL) error {
return os.ErrInvalid
}

View File

@@ -1,108 +0,0 @@
package main
import (
"encoding/pem"
"errors"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/shell"
"github.com/spf13/cobra"
)
var commandInstallCACertificate = &cobra.Command{
Use: "install-ca <path to certificate>",
Short: "Install CA certificate to system",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := installCACertificate(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandTools.AddCommand(commandInstallCACertificate)
}
func installCACertificate(path string) error {
switch runtime.GOOS {
case "windows":
return shell.Exec("powershell", "-Command", "Import-Certificate -FilePath \""+path+"\" -CertStoreLocation Cert:\\LocalMachine\\Root").Attach().Run()
case "darwin":
return shell.Exec("sudo", "security", "add-trusted-cert", "-d", "-r", "trustRoot", "-k", "/Library/Keychains/System.keychain", path).Attach().Run()
case "linux":
updateCertPath, updateCertPathNotFoundErr := exec.LookPath("update-ca-certificates")
if updateCertPathNotFoundErr == nil {
publicDer, err := os.ReadFile(path)
if err != nil {
return err
}
err = os.MkdirAll("/usr/local/share/ca-certificates", 0o755)
if err != nil {
if errors.Is(err, os.ErrPermission) {
log.Info("Try running with sudo")
return shell.Exec("sudo", os.Args...).Attach().Run()
}
return err
}
fileName := filepath.Base(updateCertPath)
if !strings.HasSuffix(fileName, ".crt") {
fileName = fileName + ".crt"
}
filePath, _ := filepath.Abs(filepath.Join("/usr/local/share/ca-certificates", fileName))
err = os.WriteFile(filePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
if err != nil {
if errors.Is(err, os.ErrPermission) {
log.Info("Try running with sudo")
return shell.Exec("sudo", os.Args...).Attach().Run()
}
return err
}
log.Info("certificate written to " + filePath + "\n")
err = shell.Exec(updateCertPath).Attach().Run()
if err != nil {
return err
}
log.Info("certificate installed")
return nil
}
updateTrustPath, updateTrustPathNotFoundErr := exec.LookPath("update-ca-trust")
if updateTrustPathNotFoundErr == nil {
publicDer, err := os.ReadFile(path)
if err != nil {
return err
}
fileName := filepath.Base(updateTrustPath)
fileExt := filepath.Ext(path)
if fileExt != "" {
fileName = fileName[:len(fileName)-len(fileExt)]
}
filePath, _ := filepath.Abs(filepath.Join("/etc/pki/ca-trust/source/anchors/", fileName+".pem"))
err = os.WriteFile(filePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
if err != nil {
if errors.Is(err, os.ErrPermission) {
log.Info("Try running with sudo")
return shell.Exec("sudo", os.Args...).Attach().Run()
}
return err
}
log.Info("certificate written to " + filePath + "\n")
err = shell.Exec(updateTrustPath, "extract").Attach().Run()
if err != nil {
return err
}
log.Info("certificate installed")
}
return E.New("update-ca-certificates or update-ca-trust not found")
default:
return E.New("unsupported operating system: ", runtime.GOOS)
}
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/spf13/cobra"
@@ -40,11 +39,20 @@ func init() {
}
func syncTime() error {
instance, err := createPreStartedClient()
if err != nil {
return err
}
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return err
}
defer instance.Close()
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
if serverAddress.Port == 0 {
serverAddress.Port = 123
}
response, err := ntp.Exchange(context.Background(), N.SystemDialer, serverAddress)
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
if err != nil {
return err
}

View File

@@ -7,7 +7,8 @@ import (
_ "unsafe"
"github.com/sagernet/sing/common"
"github.com/sagernet/utls"
"github.com/metacubex/utls"
)
func init() {
@@ -24,8 +25,8 @@ func init() {
})
}
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
func utlsReadRecord(c *tls.Conn) error
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
func utlsHandlePostHandshakeMessage(c *tls.Conn) error

View File

@@ -3,7 +3,6 @@ package certificate
import (
"context"
"crypto/x509"
"encoding/base64"
"io/fs"
"os"
"path/filepath"
@@ -17,8 +16,6 @@ import (
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service"
"software.sslmate.com/src/go-pkcs12"
)
var _ adapter.CertificateStore = (*Store)(nil)
@@ -30,9 +27,6 @@ type Store struct {
certificatePaths []string
certificateDirectoryPaths []string
watcher *fswatch.Watcher
tlsDecryptionEnabled bool
tlsDecryptionPrivateKey any
tlsDecryptionCertificate *x509.Certificate
}
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
@@ -96,19 +90,6 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
if err != nil {
return nil, E.Cause(err, "initializing certificate store")
}
if options.TLSDecryption != nil && options.TLSDecryption.Enabled {
pfxBytes, err := base64.StdEncoding.DecodeString(options.TLSDecryption.KeyPair)
if err != nil {
return nil, E.Cause(err, "decode key pair base64 bytes")
}
privateKey, certificate, err := pkcs12.Decode(pfxBytes, options.TLSDecryption.KeyPairPassword)
if err != nil {
return nil, E.Cause(err, "decode key pair")
}
store.tlsDecryptionEnabled = true
store.tlsDecryptionPrivateKey = privateKey
store.tlsDecryptionCertificate = certificate
}
return store, nil
}
@@ -202,15 +183,3 @@ func isSameDirSymlink(f fs.DirEntry, dir string) bool {
target, err := os.Readlink(filepath.Join(dir, f.Name()))
return err == nil && !strings.Contains(target, "/")
}
func (s *Store) TLSDecryptionEnabled() bool {
return s.tlsDecryptionEnabled
}
func (s *Store) TLSDecryptionCertificate() *x509.Certificate {
return s.tlsDecryptionCertificate
}
func (s *Store) TLSDecryptionPrivateKey() any {
return s.tlsDecryptionPrivateKey
}

View File

@@ -71,18 +71,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
listener.Control = control.Append(listener.Control, bindFunc)
}
if options.RoutingMark > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
}
if networkManager != nil {
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
if autoRedirectOutputMark > 0 {
if options.RoutingMark > 0 {
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
}
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
}
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
}
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
if disableDefaultBind || options.TCPFastOpen {
@@ -127,8 +117,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
}
}
if options.ReuseAddr {
@@ -210,6 +200,22 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
}, nil
}
func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefault bool) control.Func {
if networkManager == nil {
return control.RoutingMark(mark)
}
return func(network, address string, conn syscall.RawConn) error {
if networkManager.AutoRedirectOutputMark() != 0 {
if isDefault {
return E.New("`route.default_mark` is conflict with `tun.auto_redirect`")
} else {
return E.New("`routing_mark` is conflict with `tun.auto_redirect`")
}
}
return control.RoutingMark(mark)(network, address, conn)
}
}
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
if !address.IsValid() {
return nil, E.New("invalid address")
@@ -335,7 +341,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
return d.udpListener.ListenPacket(context.Background(), network, address)
udpListener := d.udpListener
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
for _, wgControlFn := range WgControlFns {
err := wgControlFn(network, address, conn)
if err != nil {
return err
}
}
return nil
})
return udpListener.ListenPacket(context.Background(), network, address)
}
func trackConn(conn net.Conn, err error) (net.Conn, error) {

View File

@@ -24,6 +24,7 @@ type Options struct {
ResolverOnDetour bool
NewDialer bool
LegacyDNSDialer bool
DirectOutbound bool
}
// TODO: merge with NewWithOptions
@@ -82,6 +83,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
}
server = dialOptions.DomainResolver.Server
dnsQueryOptions = adapter.DNSQueryOptions{
@@ -94,22 +96,31 @@ func NewWithOptions(options Options) (N.Dialer, error) {
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else if options.DirectResolver {
return nil, E.New("missing domain resolver for domain server address")
} else if defaultOptions.DomainResolver != "" {
dnsQueryOptions = defaultOptions.DomainResolveOptions
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
if !loaded {
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
}
dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address")
} else {
transports := dnsTransport.Transports()
if len(transports) < 2 {
dnsQueryOptions.Transport = dnsTransport.Default()
if defaultOptions.DomainResolver != "" {
dnsQueryOptions = defaultOptions.DomainResolveOptions
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
if !loaded {
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
}
dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
transports := dnsTransport.Transports()
if len(transports) < 2 {
dnsQueryOptions.Transport = dnsTransport.Default()
} else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address")
} else if !options.DirectOutbound {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
}
}
if
//nolint:staticcheck
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
}
}
dialer = NewResolveDialer(

View File

@@ -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)
}

View File

@@ -37,6 +37,12 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
return udpConn, err
}
func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
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) {
return listenConfig.ListenPacket(ctx, network, address)

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
)
const (
@@ -23,21 +24,26 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
var first byte
err := binary.Read(reader, binary.BigEndian, &first)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if first != 19 {
return os.ErrInvalid
}
const header = "BitTorrent protocol"
var protocol [19]byte
_, err = reader.Read(protocol[:])
if err != nil {
return err
}
if string(protocol[:]) != "BitTorrent protocol" {
var n int
n, err = reader.Read(protocol[:])
if string(protocol[:n]) != header[:n] {
return os.ErrInvalid
}
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
if n < 19 {
return ErrNeedMoreData
}
metadata.Protocol = C.ProtocolBitTorrent
return nil
@@ -67,7 +73,9 @@ func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) err
if err != nil {
return err
}
if extension > 0x04 {
return os.ErrInvalid
}
var length byte
err = binary.Read(reader, binary.BigEndian, &length)
if err != nil {

View File

@@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) {
}
}
func TestSniffIncompleteBittorrent(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("13426974546f7272656e74")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotBittorrent(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("13426974546f7272656e75")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffUTP(t *testing.T) {
t.Parallel()
@@ -71,3 +92,19 @@ func TestSniffUDPTracker(t *testing.T) {
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)
}
}

View File

@@ -5,14 +5,11 @@ import (
"encoding/binary"
"io"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/task"
E "github.com/sagernet/sing/common/exceptions"
mDNS "github.com/miekg/dns"
)
@@ -21,35 +18,40 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
var length uint16
err := binary.Read(reader, binary.BigEndian, &length)
if err != nil {
return os.ErrInvalid
return E.Cause1(ErrNeedMoreData, err)
}
if length == 0 {
if length < 12 {
return os.ErrInvalid
}
buffer := buf.NewSize(int(length))
defer buffer.Release()
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
var readTask task.Group
readTask.Append0(func(ctx context.Context) error {
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen()))
})
err = readTask.Run(readCtx)
cancel()
if err != nil {
return err
var n int
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
packet := buffer.Bytes()
if n > 2 && packet[2]&0x80 != 0 { // QR
return os.ErrInvalid
}
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 {
var msg mDNS.Msg
err := msg.Unpack(packet)
if err != nil {
if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
return err
}
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
return os.ErrInvalid
}
metadata.Protocol = C.ProtocolDNS
return nil
}

53
common/sniff/dns_test.go Normal file
View 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)
}

View File

@@ -3,10 +3,12 @@ package sniff
import (
std_bufio "bufio"
"context"
"errors"
"io"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/http"
)
@@ -14,10 +16,13 @@ import (
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
request, err := http.ReadRequest(std_bufio.NewReader(reader))
if err != nil {
return err
if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err
}
}
metadata.Protocol = C.ProtocolHTTP
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
metadata.HTTPRequest = request
return nil
}

View File

@@ -20,8 +20,6 @@ import (
"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 {
reader := bytes.NewReader(packet)
typeByte, err := reader.ReadByte()
@@ -308,7 +306,7 @@ find:
metadata.Protocol = C.ProtocolQUIC
metadata.Client = C.ClientChromium
metadata.SniffContext = fragments
return ErrClientHelloFragmented
return E.Cause1(ErrNeedMoreData, err)
}
metadata.Domain = fingerprint.ServerName
for metadata.Client == "" {

View File

@@ -20,11 +20,11 @@ func TestSniffQUICChromeNew(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
@@ -40,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)

View File

@@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
)
@@ -15,7 +16,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktVersion uint8
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if tpktVersion != 0x03 {
return os.ErrInvalid
@@ -24,7 +25,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktReserved uint8
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if tpktReserved != 0x00 {
return os.ErrInvalid
@@ -33,7 +34,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktLength uint16
err = binary.Read(reader, binary.BigEndian, &tpktLength)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if tpktLength != 19 {
@@ -43,7 +44,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var cotpLength uint8
err = binary.Read(reader, binary.BigEndian, &cotpLength)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if cotpLength != 14 {
@@ -53,7 +54,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var cotpTpduType uint8
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if cotpTpduType != 0xE0 {
return os.ErrInvalid
@@ -61,13 +62,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
err = rw.SkipN(reader, 5)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
var rdpType uint8
err = binary.Read(reader, binary.BigEndian, &rdpType)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if rdpType != 0x01 {
return os.ErrInvalid
@@ -75,12 +76,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var rdpFlags uint8
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
var rdpLength uint8
err = binary.Read(reader, binary.BigEndian, &rdpLength)
if err != nil {
return err
return E.Cause1(ErrNeedMoreData, err)
}
if rdpLength != 8 {
return os.ErrInvalid

View File

@@ -3,6 +3,7 @@ package sniff
import (
"bytes"
"context"
"errors"
"io"
"net"
"time"
@@ -19,6 +20,8 @@ type (
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
)
var ErrNeedMoreData = E.New("need more data")
func Skip(metadata *adapter.InboundContext) bool {
// skip server first protocols
switch metadata.Destination.Port {
@@ -40,7 +43,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
timeout = C.ReadPayloadTimeout
}
deadline := time.Now().Add(timeout)
var errors []error
var sniffError error
for i := 0; ; i++ {
err := conn.SetReadDeadline(deadline)
if err != nil {
@@ -54,7 +57,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
}
return E.Cause(err, "read payload")
}
errors = nil
sniffError = nil
for _, sniffer := range sniffers {
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
return bytes.NewReader(it.Bytes())
@@ -63,20 +66,23 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
if err == 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 {
var errors []error
var sniffError []error
for _, sniffer := range sniffers {
err := sniffer(ctx, metadata, packet)
if err == nil {
return nil
}
errors = append(errors, err)
sniffError = append(sniffError, err)
}
return E.Errors(errors...)
return E.Errors(sniffError...)
}

View File

@@ -5,22 +5,27 @@ import (
"context"
"io"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
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 {
scanner := bufio.NewScanner(reader)
if !scanner.Scan() {
const sshPrefix = "SSH-2.0-"
bReader := bufio.NewReader(reader)
prefix, err := bReader.Peek(len(sshPrefix))
if string(prefix[:]) != sshPrefix[:len(prefix)] {
return os.ErrInvalid
}
fistLine := scanner.Text()
if !strings.HasPrefix(fistLine, "SSH-2.0-") {
return os.ErrInvalid
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
fistLine, _, err := bReader.ReadLine()
if err != nil {
return err
}
metadata.Protocol = C.ProtocolSSH
metadata.Client = fistLine[8:]
metadata.Client = string(fistLine)[8:]
return nil
}

View File

@@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) {
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
require.Equal(t, "dropbear", metadata.Client)
}
func TestSniffIncompleteSSH(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("5353482d322e30")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotSSH(t *testing.T) {
t.Parallel()
pkt, err := hex.DecodeString("5353482d322e31")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}

View File

@@ -3,11 +3,13 @@ package sniff
import (
"context"
"crypto/tls"
"errors"
"io"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"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 {
@@ -21,8 +23,11 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
if clientHello != nil {
metadata.Protocol = C.ProtocolTLS
metadata.Domain = clientHello.ServerName
metadata.ClientHello = clientHello
return nil
}
return err
if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err
}
}

View File

@@ -18,6 +18,7 @@ type (
STDConfig = tls.Config
STDConn = tls.Conn
ConnectionState = tls.ConnectionState
CurveID = tls.CurveID
)
func ParseTLSVersion(version string) (uint16, error) {

View File

@@ -10,6 +10,8 @@ import (
"net"
"os"
"strings"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/dns"
@@ -46,7 +48,10 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
return &STDClientConfig{tlsConfig}, nil
} else {
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil
return &STDECHClientConfig{
STDClientConfig: STDClientConfig{tlsConfig},
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
}, nil
}
}
@@ -99,11 +104,28 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
type STDECHClientConfig struct {
STDClientConfig
dnsRouter adapter.DNSRouter
access sync.Mutex
dnsRouter adapter.DNSRouter
lastTTL time.Duration
lastUpdate time.Time
}
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
if len(s.config.EncryptedClientHelloConfigList) == 0 {
tlsConn, err := s.fetchAndHandshake(ctx, conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
}
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
s.access.Lock()
defer s.access.Unlock()
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
@@ -123,6 +145,7 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
if response.Rcode != mDNS.RcodeSuccess {
return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
}
match:
for _, rr := range response.Answer {
switch resource := rr.(type) {
case *mDNS.HTTPS:
@@ -132,26 +155,23 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
if err != nil {
return nil, E.Cause(err, "decode ECH config")
}
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
s.lastUpdate = time.Now()
s.config.EncryptedClientHelloConfigList = echConfigList
break match
}
}
}
}
return nil, E.New("no ECH config found in DNS records")
if len(s.config.EncryptedClientHelloConfigList) == 0 {
return nil, E.New("no ECH config found in DNS records")
}
}
tlsConn, err := s.Client(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
return s.Client(conn)
}
func (s *STDECHClientConfig) Clone() Config {
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter}
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
}
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {

View 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."

View File

@@ -8,10 +8,7 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"time"
M "github.com/sagernet/sing/common/metadata"
)
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
@@ -38,30 +35,17 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
if err != nil {
return
}
var template *x509.Certificate
if serverAddress := M.ParseAddr(serverName); serverAddress.IsValid() {
template = &x509.Certificate{
SerialNumber: serialNumber,
IPAddresses: []net.IP{serverAddress.AsSlice()},
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: expire,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
} else {
template = &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: expire,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
Subject: pkix.Name{
CommonName: serverName,
},
DNSNames: []string{serverName},
}
template := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: timeFunc().Add(time.Hour * -1),
NotAfter: expire,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
Subject: pkix.Name{
CommonName: serverName,
},
DNSNames: []string{serverName},
}
if parent == nil {
parent = template

View File

@@ -33,8 +33,8 @@ import (
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
aTLS "github.com/sagernet/sing/common/tls"
utls "github.com/sagernet/utls"
utls "github.com/metacubex/utls"
"golang.org/x/crypto/hkdf"
"golang.org/x/net/http2"
)
@@ -148,9 +148,13 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
if err != nil {
return nil, err
}
ecdheKey := uConn.HandshakeState.State13.EcdheKey
keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys
if keyShareKeys == nil {
return nil, E.New("nil KeyShareKeys")
}
ecdheKey := keyShareKeys.Ecdhe
if ecdheKey == nil {
return nil, E.New("nil ecdhe_key")
return nil, E.New("nil ecdheKey")
}
authKey, err := ecdheKey.ECDH(publicKey)
if err != nil {
@@ -214,10 +218,6 @@ func realityClientFallback(ctx context.Context, uConn net.Conn, serverName strin
response.Body.Close()
}
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
e.uClient.config.SessionIDGenerator = generator
}
func (e *RealityClientConfig) Clone() Config {
return &RealityClientConfig{
e.ctx,

View File

@@ -1,4 +1,4 @@
//go:build with_reality_server
//go:build with_utls
package tls
@@ -10,25 +10,25 @@ import (
"net"
"time"
"github.com/sagernet/reality"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
utls "github.com/metacubex/utls"
)
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
type RealityServerConfig struct {
config *reality.Config
config *utls.RealityConfig
}
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
var tlsConfig reality.Config
var tlsConfig utls.RealityConfig
if options.ACME != nil && len(options.ACME.Domain) > 0 {
return nil, E.New("acme is unavailable in reality")
@@ -89,16 +89,20 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
tlsConfig.ShortIds = make(map[[8]byte]bool)
for i, shortIDString := range options.Reality.ShortID {
var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
if err != nil {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
if len(options.Reality.ShortID) == 0 {
tlsConfig.ShortIds[[8]byte{0}] = true
} else {
for i, shortIDString := range options.Reality.ShortID {
var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
if err != nil {
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
}
if decodedLen > 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
}
tlsConfig.ShortIds[shortID] = true
}
if decodedLen > 8 {
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
}
tlsConfig.ShortIds[shortID] = true
}
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
@@ -109,10 +113,6 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
}
if debug.Enabled {
tlsConfig.Show = true
}
return &RealityServerConfig{&tlsConfig}, nil
}
@@ -153,7 +153,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
}
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
tlsConn, err := reality.Server(ctx, conn, c.config)
tlsConn, err := utls.RealityServer(ctx, conn, c.config)
if err != nil {
return nil, err
}
@@ -169,7 +169,7 @@ func (c *RealityServerConfig) Clone() Config {
var _ Conn = (*realityConnWrapper)(nil)
type realityConnWrapper struct {
*reality.Conn
*utls.Conn
}
func (c *realityConnWrapper) ConnectionState() ConnectionState {

View File

@@ -1,15 +1,5 @@
//go:build !with_reality_server
//go:build with_reality_server
package tls
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
}
var _ int = "The separate `with_reality_server` build tag has been merged into `with_utls` and is no longer needed, please update your build configuration."

View File

@@ -6,6 +6,7 @@ import (
"net"
"os"
"strings"
"time"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -233,8 +234,12 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
key = content
}
if certificate == nil && key == nil && options.Insecure {
timeFunc := ntp.TimeFuncFromContext(ctx)
if timeFunc == nil {
timeFunc = time.Now
}
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
return GenerateKeyPair(nil, nil, timeFunc, info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -16,8 +16,8 @@ import (
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
utls "github.com/sagernet/utls"
utls "github.com/metacubex/utls"
"golang.org/x/net/http2"
)

View File

@@ -14,5 +14,9 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
}
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
}
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`)
}

View File

@@ -1,7 +0,0 @@
package constant
const (
ScriptTypeSurge = "surge"
ScriptSourceTypeLocal = "local"
ScriptSourceTypeRemote = "remote"
)

View File

@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
conntrack.MemoryLimit = uint64(options.MemoryLimit)
if options.MemoryLimit.Value() != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
conntrack.MemoryLimit = options.MemoryLimit.Value()
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller

View File

@@ -7,9 +7,9 @@ import (
"runtime/debug"
"strings"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/byteformats"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
@@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
runtime.ReadMemStats(&memStats)
var memObject badjson.JSONObject
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
memObject.Put("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())

View File

@@ -232,10 +232,20 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
record.Header().Ttl = timeToLive
}
}
response.Id = messageId
if !disableCache {
c.storeCache(transport, question, response, timeToLive)
}
response.Id = messageId
requestEDNSOpt := message.IsEdns0()
responseEDNSOpt := response.IsEdns0()
if responseEDNSOpt != nil && (requestEDNSOpt == nil || requestEDNSOpt.Version() < responseEDNSOpt.Version()) {
response.Extra = common.Filter(response.Extra, func(it dns.RR) bool {
return it.Header().Rrtype != dns.TypeOPT
})
if requestEDNSOpt != nil {
response.SetEdns0(responseEDNSOpt.UDPSize(), responseEDNSOpt.Do())
}
}
logExchangedResponse(c.logger, ctx, response, timeToLive)
return response, err
}
@@ -483,7 +493,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
}
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
if response.Rcode != dns.RcodeSuccess {
return nil, RcodeError(response.Rcode)
}
addresses := make([]netip.Addr, 0, len(response.Answer))

View File

@@ -15,6 +15,8 @@ func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf
}
responseLen := response.Len()
if responseLen > maxLen {
copyResponse := *response
response = &copyResponse
response.Truncate(maxLen)
}
buffer := buf.NewSize(headroom*2 + 1 + responseLen)

View File

@@ -323,6 +323,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
err error
)
printResult := func() {
if err == nil && len(responseAddrs) == 0 {
err = E.New("empty result")
}
if err != nil {
if errors.Is(err, ErrResponseRejectedCached) {
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
@@ -331,15 +334,15 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
} else {
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
}
} else if len(responseAddrs) == 0 {
r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
err = RcodeNameError
}
if err != nil {
err = E.Cause(err, "lookup ", domain)
}
}
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
if cached {
if len(responseAddrs) == 0 {
return nil, RcodeNameError
return nil, E.New("lookup ", domain, ": empty result (cached)")
}
return responseAddrs, nil
}

View File

@@ -96,6 +96,9 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
if serverAddr.Port == 0 {
serverAddr.Port = 443
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return NewHTTPSRaw(
dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions),
logger,

View File

@@ -3,6 +3,7 @@ package local
import (
"context"
"math/rand"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -35,6 +36,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
}
return &Transport{
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
ctx: ctx,
hosts: hosts.NewFile(hosts.DefaultPath),
dialer: transportDialer,
}, nil
@@ -57,7 +59,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
}
}
systemConfig := getSystemDNSConfig()
systemConfig := getSystemDNSConfig(t.ctx)
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
} else {
@@ -89,8 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
startRacer := func(ctx context.Context, fqdn string) {
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
if err == nil {
addresses, _ := dns.MessageToAddresses(response)
if len(addresses) == 0 {
var addresses []netip.Addr
addresses, err = dns.MessageToAddresses(response)
if err == nil && len(addresses) == 0 {
err = E.New(fqdn, ": empty result")
}
}

View File

@@ -1,6 +1,7 @@
package local
import (
"context"
"os"
"runtime"
"strings"
@@ -23,19 +24,21 @@ type resolverConfig struct {
var resolvConf resolverConfig
func getSystemDNSConfig() *dnsConfig {
resolvConf.tryUpdate("/etc/resolv.conf")
func getSystemDNSConfig(ctx context.Context) *dnsConfig {
resolvConf.tryUpdate(ctx, "/etc/resolv.conf")
return resolvConf.dnsConfig.Load()
}
func (conf *resolverConfig) init() {
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf"))
func (conf *resolverConfig) init(ctx context.Context) {
conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf"))
conf.lastChecked = time.Now()
conf.ch = make(chan struct{}, 1)
}
func (conf *resolverConfig) tryUpdate(name string) {
conf.initOnce.Do(conf.init)
func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {
conf.initOnce.Do(func() {
conf.init(ctx)
})
if conf.dnsConfig.Load().noReload {
return
@@ -59,7 +62,7 @@ func (conf *resolverConfig) tryUpdate(name string) {
return
}
}
dnsConf := dnsReadConfig(name)
dnsConf := dnsReadConfig(ctx, name)
conf.dnsConfig.Store(dnsConf)
}

View File

@@ -11,6 +11,7 @@ package local
import "C"
import (
"context"
"time"
E "github.com/sagernet/sing/common/exceptions"
@@ -18,7 +19,7 @@ import (
"github.com/miekg/dns"
)
func dnsReadConfig(_ string) *dnsConfig {
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
if C.res_init() != 0 {
return &dnsConfig{
servers: defaultNS,

View File

@@ -4,6 +4,7 @@ package local
import (
"bufio"
"context"
"net"
"net/netip"
"os"
@@ -13,7 +14,7 @@ import (
"github.com/miekg/dns"
)
func dnsReadConfig(name string) *dnsConfig {
func dnsReadConfig(_ context.Context, name string) *dnsConfig {
conf := &dnsConfig{
ndots: 1,
timeout: 5 * time.Second,

View File

@@ -1,6 +1,7 @@
package local
import (
"context"
"net"
"net/netip"
"os"
@@ -8,10 +9,13 @@ import (
"time"
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/service"
"golang.org/x/sys/windows"
)
func dnsReadConfig(_ string) *dnsConfig {
func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
conf := &dnsConfig{
ndots: 1,
timeout: 5 * time.Second,
@@ -22,35 +26,35 @@ func dnsReadConfig(_ string) *dnsConfig {
conf.servers = defaultNS
}
}()
aas, err := adapterAddresses()
addresses, err := adapterAddresses()
if err != nil {
return nil
}
for _, aa := range aas {
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
if aa.OperStatus != windows.IfOperStatusUp {
var dnsAddresses []struct {
ifName string
netip.Addr
}
for _, address := range addresses {
if address.OperStatus != windows.IfOperStatusUp {
continue
}
// Only take interfaces which have at least one gateway
if aa.FirstGatewayAddress == nil {
if address.IfType == windows.IF_TYPE_TUNNEL {
continue
}
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
sa, err := dns.Address.Sockaddr.Sockaddr()
if address.FirstGatewayAddress == nil {
continue
}
for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {
rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()
if err != nil {
continue
}
var ip netip.Addr
switch sa := sa.(type) {
var dnsServerAddr netip.Addr
switch sockaddr := rawSockaddr.(type) {
case *syscall.SockaddrInet4:
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]})
dnsServerAddr = netip.AddrFrom4(sockaddr.Addr)
case *syscall.SockaddrInet6:
var addr16 [16]byte
copy(addr16[:], sa.Addr[:])
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {
// fec0/10 IPv6 addresses are site local anycast DNS
// addresses Microsoft sets by default if no other
// IPv6 DNS address is set. Site local anycast is
@@ -58,14 +62,27 @@ func dnsReadConfig(_ string) *dnsConfig {
// https://datatracker.ietf.org/doc/html/rfc3879
continue
}
ip = netip.AddrFrom16(addr16)
dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
default:
// Unexpected type.
continue
}
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53"))
dnsAddresses = append(dnsAddresses, struct {
ifName string
netip.Addr
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
}
}
var myInterface string
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
myInterface = networkManager.InterfaceMonitor().MyInterface()
}
for _, address := range dnsAddresses {
if address.ifName == myInterface {
continue
}
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
}
return conf
}

View File

@@ -92,6 +92,9 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
if serverAddr.Port == 0 {
serverAddr.Port = 443
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &HTTP3Transport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions),
logger: logger,

View File

@@ -16,6 +16,7 @@ import (
sQUIC "github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -58,6 +59,9 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
if serverAddr.Port == 0 {
serverAddr.Port = 853
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &Transport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions),
ctx: ctx,
@@ -140,12 +144,12 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.C
if err != nil {
return nil, err
}
defer stream.Close()
defer stream.CancelRead(0)
err = transport.WriteMessage(stream, 0, message)
if err != nil {
stream.Close()
return nil, err
}
stream.Close()
return transport.ReadMessage(stream)
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -40,6 +41,9 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
if serverAddr.Port == 0 {
serverAddr.Port = 53
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &TCPTransport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options),
dialer: transportDialer,

View File

@@ -57,6 +57,9 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
if serverAddr.Port == 0 {
serverAddr.Port = 853
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return &TLSTransport{
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
logger: logger,

View File

@@ -13,6 +13,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -47,6 +48,9 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
if serverAddr.Port == 0 {
serverAddr.Port = 53
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil
}
@@ -117,7 +121,7 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
conn.access.Unlock()
defer func() {
conn.access.Lock()
delete(conn.callbacks, messageId)
delete(conn.callbacks, exMessage.Id)
conn.access.Unlock()
}()
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
@@ -212,8 +216,8 @@ type dnsConnection struct {
func (c *dnsConnection) Close(err error) {
c.closeOnce.Do(func() {
close(c.done)
c.err = err
close(c.done)
})
c.Conn.Close()
}

View File

@@ -2,6 +2,58 @@
icon: material/alert-decagram
---
#### 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

View File

@@ -9,6 +9,10 @@ and the data generated by the software is always on your device.
## Android
The broad package (App) visibility (QUERY_ALL_PACKAGES) permission
is used to provide per-application proxy features for VPN,
sing-box will not collect your app list.
If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules,
sing-box uses the location permission in the background
to get information about the connected Wi-Fi network to make them work.

View File

@@ -27,19 +27,20 @@ icon: material/alert-decagram
The type of the DNS server.
| Type | Format |
|-----------------|-----------------------------|
| empty (default) | [Legacy](./legacy/) |
| `tcp` | [TCP](./tcp/) |
| `udp` | [UDP](./udp/) |
| `tls` | [TLS](./tls/) |
| `https` | [HTTPS](./https/) |
| `quic` | [QUIC](./quic/) |
| `h3` | [HTTP/3](./http3/) |
| `predefined` | [Predefined](./predefined/) |
| `dhcp` | [DHCP](./dhcp/) |
| `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
| Type | Format |
|-----------------|---------------------------|
| empty (default) | [Legacy](./legacy/) |
| `local` | [Local](./local/) |
| `hosts` | [Hosts](./hosts/) |
| `tcp` | [TCP](./tcp/) |
| `udp` | [UDP](./udp/) |
| `tls` | [TLS](./tls/) |
| `quic` | [QUIC](./quic/) |
| `https` | [HTTPS](./https/) |
| `h3` | [HTTP/3](./http3/) |
| `dhcp` | [DHCP](./dhcp/) |
| `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
#### tag

View File

@@ -27,19 +27,20 @@ icon: material/alert-decagram
DNS 服务器的类型。
| 类型 | 格式 |
|-----------------|-----------------------------|
| empty (default) | [Legacy](./legacy/) |
| `tcp` | [TCP](./tcp/) |
| `udp` | [UDP](./udp/) |
| `tls` | [TLS](./tls/) |
| `https` | [HTTPS](./https/) |
| `quic` | [QUIC](./quic/) |
| `h3` | [HTTP/3](./http3/) |
| `predefined` | [Predefined](./predefined/) |
| `dhcp` | [DHCP](./dhcp/) |
| `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
| 类型 | 格式 |
|-----------------|---------------------------|
| empty (default) | [Legacy](./legacy/) |
| `local` | [Local](./local/) |
| `hosts` | [Hosts](./hosts/) |
| `tcp` | [TCP](./tcp/) |
| `udp` | [UDP](./udp/) |
| `tls` | [TLS](./tls/) |
| `quic` | [QUIC](./quic/) |
| `https` | [HTTPS](./https/) |
| `h3` | [HTTP/3](./http3/) |
| `dhcp` | [DHCP](./dhcp/) |
| `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
#### tag

View File

@@ -42,16 +42,18 @@ AnyTLS padding scheme line array.
Default padding scheme:
```
stop=8
0=34-120
1=100-400
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
3=500-1000
4=500-1000
5=500-1000
6=500-1000
7=500-1000
```json
[
"stop=8",
"0=30-30",
"1=100-400",
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
"3=9-9,500-1000",
"4=500-1000",
"5=500-1000",
"6=500-1000",
"7=500-1000"
]
```
#### tls

View File

@@ -42,16 +42,18 @@ AnyTLS 填充方案行数组。
默认填充方案:
```
stop=8
0=34-120
1=100-400
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
3=500-1000
4=500-1000
5=500-1000
6=500-1000
7=500-1000
```json
[
"stop=8",
"0=30-30",
"1=100-400",
"2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000",
"3=9-9,500-1000",
"4=500-1000",
"5=500-1000",
"6=500-1000",
"7=500-1000"
]
```
#### tls

View File

@@ -211,6 +211,10 @@ Set the default route to the Tun.
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
!!! note "Also enable `auto_redirect`"
`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks.
#### iproute2_table_index
!!! question "Since sing-box 1.10.0"
@@ -235,22 +239,29 @@ Linux iproute2 rule start index generated by `auto_route`.
Only supported on Linux with `auto_route` enabled.
Automatically configure iptables/nftables to redirect connections.
Improve TUN routing and performance using nftables.
*In Android*
`auto_redirect` is always recommended on Linux, it provides better routing,
higher performance (better than tproxy),
and avoids conflicts between TUN and Docker bridge networks.
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
Note that `auto_redirect` also works on Android,
but due to the lack of `nftables` and `ip6tables`,
only simple IPv4 TCP forwarding is performed.
To share your VPN connection over hotspot or repeater on Android,
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
*In Linux*:
`auto_redirect` also automatically inserts compatibility rules
into the OpenWrt fw4 table, i.e.
it will work on routers without any extra configuration.
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
#### auto_redirect_input_mark
!!! question "Since sing-box 1.10.0"
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
Connection input mark used by `auto_redirect`.
`0x2023` is used by default.
@@ -258,7 +269,7 @@ Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`
!!! question "Since sing-box 1.10.0"
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
Connection output mark used by `auto_redirect`.
`0x2024` is used by default.
@@ -269,17 +280,15 @@ Enforce strict routing rules when `auto_route` is enabled:
*In Linux*:
* Let unsupported network unreachable
* Make ICMP traffic route to tun instead of upstream interfaces
* Route all connections to tun
It prevents IP address leaks and makes DNS hijacking work on Android.
* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
*In Windows*:
* Add firewall rules to prevent DNS leak caused by
* Let unsupported network unreachable
* prevent DNS leak caused by
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations.
#### route_address
@@ -367,8 +376,6 @@ Exclude custom routes when `auto_route` is enabled.
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
Matched traffic will bypass the sing-box routes.
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
=== "Without `auto_redirect` enabled"

View File

@@ -215,6 +215,10 @@ tun 接口的 IPv6 前缀。
VPN 默认优先于 tun。要使 tun 经过 VPN启用 `route.override_android_vpn`
!!! note "也启用 `auto_redirect`"
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy 并避免 TUN 与 Docker 桥接网络冲突。
#### iproute2_table_index
!!! question "自 sing-box 1.10.0 起"
@@ -237,23 +241,24 @@ tun 接口的 IPv6 前缀。
!!! quote ""
仅支持 Linux且需要 `auto_route` 已启用。
仅支持 Linux且需要 `auto_route` 已启用。
自动配置 iptables/nftables 以重定向连接
通过使用 nftables 改善 TUN 路由和性能
*在 Android 中*
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy并避免了 TUN 和 Docker 桥接网络之间的冲突。
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
请注意,`auto_redirect` 也适用于 Android但由于缺少 `nftables``ip6tables`,仅执行简单的 IPv4 TCP 转发。
若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
*在 Linux 中*:
`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。
带有 `auto_redirect ``auto_route` 可以在路由器上按预期工作,**无需干预**
`route.default_mark``[dialOptions].routing_mark` 冲突
#### auto_redirect_input_mark
!!! question "自 sing-box 1.10.0 起"
`route_address_set``route_exclude_address_set` 使用的连接输入标记。
`auto_redirect` 使用的连接输入标记。
默认使用 `0x2023`
@@ -261,29 +266,25 @@ tun 接口的 IPv6 前缀。
!!! question "自 sing-box 1.10.0 起"
`route_address_set``route_exclude_address_set` 使用的连接输出标记。
`auto_redirect` 使用的连接输出标记。
默认使用 `0x2024`
#### strict_route
启用 `auto_route` 时执行严格的路由规则
启用 `auto_route`,强制执行严格的路由规则
*在 Linux 中*:
*在 Linux 中*
* 不支持的网络无法到达
* 使 ICMP 流量路由到 tun 而不是上游接口
* 将所有连接路由到 tun
* 使不支持的网络不可达。
* 出于历史遗留原因,当未启用 `strict_route``auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
*在 Windows 中*
*在 Windows 中*:
* 使不支持的网络不可达。
* 阻止 Windows 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) 造成的 DNS 泄露
* 添加防火墙规则以阻止 Windows
的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
造成的 DNS 泄露
它可能会使某些应用程序(如 VirtualBox在某些情况下无法正常工作。
它可能会使某些 Windows 应用程序(如 VirtualBox在某些情况下无法正常工作。
#### route_address
@@ -341,8 +342,6 @@ tun 接口的 IPv6 前缀。
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
不匹配的流量将绕过 sing-box 路由。
`route.default_mark``[dialOptions].routing_mark` 冲突。
=== "`auto_redirect` 未启用"

View File

@@ -26,7 +26,7 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
| QUIC Client | Type |
|:------------------------:|:----------:|
| Chromium/Cronet | `chrimium` |
| Chromium/Cronet | `chromium` |
| Safari/Apple Network API | `safari` |
| Firefox / uquic firefox | `firefox` |
| quic-go / uquic chrome | `quic-go` |
| quic-go / uquic chrome | `quic-go` |

View File

@@ -26,7 +26,7 @@
| QUIC 客户端 | 类型 |
|:------------------------:|:----------:|
| Chromium/Cronet | `chrimium` |
| Chromium/Cronet | `chromium` |
| Safari/Apple Network API | `safari` |
| Firefox / uquic firefox | `firefox` |
| quic-go / uquic chrome | `quic-go` |
| quic-go / uquic chrome | `quic-go` |

View File

@@ -80,6 +80,8 @@ List of [Headless Rule](./headless-rule/).
Format of rule-set file, `source` or `binary`.
Optional when `path` or `url` uses `json` or `srs` as extension.
### Local Fields
#### path

View File

@@ -80,6 +80,8 @@
规则集格式, `source` 或 `binary`。
当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。
### 本地字段
#### path

View File

@@ -206,7 +206,7 @@ Only take effect when `domain_strategy` or `network_strategy` is set.
!!! failure "Deprecated in sing-box 1.12.0"
`domain_strategy` is merged to [domain_resolver](#domain_resolver) in sing-box 1.12.0.
`domain_strategy` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver).
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.

View File

@@ -194,6 +194,10 @@ icon: material/new-box
#### domain_strategy
!!! failure "已在 sing-box 1.12.0 废弃"
`domain_strategy` 已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver)。
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
如果设置,域名将在请求发出之前解析为 IP。

View File

@@ -52,7 +52,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). |
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). |
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
| `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |

View File

@@ -56,7 +56,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/). |
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/). |
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
| `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |

View File

@@ -9,43 +9,56 @@ icon: material/package
=== ":material-debian: Debian / APT"
```bash
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
sudo apt-get update
sudo apt-get install sing-box # or sing-box-beta
sudo mkdir -p /etc/apt/keyrings &&
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
echo '
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
sudo apt-get update &&
sudo apt-get install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF"
=== ":material-redhat: Redhat / DNF 5"
```bash
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
sudo dnf install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 4"
```bash
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
sudo dnf -y install dnf-plugins-core &&
sudo dnf install sing-box # or sing-box-beta
```
(This applies to any distribution that uses `dnf` as the package manager: Fedora, CentOS, even OpenSUSE with DNF installed.)
## :material-download-box: Manual Installation
=== ":material-debian: Debian / DEB"
The script download and install the latest package from GitHub releases
for deb or rpm based Linux distributions, ArchLinux and OpenWrt.
```bash
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh
```
=== ":material-redhat: Redhat / RPM"
or latest beta:
```bash
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
```
(This applies to any distribution that uses `rpm` and `systemd`. Because of how `rpm` defines dependencies, if it installs, it probably works.)
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
```
=== ":simple-archlinux: Archlinux / PKG"
or specific version:
```bash
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
```
## :material-book-lock-open: Managed Installation

View File

@@ -9,43 +9,55 @@ icon: material/package
=== ":material-debian: Debian / APT"
```bash
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
sudo apt-get update
sudo apt-get install sing-box # or sing-box-beta
sudo mkdir -p /etc/apt/keyrings &&
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
echo '
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
sudo apt-get update &&
sudo apt-get install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF"
=== ":material-redhat: Redhat / DNF 5"
```bash
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
sudo dnf install sing-box # or sing-box-beta
```
=== ":material-redhat: Redhat / DNF 4"
```bash
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
sudo dnf -y install dnf-plugins-core &&
sudo dnf install sing-box # or sing-box-beta
```
(这适用于任何使用 `dnf` 作为包管理器的发行版Fedora、CentOS甚至安装了 DNF 的 OpenSUSE。
## :material-download-box: 手动安装
=== ":material-debian: Debian / DEB"
该脚本从 GitHub 发布中下载并安装最新的软件包,适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。
```bash
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh
```
=== ":material-redhat: Redhat / RPM"
或最新测试版:
```bash
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
```
(这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。)
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
```
=== ":simple-archlinux: Archlinux / PKG"
或指定版本:
```bash
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
```
```shell
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
```
## :material-book-lock-open: 托管安装

View File

@@ -0,0 +1,116 @@
#!/bin/sh
download_beta=false
download_version=""
while [ $# -gt 0 ]; do
case "$1" in
--beta)
download_beta=true
shift
;;
--version)
shift
if [ $# -eq 0 ]; then
echo "Missing argument for --version"
echo "Usage: $0 [--beta] [--version <version>]"
exit 1
fi
download_version="$1"
shift
;;
*)
echo "Unknown argument: $1"
echo "Usage: $0 [--beta] [--version <version>]"
exit 1
;;
esac
done
if command -v pacman >/dev/null 2>&1; then
os="linux"
arch=$(uname -m)
package_suffix=".pkg.tar.zst"
package_install="pacman -U --noconfirm"
elif command -v dpkg >/dev/null 2>&1; then
os="linux"
arch=$(dpkg --print-architecture)
package_suffix=".deb"
package_install="dpkg -i"
elif command -v dnf >/dev/null 2>&1; then
os="linux"
arch=$(uname -m)
package_suffix=".rpm"
package_install="dnf install -y"
elif command -v rpm >/dev/null 2>&1; then
os="linux"
arch=$(uname -m)
package_suffix=".rpm"
package_install="rpm -i"
elif command -v opkg >/dev/null 2>&1; then
os="openwrt"
. /etc/os-release
arch="$OPENWRT_ARCH"
package_suffix=".ipk"
package_install="opkg update && opkg install"
else
echo "Missing supported package manager."
exit 1
fi
if [ -z "$download_version" ]; then
if [ "$download_beta" != "true" ]; then
if [ -n "$GITHUB_TOKEN" ]; then
latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest)
else
latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest)
fi
curl_exit_status=$?
if [ $curl_exit_status -ne 0 ]; then
exit $curl_exit_status
fi
if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then
echo "$latest_release"
exit 1
fi
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g')
else
if [ -n "$GITHUB_TOKEN" ]; then
latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases)
else
latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases)
fi
curl_exit_status=$?
if [ $curl_exit_status -ne 0 ]; then
exit $curl_exit_status
fi
if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then
echo "$latest_release"
exit 1
fi
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g')
fi
fi
package_name="sing-box_${download_version}_${os}_${arch}${package_suffix}"
package_url="https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}"
echo "Downloading $package_url"
if [ -n "$GITHUB_TOKEN" ]; then
curl --fail -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url"
else
curl --fail -Lo "$package_name" "$package_url"
fi
curl_exit_status=$?
if [ $curl_exit_status -ne 0 ]; then
exit $curl_exit_status
fi
if command -v sudo >/dev/null 2>&1; then
package_install="sudo $package_install"
fi
echo "$package_install $package_name"
sh -c "$package_install \"$package_name\""
rm -f "$package_name"

View File

@@ -292,7 +292,7 @@ DNS servers are refactored for better performance and scalability.
}
],
"fakeip": {
"enable": true,
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
@@ -516,13 +516,13 @@ DNS servers are refactored for better performance and scalability.
The legacy outbound DNS rules are deprecated and can be replaced by new domain resolver options.
!!! info "References"
[DNS rule](/configuration/dns/rule/#outbound) /
[Dial Fields](/configuration/shared/dial/#domain_resolver) /
[Route](/configuration/route/#domain_resolver)
=== ":material-card-remove: Deprecated"
```json
{
"dns": {
@@ -556,7 +556,8 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
"dns": {
"servers": [
{
"type": "local"
"type": "local",
"tag": "local"
}
]
},
@@ -586,6 +587,58 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
}
```
### Migrate outbound domain strategy option to domain resolver
!!! info "References"
[Dial Fields](/configuration/shared/dial/#domain_strategy)
The `domain_strategy` option in Dial Fields has been deprecated and can be replaced with the new domain resolver option.
Note that due to the use of Dial Fields by some of the new DNS servers introduced in sing-box 1.12,
some people mistakenly believe that `domain_strategy` is the same feature as in the legacy DNS servers.
=== ":material-card-remove: Deprecated"
```json
{
"outbounds": [
{
"type": "socks",
"server": "example.org",
"server_port": 2080,
"domain_strategy": "prefer_ipv4",
}
]
}
```
=== ":material-card-multiple: New"
```json
{
"dns": {
"servers": [
{
"type": "local",
"tag": "local"
}
]
},
"outbounds": [
{
"type": "socks",
"server": "example.org",
"server_port": 2080,
"domain_resolver": {
"server": "local",
"strategy": "prefer_ipv4"
}
}
]
}
```
## 1.11.0
### Migrate legacy special outbounds to rule actions

View File

@@ -16,7 +16,7 @@ DNS 服务器已经重构。
=== "Local"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -28,9 +28,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -46,7 +46,7 @@ DNS 服务器已经重构。
=== "TCP"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -58,9 +58,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -77,7 +77,7 @@ DNS 服务器已经重构。
=== "UDP"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -89,9 +89,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -108,7 +108,7 @@ DNS 服务器已经重构。
=== "TLS"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -120,9 +120,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -139,7 +139,7 @@ DNS 服务器已经重构。
=== "HTTPS"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -151,9 +151,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -170,7 +170,7 @@ DNS 服务器已经重构。
=== "QUIC"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -182,9 +182,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -201,7 +201,7 @@ DNS 服务器已经重构。
=== "HTTP3"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -213,9 +213,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -232,7 +232,7 @@ DNS 服务器已经重构。
=== "DHCP"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -247,9 +247,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -269,7 +269,7 @@ DNS 服务器已经重构。
=== "FakeIP"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -292,16 +292,16 @@ DNS 服务器已经重构。
}
],
"fakeip": {
"enable": true,
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -333,7 +333,7 @@ DNS 服务器已经重构。
=== "RCode"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -345,9 +345,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -368,7 +368,7 @@ DNS 服务器已经重构。
=== "带有域名地址的服务器"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -385,9 +385,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -410,7 +410,7 @@ DNS 服务器已经重构。
=== "带有域策略的服务器"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -434,9 +434,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -466,7 +466,7 @@ DNS 服务器已经重构。
=== "带有客户端子网的服务器"
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
@@ -483,9 +483,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
@@ -556,7 +556,8 @@ DNS 服务器已经重构。
"dns": {
"servers": [
{
"type": "local"
"type": "local",
"tag": "local"
}
]
},
@@ -586,6 +587,57 @@ DNS 服务器已经重构。
}
```
### 迁移出站域名策略选项到域名解析器
拨号字段中的 `domain_strategy` 选项已被弃用,可以用新的域名解析器选项替代。
请注意,由于 sing-box 1.12 中引入的一些新 DNS 服务器使用了拨号字段,一些人错误地认为 `domain_strategy` 与旧 DNS 服务器中的功能相同。
!!! info "参考"
[拨号字段](/configuration/shared/dial/#domain_strategy)
=== ":material-card-remove: 弃用的"
```json
{
"outbounds": [
{
"type": "socks",
"server": "example.org",
"server_port": 2080,
"domain_strategy": "prefer_ipv4",
}
]
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
"servers": [
{
"type": "local",
"tag": "local"
}
]
},
"outbounds": [
{
"type": "socks",
"server": "example.org",
"server_port": 2080,
"domain_resolver": {
"server": "local",
"strategy": "prefer_ipv4"
}
}
]
}
```
## 1.11.0
### 迁移旧的特殊出站到规则动作
@@ -601,7 +653,7 @@ DNS 服务器已经重构。
=== "Block"
=== ":material-card-remove: 弃用的"
```json
{
"outbounds": [
@@ -614,7 +666,7 @@ DNS 服务器已经重构。
"rules": [
{
...,
"outbound": "block"
}
]
@@ -623,14 +675,14 @@ DNS 服务器已经重构。
```
=== ":material-card-multiple: 新的"
```json
{
"route": {
"rules": [
{
...,
"action": "reject"
}
]
@@ -641,13 +693,13 @@ DNS 服务器已经重构。
=== "DNS"
=== ":material-card-remove: 弃用的"
```json
{
"inbound": [
{
...,
"sniff": true
}
],
@@ -667,9 +719,9 @@ DNS 服务器已经重构。
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"route": {
@@ -1133,4 +1185,4 @@ sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\fold
}
}
}
```
```

View File

@@ -19,12 +19,10 @@ import (
)
var (
bucketSelected = []byte("selected")
bucketExpand = []byte("group_expand")
bucketMode = []byte("clash_mode")
bucketRuleSet = []byte("rule_set")
bucketScript = []byte("script")
bucketSgPersistentStore = []byte("sg_persistent_store")
bucketSelected = []byte("selected")
bucketExpand = []byte("group_expand")
bucketMode = []byte("clash_mode")
bucketRuleSet = []byte("rule_set")
bucketNameList = []string{
string(bucketSelected),
@@ -318,70 +316,3 @@ func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {
return bucket.Put([]byte(tag), setBinary)
})
}
func (c *CacheFile) LoadScript(tag string) *adapter.SavedBinary {
var savedSet adapter.SavedBinary
err := c.DB.View(func(t *bbolt.Tx) error {
bucket := c.bucket(t, bucketScript)
if bucket == nil {
return os.ErrNotExist
}
scriptBinary := bucket.Get([]byte(tag))
if len(scriptBinary) == 0 {
return os.ErrInvalid
}
return savedSet.UnmarshalBinary(scriptBinary)
})
if err != nil {
return nil
}
return &savedSet
}
func (c *CacheFile) SaveScript(tag string, set *adapter.SavedBinary) error {
return c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := c.createBucket(t, bucketScript)
if err != nil {
return err
}
scriptBinary, err := set.MarshalBinary()
if err != nil {
return err
}
return bucket.Put([]byte(tag), scriptBinary)
})
}
func (c *CacheFile) SurgePersistentStoreRead(key string) string {
var value string
_ = c.DB.View(func(t *bbolt.Tx) error {
bucket := c.bucket(t, bucketSgPersistentStore)
if bucket == nil {
return nil
}
valueBinary := bucket.Get([]byte(key))
if len(valueBinary) > 0 {
value = string(valueBinary)
}
return nil
})
return value
}
func (c *CacheFile) SurgePersistentStoreWrite(key string, value string) error {
return c.DB.Batch(func(t *bbolt.Tx) error {
if value != "" {
bucket, err := c.createBucket(t, bucketSgPersistentStore)
if err != nil {
return err
}
return bucket.Put([]byte(key), []byte(value))
} else {
bucket := c.bucket(t, bucketSgPersistentStore)
if bucket == nil {
return nil
}
return bucket.Delete([]byte(key))
}
})
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"net"
"net/http"
"runtime/debug"
"time"
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
@@ -12,14 +13,23 @@ import (
"github.com/sagernet/ws/wsutil"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
)
// API created by Clash.Meta
func (s *Server) setupMetaAPI(r chi.Router) {
if s.logDebug {
r := chi.NewRouter()
r.Put("/gc", func(w http.ResponseWriter, r *http.Request) {
debug.FreeOSMemory()
})
r.Mount("/", middleware.Profiler())
}
r.Get("/memory", memory(s.trafficManager))
r.Mount("/group", groupRouter(s))
r.Mount("/upgrade", upgradeRouter(s))
}
type Memory struct {

Some files were not shown because too many files have changed in this diff Show More