Compare commits

..

55 Commits

Author SHA1 Message Date
世界
60167bc049 documentation: Bump version 2025-02-22 17:46:28 +08:00
世界
fc39ddb173 documentation: Fix AnyTLS doc 2025-02-22 17:46:28 +08:00
anytls
0bbe99852a Add AnyTLS protocol 2025-02-22 17:46:28 +08:00
世界
417726e3e0 Migrate to stdlib ECH support 2025-02-22 17:46:28 +08:00
世界
277dfc7c44 documentation: Bump version 2025-02-21 08:30:40 +08:00
世界
7a090e1219 Fix predefined DNS server 2025-02-21 08:30:40 +08:00
世界
8fc7725e3d Fix missing links for new deprecated warnings 2025-02-20 22:28:54 +08:00
世界
af813c0d1a Omit reject.method=default 2025-02-20 20:05:10 +08:00
Gavin Luo
918bef80d3 release: Fix systemd permissions 2025-02-20 20:05:10 +08:00
ReleTor
10cd6c9ae0 documentation: Minor fixes 2025-02-20 20:05:10 +08:00
世界
b00370a6fc Add fallback local DNS server for iOS 2025-02-20 20:05:10 +08:00
世界
cbc3844650 Get darwin local DNS server from libresolv 2025-02-20 20:05:10 +08:00
世界
32cc9d9c90 Improve resolve action 2025-02-20 20:05:10 +08:00
世界
f752cd2784 platform: Add update WIFI state func 2025-02-20 15:16:29 +08:00
世界
63a0b5e2ce documentation: Bump version 2025-02-18 21:57:28 +08:00
世界
7c6a81ed13 Fix http.FileServer short write 2025-02-18 21:57:28 +08:00
世界
28f4fff423 Fix toolchain version 2025-02-18 21:57:28 +08:00
世界
8f81011f5b Fix save domain in fake-ip 2025-02-18 21:57:23 +08:00
世界
12ac163fbf Fix parsing A records 2025-02-18 21:57:23 +08:00
世界
de6ee34f12 Fix http3 DNS server 2025-02-18 18:19:26 +08:00
世界
3b12d00320 documentation: Make it clear that auth key is not required for Tailscale 2025-02-18 18:19:25 +08:00
世界
44ee71c540 Fix linter 2025-02-18 18:19:23 +08:00
世界
4cc3472bed Add back port hopping to hysteria 1 2025-02-18 18:19:21 +08:00
世界
1926bacbb6 documentation: Bump version 2025-02-18 18:19:19 +08:00
世界
f2e2d9cecd Remove unused debug messages 2025-02-18 18:19:17 +08:00
世界
fe96959649 release: Fix update android version 2025-02-18 18:19:15 +08:00
世界
187e78968c Update dependencies 2025-02-18 18:19:13 +08:00
世界
ed5de40065 Fix crash on route address set update 2025-02-18 18:19:08 +08:00
世界
b223e8d721 Fix tailscale dialer 2025-02-18 18:19:06 +08:00
世界
d884e07147 documentation: Bump version 2025-02-18 18:19:05 +08:00
世界
44177b9e58 Fix Android certificate initialize 2025-02-18 18:19:03 +08:00
世界
c57bbcb68a documentation: Add Tailscale DNS 2025-02-18 18:19:02 +08:00
世界
d573465ae6 documentation: Fix missing advertise_exit_node 2025-02-18 18:19:00 +08:00
世界
2dd3c00719 documentation: Bump version 2025-02-18 18:18:59 +08:00
xchacha20-poly1305
039f306d50 Remove single quotes of raw Moziila certs 2025-02-18 18:18:56 +08:00
世界
a3a0362552 Add Tailscale endpoint 2025-02-18 18:18:55 +08:00
世界
edd9bed002 Bump Go to go1.24 2025-02-18 18:18:53 +08:00
世界
782d51929e Build legacy binaries with latest Go 2025-02-18 18:18:50 +08:00
世界
86b162e0f0 Fix parsing legacy DNS servers 2025-02-18 18:18:49 +08:00
世界
801f9fb4ef documentation: Bump version 2025-02-18 18:18:47 +08:00
ReleTor
9c5af6dfe4 documentation: Fixes 2025-02-18 18:18:44 +08:00
世界
3053c9a852 Fix crash in exchangeParallel 2025-02-18 18:18:42 +08:00
世界
4d70a07017 Fix match DNS rule for fqdn 2025-02-18 18:18:41 +08:00
世界
3002aba29e Fix WireGuard panic 2025-02-18 18:18:39 +08:00
世界
22acbd36c5 Fix domain resolver for DNS server 2025-02-18 18:18:36 +08:00
世界
1ddd084f41 documentation: Fix fakeip example 2025-02-18 18:18:35 +08:00
世界
90068238ce documentation: Remove outdated icons 2025-02-18 18:18:34 +08:00
世界
41a697374c documentation: Certificate store 2025-02-18 18:18:27 +08:00
世界
4239f003a6 documentation: TLS fragment 2025-02-18 18:18:26 +08:00
世界
b74568d601 documentation: Outbound domain resolver 2025-02-18 18:18:23 +08:00
世界
f539766361 documentation: Refactor DNS 2025-02-18 18:18:21 +08:00
世界
8f75ab8bb9 Add certificate store 2025-02-18 18:17:59 +08:00
世界
5ac6dd25ef Add TLS fragment support 2025-02-18 18:17:59 +08:00
世界
b0355c27fd refactor: Outbound domain resolver 2025-02-18 18:17:58 +08:00
世界
f83f1122af refactor: DNS 2025-02-18 18:17:58 +08:00
186 changed files with 1310 additions and 9476 deletions

19
.fpm
View File

@@ -1,19 +0,0 @@
-s dir
--name sing-box
--category net
--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"
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/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

View File

@@ -1,13 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION="1.23.6" VERSION="1.23.6"
mkdir -p $HOME/go
cd $HOME/go
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz" wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
tar -xzf "go${VERSION}.linux-amd64.tar.gz" tar -xzf "go${VERSION}.linux-amd64.tar.gz"
mv go go_legacy mv go $HOME/go/go_legacy
cd go_legacy cd $HOME/go/go_legacy
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557 # modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.23.x # this patch file only works on golang1.23.x

View File

@@ -50,7 +50,7 @@ jobs:
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
echo "version=${{ inputs.version }}" echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version - name: Calculate version
if: github.event_name != 'workflow_dispatch' if: github.event_name != 'workflow_dispatch'
@@ -68,42 +68,73 @@ jobs:
- calculate_version - calculate_version
strategy: strategy:
matrix: matrix:
os: [ linux, windows, darwin, android ]
arch: [ "386", amd64, arm64 ]
legacy_go: [ false ]
include: include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 } - name: linux_386
- { os: linux, arch: "386", debian: i386, rpm: i386 } goos: linux
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl } goarch: 386
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl } - name: linux_amd64
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 } goos: linux
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el } goarch: amd64
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel } - name: linux_arm64
- { os: linux, arch: s390x, debian: s390x, rpm: s390x } goos: linux
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le } goarch: arm64
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 } - name: linux_arm
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 } goos: linux
goarch: arm
- { os: windows, arch: "386", legacy_go: true } goarm: 6
- { os: windows, arch: amd64, legacy_go: true } - name: linux_arm_v7
goos: linux
- { os: android, arch: "386", ndk: "i686-linux-android21" } goarch: arm
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" } goarm: 7
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" } - name: linux_s390x
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" } goos: linux
exclude: goarch: s390x
- { os: darwin, arch: "386" } - name: linux_riscv64
goos: linux
goarch: riscv64
- name: linux_mips64le
goos: linux
goarch: mips64le
- name: windows_amd64
goos: windows
goarch: amd64
require_legacy_go: true
- name: windows_386
goos: windows
goarch: 386
require_legacy_go: true
- name: windows_arm64
goos: windows
goarch: arm64
- name: darwin_arm64
goos: darwin
goarch: arm64
- name: darwin_amd64
goos: darwin
goarch: amd64
- name: android_arm64
goos: android
goarch: arm64
- name: android_arm
goos: android
goarch: arm
goarm: 7
- name: android_amd64
goos: android
goarch: amd64
- name: android_386
goos: android
goarch: 386
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24
- name: Cache Legacy Go - name: Cache legacy Go
if: matrix.require_legacy_go if: matrix.require_legacy_go
id: cache-legacy-go id: cache-legacy-go
uses: actions/cache@v4 uses: actions/cache@v4
@@ -111,139 +142,64 @@ jobs:
path: | path: |
~/go/go_legacy ~/go/go_legacy
key: go_legacy_1236 key: go_legacy_1236
- name: Setup Legacy Go - name: Setup legacy Go
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true' if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |- run: bash .github/setup_legacy_go.sh
.github/setup_legacy_go.sh
- name: Setup Legacy Go 2
if: matrix.legacy_go
run: |-
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
- name: Setup Android NDK - name: Setup Android NDK
if: matrix.os == 'android' if: matrix.goos == 'android'
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
with: with:
ndk-version: r28 ndk-version: r28
local-cache: true local-cache: true
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: 2.5.1
install-only: true
- name: Extract signing key
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Set tag - name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_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 - name: Build
if: matrix.os != 'android' if: matrix.goos != 'android'
run: | run: |-
set -xeuo pipefail goreleaser release --clean --split
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env: env:
CGO_ENABLED: "0" GOOS: ${{ matrix.goos }}
GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.goarch }}
GOARCH: ${{ matrix.arch }} GOPATH: ${{ env.HOME }}/go
GOARM: ${{ matrix.goarm }} GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Build Android - name: Build Android
if: matrix.os == 'android' if: matrix.goos == 'android'
run: | run: |-
set -xeuo pipefail
go install -v ./cmd/internal/build go install -v ./cmd/internal/build
export CC='${{ matrix.ndk }}-clang' GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
export CXX="${CC}++"
mkdir -p dist
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env: env:
CGO_ENABLED: "1" BUILD_GOOS: ${{ matrix.goos }}
BUILD_GOOS: ${{ matrix.os }} BUILD_GOARCH: ${{ matrix.goarch }}
BUILD_GOARCH: ${{ matrix.arch }} GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
run: |- NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true) NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
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}"
- name: Package DEB
if: matrix.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y debsigs
fpm -t deb \
-v "${{ needs.calculate_version.outputs.version }}" \
-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'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
fpm -t rpm \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${PKG_NAME}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Package Pacman
if: matrix.pacman != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y libarchive-tools
fpm -t pacman \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${PKG_NAME}.pkg.tar.zst" \
--architecture ${{ matrix.pacman }} \
dist/sing-box=/usr/bin/sing-box
- name: Archive
run: |
set -xeuo pipefail
cd dist
mkdir -p "${DIR_NAME}"
cp ../LICENSE "${DIR_NAME}"
if [ '${{ matrix.os }}' = 'windoes' ]; then
cp sing-box.exe "${DIR_NAME}"
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
else
cp sing-box "${DIR_NAME}"
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
fi
rm -r "${DIR_NAME}"
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact - name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }} name: binary-${{ matrix.name }}
path: "dist" path: 'dist'
build_android: build_android:
name: Build Android name: Build Android
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android' if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
@@ -315,11 +271,13 @@ jobs:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare upload - name: Prepare upload
if: github.event_name == 'workflow_dispatch'
run: |- run: |-
mkdir -p dist/release mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
- name: Upload artifact - name: Upload artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: binary-android-apks name: binary-android-apks
@@ -477,19 +435,19 @@ jobs:
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles" PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILES_PATH" mkdir -p "$PROFILES_PATH"
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH" unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12 ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
xcrun notarytool store-credentials "notarytool-password" \ xcrun notarytool store-credentials "notarytool-password" \
--key $ASC_KEY_PATH \ --key $ASC_KEY_PATH \
--key-id $ASC_KEY_ID \ --key-id $ASC_KEY_ID \
--issuer $ASC_KEY_ISSUER_ID --issuer $ASC_KEY_ISSUER_ID
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV" echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV" echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV" echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
@@ -565,7 +523,7 @@ jobs:
cd "${{ matrix.archive }}" cd "${{ matrix.archive }}"
zip -r SFM.dSYMs.zip dSYMs zip -r SFM.dSYMs.zip dSYMs
popd popd
mkdir -p dist/release mkdir -p dist/release
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg" cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip" cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
@@ -589,6 +547,12 @@ jobs:
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: 2.5.1
install-only: true
- name: Cache ghr - name: Cache ghr
uses: actions/cache@v4 uses: actions/cache@v4
id: cache-ghr id: cache-ghr
@@ -613,17 +577,26 @@ jobs:
with: with:
path: dist path: dist
merge-multiple: true merge-multiple: true
- name: Merge builds
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
run: |-
goreleaser continue --merge --skip publish
mkdir -p dist/release
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: Upload builds - name: Upload builds
if: ${{ env.PUBLISHED == 'false' }} if: ${{ env.PUBLISHED == 'false' }}
run: |- run: |-
export PATH="$PATH:$HOME/go/bin" export PATH="$PATH:$HOME/go/bin"
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Replace builds - name: Replace builds
if: ${{ env.PUBLISHED != 'false' }} if: ${{ env.PUBLISHED != 'false' }}
run: |- run: |-
export PATH="$PATH:$HOME/go/bin" export PATH="$PATH:$HOME/go/bin"
ghr --replace -p 5 "v${VERSION}" dist ghr --replace -p 5 "v${VERSION}" dist/release
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -34,5 +34,4 @@ jobs:
with: with:
version: latest version: latest
args: --timeout=30m args: --timeout=30m
install-mode: binary install-mode: binary
verify: false

