mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
76 Commits
dev-wifi-l
...
v1.13.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8769d1344b | ||
|
|
f6f74a7b8b | ||
|
|
64c8ffe7a3 | ||
|
|
65efa19401 | ||
|
|
2ee8488ed5 | ||
|
|
09cf083171 | ||
|
|
5a0108b447 | ||
|
|
6e8222f6ce | ||
|
|
a4dbe95bca | ||
|
|
d2673ce356 | ||
|
|
1ea4c94a16 | ||
|
|
092625cc57 | ||
|
|
01e78efa4a | ||
|
|
2bf5fdb032 | ||
|
|
53513dea52 | ||
|
|
2ba7d11a06 | ||
|
|
9cf8fd85bc | ||
|
|
c0e57163bc | ||
|
|
decdb55e8d | ||
|
|
c4d869c26e | ||
|
|
481487c2dc | ||
|
|
c837263ea3 | ||
|
|
513eddfc37 | ||
|
|
25bd2c955a | ||
|
|
c5b954b89c | ||
|
|
9300dbd7e8 | ||
|
|
8cd28d1274 | ||
|
|
a3db6bd8f6 | ||
|
|
016f7ce8c1 | ||
|
|
32ede5416a | ||
|
|
5e9408712b | ||
|
|
ea0af7b199 | ||
|
|
94d9198206 | ||
|
|
cc7a5af718 | ||
|
|
541251d09a | ||
|
|
11b4dde8b8 | ||
|
|
a6308af269 | ||
|
|
9c8dea44e7 | ||
|
|
8ecb89c267 | ||
|
|
f0a9e34608 | ||
|
|
9b663235aa | ||
|
|
c851bf96d3 | ||
|
|
f56f4fdd41 | ||
|
|
389c08da2c | ||
|
|
c60257fd2a | ||
|
|
896907c77c | ||
|
|
91c5d223c3 | ||
|
|
38f646e603 | ||
|
|
97acf0b861 | ||
|
|
fad7ff16de | ||
|
|
f0cd3422c1 | ||
|
|
e385a98ced | ||
|
|
670f32baee | ||
|
|
2747a00ba2 | ||
|
|
48e76038d0 | ||
|
|
6421252d44 | ||
|
|
216c4c8bd4 | ||
|
|
5841d410a1 | ||
|
|
63c8207d7a | ||
|
|
54ed58499d | ||
|
|
b1bdc18c85 | ||
|
|
a38030cc0b | ||
|
|
4626aa2cb0 | ||
|
|
5a40b673a4 | ||
|
|
541f63fee4 | ||
|
|
5de6f4a14f | ||
|
|
5658830077 | ||
|
|
0e50edc009 | ||
|
|
444f454810 | ||
|
|
d0e1fd6c7e | ||
|
|
17b4d1e010 | ||
|
|
06791470c9 | ||
|
|
ef14c8ca0e | ||
|
|
36dc883c7c | ||
|
|
6557bd7029 | ||
|
|
41b30c91d9 |
1
.github/CRONET_GO_VERSION
vendored
Normal file
1
.github/CRONET_GO_VERSION
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3fb4098ed7e4ffe2e3d9593a744fc3717dbb369b
|
||||||
2
.github/setup_go_for_windows7.sh
vendored
2
.github/setup_go_for_windows7.sh
vendored
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.25.1"
|
VERSION="1.25.5"
|
||||||
|
|
||||||
mkdir -p $HOME/go
|
mkdir -p $HOME/go
|
||||||
cd $HOME/go
|
cd $HOME/go
|
||||||
|
|||||||
12
.github/update_cronet.sh
vendored
Executable file
12
.github/update_cronet.sh
vendored
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/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 mod tidy
|
||||||
|
git -C $PROJECTS/cronet-go rev-parse origin/HEAD > "$SCRIPT_DIR/CRONET_GO_VERSION"
|
||||||
277
.github/workflows/build.yml
vendored
277
.github/workflows/build.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.25.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -69,13 +69,23 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
- { os: linux, arch: amd64, variant: purego, 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: amd64, variant: glibc, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||||
|
- { os: linux, arch: amd64, variant: musl, naive: true }
|
||||||
|
|
||||||
|
- { os: linux, arch: arm64, variant: purego, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
|
- { os: linux, arch: arm64, variant: glibc, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
|
- { os: linux, arch: arm64, variant: musl, naive: true }
|
||||||
|
|
||||||
|
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||||
|
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2 }
|
||||||
|
|
||||||
|
- { os: linux, arch: arm, variant: glibc, 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: arm, variant: musl, naive: true, goarm: "7" }
|
||||||
|
|
||||||
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
- { 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: "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: "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: 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: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||||
@@ -87,15 +97,8 @@ jobs:
|
|||||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
- { os: linux, arch: 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: 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: "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: arm64, ndk: "aarch64-linux-android21" }
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
@@ -107,15 +110,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
|
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.25.5
|
||||||
- name: Setup Go 1.24
|
- name: Setup Go 1.24
|
||||||
if: matrix.legacy_go124
|
if: matrix.legacy_go124
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.24.6
|
go-version: ~1.24.10
|
||||||
- name: Cache Go for Windows 7
|
- name: Cache Go for Windows 7
|
||||||
if: matrix.legacy_win7
|
if: matrix.legacy_win7
|
||||||
id: cache-go-for-windows7
|
id: cache-go-for-windows7
|
||||||
@@ -123,7 +126,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_win7
|
~/go/go_win7
|
||||||
key: go_win7_1251
|
key: go_win7_1255
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup Go for Windows 7
|
||||||
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -139,6 +142,45 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28
|
||||||
local-cache: true
|
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
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
@@ -146,10 +188,65 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,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
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build (purego)
|
||||||
if: matrix.os != 'android'
|
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: 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 == ''
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
@@ -193,6 +290,11 @@ jobs:
|
|||||||
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
||||||
fi
|
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}"
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
@@ -283,7 +385,132 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_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,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 }
|
||||||
|
- { arch: "386" }
|
||||||
|
- { arch: arm64 }
|
||||||
|
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
|
||||||
|
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_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: Archive
|
||||||
|
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
|
||||||
|
run: Remove-Item dist/sing-box.exe
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-windows_${{ matrix.arch }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
@@ -300,7 +527,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -380,7 +607,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -433,7 +660,7 @@ jobs:
|
|||||||
build_apple:
|
build_apple:
|
||||||
name: Build Apple clients
|
name: Build Apple clients
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
if: false
|
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'
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
@@ -479,7 +706,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.25.5
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -619,6 +846,8 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
- build
|
- build
|
||||||
|
- build_darwin
|
||||||
|
- build_windows
|
||||||
- build_android
|
- build_android
|
||||||
- build_apple
|
- build_apple
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
149
.github/workflows/docker.yml
vendored
149
.github/workflows/docker.yml
vendored
@@ -1,6 +1,10 @@
|
|||||||
name: Publish Docker Images
|
name: Publish Docker Images
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
#push:
|
||||||
|
# branches:
|
||||||
|
# - main-next
|
||||||
|
# - dev-next
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -13,8 +17,134 @@ env:
|
|||||||
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build_binary:
|
||||||
|
name: Build binary
|
||||||
runs-on: ubuntu-latest
|
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,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:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
@@ -47,6 +177,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
platform=${{ matrix.platform }}
|
platform=${{ matrix.platform }}
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
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
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
@@ -68,8 +208,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
context: .
|
context: .
|
||||||
build-args: |
|
file: Dockerfile.binary
|
||||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
@@ -87,7 +226,7 @@ jobs:
|
|||||||
merge:
|
merge:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- build
|
- build_docker
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -121,6 +260,7 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create manifest list and push
|
- name: Create manifest list and push
|
||||||
|
if: github.event_name != 'push'
|
||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
@@ -128,6 +268,7 @@ jobs:
|
|||||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
|
if: github.event_name != 'push'
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||||
|
|||||||
76
.github/workflows/linux.yml
vendored
76
.github/workflows/linux.yml
vendored
@@ -1,6 +1,10 @@
|
|||||||
name: Build Linux Packages
|
name: Build Linux Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
#push:
|
||||||
|
# branches:
|
||||||
|
# - main-next
|
||||||
|
# - dev-next
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
@@ -30,7 +34,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.25.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -52,11 +56,13 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
# Naive-enabled builds (musl)
|
||||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
- { 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: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
- { 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: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
@@ -71,13 +77,38 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.1
|
go-version: ^1.25.5
|
||||||
- name: Setup Android NDK
|
- name: Clone cronet-go
|
||||||
if: matrix.os == 'android'
|
if: matrix.naive
|
||||||
uses: nttld/setup-ndk@v1
|
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:
|
with:
|
||||||
ndk-version: r28
|
path: |
|
||||||
local-cache: true
|
~/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
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
@@ -85,9 +116,27 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0'
|
||||||
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
|
TAGS="${TAGS},with_naive_outbound,with_musl"
|
||||||
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- 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 }}
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
@@ -185,5 +234,6 @@ jobs:
|
|||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Publish packages
|
- name: Publish packages
|
||||||
|
if: github.event_name != 'push'
|
||||||
run: |-
|
run: |-
|
||||||
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
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
213
.goreleaser.yaml
@@ -1,213 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -13,15 +13,13 @@ RUN set -ex \
|
|||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags \
|
&& go build -v -trimpath -tags \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk upgrade \
|
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
|
||||||
&& apk add bash tzdata ca-certificates nftables \
|
|
||||||
&& rm -rf /var/cache/apk/*
|
|
||||||
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
||||||
ENTRYPOINT ["sing-box"]
|
ENTRYPOINT ["sing-box"]
|
||||||
|
|||||||
8
Dockerfile.binary
Normal file
8
Dockerfile.binary
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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"]
|
||||||
6
Makefile
6
Makefile
@@ -1,6 +1,6 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
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,badlinkname,tfogo_checklinkname0
|
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
@@ -249,8 +249,8 @@ lib:
|
|||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.10
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.10
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
> 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
|
# sing-box
|
||||||
|
|
||||||
The universal proxy platform.
|
The universal proxy platform.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ type ClashServer interface {
|
|||||||
ConnectionTracker
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
|
SetModeUpdateHook(hook *observable.Subscriber[struct{}])
|
||||||
HistoryStorage() URLTestHistoryStorage
|
HistoryStorage() URLTestHistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ type URLTestHistory struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type URLTestHistoryStorage interface {
|
type URLTestHistoryStorage interface {
|
||||||
SetHook(hook chan<- struct{})
|
SetHook(hook *observable.Subscriber[struct{}])
|
||||||
LoadURLTestHistory(tag string) *URLTestHistory
|
LoadURLTestHistory(tag string) *URLTestHistory
|
||||||
DeleteURLTestHistory(tag string)
|
DeleteURLTestHistory(tag string)
|
||||||
StoreURLTestHistory(tag string, history *URLTestHistory)
|
StoreURLTestHistory(tag string, history *URLTestHistory)
|
||||||
|
|||||||
@@ -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) {
|
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
_, myMetadata := ExtendContext(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = source
|
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) {
|
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
_, myMetadata := ExtendContext(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = source
|
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) {
|
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
metadata := ContextFrom(ctx)
|
_, metadata := ExtendContext(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
metadata.Source = source
|
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) {
|
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
metadata := ContextFrom(ctx)
|
_, metadata := ExtendContext(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule clients/android updated: e08fbfcfea...f3763ba71d
Submodule clients/apple updated: 84d8cf1757...532c140f05
@@ -46,7 +46,7 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
macOSTags []string
|
darwinTags []string
|
||||||
memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
notMemcTags []string
|
||||||
debugTags []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")
|
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")
|
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_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
|
||||||
macOSTags = append(macOSTags, "with_dhcp")
|
darwinTags = append(darwinTags, "with_dhcp")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
notMemcTags = append(notMemcTags, "with_low_memory")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
@@ -158,9 +158,7 @@ func buildApple() {
|
|||||||
"-tags-not-macos=with_low_memory",
|
"-tags-not-macos=with_low_memory",
|
||||||
}
|
}
|
||||||
if !withTailscale {
|
if !withTailscale {
|
||||||
args = append(args, "-tags-macos="+strings.Join(append(macOSTags, memcTags...), ","))
|
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
||||||
} else {
|
|
||||||
args = append(args, "-tags-macos="+strings.Join(macOSTags, ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@@ -169,7 +167,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := sharedTags
|
tags := append(sharedTags, darwinTags...)
|
||||||
if withTailscale {
|
if withTailscale {
|
||||||
tags = append(tags, memcTags...)
|
tags = append(tags, memcTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,9 +142,18 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPConnectTimeout
|
dialer.Timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
// TODO: Add an option to customize the keep alive period
|
if !options.DisableTCPKeepAlive {
|
||||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
keepIdle := time.Duration(options.TCPKeepAlive)
|
||||||
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
|
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))
|
||||||
|
}
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
udpFragment = *options.UDPFragment
|
udpFragment = *options.UDPFragment
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
if l.listenOptions.ReuseAddr {
|
if l.listenOptions.ReuseAddr {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
if !l.listenOptions.DisableTCPKeepAlive {
|
||||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
|
|||||||
@@ -134,7 +134,9 @@ func (m *connmanMonitor) monitorSignals(ctx context.Context, signalChan chan *db
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if signal.Name == "PropertyChanged" {
|
// 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()
|
state := m.ReadWIFIState()
|
||||||
if state != lastState {
|
if state != lastState {
|
||||||
lastState = state
|
lastState = state
|
||||||
@@ -154,6 +156,10 @@ func (m *connmanMonitor) Close() error {
|
|||||||
close(m.signalChan)
|
close(m.signalChan)
|
||||||
}
|
}
|
||||||
if m.conn != nil {
|
if m.conn != nil {
|
||||||
|
m.conn.RemoveMatchSignal(
|
||||||
|
dbus.WithMatchInterface("net.connman.Service"),
|
||||||
|
dbus.WithMatchSender("net.connman"),
|
||||||
|
)
|
||||||
return m.conn.Close()
|
return m.conn.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ func (m *iwdMonitor) Close() error {
|
|||||||
close(m.signalChan)
|
close(m.signalChan)
|
||||||
}
|
}
|
||||||
if m.conn != nil {
|
if m.conn != nil {
|
||||||
|
m.conn.RemoveMatchSignal(
|
||||||
|
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
|
||||||
|
dbus.WithMatchSender("net.connman.iwd"),
|
||||||
|
)
|
||||||
return m.conn.Close()
|
return m.conn.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -40,57 +40,59 @@ func (m *networkManagerMonitor) ReadWIFIState() adapter.WIFIState {
|
|||||||
|
|
||||||
nmObj := m.conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
|
nmObj := m.conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
|
||||||
|
|
||||||
var primaryConnectionPath dbus.ObjectPath
|
var activeConnectionPaths []dbus.ObjectPath
|
||||||
err := nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "PrimaryConnection").Store(&primaryConnectionPath)
|
err := nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "ActiveConnections").Store(&activeConnectionPaths)
|
||||||
if err != nil || primaryConnectionPath == "/" {
|
if err != nil || len(activeConnectionPaths) == 0 {
|
||||||
return adapter.WIFIState{}
|
return adapter.WIFIState{}
|
||||||
}
|
}
|
||||||
|
|
||||||
connObj := m.conn.Object("org.freedesktop.NetworkManager", primaryConnectionPath)
|
for _, connectionPath := range activeConnectionPaths {
|
||||||
|
connObj := m.conn.Object("org.freedesktop.NetworkManager", connectionPath)
|
||||||
|
|
||||||
var devicePaths []dbus.ObjectPath
|
var devicePaths []dbus.ObjectPath
|
||||||
err = connObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Connection.Active", "Devices").Store(&devicePaths)
|
err = connObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Connection.Active", "Devices").Store(&devicePaths)
|
||||||
if err != nil || len(devicePaths) == 0 {
|
if err != nil || len(devicePaths) == 0 {
|
||||||
return adapter.WIFIState{}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var accessPointPath dbus.ObjectPath
|
for _, devicePath := range devicePaths {
|
||||||
err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device.Wireless", "ActiveAccessPoint").Store(&accessPointPath)
|
deviceObj := m.conn.Object("org.freedesktop.NetworkManager", devicePath)
|
||||||
if err != nil || accessPointPath == "/" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
apObj := m.conn.Object("org.freedesktop.NetworkManager", accessPointPath)
|
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 ssidBytes []byte
|
var accessPointPath dbus.ObjectPath
|
||||||
err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "Ssid").Store(&ssidBytes)
|
err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device.Wireless", "ActiveAccessPoint").Store(&accessPointPath)
|
||||||
if err != nil {
|
if err != nil || accessPointPath == "/" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var hwAddress string
|
apObj := m.conn.Object("org.freedesktop.NetworkManager", accessPointPath)
|
||||||
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))
|
var ssidBytes []byte
|
||||||
if ssid == "" {
|
err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "Ssid").Store(&ssidBytes)
|
||||||
continue
|
if err != nil {
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return adapter.WIFIState{
|
var hwAddress string
|
||||||
SSID: ssid,
|
err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "HwAddress").Store(&hwAddress)
|
||||||
BSSID: strings.ToUpper(strings.ReplaceAll(hwAddress, ":", "")),
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ssid := strings.TrimSpace(string(ssidBytes))
|
||||||
|
if ssid == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter.WIFIState{
|
||||||
|
SSID: ssid,
|
||||||
|
BSSID: strings.ToUpper(strings.ReplaceAll(hwAddress, ":", "")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +153,10 @@ func (m *networkManagerMonitor) Close() error {
|
|||||||
close(m.signalChan)
|
close(m.signalChan)
|
||||||
}
|
}
|
||||||
if m.conn != nil {
|
if m.conn != nil {
|
||||||
|
m.conn.RemoveMatchSignal(
|
||||||
|
dbus.WithMatchSender("org.freedesktop.NetworkManager"),
|
||||||
|
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
|
||||||
|
)
|
||||||
return m.conn.Close()
|
return m.conn.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -8,15 +8,21 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var wpaSocketCounter atomic.Uint64
|
||||||
|
|
||||||
type wpaSupplicantMonitor struct {
|
type wpaSupplicantMonitor struct {
|
||||||
socketPath string
|
socketPath string
|
||||||
callback func(adapter.WIFIState)
|
callback func(adapter.WIFIState)
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
monitorConn *net.UnixConn
|
||||||
|
connMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
|
func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
|
||||||
@@ -31,7 +37,8 @@ func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, err
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
socketPath := filepath.Join(socketDir, entry.Name())
|
socketPath := filepath.Join(socketDir, entry.Name())
|
||||||
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d", os.Getpid()), Net: "unixgram"}
|
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"}
|
remoteAddr := &net.UnixAddr{Name: socketPath, Net: "unixgram"}
|
||||||
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
|
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -45,7 +52,8 @@ func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
|
func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
|
||||||
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d", os.Getpid()), Net: "unixgram"}
|
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"}
|
remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
|
||||||
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
|
conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -85,8 +93,11 @@ func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
func (m *wpaSupplicantMonitor) sendCommand(conn *net.UnixConn, command string) (string, error) {
|
||||||
_, err := conn.Write([]byte(command + "\n"))
|
_, err := conn.Write([]byte(command))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -121,6 +132,8 @@ func (m *wpaSupplicantMonitor) Start() error {
|
|||||||
|
|
||||||
func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adapter.WIFIState) {
|
func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adapter.WIFIState) {
|
||||||
var consecutiveErrors int
|
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"}
|
localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-mon-%d", os.Getpid()), Net: "unixgram"}
|
||||||
remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
|
remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
|
||||||
@@ -130,7 +143,14 @@ func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adap
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
_, err = conn.Write([]byte("ATTACH\n"))
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -144,6 +164,12 @@ func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adap
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
debounceMutex.Lock()
|
||||||
|
if debounceTimer != nil {
|
||||||
|
debounceTimer.Stop()
|
||||||
|
}
|
||||||
|
debounceMutex.Unlock()
|
||||||
|
conn.Write([]byte("DETACH"))
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -151,6 +177,14 @@ func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adap
|
|||||||
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
consecutiveErrors++
|
consecutiveErrors++
|
||||||
if consecutiveErrors > 10 {
|
if consecutiveErrors > 10 {
|
||||||
return
|
return
|
||||||
@@ -162,11 +196,18 @@ func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adap
|
|||||||
|
|
||||||
msg := string(buf[:n])
|
msg := string(buf[:n])
|
||||||
if strings.Contains(msg, "CTRL-EVENT-CONNECTED") || strings.Contains(msg, "CTRL-EVENT-DISCONNECTED") {
|
if strings.Contains(msg, "CTRL-EVENT-CONNECTED") || strings.Contains(msg, "CTRL-EVENT-DISCONNECTED") {
|
||||||
state := m.ReadWIFIState()
|
debounceMutex.Lock()
|
||||||
if state != lastState {
|
if debounceTimer != nil {
|
||||||
lastState = state
|
debounceTimer.Stop()
|
||||||
m.callback(state)
|
|
||||||
}
|
}
|
||||||
|
debounceTimer = time.AfterFunc(500*time.Millisecond, func() {
|
||||||
|
state := m.ReadWIFIState()
|
||||||
|
if state != lastState {
|
||||||
|
lastState = state
|
||||||
|
m.callback(state)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
debounceMutex.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,5 +216,10 @@ func (m *wpaSupplicantMonitor) Close() error {
|
|||||||
if m.cancel != nil {
|
if m.cancel != nil {
|
||||||
m.cancel()
|
m.cancel()
|
||||||
}
|
}
|
||||||
|
m.connMutex.Lock()
|
||||||
|
if m.monitorConn != nil {
|
||||||
|
m.monitorConn.Close()
|
||||||
|
}
|
||||||
|
m.connMutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !linux
|
//go:build !linux && !windows
|
||||||
|
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
|
|||||||
144
common/settings/wifi_windows.go
Normal file
144
common/settings/wifi_windows.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
//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)
|
||||||
|
}
|
||||||
@@ -169,6 +169,35 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
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}
|
var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -222,6 +222,35 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
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)
|
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
||||||
@@ -21,7 +22,7 @@ var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
|||||||
type HistoryStorage struct {
|
type HistoryStorage struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
delayHistory map[string]*adapter.URLTestHistory
|
delayHistory map[string]*adapter.URLTestHistory
|
||||||
updateHook chan<- struct{}
|
updateHook *observable.Subscriber[struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistoryStorage() *HistoryStorage {
|
func NewHistoryStorage() *HistoryStorage {
|
||||||
@@ -30,7 +31,7 @@ func NewHistoryStorage() *HistoryStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
|
func (s *HistoryStorage) SetHook(hook *observable.Subscriber[struct{}]) {
|
||||||
s.updateHook = hook
|
s.updateHook = hook
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,10 +61,7 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTes
|
|||||||
func (s *HistoryStorage) notifyUpdated() {
|
func (s *HistoryStorage) notifyUpdated() {
|
||||||
updateHook := s.updateHook
|
updateHook := s.updateHook
|
||||||
if updateHook != nil {
|
if updateHook != nil {
|
||||||
select {
|
updateHook.Emit(struct{}{})
|
||||||
case updateHook <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import "time"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DHCPTTL = time.Hour
|
DHCPTTL = time.Hour
|
||||||
DHCPTimeout = time.Minute
|
DHCPTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const (
|
|||||||
TypeDERP = "derp"
|
TypeDERP = "derp"
|
||||||
TypeResolved = "resolved"
|
TypeResolved = "resolved"
|
||||||
TypeSSMAPI = "ssm-api"
|
TypeSSMAPI = "ssm-api"
|
||||||
|
TypeCCM = "ccm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package constant
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TCPKeepAliveInitial = 10 * time.Minute
|
TCPKeepAliveInitial = 5 * time.Minute
|
||||||
TCPKeepAliveInterval = 75 * time.Second
|
TCPKeepAliveInterval = 75 * time.Second
|
||||||
TCPConnectTimeout = 5 * time.Second
|
TCPConnectTimeout = 5 * time.Second
|
||||||
TCPTimeout = 15 * time.Second
|
TCPTimeout = 15 * time.Second
|
||||||
|
|||||||
@@ -7,15 +7,12 @@ import (
|
|||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,23 +26,12 @@ type Instance struct {
|
|||||||
urlTestHistoryStorage *urltest.HistoryStorage
|
urlTestHistoryStorage *urltest.HistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) baseContext() context.Context {
|
|
||||||
dnsRegistry := include.DNSTransportRegistry()
|
|
||||||
if s.platform != nil && s.platform.UsePlatformLocalDNSTransport() {
|
|
||||||
dns.RegisterTransport[option.LocalDNSServerOptions](dnsRegistry, C.DNSTypeLocal, s.platform.LocalDNSTransport())
|
|
||||||
}
|
|
||||||
ctx := box.Context(s.ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
|
||||||
ctx = filemanager.WithDefault(ctx, s.workingDirectory, s.tempDirectory, s.userID, s.groupID)
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) CheckConfig(configContent string) error {
|
func (s *StartedService) CheckConfig(configContent string) error {
|
||||||
ctx := s.baseContext()
|
options, err := parseConfig(s.ctx, configContent)
|
||||||
options, err := parseConfig(ctx, configContent)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(s.ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
@@ -58,7 +44,7 @@ func (s *StartedService) CheckConfig(configContent string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) FormatConfig(configContent string) (string, error) {
|
func (s *StartedService) FormatConfig(configContent string) (string, error) {
|
||||||
options, err := parseConfig(s.baseContext(), configContent)
|
options, err := parseConfig(s.ctx, configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -79,7 +65,7 @@ type OverrideOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) {
|
func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) {
|
||||||
ctx := s.baseContext()
|
ctx := s.ctx
|
||||||
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
||||||
ctx, cancel := context.WithCancel(include.Context(ctx))
|
ctx, cancel := context.WithCancel(include.Context(ctx))
|
||||||
options, err := parseConfig(ctx, profileContent)
|
options, err := parseConfig(ctx, profileContent)
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PlatformHandler interface {
|
type PlatformHandler interface {
|
||||||
ServiceStop() error
|
ServiceStop() error
|
||||||
ServiceReload() error
|
ServiceReload() error
|
||||||
@@ -13,10 +7,3 @@ type PlatformHandler interface {
|
|||||||
SetSystemProxyEnabled(enabled bool) error
|
SetSystemProxyEnabled(enabled bool) error
|
||||||
WriteDebugMessage(message string)
|
WriteDebugMessage(message string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlatformInterface interface {
|
|
||||||
adapter.PlatformInterface
|
|
||||||
|
|
||||||
UsePlatformLocalDNSTransport() bool
|
|
||||||
LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ import (
|
|||||||
var _ StartedServiceServer = (*StartedService)(nil)
|
var _ StartedServiceServer = (*StartedService)(nil)
|
||||||
|
|
||||||
type StartedService struct {
|
type StartedService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
platform PlatformInterface
|
// platform adapter.PlatformInterface
|
||||||
platformHandler PlatformHandler
|
handler PlatformHandler
|
||||||
debug bool
|
debug bool
|
||||||
logMaxLines int
|
logMaxLines int
|
||||||
workingDirectory string
|
// workingDirectory string
|
||||||
tempDirectory string
|
// tempDirectory string
|
||||||
userID int
|
// userID int
|
||||||
groupID int
|
// groupID int
|
||||||
systemProxyEnabled bool
|
// systemProxyEnabled bool
|
||||||
serviceAccess sync.RWMutex
|
serviceAccess sync.RWMutex
|
||||||
serviceStatus *ServiceStatus
|
serviceStatus *ServiceStatus
|
||||||
serviceStatusSubscriber *observable.Subscriber[*ServiceStatus]
|
serviceStatusSubscriber *observable.Subscriber[*ServiceStatus]
|
||||||
@@ -58,30 +58,30 @@ type StartedService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServiceOptions struct {
|
type ServiceOptions struct {
|
||||||
Context context.Context
|
Context context.Context
|
||||||
Platform PlatformInterface
|
// Platform adapter.PlatformInterface
|
||||||
PlatformHandler PlatformHandler
|
Handler PlatformHandler
|
||||||
Debug bool
|
Debug bool
|
||||||
LogMaxLines int
|
LogMaxLines int
|
||||||
WorkingDirectory string
|
// WorkingDirectory string
|
||||||
TempDirectory string
|
// TempDirectory string
|
||||||
UserID int
|
// UserID int
|
||||||
GroupID int
|
// GroupID int
|
||||||
SystemProxyEnabled bool
|
// SystemProxyEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStartedService(options ServiceOptions) *StartedService {
|
func NewStartedService(options ServiceOptions) *StartedService {
|
||||||
s := &StartedService{
|
s := &StartedService{
|
||||||
ctx: options.Context,
|
ctx: options.Context,
|
||||||
platform: options.Platform,
|
// platform: options.Platform,
|
||||||
platformHandler: options.PlatformHandler,
|
handler: options.Handler,
|
||||||
debug: options.Debug,
|
debug: options.Debug,
|
||||||
logMaxLines: options.LogMaxLines,
|
logMaxLines: options.LogMaxLines,
|
||||||
workingDirectory: options.WorkingDirectory,
|
// workingDirectory: options.WorkingDirectory,
|
||||||
tempDirectory: options.TempDirectory,
|
// tempDirectory: options.TempDirectory,
|
||||||
userID: options.UserID,
|
// userID: options.UserID,
|
||||||
groupID: options.GroupID,
|
// groupID: options.GroupID,
|
||||||
systemProxyEnabled: options.SystemProxyEnabled,
|
// systemProxyEnabled: options.SystemProxyEnabled,
|
||||||
serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE},
|
serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE},
|
||||||
serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4),
|
serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4),
|
||||||
logSubscriber: observable.NewSubscriber[*log.Entry](128),
|
logSubscriber: observable.NewSubscriber[*log.Entry](128),
|
||||||
@@ -117,6 +117,46 @@ func (s *StartedService) updateStatusError(err error) error {
|
|||||||
return err
|
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 {
|
func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error {
|
||||||
s.serviceAccess.Lock()
|
s.serviceAccess.Lock()
|
||||||
switch s.serviceStatus.Status {
|
switch s.serviceStatus.Status {
|
||||||
@@ -125,6 +165,13 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov
|
|||||||
s.serviceAccess.Unlock()
|
s.serviceAccess.Unlock()
|
||||||
return os.ErrInvalid
|
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.updateStatus(ServiceStatus_STARTING)
|
||||||
s.resetLogs()
|
s.resetLogs()
|
||||||
instance, err := s.newInstance(profileContent, options)
|
instance, err := s.newInstance(profileContent, options)
|
||||||
@@ -132,6 +179,10 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov
|
|||||||
return s.updateStatusError(err)
|
return s.updateStatusError(err)
|
||||||
}
|
}
|
||||||
s.instance = instance
|
s.instance = instance
|
||||||
|
instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber)
|
||||||
|
if instance.clashServer != nil {
|
||||||
|
instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber)
|
||||||
|
}
|
||||||
s.serviceAccess.Unlock()
|
s.serviceAccess.Unlock()
|
||||||
err = instance.Start()
|
err = instance.Start()
|
||||||
s.serviceAccess.Lock()
|
s.serviceAccess.Lock()
|
||||||
@@ -173,12 +224,11 @@ func (s *StartedService) CloseService() error {
|
|||||||
func (s *StartedService) SetError(err error) {
|
func (s *StartedService) SetError(err error) {
|
||||||
s.serviceAccess.Lock()
|
s.serviceAccess.Lock()
|
||||||
s.updateStatusError(err)
|
s.updateStatusError(err)
|
||||||
s.serviceAccess.Unlock()
|
|
||||||
s.WriteMessage(log.LevelError, err.Error())
|
s.WriteMessage(log.LevelError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
func (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
err := s.platformHandler.ServiceStop()
|
err := s.handler.ServiceStop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -186,7 +236,7 @@ func (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
func (s *StartedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
err := s.platformHandler.ServiceReload()
|
err := s.handler.ServiceReload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -227,8 +277,8 @@ func (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerSt
|
|||||||
for element := s.logLines.Front(); element != nil; element = element.Next() {
|
for element := s.logLines.Front(); element != nil; element = element.Next() {
|
||||||
savedLines = append(savedLines, element.Value)
|
savedLines = append(savedLines, element.Value)
|
||||||
}
|
}
|
||||||
s.logAccess.Unlock()
|
|
||||||
subscription, done, err := s.logObserver.Subscribe()
|
subscription, done, err := s.logObserver.Subscribe()
|
||||||
|
s.logAccess.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -252,30 +302,33 @@ func (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerSt
|
|||||||
case <-server.Context().Done():
|
case <-server.Context().Done():
|
||||||
return server.Context().Err()
|
return server.Context().Err()
|
||||||
case message := <-subscription:
|
case message := <-subscription:
|
||||||
|
var rawMessage Log
|
||||||
if message == nil {
|
if message == nil {
|
||||||
err = server.Send(&Log{Reset_: true})
|
rawMessage.Reset_ = true
|
||||||
if err != nil {
|
} else {
|
||||||
return err
|
rawMessage.Messages = append(rawMessage.Messages, &Log_Message{
|
||||||
}
|
Level: LogLevel(message.Level),
|
||||||
continue
|
Message: message.Message,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
messages := []*Log_Message{{
|
|
||||||
Level: LogLevel(message.Level),
|
|
||||||
Message: message.Message,
|
|
||||||
}}
|
|
||||||
fetch:
|
fetch:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case message = <-subscription:
|
case message = <-subscription:
|
||||||
messages = append(messages, &Log_Message{
|
if message == nil {
|
||||||
Level: LogLevel(message.Level),
|
rawMessage.Messages = nil
|
||||||
Message: message.Message,
|
rawMessage.Reset_ = true
|
||||||
})
|
} else {
|
||||||
|
rawMessage.Messages = append(rawMessage.Messages, &Log_Message{
|
||||||
|
Level: LogLevel(message.Level),
|
||||||
|
Message: message.Message,
|
||||||
|
})
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break fetch
|
break fetch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = server.Send(&Log{Messages: messages})
|
err = server.Send(&rawMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -298,6 +351,11 @@ func (s *StartedService) GetDefaultLogLevel(ctx context.Context, empty *emptypb.
|
|||||||
return &DefaultLogLevel{Level: LogLevel(logLevel)}, nil
|
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 {
|
func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server grpc.ServerStreamingServer[Status]) error {
|
||||||
interval := time.Duration(request.Interval)
|
interval := time.Duration(request.Interval)
|
||||||
if interval <= 0 {
|
if interval <= 0 {
|
||||||
@@ -335,7 +393,9 @@ func (s *StartedService) readStatus() *Status {
|
|||||||
status.Memory = memory.Inuse()
|
status.Memory = memory.Inuse()
|
||||||
status.Goroutines = int32(runtime.NumGoroutine())
|
status.Goroutines = int32(runtime.NumGoroutine())
|
||||||
status.ConnectionsOut = int32(conntrack.Count())
|
status.ConnectionsOut = int32(conntrack.Count())
|
||||||
|
s.serviceAccess.RLock()
|
||||||
nowService := s.instance
|
nowService := s.instance
|
||||||
|
s.serviceAccess.RUnlock()
|
||||||
if nowService != nil {
|
if nowService != nil {
|
||||||
if clashServer := nowService.clashServer; clashServer != nil {
|
if clashServer := nowService.clashServer; clashServer != nil {
|
||||||
status.TrafficAvailable = true
|
status.TrafficAvailable = true
|
||||||
@@ -348,6 +408,10 @@ func (s *StartedService) readStatus() *Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SubscribeGroups(empty *emptypb.Empty, server grpc.ServerStreamingServer[Groups]) error {
|
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()
|
subscription, done, err := s.urlTestObserver.Subscribe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -355,18 +419,16 @@ func (s *StartedService) SubscribeGroups(empty *emptypb.Empty, server grpc.Serve
|
|||||||
defer s.urlTestObserver.UnSubscribe(subscription)
|
defer s.urlTestObserver.UnSubscribe(subscription)
|
||||||
for {
|
for {
|
||||||
s.serviceAccess.RLock()
|
s.serviceAccess.RLock()
|
||||||
switch s.serviceStatus.Status {
|
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
||||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
|
||||||
groups := s.readGroups()
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
err = server.Send(groups)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
s.serviceAccess.RUnlock()
|
s.serviceAccess.RUnlock()
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
groups := s.readGroups()
|
||||||
|
s.serviceAccess.RUnlock()
|
||||||
|
err = server.Send(groups)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-subscription:
|
case <-subscription:
|
||||||
case <-s.ctx.Done():
|
case <-s.ctx.Done():
|
||||||
@@ -443,12 +505,27 @@ func (s *StartedService) GetClashModeStatus(ctx context.Context, empty *emptypb.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.ServerStreamingServer[ClashMode]) error {
|
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()
|
subscription, done, err := s.clashModeObserver.Subscribe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer s.clashModeObserver.UnSubscribe(subscription)
|
defer s.clashModeObserver.UnSubscribe(subscription)
|
||||||
for {
|
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 {
|
select {
|
||||||
case <-subscription:
|
case <-subscription:
|
||||||
case <-s.ctx.Done():
|
case <-s.ctx.Done():
|
||||||
@@ -458,16 +535,6 @@ func (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.Se
|
|||||||
case <-done:
|
case <-done:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.serviceAccess.RLock()
|
|
||||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
message := &ClashMode{Mode: s.instance.clashServer.Mode()}
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
err = server.Send(message)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,12 +571,7 @@ func (s *StartedService) URLTest(ctx context.Context, request *URLTestRequest) (
|
|||||||
if isURLTest {
|
if isURLTest {
|
||||||
go urlTest.CheckOutbounds()
|
go urlTest.CheckOutbounds()
|
||||||
} else {
|
} else {
|
||||||
var historyStorage adapter.URLTestHistoryStorage
|
historyStorage := boxService.urlTestHistoryStorage
|
||||||
if s.instance.clashServer != nil {
|
|
||||||
historyStorage = s.instance.clashServer.HistoryStorage()
|
|
||||||
} else {
|
|
||||||
return nil, E.New("Clash API is required for URLTest on non-URLTest group")
|
|
||||||
}
|
|
||||||
|
|
||||||
outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||||
itOutbound, _ := boxService.instance.Outbound().Outbound(it)
|
itOutbound, _ := boxService.instance.Outbound().Outbound(it)
|
||||||
@@ -566,6 +628,7 @@ func (s *StartedService) SelectOutbound(ctx context.Context, request *SelectOutb
|
|||||||
if !selector.SelectOutbound(request.OutboundTag) {
|
if !selector.SelectOutbound(request.OutboundTag) {
|
||||||
return nil, E.New("outbound not found in selector: ", request.OutboundTag)
|
return nil, E.New("outbound not found in selector: ", request.OutboundTag)
|
||||||
}
|
}
|
||||||
|
s.urlTestObserver.Emit(struct{}{})
|
||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,11 +652,11 @@ func (s *StartedService) SetGroupExpand(ctx context.Context, request *SetGroupEx
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) {
|
func (s *StartedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) {
|
||||||
return s.platformHandler.SystemProxyStatus()
|
return s.handler.SystemProxyStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
||||||
err := s.platformHandler.SetSystemProxyEnabled(request.Enabled)
|
err := s.handler.SetSystemProxyEnabled(request.Enabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -601,13 +664,11 @@ func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *Set
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error {
|
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error {
|
||||||
s.serviceAccess.RLock()
|
err := s.waitForStarted(server.Context())
|
||||||
switch s.serviceStatus.Status {
|
if err != nil {
|
||||||
case ServiceStatus_STARTING, ServiceStatus_STARTED:
|
return err
|
||||||
default:
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
}
|
||||||
|
s.serviceAccess.RLock()
|
||||||
boxService := s.instance
|
boxService := s.instance
|
||||||
s.serviceAccess.RUnlock()
|
s.serviceAccess.RUnlock()
|
||||||
ticker := time.NewTicker(time.Duration(request.Interval))
|
ticker := time.NewTicker(time.Duration(request.Interval))
|
||||||
@@ -755,15 +816,15 @@ func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
|||||||
|
|
||||||
func (s *StartedService) WriteMessage(level log.Level, message string) {
|
func (s *StartedService) WriteMessage(level log.Level, message string) {
|
||||||
item := &log.Entry{Level: level, Message: message}
|
item := &log.Entry{Level: level, Message: message}
|
||||||
s.logSubscriber.Emit(item)
|
|
||||||
s.logAccess.Lock()
|
s.logAccess.Lock()
|
||||||
s.logLines.PushBack(item)
|
s.logLines.PushBack(item)
|
||||||
if s.logLines.Len() > s.logMaxLines {
|
if s.logLines.Len() > s.logMaxLines {
|
||||||
s.logLines.Remove(s.logLines.Front())
|
s.logLines.Remove(s.logLines.Front())
|
||||||
}
|
}
|
||||||
s.logAccess.Unlock()
|
s.logAccess.Unlock()
|
||||||
|
s.logSubscriber.Emit(item)
|
||||||
if s.debug {
|
if s.debug {
|
||||||
s.platformHandler.WriteDebugMessage(message)
|
s.handler.WriteDebugMessage(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1746,13 +1746,14 @@ const file_daemon_started_service_proto_rawDesc = "" +
|
|||||||
"\x10ConnectionSortBy\x12\b\n" +
|
"\x10ConnectionSortBy\x12\b\n" +
|
||||||
"\x04DATE\x10\x00\x12\v\n" +
|
"\x04DATE\x10\x00\x12\v\n" +
|
||||||
"\aTRAFFIC\x10\x01\x12\x11\n" +
|
"\aTRAFFIC\x10\x01\x12\x11\n" +
|
||||||
"\rTOTAL_TRAFFIC\x10\x022\xf8\v\n" +
|
"\rTOTAL_TRAFFIC\x10\x022\xb7\f\n" +
|
||||||
"\x0eStartedService\x12=\n" +
|
"\x0eStartedService\x12=\n" +
|
||||||
"\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" +
|
"\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" +
|
||||||
"\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" +
|
"\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" +
|
||||||
"\x16SubscribeServiceStatus\x12\x16.google.protobuf.Empty\x1a\x15.daemon.ServiceStatus\"\x000\x01\x127\n" +
|
"\x16SubscribeServiceStatus\x12\x16.google.protobuf.Empty\x1a\x15.daemon.ServiceStatus\"\x000\x01\x127\n" +
|
||||||
"\fSubscribeLog\x12\x16.google.protobuf.Empty\x1a\v.daemon.Log\"\x000\x01\x12G\n" +
|
"\fSubscribeLog\x12\x16.google.protobuf.Empty\x1a\v.daemon.Log\"\x000\x01\x12G\n" +
|
||||||
"\x12GetDefaultLogLevel\x12\x16.google.protobuf.Empty\x1a\x17.daemon.DefaultLogLevel\"\x00\x12E\n" +
|
"\x12GetDefaultLogLevel\x12\x16.google.protobuf.Empty\x1a\x17.daemon.DefaultLogLevel\"\x00\x12=\n" +
|
||||||
|
"\tClearLogs\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12E\n" +
|
||||||
"\x0fSubscribeStatus\x12\x1e.daemon.SubscribeStatusRequest\x1a\x0e.daemon.Status\"\x000\x01\x12=\n" +
|
"\x0fSubscribeStatus\x12\x1e.daemon.SubscribeStatusRequest\x1a\x0e.daemon.Status\"\x000\x01\x12=\n" +
|
||||||
"\x0fSubscribeGroups\x12\x16.google.protobuf.Empty\x1a\x0e.daemon.Groups\"\x000\x01\x12G\n" +
|
"\x0fSubscribeGroups\x12\x16.google.protobuf.Empty\x1a\x0e.daemon.Groups\"\x000\x01\x12G\n" +
|
||||||
"\x12GetClashModeStatus\x12\x16.google.protobuf.Empty\x1a\x17.daemon.ClashModeStatus\"\x00\x12C\n" +
|
"\x12GetClashModeStatus\x12\x16.google.protobuf.Empty\x1a\x17.daemon.ClashModeStatus\"\x00\x12C\n" +
|
||||||
@@ -1835,45 +1836,47 @@ var file_daemon_started_service_proto_depIdxs = []int32{
|
|||||||
27, // 12: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty
|
27, // 12: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty
|
||||||
27, // 13: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
|
27, // 13: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
|
||||||
27, // 14: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
|
27, // 14: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
|
||||||
6, // 15: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
|
27, // 15: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty
|
||||||
27, // 16: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
|
6, // 16: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
|
||||||
27, // 17: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
|
27, // 17: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
|
||||||
27, // 18: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
|
27, // 18: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
|
||||||
16, // 19: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
|
27, // 19: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
|
||||||
13, // 20: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
|
16, // 20: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
|
||||||
14, // 21: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
|
13, // 21: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
|
||||||
15, // 22: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
|
14, // 22: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
|
||||||
27, // 23: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
|
15, // 23: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
|
||||||
19, // 24: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
|
27, // 24: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
|
||||||
20, // 25: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
|
19, // 25: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
|
||||||
23, // 26: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
|
20, // 26: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
|
||||||
27, // 27: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
|
23, // 27: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
|
||||||
27, // 28: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
|
27, // 28: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
|
||||||
27, // 29: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty
|
27, // 29: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
|
||||||
28, // 30: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse
|
27, // 30: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty
|
||||||
27, // 31: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
|
28, // 31: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse
|
||||||
27, // 32: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
|
27, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
|
||||||
4, // 33: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
|
27, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
|
||||||
7, // 34: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
|
4, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
|
||||||
8, // 35: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
|
7, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
|
||||||
9, // 36: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
|
8, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
|
||||||
10, // 37: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
|
27, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty
|
||||||
17, // 38: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
|
9, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
|
||||||
16, // 39: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
|
10, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
|
||||||
27, // 40: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
|
17, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
|
||||||
27, // 41: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
|
16, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
|
||||||
27, // 42: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
|
27, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
|
||||||
27, // 43: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
|
27, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
|
||||||
18, // 44: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
|
27, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
|
||||||
27, // 45: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
|
27, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
|
||||||
21, // 46: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections
|
18, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
|
||||||
27, // 47: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
|
27, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
|
||||||
27, // 48: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
|
21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections
|
||||||
24, // 49: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
|
27, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
|
||||||
29, // 50: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest
|
27, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
|
||||||
27, // 51: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty
|
24, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
|
||||||
31, // [31:52] is the sub-list for method output_type
|
29, // 52: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest
|
||||||
10, // [10:31] is the sub-list for method input_type
|
27, // 53: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty
|
||||||
|
32, // [32:54] is the sub-list for method output_type
|
||||||
|
10, // [10:32] is the sub-list for method input_type
|
||||||
10, // [10:10] is the sub-list for extension type_name
|
10, // [10:10] is the sub-list for extension type_name
|
||||||
10, // [10:10] is the sub-list for extension extendee
|
10, // [10:10] is the sub-list for extension extendee
|
||||||
0, // [0:10] is the sub-list for field type_name
|
0, // [0:10] is the sub-list for field type_name
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ service StartedService {
|
|||||||
rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {}
|
rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {}
|
||||||
rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {}
|
rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {}
|
||||||
rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {}
|
rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {}
|
||||||
|
rpc ClearLogs(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||||
rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {}
|
rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {}
|
||||||
rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {}
|
rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const (
|
|||||||
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||||
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||||
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||||
|
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
||||||
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||||
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||||
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||||
@@ -47,6 +48,7 @@ type StartedServiceClient interface {
|
|||||||
SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], 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)
|
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)
|
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)
|
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)
|
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)
|
GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error)
|
||||||
@@ -141,6 +143,16 @@ func (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *empty
|
|||||||
return out, nil
|
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) {
|
func (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...)
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...)
|
||||||
@@ -355,6 +367,7 @@ type StartedServiceServer interface {
|
|||||||
SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error
|
SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error
|
||||||
SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error
|
SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error
|
||||||
GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error)
|
GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error)
|
||||||
|
ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error
|
SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error
|
||||||
SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error
|
SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error
|
||||||
GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error)
|
GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error)
|
||||||
@@ -401,6 +414,10 @@ func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *em
|
|||||||
return nil, status.Errorf(codes.Unimplemented, "method GetDefaultLogLevel not implemented")
|
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 {
|
func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented")
|
return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented")
|
||||||
}
|
}
|
||||||
@@ -561,6 +578,24 @@ func _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Con
|
|||||||
return interceptor(ctx, in, info, handler)
|
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 {
|
func _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(SubscribeStatusRequest)
|
m := new(SubscribeStatusRequest)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
@@ -833,6 +868,10 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetDefaultLogLevel",
|
MethodName: "GetDefaultLogLevel",
|
||||||
Handler: _StartedService_GetDefaultLogLevel_Handler,
|
Handler: _StartedService_GetDefaultLogLevel_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ClearLogs",
|
||||||
|
Handler: _StartedService_ClearLogs_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "GetClashModeStatus",
|
MethodName: "GetClashModeStatus",
|
||||||
Handler: _StartedService_GetClashModeStatus_Handler,
|
Handler: _StartedService_GetClashModeStatus_Handler,
|
||||||
|
|||||||
@@ -95,6 +95,20 @@ 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) {
|
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 len(message.Question) == 0 {
|
||||||
if c.logger != nil {
|
if c.logger != nil {
|
||||||
@@ -214,7 +228,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
response.Answer = append(response.Answer, validResponse.Answer...)
|
response.Answer = append(response.Answer, validResponse.Answer...)
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
disableCache = disableCache || response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0
|
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
var rejected bool
|
||||||
// TODO: add accept_any rule and support to check response instead of addresses
|
// TODO: add accept_any rule and support to check response instead of addresses
|
||||||
@@ -251,10 +265,17 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var timeToLive uint32
|
var timeToLive uint32
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
if len(response.Answer) == 0 {
|
||||||
for _, record := range recordList {
|
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
|
||||||
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
timeToLive = soaTTL
|
||||||
timeToLive = record.Header().Ttl
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,14 +385,18 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.
|
|||||||
Qtype: dns.TypeA,
|
Qtype: dns.TypeA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
|
if response4 == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
response6, _ := c.loadResponse(dns.Question{
|
response6, _ := c.loadResponse(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeAAAA,
|
Qtype: dns.TypeAAAA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
if response4 != nil || response6 != nil {
|
if response6 == nil {
|
||||||
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
return nil, false
|
||||||
}
|
}
|
||||||
|
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -385,12 +385,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
if rule != nil {
|
if rule != nil {
|
||||||
switch action := rule.Action().(type) {
|
switch action := rule.Action().(type) {
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
switch action.Method {
|
return nil, &R.RejectedError{Cause: action.Error(ctx)}
|
||||||
case C.RuleActionRejectMethodDefault:
|
|
||||||
return nil, nil
|
|
||||||
case C.RuleActionRejectMethodDrop:
|
|
||||||
return nil, tun.ErrDrop
|
|
||||||
}
|
|
||||||
case *R.RuleActionPredefined:
|
case *R.RuleActionPredefined:
|
||||||
if action.Rcode != mDNS.RcodeSuccess {
|
if action.Rcode != mDNS.RcodeSuccess {
|
||||||
err = RcodeError(action.Rcode)
|
err = RcodeError(action.Rcode)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type Transport struct {
|
|||||||
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||||
transportLock sync.RWMutex
|
transportLock sync.RWMutex
|
||||||
updatedAt time.Time
|
updatedAt time.Time
|
||||||
|
lastError error
|
||||||
servers []M.Socksaddr
|
servers []M.Socksaddr
|
||||||
search []string
|
search []string
|
||||||
ndots int
|
ndots int
|
||||||
@@ -92,7 +93,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
|
|||||||
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
_, err := t.Fetch()
|
_, err := t.fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.logger.Error(E.Cause(err, "fetch DNS servers"))
|
t.logger.Error(E.Cause(err, "fetch DNS servers"))
|
||||||
}
|
}
|
||||||
@@ -108,7 +109,7 @@ func (t *Transport) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
servers, err := t.Fetch()
|
servers, err := t.fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -128,11 +129,20 @@ func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Fetch() ([]M.Socksaddr, error) {
|
func (t *Transport) Fetch() []M.Socksaddr {
|
||||||
|
servers, _ := t.fetch()
|
||||||
|
return servers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) fetch() ([]M.Socksaddr, error) {
|
||||||
t.transportLock.RLock()
|
t.transportLock.RLock()
|
||||||
updatedAt := t.updatedAt
|
updatedAt := t.updatedAt
|
||||||
|
lastError := t.lastError
|
||||||
servers := t.servers
|
servers := t.servers
|
||||||
t.transportLock.RUnlock()
|
t.transportLock.RUnlock()
|
||||||
|
if lastError != nil {
|
||||||
|
return nil, lastError
|
||||||
|
}
|
||||||
if time.Since(updatedAt) < C.DHCPTTL {
|
if time.Since(updatedAt) < C.DHCPTTL {
|
||||||
return servers, nil
|
return servers, nil
|
||||||
}
|
}
|
||||||
@@ -143,7 +153,7 @@ func (t *Transport) Fetch() ([]M.Socksaddr, error) {
|
|||||||
}
|
}
|
||||||
err := t.updateServers()
|
err := t.updateServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return servers, err
|
||||||
}
|
}
|
||||||
return t.servers, nil
|
return t.servers, nil
|
||||||
}
|
}
|
||||||
@@ -173,12 +183,15 @@ func (t *Transport) updateServers() error {
|
|||||||
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
||||||
err = t.fetchServers0(fetchCtx, iface)
|
err = t.fetchServers0(fetchCtx, iface)
|
||||||
cancel()
|
cancel()
|
||||||
|
t.updatedAt = time.Now()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
t.lastError = err
|
||||||
return err
|
return err
|
||||||
} else if len(t.servers) == 0 {
|
} else if len(t.servers) == 0 {
|
||||||
return E.New("dhcp: empty DNS servers response")
|
t.lastError = E.New("dhcp: empty DNS servers response")
|
||||||
|
return t.lastError
|
||||||
} else {
|
} else {
|
||||||
t.updatedAt = time.Now()
|
t.lastError = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,5 +75,6 @@ func (h *HTTPSTransportWrapper) Clone() *HTTPSTransportWrapper {
|
|||||||
http2Transport: &http2.Transport{
|
http2Transport: &http2.Transport{
|
||||||
DialTLSContext: h.http2Transport.DialTLSContext,
|
DialTLSContext: h.http2Transport.DialTLSContext,
|
||||||
},
|
},
|
||||||
|
fallback: h.fallback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,13 +53,15 @@ func (t *Transport) Start(stage adapter.StartStage) error {
|
|||||||
switch stage {
|
switch stage {
|
||||||
case adapter.StartStateInitialize:
|
case adapter.StartStateInitialize:
|
||||||
if !t.preferGo {
|
if !t.preferGo {
|
||||||
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
|
if isSystemdResolvedManaged() {
|
||||||
if err == nil {
|
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
|
||||||
err = resolvedResolver.Start()
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.resolved = resolvedResolver
|
err = resolvedResolver.Start()
|
||||||
} else {
|
if err == nil {
|
||||||
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
|
t.resolved = resolvedResolver
|
||||||
|
} else {
|
||||||
|
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,12 +84,11 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
addresses := t.hosts.Lookup(domain)
|
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
||||||
if len(addresses) > 0 {
|
if len(addresses) > 0 {
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return t.exchange(ctx, message, domain)
|
return t.exchange(ctx, message, question.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type Transport struct {
|
|||||||
|
|
||||||
type dhcpTransport interface {
|
type dhcpTransport interface {
|
||||||
adapter.DNSTransport
|
adapter.DNSTransport
|
||||||
Fetch() ([]M.Socksaddr, error)
|
Fetch() []M.Socksaddr
|
||||||
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
|
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,14 +74,12 @@ func (t *Transport) Start(stage adapter.StartStage) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !C.IsIos {
|
if t.fallback {
|
||||||
if t.fallback {
|
t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)
|
||||||
t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)
|
if t.dhcpTransport != nil {
|
||||||
if t.dhcpTransport != nil {
|
err := t.dhcpTransport.Start(stage)
|
||||||
err := t.dhcpTransport.Start(stage)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,27 +94,24 @@ func (t *Transport) Close() error {
|
|||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
addresses := t.hosts.Lookup(domain)
|
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
||||||
if len(addresses) > 0 {
|
if len(addresses) > 0 {
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !t.fallback {
|
if !t.fallback {
|
||||||
return t.exchange(ctx, message, domain)
|
return t.exchange(ctx, message, question.Name)
|
||||||
}
|
}
|
||||||
if !C.IsIos {
|
if t.dhcpTransport != nil {
|
||||||
if t.dhcpTransport != nil {
|
dhcpTransports := t.dhcpTransport.Fetch()
|
||||||
dhcpTransports, _ := t.dhcpTransport.Fetch()
|
if len(dhcpTransports) > 0 {
|
||||||
if len(dhcpTransports) > 0 {
|
return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)
|
||||||
return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.preferGo {
|
if t.preferGo {
|
||||||
// Assuming the user knows what they are doing, we still execute the query which will fail.
|
// Assuming the user knows what they are doing, we still execute the query which will fail.
|
||||||
return t.exchange(ctx, message, domain)
|
return t.exchange(ctx, message, question.Name)
|
||||||
}
|
}
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
var network string
|
var network string
|
||||||
@@ -125,7 +120,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
} else {
|
} else {
|
||||||
network = "ip6"
|
network = "ip6"
|
||||||
}
|
}
|
||||||
addresses, err := t.resolver.LookupNetIP(ctx, network, domain)
|
addresses, err := t.resolver.LookupNetIP(ctx, network, question.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var dnsError *net.DNSError
|
var dnsError *net.DNSError
|
||||||
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
@@ -135,9 +130,5 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
}
|
}
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
if C.IsIos {
|
return nil, E.New("only A and AAAA queries are supported on Apple platforms when using TUN and DHCP unavailable.")
|
||||||
return nil, E.New("only A and AAAA queries are supported on iOS and tvOS when using NetworkExtension.")
|
|
||||||
} else {
|
|
||||||
return nil, E.New("only A and AAAA queries are supported on macOS when using NetworkExtension and DHCP unavailable.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@@ -22,6 +24,25 @@ import (
|
|||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isSystemdResolvedManaged() bool {
|
||||||
|
resolvContent, err := os.Open("/etc/resolv.conf")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer resolvContent.Close()
|
||||||
|
scanner := bufio.NewScanner(resolvContent)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || line[0] != '#' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "systemd-resolved") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type DBusResolvedResolver struct {
|
type DBusResolvedResolver struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
@@ -188,7 +209,7 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObje
|
|||||||
int32(defaultInterface.Index),
|
int32(defaultInterface.Index),
|
||||||
)
|
)
|
||||||
if call.Err != nil {
|
if call.Err != nil {
|
||||||
return nil, err
|
return nil, call.Err
|
||||||
}
|
}
|
||||||
var linkPath dbus.ObjectPath
|
var linkPath dbus.ObjectPath
|
||||||
err = call.Store(&linkPath)
|
err = call.Store(&linkPath)
|
||||||
@@ -214,15 +235,12 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObje
|
|||||||
return nil, E.New("No appropriate name servers or networks for name found")
|
return nil, E.New("No appropriate name servers or networks for name found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ResolvedObject{
|
return nil, E.New("link has no DNS servers configured")
|
||||||
BusObject: dbusObject,
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
return &ResolvedObject{
|
|
||||||
BusObject: dbusObject,
|
|
||||||
InterfaceIndex: int32(defaultInterface.Index),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
return &ResolvedObject{
|
||||||
|
BusObject: dbusObject,
|
||||||
|
InterfaceIndex: int32(defaultInterface.Index),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) {
|
func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) {
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isSystemdResolvedManaged() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
|
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,106 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.13.0-alpha.20
|
#### 1.13.0-alpha.28
|
||||||
|
|
||||||
|
* Add naiveproxy outbound **1**
|
||||||
|
* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for dial fields **2**
|
||||||
|
* Update default TCP keep-alive initial period from 10 minutes to 5 minutes
|
||||||
|
* Update quic-go to v0.57.1
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Only available on Apple platforms, Android, Windows and some Linux architectures.
|
||||||
|
|
||||||
|
See [NaiveProxy outbound](/configuration/outbound/naive/).
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
See [Dial Fields](/configuration/shared/dial/#tcp_keep_alive).
|
||||||
|
|
||||||
|
* __Unfortunately, for non-technical reasons, we are currently unable to notarize the standalone version of the macOS client:
|
||||||
|
because system extensions require signatures to function, we have had to temporarily halt its release.__
|
||||||
|
|
||||||
|
__We plan to fix the App Store release issue and launch a new standalone desktop client, but until then,
|
||||||
|
only clients on TestFlight will be available (unless you have an Apple Developer Program and compile from source code).__
|
||||||
|
|
||||||
|
|
||||||
|
#### 1.12.13
|
||||||
|
|
||||||
|
* Fix naive inbound
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
__Unfortunately, for non-technical reasons, we are currently unable to notarize the standalone version of the macOS client:
|
||||||
|
because system extensions require signatures to function, we have had to temporarily halt its release.__
|
||||||
|
|
||||||
|
__We plan to fix the App Store release issue and launch a new standalone desktop client, but until then,
|
||||||
|
only clients on TestFlight will be available (unless you have an Apple Developer Program and compile from source code).__
|
||||||
|
|
||||||
|
#### 1.12.12
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.26
|
||||||
|
|
||||||
|
* Update quic-go to v0.55.0
|
||||||
|
* Fix memory leak in hysteria2
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.12.11
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.24
|
||||||
|
|
||||||
|
* Add Claude Code Multiplexer service **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
CCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients.
|
||||||
|
|
||||||
|
See [CCM](/configuration/service/ccm).
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.23
|
||||||
|
|
||||||
|
* Fix compatibility with MPTCP **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
`auto_redirect` now rejects MPTCP connections by default to fix compatibility issues,
|
||||||
|
but you can change it to bypass the sing-box via the new `exclude_mptcp` option.
|
||||||
|
|
||||||
|
See [TUN](/configuration/inbound/tun/#exclude_mptcp).
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.22
|
||||||
|
|
||||||
|
* Update uTLS to v1.8.1 **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
This update fixes an critical issue that could cause simulated Chrome fingerprints to be detected,
|
||||||
|
see https://github.com/refraction-networking/utls/pull/375.
|
||||||
|
|
||||||
|
#### 1.12.10
|
||||||
|
|
||||||
|
* Update uTLS to v1.8.1 **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
This update fixes an critical issue that could cause simulated Chrome fingerprints to be detected,
|
||||||
|
see https://github.com/refraction-networking/utls/pull/375.
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.21
|
||||||
|
|
||||||
|
* Fix missing mTLS support in client options **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
See [TLS](/configuration/shared/tls/).
|
||||||
|
|
||||||
#### 1.12.9
|
#### 1.12.9
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@@ -127,7 +223,8 @@ See [Tailscale](/configuration/endpoint/tailscale/).
|
|||||||
|
|
||||||
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
||||||
|
|
||||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches
|
||||||
|
from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||||
|
|
||||||
**7**:
|
**7**:
|
||||||
|
|
||||||
@@ -189,7 +286,8 @@ See [Tun](/configuration/inbound/tun/#loopback_address).
|
|||||||
|
|
||||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
||||||
|
|
||||||
The following data was tested using [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro.
|
The following data was tested
|
||||||
|
using [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro.
|
||||||
|
|
||||||
| Version | Stack | MTU | Upload | Download |
|
| Version | Stack | MTU | Upload | Download |
|
||||||
|-------------|--------|-------|--------|----------|
|
|-------------|--------|-------|--------|----------|
|
||||||
@@ -490,7 +588,8 @@ See [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/conf
|
|||||||
|
|
||||||
**2**:
|
**2**:
|
||||||
|
|
||||||
`resolve` route action now accepts `disable_cache` and other options like in DNS route actions, see [Route Action](/configuration/route/rule_action).
|
`resolve` route action now accepts `disable_cache` and other options like in DNS route actions,
|
||||||
|
see [Route Action](/configuration/route/rule_action).
|
||||||
|
|
||||||
**3**:
|
**3**:
|
||||||
|
|
||||||
@@ -521,7 +620,8 @@ See [Tailscale](/configuration/endpoint/tailscale/).
|
|||||||
|
|
||||||
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
||||||
|
|
||||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches
|
||||||
|
from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||||
|
|
||||||
### 1.11.3
|
### 1.11.3
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
|||||||
|
|
||||||
!!! failure ""
|
!!! failure ""
|
||||||
|
|
||||||
We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected).
|
Due to non-technical reasons, we are temporarily unable to update the sing-box app on the App Store and release the standalone version of the macOS client (TestFlight users are not affected)
|
||||||
|
|
||||||
## :material-graph: Requirements
|
## :material-graph: Requirements
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
|||||||
|
|
||||||
## :material-download: Download
|
## :material-download: Download
|
||||||
|
|
||||||
* [App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)
|
* ~~[App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)~~
|
||||||
* TestFlight (Beta)
|
* TestFlight (Beta)
|
||||||
|
|
||||||
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
||||||
@@ -26,15 +26,15 @@ TestFlight quota is only available to [sponsors](https://github.com/sponsors/nek
|
|||||||
Once you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot)
|
Once you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot)
|
||||||
or sending us your Apple ID [via email](mailto:contact@sagernet.org).
|
or sending us your Apple ID [via email](mailto:contact@sagernet.org).
|
||||||
|
|
||||||
## :material-file-download: Download (macOS standalone version)
|
## ~~:material-file-download: Download (macOS standalone version)~~
|
||||||
|
|
||||||
* [Homebrew Cask](https://formulae.brew.sh/cask/sfm)
|
* ~~[Homebrew Cask](https://formulae.brew.sh/cask/sfm)~~
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install sfm
|
# brew install sfm
|
||||||
```
|
```
|
||||||
|
|
||||||
* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)
|
* ~~[GitHub Releases](https://github.com/SagerNet/sing-box/releases)~~
|
||||||
|
|
||||||
## :material-source-repository: Source code
|
## :material-source-repository: Source code
|
||||||
|
|
||||||
|
|||||||
@@ -412,7 +412,7 @@ Match default interface address.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
Only supported in graphical clients on Android and Apple platforms, or on Linux.
|
||||||
|
|
||||||
Match WiFi SSID.
|
Match WiFi SSID.
|
||||||
|
|
||||||
@@ -420,7 +420,7 @@ Match WiFi SSID.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
Only supported in graphical clients on Android and Apple platforms, or on Linux.
|
||||||
|
|
||||||
Match WiFi BSSID.
|
Match WiFi BSSID.
|
||||||
|
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。
|
||||||
|
|
||||||
匹配 WiFi SSID。
|
匹配 WiFi SSID。
|
||||||
|
|
||||||
@@ -419,7 +419,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。
|
||||||
|
|
||||||
匹配 WiFi BSSID。
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [exclude_mptcp](#exclude_mptcp)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [loopback_address](#loopback_address)
|
:material-plus: [loopback_address](#loopback_address)
|
||||||
@@ -63,6 +67,7 @@ icon: material/new-box
|
|||||||
"auto_redirect": true,
|
"auto_redirect": true,
|
||||||
"auto_redirect_input_mark": "0x2023",
|
"auto_redirect_input_mark": "0x2023",
|
||||||
"auto_redirect_output_mark": "0x2024",
|
"auto_redirect_output_mark": "0x2024",
|
||||||
|
"exclude_mptcp": false,
|
||||||
"loopback_address": [
|
"loopback_address": [
|
||||||
"10.7.0.1"
|
"10.7.0.1"
|
||||||
],
|
],
|
||||||
@@ -278,6 +283,20 @@ Connection output mark used by `auto_redirect`.
|
|||||||
|
|
||||||
`0x2024` is used by default.
|
`0x2024` is used by default.
|
||||||
|
|
||||||
|
#### exclude_mptcp
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||||
|
|
||||||
|
MPTCP cannot be transparently proxied due to protocol limitations.
|
||||||
|
|
||||||
|
Such traffic is usually created by Apple systems.
|
||||||
|
|
||||||
|
When enabled, MPTCP connections will bypass sing-box and connect directly, otherwise, will be rejected to avoid errors by default.
|
||||||
|
|
||||||
#### loopback_address
|
#### loopback_address
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [exclude_mptcp](#exclude_mptcp)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [loopback_address](#loopback_address)
|
:material-plus: [loopback_address](#loopback_address)
|
||||||
@@ -63,6 +67,7 @@ icon: material/new-box
|
|||||||
"auto_redirect": true,
|
"auto_redirect": true,
|
||||||
"auto_redirect_input_mark": "0x2023",
|
"auto_redirect_input_mark": "0x2023",
|
||||||
"auto_redirect_output_mark": "0x2024",
|
"auto_redirect_output_mark": "0x2024",
|
||||||
|
"exclude_mptcp": false,
|
||||||
"loopback_address": [
|
"loopback_address": [
|
||||||
"10.7.0.1"
|
"10.7.0.1"
|
||||||
],
|
],
|
||||||
@@ -277,6 +282,20 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
默认使用 `0x2024`。
|
默认使用 `0x2024`。
|
||||||
|
|
||||||
|
#### exclude_mptcp
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||||
|
|
||||||
|
由于协议限制,MPTCP 无法被透明代理。
|
||||||
|
|
||||||
|
此类流量通常由 Apple 系统创建。
|
||||||
|
|
||||||
|
启用时,MPTCP 连接将绕过 sing-box 直接连接,否则,将被拒绝以避免错误。
|
||||||
|
|
||||||
#### loopback_address
|
#### loopback_address
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `selector` | [Selector](./selector/) |
|
| `selector` | [Selector](./selector/) |
|
||||||
| `urltest` | [URLTest](./urltest/) |
|
| `urltest` | [URLTest](./urltest/) |
|
||||||
|
| `naive` | [NaiveProxy](./naive/) |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `selector` | [Selector](./selector/) |
|
| `selector` | [Selector](./selector/) |
|
||||||
| `urltest` | [URLTest](./urltest/) |
|
| `urltest` | [URLTest](./urltest/) |
|
||||||
|
| `naive` | [NaiveProxy](./naive/) |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
68
docs/configuration/outbound/naive.md
Normal file
68
docs/configuration/outbound/naive.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: Initial release
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "naive",
|
||||||
|
"tag": "naive-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 443,
|
||||||
|
"username": "sekai",
|
||||||
|
"password": "password",
|
||||||
|
"insecure_concurrency": 0,
|
||||||
|
"extra_headers": {},
|
||||||
|
"tls": {},
|
||||||
|
|
||||||
|
... // Dial Fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
NaiveProxy outbound is only available on Apple platforms, Android, Windows and some Linux architectures, see [Build from source](/installation/build-from-source/#with_naive_outbound).
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server address.
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server port.
|
||||||
|
|
||||||
|
#### username
|
||||||
|
|
||||||
|
Authentication username.
|
||||||
|
|
||||||
|
#### password
|
||||||
|
|
||||||
|
Authentication password.
|
||||||
|
|
||||||
|
#### insecure_concurrency
|
||||||
|
|
||||||
|
Number of concurrent tunnel connections. Multiple connections make the tunneling easier to detect through traffic analysis, which defeats the purpose of NaiveProxy's design to resist traffic analysis.
|
||||||
|
|
||||||
|
#### extra_headers
|
||||||
|
|
||||||
|
Extra headers to send in HTTP requests.
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||||
|
|
||||||
|
Only `server_name`, `certificate`, `certificate_path` and `certificate_public_key_sha256` are supported.
|
||||||
|
|
||||||
|
### Dial Fields
|
||||||
|
|
||||||
|
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||||
68
docs/configuration/outbound/naive.zh.md
Normal file
68
docs/configuration/outbound/naive.zh.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: 初始版本
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "naive",
|
||||||
|
"tag": "naive-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 443,
|
||||||
|
"username": "sekai",
|
||||||
|
"password": "password",
|
||||||
|
"insecure_concurrency": 0,
|
||||||
|
"extra_headers": {},
|
||||||
|
"tls": {},
|
||||||
|
|
||||||
|
... // 拨号字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
NaiveProxy 出站仅在 Apple 平台、Android、Windows 和部分架构的 Linux 上可用,参阅 [从源代码构建](/zh/installation/build-from-source/#with_naive_outbound)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
服务器地址。
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
服务器端口。
|
||||||
|
|
||||||
|
#### username
|
||||||
|
|
||||||
|
认证用户名。
|
||||||
|
|
||||||
|
#### password
|
||||||
|
|
||||||
|
认证密码。
|
||||||
|
|
||||||
|
#### insecure_concurrency
|
||||||
|
|
||||||
|
并发隧道连接数。多连接使隧道更容易被流量分析检测,违背 NaiveProxy 抵抗流量分析的设计目的。
|
||||||
|
|
||||||
|
#### extra_headers
|
||||||
|
|
||||||
|
HTTP 请求中发送的额外头部。
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||||
|
|
||||||
|
只有 `server_name`、`certificate`、`certificate_path` 和 `certificate_public_key_sha256` 是被支持的。
|
||||||
|
|
||||||
|
### 拨号字段
|
||||||
|
|
||||||
|
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||||
@@ -430,7 +430,7 @@ Match default interface address.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
Only supported in graphical clients on Android and Apple platforms, or on Linux.
|
||||||
|
|
||||||
Match WiFi SSID.
|
Match WiFi SSID.
|
||||||
|
|
||||||
@@ -438,7 +438,7 @@ Match WiFi SSID.
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
Only supported in graphical clients on Android and Apple platforms.
|
Only supported in graphical clients on Android and Apple platforms, or on Linux.
|
||||||
|
|
||||||
Match WiFi BSSID.
|
Match WiFi BSSID.
|
||||||
|
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。
|
||||||
|
|
||||||
匹配 WiFi SSID。
|
匹配 WiFi SSID。
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。
|
||||||
|
|
||||||
匹配 WiFi BSSID。
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
|
|||||||
106
docs/configuration/service/ccm.md
Normal file
106
docs/configuration/service/ccm.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
# CCM
|
||||||
|
|
||||||
|
CCM (Claude Code Multiplexer) service is a multiplexing service that allows you to access your local Claude Code subscription remotely through custom tokens.
|
||||||
|
|
||||||
|
It handles OAuth authentication with Claude's API on your local machine while allowing remote Claude Code to authenticate using Auth Tokens via the `ANTHROPIC_AUTH_TOKEN` environment variable.
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "ccm",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"credential_path": "",
|
||||||
|
"usages_path": "",
|
||||||
|
"users": [],
|
||||||
|
"headers": {},
|
||||||
|
"detour": "",
|
||||||
|
"tls": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### credential_path
|
||||||
|
|
||||||
|
Path to the Claude Code OAuth credentials file.
|
||||||
|
|
||||||
|
If not specified, defaults to:
|
||||||
|
- `$CLAUDE_CONFIG_DIR/.credentials.json` if `CLAUDE_CONFIG_DIR` environment variable is set
|
||||||
|
- `~/.claude/.credentials.json` otherwise
|
||||||
|
|
||||||
|
On macOS, credentials are read from the system keychain first, then fall back to the file if unavailable.
|
||||||
|
|
||||||
|
Refreshed tokens are automatically written back to the same location.
|
||||||
|
|
||||||
|
#### usages_path
|
||||||
|
|
||||||
|
Path to the file for storing aggregated API usage statistics.
|
||||||
|
|
||||||
|
Usage tracking is disabled if not specified.
|
||||||
|
|
||||||
|
When enabled, the service tracks and saves comprehensive statistics including:
|
||||||
|
- Request counts
|
||||||
|
- Token usage (input, output, cache read, cache creation)
|
||||||
|
- Calculated costs in USD based on Claude API pricing
|
||||||
|
|
||||||
|
Statistics are organized by model, context window (200k standard vs 1M premium), and optionally by user when authentication is enabled.
|
||||||
|
|
||||||
|
The statistics file is automatically saved every minute and upon service shutdown.
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
List of authorized users for token authentication.
|
||||||
|
|
||||||
|
If empty, no authentication is required.
|
||||||
|
|
||||||
|
Claude Code authenticates by setting the `ANTHROPIC_AUTH_TOKEN` environment variable to their token value.
|
||||||
|
|
||||||
|
#### headers
|
||||||
|
|
||||||
|
Custom HTTP headers to send to the Claude API.
|
||||||
|
|
||||||
|
These headers will override any existing headers with the same name.
|
||||||
|
|
||||||
|
#### detour
|
||||||
|
|
||||||
|
Outbound tag for connecting to the Claude API.
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"type": "ccm",
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"listen_port": 8080
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Connect to the CCM service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export ANTHROPIC_BASE_URL="http://127.0.0.1:8080"
|
||||||
|
export ANTHROPIC_AUTH_TOKEN="sk-ant-ccm-auth-token-not-required-in-this-context"
|
||||||
|
|
||||||
|
claude
|
||||||
|
```
|
||||||
106
docs/configuration/service/ccm.zh.md
Normal file
106
docs/configuration/service/ccm.zh.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
# CCM
|
||||||
|
|
||||||
|
CCM(Claude Code 多路复用器)服务是一个多路复用服务,允许您通过自定义令牌远程访问本地的 Claude Code 订阅。
|
||||||
|
|
||||||
|
它在本地机器上处理与 Claude API 的 OAuth 身份验证,同时允许远程 Claude Code 通过 `ANTHROPIC_AUTH_TOKEN` 环境变量使用认证令牌进行身份验证。
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "ccm",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"credential_path": "",
|
||||||
|
"usages_path": "",
|
||||||
|
"users": [],
|
||||||
|
"headers": {},
|
||||||
|
"detour": "",
|
||||||
|
"tls": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### credential_path
|
||||||
|
|
||||||
|
Claude Code OAuth 凭据文件的路径。
|
||||||
|
|
||||||
|
如果未指定,默认值为:
|
||||||
|
- 如果设置了 `CLAUDE_CONFIG_DIR` 环境变量,则使用 `$CLAUDE_CONFIG_DIR/.credentials.json`
|
||||||
|
- 否则使用 `~/.claude/.credentials.json`
|
||||||
|
|
||||||
|
在 macOS 上,首先从系统钥匙串读取凭据,如果不可用则回退到文件。
|
||||||
|
|
||||||
|
刷新的令牌会自动写回相同位置。
|
||||||
|
|
||||||
|
#### usages_path
|
||||||
|
|
||||||
|
用于存储聚合 API 使用统计信息的文件路径。
|
||||||
|
|
||||||
|
如果未指定,使用跟踪将被禁用。
|
||||||
|
|
||||||
|
启用后,服务会跟踪并保存全面的统计信息,包括:
|
||||||
|
- 请求计数
|
||||||
|
- 令牌使用量(输入、输出、缓存读取、缓存创建)
|
||||||
|
- 基于 Claude API 定价计算的美元成本
|
||||||
|
|
||||||
|
统计信息按模型、上下文窗口(200k 标准版 vs 1M 高级版)以及可选的用户(启用身份验证时)进行组织。
|
||||||
|
|
||||||
|
统计文件每分钟自动保存一次,并在服务关闭时保存。
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
用于令牌身份验证的授权用户列表。
|
||||||
|
|
||||||
|
如果为空,则不需要身份验证。
|
||||||
|
|
||||||
|
Claude Code 通过设置 `ANTHROPIC_AUTH_TOKEN` 环境变量为其令牌值进行身份验证。
|
||||||
|
|
||||||
|
#### headers
|
||||||
|
|
||||||
|
发送到 Claude API 的自定义 HTTP 头。
|
||||||
|
|
||||||
|
这些头会覆盖同名的现有头。
|
||||||
|
|
||||||
|
#### detour
|
||||||
|
|
||||||
|
用于连接 Claude API 的出站标签。
|
||||||
|
|
||||||
|
#### tls
|
||||||
|
|
||||||
|
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"type": "ccm",
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"listen_port": 8080
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
连接到 CCM 服务:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export ANTHROPIC_BASE_URL="http://127.0.0.1:8080"
|
||||||
|
export ANTHROPIC_AUTH_TOKEN="sk-ant-ccm-auth-token-not-required-in-this-context"
|
||||||
|
|
||||||
|
claude
|
||||||
|
```
|
||||||
@@ -23,6 +23,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
| Type | Format |
|
| Type | Format |
|
||||||
|------------|------------------------|
|
|------------|------------------------|
|
||||||
|
| `ccm` | [CCM](./ccm) |
|
||||||
| `derp` | [DERP](./derp) |
|
| `derp` | [DERP](./derp) |
|
||||||
| `resolved` | [Resolved](./resolved) |
|
| `resolved` | [Resolved](./resolved) |
|
||||||
| `ssm-api` | [SSM API](./ssm-api) |
|
| `ssm-api` | [SSM API](./ssm-api) |
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|-----------|------------------------|
|
|-----------|------------------------|
|
||||||
|
| `ccm` | [CCM](./ccm) |
|
||||||
| `derp` | [DERP](./derp) |
|
| `derp` | [DERP](./derp) |
|
||||||
| `resolved`| [Resolved](./resolved) |
|
| `resolved`| [Resolved](./resolved) |
|
||||||
| `ssm-api` | [SSM API](./ssm-api) |
|
| `ssm-api` | [SSM API](./ssm-api) |
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive)
|
||||||
|
:material-plus: [tcp_keep_alive](#tcp_keep_alive)
|
||||||
|
:material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [domain_resolver](#domain_resolver)
|
:material-plus: [domain_resolver](#domain_resolver)
|
||||||
@@ -29,6 +35,9 @@ icon: material/new-box
|
|||||||
"connect_timeout": "",
|
"connect_timeout": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
|
"disable_tcp_keep_alive": false,
|
||||||
|
"tcp_keep_alive": "",
|
||||||
|
"tcp_keep_alive_interval": "",
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
|
|
||||||
"domain_resolver": "", // or {}
|
"domain_resolver": "", // or {}
|
||||||
@@ -112,6 +121,30 @@ Enable TCP Fast Open.
|
|||||||
|
|
||||||
Enable TCP Multi Path.
|
Enable TCP Multi Path.
|
||||||
|
|
||||||
|
#### disable_tcp_keep_alive
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
Disable TCP keep alive.
|
||||||
|
|
||||||
|
#### tcp_keep_alive
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
Default value changed from `10m` to `5m`.
|
||||||
|
|
||||||
|
TCP keep-alive initial period.
|
||||||
|
|
||||||
|
`5m` will be used by default.
|
||||||
|
|
||||||
|
#### tcp_keep_alive_interval
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
TCP keep-alive interval.
|
||||||
|
|
||||||
|
`75s` will be used by default.
|
||||||
|
|
||||||
#### udp_fragment
|
#### udp_fragment
|
||||||
|
|
||||||
Enable UDP fragmentation.
|
Enable UDP fragmentation.
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive)
|
||||||
|
:material-plus: [tcp_keep_alive](#tcp_keep_alive)
|
||||||
|
:material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [domain_resolver](#domain_resolver)
|
:material-plus: [domain_resolver](#domain_resolver)
|
||||||
@@ -29,7 +35,11 @@ icon: material/new-box
|
|||||||
"connect_timeout": "",
|
"connect_timeout": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
|
"disable_tcp_keep_alive": false,
|
||||||
|
"tcp_keep_alive": "",
|
||||||
|
"tcp_keep_alive_interval": "",
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
|
|
||||||
"domain_resolver": "", // 或 {}
|
"domain_resolver": "", // 或 {}
|
||||||
"network_strategy": "",
|
"network_strategy": "",
|
||||||
"network_type": [],
|
"network_type": [],
|
||||||
@@ -109,6 +119,30 @@ icon: material/new-box
|
|||||||
|
|
||||||
启用 TCP Multi Path。
|
启用 TCP Multi Path。
|
||||||
|
|
||||||
|
#### disable_tcp_keep_alive
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
禁用 TCP keep alive。
|
||||||
|
|
||||||
|
#### tcp_keep_alive
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
默认值从 `10m` 更改为 `5m`。
|
||||||
|
|
||||||
|
TCP keep-alive 初始周期。
|
||||||
|
|
||||||
|
默认使用 `5m`。
|
||||||
|
|
||||||
|
#### tcp_keep_alive_interval
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
TCP keep-alive 间隔。
|
||||||
|
|
||||||
|
默认使用 `75s`。
|
||||||
|
|
||||||
#### udp_fragment
|
#### udp_fragment
|
||||||
|
|
||||||
启用 UDP 分段。
|
启用 UDP 分段。
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive)
|
||||||
|
:material-alert: [tcp_keep_alive](#tcp_keep_alive)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [netns](#netns)
|
:material-plus: [netns](#netns)
|
||||||
@@ -29,6 +34,9 @@ icon: material/new-box
|
|||||||
"netns": "",
|
"netns": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
|
"disable_tcp_keep_alive": false,
|
||||||
|
"tcp_keep_alive": "",
|
||||||
|
"tcp_keep_alive_interval": "",
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"udp_timeout": "",
|
"udp_timeout": "",
|
||||||
"detour": "",
|
"detour": "",
|
||||||
@@ -101,6 +109,28 @@ Enable TCP Fast Open.
|
|||||||
|
|
||||||
Enable TCP Multi Path.
|
Enable TCP Multi Path.
|
||||||
|
|
||||||
|
#### disable_tcp_keep_alive
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
Disable TCP keep alive.
|
||||||
|
|
||||||
|
#### tcp_keep_alive
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
Default value changed from `10m` to `5m`.
|
||||||
|
|
||||||
|
TCP keep alive initial period.
|
||||||
|
|
||||||
|
`5m` will be used by default.
|
||||||
|
|
||||||
|
#### tcp_keep_alive_interval
|
||||||
|
|
||||||
|
TCP keep-alive interval.
|
||||||
|
|
||||||
|
`75s` will be used by default.
|
||||||
|
|
||||||
#### udp_fragment
|
#### udp_fragment
|
||||||
|
|
||||||
Enable UDP fragmentation.
|
Enable UDP fragmentation.
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive)
|
||||||
|
:material-alert: [tcp_keep_alive](#tcp_keep_alive)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [netns](#netns)
|
:material-plus: [netns](#netns)
|
||||||
@@ -29,6 +34,9 @@ icon: material/new-box
|
|||||||
"netns": "",
|
"netns": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
|
"disable_tcp_keep_alive": false,
|
||||||
|
"tcp_keep_alive": "",
|
||||||
|
"tcp_keep_alive_interval": "",
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"udp_timeout": "",
|
"udp_timeout": "",
|
||||||
"detour": "",
|
"detour": "",
|
||||||
@@ -101,6 +109,28 @@ icon: material/new-box
|
|||||||
|
|
||||||
启用 TCP Multi Path。
|
启用 TCP Multi Path。
|
||||||
|
|
||||||
|
#### disable_tcp_keep_alive
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
禁用 TCP keep alive。
|
||||||
|
|
||||||
|
#### tcp_keep_alive
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
默认值从 `10m` 更改为 `5m`。
|
||||||
|
|
||||||
|
TCP keep alive 初始周期。
|
||||||
|
|
||||||
|
默认使用 `5m`。
|
||||||
|
|
||||||
|
#### tcp_keep_alive_interval
|
||||||
|
|
||||||
|
TCP keep-alive 间隔。
|
||||||
|
|
||||||
|
默认使用 `75s`。
|
||||||
|
|
||||||
#### udp_fragment
|
#### udp_fragment
|
||||||
|
|
||||||
启用 UDP 分段。
|
启用 UDP 分段。
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ icon: material/new-box
|
|||||||
:material-plus: [kernel_rx](#kernel_rx)
|
:material-plus: [kernel_rx](#kernel_rx)
|
||||||
:material-plus: [curve_preferences](#curve_preferences)
|
:material-plus: [curve_preferences](#curve_preferences)
|
||||||
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
|
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
|
||||||
:material-plus: [client_authentication](#client_authentication)
|
|
||||||
:material-plus: [client_certificate](#client_certificate)
|
:material-plus: [client_certificate](#client_certificate)
|
||||||
:material-plus: [client_certificate_path](#client_certificate_path)
|
:material-plus: [client_certificate_path](#client_certificate_path)
|
||||||
|
:material-plus: [client_key](#client_key)
|
||||||
|
:material-plus: [client_key_path](#client_key_path)
|
||||||
|
:material-plus: [client_authentication](#client_authentication)
|
||||||
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
|
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
@@ -101,9 +103,14 @@ icon: material/new-box
|
|||||||
"min_version": "",
|
"min_version": "",
|
||||||
"max_version": "",
|
"max_version": "",
|
||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
|
"curve_preferences": [],
|
||||||
"certificate": "",
|
"certificate": "",
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"certificate_public_key_sha256": [],
|
"certificate_public_key_sha256": [],
|
||||||
|
"client_certificate": [],
|
||||||
|
"client_certificate_path": "",
|
||||||
|
"client_key": [],
|
||||||
|
"client_key_path": "",
|
||||||
"fragment": false,
|
"fragment": false,
|
||||||
"fragment_fallback_delay": "",
|
"fragment_fallback_delay": "",
|
||||||
"record_fragment": false,
|
"record_fragment": false,
|
||||||
@@ -258,6 +265,38 @@ openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform d
|
|||||||
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### client_certificate
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
Client certificate chain line array, in PEM format.
|
||||||
|
|
||||||
|
#### client_certificate_path
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
The path to client certificate chain, in PEM format.
|
||||||
|
|
||||||
|
#### client_key
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
Client private key line array, in PEM format.
|
||||||
|
|
||||||
|
#### client_key_path
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
==Client only==
|
||||||
|
|
||||||
|
The path to client private key, in PEM format.
|
||||||
|
|
||||||
#### key
|
#### key
|
||||||
|
|
||||||
==Server only==
|
==Server only==
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ icon: material/new-box
|
|||||||
:material-plus: [kernel_rx](#kernel_rx)
|
:material-plus: [kernel_rx](#kernel_rx)
|
||||||
:material-plus: [curve_preferences](#curve_preferences)
|
:material-plus: [curve_preferences](#curve_preferences)
|
||||||
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
|
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
|
||||||
:material-plus: [client_authentication](#client_authentication)
|
|
||||||
:material-plus: [client_certificate](#client_certificate)
|
:material-plus: [client_certificate](#client_certificate)
|
||||||
:material-plus: [client_certificate_path](#client_certificate_path)
|
:material-plus: [client_certificate_path](#client_certificate_path)
|
||||||
|
:material-plus: [client_key](#client_key)
|
||||||
|
:material-plus: [client_key_path](#client_key_path)
|
||||||
|
:material-plus: [client_authentication](#client_authentication)
|
||||||
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
|
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
@@ -101,9 +103,14 @@ icon: material/new-box
|
|||||||
"min_version": "",
|
"min_version": "",
|
||||||
"max_version": "",
|
"max_version": "",
|
||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
|
"curve_preferences": [],
|
||||||
"certificate": "",
|
"certificate": "",
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"certificate_public_key_sha256": [],
|
"certificate_public_key_sha256": [],
|
||||||
|
"client_certificate": [],
|
||||||
|
"client_certificate_path": "",
|
||||||
|
"client_key": [],
|
||||||
|
"client_key_path": "",
|
||||||
"fragment": false,
|
"fragment": false,
|
||||||
"fragment_fallback_delay": "",
|
"fragment_fallback_delay": "",
|
||||||
"record_fragment": false,
|
"record_fragment": false,
|
||||||
@@ -253,6 +260,38 @@ openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform d
|
|||||||
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### client_certificate
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
客户端证书链行数组,PEM 格式。
|
||||||
|
|
||||||
|
#### client_certificate_path
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
客户端证书链路径,PEM 格式。
|
||||||
|
|
||||||
|
#### client_key
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
客户端私钥行数组,PEM 格式。
|
||||||
|
|
||||||
|
#### client_key_path
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
==仅客户端==
|
||||||
|
|
||||||
|
客户端私钥路径,PEM 格式。
|
||||||
|
|
||||||
#### key
|
#### key
|
||||||
|
|
||||||
==仅服务器==
|
==仅服务器==
|
||||||
|
|||||||
@@ -57,6 +57,37 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
|||||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||||
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
||||||
| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) |
|
| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) |
|
||||||
|
| `with_naive_outbound` | :material-close:️ | Build with NaiveProxy outbound support, see [NaiveProxy outbound](/configuration/outbound/naive/). |
|
||||||
|
|
||||||
It is not recommended to change the default build tag list unless you really know what you are adding.
|
It is not recommended to change the default build tag list unless you really know what you are adding.
|
||||||
|
|
||||||
|
## :material-layers: with_naive_outbound
|
||||||
|
|
||||||
|
NaiveProxy outbound requires special build configurations depending on your target platform.
|
||||||
|
|
||||||
|
### Supported Platforms
|
||||||
|
|
||||||
|
| Platform | Architectures | Mode | Requirements |
|
||||||
|
|-----------------|--------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Windows | * | purego | None |
|
||||||
|
| Linux | amd64, arm64 | purego | Download libcronet from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) to system library path or sing-box binary directory |
|
||||||
|
| Linux | 386, amd64, arm, arm64 | CGO | Chromium toolchain (see [cronet-go](https://github.com/sagernet/cronet-go)) |
|
||||||
|
| Apple platforms | * | CGO | Xcode |
|
||||||
|
| Android | * | CGO | Android NDK |
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Use `with_purego` tag.
|
||||||
|
|
||||||
|
### Linux (purego, amd64/arm64 only)
|
||||||
|
|
||||||
|
Download `libcronet.so` from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and install to system library path or the same directory as sing-box binary, then use `with_purego` tag.
|
||||||
|
|
||||||
|
### Linux (CGO)
|
||||||
|
|
||||||
|
See [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions).
|
||||||
|
|
||||||
|
### Apple platforms / Android
|
||||||
|
|
||||||
|
See [cronet-go](https://github.com/sagernet/cronet-go).
|
||||||
|
|||||||
@@ -62,5 +62,36 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
|||||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||||
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
||||||
| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) |
|
| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) |
|
||||||
|
| `with_naive_outbound` | :material-close:️ | 构建 NaiveProxy 出站支持,参阅 [NaiveProxy 出站](/zh/configuration/outbound/naive/)。 |
|
||||||
|
|
||||||
除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。
|
除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。
|
||||||
|
|
||||||
|
## :material-layers: with_naive_outbound
|
||||||
|
|
||||||
|
NaiveProxy 出站需要根据目标平台进行特殊的构建配置。
|
||||||
|
|
||||||
|
### 支持的平台
|
||||||
|
|
||||||
|
| 平台 | 架构 | 模式 | 要求 |
|
||||||
|
|---------------|----------------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Windows | * | purego | 无 |
|
||||||
|
| Linux | amd64, arm64 | purego | 从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载 libcronet 到系统库目录或 sing-box 二进制文件相同目录 |
|
||||||
|
| Linux | 386, amd64, arm, arm64 | CGO | Chromium 工具链(参阅 [cronet-go](https://github.com/sagernet/cronet-go)) |
|
||||||
|
| Apple 平台 | * | CGO | Xcode |
|
||||||
|
| Android | * | CGO | Android NDK |
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
使用 `with_purego` 标记。
|
||||||
|
|
||||||
|
### Linux (purego, 仅 amd64/arm64)
|
||||||
|
|
||||||
|
从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载 `libcronet.so` 并安装到系统库目录或 sing-box 二进制文件相同目录,然后使用 `with_purego` 标记。
|
||||||
|
|
||||||
|
### Linux (CGO)
|
||||||
|
|
||||||
|
参阅 [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions)。
|
||||||
|
|
||||||
|
### Apple 平台 / Android
|
||||||
|
|
||||||
|
参阅 [cronet-go](https://github.com/sagernet/cronet-go)。
|
||||||
|
|||||||
@@ -11,16 +11,22 @@ the project maintainer via [GitHub Sponsors](https://github.com/sponsors/nekohas
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Special Sponsors
|
## Commercial Sponsors
|
||||||
|
|
||||||
**Viral Tech, Inc.**
|
> [Warp](https://go.warp.dev/sing-box), Built for coding with multiple AI agents.
|
||||||
|
|
||||||
|
[](https://go.warp.dev/sing-box)
|
||||||
|
|
||||||
|
## Special Sponsors
|
||||||
|
|
||||||
|
> Viral Tech, Inc.
|
||||||
|
|
||||||
Helping us re-list sing-box apps on the Apple Store.
|
Helping us re-list sing-box apps on the Apple Store.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[](https://www.jetbrains.com)
|
> [JetBrains](https://www.jetbrains.com)
|
||||||
|
|
||||||
Free license for the amazing IDEs.
|
Free license for the amazing IDEs.
|
||||||
|
|
||||||
---
|
[](https://www.jetbrains.com)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
"github.com/sagernet/ws"
|
"github.com/sagernet/ws"
|
||||||
@@ -53,7 +54,7 @@ type Server struct {
|
|||||||
|
|
||||||
mode string
|
mode string
|
||||||
modeList []string
|
modeList []string
|
||||||
modeUpdateHook chan<- struct{}
|
modeUpdateHook *observable.Subscriber[struct{}]
|
||||||
|
|
||||||
externalController bool
|
externalController bool
|
||||||
externalUI string
|
externalUI string
|
||||||
@@ -203,7 +204,7 @@ func (s *Server) ModeList() []string {
|
|||||||
return s.modeList
|
return s.modeList
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SetModeUpdateHook(hook chan<- struct{}) {
|
func (s *Server) SetModeUpdateHook(hook *observable.Subscriber[struct{}]) {
|
||||||
s.modeUpdateHook = hook
|
s.modeUpdateHook = hook
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,10 +222,7 @@ func (s *Server) SetMode(newMode string) {
|
|||||||
}
|
}
|
||||||
s.mode = newMode
|
s.mode = newMode
|
||||||
if s.modeUpdateHook != nil {
|
if s.modeUpdateHook != nil {
|
||||||
select {
|
s.modeUpdateHook.Emit(struct{}{})
|
||||||
case s.modeUpdateHook <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.dnsRouter.ClearCache()
|
s.dnsRouter.ClearCache()
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/daemon"
|
"github.com/sagernet/sing-box/daemon"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,11 +30,14 @@ type CommandClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CommandClientOptions struct {
|
type CommandClientOptions struct {
|
||||||
Command int32
|
commands []int32
|
||||||
Commands Int32Iterator
|
|
||||||
StatusInterval int64
|
StatusInterval int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *CommandClientOptions) AddCommand(command int32) {
|
||||||
|
o.commands = append(o.commands, command)
|
||||||
|
}
|
||||||
|
|
||||||
type CommandClientHandler interface {
|
type CommandClientHandler interface {
|
||||||
Connected()
|
Connected()
|
||||||
Disconnected(message string)
|
Disconnected(message string)
|
||||||
@@ -70,24 +73,38 @@ func NewCommandClient(handler CommandClientHandler, options *CommandClientOption
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unaryClientAuthInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||||
|
if sCommandServerSecret != "" {
|
||||||
|
ctx = metadata.AppendToOutgoingContext(ctx, "x-command-secret", sCommandServerSecret)
|
||||||
|
}
|
||||||
|
return invoker(ctx, method, req, reply, cc, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamClientAuthInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||||
|
if sCommandServerSecret != "" {
|
||||||
|
ctx = metadata.AppendToOutgoingContext(ctx, "x-command-secret", sCommandServerSecret)
|
||||||
|
}
|
||||||
|
return streamer(ctx, desc, cc, method, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CommandClient) grpcDial() (*grpc.ClientConn, error) {
|
func (c *CommandClient) grpcDial() (*grpc.ClientConn, error) {
|
||||||
var target string
|
var target string
|
||||||
if C.IsDarwin {
|
if sCommandServerListenPort == 0 {
|
||||||
port := sCommandServerListenPort
|
|
||||||
if port == 0 {
|
|
||||||
port = 8964
|
|
||||||
}
|
|
||||||
target = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))
|
|
||||||
} else {
|
|
||||||
target = "unix://" + filepath.Join(sBasePath, "command.sock")
|
target = "unix://" + filepath.Join(sBasePath, "command.sock")
|
||||||
|
} else {
|
||||||
|
target = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort)))
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
conn *grpc.ClientConn
|
conn *grpc.ClientConn
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
clientOptions := []grpc.DialOption{
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithUnaryInterceptor(unaryClientAuthInterceptor),
|
||||||
|
grpc.WithStreamInterceptor(streamClientAuthInterceptor),
|
||||||
|
}
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
conn, err = grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
conn, err = grpc.NewClient(target, clientOptions...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
@@ -110,14 +127,8 @@ func (c *CommandClient) Connect() error {
|
|||||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||||
c.clientMutex.Unlock()
|
c.clientMutex.Unlock()
|
||||||
|
|
||||||
var commands []int32
|
|
||||||
if c.options.Commands != nil {
|
|
||||||
commands = iteratorToArray[int32](c.options.Commands)
|
|
||||||
} else {
|
|
||||||
commands = []int32{c.options.Command}
|
|
||||||
}
|
|
||||||
c.handler.Connected()
|
c.handler.Connected()
|
||||||
for _, command := range commands {
|
for _, command := range c.options.commands {
|
||||||
switch command {
|
switch command {
|
||||||
case CommandLog:
|
case CommandLog:
|
||||||
go c.handleLogStream()
|
go c.handleLogStream()
|
||||||
@@ -263,14 +274,12 @@ func (c *CommandClient) handleClashModeStream() {
|
|||||||
|
|
||||||
if sFixAndroidStack {
|
if sFixAndroidStack {
|
||||||
go func() {
|
go func() {
|
||||||
c.handler.Connected()
|
|
||||||
c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode)
|
c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode)
|
||||||
if len(modeStatus.ModeList) == 0 {
|
if len(modeStatus.ModeList) == 0 {
|
||||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
c.handler.Connected()
|
|
||||||
c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode)
|
c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode)
|
||||||
if len(modeStatus.ModeList) == 0 {
|
if len(modeStatus.ModeList) == 0 {
|
||||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||||
@@ -401,6 +410,16 @@ func (c *CommandClient) ServiceClose() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CommandClient) ClearLogs() error {
|
||||||
|
client, err := c.getClientForCall()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.ClearLogs(context.Background(), &emptypb.Empty{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {
|
func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {
|
||||||
client, err := c.getClientForCall()
|
client, err := c.getClientForCall()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,29 +2,30 @@ package libbox
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/daemon"
|
"github.com/sagernet/sing-box/daemon"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandServer struct {
|
type CommandServer struct {
|
||||||
*daemon.StartedService
|
*daemon.StartedService
|
||||||
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
handler CommandServerHandler
|
handler CommandServerHandler
|
||||||
platformInterface PlatformInterface
|
platformInterface PlatformInterface
|
||||||
platformWrapper *platformInterfaceWrapper
|
platformWrapper *platformInterfaceWrapper
|
||||||
@@ -43,47 +44,88 @@ type CommandServerHandler interface {
|
|||||||
|
|
||||||
func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) {
|
func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) {
|
||||||
ctx := BaseContext(platformInterface)
|
ctx := BaseContext(platformInterface)
|
||||||
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
platformWrapper := &platformInterfaceWrapper{
|
platformWrapper := &platformInterfaceWrapper{
|
||||||
iif: platformInterface,
|
iif: platformInterface,
|
||||||
useProcFS: platformInterface.UseProcFS(),
|
useProcFS: platformInterface.UseProcFS(),
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.PlatformInterface](ctx, platformWrapper)
|
service.MustRegister[adapter.PlatformInterface](ctx, platformWrapper)
|
||||||
server := &CommandServer{
|
server := &CommandServer{
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
handler: handler,
|
handler: handler,
|
||||||
platformInterface: platformInterface,
|
platformInterface: platformInterface,
|
||||||
platformWrapper: platformWrapper,
|
platformWrapper: platformWrapper,
|
||||||
}
|
}
|
||||||
server.StartedService = daemon.NewStartedService(daemon.ServiceOptions{
|
server.StartedService = daemon.NewStartedService(daemon.ServiceOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Platform: platformWrapper,
|
// Platform: platformWrapper,
|
||||||
PlatformHandler: (*platformHandler)(server),
|
Handler: (*platformHandler)(server),
|
||||||
Debug: sDebug,
|
Debug: sDebug,
|
||||||
LogMaxLines: sLogMaxLines,
|
LogMaxLines: sLogMaxLines,
|
||||||
WorkingDirectory: sBasePath,
|
// WorkingDirectory: sWorkingPath,
|
||||||
TempDirectory: os.TempDir(),
|
// TempDirectory: sTempPath,
|
||||||
UserID: sUserID,
|
// UserID: sUserID,
|
||||||
GroupID: sGroupID,
|
// GroupID: sGroupID,
|
||||||
SystemProxyEnabled: false,
|
// SystemProxyEnabled: false,
|
||||||
})
|
})
|
||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unaryAuthInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||||
|
if sCommandServerSecret == "" {
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, status.Error(codes.Unauthenticated, "missing metadata")
|
||||||
|
}
|
||||||
|
values := md.Get("x-command-secret")
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil, status.Error(codes.Unauthenticated, "missing authentication secret")
|
||||||
|
}
|
||||||
|
if values[0] != sCommandServerSecret {
|
||||||
|
return nil, status.Error(codes.Unauthenticated, "invalid authentication secret")
|
||||||
|
}
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamAuthInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
if sCommandServerSecret == "" {
|
||||||
|
return handler(srv, ss)
|
||||||
|
}
|
||||||
|
md, ok := metadata.FromIncomingContext(ss.Context())
|
||||||
|
if !ok {
|
||||||
|
return status.Error(codes.Unauthenticated, "missing metadata")
|
||||||
|
}
|
||||||
|
values := md.Get("x-command-secret")
|
||||||
|
if len(values) == 0 {
|
||||||
|
return status.Error(codes.Unauthenticated, "missing authentication secret")
|
||||||
|
}
|
||||||
|
if values[0] != sCommandServerSecret {
|
||||||
|
return status.Error(codes.Unauthenticated, "invalid authentication secret")
|
||||||
|
}
|
||||||
|
return handler(srv, ss)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *CommandServer) Start() error {
|
func (s *CommandServer) Start() error {
|
||||||
var (
|
var (
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if C.IsAndroid && sCommandServerListenPort == 0 {
|
if sCommandServerListenPort == 0 {
|
||||||
sockPath := filepath.Join(sBasePath, "command.sock")
|
sockPath := filepath.Join(sBasePath, "command.sock")
|
||||||
os.Remove(sockPath)
|
os.Remove(sockPath)
|
||||||
listener, err = net.ListenUnix("unix", &net.UnixAddr{
|
for i := 0; i < 30; i++ {
|
||||||
Name: sockPath,
|
listener, err = net.ListenUnix("unix", &net.UnixAddr{
|
||||||
Net: "unix",
|
Name: sockPath,
|
||||||
})
|
Net: "unix",
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !errors.Is(err, syscall.EROFS) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "listen command server")
|
return E.Cause(err, "listen command server")
|
||||||
}
|
}
|
||||||
@@ -96,24 +138,23 @@ func (s *CommandServer) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
port := sCommandServerListenPort
|
listener, err = net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort))))
|
||||||
if port == 0 {
|
|
||||||
port = 8964
|
|
||||||
}
|
|
||||||
listener, err = net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port))))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "listen command server")
|
return E.Cause(err, "listen command server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.listener = listener
|
s.listener = listener
|
||||||
s.grpcServer = grpc.NewServer()
|
serverOptions := []grpc.ServerOption{
|
||||||
|
grpc.UnaryInterceptor(unaryAuthInterceptor),
|
||||||
|
grpc.StreamInterceptor(streamAuthInterceptor),
|
||||||
|
}
|
||||||
|
s.grpcServer = grpc.NewServer(serverOptions...)
|
||||||
daemon.RegisterStartedServiceServer(s.grpcServer, s.StartedService)
|
daemon.RegisterStartedServiceServer(s.grpcServer, s.StartedService)
|
||||||
go s.grpcServer.Serve(listener)
|
go s.grpcServer.Serve(listener)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CommandServer) Close() {
|
func (s *CommandServer) Close() {
|
||||||
s.cancel()
|
|
||||||
if s.grpcServer != nil {
|
if s.grpcServer != nil {
|
||||||
s.grpcServer.Stop()
|
s.grpcServer.Stop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,9 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = deprecated.Note(DeprecatedNote{})
|
var _ = deprecated.Note(DeprecatedNote{})
|
||||||
|
|
||||||
type DeprecatedNote struct {
|
type DeprecatedNote struct {
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/daemon"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
tun "github.com/sagernet/sing-tun"
|
tun "github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
@@ -21,7 +22,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ daemon.PlatformInterface = (*platformInterfaceWrapper)(nil)
|
var _ adapter.PlatformInterface = (*platformInterfaceWrapper)(nil)
|
||||||
|
|
||||||
type platformInterfaceWrapper struct {
|
type platformInterfaceWrapper struct {
|
||||||
iif PlatformInterface
|
iif PlatformInterface
|
||||||
@@ -214,16 +215,28 @@ func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notifi
|
|||||||
return w.iif.SendNotification((*Notification)(notification))
|
return w.iif.SendNotification((*Notification)(notification))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) UsePlatformLocalDNSTransport() bool {
|
func AvailablePort(startPort int32) (int32, error) {
|
||||||
return C.IsAndroid
|
for port := int(startPort); ; port++ {
|
||||||
|
if port > 65535 {
|
||||||
|
return 0, E.New("no available port found")
|
||||||
|
}
|
||||||
|
listener, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port))))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, syscall.EADDRINUSE) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return 0, E.Cause(err, "find available port")
|
||||||
|
}
|
||||||
|
err = listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
return 0, E.Cause(err, "close listener")
|
||||||
|
}
|
||||||
|
return int32(port), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions] {
|
func RandomHex(length int32) *StringBox {
|
||||||
localTransport := w.iif.LocalDNSTransport()
|
bytes := make([]byte, length)
|
||||||
if localTransport == nil {
|
common.Must1(rand.Read(bytes))
|
||||||
return nil
|
return wrapString(hex.EncodeToString(bytes))
|
||||||
}
|
|
||||||
return func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
|
||||||
return newPlatformTransport(localTransport, tag, options), nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func serviceErrorPath() string {
|
|
||||||
return filepath.Join(sWorkingPath, "network_extension_error")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearServiceError() {
|
|
||||||
os.Remove(serviceErrorPath())
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadServiceError() (*StringBox, error) {
|
|
||||||
data, err := os.ReadFile(serviceErrorPath())
|
|
||||||
if err == nil {
|
|
||||||
os.Remove(serviceErrorPath())
|
|
||||||
}
|
|
||||||
return wrapString(string(data)), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteServiceError(message string) error {
|
|
||||||
errorFile, err := os.Create(serviceErrorPath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
errorFile.WriteString(message)
|
|
||||||
errorFile.Chown(sUserID, sGroupID)
|
|
||||||
return errorFile.Close()
|
|
||||||
}
|
|
||||||
52
go.mod
52
go.mod
@@ -3,40 +3,44 @@ module github.com/sagernet/sing-box
|
|||||||
go 1.24.7
|
go 1.24.7
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/anthropics/anthropic-sdk-go v1.14.0
|
||||||
github.com/anytls/sing-anytls v0.0.11
|
github.com/anytls/sing-anytls v0.0.11
|
||||||
github.com/caddyserver/certmagic v0.23.0
|
github.com/caddyserver/certmagic v0.23.0
|
||||||
github.com/coder/websocket v1.8.13
|
github.com/coder/websocket v1.8.13
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/database64128/tfo-go/v2 v2.2.2
|
github.com/database64128/tfo-go/v2 v2.3.1
|
||||||
github.com/go-chi/chi/v5 v5.2.2
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
||||||
github.com/gofrs/uuid/v5 v5.3.2
|
github.com/gofrs/uuid/v5 v5.3.2
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
||||||
|
github.com/keybase/go-keychain v0.0.1
|
||||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1
|
github.com/libdns/alidns v1.0.5-libdns.v1.beta1
|
||||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6
|
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/metacubex/utls v1.8.0
|
github.com/metacubex/utls v1.8.3
|
||||||
github.com/mholt/acmez/v3 v3.1.2
|
github.com/mholt/acmez/v3 v3.1.2
|
||||||
github.com/miekg/dns v1.1.67
|
github.com/miekg/dns v1.1.67
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
|
github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1
|
||||||
|
github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gomobile v0.1.8
|
github.com/sagernet/gomobile v0.1.10
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||||
github.com/sagernet/quic-go v0.54.0-sing-box-mod.3
|
github.com/sagernet/quic-go v0.57.1-sing-box-mod.1
|
||||||
github.com/sagernet/sing v0.8.0-beta.5
|
github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6
|
||||||
github.com/sagernet/sing-mux v0.3.3
|
github.com/sagernet/sing-mux v0.3.3
|
||||||
github.com/sagernet/sing-quic v0.6.0-beta.3
|
github.com/sagernet/sing-quic v0.6.0-beta.5
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.8.0-beta.10
|
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||||
github.com/sagernet/smux v1.5.34-mod.2
|
github.com/sagernet/smux v1.5.34-mod.2
|
||||||
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.3
|
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4
|
||||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288
|
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
@@ -66,11 +70,12 @@ require (
|
|||||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||||
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
|
github.com/database64128/netx-go v0.1.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
||||||
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/gaissmai/bart v0.18.0 // indirect
|
github.com/gaissmai/bart v0.18.0 // indirect
|
||||||
@@ -100,8 +105,31 @@ require (
|
|||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/safchain/ethtool v0.3.0 // indirect
|
github.com/safchain/ethtool v0.3.0 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
@@ -113,6 +141,10 @@ require (
|
|||||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
|
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
|
||||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
||||||
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
|
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
|
||||||
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||||
|
|||||||
106
go.sum
106
go.sum
@@ -8,6 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
|
|||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
github.com/anthropics/anthropic-sdk-go v1.14.0 h1:EzNQvnZlaDHe2UPkoUySDz3ixRgNbwKdH8KtFpv7pi4=
|
||||||
|
github.com/anthropics/anthropic-sdk-go v1.14.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||||
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
|
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
|
||||||
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||||
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
||||||
@@ -25,10 +27,10 @@ github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||||
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a h1:t4SDi0pmNkryzKdM4QF3o5vqSP4GRjeZD/6j3nyxNP0=
|
github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM=
|
||||||
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a/go.mod h1:7K2NQKbabB5mBl41vF6YayYl5g7YpDwc4dQ5iMpP3Lg=
|
github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc=
|
||||||
github.com/database64128/tfo-go/v2 v2.2.2 h1:BxynF4qGF5ct3DpPLEG62uyJZ3LQhqaf0Ken+kyy7PM=
|
github.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw=
|
||||||
github.com/database64128/tfo-go/v2 v2.2.2/go.mod h1:2IW8jppdBwdVMjA08uEyMNnqiAHKUlqAA+J8NrsfktY=
|
github.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -39,6 +41,8 @@ github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbY
|
|||||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||||
|
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
@@ -95,6 +99,8 @@ github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBe
|
|||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||||
|
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||||
|
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
@@ -116,8 +122,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
|
|||||||
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
||||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||||
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
|
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
|
||||||
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
|
github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||||
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
||||||
@@ -135,8 +141,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
||||||
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||||
@@ -146,39 +152,89 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
|
|||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||||
|
github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 h1:ql2eCQp1sIinoSwNcJW+tBGToRoxm0rsU8uqRJA9Vao=
|
||||||
|
github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo=
|
||||||
|
github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c h1:3qNxvssYmfARhUtSFRbleSeSVoShwUEoxl42hqL75hA=
|
||||||
|
github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c/go.mod h1:/liG19g+SCq7pyaZtUHeGqUWckKfdMjR0DR83hNcxdw=
|
||||||
|
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6 h1:/4EvUgxjKFPSnlitNV90te7p4/Ywpxz1/zJuB8BT5Qs=
|
||||||
|
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
||||||
|
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:HgpJekgPVMKdODAmz6MwPBzz6dq4nImGy/5/jqD1N90=
|
||||||
|
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
|
||||||
|
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6 h1:3YsRLuWT0vEjIgsSdv8g45RuOMUDloO1rEsW/990CPQ=
|
||||||
|
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
|
||||||
|
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:1TwUqHR7/gV1hTxuhlu4qWlIN5IpPWkVA/0E8Bdjtgo=
|
||||||
|
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
|
||||||
|
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:FdA9JPgmoPYHxNgqKMyNztNyOFsxdrRvGOCxMIlHnjg=
|
||||||
|
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
|
||||||
|
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:69q8UN4r6wy15I7VGILeApQLYUMfeFzO+uZBA68vqpM=
|
||||||
|
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:2QBSQdCMrsAQOugFvgsPJpDc+JQsS6JP/JBId1YxX/g=
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:4SUxagz2PuS7I3jNf55K50ALHtVyvV8PF2/1wnfssMk=
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:5tkFlEyQ9zF4AX7asWePmwF3hQSdrrf3a8EaQ7mJ0IE=
|
||||||
|
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6 h1:lLnVN2IC00au+Vc4V2ijAWdbzlXg9r/bLxm50gKT7i4=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6 h1:SUhkusdTwrPZdrUTlyyF6NcnCdET2f1TFdE4+d6YVDI=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:aFFVgvDDBuNJKrU6Bw5btlLsX+JLyXatXmHK7KePT+c=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6 h1:VW5xkrH8Quve1UC5Py37KYHLrJN9vgLJc+raIUO6d/Q=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6 h1:YQIccJVMwMPo9yy7VB0Un/fnSMt7nDh9NS/kwfctpPA=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:dyRIgfMHkCKaSOPyjYFbXmVJDTEoSRQlk6mgBN7RuUA=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6 h1:N/4oDOsP5lCNoy8UGo4v+LdCRXOnG+uzLLNhywuaQVU=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6 h1:AsYRIu6HEZ5+1ONzzsGEk6kGJzIKPflhIXhviRQUuHg=
|
||||||
|
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:63TxOukZ5bMPrQurR2S6RPCRFydKErBCJq4oLA+lPb4=
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:WO4WmUXYh0TqmGLQ5SQyk4A05l+GSKcoFyBzW8I7kd0=
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:WQU1wWPwKOENmaSD68ltFdnn/FrdLpjJzNMjkeLGmyA=
|
||||||
|
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6 h1:1rweIHAKs+8yc1VZ/N4kod9RPJARgF5gDc4e1wh6rQo=
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk=
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:NNcFjL2/F3Ux8mJuSAJKmMGcrLmkdas1X9DWuvY7BKU=
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:TCgnN8XGroby1RkMKZgaQjCKKlVlmERtaNokKW4nyyo=
|
||||||
|
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
|
||||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||||
github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo=
|
github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c=
|
||||||
github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
|
github.com/sagernet/gomobile v0.1.10/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o=
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o=
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4=
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4=
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 h1:12pJN/zdpRltLG8l8JA65QYy/a+Mz938yAN3ZQUinbo=
|
github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 h1:6fhKbfA0b7L1CVekayV1g87uJFtMXFE0rFXR48SRrWI=
|
||||||
github.com/sagernet/quic-go v0.54.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
github.com/sagernet/quic-go v0.57.1-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing v0.8.0-beta.5 h1:Cm4CnLQGNyG5Jl1U9pKWAjFUcbjchGGqn1xeXzfI5kw=
|
github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc=
|
||||||
github.com/sagernet/sing v0.8.0-beta.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
||||||
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||||
github.com/sagernet/sing-quic v0.6.0-beta.3 h1:Z2vt49f9vNtHc9BbF9foI859n4+NAOV3gBeB1LuzL1Q=
|
github.com/sagernet/sing-quic v0.6.0-beta.5 h1:kZfRLmsPxAgl0usZUgomDurLn7ZZ26lJWIpGow9ZWR4=
|
||||||
github.com/sagernet/sing-quic v0.6.0-beta.3/go.mod h1:2/swrSS6wG6MyQA5Blq31VEWitHgBju+yZE8cPK1J5I=
|
github.com/sagernet/sing-quic v0.6.0-beta.5/go.mod h1:9D9GANrK33NjWCe1VkU5L5+8MxU39WrduBSmHuHz8GA=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-tun v0.8.0-beta.10 h1:sHqSXTvzKPDF67AwZdoBV5FA91tFdWGfA1AbenIbpA4=
|
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e h1:ZEv+9vy7vC1vbr3LfwZGx3JAOkl/w4+hnGamHw4W36M=
|
||||||
github.com/sagernet/sing-tun v0.8.0-beta.10/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg=
|
github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||||
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.3 h1:OGoHEw76F3F4keIGcOwB/5U+P1N3i+hLlgC7rvSnub0=
|
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 h1:Ceg+9Ug+qAFgEchGodlHmMOY2h7KktQQDAyuoIsPbos=
|
||||||
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.3/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw=
|
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw=
|
||||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA=
|
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA=
|
||||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
|
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||||
@@ -216,6 +272,16 @@ github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs
|
|||||||
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||||
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
|
|||||||
12
include/ccm.go
Normal file
12
include/ccm.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build with_ccm && (!darwin || cgo)
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
|
"github.com/sagernet/sing-box/service/ccm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerCCMService(registry *service.Registry) {
|
||||||
|
ccm.RegisterService(registry)
|
||||||
|
}
|
||||||
20
include/ccm_stub.go
Normal file
20
include/ccm_stub.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build !with_ccm
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerCCMService(registry *service.Registry) {
|
||||||
|
service.Register[option.CCMServiceOptions](registry, C.TypeCCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) {
|
||||||
|
return nil, E.New(`CCM is not included in this build, rebuild with -tags with_CCM`)
|
||||||
|
})
|
||||||
|
}
|
||||||
20
include/ccm_stub_darwin.go
Normal file
20
include/ccm_stub_darwin.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build with_ccm && darwin && !cgo
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerCCMService(registry *service.Registry) {
|
||||||
|
service.Register[option.CCMServiceOptions](registry, C.TypeCCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) {
|
||||||
|
return nil, E.New(`CCM requires CGO on darwin, rebuild with CGO_ENABLED=1`)
|
||||||
|
})
|
||||||
|
}
|
||||||
12
include/naive_outbound.go
Normal file
12
include/naive_outbound.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build with_naive_outbound
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/protocol/naive"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerNaiveOutbound(registry *outbound.Registry) {
|
||||||
|
naive.RegisterOutbound(registry)
|
||||||
|
}
|
||||||
20
include/naive_outbound_stub.go
Normal file
20
include/naive_outbound_stub.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build !with_naive_outbound
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerNaiveOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.NaiveOutboundOptions](registry, C.TypeNaive, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
return nil, E.New(`naive outbound is not included in this build, rebuild with -tags with_naive_outbound`)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -86,6 +86,7 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
shadowsocks.RegisterOutbound(registry)
|
shadowsocks.RegisterOutbound(registry)
|
||||||
vmess.RegisterOutbound(registry)
|
vmess.RegisterOutbound(registry)
|
||||||
trojan.RegisterOutbound(registry)
|
trojan.RegisterOutbound(registry)
|
||||||
|
registerNaiveOutbound(registry)
|
||||||
tor.RegisterOutbound(registry)
|
tor.RegisterOutbound(registry)
|
||||||
ssh.RegisterOutbound(registry)
|
ssh.RegisterOutbound(registry)
|
||||||
shadowtls.RegisterOutbound(registry)
|
shadowtls.RegisterOutbound(registry)
|
||||||
@@ -134,6 +135,7 @@ func ServiceRegistry() *service.Registry {
|
|||||||
ssmapi.RegisterService(registry)
|
ssmapi.RegisterService(registry)
|
||||||
|
|
||||||
registerDERPService(registry)
|
registerDERPService(registry)
|
||||||
|
registerCCMService(registry)
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|||||||
20
option/ccm.go
Normal file
20
option/ccm.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CCMServiceOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
InboundTLSOptionsContainer
|
||||||
|
CredentialPath string `json:"credential_path,omitempty"`
|
||||||
|
Users []CCMUser `json:"users,omitempty"`
|
||||||
|
Headers badoption.HTTPHeader `json:"headers,omitempty"`
|
||||||
|
Detour string `json:"detour,omitempty"`
|
||||||
|
UsagesPath string `json:"usages_path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CCMUser struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
}
|
||||||
@@ -65,6 +65,7 @@ type ListenOptions struct {
|
|||||||
RoutingMark FwMark `json:"routing_mark,omitempty"`
|
RoutingMark FwMark `json:"routing_mark,omitempty"`
|
||||||
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
||||||
NetNs string `json:"netns,omitempty"`
|
NetNs string `json:"netns,omitempty"`
|
||||||
|
DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,omitempty"`
|
||||||
TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"`
|
TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"`
|
||||||
TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"`
|
TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import "github.com/sagernet/sing/common/auth"
|
import (
|
||||||
|
"github.com/sagernet/sing/common/auth"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
type NaiveInboundOptions struct {
|
type NaiveInboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
@@ -8,3 +11,13 @@ type NaiveInboundOptions struct {
|
|||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NaiveOutboundOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
InsecureConcurrency int `json:"insecure_concurrency,omitempty"`
|
||||||
|
ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"`
|
||||||
|
OutboundTLSOptionsContainer
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,24 +65,27 @@ type DialerOptionsWrapper interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DialerOptions struct {
|
type DialerOptions struct {
|
||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty"`
|
||||||
BindInterface string `json:"bind_interface,omitempty"`
|
BindInterface string `json:"bind_interface,omitempty"`
|
||||||
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
|
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
|
||||||
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
|
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
|
||||||
ProtectPath string `json:"protect_path,omitempty"`
|
ProtectPath string `json:"protect_path,omitempty"`
|
||||||
RoutingMark FwMark `json:"routing_mark,omitempty"`
|
RoutingMark FwMark `json:"routing_mark,omitempty"`
|
||||||
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
||||||
NetNs string `json:"netns,omitempty"`
|
NetNs string `json:"netns,omitempty"`
|
||||||
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
|
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
|
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
|
||||||
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,omitempty"`
|
||||||
UDPFragmentDefault bool `json:"-"`
|
TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"`
|
||||||
DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"`
|
TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"`
|
||||||
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
|
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
UDPFragmentDefault bool `json:"-"`
|
||||||
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
|
DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"`
|
||||||
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
|
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
|
||||||
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
|
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
|
||||||
|
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
|
||||||
|
|
||||||
// Deprecated: migrated to domain resolver
|
// Deprecated: migrated to domain resolver
|
||||||
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ type OutboundTLSOptions struct {
|
|||||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"`
|
CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"`
|
||||||
|
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
|
||||||
|
ClientCertificatePath string `json:"client_certificate_path,omitempty"`
|
||||||
|
ClientKey badoption.Listable[string] `json:"client_key,omitempty"`
|
||||||
|
ClientKeyPath string `json:"client_key_path,omitempty"`
|
||||||
Fragment bool `json:"fragment,omitempty"`
|
Fragment bool `json:"fragment,omitempty"`
|
||||||
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
|
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
|
||||||
RecordFragment bool `json:"record_fragment,omitempty"`
|
RecordFragment bool `json:"record_fragment,omitempty"`
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type TunInboundOptions struct {
|
|||||||
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
||||||
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
|
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
|
||||||
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"`
|
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"`
|
||||||
|
ExcludeMPTCP bool `json:"exclude_mptcp,omitempty"`
|
||||||
LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"`
|
LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"`
|
||||||
StrictRoute bool `json:"strict_route,omitempty"`
|
StrictRoute bool `json:"strict_route,omitempty"`
|
||||||
RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"`
|
RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"`
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
@@ -43,6 +44,13 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
|
// TCP Fast Open is incompatible with anytls because TFO creates a lazy connection
|
||||||
|
// that only establishes on first write. The lazy connection returns an empty address
|
||||||
|
// before establishment, but anytls SOCKS wrapper tries to access the remote address
|
||||||
|
// during handshake, causing a null pointer dereference crash.
|
||||||
|
if options.DialerOptions.TCPFastOpen {
|
||||||
|
return nil, E.New("tcp_fast_open is not supported with anytls outbound")
|
||||||
|
}
|
||||||
|
|
||||||
tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package naive
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -22,7 +22,11 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
sHttp "github.com/sagernet/sing/protocol/http"
|
sHttp "github.com/sagernet/sing/protocol/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ConfigureHTTP3ListenerFunc func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error)
|
var ConfigureHTTP3ListenerFunc func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error)
|
||||||
@@ -82,16 +86,11 @@ func (n *Inbound) Start(stage adapter.StartStage) error {
|
|||||||
if stage != adapter.StartStateStart {
|
if stage != adapter.StartStateStart {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var tlsConfig *tls.STDConfig
|
|
||||||
if n.tlsConfig != nil {
|
if n.tlsConfig != nil {
|
||||||
err := n.tlsConfig.Start()
|
err := n.tlsConfig.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "create TLS config")
|
return E.Cause(err, "create TLS config")
|
||||||
}
|
}
|
||||||
tlsConfig, err = n.tlsConfig.STDConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if common.Contains(n.network, N.NetworkTCP) {
|
if common.Contains(n.network, N.NetworkTCP) {
|
||||||
tcpListener, err := n.listener.ListenTCP()
|
tcpListener, err := n.listener.ListenTCP()
|
||||||
@@ -99,20 +98,23 @@ func (n *Inbound) Start(stage adapter.StartStage) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n.httpServer = &http.Server{
|
n.httpServer = &http.Server{
|
||||||
Handler: n,
|
Handler: h2c.NewHandler(n, &http2.Server{}),
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
BaseContext: func(listener net.Listener) context.Context {
|
BaseContext: func(listener net.Listener) context.Context {
|
||||||
return n.ctx
|
return n.ctx
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
var sErr error
|
listener := net.Listener(tcpListener)
|
||||||
if tlsConfig != nil {
|
if n.tlsConfig != nil {
|
||||||
sErr = n.httpServer.ServeTLS(tcpListener, "", "")
|
if len(n.tlsConfig.NextProtos()) == 0 {
|
||||||
} else {
|
n.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"})
|
||||||
sErr = n.httpServer.Serve(tcpListener)
|
} else if !common.Contains(n.tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||||
|
n.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, n.tlsConfig.NextProtos()...))
|
||||||
|
}
|
||||||
|
listener = aTLS.NewListener(tcpListener, n.tlsConfig)
|
||||||
}
|
}
|
||||||
if sErr != nil && !E.IsClosedOrCanceled(sErr) {
|
sErr := n.httpServer.Serve(listener)
|
||||||
|
if sErr != nil && !errors.Is(sErr, http.ErrServerClosed) {
|
||||||
n.logger.Error("http server serve error: ", sErr)
|
n.logger.Error("http server serve error: ", sErr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -161,13 +163,16 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
n.badRequest(ctx, request, E.New("authorization failed"))
|
n.badRequest(ctx, request, E.New("authorization failed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writer.Header().Set("Padding", generateNaivePaddingHeader())
|
writer.Header().Set("Padding", generatePaddingHeader())
|
||||||
writer.WriteHeader(http.StatusOK)
|
writer.WriteHeader(http.StatusOK)
|
||||||
writer.(http.Flusher).Flush()
|
writer.(http.Flusher).Flush()
|
||||||
|
|
||||||
hostPort := request.URL.Host
|
hostPort := request.Header.Get("-connect-authority")
|
||||||
if hostPort == "" {
|
if hostPort == "" {
|
||||||
hostPort = request.Host
|
hostPort = request.URL.Host
|
||||||
|
if hostPort == "" {
|
||||||
|
hostPort = request.Host
|
||||||
|
}
|
||||||
}
|
}
|
||||||
source := sHttp.SourceAddress(request)
|
source := sHttp.SourceAddress(request)
|
||||||
destination := M.ParseSocksaddr(hostPort).Unwrap()
|
destination := M.ParseSocksaddr(hostPort).Unwrap()
|
||||||
@@ -178,9 +183,14 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
n.badRequest(ctx, request, E.New("hijack failed"))
|
n.badRequest(ctx, request, E.New("hijack failed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination)
|
n.newConnection(ctx, false, &naiveConn{Conn: conn}, userName, source, destination)
|
||||||
} else {
|
} else {
|
||||||
n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination)
|
n.newConnection(ctx, true, &naiveH2Conn{
|
||||||
|
reader: request.Body,
|
||||||
|
writer: writer,
|
||||||
|
flusher: writer.(http.Flusher),
|
||||||
|
remoteAddress: source,
|
||||||
|
}, userName, source, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,18 +246,3 @@ func rejectHTTP(writer http.ResponseWriter, statusCode int) {
|
|||||||
}
|
}
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateNaivePaddingHeader() string {
|
|
||||||
paddingLen := rand.Intn(32) + 30
|
|
||||||
padding := make([]byte, paddingLen)
|
|
||||||
bits := rand.Uint64()
|
|
||||||
for i := 0; i < 16; i++ {
|
|
||||||
// Codes that won't be Huffman coded.
|
|
||||||
padding[i] = "!#$()+<>?@[]^`{}"[bits&15]
|
|
||||||
bits >>= 4
|
|
||||||
}
|
|
||||||
for i := 16; i < paddingLen; i++ {
|
|
||||||
padding[i] = '~'
|
|
||||||
}
|
|
||||||
return string(padding)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,417 +7,242 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/baderror"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const kFirstPaddings = 8
|
const paddingCount = 8
|
||||||
|
|
||||||
type naiveH1Conn struct {
|
func generatePaddingHeader() string {
|
||||||
net.Conn
|
paddingLen := rand.Intn(32) + 30
|
||||||
|
padding := make([]byte, paddingLen)
|
||||||
|
bits := rand.Uint64()
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
padding[i] = "!#$()+<>?@[]^`{}"[bits&15]
|
||||||
|
bits >>= 4
|
||||||
|
}
|
||||||
|
for i := 16; i < paddingLen; i++ {
|
||||||
|
padding[i] = '~'
|
||||||
|
}
|
||||||
|
return string(padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
type paddingConn struct {
|
||||||
readPadding int
|
readPadding int
|
||||||
writePadding int
|
writePadding int
|
||||||
readRemaining int
|
readRemaining int
|
||||||
paddingRemaining int
|
paddingRemaining int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH1Conn) Read(p []byte) (n int, err error) {
|
func (p *paddingConn) readWithPadding(reader io.Reader, buffer []byte) (n int, err error) {
|
||||||
n, err = c.read(p)
|
if p.readRemaining > 0 {
|
||||||
return n, wrapHttpError(err)
|
if len(buffer) > p.readRemaining {
|
||||||
}
|
buffer = buffer[:p.readRemaining]
|
||||||
|
|
||||||
func (c *naiveH1Conn) read(p []byte) (n int, err error) {
|
|
||||||
if c.readRemaining > 0 {
|
|
||||||
if len(p) > c.readRemaining {
|
|
||||||
p = p[:c.readRemaining]
|
|
||||||
}
|
}
|
||||||
n, err = c.Conn.Read(p)
|
n, err = reader.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.readRemaining -= n
|
p.readRemaining -= n
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.paddingRemaining > 0 {
|
if p.paddingRemaining > 0 {
|
||||||
err = rw.SkipN(c.Conn, c.paddingRemaining)
|
err = rw.SkipN(reader, p.paddingRemaining)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.paddingRemaining = 0
|
p.paddingRemaining = 0
|
||||||
}
|
}
|
||||||
if c.readPadding < kFirstPaddings {
|
if p.readPadding < paddingCount {
|
||||||
var paddingHdr []byte
|
var paddingHeader []byte
|
||||||
if len(p) >= 3 {
|
if len(buffer) >= 3 {
|
||||||
paddingHdr = p[:3]
|
paddingHeader = buffer[:3]
|
||||||
} else {
|
} else {
|
||||||
paddingHdr = make([]byte, 3)
|
paddingHeader = make([]byte, 3)
|
||||||
}
|
}
|
||||||
_, err = io.ReadFull(c.Conn, paddingHdr)
|
_, err = io.ReadFull(reader, paddingHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
|
originalDataSize := int(binary.BigEndian.Uint16(paddingHeader[:2]))
|
||||||
paddingSize := int(paddingHdr[2])
|
paddingSize := int(paddingHeader[2])
|
||||||
if len(p) > originalDataSize {
|
if len(buffer) > originalDataSize {
|
||||||
p = p[:originalDataSize]
|
buffer = buffer[:originalDataSize]
|
||||||
}
|
}
|
||||||
n, err = c.Conn.Read(p)
|
n, err = reader.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.readPadding++
|
p.readPadding++
|
||||||
c.readRemaining = originalDataSize - n
|
p.readRemaining = originalDataSize - n
|
||||||
c.paddingRemaining = paddingSize
|
p.paddingRemaining = paddingSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return c.Conn.Read(p)
|
return reader.Read(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH1Conn) Write(p []byte) (n int, err error) {
|
func (p *paddingConn) writeWithPadding(writer io.Writer, data []byte) (n int, err error) {
|
||||||
for pLen := len(p); pLen > 0; {
|
if p.writePadding < paddingCount {
|
||||||
var data []byte
|
|
||||||
if pLen > 65535 {
|
|
||||||
data = p[:65535]
|
|
||||||
p = p[65535:]
|
|
||||||
pLen -= 65535
|
|
||||||
} else {
|
|
||||||
data = p
|
|
||||||
pLen = 0
|
|
||||||
}
|
|
||||||
var writeN int
|
|
||||||
writeN, err = c.write(data)
|
|
||||||
n += writeN
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) write(p []byte) (n int, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
paddingSize := rand.Intn(256)
|
paddingSize := rand.Intn(256)
|
||||||
|
buffer := buf.NewSize(3 + len(data) + paddingSize)
|
||||||
buffer := buf.NewSize(3 + len(p) + paddingSize)
|
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
header := buffer.Extend(3)
|
header := buffer.Extend(3)
|
||||||
binary.BigEndian.PutUint16(header, uint16(len(p)))
|
binary.BigEndian.PutUint16(header, uint16(len(data)))
|
||||||
header[2] = byte(paddingSize)
|
header[2] = byte(paddingSize)
|
||||||
|
common.Must1(buffer.Write(data))
|
||||||
common.Must1(buffer.Write(p))
|
_, err = writer.Write(buffer.Bytes())
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
n = len(p)
|
n = len(data)
|
||||||
}
|
}
|
||||||
c.writePadding++
|
p.writePadding++
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return c.Conn.Write(p)
|
return writer.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH1Conn) FrontHeadroom() int {
|
func (p *paddingConn) writeBufferWithPadding(writer io.Writer, buffer *buf.Buffer) error {
|
||||||
if c.writePadding < kFirstPaddings {
|
if p.writePadding < paddingCount {
|
||||||
return 3
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) RearHeadroom() int {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
return 255
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) WriterMTU() int {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
return 65535
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|
||||||
defer buffer.Release()
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
bufferLen := buffer.Len()
|
bufferLen := buffer.Len()
|
||||||
if bufferLen > 65535 {
|
if bufferLen > 65535 {
|
||||||
return common.Error(c.Write(buffer.Bytes()))
|
_, err := p.writeChunked(writer, buffer.Bytes())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
paddingSize := rand.Intn(256)
|
paddingSize := rand.Intn(256)
|
||||||
header := buffer.ExtendHeader(3)
|
header := buffer.ExtendHeader(3)
|
||||||
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
||||||
header[2] = byte(paddingSize)
|
header[2] = byte(paddingSize)
|
||||||
buffer.Extend(paddingSize)
|
buffer.Extend(paddingSize)
|
||||||
c.writePadding++
|
p.writePadding++
|
||||||
}
|
}
|
||||||
return wrapHttpError(common.Error(c.Conn.Write(buffer.Bytes())))
|
return common.Error(writer.Write(buffer.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
func (p *paddingConn) writeChunked(writer io.Writer, data []byte) (n int, err error) {
|
||||||
/*func (c *naiveH1Conn) WriteTo(w io.Writer) (n int64, err error) {
|
for len(data) > 0 {
|
||||||
if c.readPadding < kFirstPaddings {
|
var chunk []byte
|
||||||
n, err = bufio.WriteToN(c, w, kFirstPaddings-c.readPadding)
|
if len(data) > 65535 {
|
||||||
} else {
|
chunk = data[:65535]
|
||||||
n, err = bufio.Copy(w, c.Conn)
|
data = data[65535:]
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
n, err = bufio.ReadFromN(c, r, kFirstPaddings-c.writePadding)
|
|
||||||
} else {
|
|
||||||
n, err = bufio.Copy(c.Conn, r)
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) ReaderReplaceable() bool {
|
|
||||||
return c.readPadding == kFirstPaddings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) WriterReplaceable() bool {
|
|
||||||
return c.writePadding == kFirstPaddings
|
|
||||||
}
|
|
||||||
|
|
||||||
type naiveH2Conn struct {
|
|
||||||
reader io.Reader
|
|
||||||
writer io.Writer
|
|
||||||
flusher http.Flusher
|
|
||||||
rAddr net.Addr
|
|
||||||
readPadding int
|
|
||||||
writePadding int
|
|
||||||
readRemaining int
|
|
||||||
paddingRemaining int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = c.read(p)
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) read(p []byte) (n int, err error) {
|
|
||||||
if c.readRemaining > 0 {
|
|
||||||
if len(p) > c.readRemaining {
|
|
||||||
p = p[:c.readRemaining]
|
|
||||||
}
|
|
||||||
n, err = c.reader.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.readRemaining -= n
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.paddingRemaining > 0 {
|
|
||||||
err = rw.SkipN(c.reader, c.paddingRemaining)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.paddingRemaining = 0
|
|
||||||
}
|
|
||||||
if c.readPadding < kFirstPaddings {
|
|
||||||
var paddingHdr []byte
|
|
||||||
if len(p) >= 3 {
|
|
||||||
paddingHdr = p[:3]
|
|
||||||
} else {
|
} else {
|
||||||
paddingHdr = make([]byte, 3)
|
chunk = data
|
||||||
|
data = nil
|
||||||
}
|
}
|
||||||
_, err = io.ReadFull(c.reader, paddingHdr)
|
var written int
|
||||||
|
written, err = p.writeWithPadding(writer, chunk)
|
||||||
|
n += written
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
|
|
||||||
paddingSize := int(paddingHdr[2])
|
|
||||||
if len(p) > originalDataSize {
|
|
||||||
p = p[:originalDataSize]
|
|
||||||
}
|
|
||||||
n, err = c.reader.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.readPadding++
|
|
||||||
c.readRemaining = originalDataSize - n
|
|
||||||
c.paddingRemaining = paddingSize
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return c.reader.Read(p)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
func (p *paddingConn) frontHeadroom() int {
|
||||||
for pLen := len(p); pLen > 0; {
|
if p.writePadding < paddingCount {
|
||||||
var data []byte
|
|
||||||
if pLen > 65535 {
|
|
||||||
data = p[:65535]
|
|
||||||
p = p[65535:]
|
|
||||||
pLen -= 65535
|
|
||||||
} else {
|
|
||||||
data = p
|
|
||||||
pLen = 0
|
|
||||||
}
|
|
||||||
var writeN int
|
|
||||||
writeN, err = c.write(data)
|
|
||||||
n += writeN
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
c.flusher.Flush()
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) write(p []byte) (n int, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
paddingSize := rand.Intn(256)
|
|
||||||
|
|
||||||
buffer := buf.NewSize(3 + len(p) + paddingSize)
|
|
||||||
defer buffer.Release()
|
|
||||||
header := buffer.Extend(3)
|
|
||||||
binary.BigEndian.PutUint16(header, uint16(len(p)))
|
|
||||||
header[2] = byte(paddingSize)
|
|
||||||
|
|
||||||
common.Must1(buffer.Write(p))
|
|
||||||
_, err = c.writer.Write(buffer.Bytes())
|
|
||||||
if err == nil {
|
|
||||||
n = len(p)
|
|
||||||
}
|
|
||||||
c.writePadding++
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.writer.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) FrontHeadroom() int {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) RearHeadroom() int {
|
func (p *paddingConn) rearHeadroom() int {
|
||||||
if c.writePadding < kFirstPaddings {
|
if p.writePadding < paddingCount {
|
||||||
return 255
|
return 255
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) WriterMTU() int {
|
func (p *paddingConn) writerMTU() int {
|
||||||
if c.writePadding < kFirstPaddings {
|
if p.writePadding < paddingCount {
|
||||||
return 65535
|
return 65535
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *paddingConn) readerReplaceable() bool {
|
||||||
|
return p.readPadding == paddingCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *paddingConn) writerReplaceable() bool {
|
||||||
|
return p.writePadding == paddingCount
|
||||||
|
}
|
||||||
|
|
||||||
|
type naiveConn struct {
|
||||||
|
net.Conn
|
||||||
|
paddingConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveConn) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = c.readWithPadding(c.Conn, p)
|
||||||
|
return n, baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveConn) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = c.writeChunked(c.Conn, p)
|
||||||
|
return n, baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
err := c.writeBufferWithPadding(c.Conn, buffer)
|
||||||
|
return baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveConn) FrontHeadroom() int { return c.frontHeadroom() }
|
||||||
|
func (c *naiveConn) RearHeadroom() int { return c.rearHeadroom() }
|
||||||
|
func (c *naiveConn) WriterMTU() int { return c.writerMTU() }
|
||||||
|
func (c *naiveConn) Upstream() any { return c.Conn }
|
||||||
|
func (c *naiveConn) ReaderReplaceable() bool { return c.readerReplaceable() }
|
||||||
|
func (c *naiveConn) WriterReplaceable() bool { return c.writerReplaceable() }
|
||||||
|
|
||||||
|
type naiveH2Conn struct {
|
||||||
|
reader io.Reader
|
||||||
|
writer io.Writer
|
||||||
|
flusher http.Flusher
|
||||||
|
remoteAddress net.Addr
|
||||||
|
paddingConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveH2Conn) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = c.readWithPadding(c.reader, p)
|
||||||
|
return n, baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = c.writeChunked(c.writer, p)
|
||||||
|
if err == nil {
|
||||||
|
c.flusher.Flush()
|
||||||
|
}
|
||||||
|
return n, baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
if c.writePadding < kFirstPaddings {
|
err := c.writeBufferWithPadding(c.writer, buffer)
|
||||||
bufferLen := buffer.Len()
|
|
||||||
if bufferLen > 65535 {
|
|
||||||
return common.Error(c.Write(buffer.Bytes()))
|
|
||||||
}
|
|
||||||
paddingSize := rand.Intn(256)
|
|
||||||
header := buffer.ExtendHeader(3)
|
|
||||||
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
|
||||||
header[2] = byte(paddingSize)
|
|
||||||
buffer.Extend(paddingSize)
|
|
||||||
c.writePadding++
|
|
||||||
}
|
|
||||||
err := common.Error(c.writer.Write(buffer.Bytes()))
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.flusher.Flush()
|
c.flusher.Flush()
|
||||||
}
|
}
|
||||||
return wrapHttpError(err)
|
return baderror.WrapH2(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
|
||||||
/*func (c *naiveH2Conn) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
if c.readPadding < kFirstPaddings {
|
|
||||||
n, err = bufio.WriteToN(c, w, kFirstPaddings-c.readPadding)
|
|
||||||
} else {
|
|
||||||
n, err = bufio.Copy(w, c.reader)
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
n, err = bufio.ReadFromN(c, r, kFirstPaddings-c.writePadding)
|
|
||||||
} else {
|
|
||||||
n, err = bufio.Copy(c.writer, r)
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) Close() error {
|
func (c *naiveH2Conn) Close() error {
|
||||||
return common.Close(
|
return common.Close(c.reader, c.writer)
|
||||||
c.reader,
|
|
||||||
c.writer,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) LocalAddr() net.Addr {
|
func (c *naiveH2Conn) LocalAddr() net.Addr { return M.Socksaddr{} }
|
||||||
return M.Socksaddr{}
|
func (c *naiveH2Conn) RemoteAddr() net.Addr { return c.remoteAddress }
|
||||||
}
|
func (c *naiveH2Conn) SetDeadline(t time.Time) error { return os.ErrInvalid }
|
||||||
|
func (c *naiveH2Conn) SetReadDeadline(t time.Time) error { return os.ErrInvalid }
|
||||||
func (c *naiveH2Conn) RemoteAddr() net.Addr {
|
func (c *naiveH2Conn) SetWriteDeadline(t time.Time) error { return os.ErrInvalid }
|
||||||
return c.rAddr
|
func (c *naiveH2Conn) NeedAdditionalReadDeadline() bool { return true }
|
||||||
}
|
func (c *naiveH2Conn) UpstreamReader() any { return c.reader }
|
||||||
|
func (c *naiveH2Conn) UpstreamWriter() any { return c.writer }
|
||||||
func (c *naiveH2Conn) SetDeadline(t time.Time) error {
|
func (c *naiveH2Conn) FrontHeadroom() int { return c.frontHeadroom() }
|
||||||
return os.ErrInvalid
|
func (c *naiveH2Conn) RearHeadroom() int { return c.rearHeadroom() }
|
||||||
}
|
func (c *naiveH2Conn) WriterMTU() int { return c.writerMTU() }
|
||||||
|
func (c *naiveH2Conn) ReaderReplaceable() bool { return c.readerReplaceable() }
|
||||||
func (c *naiveH2Conn) SetReadDeadline(t time.Time) error {
|
func (c *naiveH2Conn) WriterReplaceable() bool { return c.writerReplaceable() }
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) UpstreamReader() any {
|
|
||||||
return c.reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) UpstreamWriter() any {
|
|
||||||
return c.writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) ReaderReplaceable() bool {
|
|
||||||
return c.readPadding == kFirstPaddings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) WriterReplaceable() bool {
|
|
||||||
return c.writePadding == kFirstPaddings
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapHttpError(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.Contains(err.Error(), "client disconnected") {
|
|
||||||
return net.ErrClosed
|
|
||||||
}
|
|
||||||
if strings.Contains(err.Error(), "body closed by handler") {
|
|
||||||
return net.ErrClosed
|
|
||||||
}
|
|
||||||
if strings.Contains(err.Error(), "canceled with error code 268") {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
179
protocol/naive/outbound.go
Normal file
179
protocol/naive/outbound.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
//go:build with_naive_outbound
|
||||||
|
|
||||||
|
package naive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/cronet-go"
|
||||||
|
_ "github.com/sagernet/cronet-go/all"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.NaiveOutboundOptions](registry, C.TypeNaive, NewOutbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
outbound.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
client *cronet.NaiveClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
|
return nil, C.ErrTLSRequired
|
||||||
|
}
|
||||||
|
if options.TLS.DisableSNI {
|
||||||
|
return nil, E.New("disable_sni is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if options.TLS.Insecure {
|
||||||
|
return nil, E.New("insecure is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if len(options.TLS.ALPN) > 0 {
|
||||||
|
return nil, E.New("alpn is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if options.TLS.MinVersion != "" {
|
||||||
|
return nil, E.New("min_version is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if options.TLS.MaxVersion != "" {
|
||||||
|
return nil, E.New("max_version is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if len(options.TLS.CipherSuites) > 0 {
|
||||||
|
return nil, E.New("cipher_suites is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if len(options.TLS.CurvePreferences) > 0 {
|
||||||
|
return nil, E.New("curve_preferences is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if len(options.TLS.ClientCertificate) > 0 || options.TLS.ClientCertificatePath != "" {
|
||||||
|
return nil, E.New("client_certificate is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if len(options.TLS.ClientKey) > 0 || options.TLS.ClientKeyPath != "" {
|
||||||
|
return nil, E.New("client_key is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if options.TLS.Fragment || options.TLS.RecordFragment {
|
||||||
|
return nil, E.New("fragment is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if options.TLS.KernelTx || options.TLS.KernelRx {
|
||||||
|
return nil, E.New("kernel TLS is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if options.TLS.ECH != nil && options.TLS.ECH.Enabled {
|
||||||
|
return nil, E.New("ECH is not currently supported on naive outbound")
|
||||||
|
}
|
||||||
|
if options.TLS.UTLS != nil && options.TLS.UTLS.Enabled {
|
||||||
|
return nil, E.New("uTLS is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
if options.TLS.Reality != nil && options.TLS.Reality.Enabled {
|
||||||
|
return nil, E.New("reality is not supported on naive outbound")
|
||||||
|
}
|
||||||
|
|
||||||
|
serverAddress := options.ServerOptions.Build()
|
||||||
|
|
||||||
|
var serverName string
|
||||||
|
if options.TLS.ServerName != "" {
|
||||||
|
serverName = options.TLS.ServerName
|
||||||
|
} else {
|
||||||
|
serverName = serverAddress.AddrString()
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundDialer, err := dialer.NewWithOptions(dialer.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: options.DialerOptions,
|
||||||
|
RemoteIsDomain: true,
|
||||||
|
ResolverOnDetour: true,
|
||||||
|
NewDialer: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var trustedRootCertificates string
|
||||||
|
if len(options.TLS.Certificate) > 0 {
|
||||||
|
trustedRootCertificates = strings.Join(options.TLS.Certificate, "\n")
|
||||||
|
} else if options.TLS.CertificatePath != "" {
|
||||||
|
content, err := os.ReadFile(options.TLS.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read certificate")
|
||||||
|
}
|
||||||
|
trustedRootCertificates = string(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
extraHeaders := make(map[string]string)
|
||||||
|
for key, values := range options.ExtraHeaders.Build() {
|
||||||
|
if len(values) > 0 {
|
||||||
|
extraHeaders[key] = values[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := cronet.NewNaiveClient(cronet.NaiveClientConfig{
|
||||||
|
Context: ctx,
|
||||||
|
ServerAddress: serverAddress,
|
||||||
|
ServerName: serverName,
|
||||||
|
Username: options.Username,
|
||||||
|
Password: options.Password,
|
||||||
|
Concurrency: options.InsecureConcurrency,
|
||||||
|
ExtraHeaders: extraHeaders,
|
||||||
|
TrustedRootCertificates: trustedRootCertificates,
|
||||||
|
CertificatePublicKeySHA256: options.TLS.CertificatePublicKeySHA256,
|
||||||
|
Dialer: outboundDialer,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Outbound{
|
||||||
|
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeNaive, tag, []string{N.NetworkTCP}, options.DialerOptions),
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
client: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := o.client.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.logger.Info("NaiveProxy started, version: ", o.client.Engine().Version())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
|
metadata.Outbound = o.Tag()
|
||||||
|
metadata.Destination = destination
|
||||||
|
o.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
|
return o.client.DialContext(ctx, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) Close() error {
|
||||||
|
return o.client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) StartNetLogToFile(fileName string, logAll bool) bool {
|
||||||
|
return o.client.Engine().StartNetLogToFile(fileName, logAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) StopNetLog() {
|
||||||
|
o.client.Engine().StopNetLog()
|
||||||
|
}
|
||||||
@@ -493,20 +493,20 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
metadata.Inbound = t.Tag()
|
metadata.Inbound = t.Tag()
|
||||||
metadata.InboundType = t.Type()
|
metadata.InboundType = t.Type()
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
metadata.Destination = destination
|
|
||||||
addr4, addr6 := t.server.TailscaleIPs()
|
addr4, addr6 := t.server.TailscaleIPs()
|
||||||
switch destination.Addr {
|
switch destination.Addr {
|
||||||
case addr4:
|
case addr4:
|
||||||
metadata.OriginDestination = destination
|
metadata.OriginDestination = destination
|
||||||
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
|
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
|
||||||
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
|
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination)
|
||||||
case addr6:
|
case addr6:
|
||||||
metadata.OriginDestination = destination
|
metadata.OriginDestination = destination
|
||||||
destination.Addr = netip.IPv6Loopback()
|
destination.Addr = netip.IPv6Loopback()
|
||||||
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
|
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination)
|
||||||
}
|
}
|
||||||
|
metadata.Destination = destination
|
||||||
t.logger.InfoContext(ctx, "inbound packet connection from ", source)
|
t.logger.InfoContext(ctx, "inbound packet connection from ", source)
|
||||||
t.logger.InfoContext(ctx, "inbound packet connection to ", destination)
|
t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||||
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
IPRoute2RuleIndex: ruleIndex,
|
IPRoute2RuleIndex: ruleIndex,
|
||||||
AutoRedirectInputMark: inputMark,
|
AutoRedirectInputMark: inputMark,
|
||||||
AutoRedirectOutputMark: outputMark,
|
AutoRedirectOutputMark: outputMark,
|
||||||
|
ExcludeMPTCP: options.ExcludeMPTCP,
|
||||||
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
|
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
|
||||||
Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6),
|
Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6),
|
||||||
StrictRoute: options.StrictRoute,
|
StrictRoute: options.StrictRoute,
|
||||||
|
|||||||
100
release/local/common.sh
Executable file
100
release/local/common.sh
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
BINARY_NAME="sing-box"
|
||||||
|
|
||||||
|
INSTALL_BIN_PATH="/usr/local/bin"
|
||||||
|
INSTALL_CONFIG_PATH="/usr/local/etc/sing-box"
|
||||||
|
INSTALL_DATA_PATH="/var/lib/sing-box"
|
||||||
|
SYSTEMD_SERVICE_PATH="/etc/systemd/system"
|
||||||
|
|
||||||
|
DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0"
|
||||||
|
|
||||||
|
setup_environment() {
|
||||||
|
if [ -d /usr/local/go ]; then
|
||||||
|
export PATH="$PATH:/usr/local/go/bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v go &> /dev/null; then
|
||||||
|
echo "Error: Go is not installed or not in PATH"
|
||||||
|
echo "Run install_go.sh to install Go"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_build_tags() {
|
||||||
|
local extra_tags="$1"
|
||||||
|
if [ -n "$extra_tags" ]; then
|
||||||
|
echo "${DEFAULT_BUILD_TAGS},${extra_tags}"
|
||||||
|
else
|
||||||
|
echo "${DEFAULT_BUILD_TAGS}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_version() {
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
GOHOSTOS=$(go env GOHOSTOS)
|
||||||
|
GOHOSTARCH=$(go env GOHOSTARCH)
|
||||||
|
CGO_ENABLED=0 GOOS=$GOHOSTOS GOARCH=$GOHOSTARCH go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest
|
||||||
|
}
|
||||||
|
|
||||||
|
get_ldflags() {
|
||||||
|
local version
|
||||||
|
version=$(get_version)
|
||||||
|
echo "-X 'github.com/sagernet/sing-box/constant.Version=${version}' -s -w -buildid= -checklinkname=0"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_sing_box() {
|
||||||
|
local tags="$1"
|
||||||
|
local ldflags
|
||||||
|
ldflags=$(get_ldflags)
|
||||||
|
|
||||||
|
echo "Building sing-box with tags: $tags"
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
export GOTOOLCHAIN=local
|
||||||
|
go install -v -trimpath -ldflags "$ldflags" -tags "$tags" ./cmd/sing-box
|
||||||
|
}
|
||||||
|
|
||||||
|
install_binary() {
|
||||||
|
local gopath
|
||||||
|
gopath=$(go env GOPATH)
|
||||||
|
echo "Installing binary to $INSTALL_BIN_PATH/$BINARY_NAME"
|
||||||
|
sudo cp "${gopath}/bin/${BINARY_NAME}" "${INSTALL_BIN_PATH}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_config() {
|
||||||
|
echo "Setting up configuration"
|
||||||
|
sudo mkdir -p "$INSTALL_CONFIG_PATH"
|
||||||
|
if [ ! -f "$INSTALL_CONFIG_PATH/config.json" ]; then
|
||||||
|
sudo cp "$PROJECT_DIR/release/config/config.json" "$INSTALL_CONFIG_PATH/config.json"
|
||||||
|
echo "Default config installed to $INSTALL_CONFIG_PATH/config.json"
|
||||||
|
else
|
||||||
|
echo "Config already exists at $INSTALL_CONFIG_PATH/config.json (not overwriting)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_systemd() {
|
||||||
|
echo "Setting up systemd service"
|
||||||
|
sudo cp "$SCRIPT_DIR/sing-box.service" "$SYSTEMD_SERVICE_PATH/"
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
if systemctl is-active --quiet sing-box; then
|
||||||
|
echo "Stopping sing-box service"
|
||||||
|
sudo systemctl stop sing-box
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
echo "Starting sing-box service"
|
||||||
|
sudo systemctl start sing-box
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_service() {
|
||||||
|
echo "Restarting sing-box service"
|
||||||
|
sudo systemctl restart sing-box
|
||||||
|
}
|
||||||
@@ -2,21 +2,25 @@
|
|||||||
|
|
||||||
set -e -o pipefail
|
set -e -o pipefail
|
||||||
|
|
||||||
if [ -d /usr/local/go ]; then
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
export PATH="$PATH:/usr/local/go/bin"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
fi
|
|
||||||
|
|
||||||
DIR=$(dirname "$0")
|
setup_environment
|
||||||
PROJECT=$DIR/../..
|
|
||||||
|
|
||||||
pushd $PROJECT
|
echo "Updating sing-box from git repository..."
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_acme,debug ./cmd/sing-box
|
|
||||||
popd
|
|
||||||
|
|
||||||
sudo systemctl stop sing-box
|
BUILD_TAGS=$(get_build_tags "debug")
|
||||||
sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/
|
|
||||||
sudo systemctl start sing-box
|
build_sing_box "$BUILD_TAGS"
|
||||||
|
|
||||||
|
stop_service
|
||||||
|
install_binary
|
||||||
|
start_service
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Following service logs (Ctrl+C to exit)..."
|
||||||
sudo journalctl -u sing-box --output cat -f
|
sudo journalctl -u sing-box --output cat -f
|
||||||
|
|||||||
@@ -2,19 +2,18 @@
|
|||||||
|
|
||||||
set -e -o pipefail
|
set -e -o pipefail
|
||||||
|
|
||||||
if [ -d /usr/local/go ]; then
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
export PATH="$PATH:/usr/local/go/bin"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
fi
|
|
||||||
|
|
||||||
DIR=$(dirname "$0")
|
setup_environment
|
||||||
PROJECT=$DIR/../..
|
|
||||||
|
|
||||||
pushd $PROJECT
|
BUILD_TAGS=$(get_build_tags)
|
||||||
go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box
|
|
||||||
popd
|
|
||||||
|
|
||||||
sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/
|
build_sing_box "$BUILD_TAGS"
|
||||||
sudo mkdir -p /usr/local/etc/sing-box
|
install_binary
|
||||||
sudo cp $PROJECT/release/config/config.json /usr/local/etc/sing-box/config.json
|
setup_config
|
||||||
sudo cp $DIR/sing-box.service /etc/systemd/system
|
setup_systemd
|
||||||
sudo systemctl daemon-reload
|
|
||||||
|
echo ""
|
||||||
|
echo "Installation complete!"
|
||||||
|
echo "To enable and start the service, run: $SCRIPT_DIR/enable.sh"
|
||||||
|
|||||||
@@ -2,17 +2,18 @@
|
|||||||
|
|
||||||
set -e -o pipefail
|
set -e -o pipefail
|
||||||
|
|
||||||
if [ -d /usr/local/go ]; then
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
export PATH="$PATH:/usr/local/go/bin"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
fi
|
|
||||||
|
|
||||||
DIR=$(dirname "$0")
|
setup_environment
|
||||||
PROJECT=$DIR/../..
|
|
||||||
|
|
||||||
pushd $PROJECT
|
BUILD_TAGS=$(get_build_tags)
|
||||||
go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box
|
|
||||||
popd
|
|
||||||
|
|
||||||
sudo systemctl stop sing-box
|
build_sing_box "$BUILD_TAGS"
|
||||||
sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/
|
|
||||||
sudo systemctl start sing-box
|
stop_service
|
||||||
|
install_binary
|
||||||
|
start_service
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Reinstallation complete!"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user