Compare commits

..

30 Commits

Author SHA1 Message Date
世界
d663278d66 documentation: Bump version 2025-09-16 00:59:31 +08:00
世界
2b207bb2ae Update quic-go to v0.54.0 2025-09-16 00:59:31 +08:00
世界
97a02c9e70 Update WireGuard and Tailscale 2025-09-15 23:57:40 +08:00
世界
15c295994d Fix preConnectionCopy 2025-09-15 17:08:16 +08:00
世界
a06a0b5253 Fix ping domain 2025-09-13 13:20:26 +08:00
世界
b5efc1d533 release: Improve publish testflight 2025-09-13 13:20:26 +08:00
世界
d2fe7ec481 release: Fix linux build 2025-09-13 13:20:26 +08:00
世界
d689419001 Improve ktls rx error handling 2025-09-13 13:20:26 +08:00
世界
831f242ddb Improve compatibility for kTLS 2025-09-13 13:20:26 +08:00
世界
71ed614dc4 ktls: Add warning for inappropriate scenarios 2025-09-13 13:20:25 +08:00
世界
9eee3a04e1 Add support for kTLS
Reference: https://gitlab.com/go-extension/tls
2025-09-13 13:20:25 +08:00
世界
6937c753e9 Add proxy support for ICMP echo request 2025-09-13 13:20:25 +08:00
世界
ae5b82df69 Fix resolve using resolved 2025-09-13 13:20:25 +08:00
世界
2df4812c31 documentation: Update behavior of local DNS server on darwin 2025-09-13 13:20:25 +08:00
世界
182a15c3af Stop using DHCP on iOS and tvOS
We do not have the `com.apple.developer.networking.multicast` entitlement and are unable to obtain it for non-technical reasons.
2025-09-13 13:20:25 +08:00
世界
fb8b1f8824 Remove use of ldflags -checklinkname=0 on darwin 2025-09-13 13:20:25 +08:00
世界
918d4ff3ca Fix local DNS server on darwin
We mistakenly believed that `libresolv`'s `search` function worked correctly in NetworkExtension, but it seems only `getaddrinfo` does.

This commit changes the behavior of the `local` DNS server in NetworkExtension to prefer DHCP, falling back to `getaddrinfo` if DHCP servers are unavailable.

It's worth noting that `prefer_go` does not disable DHCP since it respects Dial Fields, but `getaddrinfo` does the opposite. The new behavior only applies to NetworkExtension, not to all scenarios (primarily command-line binaries) as it did previously.

In addition, this commit also improves the DHCP DNS server to use the same robust query logic as `local`.
2025-09-13 13:20:24 +08:00
世界
454fe61aa0 Fix legacy DNS config 2025-09-13 13:19:48 +08:00
世界
60c464fc13 Fix rule-set format 2025-09-13 13:19:48 +08:00
世界
11720b048a documentation: Remove outdated icons 2025-09-13 13:19:48 +08:00
世界
bde25018a4 documentation: Improve local DNS server 2025-09-13 13:19:47 +08:00
世界
44bad22f39 Use libresolv in local DNS server on darwin 2025-09-13 13:19:47 +08:00
世界
2305225cc6 Use resolved in local DNS server if available 2025-09-13 13:19:47 +08:00
xchacha20-poly1305
8be89b54e5 Fix rule set version 2025-09-13 13:19:47 +08:00
世界
a256159461 documentation: Add preferred_by route rule item 2025-09-13 13:19:47 +08:00
世界
6c395e3ce7 Add preferred_by route rule item 2025-09-13 13:19:46 +08:00
世界
93cd0e5df1 documentation: Add interface address rule items 2025-09-13 13:19:46 +08:00
世界
98c00bb120 Add interface address rule items 2025-09-13 13:19:46 +08:00
neletor
8f90d500bc Add support for ech retry configs 2025-09-13 13:19:46 +08:00
Zephyruso
c113a27ecc Add /dns/flush-clash meta api 2025-09-13 13:19:46 +08:00
249 changed files with 3581 additions and 16051 deletions

View File