View File

@@ -1,63 +1,13 @@
name: Build Linux Packages name: Release to Linux repository
on: on:
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
release: release:
types: types:
- published - published
jobs: jobs:
calculate_version:
name: Calculate version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --nightly
- name: Set outputs
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
build: build:
name: Build binary
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- calculate_version
strategy:
matrix:
include:
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: "386", debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
@@ -67,114 +17,22 @@ jobs:
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24
- name: Setup Android NDK - name: Extract signing key
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
with:
ndk-version: r28
local-cache: true
- name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" mkdir -p $HOME/.gnupg
git tag v${{ needs.calculate_version.outputs.version }} -f cat > $HOME/.gnupg/sagernet.key <<EOF
- name: Set build tags ${{ secrets.GPG_KEY }}
run: | EOF
set -xeuo pipefail echo "HOME=$HOME" >> "$GITHUB_ENV"
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api' - name: Publish release
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then uses: goreleaser/goreleaser-action@v6
TAGS="${TAGS},with_ech" with:
fi distribution: goreleaser-pro
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" version: latest
- name: Build args: release -f .goreleaser.fury.yaml --clean
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
./cmd/sing-box
env: env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set mtime GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
run: |- FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
TZ=UTC touch -t '197001010000' dist/sing-box NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
- name: Set name NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
run: |-
echo "NAME=sing-box" >> "$GITHUB_ENV"
- name: Set beta name
if: contains(needs.calculate_version.outputs.version, '-')
run: |-
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
- name: Package DEB
if: matrix.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y debsigs
fpm -t deb \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/${NAME}
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
fpm -t rpm \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/${NAME}
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
upload:
name: Upload builds
runs-on: ubuntu-latest
needs:
- calculate_version
- build
steps:
- name: Checkout
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Publish packages
run: |-
wget -O fury-cli.deb https://github.com/gemfury/cli/releases/download/v0.23.0/fury-cli_0.23.0_linux_amd64.deb
sudo dpkg -i fury-cli.deb
fury migrate dist --as=sagernet --api-token ${{ secrets.FURY_TOKEN }}

1
.gitignore vendored
View File

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

View File

@@ -31,7 +31,6 @@ run:
- with_reality_server - with_reality_server
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_script
issues: issues:
exclude-dirs: exclude-dirs:

View File

@@ -21,7 +21,6 @@ builds:
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_tailscale - with_tailscale
- with_script
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
- GOTOOLCHAIN=local - GOTOOLCHAIN=local
@@ -52,7 +51,6 @@ builds:
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_tailscale - with_tailscale
- with_script
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go_legacy - GOROOT={{ .Env.GOPATH }}/go_legacy
@@ -99,12 +97,10 @@ archives:
builds: builds:
- main - main
- android - android
formats: format: tar.gz
- tar.gz
format_overrides: format_overrides:
- goos: windows - goos: windows
formats: format: zip
- zip
wrap_in_directory: true wrap_in_directory: true
files: files:
- LICENSE - LICENSE
@@ -128,8 +124,8 @@ nfpms:
- deb - deb
- rpm - rpm
- archlinux - archlinux
# - apk # - apk
# - ipk # - ipk
priority: extra priority: extra
contents: contents:
- src: release/config/config.json - src: release/config/config.json

View File

@@ -1,6 +1,8 @@
NAME = sing-box NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD) COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale,with_script TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
TAGS_GO123 = with_tailscale
TAGS ?= $(TAGS_GO120),$(TAGS_GO123)
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTOS = $(shell go env GOHOSTOS)
@@ -18,6 +20,11 @@ build:
export GOTOOLCHAIN=local && \ export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN) go build $(MAIN_PARAMS) $(MAIN)
ci_build_go120:
export GOTOOLCHAIN=local && \
go build $(PARAMS) $(MAIN) && \
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
ci_build: ci_build:
export GOTOOLCHAIN=local && \ export GOTOOLCHAIN=local && \
go build $(PARAMS) $(MAIN) && \ go build $(PARAMS) $(MAIN) && \
@@ -226,8 +233,8 @@ lib:
go run ./cmd/internal/build_libbox -target ios go run ./cmd/internal/build_libbox -target ios
lib_install: lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.5 go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.4
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.5 go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4
docs: docs:
venv/bin/mkdocs serve venv/bin/mkdocs serve

View File

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

View File

@@ -45,10 +45,10 @@ type RDRCStore interface {
} }
type DNSTransport interface { type DNSTransport interface {
Lifecycle
Type() string Type() string
Tag() string Tag() string
Dependencies() []string Dependencies() []string
Reset()
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error) Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -246,6 +246,8 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if err != nil { if err != nil {
return err return err
} }
m.access.Lock()
defer m.access.Unlock()
if m.started { if m.started {
for _, stage := range adapter.ListStartStages { for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(outbound, stage) err = adapter.LegacyStart(outbound, stage)
@@ -254,8 +256,6 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
} }
} }
} }
m.access.Lock()
defer m.access.Unlock()
if existsOutbound, loaded := m.outboundByTag[tag]; loaded { if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
if m.started { if m.started {
err = common.Close(existsOutbound) err = common.Close(existsOutbound)

View File

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

60
box.go
View File

@@ -23,11 +23,9 @@ import (
"github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/mitm"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route" "github.com/sagernet/sing-box/route"
"github.com/sagernet/sing-box/script"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
@@ -50,8 +48,6 @@ type Box struct {
dnsRouter *dns.Router dnsRouter *dns.Router
connection *route.ConnectionManager connection *route.ConnectionManager
router *route.Router router *route.Router
script *script.Manager
mitm adapter.MITMEngine //*mitm.Engine
services []adapter.LifecycleService services []adapter.LifecycleService
done chan struct{} done chan struct{}
} }
@@ -147,12 +143,18 @@ func New(options Options) (*Box, error) {
} }
var services []adapter.LifecycleService var services []adapter.LifecycleService
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), common.PtrValueOrDefault(options.Certificate)) certificateOptions := common.PtrValueOrDefault(options.Certificate)
if err != nil { if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
return nil, err len(certificateOptions.Certificate) > 0 ||
len(certificateOptions.CertificatePath) > 0 ||
len(certificateOptions.CertificateDirectoryPath) > 0 {
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
if err != nil {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
} }
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
routeOptions := common.PtrValueOrDefault(options.Route) routeOptions := common.PtrValueOrDefault(options.Route)
dnsOptions := common.PtrValueOrDefault(options.DNS) dnsOptions := common.PtrValueOrDefault(options.DNS)
@@ -171,7 +173,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize network manager") return nil, E.Cause(err, "initialize network manager")
} }
service.MustRegister[adapter.NetworkManager](ctx, networkManager) service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection")) connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager) service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions) router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router) service.MustRegister[adapter.Router](ctx, router)
@@ -179,8 +181,8 @@ func New(options Options) (*Box, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize router") return nil, E.Cause(err, "initialize router")
} }
var timeService *tls.TimeServiceWrapper
ntpOptions := common.PtrValueOrDefault(options.NTP) ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled { if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper) timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService) service.MustRegister[ntp.TimeService](ctx, timeService)
@@ -214,15 +216,8 @@ func New(options Options) (*Box, error) {
} else { } else {
tag = F.ToString(i) tag = F.ToString(i)
} }
endpointCtx := ctx
if tag != "" {
// TODO: remove this
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = endpointManager.Create( err = endpointManager.Create(
endpointCtx, ctx,
router, router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag, tag,
@@ -294,11 +289,6 @@ func New(options Options) (*Box, error) {
"local", "local",
option.LocalDNSServerOptions{}, option.LocalDNSServerOptions{},
))) )))
scriptManager, err := script.NewManager(ctx, logFactory, options.Scripts)
if err != nil {
return nil, E.Cause(err, "initialize script manager")
}
service.MustRegister[adapter.ScriptManager](ctx, scriptManager)
if platformInterface != nil { if platformInterface != nil {
err = platformInterface.Initialize(networkManager) err = platformInterface.Initialize(networkManager)
if err != nil { if err != nil {
@@ -348,16 +338,6 @@ func New(options Options) (*Box, error) {
timeService.TimeService = ntpService timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service")) services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
} }
mitmOptions := common.PtrValueOrDefault(options.MITM)
var mitmEngine adapter.MITMEngine
if mitmOptions.Enabled {
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
if err != nil {
return nil, E.Cause(err, "create MITM engine")
}
service.MustRegister[adapter.MITMEngine](ctx, engine)
mitmEngine = engine
}
return &Box{ return &Box{
network: networkManager, network: networkManager,
endpoint: endpointManager, endpoint: endpointManager,
@@ -367,8 +347,6 @@ func New(options Options) (*Box, error) {
dnsRouter: dnsRouter, dnsRouter: dnsRouter,
connection: connectionManager, connection: connectionManager,
router: router, router: router,
script: scriptManager,
mitm: mitmEngine,
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.Logger(), logger: logFactory.Logger(),
@@ -427,11 +405,11 @@ func (s *Box) preStart() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.script, s.mitm) err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil { if err != nil {
return err return err
} }
@@ -455,7 +433,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
@@ -463,7 +441,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
@@ -482,7 +460,7 @@ func (s *Box) Close() error {
close(s.done) close(s.done)
} }
err := common.Close( err := common.Close(
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network, s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
) )
for _, lifecycleService := range s.services { for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error { err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -45,7 +45,6 @@ var (
debugFlags []string debugFlags []string
sharedTags []string sharedTags []string
iosTags []string iosTags []string
memcTags []string
debugTags []string debugTags []string
) )
@@ -59,9 +58,8 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=") sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag) debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_script") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_tailscale")
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack") iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
memcTags = append(memcTags, "with_tailscale")
debugTags = append(debugTags, "debug") debugTags = append(debugTags, "debug")
} }
@@ -101,19 +99,18 @@ func buildAndroid() {
"-javapkg=io.nekohasekai", "-javapkg=io.nekohasekai",
"-libname=box", "-libname=box",
} }
if !debugEnabled { if !debugEnabled {
args = append(args, sharedFlags...) args = append(args, sharedFlags...)
} else { } else {
args = append(args, debugFlags...) args = append(args, debugFlags...)
} }
tags := append(sharedTags, memcTags...) args = append(args, "-tags")
if debugEnabled { if !debugEnabled {
tags = append(tags, debugTags...) args = append(args, strings.Join(sharedTags, ","))
} else {
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
} }
args = append(args, "-tags", strings.Join(tags, ","))
args = append(args, "./experimental/libbox") args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...) command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
@@ -151,9 +148,7 @@ func buildApple() {
"-v", "-v",
"-target", bindTarget, "-target", bindTarget,
"-libname=box", "-libname=box",
"-tags-macos=" + strings.Join(memcTags, ","),
} }
if !debugEnabled { if !debugEnabled {
args = append(args, sharedFlags...) args = append(args, sharedFlags...)
} else { } else {
@@ -161,11 +156,12 @@ func buildApple() {
} }
tags := append(sharedTags, iosTags...) tags := append(sharedTags, iosTags...)
if debugEnabled { args = append(args, "-tags")
tags = append(tags, debugTags...) if !debugEnabled {
args = append(args, strings.Join(tags, ","))
} else {
args = append(args, strings.Join(append(tags, debugTags...), ","))
} }
args = append(args, "-tags", strings.Join(tags, ","))
args = append(args, "./experimental/libbox") args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...) command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,6 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/listener"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@@ -36,7 +35,6 @@ type DefaultDialer struct {
udpListener net.ListenConfig udpListener net.ListenConfig
udpAddr4 string udpAddr4 string
udpAddr6 string udpAddr6 string
netns string
networkManager adapter.NetworkManager networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool defaultNetworkStrategy bool
@@ -200,7 +198,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpListener: listener, udpListener: listener,
udpAddr4: udpAddr4, udpAddr4: udpAddr4,
udpAddr6: udpAddr6, udpAddr6: udpAddr6,
netns: options.NetNs,
networkManager: networkManager, networkManager: networkManager,
networkStrategy: networkStrategy, networkStrategy: networkStrategy,
defaultNetworkStrategy: defaultNetworkStrategy, defaultNetworkStrategy: defaultNetworkStrategy,
@@ -213,25 +210,21 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
if !address.IsValid() { if !address.IsValid() {
return nil, E.New("invalid address") return nil, E.New("invalid address")
} else if address.IsFqdn() {
return nil, E.New("domain not resolved")
} }
if d.networkStrategy == nil { if d.networkStrategy == nil {
return trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) { switch N.NetworkName(network) {
switch N.NetworkName(network) { case N.NetworkUDP:
case N.NetworkUDP:
if !address.IsIPv6() {
return d.udpDialer4.DialContext(ctx, network, address.String())
} else {
return d.udpDialer6.DialContext(ctx, network, address.String())
}
}
if !address.IsIPv6() { if !address.IsIPv6() {
return DialSlowContext(&d.dialer4, ctx, network, address) return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else { } else {
return DialSlowContext(&d.dialer6, ctx, network, address) return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
} }
})) }
if !address.IsIPv6() {
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else {
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
}
} else { } else {
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
} }
@@ -287,15 +280,13 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if d.networkStrategy == nil { if d.networkStrategy == nil {
return trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) { if destination.IsIPv6() {
if destination.IsIPv6() { return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6) } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4) } else {
} else { return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4) }
}
}))
} else { } else {
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
} }

