Compare commits

..

66 Commits

Author SHA1 Message Date
世界
9240f6e885 documentation: Bump version 2025-04-08 16:32:16 +08:00
世界
36ba3e5782 release: Skip override version for iOS 2025-04-08 16:15:25 +08:00
iikira
24e8a19b62 Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-04-08 16:03:14 +08:00
ReleTor
2e2455edf0 Fix fetch ECH configs 2025-04-08 16:03:13 +08:00
世界
7442172833 release: Update Go to 1.24.2 2025-04-08 16:03:13 +08:00
世界
803f7d04c9 Allow direct outbounds without domain_resolver 2025-04-08 16:03:13 +08:00
世界
5827ba536b Fix Tailscale dialer 2025-04-08 16:03:12 +08:00
dyhkwong
588e723124 Fix DNS over QUIC stream close 2025-04-08 16:03:12 +08:00
anytls
161668ffdc Update anytls
Co-authored-by: anytls <anytls>
2025-04-08 16:03:12 +08:00
Rambling2076
32a14f5fb2 Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-04-08 16:03:11 +08:00
世界
e61bdf27a1 Fail when default DNS server not found 2025-04-08 16:03:11 +08:00
世界
34e0ab27fb Update gVisor to 20250319.0 2025-04-08 16:03:10 +08:00
世界
09120a251c release: Do not build tailscale on iOS and tvOS 2025-04-08 16:03:10 +08:00
世界
104079ad63 Explicitly reject detour to empty direct outbounds 2025-04-08 16:03:09 +08:00
世界
55818312b4 Add netns support 2025-04-08 16:03:09 +08:00
世界
18e4ba8681 Add wildcard name support for predefined records 2025-04-08 16:03:09 +08:00
世界
513c1f1139 Remove map usage in options 2025-04-08 16:03:09 +08:00
世界
d09b424d04 Fix unhandled DNS loop 2025-04-08 16:03:08 +08:00
世界
b9558f04c9 Add wildcard-sni support for shadow-tls inbound 2025-04-08 16:03:08 +08:00
世界
40232d5505 Fix Tailscale DNS 2025-04-08 16:02:55 +08:00
k9982874
c272a15085 Add ntp protocol sniffing 2025-04-08 16:02:55 +08:00
世界
e896367a7e option: Fix marshal legacy DNS options 2025-04-08 16:02:54 +08:00
世界
f3e17520e9 Make domain_resolver optional when only one DNS server is configured 2025-04-08 16:02:54 +08:00
世界
c26fd66bbd Fix DNS lookup context pollution 2025-04-08 16:02:54 +08:00
世界
29210d2573 Fix http3 DNS server connecting to wrong address 2025-04-08 16:02:54 +08:00
Restia-Ashbell
6ea7bc3811 documentation: Fix typo 2025-04-08 16:02:53 +08:00
anytls
225d5316e6 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-04-08 16:02:53 +08:00
k9982874
8418d7b4f1 Fix hosts DNS server 2025-04-08 16:02:53 +08:00
世界
51442076f2 Fix UDP DNS server crash 2025-04-08 16:02:53 +08:00
世界
6743864d1c documentation: Fix missing ip_accept_any DNS rule option 2025-04-08 16:02:52 +08:00
世界
65a784816e Fix anytls dialer usage 2025-04-08 16:02:52 +08:00
世界
03e7dd7f91 Move predefined DNS server to rule action 2025-04-08 16:02:52 +08:00
世界
bb6fc6e74f Fix domain resolver on direct outbound 2025-04-08 16:02:51 +08:00
Zephyruso
5c22180675 Fix missing AnyTLS display name 2025-04-08 16:02:51 +08:00
anytls
04c7899255 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-04-08 16:02:51 +08:00
Estel
ad1e5035ee documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-04-08 16:02:50 +08:00
TargetLocked
cc1c616029 Fix parsing legacy DNS options 2025-04-08 16:02:50 +08:00
世界
8d287cccae Fix DNS fallback 2025-04-08 16:02:50 +08:00
世界
8da74a709a documentation: Fix missing hosts DNS server 2025-04-08 16:02:49 +08:00
anytls
6672b999e6 Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-04-08 16:02:49 +08:00
ReleTor
7a3c3f2f6d documentation: Minor fixes 2025-04-08 16:02:49 +08:00
libtry486
4193575a98 documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-04-08 16:02:49 +08:00
Alireza Ahmadi
74d66ed907 Fix Outbound deadlock 2025-04-08 16:02:48 +08:00
世界
0648abc603 documentation: Fix AnyTLS doc 2025-04-08 16:02:48 +08:00
anytls
0dbd10aaef Add AnyTLS protocol 2025-04-08 16:02:47 +08:00
世界
ed141b15f6 Migrate to stdlib ECH support 2025-04-08 16:02:47 +08:00
世界
9207dfa0a3 Add fallback local DNS server for iOS 2025-04-08 16:02:46 +08:00
世界
a11564dbe8 Get darwin local DNS server from libresolv 2025-04-08 16:02:46 +08:00
世界
20ca0395b4 Improve resolve action 2025-04-08 16:02:46 +08:00
世界
9eecdc02c8 Fix toolchain version 2025-04-08 16:02:45 +08:00
世界
ea127216cd Add back port hopping to hysteria 1 2025-04-08 16:02:45 +08:00
世界
1955c683b0 Update dependencies 2025-04-08 16:02:44 +08:00
xchacha20-poly1305
930acaa6a8 Remove single quotes of raw Moziila certs 2025-04-08 16:02:44 +08:00
世界
8572e20794 Add Tailscale endpoint 2025-04-08 16:02:44 +08:00
世界
fee4f16e14 Build legacy binaries with latest Go 2025-04-08 16:02:43 +08:00
世界
2d440f0874 documentation: Remove outdated icons 2025-04-08 16:02:43 +08:00
世界
dfb34ee116 documentation: Certificate store 2025-04-08 16:02:42 +08:00
世界
e9c81c9df3 documentation: TLS fragment 2025-04-08 16:02:42 +08:00
世界
b1ba455a57 documentation: Outbound domain resolver 2025-04-08 16:02:42 +08:00
世界
0c1bb6366a documentation: Refactor DNS 2025-04-08 16:02:41 +08:00
世界
2a6eff6c55 Add certificate store 2025-04-08 16:02:41 +08:00
世界
09769de838 Add TLS fragment support 2025-04-08 16:02:41 +08:00
世界
5336f09ddc refactor: Outbound domain resolver 2025-04-08 16:02:40 +08:00
世界
84e579823e refactor: DNS 2025-04-08 16:02:40 +08:00
世界
78608d0c62 test: Fix auto redirect UDP rules 2025-04-08 16:02:15 +08:00
世界
680f374a25 test: Improve auto redirect 2025-04-08 15:00:44 +08:00
224 changed files with 1903 additions and 7435 deletions

View File

@@ -1,22 +1,17 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-or-later
--license GPLv3-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
--no-deb-generate-changes
--config-files /etc/sing-box/config.json
--after-install release/config/sing-box.postinst
release/config/config.json=/etc/sing-box/config.json
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish

View File

@@ -1,30 +0,0 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--no-deb-generate-changes
--config-files /etc/config/sing-box
--config-files /etc/sing-box/config.json
--depends ca-bundle
--depends kmod-inet-diag
--depends kmod-tun
--depends firewall4
--before-remove release/config/openwrt.prerm
release/config/config.json=/etc/sing-box/config.json
release/config/openwrt.conf=/etc/config/sing-box
release/config/openwrt.init=/etc/init.d/sing-box
release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