@@ -1 +0,0 @@
6228b14f72eea9db301d1fbe771c7bdecadefb40

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
VERSION="1.25.5"
VERSION="1.25.1"
mkdir -p $HOME/go
cd $HOME/go

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -e -o pipefail
SCRIPT_DIR=$(dirname "$0")
PROJECTS=$SCRIPT_DIR/../..
git -C $PROJECTS/cronet-go fetch origin main
git -C $PROJECTS/cronet-go fetch origin go
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
go mod tidy
git -C $PROJECTS/cronet-go rev-parse origin/HEAD > "$SCRIPT_DIR/CRONET_GO_VERSION"

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.5
go-version: ^1.25.1
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -69,25 +69,13 @@ jobs:
strategy:
matrix:
include:
- { os: linux, arch: amd64, variant: purego, naive: true, openwrt: "x86_64" }
- { os: linux, arch: amd64, variant: glibc, naive: true }
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
- { os: linux, arch: arm64, variant: purego, naive: true, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
- { os: linux, arch: arm64, variant: glibc, naive: true }
- { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
- { os: linux, arch: "386", go386: sse2, openwrt: "i386_pentium4" }
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 }
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
- { os: linux, arch: arm, goarm: "7", openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" }
- { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
@@ -99,8 +87,15 @@ jobs:
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- { os: windows, arch: amd64 }
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
- { os: windows, arch: "386" }
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
- { os: windows, arch: arm64 }
- { os: darwin, arch: amd64 }
- { os: darwin, arch: arm64 }
- { os: darwin, arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
@@ -112,15 +107,15 @@ jobs:
with:
fetch-depth: 0
- name: Setup Go
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
uses: actions/setup-go@v5
with:
go-version: ^1.25.5
go-version: ^1.25.1
- name: Setup Go 1.24
if: matrix.legacy_go124
uses: actions/setup-go@v5
with:
go-version: ~1.24.10
go-version: ~1.24.6
- name: Cache Go for Windows 7
if: matrix.legacy_win7
id: cache-go-for-windows7
@@ -128,7 +123,7 @@ jobs:
with:
path: |
~/go/go_win7
key: go_win7_1255
key: go_win7_1251
- name: Setup Go for Windows 7
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
run: |-
@@ -144,45 +139,6 @@ jobs:
with:
ndk-version: r28
local-cache: true
- name: Clone cronet-go
if: matrix.naive
run: |
set -xeuo pipefail
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
git init ~/cronet-go
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
git -C ~/cronet-go checkout FETCH_HEAD
git -C ~/cronet-go submodule update --init --recursive --depth=1
- name: Cache Chromium toolchain
if: matrix.naive
id: cache-chromium-toolchain
uses: actions/cache@v4
with:
path: |
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
~/cronet-go/naiveproxy/src/out/sysroot-build
key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
- name: Download Chromium toolchain
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
if [[ "${{ matrix.variant }}" == "musl" ]]; then
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
else
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain
fi
- name: Set Chromium toolchain environment
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
if [[ "${{ matrix.variant }}" == "musl" ]]; then
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
else
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env >> $GITHUB_ENV
fi
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
@@ -190,70 +146,10 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="${TAGS},with_naive_outbound"
fi
if [[ "${{ matrix.variant }}" == "purego" ]]; then
TAGS="${TAGS},with_purego"
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
TAGS="${TAGS},with_musl"
fi
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build (purego)
if: matrix.variant == 'purego'
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 }} -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Extract libcronet.so
if: matrix.variant == 'purego' && matrix.naive
run: |
cd ~/cronet-go
CGO_ENABLED=0 go run -v ./cmd/build-naive extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist
- name: Build (glibc)
if: matrix.variant == 'glibc'
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 }} -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (musl)
if: matrix.variant == 'musl'
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 }} -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (non-variant)
if: matrix.os != 'android' && matrix.variant == ''
- name: Build
if: matrix.os != 'android'
run: |
set -xeuo pipefail
mkdir -p dist
@@ -297,11 +193,6 @@ jobs:
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
fi
if [[ "${{ matrix.variant }}" == "glibc" ]]; then
DIR_NAME="${DIR_NAME}-glibc"
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
DIR_NAME="${DIR_NAME}-musl"
fi
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}"
@@ -369,12 +260,8 @@ jobs:
-p "dist/openwrt.deb" \
--architecture all \
dist/sing-box=/usr/bin/sing-box
SUFFIX=""
if [[ "${{ matrix.variant }}" == "musl" ]]; then
SUFFIX="_musl"
fi
for architecture in ${{ matrix.openwrt }}; do
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}${SUFFIX}.ipk"
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
done
rm "dist/openwrt.deb"
- name: Archive
@@ -388,177 +275,15 @@ jobs:
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
else
cp sing-box "${DIR_NAME}"
if [ -f libcronet.so ]; then
cp libcronet.so "${DIR_NAME}"
fi
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
fi
rm -r "${DIR_NAME}"
- name: Cleanup
run: rm -f dist/sing-box dist/libcronet.so
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}${{ matrix.variant && format('-{0}', matrix.variant) }}
path: "dist"
build_darwin:
name: Build Darwin binaries
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
runs-on: macos-latest
needs:
- calculate_version
strategy:
matrix:
include:
- { arch: amd64 }
- { arch: arm64 }
- { arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Setup Go
if: ${{ ! matrix.legacy_go124 }}
uses: actions/setup-go@v5
with:
go-version: ^1.25.3
- name: Setup Go 1.24
if: matrix.legacy_go124
uses: actions/setup-go@v5
with:
go-version: ~1.24.6
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then
TAGS="${TAGS},with_naive_outbound"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: darwin
GOARCH: ${{ matrix.arch }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name
run: |-
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-darwin-${{ matrix.arch }}"
if [[ -n "${{ matrix.legacy_name }}" ]]; then
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
fi
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
- name: Archive
run: |
set -xeuo pipefail
cd dist
mkdir -p "${DIR_NAME}"
cp ../LICENSE "${DIR_NAME}"
cp sing-box "${DIR_NAME}"
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
rm -r "${DIR_NAME}"
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
path: "dist"
build_windows:
name: Build Windows binaries
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
runs-on: windows-latest
needs:
- calculate_version
strategy:
matrix:
include:
- { arch: amd64, naive: true }
- { arch: "386" }
- { arch: arm64, naive: true }
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.4
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$env:GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build
if: matrix.naive
run: |
mkdir -p dist
go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" `
-ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" `
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: windows
GOARCH: ${{ matrix.arch }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build
if: ${{ !matrix.naive }}
run: |
mkdir -p dist
go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" `
-ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" `
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: windows
GOARCH: ${{ matrix.arch }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Extract libcronet.dll
if: matrix.naive
run: |
$CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION
$env:CGO_ENABLED = "0"
go run -v "github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION" extract-lib --target windows/${{ matrix.arch }} -o dist
- name: Archive
if: matrix.naive
run: |
$DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}"
mkdir "dist/$DIR_NAME"
Copy-Item LICENSE "dist/$DIR_NAME"
Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME"
Copy-Item "dist/libcronet.dll" "dist/$DIR_NAME"
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
Remove-Item -Recurse "dist/$DIR_NAME"
- name: Archive
if: ${{ !matrix.naive }}
run: |
$DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}"
mkdir "dist/$DIR_NAME"
Copy-Item LICENSE "dist/$DIR_NAME"
Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME"
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
Remove-Item -Recurse "dist/$DIR_NAME"
- name: Cleanup
if: matrix.naive
run: Remove-Item dist/sing-box.exe, dist/libcronet.dll
- name: Cleanup
if: ${{ !matrix.naive }}
run: Remove-Item dist/sing-box.exe
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-windows_${{ matrix.arch }}
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
path: "dist"
build_android:
name: Build Android
@@ -575,7 +300,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.5
go-version: ^1.25.1
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -633,17 +358,8 @@ jobs:
- name: Prepare upload
run: |-
mkdir -p dist
#cp clients/android/app/build/outputs/apk/play/release/*.apk dist
cp clients/android/app/build/outputs/apk/other/release/*.apk dist
VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2)
VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2)
cat > dist/SFA-version-metadata.json << EOF
{
"version_code": ${VERSION_CODE},
"version_name": "${VERSION_NAME}"
}
EOF
cat dist/SFA-version-metadata.json
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
@@ -664,7 +380,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.5
go-version: ^1.25.1
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -717,7 +433,7 @@ jobs:
build_apple:
name: Build Apple clients
runs-on: macos-26
if: false # github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'
if: false
needs:
- calculate_version
strategy:
@@ -763,7 +479,7 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.25.5
go-version: ^1.25.1
- name: Set tag
if: matrix.if
run: |-
@@ -882,7 +598,7 @@ jobs:
--app-drop-link 0 0 \
--skip-jenkins \
SFM.dmg "${{ matrix.export_path }}/SFM.app"
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
cd "${{ matrix.archive }}"
zip -r SFM.dSYMs.zip dSYMs
popd
@@ -903,8 +619,6 @@ jobs:
needs:
- calculate_version
- build
- build_darwin
- build_windows
- build_android
- build_apple
steps:

View File

@@ -1,10 +1,6 @@
name: Publish Docker Images
on:
#push:
# branches:
# - main-next
# - dev-next
release:
types:
- published
@@ -17,134 +13,8 @@ env:
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
jobs:
build_binary:
name: Build binary
build:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
include:
# Naive-enabled builds (musl)
- { arch: amd64, naive: true, docker_platform: "linux/amd64" }
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
- { arch: "386", naive: true, docker_platform: "linux/386" }
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
# Non-naive builds
- { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" }
- { arch: ppc64le, docker_platform: "linux/ppc64le" }
- { arch: riscv64, docker_platform: "linux/riscv64" }
- { arch: s390x, docker_platform: "linux/s390x" }
steps:
- name: Get commit to build
id: ref
run: |-
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
ref="${{ github.ref_name }}"
else
ref="${{ github.event.inputs.tag }}"
fi
echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
ref: ${{ steps.ref.outputs.ref }}
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.4
- name: Clone cronet-go
if: matrix.naive
run: |
set -xeuo pipefail
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
git init ~/cronet-go
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
git -C ~/cronet-go checkout FETCH_HEAD
git -C ~/cronet-go submodule update --init --recursive --depth=1
- name: Cache Chromium toolchain
if: matrix.naive
id: cache-chromium-toolchain
uses: actions/cache@v4
with:
path: |
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
~/cronet-go/naiveproxy/src/out/sysroot-build
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
- name: Download Chromium toolchain
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
- name: Set version
run: |
set -xeuo pipefail
VERSION=$(go run ./cmd/internal/read_tag)
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
- name: Set Chromium toolchain environment
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="${TAGS},with_naive_outbound,with_musl"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build (naive)
if: matrix.naive
run: |
set -xeuo pipefail
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
- name: Build (non-naive)
if: ${{ ! matrix.naive }}
run: |
set -xeuo pipefail
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
- name: Prepare artifact
run: |
platform=${{ matrix.docker_platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
# Rename binary to include arch info for Dockerfile.binary
BINARY_NAME="sing-box-${{ matrix.arch }}"
if [[ -n "${{ matrix.goarm }}" ]]; then
BINARY_NAME="${BINARY_NAME}v${{ matrix.goarm }}"
fi
mv sing-box "${BINARY_NAME}"
echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: binary-${{ env.PLATFORM_PAIR }}
path: ${{ env.BINARY_NAME }}
if-no-files-found: error
retention-days: 1
build_docker:
name: Build Docker image
runs-on: ubuntu-latest
needs:
- build_binary
strategy:
fail-fast: true
matrix:
@@ -177,16 +47,6 @@ jobs:
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Download binary
uses: actions/download-artifact@v5
with:
name: binary-${{ env.PLATFORM_PAIR }}
path: .
- name: Prepare binary
run: |
# Find and make the binary executable
chmod +x sing-box-*
ls -la sing-box-*
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Setup Docker Buildx
@@ -208,7 +68,8 @@ jobs:
with:
platforms: ${{ matrix.platform }}
context: .
file: Dockerfile.binary
build-args: |
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
@@ -226,7 +87,7 @@ jobs:
merge:
runs-on: ubuntu-latest
needs:
- build_docker
- build
steps:
- name: Get commit to build
id: ref
@@ -260,7 +121,6 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
if: github.event_name != 'push'
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
@@ -268,7 +128,6 @@ jobs:
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
if: github.event_name != 'push'
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}

View File

@@ -32,7 +32,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.4.0
version: latest
args: --timeout=30m
install-mode: binary
verify: false

View File

@@ -1,10 +1,6 @@
name: Build Linux Packages
on:
#push:
# branches:
# - main-next
# - dev-next
workflow_dispatch:
inputs:
version:
@@ -34,7 +30,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.5
go-version: ^1.25.1
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -56,13 +52,11 @@ jobs:
strategy:
matrix:
include:
# Naive-enabled builds (musl)
- { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }
# Non-naive builds (unsupported architectures)
- { 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 }
@@ -77,38 +71,13 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.5
- name: Clone cronet-go
if: matrix.naive
run: |
set -xeuo pipefail
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
git init ~/cronet-go
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
git -C ~/cronet-go checkout FETCH_HEAD
git -C ~/cronet-go submodule update --init --recursive --depth=1
- name: Cache Chromium toolchain
if: matrix.naive
id: cache-chromium-toolchain
uses: actions/cache@v4
go-version: ^1.25.1
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
with:
path: |
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
~/cronet-go/naiveproxy/src/out/sysroot-build
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
- name: Download Chromium toolchain
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
- name: Set Chromium toolchain environment
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
ndk-version: r28
local-cache: true
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
@@ -116,27 +85,9 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="${TAGS},with_naive_outbound,with_musl"
fi
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build (naive)
if: matrix.naive
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 }} -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (non-naive)
if: ${{ ! matrix.naive }}
- name: Build
run: |
set -xeuo pipefail
mkdir -p dist
@@ -234,6 +185,5 @@ jobs:
path: dist
merge-multiple: true
- name: Publish packages
if: github.event_name != 'push'
run: |-
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/

4
.gitignore vendored
View File

@@ -15,6 +15,4 @@
.DS_Store
/config.d/
/venv/
CLAUDE.md
AGENTS.md
/.claude/

103
.goreleaser.fury.yaml Normal file
View File

@@ -0,0 +1,103 @@
project_name: sing-box
builds:
- id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
- -s
- -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
env:
- CGO_ENABLED=0
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_7
- linux_s390x
- linux_riscv64
- linux_mips64le
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}"
nfpms:
- &template
id: package
package_name: sing-box
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds:
- main
homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform.
license: GPLv3 or later
formats:
- deb
- rpm
priority: extra
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: "config|noreplace"
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /usr/lib/systemd/system/sing-box@.service
- src: release/config/sing-box.sysusers
dst: /usr/lib/sysusers.d/sing-box.conf
- src: release/config/sing-box.rules
dst: /usr/share/polkit-1/rules.d/sing-box.rules
- src: release/config/sing-box-split-dns.xml
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
- src: release/completions/sing-box.bash
dst: /usr/share/bash-completion/completions/sing-box.bash
- src: release/completions/sing-box.fish
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
- src: release/completions/sing-box.zsh
dst: /usr/share/zsh/site-functions/_sing-box
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
deb:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
fields:
Bugs: https://github.com/SagerNet/sing-box/issues
rpm:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
conflicts:
- sing-box-beta
- id: package_beta
<<: *template
package_name: sing-box-beta
file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
formats:
- deb
- rpm
conflicts:
- sing-box
release:
disable: true
furies:
- account: sagernet
ids:
- package
disable: "{{ not (not .Prerelease) }}"
- account: sagernet
ids:
- package_beta
disable: "{{ not .Prerelease }}"

213
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,213 @@
version: 2
project_name: sing-box
builds:
- &template
id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
- -s
- -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_6
- linux_arm_7
- linux_s390x
- linux_riscv64
- linux_mips64le
- windows_amd64_v1
- windows_386
- windows_arm64
- darwin_amd64_v1
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: legacy
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
env:
- CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go_legacy
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
targets:
- windows_amd64_v1
- windows_386
- id: android
<<: *template
env:
- CGO_ENABLED=1
- GOTOOLCHAIN=local
overrides:
- goos: android
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi21-clang
- CXX=armv7a-linux-androideabi21-clang++
- goos: android
goarch: arm64
env:
- CC=aarch64-linux-android21-clang
- CXX=aarch64-linux-android21-clang++
- goos: android
goarch: 386
env:
- CC=i686-linux-android21-clang
- CXX=i686-linux-android21-clang++
- goos: android
goarch: amd64
goamd64: v1
env:
- CC=x86_64-linux-android21-clang
- CXX=x86_64-linux-android21-clang++
targets:
- android_arm_7
- android_arm64
- android_386
- android_amd64
archives:
- &template
id: archive
builds:
- main
- android
formats:
- tar.gz
format_overrides:
- goos: windows
formats:
- zip
wrap_in_directory: true
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-legacy
<<: *template
builds:
- legacy
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
nfpms:
- id: package
package_name: sing-box
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
builds:
- main
homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform.
license: GPLv3 or later
formats:
- deb
- rpm
- archlinux
# - apk
# - ipk
priority: extra
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: "config|noreplace"
- src: release/config/sing-box.service
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /usr/lib/systemd/system/sing-box@.service
- src: release/config/sing-box.sysusers
dst: /usr/lib/sysusers.d/sing-box.conf
- src: release/config/sing-box.rules
dst: /usr/share/polkit-1/rules.d/sing-box.rules
- src: release/config/sing-box-split-dns.xml
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
- src: release/completions/sing-box.bash
dst: /usr/share/bash-completion/completions/sing-box.bash
- src: release/completions/sing-box.fish
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
- src: release/completions/sing-box.zsh
dst: /usr/share/zsh/site-functions/_sing-box
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
deb:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
fields:
Bugs: https://github.com/SagerNet/sing-box/issues
rpm:
signature:
key_file: "{{ .Env.NFPM_KEY_PATH }}"
overrides:
apk:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
- src: release/config/sing-box.initd
dst: /etc/init.d/sing-box
- src: release/completions/sing-box.bash
dst: /usr/share/bash-completion/completions/sing-box.bash
- src: release/completions/sing-box.fish
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
- src: release/completions/sing-box.zsh
dst: /usr/share/zsh/site-functions/_sing-box
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
ipk:
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
- src: release/config/openwrt.init
dst: /etc/init.d/sing-box
- src: release/config/openwrt.conf
dst: /etc/config/sing-box
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
prefix_template: '{{ .ProjectName }}-{{ .Version }}/'
checksum:
disable: true
name_template: '{{ .ProjectName }}-{{ .Version }}.checksum'
signs:
- artifacts: checksum
release:
github:
owner: SagerNet
name: sing-box
draft: true
prerelease: auto
mode: replace
ids:
- archive
- package
skip_upload: true
partial:
by: target

View File

@@ -13,13 +13,15 @@ RUN set -ex \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
-o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
./cmd/sing-box
FROM --platform=$TARGETPLATFORM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
&& apk upgrade \
&& apk add bash tzdata ca-certificates nftables \
&& rm -rf /var/cache/apk/*
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]

View File

@@ -1,8 +0,0 @@
FROM alpine
ARG TARGETARCH
ARG TARGETVARIANT
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]

View File

@@ -1,6 +1,6 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
@@ -38,7 +38,7 @@ fmt:
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
fmt_install:
go install -v mvdan.cc/gofumpt@v0.8.0
go install -v mvdan.cc/gofumpt@latest
go install -v github.com/daixiang0/gci@latest
lint:
@@ -49,7 +49,7 @@ lint:
GOOS=freebsd golangci-lint run ./...
lint_install:
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
proto:
@go run ./cmd/internal/protogen
@@ -249,8 +249,8 @@ lib:
go run ./cmd/internal/build_libbox -target ios
lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.10
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.10
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8
docs:
venv/bin/mkdocs serve

View File

@@ -1,11 +1,3 @@
> Sponsored by [Warp](https://go.warp.dev/sing-box), built for coding with multiple AI agents
<a href="https://go.warp.dev/sing-box">
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/raw/refs/heads/main/Github/Sponsor/Warp-Github-LG-02.png">
</a>
---
# sing-box
The universal proxy platform.

View File

@@ -4,7 +4,6 @@ import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -12,7 +11,6 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.EndpointManager = (*Manager)(nil)
@@ -48,14 +46,10 @@ func (m *Manager) Start(stage adapter.StartStage) error {
return nil
}
for _, endpoint := range m.endpoints {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -72,15 +66,11 @@ func (m *Manager) Close() error {
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, endpoint := range endpoints {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
err = E.Append(err, endpoint.Close(), func(err error) error {
return E.Cause(err, "close ", name)
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -129,15 +119,11 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
m.access.Lock()
defer m.access.Unlock()
if m.started {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {

View File

@@ -6,7 +6,6 @@ import (
"encoding/binary"
"time"
"github.com/sagernet/sing/common/observable"
"github.com/sagernet/sing/common/varbin"
)
@@ -15,7 +14,6 @@ type ClashServer interface {
ConnectionTracker
Mode() string
ModeList() []string
SetModeUpdateHook(hook *observable.Subscriber[struct{}])
HistoryStorage() URLTestHistoryStorage
}
@@ -25,7 +23,7 @@ type URLTestHistory struct {
}
type URLTestHistoryStorage interface {
SetHook(hook *observable.Subscriber[struct{}])
SetHook(hook chan<- struct{})
LoadURLTestHistory(tag string) *URLTestHistory
DeleteURLTestHistory(tag string)
StoreURLTestHistory(tag string, history *URLTestHistory)

View File

@@ -5,6 +5,7 @@ import (
"net/netip"
"time"
"github.com/sagernet/sing-box/common/process"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -84,7 +85,7 @@ type InboundContext struct {
DestinationAddresses []netip.Addr
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *ConnectionOwner
ProcessInfo *process.Info
QueryType uint16
FakeIP bool

View File

@@ -4,7 +4,6 @@ import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -12,7 +11,6 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.InboundManager = (*Manager)(nil)
@@ -47,14 +45,10 @@ func (m *Manager) Start(stage adapter.StartStage) error {
inbounds := m.inbounds
m.access.Unlock()
for _, inbound := range inbounds {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -71,15 +65,11 @@ func (m *Manager) Close() error {
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, inbound := range inbounds {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
err = E.Append(err, inbound.Close(), func(err error) error {
return E.Cause(err, "close ", name)
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -131,15 +121,11 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
m.access.Lock()
defer m.access.Unlock()
if m.started {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsInbound, loaded := m.inboundByTag[tag]; loaded {

View File

@@ -1,14 +1,6 @@
package adapter
import (
"reflect"
"strings"
"time"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
import E "github.com/sagernet/sing/common/exceptions"
type SimpleLifecycle interface {
Start() error
@@ -56,47 +48,22 @@ type LifecycleService interface {
Lifecycle
}
func getServiceName(service any) string {
if named, ok := service.(interface {
Type() string
Tag() string
}); ok {
tag := named.Tag()
if tag != "" {
return named.Type() + "[" + tag + "]"
}
return named.Type()
}
t := reflect.TypeOf(service)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return strings.ToLower(t.Name())
}
func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {
func Start(stage StartStage, services ...Lifecycle) error {
for _, service := range services {
name := getServiceName(service)
logger.Trace(stage, " ", name)
startTime := time.Now()
err := service.Start(stage)
if err != nil {
return err
}
logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
func StartNamed(stage StartStage, services []LifecycleService) error {
for _, service := range services {
logger.Trace(stage, " ", service.Name())
startTime := time.Now()
err := service.Start(stage)
if err != nil {
return E.Cause(err, stage.String(), " ", service.Name())
}
logger.Trace(stage, " ", service.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}

View File

@@ -10,7 +10,6 @@ import (
type NetworkManager interface {
Lifecycle
Initialize(ruleSets []RuleSet)
InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultNetworkInterface() *NetworkInterface
@@ -25,10 +24,9 @@ type NetworkManager interface {
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
NeedWIFIState() bool
WIFIState() WIFIState
UpdateWIFIState()
ResetNetwork()
UpdateWIFIState()
}
type NetworkOptions struct {

View File

@@ -6,7 +6,6 @@ import (
"os"
"strings"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -14,7 +13,6 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
)
@@ -83,14 +81,10 @@ func (m *Manager) Start(stage adapter.StartStage) error {
outbounds := m.outbounds
m.access.Unlock()
for _, outbound := range outbounds {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
return nil
@@ -115,29 +109,22 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
}
started[outboundTag] = true
canContinue = true
name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]"
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
m.logger.Trace("start ", name)
startTime := time.Now()
monitor.Start("start ", name)
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start(adapter.StartStateStart)
monitor.Finish()
if err != nil {
return E.Cause(err, "start ", name)
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} else if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
m.logger.Trace("start ", name)
startTime := time.Now()
monitor.Start("start ", name)
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start ", name)
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if len(started) == len(outbounds) {
@@ -184,15 +171,11 @@ func (m *Manager) Close() error {
var err error
for _, outbound := range outbounds {
if closer, isCloser := outbound.(io.Closer); isCloser {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
err = E.Append(err, closer.Close(), func(err error) error {
return E.Cause(err, "close ", name)
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
return nil
@@ -273,15 +256,11 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
return err
}
if m.started {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
m.access.Lock()

View File

@@ -1,70 +0,0 @@
package adapter
import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger"
)
type PlatformInterface interface {
Initialize(networkManager NetworkManager) error
UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int) error
UsePlatformInterface() bool
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
UsePlatformDefaultInterfaceMonitor() bool
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
UsePlatformNetworkInterfaces() bool
NetworkInterfaces() ([]NetworkInterface, error)
UnderNetworkExtension() bool
NetworkExtensionIncludeAllNetworks() bool
ClearDNSCache()
RequestPermissionForWIFIState() error
ReadWIFIState() WIFIState
SystemCertificates() []string
UsePlatformConnectionOwnerFinder() bool
FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)
UsePlatformWIFIMonitor() bool
UsePlatformNotification() bool
SendNotification(notification *Notification) error
}
type FindConnectionOwnerRequest struct {
IpProtocol int32
SourceAddress string
SourcePort int32
DestinationAddress string
DestinationPort int32
}
type ConnectionOwner struct {
ProcessID uint32
UserId int32
UserName string
ProcessPath string
AndroidPackageName string
}
type Notification struct {
Identifier string
TypeName string
TypeID int32
Title string
Subtitle string
Body string
OpenURL string
}
type SystemProxyStatus struct {
Available bool
Enabled bool
}

View File

@@ -24,6 +24,7 @@ type Router interface {
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool
Rules() []Rule
AppendTracker(tracker ConnectionTracker)
ResetNetwork()

View File

@@ -4,7 +4,6 @@ import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -12,7 +11,6 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.ServiceManager = (*Manager)(nil)
@@ -45,14 +43,10 @@ func (m *Manager) Start(stage adapter.StartStage) error {
services := m.services
m.access.Unlock()
for _, service := range services {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(service, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -69,15 +63,11 @@ func (m *Manager) Close() error {
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, service := range services {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
err = E.Append(err, service.Close(), func(err error) error {
return E.Cause(err, "close ", name)
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -126,15 +116,11 @@ func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag stri
m.access.Lock()
defer m.access.Unlock()
if m.started {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(service, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsService, loaded := m.serviceByTag[tag]; loaded {

View File

@@ -73,7 +73,7 @@ func NewUpstreamContextHandlerEx(
}
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
_, myMetadata := ExtendContext(ctx)
myMetadata := ContextFrom(ctx)
if source.IsValid() {
myMetadata.Source = source
}
@@ -84,7 +84,7 @@ func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context,
}
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
_, myMetadata := ExtendContext(ctx)
myMetadata := ContextFrom(ctx)
if source.IsValid() {
myMetadata.Source = source
}
@@ -146,7 +146,7 @@ type routeContextHandlerWrapperEx struct {
}
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
_, metadata := ExtendContext(ctx)
metadata := ContextFrom(ctx)
if source.IsValid() {
metadata.Source = source
}
@@ -157,7 +157,7 @@ func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn
}
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
_, metadata := ExtendContext(ctx)
metadata := ContextFrom(ctx)
if source.IsValid() {
metadata.Source = source
}

58
box.go
View File

@@ -22,6 +22,7 @@ import (
"github.com/sagernet/sing-box/dns/transport/local"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/direct"
@@ -138,7 +139,7 @@ func New(options Options) (*Box, error) {
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
platformInterface := service.FromContext[platform.Interface](ctx)
var defaultLogWriter io.Writer
if platformInterface != nil {
defaultLogWriter = io.Discard
@@ -183,7 +184,7 @@ func New(options Options) (*Box, error) {
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
if err != nil {
return nil, E.Cause(err, "initialize network manager")
}
@@ -443,15 +444,15 @@ func (s *Box) preStart() error {
if err != nil {
return E.Cause(err, "start logger")
}
err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil {
return err
}
@@ -463,27 +464,27 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService)
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService)
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService)
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
if err != nil {
return err
}
@@ -497,42 +498,17 @@ func (s *Box) Close() error {
default:
close(s.done)
}
var err error
for _, closeItem := range []struct {
name string
service adapter.Lifecycle
}{
{"service", s.service},
{"endpoint", s.endpoint},
{"inbound", s.inbound},
{"outbound", s.outbound},
{"router", s.router},
{"connection", s.connection},
{"dns-router", s.dnsRouter},
{"dns-transport", s.dnsTransport},
{"network", s.network},
} {
s.logger.Trace("close ", closeItem.name)
startTime := time.Now()
err = E.Append(err, closeItem.service.Close(), func(err error) error {
return E.Cause(err, "close ", closeItem.name)
})
s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
err := common.Close(
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
)
for _, lifecycleService := range s.internalService {
s.logger.Trace("close ", lifecycleService.Name())
startTime := time.Now()
err = E.Append(err, lifecycleService.Close(), func(err error) error {
return E.Cause(err, "close ", lifecycleService.Name())
})
s.logger.Trace("close ", lifecycleService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
s.logger.Trace("close logger")
startTime := time.Now()
err = E.Append(err, s.logFactory.Close(), func(err error) error {
return E.Cause(err, "close logger")
})
s.logger.Trace("close logger completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
return err
}
@@ -551,7 +527,3 @@ func (s *Box) Inbound() adapter.InboundManager {
func (s *Box) Outbound() adapter.OutboundManager {
return s.outbound
}
func (s *Box) LogFactory() log.Factory {
return s.logFactory
}

View File

@@ -46,7 +46,7 @@ var (
sharedFlags []string
debugFlags []string
sharedTags []string
darwinTags []string
macOSTags []string
memcTags []string
notMemcTags []string
debugTags []string
@@ -62,8 +62,8 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
darwinTags = append(darwinTags, "with_dhcp")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
macOSTags = append(macOSTags, "with_dhcp")
memcTags = append(memcTags, "with_tailscale")
notMemcTags = append(notMemcTags, "with_low_memory")
debugTags = append(debugTags, "debug")
@@ -107,8 +107,10 @@ func buildAndroid() {
}
if !debugEnabled {
// sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
args = append(args, sharedFlags...)
} else {
// debugFlags[1] = debugFlags[1] + " -checklinkname=0"
args = append(args, debugFlags...)
}
@@ -158,7 +160,9 @@ func buildApple() {
"-tags-not-macos=with_low_memory",
}
if !withTailscale {
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
args = append(args, "-tags-macos="+strings.Join(append(macOSTags, memcTags...), ","))
} else {
args = append(args, "-tags-macos="+strings.Join(macOSTags, ","))
}
if !debugEnabled {
@@ -167,7 +171,7 @@ func buildApple() {
args = append(args, debugFlags...)
}
tags := append(sharedTags, darwinTags...)
tags := sharedTags
if withTailscale {
tags = append(tags, memcTags...)
}

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && badlinkname
//go:build go1.25 && !without_badtls
package badtls

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && badlinkname
//go:build go1.25 && !without_badtls
package badtls

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && badlinkname
//go:build go1.25 && !without_badtls
package badtls

View File

@@ -1,4 +1,4 @@
//go:build !go1.25 || !badlinkname
//go:build !go1.25 || without_badtls
package badtls

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && badlinkname
//go:build go1.25 && !without_badtls
package badtls

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && badlinkname
//go:build go1.25 && !without_badtls
package badtls

View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
@@ -35,7 +36,7 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
switch options.Store {
case C.CertificateStoreSystem, "":
systemPool = x509.NewCertPool()
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
platformInterface := service.FromContext[platform.Interface](ctx)
var systemValid bool
if platformInterface != nil {
for _, cert := range platformInterface.SystemCertificates() {

View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/listener"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
@@ -19,8 +20,6 @@ import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/database64128/tfo-go/v2"
)
var (
@@ -29,8 +28,8 @@ var (
)
type DefaultDialer struct {
dialer4 tfo.Dialer
dialer6 tfo.Dialer
dialer4 tcpDialer
dialer6 tcpDialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
@@ -48,7 +47,7 @@ type DefaultDialer struct {
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
platformInterface := service.FromContext[platform.Interface](ctx)
var (
dialer net.Dialer
@@ -142,18 +141,9 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
} else {
dialer.Timeout = C.TCPConnectTimeout
}
if !options.DisableTCPKeepAlive {
keepIdle := time.Duration(options.TCPKeepAlive)
if keepIdle == 0 {
keepIdle = C.TCPKeepAliveInitial
}
keepInterval := time.Duration(options.TCPKeepAliveInterval)
if keepInterval == 0 {
keepInterval = C.TCPKeepAliveInterval
}
dialer.KeepAlive = keepIdle
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(keepIdle, keepInterval))
}
// TODO: Add an option to customize the keep alive period
dialer.KeepAlive = C.TCPKeepAliveInitial
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
var udpFragment bool
if options.UDPFragment != nil {
udpFragment = *options.UDPFragment
@@ -187,10 +177,19 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
}
if options.TCPMultiPath {
dialer4.SetMultipathTCP(true)
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
setMultiPathTCP(&dialer4)
}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil {
return nil, err
}
tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen)
if err != nil {
return nil, err
}
tcpDialer4 := tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen}
tcpDialer6 := tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen}
return &DefaultDialer{
dialer4: tcpDialer4,
dialer6: tcpDialer6,
@@ -270,7 +269,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
}
var dialer net.Dialer
if N.NetworkName(network) == N.NetworkTCP {
dialer = d.dialer4.Dialer
dialer = dialerFromTCPDialer(d.dialer4)
} else {
dialer = d.udpDialer4
}
@@ -318,9 +317,9 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
if !destination.Is6() {
return d.dialer6.Dialer
return dialerFromTCPDialer(d.dialer6)
} else {
return d.dialer4.Dialer
return dialerFromTCPDialer(d.dialer4)
}
}

View File

@@ -0,0 +1,19 @@
//go:build go1.20
package dialer
import (
"net"
"github.com/metacubex/tfo-go"
)
type tcpDialer = tfo.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
}
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
return dialer.Dialer
}

View File

@@ -0,0 +1,11 @@
//go:build go1.21
package dialer
import "net"
const go121Available = true
func setMultiPathTCP(dialer *net.Dialer) {
dialer.SetMultipathTCP(true)
}

View File

@@ -0,0 +1,22 @@
//go:build !go1.20
package dialer
import (
"net"
E "github.com/sagernet/sing/common/exceptions"
)
type tcpDialer = net.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
if tfoEnabled {
return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
}
return dialer, nil
}
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
return dialer
}

View File

@@ -0,0 +1,12 @@
//go:build !go1.21
package dialer
import (
"net"
)
const go121Available = false
func setMultiPathTCP(dialer *net.Dialer) {
}

View File

@@ -1,3 +1,5 @@
//go:build go1.20
package dialer
import (
@@ -14,7 +16,7 @@ import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/database64128/tfo-go/v2"
"github.com/metacubex/tfo-go"
)
type slowOpenConn struct {
@@ -30,7 +32,7 @@ type slowOpenConn struct {
err error
}
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:

20
common/dialer/tfo_stub.go Normal file
View File

@@ -0,0 +1,20 @@
//go:build !go1.20
package dialer
import (
"context"
"net"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
return dialer.DialContext(ctx, network, destination.String())
default:
return dialer.DialContext(ctx, network, destination.AddrString())
}
}

View File

@@ -1,4 +1,4 @@
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -1,4 +1,4 @@
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -1,15 +1,15 @@
//go:build !linux
//go:build !linux || !go1.25 || without_badtls
package ktls
import (
"context"
"os"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
return nil, E.New("kTLS is only supported on Linux")
return nil, os.ErrInvalid
}

View File

@@ -1,15 +0,0 @@
//go:build linux && go1.25 && !badlinkname
package ktls
import (
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
return nil, E.New("kTLS requires build flags `badlinkname` and `-ldflags=-checklinkname=0`, please recompile your binary")
}

View File

@@ -1,15 +0,0 @@
//go:build linux && !go1.25
package ktls
import (
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
return nil, E.New("kTLS requires Go 1.25 or later, please recompile your binary")
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && badlinkname
//go:build linux && go1.25 && !without_badtls
package ktls

View File

@@ -0,0 +1,11 @@
//go:build go1.21
package listener
import "net"
const go121Available = true
func setMultiPathTCP(listenConfig *net.ListenConfig) {
listenConfig.SetMultipathTCP(true)
}

View File

@@ -0,0 +1,16 @@
//go:build go1.23
package listener
import (
"net"
"time"
)
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
listener.KeepAliveConfig = net.KeepAliveConfig{
Enable: true,
Idle: idle,
Interval: interval,
}
}

View File

@@ -0,0 +1,10 @@
//go:build !go1.21
package listener
import "net"
const go121Available = false
func setMultiPathTCP(listenConfig *net.ListenConfig) {
}

View File

@@ -0,0 +1,15 @@
//go:build !go1.23
package listener
import (
"net"
"time"
"github.com/sagernet/sing/common/control"
)
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
listener.KeepAlive = idle
listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval))
}

View File

@@ -17,7 +17,7 @@ import (
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/database64128/tfo-go/v2"
"github.com/metacubex/tfo-go"
)
func (l *Listener) ListenTCP() (net.Listener, error) {
@@ -37,7 +37,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
}
if !l.listenOptions.DisableTCPKeepAlive {
if l.listenOptions.TCPKeepAlive >= 0 {
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
if keepIdle == 0 {
keepIdle = C.TCPKeepAliveInitial
@@ -46,14 +46,13 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
if keepInterval == 0 {
keepInterval = C.TCPKeepAliveInterval
}
listenConfig.KeepAliveConfig = net.KeepAliveConfig{
Enable: true,
Idle: keepIdle,
Interval: keepInterval,
}
setKeepAliveConfig(&listenConfig, keepIdle, keepInterval)
}
if l.listenOptions.TCPMultiPath {
listenConfig.SetMultipathTCP(true)
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
setMultiPathTCP(&listenConfig)
}
if l.tproxy {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {

View File

@@ -5,7 +5,6 @@ import (
"net/netip"
"os/user"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions"
@@ -13,7 +12,7 @@ import (
)
type Searcher interface {
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error)
}
var ErrNotFound = E.New("process not found")
@@ -23,7 +22,15 @@ type Config struct {
PackageManager tun.PackageManager
}
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
type Info struct {
ProcessID uint32
ProcessPath string
PackageName string
User string
UserId int32
}
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
if err != nil {
return nil, err
@@ -31,7 +38,7 @@ func FindProcessInfo(searcher Searcher, ctx context.Context, network string, sou
if info.UserId != -1 {
osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil {
info.UserName = osUser.Username
info.User = osUser.Username
}
}
return info, nil

View File

@@ -4,7 +4,6 @@ import (
"context"
"net/netip"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
)
@@ -18,22 +17,22 @@ func NewSearcher(config Config) (Searcher, error) {
return &androidSearcher{config.PackageManager}, nil
}
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
_, uid, err := resolveSocketByNetlink(network, source, destination)
if err != nil {
return nil, err
}
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
return &adapter.ConnectionOwner{
UserId: int32(uid),
AndroidPackageName: sharedPackage,
return &Info{
UserId: int32(uid),
PackageName: sharedPackage,
}, nil
}
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
return &adapter.ConnectionOwner{
UserId: int32(uid),
AndroidPackageName: packageName,
return &Info{
UserId: int32(uid),
PackageName: packageName,
}, nil
}
return &adapter.ConnectionOwner{UserId: int32(uid)}, nil
return &Info{UserId: int32(uid)}, nil
}

View File

@@ -10,7 +10,6 @@ import (
"syscall"
"unsafe"
"github.com/sagernet/sing-box/adapter"
N "github.com/sagernet/sing/common/network"
"golang.org/x/sys/unix"
@@ -24,12 +23,12 @@ func NewSearcher(_ Config) (Searcher, error) {
return &darwinSearcher{}, nil
}
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
if err != nil {
return nil, err
}
return &adapter.ConnectionOwner{ProcessPath: processName, UserId: -1}, nil
return &Info{ProcessPath: processName, UserId: -1}, nil
}
var structSize = func() int {

View File

@@ -6,7 +6,6 @@ import (
"context"
"net/netip"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
)
@@ -20,7 +19,7 @@ func NewSearcher(config Config) (Searcher, error) {
return &linuxSearcher{config.Logger}, nil
}
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
inode, uid, err := resolveSocketByNetlink(network, source, destination)
if err != nil {
return nil, err
@@ -29,7 +28,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, sou
if err != nil {
s.logger.DebugContext(ctx, "find process path: ", err)
}
return &adapter.ConnectionOwner{
return &Info{
UserId: int32(uid),
ProcessPath: processPath,
}, nil

View File

@@ -5,7 +5,6 @@ import (
"net/netip"
"syscall"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/winiphlpapi"
@@ -28,16 +27,16 @@ func initWin32API() error {
return winiphlpapi.LoadExtendedTable()
}
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
pid, err := winiphlpapi.FindPid(network, source)
if err != nil {
return nil, err
}
path, err := getProcessPath(pid)
if err != nil {
return &adapter.ConnectionOwner{ProcessID: pid, UserId: -1}, err
return &Info{ProcessID: pid, UserId: -1}, err
}
return &adapter.ConnectionOwner{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
}
func getProcessPath(pid uint32) (string, error) {

View File

@@ -1,9 +0,0 @@
package settings
import "github.com/sagernet/sing-box/adapter"
type WIFIMonitor interface {
ReadWIFIState() adapter.WIFIState
Start() error
Close() error
}

View File

@@ -1,46 +0,0 @@
package settings
import (
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
)
type LinuxWIFIMonitor struct {
monitor WIFIMonitor
}
func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
monitors := []func(func(adapter.WIFIState)) (WIFIMonitor, error){
newNetworkManagerMonitor,
newIWDMonitor,
newWpaSupplicantMonitor,
newConnManMonitor,
}
var errors []error
for _, factory := range monitors {
monitor, err := factory(callback)
if err == nil {
return &LinuxWIFIMonitor{monitor: monitor}, nil
}
errors = append(errors, err)
}
return nil, E.Cause(E.Errors(errors...), "no supported WIFI manager found")
}
func (m *LinuxWIFIMonitor) ReadWIFIState() adapter.WIFIState {
return m.monitor.ReadWIFIState()
}
func (m *LinuxWIFIMonitor) Start() error {
if m.monitor != nil {
return m.monitor.Start()
}
return nil
}
func (m *LinuxWIFIMonitor) Close() error {
if m.monitor != nil {
return m.monitor.Close()
}
return nil
}

View File

@@ -1,166 +0,0 @@
package settings
import (
"context"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/godbus/dbus/v5"
)
type connmanMonitor struct {
conn *dbus.Conn
callback func(adapter.WIFIState)
cancel context.CancelFunc
signalChan chan *dbus.Signal
}
func newConnManMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
conn, err := dbus.ConnectSystemBus()
if err != nil {
return nil, err
}
cmObj := conn.Object("net.connman", "/")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
call := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0)
if call.Err != nil {
conn.Close()
return nil, call.Err
}
return &connmanMonitor{conn: conn, callback: callback}, nil
}
func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
cmObj := m.conn.Object("net.connman", "/")
var services []interface{}
err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services)
if err != nil {
return adapter.WIFIState{}
}
for _, service := range services {
servicePair, ok := service.([]interface{})
if !ok || len(servicePair) != 2 {
continue
}
serviceProps, ok := servicePair[1].(map[string]dbus.Variant)
if !ok {
continue
}
typeVariant, hasType := serviceProps["Type"]
if !hasType {
continue
}
serviceType, ok := typeVariant.Value().(string)
if !ok || serviceType != "wifi" {
continue
}
stateVariant, hasState := serviceProps["State"]
if !hasState {
continue
}
state, ok := stateVariant.Value().(string)
if !ok || (state != "online" && state != "ready") {
continue
}
nameVariant, hasName := serviceProps["Name"]
if !hasName {
continue
}
ssid, ok := nameVariant.Value().(string)
if !ok || ssid == "" {
continue
}
bssidVariant, hasBSSID := serviceProps["BSSID"]
if !hasBSSID {
return adapter.WIFIState{SSID: ssid}
}
bssid, ok := bssidVariant.Value().(string)
if !ok {
return adapter.WIFIState{SSID: ssid}
}
return adapter.WIFIState{
SSID: ssid,
BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
}
}
return adapter.WIFIState{}
}
func (m *connmanMonitor) Start() error {
if m.callback == nil {
return nil
}
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
m.signalChan = make(chan *dbus.Signal, 10)
m.conn.Signal(m.signalChan)
err := m.conn.AddMatchSignal(
dbus.WithMatchInterface("net.connman.Service"),
dbus.WithMatchSender("net.connman"),
)
if err != nil {
return err
}
state := m.ReadWIFIState()
go m.monitorSignals(ctx, m.signalChan, state)
m.callback(state)
return nil
}
func (m *connmanMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
for {
select {
case <-ctx.Done():
return
case signal, ok := <-signalChan:
if !ok {
return
}
// godbus Signal.Name uses "interface.member" format (e.g. "net.connman.Service.PropertyChanged"),
// not just the member name. This differs from the D-Bus signal member in the match rule.
if signal.Name == "net.connman.Service.PropertyChanged" {
state := m.ReadWIFIState()
if state != lastState {
lastState = state
m.callback(state)
}
}
}
}
}
func (m *connmanMonitor) Close() error {
if m.cancel != nil {
m.cancel()
}
if m.signalChan != nil {
m.conn.RemoveSignal(m.signalChan)
close(m.signalChan)
}
if m.conn != nil {
m.conn.RemoveMatchSignal(
dbus.WithMatchInterface("net.connman.Service"),
dbus.WithMatchSender("net.connman"),
)
return m.conn.Close()
}
return nil
}

View File

@@ -1,188 +0,0 @@
package settings
import (
"context"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/godbus/dbus/v5"
)
type iwdMonitor struct {
conn *dbus.Conn
callback func(adapter.WIFIState)
cancel context.CancelFunc
signalChan chan *dbus.Signal
}
func newIWDMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
conn, err := dbus.ConnectSystemBus()
if err != nil {
return nil, err
}
iwdObj := conn.Object("net.connman.iwd", "/")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
call := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0)
if call.Err != nil {
conn.Close()
return nil, call.Err
}
return &iwdMonitor{conn: conn, callback: callback}, nil
}
func (m *iwdMonitor) ReadWIFIState() adapter.WIFIState {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
iwdObj := m.conn.Object("net.connman.iwd", "/")
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
err := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&objects)
if err != nil {
return adapter.WIFIState{}
}
for _, interfaces := range objects {
stationProps, hasStation := interfaces["net.connman.iwd.Station"]
if !hasStation {
continue
}
stateVariant, hasState := stationProps["State"]
if !hasState {
continue
}
state, ok := stateVariant.Value().(string)
if !ok || state != "connected" {
continue
}
connectedNetworkVariant, hasNetwork := stationProps["ConnectedNetwork"]
if !hasNetwork {
continue
}
networkPath, ok := connectedNetworkVariant.Value().(dbus.ObjectPath)
if !ok || networkPath == "/" {
continue
}
networkInterfaces, hasNetworkPath := objects[networkPath]
if !hasNetworkPath {
continue
}
networkProps, hasNetworkInterface := networkInterfaces["net.connman.iwd.Network"]
if !hasNetworkInterface {
continue
}
nameVariant, hasName := networkProps["Name"]
if !hasName {
continue
}
ssid, ok := nameVariant.Value().(string)
if !ok {
continue
}
connectedBSSVariant, hasBSS := stationProps["ConnectedAccessPoint"]
if !hasBSS {
return adapter.WIFIState{SSID: ssid}
}
bssPath, ok := connectedBSSVariant.Value().(dbus.ObjectPath)
if !ok || bssPath == "/" {
return adapter.WIFIState{SSID: ssid}
}
bssInterfaces, hasBSSPath := objects[bssPath]
if !hasBSSPath {
return adapter.WIFIState{SSID: ssid}
}
bssProps, hasBSSInterface := bssInterfaces["net.connman.iwd.BasicServiceSet"]
if !hasBSSInterface {
return adapter.WIFIState{SSID: ssid}
}
addressVariant, hasAddress := bssProps["Address"]
if !hasAddress {
return adapter.WIFIState{SSID: ssid}
}
bssid, ok := addressVariant.Value().(string)
if !ok {
return adapter.WIFIState{SSID: ssid}
}
return adapter.WIFIState{
SSID: ssid,
BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
}
}
return adapter.WIFIState{}
}
func (m *iwdMonitor) Start() error {
if m.callback == nil {
return nil
}
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
m.signalChan = make(chan *dbus.Signal, 10)
m.conn.Signal(m.signalChan)
err := m.conn.AddMatchSignal(
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
dbus.WithMatchSender("net.connman.iwd"),
)
if err != nil {
return err
}
state := m.ReadWIFIState()
go m.monitorSignals(ctx, m.signalChan, state)
m.callback(state)
return nil
}
func (m *iwdMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
for {
select {
case <-ctx.Done():
return
case signal, ok := <-signalChan:
if !ok {
return
}
if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" {
state := m.ReadWIFIState()
if state != lastState {
lastState = state
m.callback(state)
}
}
}
}
}
func (m *iwdMonitor) Close() error {
if m.cancel != nil {
m.cancel()
}
if m.signalChan != nil {
m.conn.RemoveSignal(m.signalChan)
close(m.signalChan)
}
if m.conn != nil {
m.conn.RemoveMatchSignal(
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
dbus.WithMatchSender("net.connman.iwd"),
)
return m.conn.Close()
}
return nil
}

View File

@@ -1,163 +0,0 @@
package settings
import (
"context"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/godbus/dbus/v5"
)
type networkManagerMonitor struct {
conn *dbus.Conn
callback func(adapter.WIFIState)
cancel context.CancelFunc
signalChan chan *dbus.Signal
}
func newNetworkManagerMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
conn, err := dbus.ConnectSystemBus()
if err != nil {
return nil, err
}
nmObj := conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
var state uint32
err = nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "State").Store(&state)
if err != nil {
conn.Close()
return nil, err
}
return &networkManagerMonitor{conn: conn, callback: callback}, nil
}
func (m *networkManagerMonitor) ReadWIFIState() adapter.WIFIState {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
nmObj := m.conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
var activeConnectionPaths []dbus.ObjectPath
err := nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "ActiveConnections").Store(&activeConnectionPaths)
if err != nil || len(activeConnectionPaths) == 0 {
return adapter.WIFIState{}
}
for _, connectionPath := range activeConnectionPaths {
connObj := m.conn.Object("org.freedesktop.NetworkManager", connectionPath)
var devicePaths []dbus.ObjectPath
err = connObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Connection.Active", "Devices").Store(&devicePaths)
if err != nil || len(devicePaths) == 0 {
continue
}
for _, devicePath := range devicePaths {
deviceObj := m.conn.Object("org.freedesktop.NetworkManager", devicePath)
var deviceType uint32
err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device", "DeviceType").Store(&deviceType)
if err != nil || deviceType != 2 {
continue
}
var accessPointPath dbus.ObjectPath
err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device.Wireless", "ActiveAccessPoint").Store(&accessPointPath)
if err != nil || accessPointPath == "/" {
continue
}
apObj := m.conn.Object("org.freedesktop.NetworkManager", accessPointPath)
var ssidBytes []byte
err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "Ssid").Store(&ssidBytes)
if err != nil {
continue
}
var hwAddress string
err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "HwAddress").Store(&hwAddress)
if err != nil {
continue
}
ssid := strings.TrimSpace(string(ssidBytes))
if ssid == "" {
continue
}
return adapter.WIFIState{
SSID: ssid,
BSSID: strings.ToUpper(strings.ReplaceAll(hwAddress, ":", "")),
}
}
}
return adapter.WIFIState{}
}
func (m *networkManagerMonitor) Start() error {
if m.callback == nil {
return nil
}
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
m.signalChan = make(chan *dbus.Signal, 10)
m.conn.Signal(m.signalChan)
err := m.conn.AddMatchSignal(
dbus.WithMatchSender("org.freedesktop.NetworkManager"),
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
)
if err != nil {
return err
}
state := m.ReadWIFIState()
go m.monitorSignals(ctx, m.signalChan, state)
m.callback(state)
return nil
}
func (m *networkManagerMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
for {
select {
case <-ctx.Done():
return
case signal, ok := <-signalChan:
if !ok {
return
}
if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" {
state := m.ReadWIFIState()
if state != lastState {
lastState = state
m.callback(state)
}
}
}
}
}
func (m *networkManagerMonitor) Close() error {
if m.cancel != nil {
m.cancel()
}
if m.signalChan != nil {
m.conn.RemoveSignal(m.signalChan)
close(m.signalChan)
}
if m.conn != nil {
m.conn.RemoveMatchSignal(
dbus.WithMatchSender("org.freedesktop.NetworkManager"),
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
)
return m.conn.Close()
}
return nil
}

View File

@@ -1,225 +0,0 @@
package settings
import (
"bufio"
"context"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
)
var wpaSocketCounter atomic.Uint64
type wpaSupplicantMonitor struct {
socketPath string
callback func(adapter.WIFIState)
cancel context.CancelFunc
monitorConn *net.UnixConn
connMutex sync.Mutex
}
func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
socketDirs := []string{"/var/run/wpa_supplicant", "/run/wpa_supplicant"}
for _, socketDir := range socketDirs {
entries, err := os.ReadDir(socketDir)
if err != nil {
continue
}
for _, entry := range entries {
if entry.IsDir() || entry.Name() == "." || entry.Name() == ".." {
continue
}
socketPath := filepath.Join(socketDir, entry.Name())
id := wpaSocketCounter.Add(1)
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d-%d", os.Getpid(), id), Net: "unixgram"}
remoteAddr := &net.UnixAddr{Name: socketPath, Net: "unixgram"}
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
if err != nil {
continue
}
conn.Close()
return &wpaSupplicantMonitor{socketPath: socketPath, callback: callback}, nil
}
}
return nil, os.ErrNotExist
}
func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
id := wpaSocketCounter.Add(1)
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d-%d", os.Getpid(), id), Net: "unixgram"}
remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
if err != nil {
return adapter.WIFIState{}
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(3 * time.Second))
status, err := m.sendCommand(conn, "STATUS")
if err != nil {
return adapter.WIFIState{}
}
var ssid, bssid string
var connected bool
scanner := bufio.NewScanner(strings.NewReader(status))
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "wpa_state=") {
state := strings.TrimPrefix(line, "wpa_state=")
connected = state == "COMPLETED"
} else if strings.HasPrefix(line, "ssid=") {
ssid = strings.TrimPrefix(line, "ssid=")
} else if strings.HasPrefix(line, "bssid=") {
bssid = strings.TrimPrefix(line, "bssid=")
}
}
if !connected || ssid == "" {
return adapter.WIFIState{}
}
return adapter.WIFIState{
SSID: ssid,
BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
}
}
// sendCommand sends a command to wpa_supplicant and returns the response.
// Commands are sent without trailing newlines per the wpa_supplicant control
// interface protocol - the official wpa_ctrl.c sends raw command strings.
func (m *wpaSupplicantMonitor) sendCommand(conn *net.UnixConn, command string) (string, error) {
_, err := conn.Write([]byte(command))
if err != nil {
return "", err
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
return "", err
}
response := string(buf[:n])
if strings.HasPrefix(response, "FAIL") {
return "", os.ErrInvalid
}
return strings.TrimSpace(response), nil
}
func (m *wpaSupplicantMonitor) Start() error {
if m.callback == nil {
return nil
}
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
state := m.ReadWIFIState()
go m.monitorEvents(ctx, state)
m.callback(state)
return nil
}
func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adapter.WIFIState) {
var consecutiveErrors int
var debounceTimer *time.Timer
var debounceMutex sync.Mutex
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-mon-%d", os.Getpid()), Net: "unixgram"}
remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
if err != nil {
return
}
defer conn.Close()
m.connMutex.Lock()
m.monitorConn = conn
m.connMutex.Unlock()
// ATTACH/DETACH commands use os_strcmp() for exact matching in wpa_supplicant,
// so they must be sent without trailing newlines.
// See: https://w1.fi/cgit/hostap/tree/wpa_supplicant/ctrl_iface_unix.c
_, err = conn.Write([]byte("ATTACH"))
if err != nil {
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil || !strings.HasPrefix(string(buf[:n]), "OK") {
return
}
for {
select {
case <-ctx.Done():
debounceMutex.Lock()
if debounceTimer != nil {
debounceTimer.Stop()
}
debounceMutex.Unlock()
conn.Write([]byte("DETACH"))
return
default:
}
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
n, err := conn.Read(buf)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
select {
case <-ctx.Done():
return
default:
}
consecutiveErrors++
if consecutiveErrors > 10 {
return
}
time.Sleep(time.Second)
continue
}
consecutiveErrors = 0
msg := string(buf[:n])
if strings.Contains(msg, "CTRL-EVENT-CONNECTED") || strings.Contains(msg, "CTRL-EVENT-DISCONNECTED") {
debounceMutex.Lock()
if debounceTimer != nil {
debounceTimer.Stop()
}
debounceTimer = time.AfterFunc(500*time.Millisecond, func() {
state := m.ReadWIFIState()
if state != lastState {
lastState = state
m.callback(state)
}
})
debounceMutex.Unlock()
}
}
}
func (m *wpaSupplicantMonitor) Close() error {
if m.cancel != nil {
m.cancel()
}
m.connMutex.Lock()
if m.monitorConn != nil {
m.monitorConn.Close()
}
m.connMutex.Unlock()
return nil
}

View File

@@ -1,27 +0,0 @@
//go:build !linux && !windows
package settings
import (
"os"
"github.com/sagernet/sing-box/adapter"
)
type stubWIFIMonitor struct{}
func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
return nil, os.ErrInvalid
}
func (m *stubWIFIMonitor) ReadWIFIState() adapter.WIFIState {
return adapter.WIFIState{}
}
func (m *stubWIFIMonitor) Start() error {
return nil
}
func (m *stubWIFIMonitor) Close() error {
return nil
}

View File

@@ -1,144 +0,0 @@
//go:build windows
package settings
import (
"context"
"fmt"
"strings"
"sync"
"syscall"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/winwlanapi"
"golang.org/x/sys/windows"
)
type windowsWIFIMonitor struct {
handle windows.Handle
callback func(adapter.WIFIState)
cancel context.CancelFunc
lastState adapter.WIFIState
mutex sync.Mutex
}
func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
handle, err := winwlanapi.OpenHandle()
if err != nil {
return nil, err
}
interfaces, err := winwlanapi.EnumInterfaces(handle)
if err != nil {
winwlanapi.CloseHandle(handle)
return nil, err
}
if len(interfaces) == 0 {
winwlanapi.CloseHandle(handle)
return nil, fmt.Errorf("no wireless interfaces found")
}
return &windowsWIFIMonitor{
handle: handle,
callback: callback,
}, nil
}
func (m *windowsWIFIMonitor) ReadWIFIState() adapter.WIFIState {
interfaces, err := winwlanapi.EnumInterfaces(m.handle)
if err != nil || len(interfaces) == 0 {
return adapter.WIFIState{}
}
for _, iface := range interfaces {
if iface.InterfaceState != winwlanapi.InterfaceStateConnected {
continue
}
guid := iface.InterfaceGUID
attrs, err := winwlanapi.QueryCurrentConnection(m.handle, &guid)
if err != nil {
continue
}
ssidLength := attrs.AssociationAttributes.SSID.Length
if ssidLength == 0 || ssidLength > winwlanapi.Dot11SSIDMaxLength {
continue
}
ssid := string(attrs.AssociationAttributes.SSID.SSID[:ssidLength])
bssid := formatBSSID(attrs.AssociationAttributes.BSSID)
return adapter.WIFIState{
SSID: strings.TrimSpace(ssid),
BSSID: bssid,
}
}
return adapter.WIFIState{}
}
func formatBSSID(mac winwlanapi.Dot11MacAddress) string {
return fmt.Sprintf("%02X%02X%02X%02X%02X%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
}
func (m *windowsWIFIMonitor) Start() error {
if m.callback == nil {
return nil
}
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
m.lastState = m.ReadWIFIState()
callbackFunc := func(data *winwlanapi.NotificationData, callbackContext uintptr) uintptr {
if data.NotificationSource != winwlanapi.NotificationSourceACM {
return 0
}
switch data.NotificationCode {
case winwlanapi.NotificationACMConnectionComplete,
winwlanapi.NotificationACMDisconnected:
m.checkAndNotify()
}
return 0
}
callbackPointer := syscall.NewCallback(callbackFunc)
err := winwlanapi.RegisterNotification(m.handle, winwlanapi.NotificationSourceACM, callbackPointer, 0)
if err != nil {
cancel()
return err
}
go func() {
<-ctx.Done()
}()
m.callback(m.lastState)
return nil
}
func (m *windowsWIFIMonitor) checkAndNotify() {
m.mutex.Lock()
defer m.mutex.Unlock()
state := m.ReadWIFIState()
if state != m.lastState {
m.lastState = state
if m.callback != nil {
m.callback(state)
}
}
}
func (m *windowsWIFIMonitor) Close() error {
if m.cancel != nil {
m.cancel()
}
winwlanapi.UnregisterNotification(m.handle)
return winwlanapi.CloseHandle(m.handle)
}

View File

@@ -119,19 +119,21 @@ func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr
if err != nil {
return nil, err
}
tlsConn, err := aTLS.ClientHandshake(ctx, conn, d.config)
if err != nil {
conn.Close()
tlsConn, err := ClientHandshake(ctx, conn, d.config)
if err == nil {
return tlsConn, nil
}
conn.Close()
if echRetry {
var echErr *tls.ECHRejectionError
if echRetry && errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
if echConfig, isECH := d.config.(ECHCapableConfig); isECH {
echConfig.SetECHConfigList(echErr.RetryConfigList)
return d.dialContext(ctx, destination, false)
}
}
return nil, err
return d.dialContext(ctx, destination, false)
}
return tlsConn, nil
return nil, err
}
func (d *defaultDialer) Upstream() any {

View File

@@ -51,7 +51,6 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
return &ECHClientConfig{
ECHCapableConfig: clientConfig,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
queryServerName: options.ECH.QueryServerName,
}, nil
}
}
@@ -109,11 +108,10 @@ func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {
type ECHClientConfig struct {
ECHCapableConfig
access sync.Mutex
dnsRouter adapter.DNSRouter
queryServerName string
lastTTL time.Duration
lastUpdate time.Time
access sync.Mutex
dnsRouter adapter.DNSRouter
lastTTL time.Duration
lastUpdate time.Time
}
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
@@ -132,17 +130,13 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
s.access.Lock()
defer s.access.Unlock()
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL {
queryServerName := s.queryServerName
if queryServerName == "" {
queryServerName = s.ServerName()
}
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
},
Question: []mDNS.Question{
{
Name: mDNS.Fqdn(queryServerName),
Name: mDNS.Fqdn(s.ServerName()),
Qtype: mDNS.TypeHTTPS,
Qclass: mDNS.ClassINET,
},
@@ -181,12 +175,7 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
}
func (s *ECHClientConfig) Clone() Config {
return &ECHClientConfig{
ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig),
dnsRouter: s.dnsRouter,
queryServerName: s.queryServerName,
lastUpdate: s.lastUpdate,
}
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
}
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {

23
common/tls/ech_stub.go Normal file
View File

@@ -0,0 +1,23 @@
//go:build !go1.24
package tls
import (
"context"
"crypto/tls"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New("ECH requires go1.24, please recompile your binary.")
}
func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, tlsConfig *tls.Config, echKeyPath *string) error {
return E.New("ECH requires go1.24, please recompile your binary.")
}
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
panic("unreachable")
}

View File

@@ -68,10 +68,7 @@ func NewRealityServer(ctx context.Context, logger log.ContextLogger, options opt
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
if len(options.CurvePreferences) > 0 {
return nil, E.New("curve preferences is unavailable in reality")
}
if len(options.Certificate) > 0 || options.CertificatePath != "" || len(options.ClientCertificatePublicKeySHA256) > 0 {
if len(options.Certificate) > 0 || options.CertificatePath != "" {
return nil, E.New("certificate is unavailable in reality")
}
if len(options.Key) > 0 || options.KeyPath != "" {

View File

@@ -1,12 +1,9 @@
package tls
import (
"bytes"
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"net"
"os"
"strings"
@@ -111,15 +108,6 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
return err
}
}
if len(options.CertificatePublicKeySHA256) > 0 {
if len(options.Certificate) > 0 || options.CertificatePath != "" {
return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path")
}
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
}
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = options.ALPN
}
@@ -149,9 +137,6 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
for _, curve := range options.CurvePreferences {
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curve))
}
var certificate []byte
if len(options.Certificate) > 0 {
certificate = []byte(strings.Join(options.Certificate, "\n"))
@@ -169,35 +154,6 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
}
tlsConfig.RootCAs = certPool
}
var clientCertificate []byte
if len(options.ClientCertificate) > 0 {
clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n"))
} else if options.ClientCertificatePath != "" {
content, err := os.ReadFile(options.ClientCertificatePath)
if err != nil {
return nil, E.Cause(err, "read client certificate")
}
clientCertificate = content
}
var clientKey []byte
if len(options.ClientKey) > 0 {
clientKey = []byte(strings.Join(options.ClientKey, "\n"))
} else if options.ClientKeyPath != "" {
content, err := os.ReadFile(options.ClientKeyPath)
if err != nil {
return nil, E.Cause(err, "read client key")
}
clientKey = content
}
if len(clientCertificate) > 0 && len(clientKey) > 0 {
keyPair, err := tls.X509KeyPair(clientCertificate, clientKey)
if err != nil {
return nil, E.Cause(err, "parse client x509 key pair")
}
tlsConfig.Certificates = []tls.Certificate{keyPair}
} else if len(clientCertificate) > 0 || len(clientKey) > 0 {
return nil, E.New("client certificate and client key must be provided together")
}
var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
if options.ECH != nil && options.ECH.Enabled {
var err error
@@ -219,22 +175,3 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
}
return config, nil
}
func verifyPublicKeySHA256(knownHashValues [][]byte, rawCerts [][]byte, timeFunc func() time.Time) error {
leafCertificate, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return E.Cause(err, "failed to parse leaf certificate")
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(leafCertificate.PublicKey)
if err != nil {
return E.Cause(err, "failed to marshal public key")
}
hashValue := sha256.Sum256(pubKeyBytes)
for _, value := range knownHashValues {
if bytes.Equal(value, hashValue[:]) {
return nil
}
}
return E.New("unrecognized remote public key: ", base64.StdEncoding.EncodeToString(hashValue[:]))
}

View File

@@ -3,7 +3,6 @@ package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"os"
"strings"
@@ -23,17 +22,16 @@ import (
var errInsecureUnused = E.New("tls: insecure unused")
type STDServerConfig struct {
access sync.RWMutex
config *tls.Config
logger log.Logger
acmeService adapter.SimpleLifecycle
certificate []byte
key []byte
certificatePath string
keyPath string
clientCertificatePath []string
echKeyPath string
watcher *fswatch.Watcher
access sync.RWMutex
config *tls.Config
logger log.Logger
acmeService adapter.SimpleLifecycle
certificate []byte
key []byte
certificatePath string
keyPath string
echKeyPath string
watcher *fswatch.Watcher
}
func (c *STDServerConfig) ServerName() string {
@@ -113,9 +111,6 @@ func (c *STDServerConfig) startWatcher() error {
if c.echKeyPath != "" {
watchPath = append(watchPath, c.echKeyPath)
}
if len(c.clientCertificatePath) > 0 {
watchPath = append(watchPath, c.clientCertificatePath...)
}
if len(watchPath) == 0 {
return nil
}
@@ -164,30 +159,6 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
c.config = config
c.access.Unlock()
c.logger.Info("reloaded TLS certificate")
} else if common.Contains(c.clientCertificatePath, path) {
clientCertificateCA := x509.NewCertPool()
var reloaded bool
for _, certPath := range c.clientCertificatePath {
content, err := os.ReadFile(certPath)
if err != nil {
c.logger.Error(E.Cause(err, "reload certificate from ", c.clientCertificatePath))
continue
}
if !clientCertificateCA.AppendCertsFromPEM(content) {
c.logger.Error(E.New("invalid client certificate file: ", certPath))
continue
}
reloaded = true
}
if !reloaded {
return E.New("client certificates is empty")
}
c.access.Lock()
config := c.config.Clone()
config.ClientCAs = clientCertificateCA
c.config = config
c.access.Unlock()
c.logger.Info("reloaded client certificates")
} else if path == c.echKeyPath {
echKey, err := os.ReadFile(c.echKeyPath)
if err != nil {
@@ -264,14 +235,8 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
for _, curveID := range options.CurvePreferences {
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curveID))
}
tlsConfig.ClientAuth = tls.ClientAuthType(options.ClientAuthentication)
var (
certificate []byte
key []byte
)
var certificate []byte
var key []byte
if acmeService == nil {
if len(options.Certificate) > 0 {
certificate = []byte(strings.Join(options.Certificate, "\n"))
@@ -313,43 +278,6 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
tlsConfig.Certificates = []tls.Certificate{keyPair}
}
}
if len(options.ClientCertificate) > 0 || len(options.ClientCertificatePath) > 0 {
if tlsConfig.ClientAuth == tls.NoClientCert {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
if len(options.ClientCertificate) > 0 {
clientCertificateCA := x509.NewCertPool()
if !clientCertificateCA.AppendCertsFromPEM([]byte(strings.Join(options.ClientCertificate, "\n"))) {
return nil, E.New("invalid client certificate strings")
}
tlsConfig.ClientCAs = clientCertificateCA
} else if len(options.ClientCertificatePath) > 0 {
clientCertificateCA := x509.NewCertPool()
for _, path := range options.ClientCertificatePath {
content, err := os.ReadFile(path)
if err != nil {
return nil, E.Cause(err, "read client certificate from ", path)
}
if !clientCertificateCA.AppendCertsFromPEM(content) {
return nil, E.New("invalid client certificate file: ", path)
}
}
tlsConfig.ClientCAs = clientCertificateCA
} else if len(options.ClientCertificatePublicKeySHA256) > 0 {
if tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
tlsConfig.ClientAuth = tls.RequireAnyClientCert
} else if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven {
tlsConfig.ClientAuth = tls.RequestClientCert
}
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return verifyPublicKeySHA256(options.ClientCertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
}
} else {
return nil, E.New("missing client_certificate, client_certificate_path or client_certificate_public_key_sha256 for client authentication")
}
}
var echKeyPath string
if options.ECH != nil && options.ECH.Enabled {
err = parseECHServerConfig(ctx, options, tlsConfig, &echKeyPath)
@@ -358,15 +286,14 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
}
}
serverConfig := &STDServerConfig{
config: tlsConfig,
logger: logger,
acmeService: acmeService,
certificate: certificate,
key: key,
certificatePath: options.CertificatePath,
clientCertificatePath: options.ClientCertificatePath,
keyPath: options.KeyPath,
echKeyPath: echKeyPath,
config: tlsConfig,
logger: logger,
acmeService: acmeService,
certificate: certificate,
key: key,
certificatePath: options.CertificatePath,
keyPath: options.KeyPath,
echKeyPath: echKeyPath,
}
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
serverConfig.access.Lock()

View File

@@ -167,15 +167,6 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
}
tlsConfig.InsecureServerNameToVerify = serverName
}
if len(options.CertificatePublicKeySHA256) > 0 {
if len(options.Certificate) > 0 || options.CertificatePath != "" {
return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path")
}
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
}
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = options.ALPN
}
@@ -222,35 +213,6 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
}
tlsConfig.RootCAs = certPool
}
var clientCertificate []byte
if len(options.ClientCertificate) > 0 {
clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n"))
} else if options.ClientCertificatePath != "" {
content, err := os.ReadFile(options.ClientCertificatePath)
if err != nil {
return nil, E.Cause(err, "read client certificate")
}
clientCertificate = content
}
var clientKey []byte
if len(options.ClientKey) > 0 {
clientKey = []byte(strings.Join(options.ClientKey, "\n"))
} else if options.ClientKeyPath != "" {
content, err := os.ReadFile(options.ClientKeyPath)
if err != nil {
return nil, E.Cause(err, "read client key")
}
clientKey = content
}
if len(clientCertificate) > 0 && len(clientKey) > 0 {
keyPair, err := utls.X509KeyPair(clientCertificate, clientKey)
if err != nil {
return nil, E.Cause(err, "parse client x509 key pair")
}
tlsConfig.Certificates = []utls.Certificate{keyPair}
} else if len(clientCertificate) > 0 || len(clientKey) > 0 {
return nil, E.New("client certificate and client key must be provided together")
}
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
if err != nil {
return nil, err

View File

@@ -14,7 +14,6 @@ import (
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/observable"
)
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
@@ -22,7 +21,7 @@ var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
type HistoryStorage struct {
access sync.RWMutex
delayHistory map[string]*adapter.URLTestHistory
updateHook *observable.Subscriber[struct{}]
updateHook chan<- struct{}
}
func NewHistoryStorage() *HistoryStorage {
@@ -31,7 +30,7 @@ func NewHistoryStorage() *HistoryStorage {
}
}
func (s *HistoryStorage) SetHook(hook *observable.Subscriber[struct{}]) {
func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
s.updateHook = hook
}
@@ -61,7 +60,10 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTes
func (s *HistoryStorage) notifyUpdated() {
updateHook := s.updateHook
if updateHook != nil {
updateHook.Emit(struct{}{})
select {
case updateHook <- struct{}{}:
default:
}
}
}

View File

@@ -4,5 +4,5 @@ import "time"
const (
DHCPTTL = time.Hour
DHCPTimeout = 5 * time.Second
DHCPTimeout = time.Minute
)

View File

@@ -28,8 +28,6 @@ const (
TypeDERP = "derp"
TypeResolved = "resolved"
TypeSSMAPI = "ssm-api"
TypeCCM = "ccm"
TypeOCM = "ocm"
)
const (

View File

@@ -3,7 +3,7 @@ package constant
import "time"
const (
TCPKeepAliveInitial = 5 * time.Minute
TCPKeepAliveInitial = 10 * time.Minute
TCPKeepAliveInterval = 75 * time.Second
TCPConnectTimeout = 5 * time.Second
TCPTimeout = 15 * time.Second

View File

@@ -1,29 +0,0 @@
package daemon
import (
"sync"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing/common"
)
var _ deprecated.Manager = (*deprecatedManager)(nil)
type deprecatedManager struct {
access sync.Mutex
notes []deprecated.Note
}
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
m.access.Lock()
defer m.access.Unlock()
m.notes = common.Uniq(append(m.notes, feature))
}
func (m *deprecatedManager) Get() []deprecated.Note {
m.access.Lock()
defer m.access.Unlock()
notes := m.notes
m.notes = nil
return notes
}

View File

@@ -1,702 +0,0 @@
package daemon
import (
reflect "reflect"
sync "sync"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type SubscribeHelperRequestRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
AcceptGetWIFIStateRequests bool `protobuf:"varint,1,opt,name=acceptGetWIFIStateRequests,proto3" json:"acceptGetWIFIStateRequests,omitempty"`
AcceptFindConnectionOwnerRequests bool `protobuf:"varint,2,opt,name=acceptFindConnectionOwnerRequests,proto3" json:"acceptFindConnectionOwnerRequests,omitempty"`
AcceptSendNotificationRequests bool `protobuf:"varint,3,opt,name=acceptSendNotificationRequests,proto3" json:"acceptSendNotificationRequests,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SubscribeHelperRequestRequest) Reset() {
*x = SubscribeHelperRequestRequest{}
mi := &file_daemon_helper_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SubscribeHelperRequestRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribeHelperRequestRequest) ProtoMessage() {}
func (x *SubscribeHelperRequestRequest) ProtoReflect() protoreflect.Message {
mi := &file_daemon_helper_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribeHelperRequestRequest.ProtoReflect.Descriptor instead.
func (*SubscribeHelperRequestRequest) Descriptor() ([]byte, []int) {
return file_daemon_helper_proto_rawDescGZIP(), []int{0}
}
func (x *SubscribeHelperRequestRequest) GetAcceptGetWIFIStateRequests() bool {
if x != nil {
return x.AcceptGetWIFIStateRequests
}
return false
}
func (x *SubscribeHelperRequestRequest) GetAcceptFindConnectionOwnerRequests() bool {
if x != nil {
return x.AcceptFindConnectionOwnerRequests
}
return false
}
func (x *SubscribeHelperRequestRequest) GetAcceptSendNotificationRequests() bool {
if x != nil {
return x.AcceptSendNotificationRequests
}
return false
}
type HelperRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
// Types that are valid to be assigned to Request:
//
// *HelperRequest_GetWIFIState
// *HelperRequest_FindConnectionOwner
// *HelperRequest_SendNotification
Request isHelperRequest_Request `protobuf_oneof:"request"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HelperRequest) Reset() {
*x = HelperRequest{}
mi := &file_daemon_helper_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HelperRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelperRequest) ProtoMessage() {}
func (x *HelperRequest) ProtoReflect() protoreflect.Message {
mi := &file_daemon_helper_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelperRequest.ProtoReflect.Descriptor instead.
func (*HelperRequest) Descriptor() ([]byte, []int) {
return file_daemon_helper_proto_rawDescGZIP(), []int{1}
}
func (x *HelperRequest) GetId() int64 {
if x != nil {
return x.Id
}
return 0
}
func (x *HelperRequest) GetRequest() isHelperRequest_Request {
if x != nil {
return x.Request
}
return nil
}
func (x *HelperRequest) GetGetWIFIState() *emptypb.Empty {
if x != nil {
if x, ok := x.Request.(*HelperRequest_GetWIFIState); ok {
return x.GetWIFIState
}
}
return nil
}
func (x *HelperRequest) GetFindConnectionOwner() *FindConnectionOwnerRequest {
if x != nil {
if x, ok := x.Request.(*HelperRequest_FindConnectionOwner); ok {
return x.FindConnectionOwner
}
}
return nil
}
func (x *HelperRequest) GetSendNotification() *Notification {
if x != nil {
if x, ok := x.Request.(*HelperRequest_SendNotification); ok {
return x.SendNotification
}
}
return nil
}
type isHelperRequest_Request interface {
isHelperRequest_Request()
}
type HelperRequest_GetWIFIState struct {
GetWIFIState *emptypb.Empty `protobuf:"bytes,2,opt,name=getWIFIState,proto3,oneof"`
}
type HelperRequest_FindConnectionOwner struct {
FindConnectionOwner *FindConnectionOwnerRequest `protobuf:"bytes,3,opt,name=findConnectionOwner,proto3,oneof"`
}
type HelperRequest_SendNotification struct {
SendNotification *Notification `protobuf:"bytes,4,opt,name=sendNotification,proto3,oneof"`
}
func (*HelperRequest_GetWIFIState) isHelperRequest_Request() {}
func (*HelperRequest_FindConnectionOwner) isHelperRequest_Request() {}
func (*HelperRequest_SendNotification) isHelperRequest_Request() {}
type FindConnectionOwnerRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
IpProtocol int32 `protobuf:"varint,1,opt,name=ipProtocol,proto3" json:"ipProtocol,omitempty"`
SourceAddress string `protobuf:"bytes,2,opt,name=sourceAddress,proto3" json:"sourceAddress,omitempty"`
SourcePort int32 `protobuf:"varint,3,opt,name=sourcePort,proto3" json:"sourcePort,omitempty"`
DestinationAddress string `protobuf:"bytes,4,opt,name=destinationAddress,proto3" json:"destinationAddress,omitempty"`
DestinationPort int32 `protobuf:"varint,5,opt,name=destinationPort,proto3" json:"destinationPort,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *FindConnectionOwnerRequest) Reset() {
*x = FindConnectionOwnerRequest{}
mi := &file_daemon_helper_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FindConnectionOwnerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FindConnectionOwnerRequest) ProtoMessage() {}
func (x *FindConnectionOwnerRequest) ProtoReflect() protoreflect.Message {
mi := &file_daemon_helper_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FindConnectionOwnerRequest.ProtoReflect.Descriptor instead.
func (*FindConnectionOwnerRequest) Descriptor() ([]byte, []int) {
return file_daemon_helper_proto_rawDescGZIP(), []int{2}
}
func (x *FindConnectionOwnerRequest) GetIpProtocol() int32 {
if x != nil {
return x.IpProtocol
}
return 0
}
func (x *FindConnectionOwnerRequest) GetSourceAddress() string {
if x != nil {
return x.SourceAddress
}
return ""
}
func (x *FindConnectionOwnerRequest) GetSourcePort() int32 {
if x != nil {
return x.SourcePort
}
return 0
}
func (x *FindConnectionOwnerRequest) GetDestinationAddress() string {
if x != nil {
return x.DestinationAddress
}
return ""
}
func (x *FindConnectionOwnerRequest) GetDestinationPort() int32 {
if x != nil {
return x.DestinationPort
}
return 0
}
type Notification struct {
state protoimpl.MessageState `protogen:"open.v1"`
Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
TypeName string `protobuf:"bytes,2,opt,name=typeName,proto3" json:"typeName,omitempty"`
TypeId int32 `protobuf:"varint,3,opt,name=typeId,proto3" json:"typeId,omitempty"`
Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"`
Subtitle string `protobuf:"bytes,5,opt,name=subtitle,proto3" json:"subtitle,omitempty"`
Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"`
OpenURL string `protobuf:"bytes,7,opt,name=openURL,proto3" json:"openURL,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Notification) Reset() {
*x = Notification{}
mi := &file_daemon_helper_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Notification) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Notification) ProtoMessage() {}
func (x *Notification) ProtoReflect() protoreflect.Message {
mi := &file_daemon_helper_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Notification.ProtoReflect.Descriptor instead.
func (*Notification) Descriptor() ([]byte, []int) {
return file_daemon_helper_proto_rawDescGZIP(), []int{3}
}
func (x *Notification) GetIdentifier() string {
if x != nil {
return x.Identifier
}
return ""
}
func (x *Notification) GetTypeName() string {
if x != nil {
return x.TypeName
}
return ""
}
func (x *Notification) GetTypeId() int32 {
if x != nil {
return x.TypeId
}
return 0
}
func (x *Notification) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *Notification) GetSubtitle() string {
if x != nil {
return x.Subtitle
}
return ""
}
func (x *Notification) GetBody() string {
if x != nil {
return x.Body
}
return ""
}
func (x *Notification) GetOpenURL() string {
if x != nil {
return x.OpenURL
}
return ""
}
type HelperResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
// Types that are valid to be assigned to Response:
//
// *HelperResponse_WifiState
// *HelperResponse_Error
// *HelperResponse_ConnectionOwner
Response isHelperResponse_Response `protobuf_oneof:"response"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HelperResponse) Reset() {
*x = HelperResponse{}
mi := &file_daemon_helper_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HelperResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelperResponse) ProtoMessage() {}
func (x *HelperResponse) ProtoReflect() protoreflect.Message {
mi := &file_daemon_helper_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelperResponse.ProtoReflect.Descriptor instead.
func (*HelperResponse) Descriptor() ([]byte, []int) {
return file_daemon_helper_proto_rawDescGZIP(), []int{4}
}
func (x *HelperResponse) GetId() int64 {
if x != nil {
return x.Id
}
return 0
}
func (x *HelperResponse) GetResponse() isHelperResponse_Response {
if x != nil {
return x.Response
}
return nil
}
func (x *HelperResponse) GetWifiState() *WIFIState {
if x != nil {
if x, ok := x.Response.(*HelperResponse_WifiState); ok {
return x.WifiState
}
}
return nil
}
func (x *HelperResponse) GetError() string {
if x != nil {
if x, ok := x.Response.(*HelperResponse_Error); ok {
return x.Error
}
}
return ""
}
func (x *HelperResponse) GetConnectionOwner() *ConnectionOwner {
if x != nil {
if x, ok := x.Response.(*HelperResponse_ConnectionOwner); ok {
return x.ConnectionOwner
}
}
return nil
}
type isHelperResponse_Response interface {
isHelperResponse_Response()
}
type HelperResponse_WifiState struct {
WifiState *WIFIState `protobuf:"bytes,2,opt,name=wifiState,proto3,oneof"`
}
type HelperResponse_Error struct {
Error string `protobuf:"bytes,3,opt,name=error,proto3,oneof"`
}
type HelperResponse_ConnectionOwner struct {
ConnectionOwner *ConnectionOwner `protobuf:"bytes,4,opt,name=connectionOwner,proto3,oneof"`
}
func (*HelperResponse_WifiState) isHelperResponse_Response() {}
func (*HelperResponse_Error) isHelperResponse_Response() {}
func (*HelperResponse_ConnectionOwner) isHelperResponse_Response() {}
type ConnectionOwner struct {
state protoimpl.MessageState `protogen:"open.v1"`
UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"`
UserName string `protobuf:"bytes,2,opt,name=userName,proto3" json:"userName,omitempty"`
ProcessPath string `protobuf:"bytes,3,opt,name=processPath,proto3" json:"processPath,omitempty"`
AndroidPackageName string `protobuf:"bytes,4,opt,name=androidPackageName,proto3" json:"androidPackageName,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ConnectionOwner) Reset() {
*x = ConnectionOwner{}
mi := &file_daemon_helper_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ConnectionOwner) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConnectionOwner) ProtoMessage() {}
func (x *ConnectionOwner) ProtoReflect() protoreflect.Message {
mi := &file_daemon_helper_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConnectionOwner.ProtoReflect.Descriptor instead.
func (*ConnectionOwner) Descriptor() ([]byte, []int) {
return file_daemon_helper_proto_rawDescGZIP(), []int{5}
}
func (x *ConnectionOwner) GetUserId() int32 {
if x != nil {
return x.UserId
}
return 0
}
func (x *ConnectionOwner) GetUserName() string {
if x != nil {
return x.UserName
}
return ""
}
func (x *ConnectionOwner) GetProcessPath() string {
if x != nil {
return x.ProcessPath
}
return ""
}
func (x *ConnectionOwner) GetAndroidPackageName() string {
if x != nil {
return x.AndroidPackageName
}
return ""
}
type WIFIState struct {
state protoimpl.MessageState `protogen:"open.v1"`
Ssid string `protobuf:"bytes,1,opt,name=ssid,proto3" json:"ssid,omitempty"`
Bssid string `protobuf:"bytes,2,opt,name=bssid,proto3" json:"bssid,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WIFIState) Reset() {
*x = WIFIState{}
mi := &file_daemon_helper_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WIFIState) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WIFIState) ProtoMessage() {}
func (x *WIFIState) ProtoReflect() protoreflect.Message {
mi := &file_daemon_helper_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WIFIState.ProtoReflect.Descriptor instead.
func (*WIFIState) Descriptor() ([]byte, []int) {
return file_daemon_helper_proto_rawDescGZIP(), []int{6}
}
func (x *WIFIState) GetSsid() string {
if x != nil {
return x.Ssid
}
return ""
}
func (x *WIFIState) GetBssid() string {
if x != nil {
return x.Bssid
}
return ""
}
var File_daemon_helper_proto protoreflect.FileDescriptor
const file_daemon_helper_proto_rawDesc = "" +
"\n" +
"\x13daemon/helper.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"\xf5\x01\n" +
"\x1dSubscribeHelperRequestRequest\x12>\n" +
"\x1aacceptGetWIFIStateRequests\x18\x01 \x01(\bR\x1aacceptGetWIFIStateRequests\x12L\n" +
"!acceptFindConnectionOwnerRequests\x18\x02 \x01(\bR!acceptFindConnectionOwnerRequests\x12F\n" +
"\x1eacceptSendNotificationRequests\x18\x03 \x01(\bR\x1eacceptSendNotificationRequests\"\x84\x02\n" +
"\rHelperRequest\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12<\n" +
"\fgetWIFIState\x18\x02 \x01(\v2\x16.google.protobuf.EmptyH\x00R\fgetWIFIState\x12V\n" +
"\x13findConnectionOwner\x18\x03 \x01(\v2\".daemon.FindConnectionOwnerRequestH\x00R\x13findConnectionOwner\x12B\n" +
"\x10sendNotification\x18\x04 \x01(\v2\x14.daemon.NotificationH\x00R\x10sendNotificationB\t\n" +
"\arequest\"\xdc\x01\n" +
"\x1aFindConnectionOwnerRequest\x12\x1e\n" +
"\n" +
"ipProtocol\x18\x01 \x01(\x05R\n" +
"ipProtocol\x12$\n" +
"\rsourceAddress\x18\x02 \x01(\tR\rsourceAddress\x12\x1e\n" +
"\n" +
"sourcePort\x18\x03 \x01(\x05R\n" +
"sourcePort\x12.\n" +
"\x12destinationAddress\x18\x04 \x01(\tR\x12destinationAddress\x12(\n" +
"\x0fdestinationPort\x18\x05 \x01(\x05R\x0fdestinationPort\"\xc2\x01\n" +
"\fNotification\x12\x1e\n" +
"\n" +
"identifier\x18\x01 \x01(\tR\n" +
"identifier\x12\x1a\n" +
"\btypeName\x18\x02 \x01(\tR\btypeName\x12\x16\n" +
"\x06typeId\x18\x03 \x01(\x05R\x06typeId\x12\x14\n" +
"\x05title\x18\x04 \x01(\tR\x05title\x12\x1a\n" +
"\bsubtitle\x18\x05 \x01(\tR\bsubtitle\x12\x12\n" +
"\x04body\x18\x06 \x01(\tR\x04body\x12\x18\n" +
"\aopenURL\x18\a \x01(\tR\aopenURL\"\xbc\x01\n" +
"\x0eHelperResponse\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x121\n" +
"\twifiState\x18\x02 \x01(\v2\x11.daemon.WIFIStateH\x00R\twifiState\x12\x16\n" +
"\x05error\x18\x03 \x01(\tH\x00R\x05error\x12C\n" +
"\x0fconnectionOwner\x18\x04 \x01(\v2\x17.daemon.ConnectionOwnerH\x00R\x0fconnectionOwnerB\n" +
"\n" +
"\bresponse\"\x97\x01\n" +
"\x0fConnectionOwner\x12\x16\n" +
"\x06userId\x18\x01 \x01(\x05R\x06userId\x12\x1a\n" +
"\buserName\x18\x02 \x01(\tR\buserName\x12 \n" +
"\vprocessPath\x18\x03 \x01(\tR\vprocessPath\x12.\n" +
"\x12androidPackageName\x18\x04 \x01(\tR\x12androidPackageName\"5\n" +
"\tWIFIState\x12\x12\n" +
"\x04ssid\x18\x01 \x01(\tR\x04ssid\x12\x14\n" +
"\x05bssid\x18\x02 \x01(\tR\x05bssidB%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
var (
file_daemon_helper_proto_rawDescOnce sync.Once
file_daemon_helper_proto_rawDescData []byte
)
func file_daemon_helper_proto_rawDescGZIP() []byte {
file_daemon_helper_proto_rawDescOnce.Do(func() {
file_daemon_helper_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)))
})
return file_daemon_helper_proto_rawDescData
}
var (
file_daemon_helper_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
file_daemon_helper_proto_goTypes = []any{
(*SubscribeHelperRequestRequest)(nil), // 0: daemon.SubscribeHelperRequestRequest
(*HelperRequest)(nil), // 1: daemon.HelperRequest
(*FindConnectionOwnerRequest)(nil), // 2: daemon.FindConnectionOwnerRequest
(*Notification)(nil), // 3: daemon.Notification
(*HelperResponse)(nil), // 4: daemon.HelperResponse
(*ConnectionOwner)(nil), // 5: daemon.ConnectionOwner
(*WIFIState)(nil), // 6: daemon.WIFIState
(*emptypb.Empty)(nil), // 7: google.protobuf.Empty
}
)
var file_daemon_helper_proto_depIdxs = []int32{
7, // 0: daemon.HelperRequest.getWIFIState:type_name -> google.protobuf.Empty
2, // 1: daemon.HelperRequest.findConnectionOwner:type_name -> daemon.FindConnectionOwnerRequest
3, // 2: daemon.HelperRequest.sendNotification:type_name -> daemon.Notification
6, // 3: daemon.HelperResponse.wifiState:type_name -> daemon.WIFIState
5, // 4: daemon.HelperResponse.connectionOwner:type_name -> daemon.ConnectionOwner
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_daemon_helper_proto_init() }
func file_daemon_helper_proto_init() {
if File_daemon_helper_proto != nil {
return
}
file_daemon_helper_proto_msgTypes[1].OneofWrappers = []any{
(*HelperRequest_GetWIFIState)(nil),
(*HelperRequest_FindConnectionOwner)(nil),
(*HelperRequest_SendNotification)(nil),
}
file_daemon_helper_proto_msgTypes[4].OneofWrappers = []any{
(*HelperResponse_WifiState)(nil),
(*HelperResponse_Error)(nil),
(*HelperResponse_ConnectionOwner)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)),
NumEnums: 0,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_daemon_helper_proto_goTypes,
DependencyIndexes: file_daemon_helper_proto_depIdxs,
MessageInfos: file_daemon_helper_proto_msgTypes,
}.Build()
File_daemon_helper_proto = out.File
file_daemon_helper_proto_goTypes = nil
file_daemon_helper_proto_depIdxs = nil
}

View File

@@ -1,61 +0,0 @@
syntax = "proto3";
package daemon;
option go_package = "github.com/sagernet/sing-box/daemon";
import "google/protobuf/empty.proto";
message SubscribeHelperRequestRequest {
bool acceptGetWIFIStateRequests = 1;
bool acceptFindConnectionOwnerRequests = 2;
bool acceptSendNotificationRequests = 3;
}
message HelperRequest {
int64 id = 1;
oneof request {
google.protobuf.Empty getWIFIState = 2;
FindConnectionOwnerRequest findConnectionOwner = 3;
Notification sendNotification = 4;
}
}
message FindConnectionOwnerRequest {
int32 ipProtocol = 1;
string sourceAddress = 2;
int32 sourcePort = 3;
string destinationAddress = 4;
int32 destinationPort = 5;
}
message Notification {
string identifier = 1;
string typeName = 2;
int32 typeId = 3;
string title = 4;
string subtitle = 5;
string body = 6;
string openURL = 7;
}
message HelperResponse {
int64 id = 1;
oneof response {
WIFIState wifiState = 2;
string error = 3;
ConnectionOwner connectionOwner = 4;
}
}
message ConnectionOwner {
int32 userId = 1;
string userName = 2;
string processPath = 3;
string androidPackageName = 4;
}
message WIFIState {
string ssid = 1;
string bssid = 2;
}

View File

@@ -1,133 +0,0 @@
package daemon
import (
"bytes"
"context"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
type Instance struct {
ctx context.Context
cancel context.CancelFunc
instance *box.Box
clashServer adapter.ClashServer
cacheFile adapter.CacheFile
pauseManager pause.Manager
urlTestHistoryStorage *urltest.HistoryStorage
}
func (s *StartedService) CheckConfig(configContent string) error {
options, err := parseConfig(s.ctx, configContent)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(s.ctx)
defer cancel()
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
if err == nil {
instance.Close()
}
return err
}
func (s *StartedService) FormatConfig(configContent string) (string, error) {
options, err := parseConfig(s.ctx, configContent)
if err != nil {
return "", err
}
var buffer bytes.Buffer
encoder := json.NewEncoder(&buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(options)
if err != nil {
return "", err
}
return buffer.String(), nil
}
type OverrideOptions struct {
AutoRedirect bool
IncludePackage []string
ExcludePackage []string
}
func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) {
ctx := s.ctx
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
ctx, cancel := context.WithCancel(include.Context(ctx))
options, err := parseConfig(ctx, profileContent)
if err != nil {
cancel()
return nil, err
}
if overrideOptions != nil {
for _, inbound := range options.Inbounds {
if tunInboundOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN {
tunInboundOptions.AutoRedirect = overrideOptions.AutoRedirect
tunInboundOptions.IncludePackage = append(tunInboundOptions.IncludePackage, overrideOptions.IncludePackage...)
tunInboundOptions.ExcludePackage = append(tunInboundOptions.ExcludePackage, overrideOptions.ExcludePackage...)
break
}
}
}
urlTestHistoryStorage := urltest.NewHistoryStorage()
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
i := &Instance{
ctx: ctx,
cancel: cancel,
urlTestHistoryStorage: urlTestHistoryStorage,
}
boxInstance, err := box.New(box.Options{
Context: ctx,
Options: options,
PlatformLogWriter: s,
})
if err != nil {
cancel()
return nil, err
}
i.instance = boxInstance
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
i.pauseManager = service.FromContext[pause.Manager](ctx)
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
return i, nil
}
func (i *Instance) Start() error {
return i.instance.Start()
}
func (i *Instance) Close() error {
i.cancel()
i.urlTestHistoryStorage.Close()
return i.instance.Close()
}
func (i *Instance) Box() *box.Box {
return i.instance
}
func (i *Instance) PauseManager() pause.Manager {
return i.pauseManager
}
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
if err != nil {
return option.Options{}, E.Cause(err, "decode config")
}
return options, nil
}

View File

@@ -1,9 +0,0 @@
package daemon
type PlatformHandler interface {
ServiceStop() error
ServiceReload() error
SystemProxyStatus() (*SystemProxyStatus, error)
SetSystemProxyEnabled(enabled bool) error
WriteDebugMessage(message string)
}

View File

@@ -1,835 +0,0 @@
package daemon
import (
"context"
"os"
"runtime"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/experimental/clashapi"
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/protocol/group"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory"
"github.com/sagernet/sing/common/observable"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
"github.com/gofrs/uuid/v5"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
)
var _ StartedServiceServer = (*StartedService)(nil)
type StartedService struct {
ctx context.Context
// platform adapter.PlatformInterface
handler PlatformHandler
debug bool
logMaxLines int
// workingDirectory string
// tempDirectory string
// userID int
// groupID int
// systemProxyEnabled bool
serviceAccess sync.RWMutex
serviceStatus *ServiceStatus
serviceStatusSubscriber *observable.Subscriber[*ServiceStatus]
serviceStatusObserver *observable.Observer[*ServiceStatus]
logAccess sync.RWMutex
logLines list.List[*log.Entry]
logSubscriber *observable.Subscriber[*log.Entry]
logObserver *observable.Observer[*log.Entry]
instance *Instance
urlTestSubscriber *observable.Subscriber[struct{}]
urlTestObserver *observable.Observer[struct{}]
urlTestHistoryStorage *urltest.HistoryStorage
clashModeSubscriber *observable.Subscriber[struct{}]
clashModeObserver *observable.Observer[struct{}]
}
type ServiceOptions struct {
Context context.Context
// Platform adapter.PlatformInterface
Handler PlatformHandler
Debug bool
LogMaxLines int
// WorkingDirectory string
// TempDirectory string
// UserID int
// GroupID int
// SystemProxyEnabled bool
}
func NewStartedService(options ServiceOptions) *StartedService {
s := &StartedService{
ctx: options.Context,
// platform: options.Platform,
handler: options.Handler,
debug: options.Debug,
logMaxLines: options.LogMaxLines,
// workingDirectory: options.WorkingDirectory,
// tempDirectory: options.TempDirectory,
// userID: options.UserID,
// groupID: options.GroupID,
// systemProxyEnabled: options.SystemProxyEnabled,
serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE},
serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4),
logSubscriber: observable.NewSubscriber[*log.Entry](128),
urlTestSubscriber: observable.NewSubscriber[struct{}](1),
urlTestHistoryStorage: urltest.NewHistoryStorage(),
clashModeSubscriber: observable.NewSubscriber[struct{}](1),
}
s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2)
s.logObserver = observable.NewObserver(s.logSubscriber, 64)
s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1)
s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1)
return s
}
func (s *StartedService) resetLogs() {
s.logAccess.Lock()
s.logLines = list.List[*log.Entry]{}
s.logAccess.Unlock()
s.logSubscriber.Emit(nil)
}
func (s *StartedService) updateStatus(newStatus ServiceStatus_Type) {
statusObject := &ServiceStatus{Status: newStatus}
s.serviceStatusSubscriber.Emit(statusObject)
s.serviceStatus = statusObject
}
func (s *StartedService) updateStatusError(err error) error {
statusObject := &ServiceStatus{Status: ServiceStatus_FATAL, ErrorMessage: err.Error()}
s.serviceStatusSubscriber.Emit(statusObject)
s.serviceStatus = statusObject
s.serviceAccess.Unlock()
return err
}
func (s *StartedService) waitForStarted(ctx context.Context) error {
s.serviceAccess.RLock()
currentStatus := s.serviceStatus.Status
s.serviceAccess.RUnlock()
switch currentStatus {
case ServiceStatus_STARTED:
return nil
case ServiceStatus_STARTING:
default:
return os.ErrInvalid
}
subscription, done, err := s.serviceStatusObserver.Subscribe()
if err != nil {
return err
}
defer s.serviceStatusObserver.UnSubscribe(subscription)
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-s.ctx.Done():
return s.ctx.Err()
case status := <-subscription:
switch status.Status {
case ServiceStatus_STARTED:
return nil
case ServiceStatus_FATAL:
return E.New(status.ErrorMessage)
case ServiceStatus_IDLE, ServiceStatus_STOPPING:
return os.ErrInvalid
}
case <-done:
return os.ErrClosed
}
}
}
func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error {
s.serviceAccess.Lock()
switch s.serviceStatus.Status {
case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING:
default:
s.serviceAccess.Unlock()
return os.ErrInvalid
}
oldInstance := s.instance
if oldInstance != nil {
s.updateStatus(ServiceStatus_STOPPING)
s.serviceAccess.Unlock()
_ = oldInstance.Close()
s.serviceAccess.Lock()
}
s.updateStatus(ServiceStatus_STARTING)
s.resetLogs()
instance, err := s.newInstance(profileContent, options)
if err != nil {
return s.updateStatusError(err)
}
s.instance = instance
instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber)
if instance.clashServer != nil {
instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber)
}
s.serviceAccess.Unlock()
err = instance.Start()
s.serviceAccess.Lock()
if s.serviceStatus.Status != ServiceStatus_STARTING {
s.serviceAccess.Unlock()
return nil
}
if err != nil {
return s.updateStatusError(err)
}
s.updateStatus(ServiceStatus_STARTED)
s.serviceAccess.Unlock()
runtime.GC()
return nil
}
func (s *StartedService) CloseService() error {
s.serviceAccess.Lock()
switch s.serviceStatus.Status {
case ServiceStatus_STARTING, ServiceStatus_STARTED:
default:
s.serviceAccess.Unlock()
return os.ErrInvalid
}
s.updateStatus(ServiceStatus_STOPPING)
if s.instance != nil {
err := s.instance.Close()
if err != nil {
return s.updateStatusError(err)
}
}
s.instance = nil
s.updateStatus(ServiceStatus_IDLE)
s.serviceAccess.Unlock()
runtime.GC()
return nil
}
func (s *StartedService) SetError(err error) {
s.serviceAccess.Lock()
s.updateStatusError(err)
s.WriteMessage(log.LevelError, err.Error())
}
func (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
err := s.handler.ServiceStop()
if err != nil {
return nil, err
}
return &emptypb.Empty{}, nil
}
func (s *StartedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
err := s.handler.ServiceReload()
if err != nil {
return nil, err
}
return &emptypb.Empty{}, nil
}
func (s *StartedService) SubscribeServiceStatus(empty *emptypb.Empty, server grpc.ServerStreamingServer[ServiceStatus]) error {
subscription, done, err := s.serviceStatusObserver.Subscribe()
if err != nil {
return err
}
defer s.serviceStatusObserver.UnSubscribe(subscription)
err = server.Send(s.serviceStatus)
if err != nil {
return err
}
for {
select {
case <-s.ctx.Done():
return s.ctx.Err()
case <-server.Context().Done():
return server.Context().Err()
case newStatus := <-subscription:
err = server.Send(newStatus)
if err != nil {
return err
}
case <-done:
return nil
}
}
}
func (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerStreamingServer[Log]) error {
var savedLines []*log.Entry
s.logAccess.Lock()
savedLines = make([]*log.Entry, 0, s.logLines.Len())
for element := s.logLines.Front(); element != nil; element = element.Next() {
savedLines = append(savedLines, element.Value)
}
subscription, done, err := s.logObserver.Subscribe()
s.logAccess.Unlock()
if err != nil {
return err
}
defer s.logObserver.UnSubscribe(subscription)
err = server.Send(&Log{
Messages: common.Map(savedLines, func(it *log.Entry) *Log_Message {
return &Log_Message{
Level: LogLevel(it.Level),
Message: it.Message,
}
}),
Reset_: true,
})
if err != nil {
return err
}
for {
select {
case <-s.ctx.Done():
return s.ctx.Err()
case <-server.Context().Done():
return server.Context().Err()
case message := <-subscription:
var rawMessage Log
if message == nil {
rawMessage.Reset_ = true
} else {
rawMessage.Messages = append(rawMessage.Messages, &Log_Message{
Level: LogLevel(message.Level),
Message: message.Message,
})
}
fetch:
for {
select {
case message = <-subscription:
if message == nil {
rawMessage.Messages = nil
rawMessage.Reset_ = true
} else {
rawMessage.Messages = append(rawMessage.Messages, &Log_Message{
Level: LogLevel(message.Level),
Message: message.Message,
})
}
default:
break fetch
}
}
err = server.Send(&rawMessage)
if err != nil {
return err
}
case <-done:
return nil
}
}
}
func (s *StartedService) GetDefaultLogLevel(ctx context.Context, empty *emptypb.Empty) (*DefaultLogLevel, error) {
s.serviceAccess.RLock()
switch s.serviceStatus.Status {
case ServiceStatus_STARTING, ServiceStatus_STARTED:
default:
s.serviceAccess.RUnlock()
return nil, os.ErrInvalid
}
logLevel := s.instance.instance.LogFactory().Level()
s.serviceAccess.RUnlock()
return &DefaultLogLevel{Level: LogLevel(logLevel)}, nil
}
func (s *StartedService) ClearLogs(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
s.resetLogs()
return &emptypb.Empty{}, nil
}
func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server grpc.ServerStreamingServer[Status]) error {
interval := time.Duration(request.Interval)
if interval <= 0 {
interval = time.Second // Default to 1 second
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
status := s.readStatus()
uploadTotal := status.UplinkTotal
downloadTotal := status.DownlinkTotal
for {
err := server.Send(status)
if err != nil {
return err
}
select {
case <-s.ctx.Done():
return s.ctx.Err()
case <-server.Context().Done():
return server.Context().Err()
case <-ticker.C:
}
status = s.readStatus()
upload := status.UplinkTotal - uploadTotal
download := status.DownlinkTotal - downloadTotal
uploadTotal = status.UplinkTotal
downloadTotal = status.DownlinkTotal
status.Uplink = upload
status.Downlink = download
}
}
func (s *StartedService) readStatus() *Status {
var status Status
status.Memory = memory.Inuse()
status.Goroutines = int32(runtime.NumGoroutine())
status.ConnectionsOut = int32(conntrack.Count())
s.serviceAccess.RLock()
nowService := s.instance
s.serviceAccess.RUnlock()
if nowService != nil {
if clashServer := nowService.clashServer; clashServer != nil {
status.TrafficAvailable = true
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
status.UplinkTotal, status.DownlinkTotal = trafficManager.Total()
status.ConnectionsIn = int32(trafficManager.ConnectionsLen())
}
}
return &status
}
func (s *StartedService) SubscribeGroups(empty *emptypb.Empty, server grpc.ServerStreamingServer[Groups]) error {
err := s.waitForStarted(server.Context())
if err != nil {
return err
}
subscription, done, err := s.urlTestObserver.Subscribe()
if err != nil {
return err
}
defer s.urlTestObserver.UnSubscribe(subscription)
for {
s.serviceAccess.RLock()
if s.serviceStatus.Status != ServiceStatus_STARTED {
s.serviceAccess.RUnlock()
return os.ErrInvalid
}
groups := s.readGroups()
s.serviceAccess.RUnlock()
err = server.Send(groups)
if err != nil {
return err
}
select {
case <-subscription:
case <-s.ctx.Done():
return s.ctx.Err()
case <-server.Context().Done():
return server.Context().Err()
case <-done:
return nil
}
}
}
func (s *StartedService) readGroups() *Groups {
historyStorage := s.instance.urlTestHistoryStorage
boxService := s.instance
outbounds := boxService.instance.Outbound().Outbounds()
var iGroups []adapter.OutboundGroup
for _, it := range outbounds {
if group, isGroup := it.(adapter.OutboundGroup); isGroup {
iGroups = append(iGroups, group)
}
}
var gs Groups
for _, iGroup := range iGroups {
var g Group
g.Tag = iGroup.Tag()
g.Type = iGroup.Type()
_, g.Selectable = iGroup.(*group.Selector)
g.Selected = iGroup.Now()
if boxService.cacheFile != nil {
if isExpand, loaded := boxService.cacheFile.LoadGroupExpand(g.Tag); loaded {
g.IsExpand = isExpand
}
}
for _, itemTag := range iGroup.All() {
itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag)
if !isLoaded {
continue
}
var item GroupItem
item.Tag = itemTag
item.Type = itemOutbound.Type()
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil {
item.UrlTestTime = history.Time.Unix()
item.UrlTestDelay = int32(history.Delay)
}
g.Items = append(g.Items, &item)
}
if len(g.Items) < 2 {
continue
}
gs.Group = append(gs.Group, &g)
}
return &gs
}
func (s *StartedService) GetClashModeStatus(ctx context.Context, empty *emptypb.Empty) (*ClashModeStatus, error) {
s.serviceAccess.RLock()
if s.serviceStatus.Status != ServiceStatus_STARTED {
s.serviceAccess.RUnlock()
return nil, os.ErrInvalid
}
clashServer := s.instance.clashServer
s.serviceAccess.RUnlock()
if clashServer == nil {
return nil, os.ErrInvalid
}
return &ClashModeStatus{
ModeList: clashServer.ModeList(),
CurrentMode: clashServer.Mode(),
}, nil
}
func (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.ServerStreamingServer[ClashMode]) error {
err := s.waitForStarted(server.Context())
if err != nil {
return err
}
subscription, done, err := s.clashModeObserver.Subscribe()
if err != nil {
return err
}
defer s.clashModeObserver.UnSubscribe(subscription)
for {
s.serviceAccess.RLock()
if s.serviceStatus.Status != ServiceStatus_STARTED {
s.serviceAccess.RUnlock()
return os.ErrInvalid
}
message := &ClashMode{Mode: s.instance.clashServer.Mode()}
s.serviceAccess.RUnlock()
err = server.Send(message)
if err != nil {
return err
}
select {
case <-subscription:
case <-s.ctx.Done():
return s.ctx.Err()
case <-server.Context().Done():
return server.Context().Err()
case <-done:
return nil
}
}
}
func (s *StartedService) SetClashMode(ctx context.Context, request *ClashMode) (*emptypb.Empty, error) {
s.serviceAccess.RLock()
if s.serviceStatus.Status != ServiceStatus_STARTED {
s.serviceAccess.RUnlock()
return nil, os.ErrInvalid
}
clashServer := s.instance.clashServer
s.serviceAccess.RUnlock()
clashServer.(*clashapi.Server).SetMode(request.Mode)
return &emptypb.Empty{}, nil
}
func (s *StartedService) URLTest(ctx context.Context, request *URLTestRequest) (*emptypb.Empty, error) {
s.serviceAccess.RLock()
if s.serviceStatus.Status != ServiceStatus_STARTED {
s.serviceAccess.RUnlock()
return nil, os.ErrInvalid
}
boxService := s.instance
s.serviceAccess.RUnlock()
groupTag := request.OutboundTag
abstractOutboundGroup, isLoaded := boxService.instance.Outbound().Outbound(groupTag)
if !isLoaded {
return nil, E.New("outbound group not found: ", groupTag)
}
outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup)
if !isOutboundGroup {
return nil, E.New("outbound is not a group: ", groupTag)
}
urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest)
if isURLTest {
go urlTest.CheckOutbounds()
} else {
historyStorage := boxService.urlTestHistoryStorage
outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
itOutbound, _ := boxService.instance.Outbound().Outbound(it)
return itOutbound
}), func(it adapter.Outbound) bool {
if it == nil {
return false
}
_, isGroup := it.(adapter.OutboundGroup)
if isGroup {
return false
}
return true
})
b, _ := batch.New(boxService.ctx, batch.WithConcurrencyNum[any](10))
for _, detour := range outbounds {
outboundToTest := detour
outboundTag := outboundToTest.Tag()
b.Go(outboundTag, func() (any, error) {
t, err := urltest.URLTest(boxService.ctx, "", outboundToTest)
if err != nil {
historyStorage.DeleteURLTestHistory(outboundTag)
} else {
historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{
Time: time.Now(),
Delay: t,
})
}
return nil, nil
})
}
}
return &emptypb.Empty{}, nil
}
func (s *StartedService) SelectOutbound(ctx context.Context, request *SelectOutboundRequest) (*emptypb.Empty, error) {
s.serviceAccess.RLock()
switch s.serviceStatus.Status {
case ServiceStatus_STARTING, ServiceStatus_STARTED:
default:
s.serviceAccess.RUnlock()
return nil, os.ErrInvalid
}
boxService := s.instance.instance
s.serviceAccess.RUnlock()
outboundGroup, isLoaded := boxService.Outbound().Outbound(request.GroupTag)
if !isLoaded {
return nil, E.New("selector not found: ", request.GroupTag)
}
selector, isSelector := outboundGroup.(*group.Selector)
if !isSelector {
return nil, E.New("outbound is not a selector: ", request.GroupTag)
}
if !selector.SelectOutbound(request.OutboundTag) {
return nil, E.New("outbound not found in selector: ", request.OutboundTag)
}
s.urlTestObserver.Emit(struct{}{})
return &emptypb.Empty{}, nil
}
func (s *StartedService) SetGroupExpand(ctx context.Context, request *SetGroupExpandRequest) (*emptypb.Empty, error) {
s.serviceAccess.RLock()
switch s.serviceStatus.Status {
case ServiceStatus_STARTING, ServiceStatus_STARTED:
default:
s.serviceAccess.RUnlock()
return nil, os.ErrInvalid
}
boxService := s.instance
s.serviceAccess.RUnlock()
if boxService.cacheFile != nil {
err := boxService.cacheFile.StoreGroupExpand(request.GroupTag, request.IsExpand)
if err != nil {
return nil, err
}
}
return &emptypb.Empty{}, nil
}
func (s *StartedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) {
return s.handler.SystemProxyStatus()
}
func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
err := s.handler.SetSystemProxyEnabled(request.Enabled)
if err != nil {
return nil, err
}
return nil, err
}
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error {
err := s.waitForStarted(server.Context())
if err != nil {
return err
}
s.serviceAccess.RLock()
boxService := s.instance
s.serviceAccess.RUnlock()
ticker := time.NewTicker(time.Duration(request.Interval))
defer ticker.Stop()
trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager()
var (
connections = make(map[uuid.UUID]*Connection)
outConnections []*Connection
)
for {
outConnections = outConnections[:0]
for _, connection := range trafficManager.Connections() {
outConnections = append(outConnections, newConnection(connections, connection, false))
}
for _, connection := range trafficManager.ClosedConnections() {
outConnections = append(outConnections, newConnection(connections, connection, true))
}
err := server.Send(&Connections{Connections: outConnections})
if err != nil {
return err
}
select {
case <-s.ctx.Done():
return s.ctx.Err()
case <-server.Context().Done():
return server.Context().Err()
case <-ticker.C:
}
}
}
func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) *Connection {
if oldConnection, loaded := connections[metadata.ID]; loaded {
if isClosed {
if oldConnection.ClosedAt == 0 {
oldConnection.Uplink = 0
oldConnection.Downlink = 0
oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli()
}
return oldConnection
}
lastUplink := oldConnection.UplinkTotal
lastDownlink := oldConnection.DownlinkTotal
uplinkTotal := metadata.Upload.Load()
downlinkTotal := metadata.Download.Load()
oldConnection.Uplink = uplinkTotal - lastUplink
oldConnection.Downlink = downlinkTotal - lastDownlink
oldConnection.UplinkTotal = uplinkTotal
oldConnection.DownlinkTotal = downlinkTotal
return oldConnection
}
var rule string
if metadata.Rule != nil {
rule = metadata.Rule.String()
}
uplinkTotal := metadata.Upload.Load()
downlinkTotal := metadata.Download.Load()
uplink := uplinkTotal
downlink := downlinkTotal
var closedAt int64
if !metadata.ClosedAt.IsZero() {
closedAt = metadata.ClosedAt.UnixMilli()
uplink = 0
downlink = 0
}
connection := &Connection{
Id: metadata.ID.String(),
Inbound: metadata.Metadata.Inbound,
InboundType: metadata.Metadata.InboundType,
IpVersion: int32(metadata.Metadata.IPVersion),
Network: metadata.Metadata.Network,
Source: metadata.Metadata.Source.String(),
Destination: metadata.Metadata.Destination.String(),
Domain: metadata.Metadata.Domain,
Protocol: metadata.Metadata.Protocol,
User: metadata.Metadata.User,
FromOutbound: metadata.Metadata.Outbound,
CreatedAt: metadata.CreatedAt.UnixMilli(),
ClosedAt: closedAt,
Uplink: uplink,
Downlink: downlink,
UplinkTotal: uplinkTotal,
DownlinkTotal: downlinkTotal,
Rule: rule,
Outbound: metadata.Outbound,
OutboundType: metadata.OutboundType,
ChainList: metadata.Chain,
}
connections[metadata.ID] = connection
return connection
}
func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) {
s.serviceAccess.RLock()
switch s.serviceStatus.Status {
case ServiceStatus_STARTING, ServiceStatus_STARTED:
default:
s.serviceAccess.RUnlock()
return nil, os.ErrInvalid
}
boxService := s.instance
s.serviceAccess.RUnlock()
targetConn := boxService.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(request.Id))
if targetConn != nil {
targetConn.Close()
}
return &emptypb.Empty{}, nil
}
func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
conntrack.Close()
return &emptypb.Empty{}, nil
}
func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *emptypb.Empty) (*DeprecatedWarnings, error) {
s.serviceAccess.RLock()
if s.serviceStatus.Status != ServiceStatus_STARTED {
s.serviceAccess.RUnlock()
return nil, os.ErrInvalid
}
boxService := s.instance
s.serviceAccess.RUnlock()
notes := service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get()
return &DeprecatedWarnings{
Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning {
return &DeprecatedWarning{
Message: it.Message(),
Impending: it.Impending(),
MigrationLink: it.MigrationLink,
}
}),
}, nil
}
func (s *StartedService) SubscribeHelperEvents(empty *emptypb.Empty, server grpc.ServerStreamingServer[HelperRequest]) error {
return os.ErrInvalid
}
func (s *StartedService) SendHelperResponse(ctx context.Context, response *HelperResponse) (*emptypb.Empty, error) {
return nil, os.ErrInvalid
}
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
}
func (s *StartedService) WriteMessage(level log.Level, message string) {
item := &log.Entry{Level: level, Message: message}
s.logAccess.Lock()
s.logLines.PushBack(item)
if s.logLines.Len() > s.logMaxLines {
s.logLines.Remove(s.logLines.Front())
}
s.logAccess.Unlock()
s.logSubscriber.Emit(item)
if s.debug {
s.handler.WriteDebugMessage(message)
}
}
func (s *StartedService) Instance() *Instance {
s.serviceAccess.RLock()
defer s.serviceAccess.RUnlock()
return s.instance
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,205 +0,0 @@
syntax = "proto3";
package daemon;
option go_package = "github.com/sagernet/sing-box/daemon";
import "google/protobuf/empty.proto";
import "daemon/helper.proto";
service StartedService {
rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty);
rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty);
rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {}
rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {}
rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {}
rpc ClearLogs(google.protobuf.Empty) returns(google.protobuf.Empty) {}
rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {}
rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {}
rpc GetClashModeStatus(google.protobuf.Empty) returns(ClashModeStatus) {}
rpc SubscribeClashMode(google.protobuf.Empty) returns(stream ClashMode) {}
rpc SetClashMode(ClashMode) returns(google.protobuf.Empty) {}
rpc URLTest(URLTestRequest) returns(google.protobuf.Empty) {}
rpc SelectOutbound(SelectOutboundRequest) returns (google.protobuf.Empty) {}
rpc SetGroupExpand(SetGroupExpandRequest) returns (google.protobuf.Empty) {}
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream Connections) {}
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
rpc SubscribeHelperEvents(google.protobuf.Empty) returns(stream HelperRequest) {}
rpc SendHelperResponse(HelperResponse) returns(google.protobuf.Empty) {}
}
message ServiceStatus {
enum Type {
IDLE = 0;
STARTING = 1;
STARTED = 2;
STOPPING = 3;
FATAL = 4;
}
Type status = 1;
string errorMessage = 2;
}
message ReloadServiceRequest {
string newProfileContent = 1;
}
message SubscribeStatusRequest {
int64 interval = 1;
}
enum LogLevel {
PANIC = 0;
FATAL = 1;
ERROR = 2;
WARN = 3;
INFO = 4;
DEBUG = 5;
TRACE = 6;
}
message Log {
repeated Message messages = 1;
bool reset = 2;
message Message {
LogLevel level = 1;
string message = 2;
}
}
message DefaultLogLevel {
LogLevel level = 1;
}
message Status {
uint64 memory = 1;
int32 goroutines = 2;
int32 connectionsIn = 3;
int32 connectionsOut = 4;
bool trafficAvailable = 5;
int64 uplink = 6;
int64 downlink = 7;
int64 uplinkTotal = 8;
int64 downlinkTotal = 9;
}
message Groups {
repeated Group group = 1;
}
message Group {
string tag = 1;
string type = 2;
bool selectable = 3;
string selected = 4;
bool isExpand = 5;
repeated GroupItem items = 6;
}
message GroupItem {
string tag = 1;
string type = 2;
int64 urlTestTime = 3;
int32 urlTestDelay = 4;
}
message URLTestRequest {
string outboundTag = 1;
}
message SelectOutboundRequest {
string groupTag = 1;
string outboundTag = 2;
}
message SetGroupExpandRequest {
string groupTag = 1;
bool isExpand = 2;
}
message ClashMode {
string mode = 3;
}
message ClashModeStatus {
repeated string modeList = 1;
string currentMode = 2;
}
message SystemProxyStatus {
bool available = 1;
bool enabled = 2;
}
message SetSystemProxyEnabledRequest {
bool enabled = 1;
}
message SubscribeConnectionsRequest {
int64 interval = 1;
ConnectionFilter filter = 2;
ConnectionSortBy sortBy = 3;
}
enum ConnectionFilter {
ALL = 0;
ACTIVE = 1;
CLOSED = 2;
}
enum ConnectionSortBy {
DATE = 0;
TRAFFIC = 1;
TOTAL_TRAFFIC = 2;
}
message Connections {
repeated Connection connections = 1;
}
message Connection {
string id = 1;
string inbound = 2;
string inboundType = 3;
int32 ipVersion = 4;
string network = 5;
string source = 6;
string destination = 7;
string domain = 8;
string protocol = 9;
string user = 10;
string fromOutbound = 11;
int64 createdAt = 12;
int64 closedAt = 13;
int64 uplink = 14;
int64 downlink = 15;
int64 uplinkTotal = 16;
int64 downlinkTotal = 17;
string rule = 18;
string outbound = 19;
string outboundType = 20;
repeated string chainList = 21;
}
message CloseConnectionRequest {
string id = 1;
}
message DeprecatedWarnings {
repeated DeprecatedWarning warnings = 1;
}
message DeprecatedWarning {
string message = 1;
bool impending = 2;
string migrationLink = 3;
}

View File

@@ -1,958 +0,0 @@
package daemon
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
StartedService_SubscribeHelperEvents_FullMethodName = "/daemon.StartedService/SubscribeHelperEvents"
StartedService_SendHelperResponse_FullMethodName = "/daemon.StartedService/SendHelperResponse"
)
// StartedServiceClient is the client API for StartedService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type StartedServiceClient interface {
StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error)
SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error)
GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error)
ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error)
SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error)
GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error)
SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error)
SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error)
URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error)
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error)
SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error)
}
type startedServiceClient struct {
cc grpc.ClientConnInterface
}
func NewStartedServiceClient(cc grpc.ClientConnInterface) StartedServiceClient {
return &startedServiceClient{cc}
}
func (c *startedServiceClient) StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_StopService_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_ReloadService_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[0], StartedService_SubscribeServiceStatus_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[emptypb.Empty, ServiceStatus]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeServiceStatusClient = grpc.ServerStreamingClient[ServiceStatus]
func (c *startedServiceClient) SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[1], StartedService_SubscribeLog_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[emptypb.Empty, Log]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeLogClient = grpc.ServerStreamingClient[Log]
func (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DefaultLogLevel)
err := c.cc.Invoke(ctx, StartedService_GetDefaultLogLevel_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_ClearLogs_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SubscribeStatusRequest, Status]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeStatusClient = grpc.ServerStreamingClient[Status]
func (c *startedServiceClient) SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[3], StartedService_SubscribeGroups_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[emptypb.Empty, Groups]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeGroupsClient = grpc.ServerStreamingClient[Groups]
func (c *startedServiceClient) GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ClashModeStatus)
err := c.cc.Invoke(ctx, StartedService_GetClashModeStatus_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[4], StartedService_SubscribeClashMode_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[emptypb.Empty, ClashMode]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeClashModeClient = grpc.ServerStreamingClient[ClashMode]
func (c *startedServiceClient) SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_SetClashMode_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_URLTest_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_SelectOutbound_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_SetGroupExpand_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SystemProxyStatus)
err := c.cc.Invoke(ctx, StartedService_GetSystemProxyStatus_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_SetSystemProxyEnabled_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SubscribeConnectionsRequest, Connections]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[Connections]
func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_CloseConnection_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_CloseAllConnections_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DeprecatedWarnings)
err := c.cc.Invoke(ctx, StartedService_GetDeprecatedWarnings_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *startedServiceClient) SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeHelperEvents_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[emptypb.Empty, HelperRequest]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeHelperEventsClient = grpc.ServerStreamingClient[HelperRequest]
func (c *startedServiceClient) SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, StartedService_SendHelperResponse_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// StartedServiceServer is the server API for StartedService service.
// All implementations must embed UnimplementedStartedServiceServer
// for forward compatibility.
type StartedServiceServer interface {
StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error
SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error
GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error)
ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error
SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error
GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error)
SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error
SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error)
URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error)
SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error)
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error
SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error)
mustEmbedUnimplementedStartedServiceServer()
}
// UnimplementedStartedServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedStartedServiceServer struct{}
func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method StopService not implemented")
}
func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReloadService not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error {
return status.Errorf(codes.Unimplemented, "method SubscribeServiceStatus not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error {
return status.Errorf(codes.Unimplemented, "method SubscribeLog not implemented")
}
func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDefaultLogLevel not implemented")
}
func (UnimplementedStartedServiceServer) ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClearLogs not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error {
return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error {
return status.Errorf(codes.Unimplemented, "method SubscribeGroups not implemented")
}
func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetClashModeStatus not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error {
return status.Errorf(codes.Unimplemented, "method SubscribeClashMode not implemented")
}
func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetClashMode not implemented")
}
func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method URLTest not implemented")
}
func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SelectOutbound not implemented")
}
func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetGroupExpand not implemented")
}
func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSystemProxyStatus not implemented")
}
func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error {
return status.Errorf(codes.Unimplemented, "method SubscribeConnections not implemented")
}
func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method CloseConnection not implemented")
}
func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method CloseAllConnections not implemented")
}
func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDeprecatedWarnings not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error {
return status.Errorf(codes.Unimplemented, "method SubscribeHelperEvents not implemented")
}
func (UnimplementedStartedServiceServer) SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendHelperResponse not implemented")
}
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
// UnsafeStartedServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to StartedServiceServer will
// result in compilation errors.
type UnsafeStartedServiceServer interface {
mustEmbedUnimplementedStartedServiceServer()
}
func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) {
// If the following call pancis, it indicates UnimplementedStartedServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&StartedService_ServiceDesc, srv)
}
func _StartedService_StopService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).StopService(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_StopService_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).StopService(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_ReloadService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).ReloadService(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_ReloadService_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).ReloadService(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_SubscribeServiceStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(emptypb.Empty)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StartedServiceServer).SubscribeServiceStatus(m, &grpc.GenericServerStream[emptypb.Empty, ServiceStatus]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeServiceStatusServer = grpc.ServerStreamingServer[ServiceStatus]
func _StartedService_SubscribeLog_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(emptypb.Empty)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StartedServiceServer).SubscribeLog(m, &grpc.GenericServerStream[emptypb.Empty, Log]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeLogServer = grpc.ServerStreamingServer[Log]
func _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_GetDefaultLogLevel_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_ClearLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).ClearLogs(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_ClearLogs_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).ClearLogs(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeStatusRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StartedServiceServer).SubscribeStatus(m, &grpc.GenericServerStream[SubscribeStatusRequest, Status]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeStatusServer = grpc.ServerStreamingServer[Status]
func _StartedService_SubscribeGroups_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(emptypb.Empty)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StartedServiceServer).SubscribeGroups(m, &grpc.GenericServerStream[emptypb.Empty, Groups]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeGroupsServer = grpc.ServerStreamingServer[Groups]
func _StartedService_GetClashModeStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).GetClashModeStatus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_GetClashModeStatus_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).GetClashModeStatus(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_SubscribeClashMode_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(emptypb.Empty)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StartedServiceServer).SubscribeClashMode(m, &grpc.GenericServerStream[emptypb.Empty, ClashMode]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeClashModeServer = grpc.ServerStreamingServer[ClashMode]
func _StartedService_SetClashMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ClashMode)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).SetClashMode(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_SetClashMode_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).SetClashMode(ctx, req.(*ClashMode))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_URLTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(URLTestRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).URLTest(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_URLTest_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).URLTest(ctx, req.(*URLTestRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_SelectOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SelectOutboundRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).SelectOutbound(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_SelectOutbound_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).SelectOutbound(ctx, req.(*SelectOutboundRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_SetGroupExpand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetGroupExpandRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).SetGroupExpand(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_SetGroupExpand_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).SetGroupExpand(ctx, req.(*SetGroupExpandRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_GetSystemProxyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_GetSystemProxyStatus_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetSystemProxyEnabledRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_SetSystemProxyEnabled_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, req.(*SetSystemProxyEnabledRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeConnectionsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, Connections]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[Connections]
func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CloseConnectionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).CloseConnection(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_CloseConnection_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).CloseConnection(ctx, req.(*CloseConnectionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_CloseAllConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).CloseAllConnections(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_CloseAllConnections_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).CloseAllConnections(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_GetDeprecatedWarnings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_GetDeprecatedWarnings_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _StartedService_SubscribeHelperEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(emptypb.Empty)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StartedServiceServer).SubscribeHelperEvents(m, &grpc.GenericServerStream[emptypb.Empty, HelperRequest]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_SubscribeHelperEventsServer = grpc.ServerStreamingServer[HelperRequest]
func _StartedService_SendHelperResponse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelperResponse)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StartedServiceServer).SendHelperResponse(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StartedService_SendHelperResponse_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StartedServiceServer).SendHelperResponse(ctx, req.(*HelperResponse))
}
return interceptor(ctx, in, info, handler)
}
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var StartedService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "daemon.StartedService",
HandlerType: (*StartedServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "StopService",
Handler: _StartedService_StopService_Handler,
},
{
MethodName: "ReloadService",
Handler: _StartedService_ReloadService_Handler,
},
{
MethodName: "GetDefaultLogLevel",
Handler: _StartedService_GetDefaultLogLevel_Handler,
},
{
MethodName: "ClearLogs",
Handler: _StartedService_ClearLogs_Handler,
},
{
MethodName: "GetClashModeStatus",
Handler: _StartedService_GetClashModeStatus_Handler,
},
{
MethodName: "SetClashMode",
Handler: _StartedService_SetClashMode_Handler,
},
{
MethodName: "URLTest",
Handler: _StartedService_URLTest_Handler,
},
{
MethodName: "SelectOutbound",
Handler: _StartedService_SelectOutbound_Handler,
},
{
MethodName: "SetGroupExpand",
Handler: _StartedService_SetGroupExpand_Handler,
},
{
MethodName: "GetSystemProxyStatus",
Handler: _StartedService_GetSystemProxyStatus_Handler,
},
{
MethodName: "SetSystemProxyEnabled",
Handler: _StartedService_SetSystemProxyEnabled_Handler,
},
{
MethodName: "CloseConnection",
Handler: _StartedService_CloseConnection_Handler,
},
{
MethodName: "CloseAllConnections",
Handler: _StartedService_CloseAllConnections_Handler,
},
{
MethodName: "GetDeprecatedWarnings",
Handler: _StartedService_GetDeprecatedWarnings_Handler,
},
{
MethodName: "SendHelperResponse",
Handler: _StartedService_SendHelperResponse_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "SubscribeServiceStatus",
Handler: _StartedService_SubscribeServiceStatus_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeLog",
Handler: _StartedService_SubscribeLog_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeStatus",
Handler: _StartedService_SubscribeStatus_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeGroups",
Handler: _StartedService_SubscribeGroups_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeClashMode",
Handler: _StartedService_SubscribeClashMode_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeConnections",
Handler: _StartedService_SubscribeConnections_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeHelperEvents",
Handler: _StartedService_SubscribeHelperEvents_Handler,
ServerStreams: true,
},
},
Metadata: "daemon/started_service.proto",
}

View File

@@ -95,20 +95,6 @@ func (c *Client) Start() {
}
}
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
for _, record := range response.Ns {
if soa, isSOA := record.(*dns.SOA); isSOA {
soaTTL := soa.Header().Ttl
soaMinimum := soa.Minttl
if soaTTL < soaMinimum {
return soaTTL, true
}
return soaMinimum, true
}
}
return 0, false
}
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
if len(message.Question) == 0 {
if c.logger != nil {
@@ -228,7 +214,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
response.Answer = append(response.Answer, validResponse.Answer...)
}
}*/
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
disableCache = disableCache || response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0
if responseChecker != nil {
var rejected bool
// TODO: add accept_any rule and support to check response instead of addresses
@@ -265,17 +251,10 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
}
var timeToLive uint32
if len(response.Answer) == 0 {
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
timeToLive = soaTTL
}
}
if timeToLive == 0 {
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList {
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
timeToLive = record.Header().Ttl
}
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList {
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
timeToLive = record.Header().Ttl
}
}
}
@@ -385,18 +364,14 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}, nil)
if response4 == nil {
return nil, false
}
response6, _ := c.loadResponse(dns.Question{
Name: dnsName,
Qtype: dns.TypeAAAA,
Qclass: dns.ClassINET,
}, nil)
if response6 == nil {
return nil, false
if response4 != nil || response6 != nil {
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
}
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
}
return nil, false
}