View File

@@ -18,7 +18,6 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface") return nil, false, E.New("no available network interface")
} }
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
if fallbackDelay == 0 { if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay fallbackDelay = N.DefaultFallbackDelay
} }
@@ -32,9 +31,7 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
results := make(chan dialResult) // unbuffered results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer perNetDialer := dialer
if defaultInterface == nil || iif.Index != defaultInterface.Index { perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr) conn, err := perNetDialer.DialContext(ctx, network, addr)
if err != nil { if err != nil {
select { select {
@@ -92,7 +89,6 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface") return nil, false, E.New("no available network interface")
} }
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
if fallbackDelay == 0 { if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay fallbackDelay = N.DefaultFallbackDelay
} }
@@ -107,9 +103,7 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
results := make(chan dialResult) // unbuffered results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) { startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer perNetDialer := dialer
if defaultInterface == nil || iif.Index != defaultInterface.Index { perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr) conn, err := perNetDialer.DialContext(ctx, network, addr)
if err != nil { if err != nil {
select { select {
@@ -155,13 +149,10 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, E.New("no available network interface") return nil, E.New("no available network interface")
} }
defaultInterface := d.networkManager.InterfaceMonitor().DefaultInterface()
var errors []error var errors []error
for _, primaryInterface := range primaryInterfaces { for _, primaryInterface := range primaryInterfaces {
perNetListener := listener perNetListener := listener
if defaultInterface == nil || primaryInterface.Index != defaultInterface.Index { perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
}
conn, err := perNetListener.ListenPacket(ctx, network, addr) conn, err := perNetListener.ListenPacket(ctx, network, addr)
if err == nil { if err == nil {
return conn, nil return conn, nil
@@ -170,9 +161,7 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
} }
for _, fallbackInterface := range fallbackInterfaces { for _, fallbackInterface := range fallbackInterfaces {
perNetListener := listener perNetListener := listener
if defaultInterface == nil || fallbackInterface.Index != defaultInterface.Index { perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
}
conn, err := perNetListener.ListenPacket(ctx, network, addr) conn, err := perNetListener.ListenPacket(ctx, network, addr)
if err == nil { if err == nil {
return conn, nil return conn, nil

View File

@@ -6,39 +6,26 @@ import (
"sync" "sync"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
type DirectDialer interface {
IsEmpty() bool
}
type DetourDialer struct { type DetourDialer struct {
outboundManager adapter.OutboundManager outboundManager adapter.OutboundManager
detour string detour string
legacyDNSDialer bool
dialer N.Dialer dialer N.Dialer
initOnce sync.Once initOnce sync.Once
initErr error initErr error
} }
func NewDetour(outboundManager adapter.OutboundManager, detour string, legacyDNSDialer bool) N.Dialer { func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer {
return &DetourDialer{ return &DetourDialer{outboundManager: outboundManager, detour: detour}
outboundManager: outboundManager,
detour: detour,
legacyDNSDialer: legacyDNSDialer,
}
} }
func InitializeDetour(dialer N.Dialer) error { func (d *DetourDialer) Start() error {
detourDialer, isDetour := common.Cast[*DetourDialer](dialer) _, err := d.Dialer()
if !isDetour { return err
return nil
}
return common.Error(detourDialer.Dialer())
} }
func (d *DetourDialer) Dialer() (N.Dialer, error) { func (d *DetourDialer) Dialer() (N.Dialer, error) {
@@ -47,20 +34,11 @@ func (d *DetourDialer) Dialer() (N.Dialer, error) {
} }
func (d *DetourDialer) init() { func (d *DetourDialer) init() {
dialer, loaded := d.outboundManager.Outbound(d.detour) var loaded bool
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
if !loaded { if !loaded {
d.initErr = E.New("outbound detour not found: ", d.detour) d.initErr = E.New("outbound detour not found: ", d.detour)
return
} }
if !d.legacyDNSDialer {
if directDialer, isDirect := dialer.(DirectDialer); isDirect {
if directDialer.IsEmpty() {
d.initErr = E.New("detour to an empty direct outbound makes no sense")
return
}
}
}
d.dialer = dialer
} }
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {

View File

@@ -23,7 +23,6 @@ type Options struct {
DirectResolver bool DirectResolver bool
ResolverOnDetour bool ResolverOnDetour bool
NewDialer bool NewDialer bool
LegacyDNSDialer bool
} }
// TODO: merge with NewWithOptions // TODO: merge with NewWithOptions
@@ -46,14 +45,14 @@ func NewWithOptions(options Options) (N.Dialer, error) {
if outboundManager == nil { if outboundManager == nil {
return nil, E.New("missing outbound manager") return nil, E.New("missing outbound manager")
} }
dialer = NewDetour(outboundManager, dialOptions.Detour, options.LegacyDNSDialer) dialer = NewDetour(outboundManager, dialOptions.Detour)
} else { } else {
dialer, err = NewDefault(options.Context, dialOptions) dialer, err = NewDefault(options.Context, dialOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour || dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "") { if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) {
networkManager := service.FromContext[adapter.NetworkManager](options.Context) networkManager := service.FromContext[adapter.NetworkManager](options.Context)
dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context) dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context)
var defaultOptions adapter.NetworkOptions var defaultOptions adapter.NetworkOptions
@@ -105,12 +104,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
} else if options.NewDialer { } else if options.NewDialer {
return nil, E.New("missing domain resolver for domain server address") return nil, E.New("missing domain resolver for domain server address")
} else { } else {
transports := dnsTransport.Transports() deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
if len(transports) < 2 {
dnsQueryOptions.Transport = dnsTransport.Default()
} else {
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
}
} }
dialer = NewResolveDialer( dialer = NewResolveDialer(
options.Context, options.Context,

View File

@@ -44,20 +44,6 @@ type resolveDialer struct {
} }
func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer { func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
return &resolveParallelNetworkDialer{
resolveDialer{
transport: service.FromContext[adapter.DNSTransportManager](ctx),
router: service.FromContext[adapter.DNSRouter](ctx),
dialer: dialer,
parallel: parallel,
server: server,
queryOptions: queryOptions,
fallbackDelay: fallbackDelay,
},
parallelDialer,
}
}
return &resolveDialer{ return &resolveDialer{
transport: service.FromContext[adapter.DNSTransportManager](ctx), transport: service.FromContext[adapter.DNSTransportManager](ctx),
router: service.FromContext[adapter.DNSRouter](ctx), router: service.FromContext[adapter.DNSRouter](ctx),
@@ -74,6 +60,21 @@ type resolveParallelNetworkDialer struct {
dialer ParallelInterfaceDialer dialer ParallelInterfaceDialer
} }
func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer {
return &resolveParallelNetworkDialer{
resolveDialer{
transport: service.FromContext[adapter.DNSTransportManager](ctx),
router: service.FromContext[adapter.DNSRouter](ctx),
dialer: dialer,
parallel: parallel,
server: server,
queryOptions: queryOptions,
fallbackDelay: fallbackDelay,
},
dialer,
}
}
func (d *resolveDialer) initialize() error { func (d *resolveDialer) initialize() error {
d.initOnce.Do(d.initServer) d.initOnce.Do(d.initServer)
return d.initErr return d.initErr

View File

@@ -4,8 +4,6 @@ import (
"context" "context"
"net" "net"
"net/netip" "net/netip"
"runtime"
"strings"
"sync/atomic" "sync/atomic"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -16,8 +14,6 @@ import (
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/vishvananda/netns"
) )
type Listener struct { type Listener struct {
@@ -139,30 +135,3 @@ func (l *Listener) UDPConn() *net.UDPConn {
func (l *Listener) ListenOptions() option.ListenOptions { func (l *Listener) ListenOptions() option.ListenOptions {
return l.listenOptions return l.listenOptions
} }
func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (T, error) {
if nameOrPath != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
currentNs, err := netns.Get()
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "get current netns")
}
defer netns.Set(currentNs)
var targetNs netns.NsHandle
if strings.HasPrefix(nameOrPath, "/") {
targetNs, err = netns.GetFromPath(nameOrPath)
} else {
targetNs, err = netns.GetFromName(nameOrPath)
}
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "get netns ", nameOrPath)
}
defer targetNs.Close()
err = netns.Set(targetNs)
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "set netns to ", nameOrPath)
}
}
return block()
}

View File

@@ -16,12 +16,9 @@ import (
) )
func (l *Listener) ListenTCP() (net.Listener, error) { func (l *Listener) ListenTCP() (net.Listener, error) {
//nolint:staticcheck
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
}
var err error var err error
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var tcpListener net.Listener
var listenConfig net.ListenConfig var listenConfig net.ListenConfig
if l.listenOptions.TCPKeepAlive >= 0 { if l.listenOptions.TCPKeepAlive >= 0 {
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
@@ -40,19 +37,20 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
} }
setMultiPathTCP(&listenConfig) setMultiPathTCP(&listenConfig)
} }
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) { if l.listenOptions.TCPFastOpen {
if l.listenOptions.TCPFastOpen { var tfoConfig tfo.ListenConfig
var tfoConfig tfo.ListenConfig tfoConfig.ListenConfig = listenConfig
tfoConfig.ListenConfig = listenConfig tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
return tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) } else {
} else { tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
return listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) }
} if err == nil {
}) l.logger.Info("tcp server started at ", tcpListener.Addr())
if err != nil { }
return nil, err //nolint:staticcheck
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
} }
l.logger.Info("tcp server started at ", tcpListener.Addr())
l.tcpListener = tcpListener l.tcpListener = tcpListener
return tcpListener, err return tcpListener, err
} }

View File

@@ -1,7 +1,6 @@
package listener package listener
import ( import (
"context"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@@ -25,9 +24,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
if !udpFragment { if !udpFragment {
lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
} }
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -37,12 +34,6 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
return udpConn, err return udpConn, err
} }
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
return listenConfig.ListenPacket(ctx, network, address)
})
}
func (l *Listener) UDPAddr() M.Socksaddr { func (l *Listener) UDPAddr() M.Socksaddr {
return l.udpAddr return l.udpAddr
} }

View File

@@ -18,6 +18,5 @@ func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Rea
} }
metadata.Protocol = C.ProtocolHTTP metadata.Protocol = C.ProtocolHTTP
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString() metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
metadata.HTTPRequest = request
return nil return nil
} }

View File

@@ -1,58 +0,0 @@
package sniff
import (
"context"
"encoding/binary"
"os"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
)
func NTP(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
// NTP packets must be at least 48 bytes long (standard NTP header size).
pLen := len(packet)
if pLen < 48 {
return os.ErrInvalid
}
// Check the LI (Leap Indicator) and Version Number (VN) in the first byte.
// We'll primarily focus on ensuring the version is valid for NTP.
// Many NTP versions are used, but let's check for generally accepted ones (3 & 4 for IPv4, plus potential extensions/customizations)
firstByte := packet[0]
li := (firstByte >> 6) & 0x03 // Extract LI
vn := (firstByte >> 3) & 0x07 // Extract VN
mode := firstByte & 0x07 // Extract Mode
// Leap Indicator should be a valid value (0-3).
if li > 3 {
return os.ErrInvalid
}
// Version Check (common NTP versions are 3 and 4)
if vn != 3 && vn != 4 {
return os.ErrInvalid
}
// Check the Mode field for a client request (Mode 3). This validates it *is* a request.
if mode != 3 {
return os.ErrInvalid
}
// Check Root Delay and Root Dispersion. While not strictly *required* for a request,
// we can check if they appear to be reasonable values (not excessively large).
rootDelay := binary.BigEndian.Uint32(packet[4:8])
rootDispersion := binary.BigEndian.Uint32(packet[8:12])
// Check for unreasonably large root delay and dispersion. NTP RFC specifies max values of approximately 16 seconds.
// Convert to milliseconds for easy comparison. Each unit is 1/2^16 seconds.
if float64(rootDelay)/65536.0 > 16.0 {
return os.ErrInvalid
}
if float64(rootDispersion)/65536.0 > 16.0 {
return os.ErrInvalid
}
metadata.Protocol = C.ProtocolNTP
return nil
}

View File

@@ -1,33 +0,0 @@
package sniff_test
import (
"context"
"encoding/hex"
"os"
"testing"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/stretchr/testify/require"
)
func TestSniffNTP(t *testing.T) {
t.Parallel()
packet, err := hex.DecodeString("1b0006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.NTP(context.Background(), &metadata, packet)
require.NoError(t, err)
require.Equal(t, metadata.Protocol, C.ProtocolNTP)
}
func TestSniffNTPFailed(t *testing.T) {
t.Parallel()
packet, err := hex.DecodeString("400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
require.NoError(t, err)
var metadata adapter.InboundContext
err = sniff.NTP(context.Background(), &metadata, packet)
require.ErrorIs(t, err, os.ErrInvalid)
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
@@ -35,7 +34,7 @@ func Skip(metadata *adapter.InboundContext) bool {
return false return false
} }
func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffers []*buf.Buffer, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error { func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.Conn, buffer *buf.Buffer, timeout time.Duration, sniffers ...StreamSniffer) error {
if timeout == 0 { if timeout == 0 {
timeout = C.ReadPayloadTimeout timeout = C.ReadPayloadTimeout
} }
@@ -56,10 +55,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
} }
errors = nil errors = nil
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader { err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
return bytes.NewReader(it.Bytes())
})...)
err = sniffer(ctx, metadata, reader)
if err == nil { if err == nil {
return nil return nil
} }