28
.github/deb2ipk.sh vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
set -e -o pipefail
PROJECT=$(dirname "$0")/../..
TMP_PATH=`mktemp -d`
cp $2 $TMP_PATH
pushd $TMP_PATH
DEB_NAME=`ls *.deb`
ar x $DEB_NAME
mkdir control
pushd control
tar xf ../control.tar.gz
rm md5sums
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
cat control
tar czf ../control.tar.gz ./*
popd
DEB_NAME=${DEB_NAME%.deb}
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
popd
cp $TMP_PATH/$DEB_NAME.ipk $3
rm -r $TMP_PATH

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.6
go-version: ^1.24.2
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -68,38 +68,31 @@ 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, 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: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: "386", debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
- { os: windows, arch: amd64 }
- { os: windows, arch: amd64, legacy_go: true }
- { os: windows, arch: "386" }
- { os: windows, arch: "386", legacy_go: true }
- { os: windows, arch: arm64 }
- { os: darwin, arch: amd64 }
- { os: darwin, arch: arm64 }
- { os: windows, arch: amd64, legacy_go: true }
- { 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" }
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
- { os: android, arch: "386", ndk: "i686-linux-android21" }
exclude:
- { os: darwin, arch: "386" }
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@@ -109,7 +102,7 @@ jobs:
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5
with:
go-version: ^1.24.6
go-version: ^1.24.2
- name: Cache Legacy Go
if: matrix.require_legacy_go
id: cache-legacy-go
@@ -140,7 +133,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api,with_tailscale'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
if: matrix.os != 'android'
@@ -154,10 +147,7 @@ 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'
@@ -177,17 +167,12 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name
run: |-
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
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}"
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}"
@@ -196,12 +181,10 @@ jobs:
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get update
sudo apt-get install -y debsigs
cp .fpm_systemd .fpm
fpm -t deb \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
-p "dist/${PKG_NAME}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
@@ -216,10 +199,9 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
-p "dist/${PKG_NAME}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
@@ -235,29 +217,12 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
sudo apt-get update
sudo apt-get install -y libarchive-tools
cp .fpm_systemd .fpm
fpm -t pacman \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
-p "dist/${PKG_NAME}.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
@@ -277,7 +242,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.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
build_android:
name: Build Android
@@ -294,7 +259,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.6
go-version: ^1.24.2
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -374,7 +339,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.6
go-version: ^1.24.2
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -472,15 +437,15 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.24.6
go-version: ^1.24.2
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Set tag
if: matrix.if
run: |-
@@ -615,7 +580,7 @@ jobs:
path: 'dist'
upload:
name: Upload builds
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
runs-on: ubuntu-latest
needs:
- calculate_version

View File

@@ -28,7 +28,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.6
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.6
go-version: ^1.24.2
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -66,7 +66,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24.6
go-version: ^1.24.2
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
@@ -80,7 +80,10 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
TAGS="${TAGS},with_ech"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
run: |
@@ -117,7 +120,6 @@ jobs:
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y debsigs
cp .fpm_systemd .fpm
fpm -t deb \
--name "${NAME}" \
-v "$PKG_VERSION" \
@@ -136,7 +138,6 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \
--name "${NAME}" \
-v "$PKG_VERSION" \

View File

@@ -21,13 +21,14 @@ linters-settings:
- -SA1003
run:
go: "1.23"
go: "1.24"
build-tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
- with_acme
- with_clash_api

View File

@@ -15,6 +15,7 @@ builds:
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
- with_acme
- with_clash_api
- with_tailscale
@@ -55,12 +56,6 @@ nfpms:
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /usr/lib/systemd/system/sing-box@.service
- src: release/config/sing-box.sysusers
dst: /usr/lib/sysusers.d/sing-box.conf
- src: release/config/sing-box.rules
dst: /usr/share/polkit-1/rules.d/sing-box.rules
- src: release/config/sing-box-split-dns.xml
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
- src: release/completions/sing-box.bash
dst: /usr/share/bash-completion/completions/sing-box.bash

View File

@@ -17,6 +17,7 @@ builds:
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
- with_acme
- with_clash_api
- with_tailscale
@@ -46,6 +47,7 @@ builds:
- with_dhcp
- with_wireguard
- with_utls
- with_reality_server
- with_acme
- with_clash_api
- with_tailscale
@@ -136,12 +138,6 @@ nfpms:
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /usr/lib/systemd/system/sing-box@.service
- src: release/config/sing-box.sysusers
dst: /usr/lib/sysusers.d/sing-box.conf
- src: release/config/sing-box.rules
dst: /usr/share/polkit-1/rules.d/sing-box.rules
- src: release/config/sing-box-split-dns.xml
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
- src: release/completions/sing-box.bash
dst: /usr/share/bash-completion/completions/sing-box.bash

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_acme,with_clash_api,with_tailscale" \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api,with_tailscale" \
-o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box

View File

@@ -1,13 +1,14 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
@@ -23,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)
@@ -108,16 +109,6 @@ upload_ios_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
export_ios_ipa:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
cp build/SFI/sing-box.ipa dist/SFI.ipa
upload_ios_ipa:
cd dist && \
cp SFI.ipa "SFI-${VERSION}.ipa" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
release_ios: build_ios upload_ios_app_store
build_macos:
@@ -185,16 +176,6 @@ upload_tvos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
export_tvos_ipa:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
cp build/SFT/sing-box.ipa dist/SFT.ipa
upload_tvos_ipa:
cd dist && \
cp SFT.ipa "SFT-${VERSION}.ipa" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
release_tvos: build_tvos upload_tvos_app_store
update_apple_version:
@@ -245,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.7
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.7
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
docs:
venv/bin/mkdocs serve
@@ -265,4 +246,4 @@ clean:
update:
git fetch
git reset FETCH_HEAD --hard
git clean -fdx
git clean -fdx

View File

@@ -7,9 +7,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service"
"github.com/miekg/dns"
)
@@ -33,30 +31,11 @@ type DNSClient interface {
}
type DNSQueryOptions struct {
Transport DNSTransport
Strategy C.DomainStrategy
LookupStrategy C.DomainStrategy
DisableCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
}
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
if options == nil {
return &DNSQueryOptions{}, nil
}
transportManager := service.FromContext[DNSTransportManager](ctx)
transport, loaded := transportManager.Transport(options.Server)
if !loaded {
return nil, E.New("domain resolver not found: " + options.Server)
}
return &DNSQueryOptions{
Transport: transport,
Strategy: C.DomainStrategy(options.Strategy),
DisableCache: options.DisableCache,
RewriteTTL: options.RewriteTTL,
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
}, nil
Transport DNSTransport
Strategy C.DomainStrategy
DisableCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
}
type RDRCStore interface {

View File

@@ -7,7 +7,7 @@ import (
)
type FakeIPStore interface {
SimpleLifecycle
Service
Contains(address netip.Addr) bool
Create(domain string, isIPv6 bool) (netip.Addr, error)
Lookup(address netip.Addr) (string, bool)

View File

@@ -53,11 +53,11 @@ type InboundContext struct {
// sniffer
Protocol string
Domain string
Client string
SniffContext any
SniffError error
Protocol string
Domain string
Client string
SniffContext any
PacketSniffError error
// cache
@@ -74,7 +74,6 @@ type InboundContext struct {
UDPTimeout time.Duration
TLSFragment bool
TLSFragmentFallbackDelay time.Duration
TLSRecordFragment bool
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType

View File

@@ -37,14 +37,13 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
defer m.access.Unlock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
inbounds := m.inbounds
m.access.Unlock()
for _, inbound := range inbounds {
for _, inbound := range m.inbounds {
err := adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")

View File

@@ -2,11 +2,6 @@ package adapter
import E "github.com/sagernet/sing/common/exceptions"
type SimpleLifecycle interface {
Start() error
Close() error
}
type StartStage uint8
const (

View File

@@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error {
}
type lifecycleServiceWrapper struct {
SimpleLifecycle
Service
name string
}
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
func NewLifecycleService(service Service, name string) LifecycleService {
return &lifecycleServiceWrapper{
SimpleLifecycle: service,
name: name,
Service: service,
name: name,
}
}
@@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
}
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
return LegacyStart(l.SimpleLifecycle, stage)
return LegacyStart(l.Service, stage)
}
func (l *lifecycleServiceWrapper) Close() error {
return l.SimpleLifecycle.Close()
return l.Service.Close()
}

View File

@@ -11,7 +11,7 @@ type HeadlessRule interface {
type Rule interface {
HeadlessRule
SimpleLifecycle
Service
Type() string
Action() RuleAction
}

View File

@@ -1,27 +1,6 @@
package adapter
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type Service interface {
Lifecycle
Type() string
Tag() string
}
type ServiceRegistry interface {
option.ServiceOptionsRegistry
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
}
type ServiceManager interface {
Lifecycle
Services() []Service
Get(tag string) (Service, bool)
Remove(tag string) error
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
Start() error
Close() error
}

View File

@@ -1,21 +0,0 @@
package service
type Adapter struct {
serviceType string
serviceTag string
}
func NewAdapter(serviceType string, serviceTag string) Adapter {
return Adapter{
serviceType: serviceType,
serviceTag: serviceTag,
}
}
func (a *Adapter) Type() string {
return a.serviceType
}
func (a *Adapter) Tag() string {
return a.serviceTag
}

View File

@@ -1,144 +0,0 @@
package service
import (
"context"
"os"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ adapter.ServiceManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.ServiceRegistry
access sync.Mutex
started bool
stage adapter.StartStage
services []adapter.Service
serviceByTag map[string]adapter.Service
}
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
serviceByTag: make(map[string]adapter.Service),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
services := m.services
m.access.Unlock()
for _, service := range services {
err := adapter.LegacyStart(service, stage)
if err != nil {
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
}
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
services := m.services
m.services = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, service := range services {
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
err = E.Append(err, service.Close(), func(err error) error {
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
})
monitor.Finish()
}
return nil
}
func (m *Manager) Services() []adapter.Service {
m.access.Lock()
defer m.access.Unlock()
return m.services
}
func (m *Manager) Get(tag string) (adapter.Service, bool) {
m.access.Lock()
service, found := m.serviceByTag[tag]
m.access.Unlock()
return service, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
service, found := m.serviceByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.serviceByTag, tag)
index := common.Index(m.services, func(it adapter.Service) bool {
return it == service
})
if index == -1 {
panic("invalid service index")
}
m.services = append(m.services[:index], m.services[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return service.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(service, stage)
if err != nil {
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
}
}
}
if existsService, loaded := m.serviceByTag[tag]; loaded {
if m.started {
err = existsService.Close()
if err != nil {
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
}
}
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
return it == existsService
})
if existsIndex == -1 {
panic("invalid service index")
}
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
}
m.services = append(m.services, service)
m.serviceByTag[tag] = service
return nil
}

View File

@@ -1,72 +0,0 @@
package service
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.ServiceRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

View File

@@ -1,18 +0,0 @@
package adapter
import (
"net"
N "github.com/sagernet/sing/common/network"
)
type ManagedSSMServer interface {
Inbound
SetTracker(tracker SSMTracker)
UpdateUsers(users []string, uPSKs []string) error
}
type SSMTracker interface {
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
}

View File

@@ -3,6 +3,6 @@ package adapter
import "time"
type TimeService interface {
SimpleLifecycle
Service
TimeFunc() func() time.Time
}

125
box.go
View File

@@ -12,7 +12,6 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/certificate"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -35,23 +34,22 @@ import (
"github.com/sagernet/sing/service/pause"
)
var _ adapter.SimpleLifecycle = (*Box)(nil)
var _ adapter.Service = (*Box)(nil)
type Box struct {
createdAt time.Time
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
service *boxService.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
internalService []adapter.LifecycleService
done chan struct{}
createdAt time.Time
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
services []adapter.LifecycleService
done chan struct{}
}
type Options struct {
@@ -66,7 +64,6 @@ func Context(
outboundRegistry adapter.OutboundRegistry,
endpointRegistry adapter.EndpointRegistry,
dnsTransportRegistry adapter.DNSTransportRegistry,
serviceRegistry adapter.ServiceRegistry,
) context.Context {
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
@@ -87,10 +84,6 @@ func Context(
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
}
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
}
return ctx
}
@@ -106,7 +99,6 @@ func New(options Options) (*Box, error) {
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
if endpointRegistry == nil {
return nil, E.New("missing endpoint registry in context")
@@ -117,12 +109,6 @@ func New(options Options) (*Box, error) {
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
if dnsTransportRegistry == nil {
return nil, E.New("missing DNS transport registry in context")
}
if serviceRegistry == nil {
return nil, E.New("missing service registry in context")
}
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
@@ -156,7 +142,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create log factory")
}
var internalServices []adapter.LifecycleService
var services []adapter.LifecycleService
certificateOptions := common.PtrValueOrDefault(options.Certificate)
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
len(certificateOptions.Certificate) > 0 ||
@@ -167,7 +153,7 @@ func New(options Options) (*Box, error) {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
internalServices = append(internalServices, certificateStore)
services = append(services, certificateStore)
}
routeOptions := common.PtrValueOrDefault(options.Route)
@@ -176,12 +162,10 @@ func New(options Options) (*Box, error) {
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
@@ -296,24 +280,6 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize outbound[", i, "]")
}
}
for i, serviceOptions := range options.Services {
var tag string
if serviceOptions.Tag != "" {
tag = serviceOptions.Tag
} else {
tag = F.ToString(i)
}
err = serviceManager.Create(
ctx,
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
tag,
serviceOptions.Type,
serviceOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize service[", i, "]")
}
}
outboundManager.Initialize(common.Must1(
direct.NewOutbound(
ctx,
@@ -339,7 +305,7 @@ func New(options Options) (*Box, error) {
if needCacheFile {
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
internalServices = append(internalServices, cacheFile)
services = append(services, cacheFile)
}
if needClashAPI {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
@@ -350,7 +316,7 @@ func New(options Options) (*Box, error) {
}
router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
internalServices = append(internalServices, clashServer)
services = append(services, clashServer)
}
if needV2RayAPI {
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
@@ -359,7 +325,7 @@ func New(options Options) (*Box, error) {
}
if v2rayServer.StatsService() != nil {
router.AppendTracker(v2rayServer.StatsService())
internalServices = append(internalServices, v2rayServer)
services = append(services, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
}
@@ -377,23 +343,22 @@ func New(options Options) (*Box, error) {
WriteToSystem: ntpOptions.WriteToSystem,
})
timeService.TimeService = ntpService
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
}
return &Box{
network: networkManager,
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
dnsTransport: dnsTransportManager,
service: serviceManager,
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
internalService: internalServices,
done: make(chan struct{}),
network: networkManager,
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
dnsTransport: dnsTransportManager,
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
services: services,
done: make(chan struct{}),
}, nil
}
@@ -443,11 +408,11 @@ func (s *Box) preStart() error {
if err != nil {
return E.Cause(err, "start logger")
}
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
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
}
@@ -463,27 +428,31 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
err = adapter.StartNamed(adapter.StartStateStart, s.services)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
err = s.inbound.Start(adapter.StartStateStart)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
err = adapter.Start(adapter.StartStateStart, s.endpoint)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
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
}
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
if err != nil {
return err
}
@@ -498,9 +467,9 @@ func (s *Box) Close() error {
close(s.done)
}
err := common.Close(
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
)
for _, lifecycleService := range s.internalService {
for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error {
return E.Cause(err, "close ", lifecycleService.Name())
})

View File

@@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
return err
}
tag := tagVersion.VersionString()
client := createClient(20 * time.Minute)
client := createClient(10 * time.Minute)
log.Info(tag, " list build IDs")
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
@@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
return err
}
build := builds.Data[0]
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
log.Info(string(platform), " ", tag, " waiting for process")
time.Sleep(15 * time.Second)
continue
@@ -177,7 +177,7 @@ func publishTestflight(ctx context.Context) error {
}
log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
log.Info("waiting for process")
time.Sleep(15 * time.Second)
continue

View File

@@ -16,17 +16,15 @@ import (
)
var (
debugEnabled bool
target string
platform string
withTailscale bool
debugEnabled bool
target string
platform string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
flag.StringVar(&platform, "platform", "", "specify platform")
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
}
func main() {
@@ -46,9 +44,8 @@ var (
sharedFlags []string
debugFlags []string
sharedTags []string
darwinTags []string
iosTags []string
memcTags []string
notMemcTags []string
debugTags []string
)
@@ -62,10 +59,9 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
darwinTags = append(darwinTags, "with_dhcp")
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")
notMemcTags = append(notMemcTags, "with_low_memory")
debugTags = append(debugTags, "debug")
}
@@ -112,8 +108,6 @@ func buildAndroid() {
args = append(args, debugFlags...)
}
args = append(args, "-ldflags", "-checklinkname=0")
tags := append(sharedTags, memcTags...)
if debugEnabled {
tags = append(tags, debugTags...)
@@ -157,10 +151,7 @@ func buildApple() {
"-v",
"-target", bindTarget,
"-libname=box",
"-tags-not-macos=with_low_memory",
}
if !withTailscale {
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
"-tags-macos=" + strings.Join(memcTags, ","),
}
if !debugEnabled {
@@ -169,10 +160,7 @@ func buildApple() {
args = append(args, debugFlags...)
}
tags := append(sharedTags, darwinTags...)
if withTailscale {
tags = append(tags, memcTags...)
}
tags := append(sharedTags, iosTags...)
if debugEnabled {
tags = append(tags, debugTags...)
}

View File

@@ -1,286 +0,0 @@
package main
import (
"context"
"fmt"
"io"
"net/netip"
"os"
"os/exec"
"strings"
"syscall"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/shell"
)
var iperf3Path string
func main() {
err := main0()
if err != nil {
log.Fatal(err)
}
}
func main0() error {
err := shell.Exec("sudo", "ls").Run()
if err != nil {
return err
}
results, err := runTests()
if err != nil {
return err
}
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
return encoder.Encode(results)
}
func runTests() ([]TestResult, error) {
boxPaths := []string{
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
"./sing-box",
}
stacks := []string{
"gvisor",
"system",
}
mtus := []int{
1500,
4064,
// 16384,
// 32768,
// 49152,
65535,
}
flagList := [][]string{
{},
}
var results []TestResult
for _, boxPath := range boxPaths {
for _, stack := range stacks {
for _, mtu := range mtus {
if strings.HasPrefix(boxPath, ".") {
for _, flags := range flagList {
result, err := testOnce(boxPath, stack, mtu, false, flags)
if err != nil {
return nil, err
}
results = append(results, *result)
}
} else {
result, err := testOnce(boxPath, stack, mtu, false, nil)
if err != nil {
return nil, err
}
results = append(results, *result)
}
}
}
}
return results, nil
}
type TestResult struct {
BoxPath string `json:"box_path"`
Stack string `json:"stack"`
MTU int `json:"mtu"`
Flags []string `json:"flags"`
MultiThread bool `json:"multi_thread"`
UploadSpeed string `json:"upload_speed"`
DownloadSpeed string `json:"download_speed"`
}
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
testAddress := netip.MustParseAddr("1.1.1.1")
testConfig := option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeTun,
Options: &option.TunInboundOptions{
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
AutoRoute: true,
MTU: uint32(mtu),
Stack: stackName,
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
},
},
},
Route: &option.RouteOptions{
Rules: []option.Rule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
RawDefaultRule: option.RawDefaultRule{
IPCIDR: []string{testAddress.String()},
},
RuleAction: option.RuleAction{
Action: C.RuleActionTypeRouteOptions,
RouteOptionsOptions: option.RouteOptionsActionOptions{
OverrideAddress: "127.0.0.1",
},
},
},
},
},
AutoDetectInterface: true,
},
}
ctx := include.Context(context.Background())
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
if err != nil {
return
}
defer os.Remove(tempConfig.Name())
encoder := json.NewEncoderContext(ctx, tempConfig)
encoder.SetIndent("", " ")
err = encoder.Encode(testConfig)
if err != nil {
return nil, E.Cause(err, "encode test config")
}
tempConfig.Close()
var sudoArgs []string
if len(flags) > 0 {
sudoArgs = append(sudoArgs, "env")
for _, flag := range flags {
sudoArgs = append(sudoArgs, flag)
}
}
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
boxProcess := shell.Exec("sudo", sudoArgs...)
boxProcess.Stdout = &stderrWriter{}
boxProcess.Stderr = io.Discard
err = boxProcess.Start()
if err != nil {
return
}
if C.IsDarwin {
iperf3Path, err = exec.LookPath("iperf3-darwin")
} else {
iperf3Path, err = exec.LookPath("iperf3")
}
if err != nil {
return
}
serverProcess := shell.Exec(iperf3Path, "-s")
serverProcess.Stdout = io.Discard
serverProcess.Stderr = io.Discard
err = serverProcess.Start()
if err != nil {
return nil, E.Cause(err, "start iperf3 server")
}
time.Sleep(time.Second)
args := []string{"-c", testAddress.String()}
if multiThread {
args = append(args, "-P", "10")
}
uploadProcess := shell.Exec(iperf3Path, args...)
output, err := uploadProcess.Read()
if err != nil {
boxProcess.Process.Signal(syscall.SIGKILL)
serverProcess.Process.Signal(syscall.SIGKILL)
println(output)
return
}
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
result = &TestResult{
BoxPath: boxPath,
Stack: stackName,
MTU: mtu,
Flags: flags,
MultiThread: multiThread,
UploadSpeed: uploadResult,
}
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
output, err = downloadProcess.Read()
if err != nil {
boxProcess.Process.Signal(syscall.SIGKILL)
serverProcess.Process.Signal(syscall.SIGKILL)
println(output)
return
}
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
result.DownloadSpeed = downloadResult
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
if len(flags) > 0 {
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
}
if multiThread {
printArgs = append(printArgs, "(-P 10)")
}
fmt.Println(printArgs...)
err = boxProcess.Process.Signal(syscall.SIGTERM)
if err != nil {
return
}
err = serverProcess.Process.Signal(syscall.SIGTERM)
if err != nil {
return
}
boxDone := make(chan struct{})
go func() {
boxProcess.Cmd.Wait()
close(boxDone)
}()
serverDone := make(chan struct{})
go func() {
serverProcess.Process.Wait()
close(serverDone)
}()
select {
case <-boxDone:
case <-time.After(2 * time.Second):
boxProcess.Process.Kill()
case <-time.After(4 * time.Second):
println("box process did not close!")
os.Exit(1)
}
select {
case <-serverDone:
case <-time.After(2 * time.Second):
serverProcess.Process.Kill()
case <-time.After(4 * time.Second):
println("server process did not close!")
os.Exit(1)
}
return
}
type stderrWriter struct{}
func (w *stderrWriter) Write(p []byte) (n int, err error) {
return os.Stderr.Write(p)
}

View File

@@ -7,6 +7,7 @@ import (
"strconv"
"time"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
@@ -67,5 +68,6 @@ func preRun(cmd *cobra.Command, args []string) {
if len(configPaths) == 0 && len(configDirectories) == 0 {
configPaths = append(configPaths, "config.json")
}
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
}

View File

@@ -5,7 +5,7 @@ import (
"os"
"strings"
"github.com/sagernet/sing-box/common/convertor/adguard"
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
var rules []option.HeadlessRule
switch flagRuleSetConvertType {
case "adguard":
rules, err = adguard.ToOptions(reader, log.StdLogger())
rules, err = adguard.Convert(reader)
case "":
return E.New("source type is required")
default:

View File

@@ -6,10 +6,7 @@ import (
"strings"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
if err != nil {
return err
}
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
return len(rule.AdGuardDomain) > 0
}) {
return E.New("unable to decompile binary AdGuard rules to rule-set.")
}
var outputPath string
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".srs") {
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
outputFile.Close()
return nil
}
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
for _, rule := range rules {
switch rule.Type {
case C.RuleTypeDefault:
if cond(rule.DefaultOptions) {
return true
}
case C.RuleTypeLogical:
if hasRule(rule.LogicalOptions.Rules, cond) {
return true
}
}
}
return false
}

View File

@@ -5,7 +5,6 @@ import (
"context"
"io"
"os"
"path/filepath"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
@@ -57,14 +56,6 @@ 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

@@ -2,7 +2,6 @@ package adguard
import (
"bufio"
"bytes"
"io"
"net/netip"
"os"
@@ -10,10 +9,10 @@ import (
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
)
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
isImportant bool
}
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
scanner := bufio.NewScanner(reader)
var (
ruleLines []agdguardRuleLine
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
parseLine:
for scanner.Scan() {
ruleLine := scanner.Text()
if ruleLine == "" {
continue
}
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
continue
}
originRuleLine := ruleLine
@@ -96,7 +92,7 @@ parseLine:
}
if !ignored {
ignoredLines++
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
continue parseLine
}
}
@@ -124,35 +120,27 @@ parseLine:
ruleLine = ruleLine[1 : len(ruleLine)-1]
if ignoreIPCIDRRegexp(ruleLine) {
ignoredLines++
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
continue
}
isRegexp = true
} else {
if strings.Contains(ruleLine, "://") {
ruleLine = common.SubstringAfter(ruleLine, "://")
isSuffix = true
}
if strings.Contains(ruleLine, "/") {
ignoredLines++
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
log.Debug("ignored unsupported rule with path: ", ruleLine)
continue
}
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
if strings.Contains(ruleLine, "##") {
ignoredLines++
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
continue
}
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
if strings.Contains(ruleLine, "#$#") {
ignoredLines++
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
continue
}
if strings.Contains(ruleLine, "~") {
ignoredLines++
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
continue
}
var domainCheck string
@@ -163,7 +151,7 @@ parseLine:
}
if ruleLine == "" {
ignoredLines++
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
continue
} else {
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
@@ -171,13 +159,13 @@ parseLine:
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
if ipErr == nil {
ignoredLines++
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
continue
}
if M.ParseSocksaddr(domainCheck).Port != 0 {
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
log.Debug("ignored unsupported rule with port: ", ruleLine)
} else {
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
}
ignoredLines++
continue
@@ -295,112 +283,10 @@ parseLine:
},
}
}
if ignoredLines > 0 {
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
}
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
return []option.HeadlessRule{currentRule}, nil
}
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
if len(rules) != 1 {
return nil, ErrInvalid
}
rule := rules[0]
var (
importantDomain []string
importantDomainRegex []string
importantExcludeDomain []string
importantExcludeDomainRegex []string
domain []string
domainRegex []string
excludeDomain []string
excludeDomainRegex []string
)
parse:
for {
switch rule.Type {
case C.RuleTypeLogical:
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
return nil, ErrInvalid
}
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
return nil, ErrInvalid
}
} else {
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
return nil, ErrInvalid
}
}
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
if len(importantDomain)+len(importantDomainRegex) == 0 {
return nil, ErrInvalid
}
} else {
return nil, ErrInvalid
}
rule = rule.LogicalOptions.Rules[1]
case C.RuleTypeDefault:
domain = rule.DefaultOptions.AdGuardDomain
domainRegex = rule.DefaultOptions.DomainRegex
if len(domain)+len(domainRegex) == 0 {
return nil, ErrInvalid
}
break parse
}
}
var output bytes.Buffer
for _, ruleLine := range importantDomain {
output.WriteString(ruleLine)
output.WriteString("$important\n")
}
for _, ruleLine := range importantDomainRegex {
output.WriteString("/")
output.WriteString(ruleLine)
output.WriteString("/$important\n")
}
for _, ruleLine := range importantExcludeDomain {
output.WriteString("@@")
output.WriteString(ruleLine)
output.WriteString("$important\n")
}
for _, ruleLine := range importantExcludeDomainRegex {
output.WriteString("@@/")
output.WriteString(ruleLine)
output.WriteString("/$important\n")
}
for _, ruleLine := range domain {
output.WriteString(ruleLine)
output.WriteString("\n")
}
for _, ruleLine := range domainRegex {
output.WriteString("/")
output.WriteString(ruleLine)
output.WriteString("/\n")
}
for _, ruleLine := range excludeDomain {
output.WriteString("@@")
output.WriteString(ruleLine)
output.WriteString("\n")
}
for _, ruleLine := range excludeDomainRegex {
output.WriteString("@@/")
output.WriteString(ruleLine)
output.WriteString("/\n")
}
return output.Bytes(), nil
}
func ignoreIPCIDRRegexp(ruleLine string) bool {
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
ruleLine = ruleLine[12:]
@@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
ruleLine = ruleLine[13:]
} else if strings.HasPrefix(ruleLine, "^") {
ruleLine = ruleLine[1:]
} else {
return false
}
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
return parseErr == nil
}
func parseAdGuardHostLine(ruleLine string) (string, error) {
@@ -454,5 +342,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
for len(ruleParts) < 4 {
ruleParts = append(ruleParts, 0)
}
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
}

View File

@@ -7,15 +7,13 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing/common/logger"
"github.com/stretchr/testify/require"
)
func TestConverter(t *testing.T) {
t.Parallel()
ruleString := `||sagernet.org^$important
@@|sing-box.sagernet.org^$important
rules, err := Convert(strings.NewReader(`
||example.org^
|example.com^
example.net^
@@ -23,9 +21,10 @@ example.net^
||example.edu.tw^
|example.gov
example.arpa
@@|sagernet.example.org^
`
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
@@|sagernet.example.org|
||sagernet.org^$important
@@|sing-box.sagernet.org^$important
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
@@ -76,18 +75,15 @@ example.arpa
Domain: domain,
}), domain)
}
ruleFromOptions, err := FromOptions(rules)
require.NoError(t, err)
require.Equal(t, ruleString, string(ruleFromOptions))
}
func TestHosts(t *testing.T) {
t.Parallel()
rules, err := ToOptions(strings.NewReader(`
rules, err := Convert(strings.NewReader(`
127.0.0.1 localhost
::1 localhost #[IPv6]
0.0.0.0 google.com
`), logger.NOP())
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
@@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
func TestSimpleHosts(t *testing.T) {
t.Parallel()
rules, err := ToOptions(strings.NewReader(`
rules, err := Convert(strings.NewReader(`
example.com
www.example.org
`), logger.NOP())
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])

View File

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

View File

@@ -66,19 +66,23 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
interfaceFinder = control.NewDefaultInterfaceFinder()
}
if options.BindInterface != "" {
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
}
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
if options.RoutingMark > 0 {
if !C.IsLinux {
return nil, E.New("`routing_mark` is only supported on Linux")
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 {
@@ -97,6 +101,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
} else if networkManager.AutoDetectInterface() {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
@@ -108,10 +116,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
networkFallbackDelay = defaultOptions.FallbackDelay
}
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
@@ -123,8 +127,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
}
}
if options.ReuseAddr {
@@ -206,22 +210,6 @@ 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")
@@ -347,17 +335,7 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
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)
return d.udpListener.ListenPacket(context.Background(), network, address)
}
func trackConn(conn net.Conn, err error) (net.Conn, error) {

View File

@@ -83,7 +83,6 @@ 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{
@@ -96,31 +95,22 @@ func NewWithOptions(options Options) (N.Dialer, error) {
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else if options.DirectResolver {
return nil, E.New("missing domain resolver for domain server address")
} else {
if defaultOptions.DomainResolver != "" {
dnsQueryOptions = defaultOptions.DomainResolveOptions
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
if !loaded {
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
}
dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else {
transports := dnsTransport.Transports()
if len(transports) < 2 {
dnsQueryOptions.Transport = dnsTransport.Default()
} else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address")
} else {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
}
} 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)
}
if
//nolint:staticcheck
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
//nolint:staticcheck
dnsQueryOptions.Strategy = C.DomainStrategy(dialOptions.DomainStrategy)
deprecated.Report(options.Context, deprecated.OptionLegacyDomainStrategyOptions)
dnsQueryOptions.Transport = transport
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
} else {
transports := dnsTransport.Transports()
if len(transports) < 2 {
dnsQueryOptions.Transport = dnsTransport.Default()
} else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address")
} else if !options.DirectOutbound {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
}
}
dialer = NewResolveDialer(

View File

@@ -10,7 +10,9 @@ import (
"sync"
"time"
"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"
@@ -24,9 +26,7 @@ type slowOpenConn struct {
destination M.Socksaddr
conn net.Conn
create chan struct{}
done chan struct{}
access sync.Mutex
closeOnce sync.Once
err error
}
@@ -45,7 +45,6 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
network: network,
destination: destination,
create: make(chan struct{}),
done: make(chan struct{}),
}, nil
}
@@ -56,8 +55,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
if c.err != nil {
return 0, c.err
}
case <-c.done:
return 0, os.ErrClosed
case <-c.ctx.Done():
return 0, c.ctx.Err()
}
}
return c.conn.Read(b)
@@ -75,15 +74,12 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
return 0, c.err
}
return c.conn.Write(b)
case <-c.done:
return 0, os.ErrClosed
default:
}
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
if err != nil {
c.err = err
} else {
c.conn = conn
c.conn = nil
c.err = E.Cause(err, "dial tcp fast open")
}
n = len(b)
close(c.create)
@@ -91,13 +87,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
}
func (c *slowOpenConn) Close() error {
c.closeOnce.Do(func() {
close(c.done)
if c.conn != nil {
c.conn.Close()
}
})
return nil
return common.Close(c.conn)
}
func (c *slowOpenConn) LocalAddr() net.Addr {
@@ -162,8 +152,8 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
if c.err != nil {
return 0, c.err
}
case <-c.done:
return 0, c.err
case <-c.ctx.Done():
return 0, c.ctx.Err()
}
}
return bufio.Copy(w, c.conn)

158
common/humanize/bytes.go Normal file
View File

@@ -0,0 +1,158 @@
package humanize
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
)
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var defaultSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
var memorysSizeTable = map[string]uint64{
"b": Byte,
"kb": KiByte,
"mb": MiByte,
"gb": GiByte,
"tb": TiByte,
"pb": PiByte,
"eb": EiByte,
"": Byte,
"k": KiByte,
"m": MiByte,
"g": GiByte,
"t": TiByte,
"p": PiByte,
"e": EiByte,
}
var (
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
)
func Bytes(s uint64) string {
return humanateBytes(s, 1000, defaultSizes)
}
func MemoryBytes(s uint64) string {
return humanateBytes(s, 1024, defaultSizes)
}
func IBytes(s uint64) string {
return humanateBytes(s, 1024, iSizes)
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
func ParseBytes(s string) (uint64, error) {
return parseBytes0(s, defaultSizeTable)
}
func ParseMemoryBytes(s string) (uint64, error) {
return parseBytes0(s, memorysSizeTable)
}
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := sizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}

View File

@@ -32,7 +32,6 @@ type Listener struct {
disablePacketOutput bool
setSystemProxy bool
systemProxySOCKS bool
tproxy bool
tcpListener net.Listener
systemProxy settings.SystemProxy
@@ -55,7 +54,6 @@ type Options struct {
DisablePacketOutput bool
SetSystemProxy bool
SystemProxySOCKS bool
TProxy bool
}
func New(
@@ -73,7 +71,6 @@ func New(
disablePacketOutput: options.DisablePacketOutput,
setSystemProxy: options.SetSystemProxy,
systemProxySOCKS: options.SystemProxySOCKS,
tproxy: options.TProxy,
}
}

View File

@@ -3,18 +3,14 @@ package listener
import (
"net"
"net/netip"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/metacubex/tfo-go"
)
@@ -27,15 +23,6 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
var err error
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var listenConfig net.ListenConfig
if l.listenOptions.BindInterface != "" {
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
if l.listenOptions.RoutingMark != 0 {
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
}
if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
}
if l.listenOptions.TCPKeepAlive >= 0 {
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
if keepIdle == 0 {
@@ -53,13 +40,6 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
}
setMultiPathTCP(&listenConfig)
}
if l.tproxy {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false)
})
})
}
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
if l.listenOptions.TCPFastOpen {
var tfoConfig tfo.ListenConfig

View File

@@ -5,30 +5,17 @@ import (
"net"
"net/netip"
"os"
"syscall"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func (l *Listener) ListenUDP() (net.PacketConn, error) {
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var listenConfig net.ListenConfig
if l.listenOptions.BindInterface != "" {
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
if l.listenOptions.RoutingMark != 0 {
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
}
if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
}
var lc net.ListenConfig
var udpFragment bool
if l.listenOptions.UDPFragment != nil {
udpFragment = *l.listenOptions.UDPFragment
@@ -36,17 +23,10 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
udpFragment = l.listenOptions.UDPFragmentDefault
}
if !udpFragment {
listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())
}
if l.tproxy {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true)
})
})
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
}
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
})
if err != nil {
return nil, err
@@ -57,32 +37,8 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
return udpConn, err
}
func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
if l.listenOptions.BindInterface != "" {
dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
if l.listenOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
}
if l.listenOptions.ReuseAddr {
dialer.Control = control.Append(dialer.Control, control.ReuseAddr())
}
return dialer.DialContext(ctx, network, address)
})
}
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
if l.listenOptions.BindInterface != "" {
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
}
if l.listenOptions.RoutingMark != 0 {
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
}
if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
}
return listenConfig.ListenPacket(ctx, network, address)
})
}
@@ -164,8 +120,9 @@ func (l *Listener) loopUDPOut() {
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.udpConn.Close()
l.logger.Error("udp listener write back: ", destination, ": ", err)
continue
return
}
continue
case <-l.packetOutboundClosed:

View File

@@ -76,8 +76,6 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
// rup8(sizeof(xtcpcb_n))
itemSize += 208
}
var fallbackUDPProcess string
// skip the first xinpgen(24 bytes) block
for i := 24; i+itemSize <= len(buf); i += itemSize {
// offset of xinpcb_n and xsocket_n
@@ -92,34 +90,24 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
flag := buf[inp+44]
var srcIP netip.Addr
srcIsIPv4 := false
switch {
case flag&0x1 > 0 && isIPv4:
// ipv4
srcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))
srcIsIPv4 = true
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
case flag&0x2 > 0 && !isIPv4:
// ipv6
srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
default:
continue
}
if ip == srcIP {
// xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
if ip != srcIP {
continue
}
// udp packet connection may be not equal with srcIP
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
pid := readNativeUint32(buf[so+68 : so+72])
fallbackUDPProcess, _ = getExecPathFromPID(pid)
}
}
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
return fallbackUDPProcess, nil
// xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
}
return "", ErrNotFound

View File

@@ -12,7 +12,7 @@ import (
"golang.org/x/sys/unix"
)
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
func TProxy(fd uintptr, isIPv6 bool) error {
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
@@ -20,13 +20,11 @@ func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
}
if isUDP {
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
}
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
}
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
}
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
}
return err
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/sagernet/sing/common/control"
)
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
func TProxy(fd uintptr, isIPv6 bool) error {
return os.ErrInvalid
}

View File

@@ -31,18 +31,13 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
return os.ErrInvalid
}
const header = "BitTorrent protocol"
var protocol [19]byte
var n int
n, err = reader.Read(protocol[:])
if string(protocol[:n]) != header[:n] {
return os.ErrInvalid
}
_, err = reader.Read(protocol[:])
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
if n < 19 {
return ErrNeedMoreData
if string(protocol[:]) != "BitTorrent protocol" {
return os.ErrInvalid
}
metadata.Protocol = C.ProtocolBitTorrent

View File

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

View File

@@ -20,36 +20,22 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
if length < 12 {
if length == 0 {
return os.ErrInvalid
}
buffer := buf.NewSize(int(length))
defer buffer.Release()
var n int
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
packet := buffer.Bytes()
if n > 2 && packet[2]&0x80 != 0 { // QR
return os.ErrInvalid
}
if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
return os.ErrInvalid
}
for i := 6; i < 10; i++ {
// ANCOUNT, NSCOUNT
if n > i && packet[i] != 0 {
return os.ErrInvalid
}
}
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
}
return DomainNameQuery(readCtx, metadata, packet)
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
}
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
var msg mDNS.Msg
err := msg.Unpack(packet)
if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
if err != nil {
return err
}
metadata.Protocol = C.ProtocolDNS

View File

@@ -1,7 +1,6 @@
package sniff_test
import (
"bytes"
"context"
"encoding/hex"
"testing"
@@ -22,32 +21,3 @@ func TestSniffDNS(t *testing.T) {
require.NoError(t, err)
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
}
func TestSniffStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.NoError(t, err)
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
}
func TestSniffIncompleteStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000001000000000000")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
}
func TestSniffNotStreamDNS(t *testing.T) {
t.Parallel()
query, err := hex.DecodeString("001e740701000000000000000000")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
require.NotEmpty(t, err)
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
}

View File

@@ -68,7 +68,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
}
sniffError = E.Errors(sniffError, err)
}
if !errors.Is(sniffError, ErrNeedMoreData) {
if !errors.Is(err, ErrNeedMoreData) {
break
}
}

View File

@@ -15,11 +15,10 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
const sshPrefix = "SSH-2.0-"
bReader := bufio.NewReader(reader)
prefix, err := bReader.Peek(len(sshPrefix))
if string(prefix[:]) != sshPrefix[:len(prefix)] {
return os.ErrInvalid
}
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
} else if string(prefix) != sshPrefix {
return os.ErrInvalid
}
fistLine, _, err := bReader.ReadLine()
if err != nil {

View File

@@ -24,24 +24,3 @@ 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

@@ -215,15 +215,16 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
case ruleItemWIFIBSSID:
rule.WIFIBSSID, err = readRuleItemString(reader)
case ruleItemAdGuardDomain:
if recover {
err = E.New("unable to decompile binary AdGuard rules to rule-set")
return
}
var matcher *domain.AdGuardMatcher
matcher, err = domain.ReadAdGuardMatcher(reader)
if err != nil {
return
}
rule.AdGuardDomainMatcher = matcher
if recover {
rule.AdGuardDomain = matcher.Dump()
}
case ruleItemNetworkType:
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
case ruleItemNetworkIsExpensive:

View File

@@ -5,13 +5,13 @@ package tls
import (
"context"
"crypto/tls"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/caddyserver/certmagic"
"github.com/libdns/alidns"
@@ -37,38 +37,7 @@ func (w *acmeWrapper) Close() error {
return nil
}
type acmeLogWriter struct {
logger logger.Logger
}
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
logLine := strings.ReplaceAll(string(p), " ", ": ")
switch {
case strings.HasPrefix(logLine, "error: "):
w.logger.Error(logLine[7:])
case strings.HasPrefix(logLine, "warn: "):
w.logger.Warn(logLine[6:])
case strings.HasPrefix(logLine, "info: "):
w.logger.Info(logLine[6:])
case strings.HasPrefix(logLine, "debug: "):
w.logger.Debug(logLine[7:])
default:
w.logger.Debug(logLine)
}
return len(p), nil
}
func (w *acmeLogWriter) Sync() error {
return nil
}
func encoderConfig() zapcore.EncoderConfig {
config := zap.NewProductionEncoderConfig()
config.TimeKey = zapcore.OmitKey
return config
}
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
var acmeServer string
switch options.Provider {
case "", "letsencrypt":
@@ -89,15 +58,14 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
} else {
storage = certmagic.Default.Storage
}
zapLogger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderConfig()),
&acmeLogWriter{logger: logger},
zap.DebugLevel,
))
config := &certmagic.Config{
DefaultServerName: options.DefaultServerName,
Storage: storage,
Logger: zapLogger,
Logger: zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.InfoLevel,
)),
}
acmeConfig := certmagic.ACMEIssuer{
CA: acmeServer,
@@ -107,7 +75,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
AltHTTPPort: int(options.AlternativeHTTPPort),
AltTLSALPNPort: int(options.AlternativeTLSPort),
Logger: zapLogger,
Logger: config.Logger,
}
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
var solver certmagic.DNS01Solver
@@ -135,7 +103,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
return config, nil
},
Logger: zapLogger,
})
config = certmagic.New(cache, *config)
var tlsConfig *tls.Config

View File

@@ -9,9 +9,8 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
}

View File

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

View File

@@ -10,8 +10,6 @@ import (
"net"
"os"
"strings"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/dns"
@@ -25,7 +23,7 @@ import (
"golang.org/x/crypto/cryptobyte"
)
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
var echConfig []byte
if len(options.ECH.Config) > 0 {
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
@@ -45,13 +43,10 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
return nil, E.New("invalid ECH configs pem")
}
clientConfig.SetECHConfigList(block.Bytes)
return clientConfig, nil
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
return &STDClientConfig{tlsConfig}, nil
} else {
return &ECHClientConfig{
ECHCapableConfig: clientConfig,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
}, nil
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil
}
}
@@ -102,37 +97,20 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
return nil
}
type ECHClientConfig struct {
ECHCapableConfig
access sync.Mutex
dnsRouter adapter.DNSRouter
lastTTL time.Duration
lastUpdate time.Time
type STDECHClientConfig struct {
STDClientConfig
dnsRouter adapter.DNSRouter
}
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
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 *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
s.access.Lock()
defer s.access.Unlock()
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
if len(s.config.EncryptedClientHelloConfigList) == 0 {
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
},
Question: []mDNS.Question{
{
Name: mDNS.Fqdn(s.ServerName()),
Name: mDNS.Fqdn(s.config.ServerName),
Qtype: mDNS.TypeHTTPS,
Qclass: mDNS.ClassINET,
},
@@ -155,23 +133,29 @@ func (s *ECHClientConfig) fetchAndHandshake(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.SetECHConfigList(echConfigList)
s.config.EncryptedClientHelloConfigList = echConfigList
break match
}
}
}
}
if len(s.ECHConfigList()) == 0 {
if len(s.config.EncryptedClientHelloConfigList) == 0 {
return nil, E.New("no ECH config found in DNS records")
}
}
return s.Client(conn)
tlsConn, err := s.Client(conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
}
func (s *ECHClientConfig) Clone() Config {
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
func (s *STDECHClientConfig) Clone() Config {
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter}
}
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {

155
common/tls/ech_keygen.go Normal file
View File

@@ -0,0 +1,155 @@
package tls
import (
"bytes"
"encoding/binary"
"encoding/pem"
E "github.com/sagernet/sing/common/exceptions"
"github.com/cloudflare/circl/hpke"
"github.com/cloudflare/circl/kem"
)
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
cipherSuites := []echCipherSuite{
{
kdf: hpke.KDF_HKDF_SHA256,
aead: hpke.AEAD_AES128GCM,
}, {
kdf: hpke.KDF_HKDF_SHA256,
aead: hpke.AEAD_ChaCha20Poly1305,
},
}
keyConfig := []myECHKeyConfig{
{id: 0, kem: hpke.KEM_X25519_HKDF_SHA256},
}
keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
if err != nil {
return
}
var configBuffer bytes.Buffer
var totalLen uint16
for _, keyPair := range keyPairs {
totalLen += uint16(len(keyPair.rawConf))
}
binary.Write(&configBuffer, binary.BigEndian, totalLen)
for _, keyPair := range keyPairs {
configBuffer.Write(keyPair.rawConf)
}
var keyBuffer bytes.Buffer
for _, keyPair := range keyPairs {
keyBuffer.Write(keyPair.rawKey)
}
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
return
}
type echKeyConfigPair struct {
id uint8
rawKey []byte
conf myECHKeyConfig
rawConf []byte
}
type echCipherSuite struct {
kdf hpke.KDF
aead hpke.AEAD
}
type myECHKeyConfig struct {
id uint8
kem hpke.KEM
seed []byte
}
func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
be := binary.BigEndian
// prepare for future update
if version != 0xfe0d {
return nil, E.New("unsupported ECH version", version)
}
suiteBuf := make([]byte, 0, len(suite)*4+2)
suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
for _, s := range suite {
if !s.kdf.IsValid() || !s.aead.IsValid() {
return nil, E.New("invalid HPKE cipher suite")
}
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf))
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead))
}
pairs := []echKeyConfigPair{}
for _, c := range conf {
pair := echKeyConfigPair{}
pair.id = c.id
pair.conf = c
if !c.kem.IsValid() {
return nil, E.New("invalid HPKE KEM")
}
kpGenerator := c.kem.Scheme().GenerateKeyPair
if len(c.seed) > 0 {
kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) {
pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed)
return pub, sec, nil
}
if len(c.seed) < c.kem.Scheme().PrivateKeySize() {
return nil, E.New("HPKE KEM seed too short")
}
}
pub, sec, err := kpGenerator()
if err != nil {
return nil, E.Cause(err, "generate ECH config key pair")
}
b := []byte{}
b = be.AppendUint16(b, version)
b = be.AppendUint16(b, 0) // length field
// contents
// key config
b = append(b, c.id)
b = be.AppendUint16(b, uint16(c.kem))
pubBuf, err := pub.MarshalBinary()
if err != nil {
return nil, E.Cause(err, "serialize ECH public key")
}
b = be.AppendUint16(b, uint16(len(pubBuf)))
b = append(b, pubBuf...)
b = append(b, suiteBuf...)
// end key config
// max name len, not supported
b = append(b, 0)
// server name
b = append(b, byte(len(serverName)))
b = append(b, []byte(serverName)...)
// extensions, not supported
b = be.AppendUint16(b, 0)
be.PutUint16(b[2:], uint16(len(b)-4))
pair.rawConf = b
secBuf, err := sec.MarshalBinary()
if err != nil {
return nil, E.Cause(err, "serialize ECH private key")
}
sk := []byte{}
sk = be.AppendUint16(sk, uint16(len(secBuf)))
sk = append(sk, secBuf...)
sk = be.AppendUint16(sk, uint16(len(b)))
sk = append(sk, b...)
pair.rawKey = sk
pairs = append(pairs, pair)
}
return pairs, nil
}

View File

@@ -1,81 +0,0 @@
package tls
import (
"crypto/ecdh"
"crypto/rand"
"encoding/pem"
"golang.org/x/crypto/cryptobyte"
)
type ECHCapableConfig interface {
Config
ECHConfigList() []byte
SetECHConfigList([]byte)
}
func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return
}
echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
if err != nil {
return
}
configBuilder := cryptobyte.NewBuilder(nil)
configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(echConfig)
})
configBytes, err := configBuilder.Bytes()
if err != nil {
return
}
keyBuilder := cryptobyte.NewBuilder(nil)
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(echKey.Bytes())
})
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(echConfig)
})
keyBytes, err := keyBuilder.Bytes()
if err != nil {
return
}
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes}))
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes}))
return
}
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) {
const extensionEncryptedClientHello = 0xfe0d
const DHKEM_X25519_HKDF_SHA256 = 0x0020
const KDF_HKDF_SHA256 = 0x0001
builder := cryptobyte.NewBuilder(nil)
builder.AddUint16(extensionEncryptedClientHello)
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddUint8(id)
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(pubKey)
})
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
const (
AEAD_AES_128_GCM = 0x0001
AEAD_AES_256_GCM = 0x0002
AEAD_ChaCha20Poly1305 = 0x0003
)
for _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} {
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
builder.AddUint16(aeadID)
}
})
builder.AddUint8(maxNameLen)
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes([]byte(publicName))
})
builder.AddUint16(0) // extensions
})
return builder.Bytes()
}

View File

@@ -10,7 +10,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
)
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
return nil, E.New("ECH requires go1.24, please recompile your binary.")
}

View File

@@ -1,5 +0,0 @@
//go:build with_ech
package tls
var _ int = "Due to the migration to stdlib, the separate `with_ech` build tag has been deprecated and is no longer needed, please update your build configuration."

View File

@@ -29,13 +29,12 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
aTLS "github.com/sagernet/sing/common/tls"
utls "github.com/sagernet/utls"
utls "github.com/metacubex/utls"
"golang.org/x/crypto/hkdf"
"golang.org/x/net/http2"
)
@@ -74,7 +73,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
if decodedLen > 8 {
return nil, E.New("invalid short_id")
}
return &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID}, nil
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
}
func (e *RealityClientConfig) ServerName() string {
@@ -115,22 +114,6 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
if err != nil {
return nil, err
}
for _, extension := range uConn.Extensions {
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
ce.Curves = common.Filter(ce.Curves, func(curveID utls.CurveID) bool {
return curveID != utls.X25519MLKEM768
})
}
if ks, ok := extension.(*utls.KeyShareExtension); ok {
ks.KeyShares = common.Filter(ks.KeyShares, func(share utls.KeyShare) bool {
return share.Group != utls.X25519MLKEM768
})
}
}
err = uConn.BuildHandshakeState()
if err != nil {
return nil, err
}
if len(uConfig.NextProtos) > 0 {
for _, extension := range uConn.Extensions {
@@ -165,13 +148,9 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
if err != nil {
return nil, err
}
keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys
if keyShareKeys == nil {
return nil, E.New("nil KeyShareKeys")
}
ecdheKey := keyShareKeys.Ecdhe
ecdheKey := uConn.HandshakeState.State13.EcdheKey
if ecdheKey == nil {
return nil, E.New("nil ecdheKey")
return nil, E.New("nil ecdhe_key")
}
authKey, err := ecdheKey.ECDH(publicKey)
if err != nil {
@@ -235,6 +214,10 @@ 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_utls
//go:build with_reality_server
package tls
@@ -7,29 +7,28 @@ import (
"crypto/tls"
"encoding/base64"
"encoding/hex"
"fmt"
"net"
"time"
"github.com/sagernet/reality"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
utls "github.com/metacubex/utls"
)
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
type RealityServerConfig struct {
config *utls.RealityConfig
config *reality.Config
}
func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
var tlsConfig utls.RealityConfig
var tlsConfig reality.Config
if options.ACME != nil && len(options.ACME.Domain) > 0 {
return nil, E.New("acme is unavailable in reality")
@@ -75,11 +74,6 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
}
tlsConfig.SessionTicketsDisabled = true
tlsConfig.Log = func(format string, v ...any) {
if logger != nil {
logger.Trace(fmt.Sprintf(format, v...))
}
}
tlsConfig.Type = N.NetworkTCP
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
@@ -95,20 +89,16 @@ 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)
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
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
}
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
@@ -119,6 +109,10 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
}
if debug.Enabled {
tlsConfig.Show = true
}
return &RealityServerConfig{&tlsConfig}, nil
}
@@ -159,7 +153,7 @@ func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
}
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
tlsConn, err := utls.RealityServer(ctx, conn, c.config)
tlsConn, err := reality.Server(ctx, conn, c.config)
if err != nil {
return nil, err
}
@@ -175,7 +169,7 @@ func (c *RealityServerConfig) Clone() Config {
var _ Conn = (*realityConnWrapper)(nil)
type realityConnWrapper struct {
*utls.Conn
*reality.Conn
}
func (c *realityConnWrapper) ConnectionState() ConnectionState {

View File

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

View File

@@ -7,60 +7,43 @@ import (
"net"
"os"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tlsfragment"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
)
type STDClientConfig struct {
ctx context.Context
config *tls.Config
fragment bool
fragmentFallbackDelay time.Duration
recordFragment bool
config *tls.Config
}
func (c *STDClientConfig) ServerName() string {
return c.config.ServerName
func (s *STDClientConfig) ServerName() string {
return s.config.ServerName
}
func (c *STDClientConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
func (s *STDClientConfig) SetServerName(serverName string) {
s.config.ServerName = serverName
}
func (c *STDClientConfig) NextProtos() []string {
return c.config.NextProtos
func (s *STDClientConfig) NextProtos() []string {
return s.config.NextProtos
}
func (c *STDClientConfig) SetNextProtos(nextProto []string) {
c.config.NextProtos = nextProto
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
s.config.NextProtos = nextProto
}
func (c *STDClientConfig) Config() (*STDConfig, error) {
return c.config, nil
func (s *STDClientConfig) Config() (*STDConfig, error) {
return s.config, nil
}
func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
if c.recordFragment {
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
}
return tls.Client(conn, c.config), nil
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
return tls.Client(conn, s.config), nil
}
func (c *STDClientConfig) Clone() Config {
return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment}
}
func (c *STDClientConfig) ECHConfigList() []byte {
return c.config.EncryptedClientHelloConfigList
}
func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
@@ -77,7 +60,9 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
var tlsConfig tls.Config
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
if !options.DisableSNI {
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
tlsConfig.ServerName = serverName
}
if options.Insecure {
@@ -86,16 +71,12 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
verifyOptions := x509.VerifyOptions{
Roots: tlsConfig.RootCAs,
DNSName: serverName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range state.PeerCertificates[1:] {
verifyOptions.Intermediates.AddCert(cert)
}
if tlsConfig.Time != nil {
verifyOptions.CurrentTime = tlsConfig.Time()
}
_, err := state.PeerCertificates[0].Verify(verifyOptions)
return err
}
@@ -146,10 +127,8 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
}
tlsConfig.RootCAs = certPool
}
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
if options.ECH != nil && options.ECH.Enabled {
return parseECHClientConfig(ctx, stdConfig, options)
} else {
return stdConfig, nil
return parseECHClientConfig(ctx, options, &tlsConfig)
}
return &STDClientConfig{&tlsConfig}, nil
}

View File

@@ -6,7 +6,6 @@ import (
"net"
"os"
"strings"
"time"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -22,7 +21,7 @@ var errInsecureUnused = E.New("tls: insecure unused")
type STDServerConfig struct {
config *tls.Config
logger log.Logger
acmeService adapter.SimpleLifecycle
acmeService adapter.Service
certificate []byte
key []byte
certificatePath string
@@ -165,11 +164,11 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
return nil, nil
}
var tlsConfig *tls.Config
var acmeService adapter.SimpleLifecycle
var acmeService adapter.Service
var err error
if options.ACME != nil && len(options.ACME.Domain) > 0 {
//nolint:staticcheck
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
if err != nil {
return nil, err
}
@@ -234,12 +233,8 @@ 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, timeFunc, info.ServerName)
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
}
} else {
if certificate == nil {

View File

@@ -11,13 +11,10 @@ type TimeServiceWrapper struct {
}
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
return func() time.Time {
if w.TimeService != nil {
return w.TimeService.TimeFunc()()
} else {
return time.Now()
}
if w.TimeService == nil {
return nil
}
return w.TimeService.TimeFunc()
}
func (w *TimeServiceWrapper) Upstream() any {

View File

@@ -8,77 +8,62 @@ import (
"crypto/x509"
"math/rand"
"net"
"net/netip"
"os"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tlsfragment"
"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"
)
type UTLSClientConfig struct {
ctx context.Context
config *utls.Config
id utls.ClientHelloID
fragment bool
fragmentFallbackDelay time.Duration
recordFragment bool
config *utls.Config
id utls.ClientHelloID
}
func (c *UTLSClientConfig) ServerName() string {
return c.config.ServerName
func (e *UTLSClientConfig) ServerName() string {
return e.config.ServerName
}
func (c *UTLSClientConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
func (e *UTLSClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
func (c *UTLSClientConfig) NextProtos() []string {
return c.config.NextProtos
func (e *UTLSClientConfig) NextProtos() []string {
return e.config.NextProtos
}
func (c *UTLSClientConfig) SetNextProtos(nextProto []string) {
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
nextProto = append(nextProto, "http/1.1")
}
c.config.NextProtos = nextProto
e.config.NextProtos = nextProto
}
func (c *UTLSClientConfig) Config() (*STDConfig, error) {
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
return nil, E.New("unsupported usage for uTLS")
}
func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
if c.recordFragment {
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
}
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
}
func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
c.config.SessionIDGenerator = generator
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
e.config.SessionIDGenerator = generator
}
func (c *UTLSClientConfig) Clone() Config {
func (e *UTLSClientConfig) Clone() Config {
return &UTLSClientConfig{
c.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment,
config: e.config.Clone(),
id: e.id,
}
}
func (c *UTLSClientConfig) ECHConfigList() []byte {
return c.config.EncryptedClientHelloConfigList
}
func (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
}
type utlsConnWrapper struct {
*utls.UConn
}
@@ -131,12 +116,14 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
return c.UConn.HandshakeContext(ctx)
}
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
var serverName string
if options.ServerName != "" {
serverName = options.ServerName
} else if serverAddress != "" {
serverName = serverAddress
if _, err := netip.ParseAddr(serverName); err != nil {
serverName = serverAddress
}
}
if serverName == "" && !options.Insecure {
return nil, E.New("missing server_name or insecure=true")
@@ -145,16 +132,15 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
var tlsConfig utls.Config
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
if !options.DisableSNI {
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
tlsConfig.ServerName = serverName
}
if options.Insecure {
tlsConfig.InsecureSkipVerify = options.Insecure
} else if options.DisableSNI {
if options.Reality != nil && options.Reality.Enabled {
return nil, E.New("disable_sni is unsupported in reality")
}
tlsConfig.InsecureServerNameToVerify = serverName
return nil, E.New("disable_sni is unsupported in uTLS")
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = options.ALPN
@@ -206,15 +192,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
if err != nil {
return nil, err
}
uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
if options.ECH != nil && options.ECH.Enabled {
if options.Reality != nil && options.Reality.Enabled {
return nil, E.New("Reality is conflict with ECH")
}
return parseECHClientConfig(ctx, uConfig, options)
} else {
return uConfig, nil
}
return &UTLSClientConfig{&tlsConfig, id}, nil
}
var (
@@ -242,7 +220,7 @@ func init() {
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
switch name {
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq", "chrome_pq_psk":
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
fallthrough
case "chrome", "":
return utls.HelloChrome_Auto, nil

View File

@@ -5,7 +5,6 @@ package tls
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
@@ -15,9 +14,5 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
}
func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
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`)
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
}

View File

@@ -1,15 +1,12 @@
package tf
import (
"bytes"
"context"
"encoding/binary"
"math/rand"
"net"
"strings"
"time"
C "github.com/sagernet/sing-box/constant"
N "github.com/sagernet/sing/common/network"
"golang.org/x/net/publicsuffix"
@@ -20,24 +17,17 @@ type Conn struct {
tcpConn *net.TCPConn
ctx context.Context
firstPacketWritten bool
splitPacket bool
splitRecord bool
fallbackDelay time.Duration
}
func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {
if fallbackDelay == 0 {
fallbackDelay = C.TLSFragmentFallbackDelay
}
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
return &Conn{
Conn: conn,
tcpConn: tcpConn,
ctx: ctx,
splitPacket: splitPacket,
splitRecord: splitRecord,
fallbackDelay: fallbackDelay,
}
}, nil
}
func (c *Conn) Write(b []byte) (n int, err error) {
@@ -45,14 +35,12 @@ func (c *Conn) Write(b []byte) (n int, err error) {
defer func() {
c.firstPacketWritten = true
}()
serverName := IndexTLSServerName(b)
serverName := indexTLSServerName(b)
if serverName != nil {
if c.splitPacket {
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(true)
if err != nil {
return
}
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(true)
if err != nil {
return
}
}
splits := strings.Split(serverName.ServerName, ".")
@@ -73,50 +61,26 @@ func (c *Conn) Write(b []byte) (n int, err error) {
currentIndex++
}
}
var buffer bytes.Buffer
for i := 0; i <= len(splitIndexes); i++ {
var payload []byte
if i == 0 {
payload = b[:splitIndexes[i]]
if c.splitRecord {
payload = payload[recordLayerHeaderLen:]
}
} else if i == len(splitIndexes) {
payload = b[splitIndexes[i-1]:]
} else {
payload = b[splitIndexes[i-1]:splitIndexes[i]]
}
if c.splitRecord {
if c.splitPacket {
buffer.Reset()
if c.tcpConn != nil && i != len(splitIndexes) {
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
if err != nil {
return
}
payloadLen := uint16(len(payload))
buffer.Write(b[:3])
binary.Write(&buffer, binary.BigEndian, payloadLen)
buffer.Write(payload)
if c.splitPacket {
payload = buffer.Bytes()
} else {
_, err = c.Conn.Write(payload)
if err != nil {
return
}
}
if c.splitPacket {
if c.tcpConn != nil && i != len(splitIndexes) {
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
if err != nil {
return
}
} else {
_, err = c.Conn.Write(payload)
if err != nil {
return
}
}
}
}
if c.splitRecord && !c.splitPacket {
_, err = c.Conn.Write(buffer.Bytes())
if err != nil {
return
}
}
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(false)

View File

@@ -1,42 +0,0 @@
package tf_test
import (
"context"
"crypto/tls"
"net"
"testing"
tf "github.com/sagernet/sing-box/common/tlsfragment"
"github.com/stretchr/testify/require"
)
func TestTLSFragment(t *testing.T) {
t.Parallel()
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
require.NoError(t, err)
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, false, 0), &tls.Config{
ServerName: "www.cloudflare.com",
})
require.NoError(t, tlsConn.Handshake())
}
func TestTLSRecordFragment(t *testing.T) {
t.Parallel()
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
require.NoError(t, err)
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, true, 0), &tls.Config{
ServerName: "www.cloudflare.com",
})
require.NoError(t, tlsConn.Handshake())
}
func TestTLS2Fragment(t *testing.T) {
t.Parallel()
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
require.NoError(t, err)
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, true, 0), &tls.Config{
ServerName: "www.cloudflare.com",
})
require.NoError(t, tlsConn.Handshake())
}

View File

@@ -22,13 +22,13 @@ const (
tls13 uint16 = 0x0304
)
type MyServerName struct {
type myServerName struct {
Index int
Length int
ServerName string
}
func IndexTLSServerName(payload []byte) *MyServerName {
func indexTLSServerName(payload []byte) *myServerName {
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
return nil
}
@@ -36,48 +36,47 @@ func IndexTLSServerName(payload []byte) *MyServerName {
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
return nil
}
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen:])
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
if serverName == nil {
return nil
}
serverName.Index += recordLayerHeaderLen
serverName.Length += recordLayerHeaderLen
return serverName
}
func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
if len(handshake) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
return nil
}
if handshake[0] != handshakeType {
if hs[0] != handshakeType {
return nil
}
handshakeLen := uint32(handshake[1])<<16 | uint32(handshake[2])<<8 | uint32(handshake[3])
if len(handshake[4:]) != int(handshakeLen) {
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
if len(hs[4:]) != int(handshakeLen) {
return nil
}
tlsVersion := uint16(handshake[4])<<8 | uint16(handshake[5])
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
return nil
}
sessionIDLen := handshake[38]
currentIndex := handshakeHeaderLen + randomDataLen + sessionIDHeaderLen + int(sessionIDLen)
if len(handshake) < currentIndex {
sessionIDLen := hs[38]
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
return nil
}
cipherSuites := handshake[currentIndex:]
if len(cipherSuites) < cipherSuiteHeaderLen {
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
if len(cs) < cipherSuiteHeaderLen {
return nil
}
csLen := uint16(cipherSuites[0])<<8 | uint16(cipherSuites[1])
if len(cipherSuites) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
csLen := uint16(cs[0])<<8 | uint16(cs[1])
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
return nil
}
compressMethodLen := uint16(cipherSuites[cipherSuiteHeaderLen+int(csLen)])
currentIndex += cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
if len(handshake) < currentIndex {
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
return nil
}
serverName := indexTLSServerNameFromExtensions(handshake[currentIndex:])
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
if serverName == nil {
return nil
}
@@ -85,7 +84,7 @@ func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
return serverName
}
func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
if len(exs) == 0 {
return nil
}
@@ -119,8 +118,7 @@ func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
}
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
sex = sex[sniExtensionHeaderLen:]
return &MyServerName{
return &myServerName{
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
Length: int(sniLen),
ServerName: string(sex),

View File

@@ -1,20 +0,0 @@
package tf_test
import (
"encoding/hex"
"testing"
"github.com/sagernet/sing-box/common/tlsfragment"
"github.com/stretchr/testify/require"
)
func TestIndexTLSServerName(t *testing.T) {
t.Parallel()
payload, err := hex.DecodeString("16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100")
require.NoError(t, err)
serverName := tf.IndexTLSServerName(payload)
require.NotNil(t, serverName)
require.Equal(t, serverName.ServerName, string(payload[serverName.Index:serverName.Index+serverName.Length]))
require.Equal(t, "github.com", serverName.ServerName)
}

View File

@@ -25,9 +25,6 @@ const (
TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2"
TypeTailscale = "tailscale"
TypeDERP = "derp"
TypeResolved = "resolved"
TypeSSMAPI = "ssm-api"
)
const (

View File

@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
if options.TraceBack != "" {
debug.SetTraceback(options.TraceBack)
}
if options.MemoryLimit.Value() != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
conntrack.MemoryLimit = options.MemoryLimit.Value()
if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
conntrack.MemoryLimit = uint64(options.MemoryLimit)
}
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", byteformats.FormatMemoryBytes(memStats.HeapInuse))
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
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("goroutines", runtime.NumGoroutine())
memObject.Put("rss", rusageMaxRSS())

View File

@@ -34,7 +34,6 @@ type Client struct {
disableCache bool
disableExpire bool
independentCache bool
clientSubnet netip.Prefix
rdrc adapter.RDRCStore
initRDRCFunc func() adapter.RDRCStore
logger logger.ContextLogger
@@ -48,7 +47,6 @@ type ClientOptions struct {
DisableExpire bool
IndependentCache bool
CacheCapacity uint32
ClientSubnet netip.Prefix
RDRC func() adapter.RDRCStore
Logger logger.ContextLogger
}
@@ -59,7 +57,6 @@ func NewClient(options ClientOptions) *Client {
disableCache: options.DisableCache,
disableExpire: options.DisableExpire,
independentCache: options.IndependentCache,
clientSubnet: options.ClientSubnet,
initRDRCFunc: options.RDRC,
logger: options.Logger,
}
@@ -107,12 +104,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
return &responseMessage, nil
}
question := message.Question[0]
clientSubnet := options.ClientSubnet
if !clientSubnet.IsValid() {
clientSubnet = c.clientSubnet
}
if clientSubnet.IsValid() {
message = SetClientSubnet(message, clientSubnet)
if options.ClientSubnet.IsValid() {
message = SetClientSubnet(message, options.ClientSubnet, true)
}
isSimpleRequest := len(message.Question) == 1 &&
len(message.Ns) == 0 &&
@@ -195,13 +188,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
}*/
if responseChecker != nil {
var rejected bool
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) {
rejected = true
} else {
rejected = !responseChecker(MessageToAddresses(response))
}
if rejected {
addr, addrErr := MessageToAddresses(response)
if addrErr != nil || !responseChecker(addr) {
if c.rdrc != nil {
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
}
@@ -244,20 +232,10 @@ 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
}
@@ -265,15 +243,9 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
domain = FqdnToDomain(domain)
dnsName := dns.Fqdn(domain)
var strategy C.DomainStrategy
if options.LookupStrategy != C.DomainStrategyAsIS {
strategy = options.LookupStrategy
} else {
strategy = options.Strategy
}
if strategy == C.DomainStrategyIPv4Only {
if options.Strategy == C.DomainStrategyIPv4Only {
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
} else if strategy == C.DomainStrategyIPv6Only {
} else if options.Strategy == C.DomainStrategyIPv6Only {
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
}
var response4 []netip.Addr
@@ -299,7 +271,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
if len(response4) == 0 && len(response6) == 0 {
return nil, err
}
return sortAddresses(response4, response6, strategy), nil
return sortAddresses(response4, response6, options.Strategy), nil
}
func (c *Client) ClearCache() {
@@ -425,10 +397,7 @@ func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTran
if err != nil {
return nil, err
}
if response.Rcode != dns.RcodeSuccess {
return nil, RcodeError(response.Rcode)
}
return MessageToAddresses(response), nil
return MessageToAddresses(response)
}
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
@@ -436,10 +405,7 @@ func (c *Client) questionCache(question dns.Question, transport adapter.DNSTrans
if response == nil {
return nil, ErrNotCached
}
if response.Rcode != dns.RcodeSuccess {
return nil, RcodeError(response.Rcode)
}
return MessageToAddresses(response), nil
return MessageToAddresses(response)
}
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
@@ -516,7 +482,10 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
}
}
func MessageToAddresses(response *dns.Msg) []netip.Addr {
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
return nil, RcodeError(response.Rcode)
}
addresses := make([]netip.Addr, 0, len(response.Answer))
for _, rawAnswer := range response.Answer {
switch answer := rawAnswer.(type) {
@@ -532,7 +501,7 @@ func MessageToAddresses(response *dns.Msg) []netip.Addr {
}
}
}
return addresses
return addresses, nil
}
func wrapError(err error) error {
@@ -558,26 +527,12 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
return value, loaded
}
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
return &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: message.Id,
Rcode: rcode,
Response: true,
},
Question: message.Question,
}
}
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
response := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: dns.RcodeSuccess,
Id: id,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{question},
}
@@ -610,12 +565,9 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg {
response := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: dns.RcodeSuccess,
Id: id,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{question},
Answer: []dns.RR{
@@ -636,12 +588,9 @@ func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToL
func FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg {
response := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: dns.RcodeSuccess,
Id: id,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{question},
Answer: []dns.RR{
@@ -662,12 +611,9 @@ func FixedResponseTXT(id uint16, question dns.Question, records []string, timeTo
func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg {
response := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: dns.RcodeSuccess,
Id: id,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{question},
}

View File

@@ -6,11 +6,7 @@ import (
"github.com/miekg/dns"
)
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg {
return setClientSubnet(message, clientSubnet, true)
}
func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg {
var (
optRecord *dns.OPT
subnetOption *dns.EDNS0_SUBNET
@@ -23,6 +19,9 @@ findExists:
var isEDNS0Subnet bool
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
if isEDNS0Subnet {
if !override {
return message
}
break findExists
}
}
@@ -38,14 +37,14 @@ findExists:
},
}
message.Extra = append(message.Extra, optRecord)
} else if clone {
return setClientSubnet(message.Copy(), clientSubnet, false)
} else {
message = message.Copy()
}
if subnetOption == nil {
subnetOption = new(dns.EDNS0_SUBNET)
subnetOption.Code = dns.EDNS0SUBNET
optRecord.Option = append(optRecord.Option, subnetOption)
}
subnetOption.Code = dns.EDNS0SUBNET
if clientSubnet.Addr().Is4() {
subnetOption.Family = 1
} else {

View File

@@ -55,7 +55,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
DisableExpire: options.DNSClientOptions.DisableExpire,
IndependentCache: options.DNSClientOptions.IndependentCache,
CacheCapacity: options.DNSClientOptions.CacheCapacity,
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
RDRC: func() adapter.RDRCStore {
cacheFile := service.FromContext[adapter.CacheFile](ctx)
if cacheFile == nil {
@@ -259,14 +258,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
case *R.RuleActionReject:
switch action.Method {
case C.RuleActionRejectMethodDefault:
return &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Rcode: mDNS.RcodeRefused,
Response: true,
},
Question: []mDNS.Question{message.Question[0]},
}, nil
return FixedResponse(message.Id, message.Question[0], nil, 0), nil
case C.RuleActionRejectMethodDrop:
return nil, tun.ErrDrop
}
@@ -293,12 +285,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
} else if errors.Is(err, ErrResponseRejected) {
rejected = true
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
/*} else if responseCheck!= nil && errors.Is(err, RcodeError(mDNS.RcodeNameError)) {
rejected = true
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
*/
} else if len(message.Question) > 0 {
rejected = true
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
} else {
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
@@ -336,9 +323,6 @@ 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)")
@@ -347,15 +331,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))
}
}
if err != nil {
err = E.Cause(err, "lookup ", domain)
} else if len(responseAddrs) == 0 {
r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
err = RcodeNameError
}
}
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
if cached {
if len(responseAddrs) == 0 {
return nil, E.New("lookup ", domain, ": empty result (cached)")
return nil, RcodeNameError
}
return responseAddrs, nil
}