View File

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

View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
R "github.com/sagernet/sing-box/route/rule"
@@ -37,7 +38,7 @@ type Router struct {
rules []adapter.DNSRule
defaultDomainStrategy C.DomainStrategy
dnsReverseMapping freelru.Cache[netip.Addr, string]
platformInterface adapter.PlatformInterface
platformInterface platform.Interface
}
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
@@ -385,7 +386,12 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
if rule != nil {
switch action := rule.Action().(type) {
case *R.RuleActionReject:
return nil, &R.RejectedError{Cause: action.Error(ctx)}
switch action.Method {
case C.RuleActionRejectMethodDefault:
return nil, nil
case C.RuleActionRejectMethodDrop:
return nil, tun.ErrDrop
}
case *R.RuleActionPredefined:
if action.Rcode != mDNS.RcodeSuccess {
err = RcodeError(action.Rcode)

View File

@@ -49,7 +49,6 @@ type Transport struct {
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
transportLock sync.RWMutex
updatedAt time.Time
lastError error
servers []M.Socksaddr
search []string
ndots int
@@ -93,7 +92,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
}
go func() {
_, err := t.fetch()
_, err := t.Fetch()
if err != nil {
t.logger.Error(E.Cause(err, "fetch DNS servers"))
}
@@ -109,7 +108,7 @@ func (t *Transport) Close() error {
}
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
servers, err := t.fetch()
servers, err := t.Fetch()
if err != nil {
return nil, err
}
@@ -129,20 +128,11 @@ func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []
}
}
func (t *Transport) Fetch() []M.Socksaddr {
servers, _ := t.fetch()
return servers
}
func (t *Transport) fetch() ([]M.Socksaddr, error) {
func (t *Transport) Fetch() ([]M.Socksaddr, error) {
t.transportLock.RLock()
updatedAt := t.updatedAt
lastError := t.lastError
servers := t.servers
t.transportLock.RUnlock()
if lastError != nil {
return nil, lastError
}
if time.Since(updatedAt) < C.DHCPTTL {
return servers, nil
}
@@ -153,7 +143,7 @@ func (t *Transport) fetch() ([]M.Socksaddr, error) {
}
err := t.updateServers()
if err != nil {
return servers, err
return nil, err
}
return t.servers, nil
}
@@ -183,15 +173,12 @@ func (t *Transport) updateServers() error {
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
err = t.fetchServers0(fetchCtx, iface)
cancel()
t.updatedAt = time.Now()
if err != nil {
t.lastError = err
return err
} else if len(t.servers) == 0 {
t.lastError = E.New("dhcp: empty DNS servers response")
return t.lastError
return E.New("dhcp: empty DNS servers response")
} else {
t.lastError = nil
t.updatedAt = time.Now()
return nil
}
}

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