View File

@@ -21,7 +21,6 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
if clientHello != nil { if clientHello != nil {
metadata.Protocol = C.ProtocolTLS metadata.Protocol = C.ProtocolTLS
metadata.Domain = clientHello.ServerName metadata.Domain = clientHello.ServerName
metadata.ClientHello = clientHello
return nil return nil
} }
return err return err

View File

@@ -46,7 +46,7 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
tlsConfig.EncryptedClientHelloConfigList = block.Bytes tlsConfig.EncryptedClientHelloConfigList = block.Bytes
return &STDClientConfig{tlsConfig}, nil return &STDClientConfig{tlsConfig}, nil
} else { } else {
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil return &STDECHClientConfig{STDClientConfig{tlsConfig}}, nil
} }
} }
@@ -99,10 +99,9 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
type STDECHClientConfig struct { type STDECHClientConfig struct {
STDClientConfig STDClientConfig
dnsRouter adapter.DNSRouter
} }
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { func (s *STDClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
if len(s.config.EncryptedClientHelloConfigList) == 0 { if len(s.config.EncryptedClientHelloConfigList) == 0 {
message := &mDNS.Msg{ message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{ MsgHdr: mDNS.MsgHdr{
@@ -116,7 +115,8 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
}, },
}, },
} }
response, err := s.dnsRouter.Exchange(ctx, message, adapter.DNSQueryOptions{}) dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
response, err := dnsRouter.Exchange(ctx, message, adapter.DNSQueryOptions{})
if err != nil { if err != nil {
return nil, E.Cause(err, "fetch ECH config list") return nil, E.Cause(err, "fetch ECH config list")
} }
@@ -151,7 +151,7 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
} }
func (s *STDECHClientConfig) Clone() Config { func (s *STDECHClientConfig) Clone() Config {
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter} return &STDECHClientConfig{STDClientConfig{s.config.Clone()}}
} }
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) { func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {

View File

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

View File

@@ -15,19 +15,19 @@ const (
) )
const ( const (
DNSTypeLegacy = "legacy" DNSTypeLegacy = "legacy"
DNSTypeLegacyRcode = "legacy_rcode" DNSTypeUDP = "udp"
DNSTypeUDP = "udp" DNSTypeTCP = "tcp"
DNSTypeTCP = "tcp" DNSTypeTLS = "tls"
DNSTypeTLS = "tls" DNSTypeHTTPS = "https"
DNSTypeHTTPS = "https" DNSTypeQUIC = "quic"
DNSTypeQUIC = "quic" DNSTypeHTTP3 = "h3"
DNSTypeHTTP3 = "h3" DNSTypeHosts = "hosts"
DNSTypeLocal = "local" DNSTypeLocal = "local"
DNSTypeHosts = "hosts" DNSTypePreDefined = "predefined"
DNSTypeFakeIP = "fakeip" DNSTypeFakeIP = "fakeip"
DNSTypeDHCP = "dhcp" DNSTypeDHCP = "dhcp"
DNSTypeTailscale = "tailscale" DNSTypeTailscale = "tailscale"
) )
const ( const (

View File

@@ -78,8 +78,6 @@ func ProxyDisplayName(proxyType string) string {
return "TUIC" return "TUIC"
case TypeHysteria2: case TypeHysteria2:
return "Hysteria2" return "Hysteria2"
case TypeAnyTLS:
return "AnyTLS"
case TypeSelector: case TypeSelector:
return "Selector" return "Selector"
case TypeURLTest: case TypeURLTest:

View File

@@ -33,7 +33,6 @@ const (
RuleActionTypeHijackDNS = "hijack-dns" RuleActionTypeHijackDNS = "hijack-dns"
RuleActionTypeSniff = "sniff" RuleActionTypeSniff = "sniff"
RuleActionTypeResolve = "resolve" RuleActionTypeResolve = "resolve"
RuleActionTypePredefined = "predefined"
) )
const ( const (

View File

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

View File

@@ -537,7 +537,7 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
Question: []dns.Question{question}, Question: []dns.Question{question},
} }
for _, address := range addresses { for _, address := range addresses {
if address.Is4() && question.Qtype == dns.TypeA { if address.Is4() {
response.Answer = append(response.Answer, &dns.A{ response.Answer = append(response.Answer, &dns.A{
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: question.Name, Name: question.Name,
@@ -547,7 +547,7 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
}, },
A: address.AsSlice(), A: address.AsSlice(),
}) })
} else if address.Is6() && question.Qtype == dns.TypeAAAA { } else {
response.Answer = append(response.Answer, &dns.AAAA{ response.Answer = append(response.Answer, &dns.AAAA{
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: question.Name, Name: question.Name,

View File

@@ -190,8 +190,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
} }
case *R.RuleActionReject: case *R.RuleActionReject:
return nil, currentRule, currentRuleIndex return nil, currentRule, currentRuleIndex
case *R.RuleActionPredefined:
return nil, currentRule, currentRuleIndex
} }
} }
} }
@@ -262,8 +260,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
case C.RuleActionRejectMethodDrop: case C.RuleActionRejectMethodDrop:
return nil, tun.ErrDrop return nil, tun.ErrDrop
} }
case *R.RuleActionPredefined:
return action.Response(message), nil
} }
} }
var responseCheck func(responseAddrs []netip.Addr) bool var responseCheck func(responseAddrs []netip.Addr) bool
@@ -370,8 +366,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
ruleIndex = -1 ruleIndex = -1
for { for {
dnsCtx := adapter.OverrideContext(ctx) dnsCtx := adapter.OverrideContext(ctx)
dnsOptions := options transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &options)
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &dnsOptions)
if rule != nil { if rule != nil {
switch action := rule.Action().(type) { switch action := rule.Action().(type) {
case *R.RuleActionReject: case *R.RuleActionReject:
@@ -381,20 +376,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
case C.RuleActionRejectMethodDrop: case C.RuleActionRejectMethodDrop:
return nil, tun.ErrDrop return nil, tun.ErrDrop
} }
case *R.RuleActionPredefined:
if action.Rcode != mDNS.RcodeSuccess {
err = RcodeError(action.Rcode)
} else {
for _, answer := range action.Answer {
switch record := answer.(type) {
case *mDNS.A:
responseAddrs = append(responseAddrs, M.AddrFromIP(record.A))
case *mDNS.AAAA:
responseAddrs = append(responseAddrs, M.AddrFromIP(record.AAAA))
}
}
}
goto response
} }
} }
var responseCheck func(responseAddrs []netip.Addr) bool var responseCheck func(responseAddrs []netip.Addr) bool
@@ -404,17 +385,16 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
return rule.MatchAddressLimit(metadata) return rule.MatchAddressLimit(metadata)
} }
} }
if dnsOptions.Strategy == C.DomainStrategyAsIS { if options.Strategy == C.DomainStrategyAsIS {
dnsOptions.Strategy = r.defaultDomainStrategy options.Strategy = r.defaultDomainStrategy
} }
responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, dnsOptions, responseCheck) responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, options, responseCheck)
if responseCheck == nil || err == nil { if responseCheck == nil || err == nil {
break break
} }
printResult() printResult()
} }
} }
response:
printResult() printResult()
if len(responseAddrs) > 0 { if len(responseAddrs) > 0 {
r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " ")) r.logger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(responseAddrs), " "))
@@ -449,6 +429,6 @@ func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) {
func (r *Router) ResetNetwork() { func (r *Router) ResetNetwork() {
r.ClearCache() r.ClearCache()
for _, transport := range r.transport.Transports() { for _, transport := range r.transport.Transports() {
transport.Close() transport.Reset()
} }
} }

View File

@@ -81,7 +81,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
func (t *Transport) Close() error { func (t *Transport) Close() error {
for _, transport := range t.transports { for _, transport := range t.transports {
transport.Close() transport.Reset()
} }
if t.interfaceCallback != nil { if t.interfaceCallback != nil {
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
@@ -89,6 +89,12 @@ func (t *Transport) Close() error {
return nil return nil
} }
func (t *Transport) Reset() {
for _, transport := range t.transports {
transport.Reset()
}
}
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
err := t.fetchServers() err := t.fetchServers()
if err != nil { if err != nil {
@@ -246,7 +252,7 @@ func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.So
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr)) transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
} }
for _, transport := range t.transports { for _, transport := range t.transports {
transport.Close() transport.Reset()
} }
t.transports = transports t.transports = transports
return nil return nil

View File

@@ -2,15 +2,12 @@ package hosts
import ( import (
"context" "context"
"net/netip"
"os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/service/filemanager"
mDNS "github.com/miekg/dns" mDNS "github.com/miekg/dns"
) )
@@ -23,49 +20,31 @@ var _ adapter.DNSTransport = (*Transport)(nil)
type Transport struct { type Transport struct {
dns.TransportAdapter dns.TransportAdapter
files []*File files []*File
predefined map[string][]netip.Addr
} }
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) { func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) {
var ( var files []*File
files []*File
predefined = make(map[string][]netip.Addr)
)
if len(options.Path) == 0 { if len(options.Path) == 0 {
files = append(files, NewFile(DefaultPath)) files = append(files, NewFile(DefaultPath))
} else { } else {
for _, path := range options.Path { for _, path := range options.Path {
files = append(files, NewFile(filemanager.BasePath(ctx, os.ExpandEnv(path)))) files = append(files, NewFile(path))
}
}
if options.Predefined != nil {
for _, entry := range options.Predefined.Entries() {
predefined[mDNS.CanonicalName(entry.Key)] = entry.Value
} }
} }
return &Transport{ return &Transport{
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil), TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil),
files: files, files: files,
predefined: predefined,
}, nil }, nil
} }
func (t *Transport) Start(stage adapter.StartStage) error { func (t *Transport) Reset() {
return nil
}
func (t *Transport) Close() error {
return nil
} }
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
question := message.Question[0] question := message.Question[0]
domain := mDNS.CanonicalName(question.Name) domain := dns.FqdnToDomain(question.Name)
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
if addresses, ok := t.predefined[domain]; ok {
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
}
for _, file := range t.files { for _, file := range t.files {
addresses := file.Lookup(domain) addresses := file.Lookup(domain)
if len(addresses) > 0 { if len(addresses) > 0 {

View File

@@ -34,7 +34,7 @@ func (f *File) Lookup(name string) []netip.Addr {
f.access.Lock() f.access.Lock()
defer f.access.Unlock() defer f.access.Unlock()
f.update() f.update()
return f.byName[dns.CanonicalName(name)] return f.byName[name]
} }
func (f *File) update() { func (f *File) update() {

View File

@@ -11,6 +11,6 @@ import (
func TestHosts(t *testing.T) { func TestHosts(t *testing.T) {
t.Parallel() t.Parallel()
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost")) require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost."))
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost")) require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
} }

View File

@@ -10,7 +10,6 @@ import (
"strconv" "strconv"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns"
@@ -92,7 +91,7 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.DNSServerAddressOptions.Build() serverAddr := options.ServerOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 443 serverAddr.Port = 443
} }
@@ -150,17 +149,9 @@ func NewHTTPSRaw(
} }
} }
func (t *HTTPSTransport) Start(stage adapter.StartStage) error { func (t *HTTPSTransport) Reset() {
if stage != adapter.StartStateStart {
return nil
}
return dialer.InitializeDetour(t.dialer)
}
func (t *HTTPSTransport) Close() error {
t.transport.CloseIdleConnections() t.transport.CloseIdleConnections()
t.transport = t.transport.Clone() t.transport = t.transport.Clone()
return nil
} }
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {

View File

@@ -40,12 +40,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
}, nil }, nil
} }
func (t *Transport) Start(stage adapter.StartStage) error { func (t *Transport) Reset() {
return nil
}
func (t *Transport) Close() error {
return nil
} }
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
@@ -144,9 +139,6 @@ func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn stri
} }
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) { func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
if server.Port == 0 {
server.Port = 53
}
var networks []string var networks []string
if useTCP { if useTCP {
networks = []string{N.NetworkTCP} networks = []string{N.NetworkTCP}

View File

@@ -58,12 +58,8 @@ func (f *FallbackTransport) Start(stage adapter.StartStage) error {
return nil return nil
} }
func (f *FallbackTransport) Close() error {
return nil
}
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
if !f.fallback { if f.fallback {
return f.DNSTransport.Exchange(ctx, message) return f.DNSTransport.Exchange(ctx, message)
} }
question := message.Question[0] question := message.Question[0]

View File

@@ -0,0 +1,83 @@
package transport
import (
"context"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
mDNS "github.com/miekg/dns"
)
var _ adapter.DNSTransport = (*PredefinedTransport)(nil)
func RegisterPredefined(registry *dns.TransportRegistry) {
dns.RegisterTransport[option.PredefinedDNSServerOptions](registry, C.DNSTypePreDefined, NewPredefined)
}
type PredefinedTransport struct {
dns.TransportAdapter
responses []*predefinedResponse
}
type predefinedResponse struct {
questions []mDNS.Question
answer *mDNS.Msg
}
func NewPredefined(ctx context.Context, logger log.ContextLogger, tag string, options option.PredefinedDNSServerOptions) (adapter.DNSTransport, error) {
var responses []*predefinedResponse
for _, response := range options.Responses {
questions, msg, err := response.Build()
if err != nil {
return nil, err
}
responses = append(responses, &predefinedResponse{
questions: questions,
answer: msg,
})
}
if len(responses) == 0 {
return nil, E.New("empty predefined responses")
}
return &PredefinedTransport{
TransportAdapter: dns.NewTransportAdapter(C.DNSTypePreDefined, tag, nil),
responses: responses,
}, nil
}
func (t *PredefinedTransport) Reset() {
}
func (t *PredefinedTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
for _, response := range t.responses {
for _, question := range response.questions {
if func() bool {
if question.Name == "" && question.Qtype == mDNS.TypeNone {
return true
} else if question.Name == "" {
return common.Any(message.Question, func(it mDNS.Question) bool {
return it.Qtype == question.Qtype
})
} else if question.Qtype == mDNS.TypeNone {
return common.Any(message.Question, func(it mDNS.Question) bool {
return it.Name == question.Name
})
} else {
return common.Contains(message.Question, question)
}
}() {
copyAnswer := *response.answer
copyAnswer.Id = message.Id
copyAnswer.Question = message.Question
return &copyAnswer, nil
}
}
}
return nil, dns.RcodeNameError
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
sHTTP "github.com/sagernet/sing/protocol/http" sHTTP "github.com/sagernet/sing/protocol/http"
@@ -88,7 +89,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.DNSServerAddressOptions.Build() serverAddr := options.ServerOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 443 serverAddr.Port = 443
} }
@@ -100,7 +101,8 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
headers: headers, headers: headers,
transport: &http3.Transport{ transport: &http3.Transport{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) { Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) {
conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, serverAddr) destinationAddr := M.ParseSocksaddr(addr)
conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, destinationAddr)
if dialErr != nil { if dialErr != nil {
return nil, dialErr return nil, dialErr
} }
@@ -111,12 +113,8 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
}, nil }, nil
} }
func (t *HTTP3Transport) Start(stage adapter.StartStage) error { func (t *HTTP3Transport) Reset() {
return nil t.transport.Close()
}
func (t *HTTP3Transport) Close() error {
return t.transport.Close()
} }
func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {

View File

@@ -54,7 +54,7 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
if len(tlsConfig.NextProtos()) == 0 { if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"doq"}) tlsConfig.SetNextProtos([]string{"doq"})
} }
serverAddr := options.DNSServerAddressOptions.Build() serverAddr := options.ServerOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 853 serverAddr.Port = 853
} }
@@ -68,18 +68,13 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
}, nil }, nil
} }
func (t *Transport) Start(stage adapter.StartStage) error { func (t *Transport) Reset() {
return nil
}
func (t *Transport) Close() error {
t.access.Lock() t.access.Lock()
defer t.access.Unlock() defer t.access.Unlock()
connection := t.connection connection := t.connection
if connection != nil { if connection != nil {
connection.CloseWithError(0, "") connection.CloseWithError(0, "")
} }
return nil
} }
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {

View File

@@ -6,7 +6,6 @@ import (
"io" "io"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@@ -36,7 +35,7 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.DNSServerAddressOptions.Build() serverAddr := options.ServerOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 53 serverAddr.Port = 53
} }
@@ -47,15 +46,7 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
}, nil }, nil
} }
func (t *TCPTransport) Start(stage adapter.StartStage) error { func (t *TCPTransport) Reset() {
if stage != adapter.StartStateStart {
return nil
}
return dialer.InitializeDetour(t.dialer)
}
func (t *TCPTransport) Close() error {
return nil
} }
func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {

View File

@@ -5,7 +5,6 @@ import (
"sync" "sync"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns"
@@ -53,7 +52,7 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.DNSServerAddressOptions.Build() serverAddr := options.ServerOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 853 serverAddr.Port = 853
} }
@@ -66,21 +65,13 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
}, nil }, nil
} }
func (t *TLSTransport) Start(stage adapter.StartStage) error { func (t *TLSTransport) Reset() {
if stage != adapter.StartStateStart {
return nil
}
return dialer.InitializeDetour(t.dialer)
}
func (t *TLSTransport) Close() error {
t.access.Lock() t.access.Lock()
defer t.access.Unlock() defer t.access.Unlock()
for connection := t.connections.Front(); connection != nil; connection = connection.Next() { for connection := t.connections.Front(); connection != nil; connection = connection.Next() {
connection.Value.Close() connection.Value.Close()
} }
t.connections.Init() t.connections.Init()
return nil
} }
func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {

View File

@@ -7,7 +7,6 @@ import (
"sync" "sync"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@@ -43,7 +42,7 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.DNSServerAddressOptions.Build() serverAddr := options.ServerOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 53 serverAddr.Port = 53
} }
@@ -65,19 +64,11 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
} }
} }
func (t *UDPTransport) Start(stage adapter.StartStage) error { func (t *UDPTransport) Reset() {
if stage != adapter.StartStateStart {
return nil
}
return dialer.InitializeDetour(t.dialer)
}
func (t *UDPTransport) Close() error {
t.access.Lock() t.access.Lock()
defer t.access.Unlock() defer t.access.Unlock()
close(t.done) close(t.done)
t.done = make(chan struct{}) t.done = make(chan struct{})
return nil
} }
func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
@@ -119,6 +110,13 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
conn.access.Lock() conn.access.Lock()
delete(conn.callbacks, messageId) delete(conn.callbacks, messageId)
conn.access.Unlock() conn.access.Unlock()
callback.access.Lock()
select {
case <-callback.done:
default:
close(callback.done)
}
callback.access.Unlock()
}() }()
rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes())
if err != nil { if err != nil {

View File

@@ -20,10 +20,9 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
return dialer.NewDefaultOutbound(ctx), nil return dialer.NewDefaultOutbound(ctx), nil
} else { } else {
return dialer.NewWithOptions(dialer.Options{ return dialer.NewWithOptions(dialer.Options{
Context: ctx, Context: ctx,
Options: options.DialerOptions, Options: options.DialerOptions,
DirectResolver: true, DirectResolver: true,
LegacyDNSDialer: options.Legacy,
}) })
} }
} }
@@ -44,11 +43,10 @@ func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions)
return transportDialer, nil return transportDialer, nil
} else { } else {
return dialer.NewWithOptions(dialer.Options{ return dialer.NewWithOptions(dialer.Options{
Context: ctx, Context: ctx,
Options: options.DialerOptions, Options: options.DialerOptions,
RemoteIsDomain: options.ServerIsDomain(), RemoteIsDomain: options.ServerIsDomain(),
DirectResolver: true, DirectResolver: true,
LegacyDNSDialer: options.Legacy,
}) })
} }
} }