View File

@@ -3,15 +3,11 @@ package transport
import (
"bytes"
"context"
"errors"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
@@ -43,13 +39,11 @@ func RegisterHTTPS(registry *dns.TransportRegistry) {
type HTTPSTransport struct {
dns.TransportAdapter
logger logger.ContextLogger
dialer N.Dialer
destination *url.URL
headers http.Header
transportAccess sync.Mutex
transport *http.Transport
transportResetAt time.Time
logger logger.ContextLogger
dialer N.Dialer
destination *url.URL
headers http.Header
transport *http.Transport
}
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
@@ -102,9 +96,6 @@ 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,
@@ -167,33 +158,12 @@ func (t *HTTPSTransport) Start(stage adapter.StartStage) error {
}
func (t *HTTPSTransport) Close() error {
t.transportAccess.Lock()
defer t.transportAccess.Unlock()
t.transport.CloseIdleConnections()
t.transport = t.transport.Clone()
return nil
}
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
startAt := time.Now()
response, err := t.exchange(ctx, message)
if err != nil {
if errors.Is(err, os.ErrDeadlineExceeded) {
t.transportAccess.Lock()
defer t.transportAccess.Unlock()
if t.transportResetAt.After(startAt) {
return nil, err
}
t.transport.CloseIdleConnections()
t.transport = t.transport.Clone()
t.transportResetAt = time.Now()
}
return nil, err
}
return response, nil
}
func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
exMessage := *message
exMessage.Id = 0
exMessage.Compress = true

View File

@@ -35,7 +35,6 @@ 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
@@ -58,7 +57,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
}
}
systemConfig := getSystemDNSConfig(t.ctx)
systemConfig := getSystemDNSConfig()
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
} else {
@@ -90,9 +89,8 @@ 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 {
if response.Rcode != mDNS.RcodeSuccess {
err = dns.RcodeError(response.Rcode)
} else if len(dns.MessageToAddresses(response)) == 0 {
addresses, _ := dns.MessageToAddresses(response)
if len(addresses) == 0 {
err = E.New(fqdn, ": empty result")
}
}

View File

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

View File

@@ -11,7 +11,6 @@ package local
import "C"
import (
"context"
"time"
E "github.com/sagernet/sing/common/exceptions"
@@ -19,9 +18,8 @@ import (
"github.com/miekg/dns"
)
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
var state C.struct___res_state
if C.res_ninit(&state) != 0 {
func dnsReadConfig(_ string) *dnsConfig {
if C.res_init() != 0 {
return &dnsConfig{
servers: defaultNS,
search: dnsDefaultSearch(),
@@ -34,10 +32,10 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
conf := &dnsConfig{
ndots: 1,
timeout: 5 * time.Second,
attempts: int(state.retry),
attempts: int(C._res.retry),
}
for i := 0; i < int(state.nscount); i++ {
ns := state.nsaddr_list[i]
for i := 0; i < int(C._res.nscount); i++ {
ns := C._res.nsaddr_list[i]
addr := C.inet_ntoa(ns.sin_addr)
if addr == nil {
continue
@@ -45,7 +43,7 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
conf.servers = append(conf.servers, C.GoString(addr))
}
for i := 0; ; i++ {
search := state.dnsrch[i]
search := C._res.dnsrch[i]
if search == nil {
break
}

View File

@@ -1,13 +0,0 @@
package local
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestDNSReadConfig(t *testing.T) {
t.Parallel()
require.NoError(t, dnsReadConfig(context.Background(), "/etc/resolv.conf").err)
}

View File

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

View File

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

View File

@@ -13,7 +13,6 @@ 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"
@@ -41,9 +40,6 @@ 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,20 +57,13 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
if serverAddr.Port == 0 {
serverAddr.Port = 853
}
if !serverAddr.IsValid() {
return nil, E.New("invalid server address: ", serverAddr)
}
return NewTLSRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), transportDialer, serverAddr, tlsConfig), nil
}
func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr, tlsConfig tls.Config) *TLSTransport {
return &TLSTransport{
TransportAdapter: adapter,
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
logger: logger,
dialer: dialer,
dialer: transportDialer,
serverAddr: serverAddr,
tlsConfig: tlsConfig,
}
}, nil
}
func (t *TLSTransport) Start(stage adapter.StartStage) error {

View File

@@ -13,7 +13,6 @@ 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"
@@ -48,9 +47,6 @@ 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
}
@@ -60,7 +56,7 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
logger: logger,
dialer: dialer,
serverAddr: serverAddr,
udpSize: 2048,
udpSize: 512,
tcpTransport: &TCPTransport{
dialer: dialer,
serverAddr: serverAddr,
@@ -97,19 +93,15 @@ func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
}
func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
t.access.Lock()
if edns0Opt := message.IsEdns0(); edns0Opt != nil {
if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize {
t.udpSize = udpSize
close(t.done)
t.done = make(chan struct{})
}
}
t.access.Unlock()
conn, err := t.open(ctx)
if err != nil {
return nil, err
}
if edns0Opt := message.IsEdns0(); edns0Opt != nil {
if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize {
t.udpSize = udpSize
}
}
buffer := buf.NewSize(1 + message.Len())
defer buffer.Release()
exMessage := *message
@@ -125,7 +117,7 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
conn.access.Unlock()
defer func() {
conn.access.Lock()
delete(conn.callbacks, exMessage.Id)
delete(conn.callbacks, messageId)
conn.access.Unlock()
}()
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())

View File

@@ -2,340 +2,7 @@
icon: material/alert-decagram
---
#### 1.12.1
* Fixes and improvements
#### 1.12.0
* Refactor DNS servers **1**
* Add domain resolver options**2**
* Add TLS fragment/record fragment support to route options and outbound TLS options **3**
* Add certificate options **4**
* Add Tailscale endpoint and DNS server **5**
* Drop support for go1.22 **6**
* Add AnyTLS protocol **7**
* Migrate to stdlib ECH implementation **8**
* Add NTP sniffer **9**
* Add wildcard SNI support for ShadowTLS inbound **10**
* Improve `auto_redirect` **11**
* Add control options for listeners **12**
* Add DERP service **13**
* Add Resolved service and DNS server **14**
* Add SSM API service **15**
* Add loopback address support for tun **16**
* Improve tun performance on Apple platforms **17**
* Update quic-go to v0.52.0
* Update gVisor to 20250319.0
* Update the status of graphical clients in stores **18**
**1**:
DNS servers are refactored for better performance and scalability.
See [DNS server](/configuration/dns/server/).
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
Compatibility for old formats will be removed in sing-box 1.14.0.
**2**:
Legacy `outbound` DNS rules are deprecated
and can be replaced by the new `domain_resolver` option.
See [Dial Fields](/configuration/shared/dial/#domain_resolver) and
[Route](/configuration/route/#default_domain_resolver).
For migration,
see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
**3**:
See [Route Action](/configuration/route/rule_action/#tls_fragment) and [TLS](/configuration/shared/tls/).
**4**:
New certificate options allow you to manage the default list of trusted X509 CA certificates.
For the system certificate list, fixed Go not reading Android trusted certificates correctly.
You can also use the Mozilla Included List instead, or add trusted certificates yourself.
See [Certificate](/configuration/certificate/).
**5**:
See [Tailscale](/configuration/endpoint/tailscale/).
**6**:
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
**7**:
The new AnyTLS protocol claims to mitigate TLS proxy traffic characteristics and comes with a new multiplexing scheme.
See [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/configuration/outbound/anytls/).
**8**:
See [TLS](/configuration/shared/tls).
The build tag `with_ech` is no longer needed and has been removed.
**9**:
See [Protocol Sniff](/configuration/route/sniff/).
**10**:
See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).
**11**:
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
see [Tun](/configuration/inbound/tun/#auto_redirect).
**12**:
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
See [Listen Fields](/configuration/shared/listen/).
**13**:
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
See [DERP Service](/configuration/service/derp/).
**14**:
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
(e.g. NetworkManager) and provide DNS resolution.
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
**15**:
SSM API service is a RESTful API server for managing Shadowsocks servers.
See [SSM API Service](/configuration/service/ssm-api/).
**16**:
TUN now implements SideStore's StosVPN.
See [Tun](/configuration/inbound/tun/#loopback_address).
**17**:
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
The following data was tested using [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro.
| Version | Stack | MTU | Upload | Download |
|-------------|--------|-------|--------|----------|
| 1.11.15 | gvisor | 1500 | 852M | 2.57G |
| 1.12.0-rc.4 | gvisor | 1500 | 2.90G | 4.68G |
| 1.11.15 | gvisor | 4064 | 2.31G | 6.34G |
| 1.12.0-rc.4 | gvisor | 4064 | 7.54G | 12.2G |
| 1.11.15 | gvisor | 65535 | 27.6G | 18.1G |
| 1.12.0-rc.4 | gvisor | 65535 | 39.8G | 34.7G |
| 1.11.15 | system | 1500 | 664M | 706M |
| 1.12.0-rc.4 | system | 1500 | 2.44G | 2.51G |
| 1.11.15 | system | 4064 | 1.88G | 1.94G |
| 1.12.0-rc.4 | system | 4064 | 6.45G | 6.27G |
| 1.11.15 | system | 65535 | 26.2G | 17.4G |
| 1.12.0-rc.4 | system | 65535 | 17.6G | 21.0G |
**18**:
We continue to experience issues updating our sing-box apps on the App Store and Play Store.
Until we rewrite and resubmit the apps, they are considered irrecoverable.
Therefore, after this release, we will not be repeating this notice unless there is new information.
### 1.11.15
* 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.32
* Improve tun performance on Apple platforms **1**
* Fixes and improvements
**1**:
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
### 1.11.14
* 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.24
* Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1**
* Also add fragment options for TLS client configuration **2**
* Fixes and improvements
**1**:
For debugging only, it is recommended to disable if record fragmentation works.
See [Route Action](/configuration/route/rule_action/#tls_fragment).
**2**:
See [TLS](/configuration/shared/tls/).
#### 1.12.0-beta.23
* Add loopback address support for tun **1**
* Add cache support for ssm-api **2**
* Fixes and improvements
**1**:
TUN now implements SideStore's StosVPN.
See [Tun](/configuration/inbound/tun/#loopback_address).
**2**:
See [SSM API Service](/configuration/service/ssm-api/#cache_path).
#### 1.12.0-beta.21
* Fix missing `home` option for DERP service **1**
* Fixes and improvements
**1**:
You can now choose what the DERP home page shows, just like with derper's `-home` flag.
See [DERP](/configuration/service/derp/#home).
### 1.11.13
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.17
* Update quic-go to v0.52.0
* Fixes and improvements
#### 1.12.0-beta.15
* Add DERP service **1**
* Add Resolved service and DNS server **2**
* Add SSM API service **3**
* Fixes and improvements
**1**:
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
See [DERP Service](/configuration/service/derp/).
**2**:
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
(e.g. NetworkManager) and provide DNS resolution.
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
**3**:
SSM API service is a RESTful API server for managing Shadowsocks servers.
See [SSM API Service](/configuration/service/ssm-api/).
### 1.11.11
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.13
* Add TLS record fragment route options **1**
* Add missing `accept_routes` option for Tailscale **2**
* Fixes and improvements
**1**:
See [Route Action](/configuration/route/rule_action/#tls_record_fragment).
**2**:
See [Tailscale](/configuration/endpoint/tailscale/#accept_routes).
#### 1.12.0-beta.10
* Add control options for listeners **1**
* Fixes and improvements
**1**:
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
See [Listen Fields](/configuration/shared/listen/).
### 1.11.10
* Undeprecate the `block` outbound **1**
* Fixes and improvements
**1**:
Since we dont have a replacement for using the `block` outbound in selectors yet,
we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future.
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.9
* Update quic-go to v0.51.0
* Fixes and improvements
### 1.11.9
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.5
* Fixes and improvements
### 1.11.8
* Improve `auto_redirect` **1**
* Fixes and improvements
**1**:
Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks,
see [Tun](/configuration/inbound/tun/#auto_redirect).
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.3
#### 1.12.0-beta.2
* Fixes and improvements
@@ -343,8 +10,7 @@ violated the rules (TestFlight users are not affected)._
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.1
@@ -359,8 +25,7 @@ see [Tun](/configuration/inbound/tun/#auto_redirect).
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
#### 1.12.0-alpha.19
@@ -400,8 +65,7 @@ See [Dial Fields](/configuration/shared/dial/#domain_resolver).
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
#### 1.12.0-alpha.13
@@ -472,8 +136,7 @@ For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and pa
* Fixes and improvements
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration
process._
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
#### 1.12.0-alpha.5

View File

@@ -9,10 +9,6 @@ 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

@@ -1,11 +1,7 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-decagram: [servers](#servers)
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [cache_capacity](#cache_capacity)

View File

@@ -1,11 +1,7 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! quote "sing-box 1.12.0 中的更改"
:material-decagram: [servers](#servers)
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [cache_capacity](#cache_capacity)

View File

@@ -81,7 +81,7 @@ Will overrides `dns.client_subnet`.
#### method
- `default`: Reply with REFUSED.
- `default`: Reply with NXDOMAIN.
- `drop`: Drop the request.
`default` will be used by default.

View File

@@ -81,7 +81,7 @@ icon: material/new-box
#### method
- `default`: 返回 REFUSED
- `default`: 返回 NXDOMAIN
- `drop`: 丢弃请求。
默认使用 `defualt`

View File

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

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