View File

@@ -59,9 +59,6 @@ func (m *TransportManager) Start(stage adapter.StartStage) error {
transports := m.transports transports := m.transports
m.access.Unlock() m.access.Unlock()
if stage == adapter.StartStateStart { if stage == adapter.StartStateStart {
if m.defaultTag != "" && m.defaultTransport == nil {
return E.New("default DNS server not found: ", m.defaultTag)
}
return m.startTransports(m.transports) return m.startTransports(m.transports)
} else { } else {
for _, outbound := range transports { for _, outbound := range transports {
@@ -228,7 +225,7 @@ func (m *TransportManager) Remove(tag string) error {
} }
} }
if started { if started {
transport.Close() transport.Reset()
} }
return nil return nil
} }

View File

@@ -2,63 +2,6 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.12.0-alpha.19
* Update gVisor to 20250319.0
* Fixes and improvements
#### 1.12.0-alpha.18
* Add wildcard SNI support for ShadowTLS inbound **1**
* Fixes and improvements
**1**:
See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).
#### 1.12.0-alpha.17
* Add NTP sniffer **1**
* Fixes and improvements
**1**:
See [Protocol Sniff](/configuration/route/sniff/).
#### 1.12.0-alpha.16
* Update `domain_resolver` behavior **1**
* Fixes and improvements
**1**:
`route.default_domain_resolver` or `outbound.domain_resolver` is now optional when only one DNS server is configured.
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
### 1.11.5
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
#### 1.12.0-alpha.13
* Move `predefined` DNS server to DNS rule action **1**
* Fixes and improvements
**1**:
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
### 1.11.4
* Fixes and improvements
#### 1.12.0-alpha.11
* Fixes and improvements
#### 1.12.0-alpha.10 #### 1.12.0-alpha.10
* Add AnyTLS protocol **1** * Add AnyTLS protocol **1**

View File

@@ -7,10 +7,6 @@ icon: material/apple
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
platform-specific function implementation, such as TUN transparent proxy implementation. platform-specific function implementation, such as TUN transparent proxy implementation.
!!! failure ""
We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected).
## :material-graph: Requirements ## :material-graph: Requirements
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+ * iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+

View File

@@ -4,7 +4,6 @@ icon: material/alert-decagram
!!! quote "Changes in sing-box 1.12.0" !!! quote "Changes in sing-box 1.12.0"
:material-plus: [ip_accept_any](#ip_accept_any)
:material-delete-clock: [outbound](#outbound) :material-delete-clock: [outbound](#outbound)
!!! quote "Changes in sing-box 1.11.0" !!! quote "Changes in sing-box 1.11.0"
@@ -78,6 +77,15 @@ icon: material/alert-decagram
"domain_regex": [ "domain_regex": [
"^stun\\..+" "^stun\\..+"
], ],
"geosite": [
"cn"
],
"source_geoip": [
"private"
],
"geoip": [
"cn"
],
"source_ip_cidr": [ "source_ip_cidr": [
"10.0.0.0/24", "10.0.0.0/24",
"192.168.0.1" "192.168.0.1"
@@ -88,7 +96,6 @@ icon: material/alert-decagram
"192.168.0.1" "192.168.0.1"
], ],
"ip_is_private": false, "ip_is_private": false,
"ip_accept_any": false,
"source_port": [ "source_port": [
12345 12345
], ],
@@ -140,6 +147,8 @@ icon: material/alert-decagram
"geoip-cn", "geoip-cn",
"geosite-cn" "geosite-cn"
], ],
// deprecated
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false, "rule_set_ip_cidr_match_source": false,
"rule_set_ip_cidr_accept_empty": false, "rule_set_ip_cidr_accept_empty": false,
"invert": false, "invert": false,
@@ -147,20 +156,7 @@ icon: material/alert-decagram
"direct" "direct"
], ],
"action": "route", "action": "route",
"server": "local", "server": "local"
// Deprecated
"rule_set_ipcidr_match_source": false,
"geosite": [
"cn"
],
"source_geoip": [
"private"
],
"geoip": [
"cn"
]
}, },
{ {
"type": "logical", "type": "logical",
@@ -455,9 +451,7 @@ Only takes effect for address requests (A/AAAA/HTTPS). When the query results do
#### geoip #### geoip
!!! failure "Removed in sing-box 1.12.0" !!! question "Since sing-box 1.9.0"
GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
Match GeoIP with query response. Match GeoIP with query response.
@@ -479,12 +473,6 @@ Match private IP with query response.
Make `ip_cidr` rules in rule-sets accept empty query response. Make `ip_cidr` rules in rule-sets accept empty query response.
#### ip_accept_any
!!! question "Since sing-box 1.12.0"
Match any IP with query response.
### Logical Fields ### Logical Fields
#### type #### type

View File

@@ -4,7 +4,6 @@ icon: material/alert-decagram
!!! quote "sing-box 1.12.0 中的更改" !!! quote "sing-box 1.12.0 中的更改"
:material-plus: [ip_accept_any](#ip_accept_any)
:material-delete-clock: [outbound](#outbound) :material-delete-clock: [outbound](#outbound)
!!! quote "sing-box 1.11.0 中的更改" !!! quote "sing-box 1.11.0 中的更改"
@@ -78,6 +77,15 @@ icon: material/alert-decagram
"domain_regex": [ "domain_regex": [
"^stun\\..+" "^stun\\..+"
], ],
"geosite": [
"cn"
],
"source_geoip": [
"private"
],
"geoip": [
"cn"
],
"source_ip_cidr": [ "source_ip_cidr": [
"10.0.0.0/24", "10.0.0.0/24",
"192.168.0.1" "192.168.0.1"
@@ -88,7 +96,6 @@ icon: material/alert-decagram
"192.168.0.1" "192.168.0.1"
], ],
"ip_is_private": false, "ip_is_private": false,
"ip_accept_any": false,
"source_port": [ "source_port": [
12345 12345
], ],
@@ -140,6 +147,8 @@ icon: material/alert-decagram
"geoip-cn", "geoip-cn",
"geosite-cn" "geosite-cn"
], ],
// 已弃用
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false, "rule_set_ip_cidr_match_source": false,
"rule_set_ip_cidr_accept_empty": false, "rule_set_ip_cidr_accept_empty": false,
"invert": false, "invert": false,
@@ -147,19 +156,7 @@ icon: material/alert-decagram
"direct" "direct"
], ],
"action": "route", "action": "route",
"server": "local", "server": "local"
// 已弃用
"rule_set_ipcidr_match_source": false,
"geosite": [
"cn"
],
"source_geoip": [
"private"
],
"geoip": [
"cn"
]
}, },
{ {
"type": "logical", "type": "logical",
@@ -235,17 +232,17 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### geosite #### geosite
!!! failure "已在 sing-box 1.12.0 中被移除" !!! failure "已在 sing-box 1.8.0 废弃"
GeoSite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。 Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
匹配 Geosite。 匹配 Geosite。
#### source_geoip #### source_geoip
!!! failure "已在 sing-box 1.12.0 中被移除" !!! failure "已在 sing-box 1.8.0 废弃"
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。 GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
匹配源 GeoIP。 匹配源 GeoIP。
@@ -454,10 +451,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### geoip #### geoip
!!! failure "已在 sing-box 1.12.0 中被移除" !!! question "自 sing-box 1.9.0 "
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
与查询响应匹配 GeoIP。 与查询响应匹配 GeoIP。
@@ -473,12 +467,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
与查询响应匹配非公开 IP。 与查询响应匹配非公开 IP。
#### ip_accept_any
!!! question "自 sing-box 1.12.0 起"
匹配任意 IP。
#### rule_set_ip_cidr_accept_empty #### rule_set_ip_cidr_accept_empty
!!! question "自 sing-box 1.10.0 起" !!! question "自 sing-box 1.10.0 起"

View File

@@ -4,8 +4,7 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.12.0" !!! quote "Changes in sing-box 1.12.0"
:material-plus: [strategy](#strategy) :material-plus: [strategy](#strategy)
:material-plus: [predefined](#predefined)
!!! question "Since sing-box 1.11.0" !!! question "Since sing-box 1.11.0"
@@ -32,8 +31,6 @@ Tag of target server.
#### strategy #### strategy
!!! question "Since sing-box 1.12.0"
Set domain strategy for this query. Set domain strategy for this query.
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
@@ -72,7 +69,7 @@ Will overrides `dns.client_subnet`.
```json ```json
{ {
"action": "reject", "action": "reject",
"method": "", "method": "default", // default
"no_drop": false "no_drop": false
} }
``` ```
@@ -84,61 +81,8 @@ Will overrides `dns.client_subnet`.
- `default`: Reply with NXDOMAIN. - `default`: Reply with NXDOMAIN.
- `drop`: Drop the request. - `drop`: Drop the request.
`default` will be used by default.
#### no_drop #### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop. Not available when `method` is set to drop.
### predefined
!!! question "Since sing-box 1.12.0"
```json
{
"action": "predefined",
"rcode": "",
"answer": [],
"ns": [],
"extra": []
}
```
`predefined` responds with predefined DNS records.
#### rcode
The response code.
| Value | Value in the legacy rcode server | Description |
|------------|----------------------------------|-----------------|
| `NOERROR` | `success` | Ok |
| `FORMERR` | `format_error` | Bad request |
| `SERVFAIL` | `server_failure` | Server failure |
| `NXDOMAIN` | `name_error` | Not found |
| `NOTIMP` | `not_implemented` | Not implemented |
| `REFUSED` | `refused` | Refused |
`NOERROR` will be used by default.
#### answer
List of text DNS record to respond as answers.
Examples:
| Record Type | Example |
|-------------|-------------------------------|
| `A` | `localhost. IN A 127.0.0.1` |
| `AAAA` | `localhost. IN AAAA ::1` |
| `TXT` | `localhost. IN TXT \"Hello\"` |
#### ns
List of text DNS record to respond as name servers.
#### extra
List of text DNS record to respond as extra records.

View File

@@ -4,8 +4,7 @@ icon: material/new-box
!!! quote "sing-box 1.12.0 中的更改" !!! quote "sing-box 1.12.0 中的更改"
:material-plus: [strategy](#strategy) :material-plus: [strategy](#strategy)
:material-plus: [predefined](#predefined)
!!! question "自 sing-box 1.11.0 起" !!! question "自 sing-box 1.11.0 起"
@@ -13,8 +12,9 @@ icon: material/new-box
```json ```json
{ {
"action": "route", // 默认 "action": "route", // 默认
"server": "", "server": "",
"strategy": "", "strategy": "",
"disable_cache": false, "disable_cache": false,
"rewrite_ttl": null, "rewrite_ttl": null,
@@ -32,8 +32,6 @@ icon: material/new-box
#### strategy #### strategy
!!! question "自 sing-box 1.12.0 起"
为此查询设置域名策略。 为此查询设置域名策略。
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only` 可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
@@ -72,7 +70,7 @@ icon: material/new-box
```json ```json
{ {
"action": "reject", "action": "reject",
"method": "", "method": "default", // default
"no_drop": false "no_drop": false
} }
``` ```
@@ -84,61 +82,8 @@ icon: material/new-box
- `default`: 返回 NXDOMAIN。 - `default`: 返回 NXDOMAIN。
- `drop`: 丢弃请求。 - `drop`: 丢弃请求。
默认使用 `defualt`
#### no_drop #### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop` 如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。 `method` 设为 `drop` 时不可用。
### predefined
!!! question "自 sing-box 1.12.0 起"
```json
{
"action": "predefined",
"rcode": "",
"answer": [],
"ns": [],
"extra": []
}
```
`predefined` 以预定义的 DNS 记录响应。
#### rcode
响应码。
| 值 | 旧 rcode DNS 服务器中的值 | 描述 |
|------------|--------------------|-----------------|
| `NOERROR` | `success` | Ok |
| `FORMERR` | `format_error` | Bad request |
| `SERVFAIL` | `server_failure` | Server failure |
| `NXDOMAIN` | `name_error` | Not found |
| `NOTIMP` | `not_implemented` | Not implemented |
| `REFUSED` | `refused` | Refused |
默认使用 `NOERROR`
#### answer
用于作为回答响应的文本 DNS 记录列表。
例子:
| 记录类型 | 例子 |
|--------|-------------------------------|
| `A` | `localhost. IN A 127.0.0.1` |
| `AAAA` | `localhost. IN AAAA ::1` |
| `TXT` | `localhost. IN TXT \"Hello\"` |
#### ns
用于作为名称服务器响应的文本 DNS 记录列表。
#### extra
用于作为额外记录响应的文本 DNS 记录列表。

View File

@@ -1,96 +0,0 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# Hosts
### Structure
```json
{
"dns": {
"servers": [
{
"type": "hosts",
"tag": "",
"path": [],
"predefined": {}
}
]
}
}
```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
### Fields
#### path
List of paths to hosts files.
`/etc/hosts` is used by default.
`C:\Windows\System32\Drivers\etc\hosts` is used by default on Windows.
Example:
```json
{
// "path": "/etc/hosts"
"path": [
"/etc/hosts",
"$HOME/.hosts"
]
}
```
#### predefined
Predefined hosts.
Example:
```json
{
"predefined": {
"www.google.com": "127.0.0.1",
"localhost": [
"127.0.0.1",
"::1"
]
}
}
```
### Examples
=== "Use hosts if available"
```json
{
"dns": {
"servers": [
{
...
},
{
"type": "hosts",
"tag": "hosts"
}
],
"rules": [
{
"ip_accept_any": true,
"server": "hosts"
}
]
}
}
```

View File

@@ -0,0 +1,93 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.12.0"
# Predefined
### Structure
```json
{
"dns": {
"servers": [
{
"type": "predefined",
"tag": "",
"responses": []
}
]
}
}
```
### Fields
#### responses
==Required==
List of [Response](#response-structure).
### Response Structure
```json
{
"query": [],
"query_type": [],
"rcode": "",
"answer": [],
"ns": [],
"extra": []
}
```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
### Response Fields
#### query
List of domain name to match.
#### query_type
List of query type to match.
#### rcode
The response code.
| Value | Value in the legacy rcode server | Description |
|------------|----------------------------------|-----------------|
| `NOERROR` | `success` | Ok |
| `FORMERR` | `format_error` | Bad request |
| `SERVFAIL` | `server_failure` | Server failure |
| `NXDOMAIN` | `name_error` | Not found |
| `NOTIMP` | `not_implemented` | Not implemented |
| `REFUSED` | `refused` | Refused |
`NOERROR` will be used by default.
#### answer
List of text DNS record to respond as answers.
Examples:
| Record Type | Example |
|-------------|-------------------------------|
| `A` | `localhost. IN A 127.0.0.1` |
| `AAAA` | `localhost. IN AAAA ::1` |
| `TXT` | `localhost. IN TXT \"Hello\"` |
#### ns
List of text DNS record to respond as name servers.
#### extra
List of text DNS record to respond as extra records.

View File

@@ -4,7 +4,7 @@ icon: material/new-box
!!! question "Since sing-box 1.12.0" !!! question "Since sing-box 1.12.0"
# UDP # TCP
### Structure ### Structure

View File

@@ -1,11 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [wildcard_sni](#wildcard_sni)
### Structure ### Structure
```json ```json
@@ -37,8 +29,7 @@ icon: material/new-box
... // Dial Fields ... // Dial Fields
} }
}, },
"strict_mode": false, "strict_mode": false
"wildcard_sni": ""
} }
``` ```
@@ -64,6 +55,7 @@ ShadowTLS password.
Only available in the ShadowTLS protocol 2. Only available in the ShadowTLS protocol 2.
#### users #### users
ShadowTLS users. ShadowTLS users.
@@ -74,8 +66,6 @@ Only available in the ShadowTLS protocol 3.
==Required== ==Required==
When `wildcard_sni` is configured to `all`, the server address is optional.
Handshake server address and [Dial Fields](/configuration/shared/dial/). Handshake server address and [Dial Fields](/configuration/shared/dial/).
#### handshake_for_server_name #### handshake_for_server_name
@@ -89,19 +79,3 @@ Only available in the ShadowTLS protocol 2/3.
ShadowTLS strict mode. ShadowTLS strict mode.
Only available in the ShadowTLS protocol 3. Only available in the ShadowTLS protocol 3.
#### wildcard_sni
!!! question "Since sing-box 1.12.0"
ShadowTLS wildcard SNI mode.
Available values are:
* `off`: (default) Disabled.
* `authed`: Authenticated connections will have their destination overwritten to `(servername):443`
* `all`: All connections will have their destination overwritten to `(servername):443`
Additionally, connections matching `handshake_for_server_name` are not affected.
Only available in the ShadowTLS protocol 3.

View File

@@ -1,11 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [wildcard_sni](#wildcard_sni)
### 结构 ### 结构
```json ```json
@@ -37,8 +29,7 @@ icon: material/new-box
... // 拨号字段 ... // 拨号字段
} }
}, },
"strict_mode": false, "strict_mode": false
"wildcard_sni": ""
} }
``` ```
@@ -89,19 +80,3 @@ ShadowTLS 用户。
ShadowTLS 严格模式。 ShadowTLS 严格模式。
仅在 ShadowTLS 协议版本 3 中可用。 仅在 ShadowTLS 协议版本 3 中可用。
#### wildcard_sni
!!! question "自 sing-box 1.12.0 起"
ShadowTLS 通配符 SNI 模式。
可用值:
* `off`:(默认)禁用。
* `authed`:已认证的连接的目标将被重写为 `(servername):443`
* `all`:所有连接的目标将被重写为 `(servername):443`
此外,匹配 `handshake_for_server_name` 的连接不受影响。
仅在 ShadowTLS 协议 3 中可用。

View File

@@ -16,7 +16,6 @@ icon: material/new-box
"password": "8JCsPssfgS8tiRwiMlhARg==", "password": "8JCsPssfgS8tiRwiMlhARg==",
"idle_session_check_interval": "30s", "idle_session_check_interval": "30s",
"idle_session_timeout": "30s", "idle_session_timeout": "30s",
"min_idle_session": 5,
"tls": {}, "tls": {},
... // Dial Fields ... // Dial Fields
@@ -51,10 +50,6 @@ Interval checking for idle sessions. Default: 30s.
In the check, close sessions that have been idle for longer than this. Default: 30s. In the check, close sessions that have been idle for longer than this. Default: 30s.
#### min_idle_session
In the check, at least the first `n` idle sessions are kept open. Default value: `n`=0
#### tls #### tls
==Required== ==Required==

View File

@@ -16,7 +16,6 @@ icon: material/new-box
"password": "8JCsPssfgS8tiRwiMlhARg==", "password": "8JCsPssfgS8tiRwiMlhARg==",
"idle_session_check_interval": "30s", "idle_session_check_interval": "30s",
"idle_session_timeout": "30s", "idle_session_timeout": "30s",
"min_idle_session": 5,
"tls": {}, "tls": {},
... // 拨号字段 ... // 拨号字段
@@ -51,10 +50,6 @@ AnyTLS 密码。
在检查中关闭闲置时间超过此值的会话。默认值30秒。 在检查中关闭闲置时间超过此值的会话。默认值30秒。
#### min_idle_session
在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0
#### tls #### tls
==必填== ==必填==

View File

@@ -22,7 +22,6 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
| UDP | `dtls` | / | / | | UDP | `dtls` | / | / |
| TCP | `ssh` | / | SSH Client Name | | TCP | `ssh` | / | SSH Client Name |
| TCP | `rdp` | / | / | | TCP | `rdp` | / | / |
| UDP | `ntp` | / | / |
| QUIC Client | Type | | QUIC Client | Type |
|:------------------------:|:----------:| |:------------------------:|:----------:|

View File

@@ -22,7 +22,6 @@
| UDP | `dtls` | / | / | | UDP | `dtls` | / | / |
| TCP | `ssh` | / | SSH 客户端名称 | | TCP | `ssh` | / | SSH 客户端名称 |
| TCP | `rdp` | / | / | | TCP | `rdp` | / | / |
| UDP | `ntp` | / | / |
| QUIC 客户端 | 类型 | | QUIC 客户端 | 类型 |
|:------------------------:|:----------:| |:------------------------:|:----------:|

View File

@@ -5,8 +5,7 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.12.0" !!! quote "Changes in sing-box 1.12.0"
:material-plus: [domain_resolver](#domain_resolver) :material-plus: [domain_resolver](#domain_resolver)
:material-delete-clock: [domain_strategy](#domain_strategy) :material-delete-clock: [domain_strategy](#domain_strategy)
:material-plus: [netns](#netns)
!!! quote "Changes in sing-box 1.11.0" !!! quote "Changes in sing-box 1.11.0"
@@ -19,25 +18,24 @@ icon: material/new-box
```json ```json
{ {
"detour": "", "detour": "upstream-out",
"bind_interface": "", "bind_interface": "en0",
"inet4_bind_address": "", "inet4_bind_address": "0.0.0.0",
"inet6_bind_address": "", "inet6_bind_address": "::",
"routing_mark": 0, "routing_mark": 1234,
"reuse_addr": false, "reuse_addr": false,
"connect_timeout": "", "connect_timeout": "5s",
"tcp_fast_open": false, "tcp_fast_open": false,
"tcp_multi_path": false, "tcp_multi_path": false,
"udp_fragment": false, "udp_fragment": false,
"netns": "",
"domain_resolver": "", // or {} "domain_resolver": "", // or {}
"network_strategy": "", "network_strategy": "default",
"network_type": [], "network_type": [],
"fallback_network_type": [], "fallback_network_type": [],
"fallback_delay": "", "fallback_delay": "300ms",
// Deprecated // Deprecated
"domain_strategy": "" "domain_strategy": "prefer_ipv6"
} }
``` ```
@@ -77,15 +75,6 @@ Set netfilter routing mark.
Reuse listener address. Reuse listener address.
#### connect_timeout
Connect timeout, in golang's Duration format.
A duration string is a possibly signed sequence of
decimal numbers, each with optional fraction and a unit suffix,
such as "300ms", "-1.5h" or "2h45m".
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
#### tcp_fast_open #### tcp_fast_open
Enable TCP Fast Open. Enable TCP Fast Open.
@@ -102,15 +91,14 @@ Enable TCP Multi Path.
Enable UDP fragmentation. Enable UDP fragmentation.
#### netns #### connect_timeout
!!! question "Since sing-box 1.12.0" Connect timeout, in golang's Duration format.
!!! quote "" A duration string is a possibly signed sequence of
decimal numbers, each with optional fraction and a unit suffix,
Only supported on Linux. such as "300ms", "-1.5h" or "2h45m".
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Set network namespace, name or path.
#### domain_resolver #### domain_resolver
@@ -118,10 +106,6 @@ Set network namespace, name or path.
`outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0. `outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0.
!!! info ""
`domain_resolver` or `route.default_domain_resolver` is optional when only one DNS server is configured.
Set domain resolver to use for resolving domain names. Set domain resolver to use for resolving domain names.
This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field. This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field.

View File

@@ -5,8 +5,7 @@ icon: material/new-box
!!! quote "sing-box 1.12.0 中的更改" !!! quote "sing-box 1.12.0 中的更改"
:material-plus: [domain_resolver](#domain_resolver) :material-plus: [domain_resolver](#domain_resolver)
:material-delete-clock: [domain_strategy](#domain_strategy) :material-delete-clock: [domain_strategy](#domain_strategy)
:material-plus: [netns](#netns)
!!! quote "sing-box 1.11.0 中的更改" !!! quote "sing-box 1.11.0 中的更改"
@@ -19,26 +18,25 @@ icon: material/new-box
```json ```json
{ {
"detour": "", "detour": "upstream-out",
"bind_interface": "", "bind_interface": "en0",
"inet4_bind_address": "", "inet4_bind_address": "0.0.0.0",
"inet6_bind_address": "", "inet6_bind_address": "::",
"routing_mark": 0, "routing_mark": 1234,
"reuse_addr": false, "reuse_addr": false,
"connect_timeout": "", "connect_timeout": "5s",
"tcp_fast_open": false, "tcp_fast_open": false,
"tcp_multi_path": false, "tcp_multi_path": false,
"udp_fragment": false, "udp_fragment": false,
"netns": "",
"domain_resolver": "", // 或 {} "domain_resolver": "", // 或 {}
"network_strategy": "", "network_strategy": "",
"network_type": [], "network_type": [],
"fallback_network_type": [], "fallback_network_type": [],
"fallback_delay": "", "fallback_delay": "300ms",
// 废弃的 // 废弃的
"domain_strategy": "" "domain_strategy": "prefer_ipv6"
} }
``` ```
@@ -78,13 +76,6 @@ icon: material/new-box
重用监听地址。 重用监听地址。
#### connect_timeout
连接超时,采用 golang 的 Duration 格式。
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
#### tcp_fast_open #### tcp_fast_open
启用 TCP Fast Open。 启用 TCP Fast Open。
@@ -101,15 +92,12 @@ icon: material/new-box
启用 UDP 分段。 启用 UDP 分段。
#### netns #### connect_timeout
!!! question "自 sing-box 1.12.0 起" 连接超时,采用 golang 的 Duration 格式。
!!! quote "" 持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
仅支持 Linux。
设置网络命名空间,名称或路径。
#### domain_resolver #### domain_resolver
@@ -117,10 +105,6 @@ icon: material/new-box
`outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项。 `outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项。
!!! info ""
当只有一个 DNS 服务器已配置时,`domain_resolver``route.default_domain_resolver` 是可选的。
用于设置解析域名的域名解析器。 用于设置解析域名的域名解析器。
此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。 此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。

View File

@@ -1,11 +1,7 @@
--- ---
icon: material/new-box icon: material/delete-clock
--- ---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [netns](#netns)
!!! quote "Changes in sing-box 1.11.0" !!! quote "Changes in sing-box 1.11.0"
:material-delete-clock: [sniff](#sniff) :material-delete-clock: [sniff](#sniff)
@@ -18,18 +14,17 @@ icon: material/new-box
```json ```json
{ {
"listen": "", "listen": "::",
"listen_port": 0, "listen_port": 5353,
"tcp_fast_open": false, "tcp_fast_open": false,
"tcp_multi_path": false, "tcp_multi_path": false,
"udp_fragment": false, "udp_fragment": false,
"udp_timeout": "", "udp_timeout": "5m",
"netns": "", "detour": "another-in",
"detour": "",
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_timeout": "", "sniff_timeout": "300ms",
"domain_strategy": "", "domain_strategy": "prefer_ipv6",
"udp_disable_domain_unmapping": false "udp_disable_domain_unmapping": false
} }
``` ```
@@ -77,16 +72,6 @@ UDP NAT expiration time.
`5m` will be used by default. `5m` will be used by default.
#### netns
!!! question "Since sing-box 1.12.0"
!!! quote ""
Only supported on Linux.
Set network namespace, name or path.
#### detour #### detour
If set, connections will be forwarded to the specified inbound. If set, connections will be forwarded to the specified inbound.

View File

@@ -1,11 +1,7 @@
--- ---
icon: material/new-box icon: material/delete-clock
--- ---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [netns](#netns)
!!! quote "sing-box 1.11.0 中的更改" !!! quote "sing-box 1.11.0 中的更改"
:material-delete-clock: [sniff](#sniff) :material-delete-clock: [sniff](#sniff)
@@ -18,18 +14,17 @@ icon: material/new-box
```json ```json
{ {
"listen": "", "listen": "::",
"listen_port": 0, "listen_port": 5353,
"tcp_fast_open": false, "tcp_fast_open": false,
"tcp_multi_path": false, "tcp_multi_path": false,
"udp_fragment": false, "udp_fragment": false,
"udp_timeout": "", "udp_timeout": "5m",
"netns": "", "detour": "another-in",
"detour": "",
"sniff": false, "sniff": false,
"sniff_override_destination": false, "sniff_override_destination": false,
"sniff_timeout": "", "sniff_timeout": "300ms",
"domain_strategy": "", "domain_strategy": "prefer_ipv6",
"udp_disable_domain_unmapping": false "udp_disable_domain_unmapping": false
} }
``` ```
@@ -78,16 +73,6 @@ UDP NAT 过期时间。
默认使用 `5m` 默认使用 `5m`
#### netns
!!! question "自 sing-box 1.12.0 起"
!!! quote ""
仅支持 Linux。
设置网络命名空间,名称或路径。
#### detour #### detour
如果设置,连接将被转发到指定的入站。 如果设置,连接将被转发到指定的入站。

View File

@@ -31,11 +31,12 @@ The protocol version, `1` or `2`.
### Application support ### Application support
| Project | UoT v1 | UoT v2 | | Project | UoT v1 | UoT v2 |
|--------------|----------------------|----------------------| |--------------|----------------------|-------------------------------------------------------------------------------------------------------------------|
| sing-box | v0 (2022/08/11) | v1.2-beta9 | | sing-box | v0 (2022/08/11) | v1.2-beta9 |
| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) | | Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) |
| Shadowrocket | v2.2.12 (2022/08/13) | / | | Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) |
| Shadowrocket | v2.2.12 (2022/08/13) | / |
### Protocol details ### Protocol details
@@ -49,13 +50,7 @@ The client requests the magic address to the upper layer proxy protocol to indic
|------|----------|-------|--------|----------| |------|----------|-------|--------|----------|
| u8 | variable | u16be | u16be | variable | | u8 | variable | u16be | u16be | variable |
**ATYP / address / port**: Uses the SOCKS address format, but with different address types: **ATYP / address / port**: Uses the SOCKS address format.
| ATYP | Address type |
|--------|--------------|
| `0x00` | IPv4 Address |
| `0x01` | IPv6 Address |
| `0x02` | Domain Name |
#### Protocol version 2 #### Protocol version 2

View File

@@ -17,15 +17,6 @@ DNS 服务器已重构,
且可被拨号字段代替, 且可被拨号字段代替,
参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver). 参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
#### 旧的 ECH 字段
ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支持后量子对等证书签名方案,
因此 `pq_signature_schemes_enabled` 已被弃用且不再工作。
另外,`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。
相关字段将在 sing-box 1.13.0 中被移除。
## 1.11.0 ## 1.11.0
#### 旧的特殊出站 #### 旧的特殊出站

View File

@@ -58,6 +58,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
| `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | | `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | | `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
| `with_embedded_tor` (CGO required) | :material-close: | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | | `with_embedded_tor` (CGO required) | :material-close: | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) | | `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) |
It is not recommended to change the default build tag list unless you really know what you are adding. It is not recommended to change the default build tag list unless you really know what you are adding.

View File

@@ -51,7 +51,7 @@ icon: material/package
=== ":material-linux: Linux" === ":material-linux: Linux"
| 类型 | 平台 | 命令 | 链接 | | 类型 | 平台 | 链接 | 命令 |
|----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------| |----------|---------------|------------------------------|---------------------------------------------------------------------------------------------------------------|
| AUR | Arch Linux | `? -S sing-box` | [![AUR package](https://repology.org/badge/version-for-repo/aur/sing-box.svg)][aur] | | AUR | Arch Linux | `? -S sing-box` | [![AUR package](https://repology.org/badge/version-for-repo/aur/sing-box.svg)][aur] |
| nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] | | nixpkgs | NixOS | `nix-env -iA nixos.sing-box` | [![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/sing-box.svg)][nixpkgs] |
@@ -61,13 +61,13 @@ icon: material/package
=== ":material-apple: macOS" === ":material-apple: macOS"
| 类型 | 平台 | 命令 | 链接 | | 类型 | 平台 | 链接 | 命令 |
|----------|-------|-------------------------|------------------------------------------------------------------------------------------------| |----------|-------|-------------------------|------------------------------------------------------------------------------------------------|
| Homebrew | macOS | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] | | Homebrew | macOS | `brew install sing-box` | [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/sing-box.svg)][brew] |
=== ":material-microsoft-windows: Windows" === ":material-microsoft-windows: Windows"
| 类型 | 平台 | 命令 | 链接 | | 类型 | 平台 | 链接 | 命令 |
|------------|---------|---------------------------|-----------------------------------------------------------------------------------------------------| |------------|---------|---------------------------|-----------------------------------------------------------------------------------------------------|
| Scoop | Windows | `scoop install sing-box` | [![Scoop package](https://repology.org/badge/version-for-repo/scoop/sing-box.svg)][scoop] | | Scoop | Windows | `scoop install sing-box` | [![Scoop package](https://repology.org/badge/version-for-repo/scoop/sing-box.svg)][scoop] |
| Chocolatey | Windows | `choco install sing-box` | [![Chocolatey package](https://repology.org/badge/version-for-repo/chocolatey/sing-box.svg)][choco] | | Chocolatey | Windows | `choco install sing-box` | [![Chocolatey package](https://repology.org/badge/version-for-repo/chocolatey/sing-box.svg)][choco] |
@@ -75,13 +75,13 @@ icon: material/package
=== ":material-android: Android" === ":material-android: Android"
| 类型 | 平台 | 命令 | 链接 | | 类型 | 平台 | 链接 | 命令 |
|--------|---------|--------------------|----------------------------------------------------------------------------------------------| |--------|---------|--------------------|----------------------------------------------------------------------------------------------|
| Termux | Android | `pkg add sing-box` | [![Termux package](https://repology.org/badge/version-for-repo/termux/sing-box.svg)][termux] | | Termux | Android | `pkg add sing-box` | [![Termux package](https://repology.org/badge/version-for-repo/termux/sing-box.svg)][termux] |
=== ":material-freebsd: FreeBSD" === ":material-freebsd: FreeBSD"
| 类型 | 平台 | 命令 | 链接 | | 类型 | 平台 | 链接 | 命令 |
|------------|---------|------------------------|--------------------------------------------------------------------------------------------| |------------|---------|------------------------|--------------------------------------------------------------------------------------------|
| FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] | | FreshPorts | FreeBSD | `pkg install sing-box` | [![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/sing-box.svg)][ports] |

View File

@@ -567,7 +567,7 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
"server_port": 2080, "server_port": 2080,
"domain_resolver": { "domain_resolver": {
"server": "local", "server": "local",
"rewrite_ttl": 60, "rewrite_tll": 60,
"client_subnet": "1.1.1.1" "client_subnet": "1.1.1.1"
}, },
// or "domain_resolver": "local", // or "domain_resolver": "local",
@@ -579,7 +579,7 @@ The legacy outbound DNS rules are deprecated and can be replaced by new domain r
"route": { "route": {
"default_domain_resolver": { "default_domain_resolver": {
"server": "local", "server": "local",
"rewrite_ttl": 60, "rewrite_tll": 60,
"client_subnet": "1.1.1.1" "client_subnet": "1.1.1.1"
} }
} }

View File

@@ -565,21 +565,13 @@ DNS 服务器已经重构。
"type": "socks", "type": "socks",
"server": "example.org", "server": "example.org",
"server_port": 2080, "server_port": 2080,
"domain_resolver": { "domain_resolver": "local",
"server": "local",
"rewrite_ttl": 60,
"client_subnet": "1.1.1.1"
},
// 或 "domain_resolver": "local",
} }
], ],
// 或
"route": { "route": {
"default_domain_resolver": { "default_domain_resolver": {
"server": "local", "server": "local",
"rewrite_ttl": 60, "rewrite_tll": 60,
"client_subnet": "1.1.1.1" "client_subnet": "1.1.1.1"
} }
} }

View File

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

View File

@@ -1,186 +0,0 @@
package clashapi
import (
"archive/zip"
"context"
"crypto/x509"
"encoding/pem"
"io"
"net/http"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/service"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/gofrs/uuid/v5"
"howett.net/plist"
)
func mitmRouter(ctx context.Context) http.Handler {
r := chi.NewRouter()
r.Get("/mobileconfig", getMobileConfig(ctx))
r.Get("/crt", getCertificate(ctx))
r.Get("/pem", getCertificatePEM(ctx))
r.Get("/magisk", getMagiskModule(ctx))
return r
}
func getMobileConfig(ctx context.Context) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
store := service.FromContext[adapter.CertificateStore](ctx)
if !store.TLSDecryptionEnabled() {
http.NotFound(writer, request)
render.PlainText(writer, request, "TLS decryption not enabled")
return
}
certificate := store.TLSDecryptionCertificate()
writer.Header().Set("Content-Type", "application/x-apple-aspen-config")
uuidGen := common.Must1(uuid.NewV4()).String()
mobileConfig := map[string]interface{}{
"PayloadContent": []interface{}{
map[string]interface{}{
"PayloadCertificateFileName": "Certificates.cer",
"PayloadContent": certificate.Raw,
"PayloadDescription": "Adds a root certificate",
"PayloadDisplayName": certificate.Subject.CommonName,
"PayloadIdentifier": "com.apple.security.root." + uuidGen,
"PayloadType": "com.apple.security.root",
"PayloadUUID": uuidGen,
"PayloadVersion": 1,
},
},
"PayloadDisplayName": certificate.Subject.CommonName,
"PayloadIdentifier": "io.nekohasekai.sfa.ca.profile." + uuidGen,
"PayloadRemovalDisallowed": false,
"PayloadType": "Configuration",
"PayloadUUID": uuidGen,
"PayloadVersion": 1,
}
encoder := plist.NewEncoder(writer)
encoder.Indent("\t")
encoder.Encode(mobileConfig)
}
}
func getCertificate(ctx context.Context) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
store := service.FromContext[adapter.CertificateStore](ctx)
if !store.TLSDecryptionEnabled() {
http.NotFound(writer, request)
render.PlainText(writer, request, "TLS decryption not enabled")
return
}
writer.Header().Set("Content-Type", "application/x-x509-ca-cert")
writer.Header().Set("Content-Disposition", "attachment; filename=Certificate.crt")
writer.Write(store.TLSDecryptionCertificate().Raw)
}
}
func getCertificatePEM(ctx context.Context) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
store := service.FromContext[adapter.CertificateStore](ctx)
if !store.TLSDecryptionEnabled() {
http.NotFound(writer, request)
render.PlainText(writer, request, "TLS decryption not enabled")
return
}
writer.Header().Set("Content-Type", "application/x-pem-file")
writer.Header().Set("Content-Disposition", "attachment; filename=Certificate.pem")
writer.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: store.TLSDecryptionCertificate().Raw}))
}
}
func getMagiskModule(ctx context.Context) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
store := service.FromContext[adapter.CertificateStore](ctx)
if !store.TLSDecryptionEnabled() {
http.NotFound(writer, request)
render.PlainText(writer, request, "TLS decryption not enabled")
return
}
writer.Header().Set("Content-Type", "application/zip")
writer.Header().Set("Content-Disposition", "attachment; filename="+store.TLSDecryptionCertificate().Subject.CommonName+".zip")
createMagiskModule(writer, store.TLSDecryptionCertificate())
}
}
func createMagiskModule(writer io.Writer, certificate *x509.Certificate) error {
zipWriter := zip.NewWriter(writer)
defer zipWriter.Close()
moduleProp, err := zipWriter.Create("module.prop")
if err != nil {
return err
}
_, err = moduleProp.Write([]byte(`
id=sing-box-certificate
name=` + certificate.Subject.CommonName + `
version=v0.0.1
versionCode=1
author=sing-box
description=This module adds ` + certificate.Subject.CommonName + ` to the system trust store.
`))
if err != nil {
return err
}
certificateFile, err := zipWriter.Create("system/etc/security/cacerts/" + certificate.Subject.CommonName + ".pem")
if err != nil {
return err
}
err = pem.Encode(certificateFile, &pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw})
if err != nil {
return err
}
updateBinary, err := zipWriter.Create("META-INF/com/google/android/update-binary")
if err != nil {
return err
}
_, err = updateBinary.Write([]byte(`
#!/sbin/sh
#################
# Initialization
#################
umask 022
# echo before loading util_functions
ui_print() { echo "$1"; }
require_new_magisk() {
ui_print "*******************************"
ui_print " Please install Magisk v20.4+! "
ui_print "*******************************"
exit 1
}
#########################
# Load util_functions.sh
#########################
OUTFD=$2
ZIPFILE=$3
mount /data 2>/dev/null
[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk
. /data/adb/magisk/util_functions.sh
[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk
install_module
exit 0
`))
if err != nil {
return err
}
updaterScript, err := zipWriter.Create("META-INF/com/google/android/updater-script")
if err != nil {
return err
}
_, err = updaterScript.Write([]byte("#MAGISK"))
if err != nil {
return err
}
return nil
}

View File

@@ -124,7 +124,6 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
r.Mount("/profile", profileRouter()) r.Mount("/profile", profileRouter())
r.Mount("/cache", cacheRouter(ctx)) r.Mount("/cache", cacheRouter(ctx))
r.Mount("/dns", dnsRouter(s.dnsRouter)) r.Mount("/dns", dnsRouter(s.dnsRouter))
r.Mount("/mitm", mitmRouter(ctx))
s.setupMetaAPI(r) s.setupMetaAPI(r)
}) })

View File

@@ -77,15 +77,15 @@ func (s *Server) downloadExternalUI() error {
if response.StatusCode != http.StatusOK { if response.StatusCode != http.StatusOK {
return E.New("download external ui failed: ", response.Status) return E.New("download external ui failed: ", response.Status)
} }
err = s.downloadZIP(response.Body, s.externalUI) err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
if err != nil { if err != nil {
removeAllInDirectory(s.externalUI) removeAllInDirectory(s.externalUI)
} }
return err return err
} }
func (s *Server) downloadZIP(body io.Reader, output string) error { func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
tempFile, err := filemanager.CreateTemp(s.ctx, "external-ui.zip") tempFile, err := filemanager.CreateTemp(s.ctx, name)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -144,7 +144,6 @@ var OptionTUNGSO = Note{
DeprecatedVersion: "1.11.0", DeprecatedVersion: "1.11.0",
ScheduledVersion: "1.12.0", ScheduledVersion: "1.12.0",
EnvName: "TUN_GSO", EnvName: "TUN_GSO",
MigrationLink: "https://sing-box.sagernet.org/deprecated/#gso-option-in-tun",
} }
var OptionLegacyDNSTransport = Note{ var OptionLegacyDNSTransport = Note{
@@ -185,7 +184,6 @@ var OptionLegacyECHOptions = Note{
Description: "legacy ECH options", Description: "legacy ECH options",
DeprecatedVersion: "1.12.0", DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.13.0", ScheduledVersion: "1.13.0",
MigrationLink: "https://sing-box.sagernet.org/deprecated/#legacy-ech-fields",
} }
var Options = []Note{ var Options = []Note{

View File

@@ -38,12 +38,7 @@ func newPlatformTransport(iif LocalDNSTransport, tag string, options option.Loca
} }
} }
func (p *platformTransport) Start(stage adapter.StartStage) error { func (p *platformTransport) Reset() {
return nil
}
func (p *platformTransport) Close() error {
return nil
} }
func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {

View File

@@ -56,12 +56,7 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
if sFixAndroidStack { if sFixAndroidStack {
done := make(chan struct{}) go m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
go func() {
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
close(done)
}()
<-done
} else { } else {
m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained) m.updateDefaultInterface(interfaceName, interfaceIndex32, isExpensive, isConstrained)
} }

View File

@@ -32,9 +32,4 @@ type Notification struct {
Subtitle string Subtitle string
Body string Body string
OpenURL string OpenURL string
Clipboard string
MediaURL string
MediaData []byte
MediaType string
Timeout int
} }

51
go.mod
View File

@@ -3,12 +3,10 @@ module github.com/sagernet/sing-box
go 1.23.1 go 1.23.1
require ( require (
github.com/adhocore/gronx v1.19.5 github.com/anytls/sing-anytls v0.0.2
github.com/anytls/sing-anytls v0.0.6
github.com/caddyserver/certmagic v0.21.7 github.com/caddyserver/certmagic v0.21.7
github.com/cloudflare/circl v1.6.0 github.com/cloudflare/circl v1.6.0
github.com/cretz/bine v0.2.0 github.com/cretz/bine v0.2.0
github.com/dop251/goja v0.0.0-20250125213203-5ef83b82af17
github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/render v1.0.3 github.com/go-chi/render v1.0.3
github.com/gofrs/uuid/v5 v5.3.1 github.com/gofrs/uuid/v5 v5.3.1
@@ -25,29 +23,28 @@ require (
github.com/sagernet/cors v1.2.1 github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1 github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gomobile v0.1.4
github.com/sagernet/gvisor v0.0.0-20250324121324-d3f3d7570296 github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/quic-go v0.49.0-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.5-0.20250324102321-1ddf4ccbfab8 github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff
github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.1-beta.1 github.com/sagernet/sing-quic v0.4.1-beta.1
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 github.com/sagernet/sing-shadowtls v0.2.0
github.com/sagernet/sing-tun v0.6.2-0.20250319123703-35b5747b44ec github.com/sagernet/sing-tun v0.6.1
github.com/sagernet/sing-vmess v0.2.0 github.com/sagernet/sing-vmess v0.2.0
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/tailscale v1.80.3-mod.0 github.com/sagernet/tailscale v1.79.0-mod.1
github.com/sagernet/utls v1.6.7 github.com/sagernet/utls v1.6.7
github.com/sagernet/wireguard-go v0.0.1-beta.5 github.com/sagernet/wireguard-go v0.0.1-beta.5
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/vishvananda/netns v0.0.4
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.33.0 golang.org/x/crypto v0.33.0
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/mod v0.23.0 golang.org/x/mod v0.23.0
golang.org/x/net v0.35.0 golang.org/x/net v0.35.0
golang.org/x/sys v0.30.0 golang.org/x/sys v0.30.0
@@ -55,7 +52,6 @@ require (
google.golang.org/grpc v1.70.0 google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.5 google.golang.org/protobuf v1.36.5
howett.net/plist v1.0.1 howett.net/plist v1.0.1
software.sslmate.com/src/go-pkcs12 v0.4.0
) )
//replace github.com/sagernet/sing => ../sing //replace github.com/sagernet/sing => ../sing
@@ -75,14 +71,12 @@ require (
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/gaissmai/bart v0.11.1 // indirect github.com/gaissmai/bart v0.11.1 // indirect
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
@@ -91,25 +85,26 @@ require (
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect github.com/gorilla/csrf v1.7.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify/v2 v2.0.3 // indirect github.com/illarion/gonotify/v2 v2.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v0.2.2 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect github.com/mdlayher/socket v0.5.1 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/onsi/ginkgo/v2 v2.17.2 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect
@@ -125,22 +120,26 @@ require (
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/tcnksm/go-httpstat v0.2.0 // indirect
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/sync v0.11.0 // indirect
golang.org/x/term v0.29.0 // indirect golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.29.0 // indirect golang.org/x/tools v0.24.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.3.0 // indirect
) )
//replace github.com/sagernet/sing => ../sing

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