mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-06 16:39:17 +03:00
Compare commits
102 Commits
v1.12.0-al
...
dev-wip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
847c80a1fe | ||
|
|
40be0627f5 | ||
|
|
ae3b943b93 | ||
|
|
7da9b40c66 | ||
|
|
48772992fa | ||
|
|
a66772fa14 | ||
|
|
3b49ed8a71 | ||
|
|
1c34ee26dc | ||
|
|
0ca4d8f327 | ||
|
|
7e69892694 | ||
|
|
0494541057 | ||
|
|
b55d5914e0 | ||
|
|
584fd40d59 | ||
|
|
3c595f205b | ||
|
|
f75bd13383 | ||
|
|
99effc44f8 | ||
|
|
7abe17cea0 | ||
|
|
04f0ae6c00 | ||
|
|
8f79ee6919 | ||
|
|
29150fc0f7 | ||
|
|
30ac4ca62c | ||
|
|
819f14eca0 | ||
|
|
24df55660d | ||
|
|
02943cefb3 | ||
|
|
9f5a403532 | ||
|
|
9bcab743fc | ||
|
|
af47fc2543 | ||
|
|
07160909d7 | ||
|
|
74c37a6f9c | ||
|
|
0dd11223ef | ||
|
|
f635cdd3ef | ||
|
|
dccf5c56fb | ||
|
|
12141bb74a | ||
|
|
657bd4725f | ||
|
|
c50c8815f0 | ||
|
|
3026b37734 | ||
|
|
ecbd8f55f6 | ||
|
|
743b00f748 | ||
|
|
0249fc5e67 | ||
|
|
51e63cbeba | ||
|
|
b6a9e0ce0c | ||
|
|
3114db2450 | ||
|
|
ac29d4ca9c | ||
|
|
70bb012989 | ||
|
|
c6090a32dc | ||
|
|
e7d0227c90 | ||
|
|
6bbab76cc7 | ||
|
|
0af1236d8f | ||
|
|
488e26aebc | ||
|
|
8a2a65133a | ||
|
|
5e6cc89c65 | ||
|
|
3145df4b6c | ||
|
|
4b2f455e1e | ||
|
|
02619e3dfa | ||
|
|
00082ab6c8 | ||
|
|
ce26077d83 | ||
|
|
089e13451b | ||
|
|
d3184a7997 | ||
|
|
65f5767180 | ||
|
|
c594f33dc4 | ||
|
|
e65a9d7380 | ||
|
|
dd304bebfc | ||
|
|
6a6c6a5ba5 | ||
|
|
b99dc4609d | ||
|
|
e59dab657e | ||
|
|
908174ddf6 | ||
|
|
34c48fa421 | ||
|
|
1fe5b8fd9d | ||
|
|
4bef6ea0e8 | ||
|
|
941049d21b | ||
|
|
bdc1724975 | ||
|
|
942511ad6d | ||
|
|
cb68a40c43 | ||
|
|
023218e6e7 | ||
|
|
2a24b94b8d | ||
|
|
c6531cf184 | ||
|
|
d4fa0ed349 | ||
|
|
10874d2dc4 | ||
|
|
5adaf1ac75 | ||
|
|
9668ea69b8 | ||
|
|
ae9bc7acf1 | ||
|
|
594ee480a2 | ||
|
|
a15b5a2463 | ||
|
|
991e755789 | ||
|
|
97d41ffde8 | ||
|
|
24af0766ac | ||
|
|
af17eaa537 | ||
|
|
3adc10a797 | ||
|
|
5eeef6b28e | ||
|
|
f4c29840c3 | ||
|
|
47fc3ebda4 | ||
|
|
9774a659b0 | ||
|
|
2e4a6de4e7 | ||
|
|
a530e424e9 | ||
|
|
0bfd487ee9 | ||
|
|
6aae834493 | ||
|
|
f56131f38e | ||
|
|
273a11d550 | ||
|
|
ae8ce75e41 | ||
|
|
d6d94b689f | ||
|
|
30d785f1ee | ||
|
|
db5ec3cdfc |
23
.fpm
Normal file
23
.fpm
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-s dir
|
||||||
|
--name sing-box
|
||||||
|
--category net
|
||||||
|
--license GPLv3-or-later
|
||||||
|
--description "The universal proxy platform."
|
||||||
|
--url "https://sing-box.sagernet.org/"
|
||||||
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
|
--config-files /etc/sing-box/config.json
|
||||||
|
|
||||||
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
|
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||||
|
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||||
|
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
||||||
|
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
||||||
7
.github/setup_legacy_go.sh
vendored
7
.github/setup_legacy_go.sh
vendored
@@ -1,10 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.23.6"
|
VERSION="1.23.6"
|
||||||
|
|
||||||
|
mkdir -p $HOME/go
|
||||||
|
cd $HOME/go
|
||||||
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||||
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||||
mv go $HOME/go/go_legacy
|
mv go go_legacy
|
||||||
cd $HOME/go/go_legacy
|
cd go_legacy
|
||||||
|
|
||||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
# this patch file only works on golang1.23.x
|
# this patch file only works on golang1.23.x
|
||||||
|
|||||||
296
.github/workflows/build.yml
vendored
296
.github/workflows/build.yml
vendored
@@ -46,16 +46,16 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.2
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
echo "version=${{ inputs.version }}"
|
echo "version=${{ inputs.version }}"
|
||||||
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Calculate version
|
- name: Calculate version
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/read_tag --nightly
|
go run -v ./cmd/internal/read_tag --ci --nightly
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
id: outputs
|
id: outputs
|
||||||
run: |-
|
run: |-
|
||||||
@@ -68,73 +68,42 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
os: [ linux, windows, darwin, android ]
|
||||||
|
arch: [ "386", amd64, arm64 ]
|
||||||
|
legacy_go: [ false ]
|
||||||
include:
|
include:
|
||||||
- name: linux_386
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||||
goos: linux
|
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||||
goarch: 386
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||||
- name: linux_amd64
|
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||||
goos: linux
|
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||||
goarch: amd64
|
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
- name: linux_arm64
|
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||||
goos: linux
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
goarch: arm64
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
- name: linux_arm
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||||
goos: linux
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
goarch: arm
|
|
||||||
goarm: 6
|
- { os: windows, arch: "386", legacy_go: true }
|
||||||
- name: linux_arm_v7
|
- { os: windows, arch: amd64, legacy_go: true }
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||||
goarm: 7
|
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||||
- name: linux_s390x
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||||
goos: linux
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
goarch: s390x
|
exclude:
|
||||||
- name: linux_riscv64
|
- { os: darwin, arch: "386" }
|
||||||
goos: linux
|
|
||||||
goarch: riscv64
|
|
||||||
- name: linux_mips64le
|
|
||||||
goos: linux
|
|
||||||
goarch: mips64le
|
|
||||||
- name: windows_amd64
|
|
||||||
goos: windows
|
|
||||||
goarch: amd64
|
|
||||||
require_legacy_go: true
|
|
||||||
- name: windows_386
|
|
||||||
goos: windows
|
|
||||||
goarch: 386
|
|
||||||
require_legacy_go: true
|
|
||||||
- name: windows_arm64
|
|
||||||
goos: windows
|
|
||||||
goarch: arm64
|
|
||||||
- name: darwin_arm64
|
|
||||||
goos: darwin
|
|
||||||
goarch: arm64
|
|
||||||
- name: darwin_amd64
|
|
||||||
goos: darwin
|
|
||||||
goarch: amd64
|
|
||||||
- name: android_arm64
|
|
||||||
goos: android
|
|
||||||
goarch: arm64
|
|
||||||
- name: android_arm
|
|
||||||
goos: android
|
|
||||||
goarch: arm
|
|
||||||
goarm: 7
|
|
||||||
- name: android_amd64
|
|
||||||
goos: android
|
|
||||||
goarch: amd64
|
|
||||||
- name: android_386
|
|
||||||
goos: android
|
|
||||||
goarch: 386
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: ${{ ! matrix.legacy_go }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.2
|
||||||
- name: Cache legacy Go
|
- name: Cache Legacy Go
|
||||||
if: matrix.require_legacy_go
|
if: matrix.require_legacy_go
|
||||||
id: cache-legacy-go
|
id: cache-legacy-go
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -142,64 +111,139 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
~/go/go_legacy
|
~/go/go_legacy
|
||||||
key: go_legacy_1236
|
key: go_legacy_1236
|
||||||
- name: Setup legacy Go
|
- name: Setup Legacy Go
|
||||||
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
run: bash .github/setup_legacy_go.sh
|
run: |-
|
||||||
|
.github/setup_legacy_go.sh
|
||||||
|
- name: Setup Legacy Go 2
|
||||||
|
if: matrix.legacy_go
|
||||||
|
run: |-
|
||||||
|
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
||||||
|
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.goos == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28
|
||||||
local-cache: true
|
local-cache: true
|
||||||
- name: Setup Goreleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
with:
|
|
||||||
distribution: goreleaser-pro
|
|
||||||
version: '~> v2'
|
|
||||||
install-only: true
|
|
||||||
- name: Extract signing key
|
|
||||||
run: |-
|
|
||||||
mkdir -p $HOME/.gnupg
|
|
||||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
|
||||||
EOF
|
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Set build tags
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api,with_tailscale'
|
||||||
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.goos != 'android'
|
if: matrix.os != 'android'
|
||||||
run: |-
|
run: |
|
||||||
goreleaser release --clean --split
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
CGO_ENABLED: "0"
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOOS: ${{ matrix.os }}
|
||||||
GOPATH: ${{ env.HOME }}/go
|
GOARCH: ${{ matrix.arch }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
|
||||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
|
||||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
|
||||||
- name: Build Android
|
- name: Build Android
|
||||||
if: matrix.goos == 'android'
|
if: matrix.os == 'android'
|
||||||
run: |-
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
go install -v ./cmd/internal/build
|
go install -v ./cmd/internal/build
|
||||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
|
export CC='${{ matrix.ndk }}-clang'
|
||||||
|
export CXX="${CC}++"
|
||||||
|
mkdir -p dist
|
||||||
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
BUILD_GOOS: ${{ matrix.goos }}
|
CGO_ENABLED: "1"
|
||||||
BUILD_GOARCH: ${{ matrix.goarch }}
|
BUILD_GOOS: ${{ matrix.os }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
BUILD_GOARCH: ${{ matrix.arch }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
- name: Set name
|
||||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
run: |-
|
||||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
|
||||||
|
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
|
||||||
|
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
|
||||||
|
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
||||||
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
|
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
||||||
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Package DEB
|
||||||
|
if: matrix.debian != ''
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get install -y debsigs
|
||||||
|
fpm -t deb \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${PKG_NAME}.deb" \
|
||||||
|
--architecture ${{ matrix.debian }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
|
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||||
|
rm -rf $HOME/.gnupg
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
||||||
|
- name: Package RPM
|
||||||
|
if: matrix.rpm != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
fpm -t rpm \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${PKG_NAME}.rpm" \
|
||||||
|
--architecture ${{ matrix.rpm }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
|
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||||
|
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
EOF
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
rpmsign --addsign dist/*.rpm
|
||||||
|
- name: Package Pacman
|
||||||
|
if: matrix.pacman != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get install -y libarchive-tools
|
||||||
|
fpm -t pacman \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
||||||
|
--architecture ${{ matrix.pacman }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
- name: Archive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd dist
|
||||||
|
mkdir -p "${DIR_NAME}"
|
||||||
|
cp ../LICENSE "${DIR_NAME}"
|
||||||
|
if [ '${{ matrix.os }}' = 'windows' ]; then
|
||||||
|
cp sing-box "${DIR_NAME}/sing-box.exe"
|
||||||
|
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
||||||
|
else
|
||||||
|
cp sing-box "${DIR_NAME}"
|
||||||
|
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
||||||
|
fi
|
||||||
|
rm -r "${DIR_NAME}"
|
||||||
|
- name: Cleanup
|
||||||
|
run: rm dist/sing-box
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.name }}
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||||
path: 'dist'
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||||
@@ -215,7 +259,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.2
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -271,13 +315,11 @@ jobs:
|
|||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
- name: Prepare upload
|
- name: Prepare upload
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p dist/release
|
mkdir -p dist
|
||||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
||||||
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
|
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-android-apks
|
name: binary-android-apks
|
||||||
@@ -297,7 +339,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.2
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -395,7 +437,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.2
|
||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -435,19 +477,19 @@ jobs:
|
|||||||
|
|
||||||
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
||||||
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
||||||
|
|
||||||
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||||
mkdir -p "$PROFILES_PATH"
|
mkdir -p "$PROFILES_PATH"
|
||||||
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
||||||
|
|
||||||
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
||||||
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
||||||
|
|
||||||
xcrun notarytool store-credentials "notarytool-password" \
|
xcrun notarytool store-credentials "notarytool-password" \
|
||||||
--key $ASC_KEY_PATH \
|
--key $ASC_KEY_PATH \
|
||||||
--key-id $ASC_KEY_ID \
|
--key-id $ASC_KEY_ID \
|
||||||
--issuer $ASC_KEY_ISSUER_ID
|
--issuer $ASC_KEY_ISSUER_ID
|
||||||
|
|
||||||
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
||||||
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
||||||
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
||||||
@@ -472,10 +514,13 @@ jobs:
|
|||||||
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||||
|
- name: Update version
|
||||||
|
if: matrix.if && matrix.name != 'iOS'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_apple_version --ci
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/update_apple_version --ci
|
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
xcodebuild archive \
|
xcodebuild archive \
|
||||||
-scheme "${{ matrix.scheme }}" \
|
-scheme "${{ matrix.scheme }}" \
|
||||||
@@ -523,10 +568,10 @@ jobs:
|
|||||||
cd "${{ matrix.archive }}"
|
cd "${{ matrix.archive }}"
|
||||||
zip -r SFM.dSYMs.zip dSYMs
|
zip -r SFM.dSYMs.zip dSYMs
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p dist/release
|
mkdir -p dist
|
||||||
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
|
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
|
||||||
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
|
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
- name: Upload image
|
- name: Upload image
|
||||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -547,12 +592,6 @@ jobs:
|
|||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Goreleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
with:
|
|
||||||
distribution: goreleaser-pro
|
|
||||||
version: '~> v2'
|
|
||||||
install-only: true
|
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
id: cache-ghr
|
id: cache-ghr
|
||||||
@@ -577,26 +616,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Merge builds
|
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
|
||||||
run: |-
|
|
||||||
goreleaser continue --merge --skip publish
|
|
||||||
mkdir -p dist/release
|
|
||||||
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
|
||||||
- name: Upload builds
|
- name: Upload builds
|
||||||
if: ${{ env.PUBLISHED == 'false' }}
|
if: ${{ env.PUBLISHED == 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Replace builds
|
- name: Replace builds
|
||||||
if: ${{ env.PUBLISHED != 'false' }}
|
if: ${{ env.PUBLISHED != 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace -p 5 "v${VERSION}" dist/release
|
ghr --replace -p 5 "v${VERSION}" dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
5
.github/workflows/lint.yml
vendored
5
.github/workflows/lint.yml
vendored
@@ -28,10 +28,11 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.2
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
|
verify: false
|
||||||
|
|||||||
183
.github/workflows/linux.yml
vendored
183
.github/workflows/linux.yml
vendored
@@ -1,13 +1,22 @@
|
|||||||
name: Release to Linux repository
|
name: Build Linux Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version name"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
calculate_version:
|
||||||
|
name: Calculate version
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
@@ -16,23 +25,161 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.24.2
|
||||||
- name: Extract signing key
|
- name: Check input version
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p $HOME/.gnupg
|
echo "version=${{ inputs.version }}"
|
||||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Calculate version
|
||||||
|
if: github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/read_tag --ci --nightly
|
||||||
|
- name: Set outputs
|
||||||
|
id: outputs
|
||||||
|
run: |-
|
||||||
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
build:
|
||||||
|
name: Build binary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||||
|
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||||
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||||
|
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||||
|
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||||
|
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
|
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||||
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||||
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.24.2
|
||||||
|
- name: Setup Android NDK
|
||||||
|
if: matrix.os == 'android'
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28
|
||||||
|
local-cache: true
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Set build tags
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
|
||||||
|
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
|
||||||
|
TAGS="${TAGS},with_ech"
|
||||||
|
fi
|
||||||
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: ${{ matrix.os }}
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Set mtime
|
||||||
|
run: |-
|
||||||
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
|
- name: Set name
|
||||||
|
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||||
|
- name: Set beta name
|
||||||
|
if: contains(needs.calculate_version.outputs.version, '-')
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
|
- name: Set version
|
||||||
|
run: |-
|
||||||
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Package DEB
|
||||||
|
if: matrix.debian != ''
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
sudo apt-get install -y debsigs
|
||||||
|
fpm -t deb \
|
||||||
|
--name "${NAME}" \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
||||||
|
--architecture ${{ matrix.debian }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||||
|
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
||||||
|
rm -rf $HOME/.gnupg
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
${{ secrets.GPG_KEY }}
|
${{ secrets.GPG_KEY }}
|
||||||
EOF
|
EOF
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
||||||
- name: Publish release
|
- name: Package RPM
|
||||||
uses: goreleaser/goreleaser-action@v6
|
if: matrix.rpm != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
sudo gem install fpm
|
||||||
|
fpm -t rpm \
|
||||||
|
--name "${NAME}" \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
||||||
|
--architecture ${{ matrix.rpm }} \
|
||||||
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
cat > $HOME/.rpmmacros <<EOF
|
||||||
|
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
||||||
|
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
EOF
|
||||||
|
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
rpmsign --addsign dist/*.rpm
|
||||||
|
- name: Cleanup
|
||||||
|
run: rm dist/sing-box
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||||
version: '~> v2'
|
path: "dist"
|
||||||
args: release -f .goreleaser.fury.yaml --clean
|
upload:
|
||||||
env:
|
name: Upload builds
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
runs-on: ubuntu-latest
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
needs:
|
||||||
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
- calculate_version
|
||||||
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
- build
|
||||||
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Download builds
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Publish packages
|
||||||
|
run: |-
|
||||||
|
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
||||||
|
|||||||
@@ -50,12 +50,18 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: "config|noreplace"
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/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
|
- src: release/completions/sing-box.bash
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
|||||||
@@ -126,18 +126,24 @@ nfpms:
|
|||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
- archlinux
|
||||||
# - apk
|
# - apk
|
||||||
# - ipk
|
# - ipk
|
||||||
priority: extra
|
priority: extra
|
||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: "config|noreplace"
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/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
|
- src: release/completions/sing-box.bash
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ 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_reality_server,with_acme,with_clash_api" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api,with_tailscale" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|||||||
19
Makefile
19
Makefile
@@ -1,8 +1,6 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_tailscale
|
||||||
TAGS_GO123 = with_tailscale
|
|
||||||
TAGS ?= $(TAGS_GO120),$(TAGS_GO123)
|
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
@@ -10,7 +8,7 @@ GOHOSTARCH = $(shell go env GOHOSTARCH)
|
|||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
|
||||||
@@ -20,18 +18,13 @@ build:
|
|||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
ci_build_go120:
|
|
||||||
export GOTOOLCHAIN=local && \
|
|
||||||
go build $(PARAMS) $(MAIN) && \
|
|
||||||
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
|
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(PARAMS) $(MAIN) && \
|
go build $(PARAMS) $(MAIN) && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -233,8 +226,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.4
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
@@ -253,4 +246,4 @@ clean:
|
|||||||
update:
|
update:
|
||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -31,11 +33,30 @@ type DNSClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSQueryOptions struct {
|
type DNSQueryOptions struct {
|
||||||
Transport DNSTransport
|
Transport DNSTransport
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
DisableCache bool
|
LookupStrategy C.DomainStrategy
|
||||||
RewriteTTL *uint32
|
DisableCache bool
|
||||||
ClientSubnet netip.Prefix
|
RewriteTTL *uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
||||||
|
if options == nil {
|
||||||
|
return &DNSQueryOptions{}, nil
|
||||||
|
}
|
||||||
|
transportManager := service.FromContext[DNSTransportManager](ctx)
|
||||||
|
transport, loaded := transportManager.Transport(options.Server)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("domain resolver not found: " + options.Server)
|
||||||
|
}
|
||||||
|
return &DNSQueryOptions{
|
||||||
|
Transport: transport,
|
||||||
|
Strategy: C.DomainStrategy(options.Strategy),
|
||||||
|
DisableCache: options.DisableCache,
|
||||||
|
RewriteTTL: options.RewriteTTL,
|
||||||
|
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RDRCStore interface {
|
type RDRCStore interface {
|
||||||
@@ -45,10 +66,10 @@ type RDRCStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSTransport interface {
|
type DNSTransport interface {
|
||||||
|
Lifecycle
|
||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
Reset()
|
|
||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FakeIPStore interface {
|
type FakeIPStore interface {
|
||||||
Service
|
SimpleLifecycle
|
||||||
Contains(address netip.Addr) bool
|
Contains(address netip.Addr) bool
|
||||||
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
||||||
Lookup(address netip.Addr) (string, bool)
|
Lookup(address netip.Addr) (string, bool)
|
||||||
|
|||||||
@@ -37,13 +37,14 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
for _, inbound := range m.inbounds {
|
inbounds := m.inbounds
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, inbound := range inbounds {
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package adapter
|
|||||||
|
|
||||||
import E "github.com/sagernet/sing/common/exceptions"
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
type SimpleLifecycle interface {
|
||||||
|
Start() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
type StartStage uint8
|
type StartStage uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lifecycleServiceWrapper struct {
|
type lifecycleServiceWrapper struct {
|
||||||
Service
|
SimpleLifecycle
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLifecycleService(service Service, name string) LifecycleService {
|
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
|
||||||
return &lifecycleServiceWrapper{
|
return &lifecycleServiceWrapper{
|
||||||
Service: service,
|
SimpleLifecycle: service,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||||
return LegacyStart(l.Service, stage)
|
return LegacyStart(l.SimpleLifecycle, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Close() error {
|
func (l *lifecycleServiceWrapper) Close() error {
|
||||||
return l.Service.Close()
|
return l.SimpleLifecycle.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type Router interface {
|
|||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
SetTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type HeadlessRule interface {
|
|||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
Service
|
SimpleLifecycle
|
||||||
Type() string
|
Type() string
|
||||||
Action() RuleAction
|
Action() RuleAction
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,27 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Start() error
|
Lifecycle
|
||||||
Close() error
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceRegistry interface {
|
||||||
|
option.ServiceOptionsRegistry
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Services() []Service
|
||||||
|
Get(tag string) (Service, bool)
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
|
||||||
}
|
}
|
||||||
|
|||||||
21
adapter/service/adapter.go
Normal file
21
adapter/service/adapter.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
serviceType string
|
||||||
|
serviceTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(serviceType string, serviceTag string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
serviceType: serviceType,
|
||||||
|
serviceTag: serviceTag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.serviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.serviceTag
|
||||||
|
}
|
||||||
144
adapter/service/manager.go
Normal file
144
adapter/service/manager.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.ServiceManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.ServiceRegistry
|
||||||
|
access sync.Mutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
services []adapter.Service
|
||||||
|
serviceByTag map[string]adapter.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
serviceByTag: make(map[string]adapter.Service),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
services := m.services
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, service := range services {
|
||||||
|
err := adapter.LegacyStart(service, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if !m.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
services := m.services
|
||||||
|
m.services = nil
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for _, service := range services {
|
||||||
|
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
err = E.Append(err, service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Services() []adapter.Service {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.services
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Get(tag string) (adapter.Service, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
service, found := m.serviceByTag[tag]
|
||||||
|
m.access.Unlock()
|
||||||
|
return service, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
service, found := m.serviceByTag[tag]
|
||||||
|
if !found {
|
||||||
|
m.access.Unlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.serviceByTag, tag)
|
||||||
|
index := common.Index(m.services, func(it adapter.Service) bool {
|
||||||
|
return it == service
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid service index")
|
||||||
|
}
|
||||||
|
m.services = append(m.services[:index], m.services[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
m.access.Unlock()
|
||||||
|
if started {
|
||||||
|
return service.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
|
||||||
|
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(service, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = existsService.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
|
||||||
|
return it == existsService
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid service index")
|
||||||
|
}
|
||||||
|
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.services = append(m.services, service)
|
||||||
|
m.serviceByTag[tag] = service
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
adapter/service/registry.go
Normal file
72
adapter/service/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.ServiceRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructor map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructor: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
constructor, loaded := m.constructor[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.optionsType[outboundType] = optionsConstructor
|
||||||
|
m.constructor[outboundType] = constructor
|
||||||
|
}
|
||||||
18
adapter/ssm.go
Normal file
18
adapter/ssm.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ManagedSSMServer interface {
|
||||||
|
Inbound
|
||||||
|
SetTracker(tracker SSMTracker)
|
||||||
|
UpdateUsers(users []string, uPSKs []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSMTracker interface {
|
||||||
|
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
|
||||||
|
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
|
||||||
|
}
|
||||||
@@ -3,6 +3,6 @@ package adapter
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type TimeService interface {
|
type TimeService interface {
|
||||||
Service
|
SimpleLifecycle
|
||||||
TimeFunc() func() time.Time
|
TimeFunc() func() time.Time
|
||||||
}
|
}
|
||||||
|
|||||||
127
box.go
127
box.go
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
"github.com/sagernet/sing-box/common/certificate"
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -34,22 +35,23 @@ import (
|
|||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Service = (*Box)(nil)
|
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
network *route.NetworkManager
|
||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
dnsTransport *dns.TransportManager
|
service *boxService.Manager
|
||||||
dnsRouter *dns.Router
|
dnsTransport *dns.TransportManager
|
||||||
connection *route.ConnectionManager
|
dnsRouter *dns.Router
|
||||||
router *route.Router
|
connection *route.ConnectionManager
|
||||||
services []adapter.LifecycleService
|
router *route.Router
|
||||||
done chan struct{}
|
internalService []adapter.LifecycleService
|
||||||
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -64,6 +66,7 @@ func Context(
|
|||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
|
serviceRegistry adapter.ServiceRegistry,
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -84,6 +87,10 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
}
|
}
|
||||||
|
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
||||||
|
}
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +106,7 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
|
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@@ -109,6 +117,12 @@ func New(options Options) (*Box, error) {
|
|||||||
if outboundRegistry == nil {
|
if outboundRegistry == nil {
|
||||||
return nil, E.New("missing outbound registry in context")
|
return nil, E.New("missing outbound registry in context")
|
||||||
}
|
}
|
||||||
|
if dnsTransportRegistry == nil {
|
||||||
|
return nil, E.New("missing DNS transport registry in context")
|
||||||
|
}
|
||||||
|
if serviceRegistry == nil {
|
||||||
|
return nil, E.New("missing service registry in context")
|
||||||
|
}
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@@ -142,7 +156,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
|
||||||
var services []adapter.LifecycleService
|
var internalServices []adapter.LifecycleService
|
||||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||||
len(certificateOptions.Certificate) > 0 ||
|
len(certificateOptions.Certificate) > 0 ||
|
||||||
@@ -153,7 +167,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
services = append(services, certificateStore)
|
internalServices = append(internalServices, certificateStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
@@ -162,10 +176,12 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
|
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
@@ -280,6 +296,24 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i, serviceOptions := range options.Services {
|
||||||
|
var tag string
|
||||||
|
if serviceOptions.Tag != "" {
|
||||||
|
tag = serviceOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = serviceManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
serviceOptions.Type,
|
||||||
|
serviceOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
outboundManager.Initialize(common.Must1(
|
outboundManager.Initialize(common.Must1(
|
||||||
direct.NewOutbound(
|
direct.NewOutbound(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -305,7 +339,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
services = append(services, cacheFile)
|
internalServices = append(internalServices, cacheFile)
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
@@ -314,9 +348,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash-server")
|
return nil, E.Cause(err, "create clash-server")
|
||||||
}
|
}
|
||||||
router.SetTracker(clashServer)
|
router.AppendTracker(clashServer)
|
||||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||||
services = append(services, clashServer)
|
internalServices = append(internalServices, clashServer)
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
@@ -324,8 +358,8 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create v2ray-server")
|
return nil, E.Cause(err, "create v2ray-server")
|
||||||
}
|
}
|
||||||
if v2rayServer.StatsService() != nil {
|
if v2rayServer.StatsService() != nil {
|
||||||
router.SetTracker(v2rayServer.StatsService())
|
router.AppendTracker(v2rayServer.StatsService())
|
||||||
services = append(services, v2rayServer)
|
internalServices = append(internalServices, v2rayServer)
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,22 +377,23 @@ func New(options Options) (*Box, error) {
|
|||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
})
|
})
|
||||||
timeService.TimeService = ntpService
|
timeService.TimeService = ntpService
|
||||||
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
dnsRouter: dnsRouter,
|
service: serviceManager,
|
||||||
connection: connectionManager,
|
dnsRouter: dnsRouter,
|
||||||
router: router,
|
connection: connectionManager,
|
||||||
createdAt: createdAt,
|
router: router,
|
||||||
logFactory: logFactory,
|
createdAt: createdAt,
|
||||||
logger: logFactory.Logger(),
|
logFactory: logFactory,
|
||||||
services: services,
|
logger: logFactory.Logger(),
|
||||||
done: make(chan struct{}),
|
internalService: internalServices,
|
||||||
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,11 +443,11 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -428,31 +463,27 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.inbound.Start(adapter.StartStateStart)
|
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -469,7 +500,7 @@ func (s *Box) Close() error {
|
|||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.services {
|
for _, lifecycleService := range s.internalService {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
})
|
||||||
|
|||||||
Submodule clients/android updated: aefe3c0290...8354b78e5d
@@ -45,6 +45,7 @@ var (
|
|||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
iosTags []string
|
iosTags []string
|
||||||
|
memcTags []string
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,8 +59,9 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_tailscale")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,18 +101,19 @@ func buildAndroid() {
|
|||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-tags")
|
tags := append(sharedTags, memcTags...)
|
||||||
if !debugEnabled {
|
if debugEnabled {
|
||||||
args = append(args, strings.Join(sharedTags, ","))
|
tags = append(tags, debugTags...)
|
||||||
} else {
|
|
||||||
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = append(args, "-tags", strings.Join(tags, ","))
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||||
@@ -148,7 +151,9 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
@@ -156,12 +161,11 @@ func buildApple() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, iosTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
args = append(args, "-tags")
|
if debugEnabled {
|
||||||
if !debugEnabled {
|
tags = append(tags, debugTags...)
|
||||||
args = append(args, strings.Join(tags, ","))
|
|
||||||
} else {
|
|
||||||
args = append(args, strings.Join(append(tags, debugTags...), ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = append(args, "-tags", strings.Join(tags, ","))
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||||
|
|||||||
@@ -5,40 +5,49 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
|
"github.com/sagernet/sing-box/common/badversion"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nightly bool
|
var (
|
||||||
|
flagRunInCI bool
|
||||||
|
flagRunNightly bool
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
|
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if nightly {
|
var (
|
||||||
version, err := build_shared.ReadTagVersionRev()
|
versionStr string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if flagRunNightly {
|
||||||
|
var version badversion.Version
|
||||||
|
version, err = build_shared.ReadTagVersion()
|
||||||
|
if err == nil {
|
||||||
|
versionStr = version.String()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
versionStr, err = build_shared.ReadTag()
|
||||||
|
}
|
||||||
|
if flagRunInCI {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
var versionStr string
|
|
||||||
if version.PreReleaseIdentifier != "" {
|
|
||||||
versionStr = version.VersionString() + "-nightly"
|
|
||||||
} else {
|
|
||||||
version.Patch++
|
|
||||||
versionStr = version.VersionString() + "-nightly"
|
|
||||||
}
|
|
||||||
err = setGitHubEnv("version", versionStr)
|
err = setGitHubEnv("version", versionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tag, err := build_shared.ReadTag()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
os.Stdout.WriteString("unknown\n")
|
os.Stdout.WriteString("unknown\n")
|
||||||
} else {
|
} else {
|
||||||
os.Stdout.WriteString(tag + "\n")
|
os.Stdout.WriteString(versionStr + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry(), include.ServiceRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -35,6 +36,7 @@ type DefaultDialer struct {
|
|||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
|
netns string
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
networkStrategy *C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
defaultNetworkStrategy bool
|
defaultNetworkStrategy bool
|
||||||
@@ -69,18 +71,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark > 0 {
|
if options.RoutingMark > 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
}
|
|
||||||
if networkManager != nil {
|
|
||||||
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
|
|
||||||
if autoRedirectOutputMark > 0 {
|
|
||||||
if options.RoutingMark > 0 {
|
|
||||||
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
|
|
||||||
}
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
||||||
if disableDefaultBind || options.TCPFastOpen {
|
if disableDefaultBind || options.TCPFastOpen {
|
||||||
@@ -125,8 +117,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
@@ -198,6 +190,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpListener: listener,
|
udpListener: listener,
|
||||||
udpAddr4: udpAddr4,
|
udpAddr4: udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6: udpAddr6,
|
||||||
|
netns: options.NetNs,
|
||||||
networkManager: networkManager,
|
networkManager: networkManager,
|
||||||
networkStrategy: networkStrategy,
|
networkStrategy: networkStrategy,
|
||||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||||
@@ -207,6 +200,22 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefault bool) control.Func {
|
||||||
|
if networkManager == nil {
|
||||||
|
return control.RoutingMark(mark)
|
||||||
|
}
|
||||||
|
return func(network, address string, conn syscall.RawConn) error {
|
||||||
|
if networkManager.AutoRedirectOutputMark() != 0 {
|
||||||
|
if isDefault {
|
||||||
|
return E.New("`route.default_mark` is conflict with `tun.auto_redirect`")
|
||||||
|
} else {
|
||||||
|
return E.New("`routing_mark` is conflict with `tun.auto_redirect`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return control.RoutingMark(mark)(network, address, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
@@ -214,19 +223,21 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
return nil, E.New("domain not resolved")
|
return nil, E.New("domain not resolved")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
switch N.NetworkName(network) {
|
return trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
||||||
case N.NetworkUDP:
|
switch N.NetworkName(network) {
|
||||||
if !address.IsIPv6() {
|
case N.NetworkUDP:
|
||||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
if !address.IsIPv6() {
|
||||||
} else {
|
return d.udpDialer4.DialContext(ctx, network, address.String())
|
||||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
} else {
|
||||||
|
return d.udpDialer6.DialContext(ctx, network, address.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if !address.IsIPv6() {
|
||||||
if !address.IsIPv6() {
|
return DialSlowContext(&d.dialer4, ctx, network, address)
|
||||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
} else {
|
||||||
} else {
|
return DialSlowContext(&d.dialer6, ctx, network, address)
|
||||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
}
|
||||||
}
|
}))
|
||||||
} else {
|
} else {
|
||||||
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||||
}
|
}
|
||||||
@@ -282,13 +293,15 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
if destination.IsIPv6() {
|
return trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
if destination.IsIPv6() {
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
} else {
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
} else {
|
||||||
}
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||||
|
}
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,26 +6,39 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DirectDialer interface {
|
||||||
|
IsEmpty() bool
|
||||||
|
}
|
||||||
|
|
||||||
type DetourDialer struct {
|
type DetourDialer struct {
|
||||||
outboundManager adapter.OutboundManager
|
outboundManager adapter.OutboundManager
|
||||||
detour string
|
detour string
|
||||||
|
legacyDNSDialer bool
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
initErr error
|
initErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer {
|
func NewDetour(outboundManager adapter.OutboundManager, detour string, legacyDNSDialer bool) N.Dialer {
|
||||||
return &DetourDialer{outboundManager: outboundManager, detour: detour}
|
return &DetourDialer{
|
||||||
|
outboundManager: outboundManager,
|
||||||
|
detour: detour,
|
||||||
|
legacyDNSDialer: legacyDNSDialer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Start() error {
|
func InitializeDetour(dialer N.Dialer) error {
|
||||||
_, err := d.Dialer()
|
detourDialer, isDetour := common.Cast[*DetourDialer](dialer)
|
||||||
return err
|
if !isDetour {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return common.Error(detourDialer.Dialer())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
||||||
@@ -34,11 +47,20 @@ func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) init() {
|
func (d *DetourDialer) init() {
|
||||||
var loaded bool
|
dialer, loaded := d.outboundManager.Outbound(d.detour)
|
||||||
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
|
|
||||||
if !loaded {
|
if !loaded {
|
||||||
d.initErr = E.New("outbound detour not found: ", d.detour)
|
d.initErr = E.New("outbound detour not found: ", d.detour)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if !d.legacyDNSDialer {
|
||||||
|
if directDialer, isDirect := dialer.(DirectDialer); isDirect {
|
||||||
|
if directDialer.IsEmpty() {
|
||||||
|
d.initErr = E.New("detour to an empty direct outbound makes no sense")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.dialer = dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ type Options struct {
|
|||||||
DirectResolver bool
|
DirectResolver bool
|
||||||
ResolverOnDetour bool
|
ResolverOnDetour bool
|
||||||
NewDialer bool
|
NewDialer bool
|
||||||
|
LegacyDNSDialer bool
|
||||||
|
DirectOutbound bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge with NewWithOptions
|
// TODO: merge with NewWithOptions
|
||||||
@@ -45,7 +47,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
if outboundManager == nil {
|
if outboundManager == nil {
|
||||||
return nil, E.New("missing outbound manager")
|
return nil, E.New("missing outbound manager")
|
||||||
}
|
}
|
||||||
dialer = NewDetour(outboundManager, dialOptions.Detour)
|
dialer = NewDetour(outboundManager, dialOptions.Detour, options.LegacyDNSDialer)
|
||||||
} else {
|
} else {
|
||||||
dialer, err = NewDefault(options.Context, dialOptions)
|
dialer, err = NewDefault(options.Context, dialOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,13 +103,13 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
}
|
}
|
||||||
dnsQueryOptions.Transport = transport
|
dnsQueryOptions.Transport = transport
|
||||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
} else if options.NewDialer {
|
|
||||||
return nil, E.New("missing domain resolver for domain server address")
|
|
||||||
} else {
|
} else {
|
||||||
transports := dnsTransport.Transports()
|
transports := dnsTransport.Transports()
|
||||||
if len(transports) < 2 {
|
if len(transports) < 2 {
|
||||||
dnsQueryOptions.Transport = dnsTransport.Default()
|
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||||
} else {
|
} else if options.NewDialer {
|
||||||
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
|
} else if !options.DirectOutbound {
|
||||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -14,6 +16,8 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
@@ -135,3 +139,30 @@ func (l *Listener) UDPConn() *net.UDPConn {
|
|||||||
func (l *Listener) ListenOptions() option.ListenOptions {
|
func (l *Listener) ListenOptions() option.ListenOptions {
|
||||||
return l.listenOptions
|
return l.listenOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (T, error) {
|
||||||
|
if nameOrPath != "" {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
currentNs, err := netns.Get()
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "get current netns")
|
||||||
|
}
|
||||||
|
defer netns.Set(currentNs)
|
||||||
|
var targetNs netns.NsHandle
|
||||||
|
if strings.HasPrefix(nameOrPath, "/") {
|
||||||
|
targetNs, err = netns.GetFromPath(nameOrPath)
|
||||||
|
} else {
|
||||||
|
targetNs, err = netns.GetFromName(nameOrPath)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "get netns ", nameOrPath)
|
||||||
|
}
|
||||||
|
defer targetNs.Close()
|
||||||
|
err = netns.Set(targetNs)
|
||||||
|
if err != nil {
|
||||||
|
return common.DefaultValue[T](), E.Cause(err, "set netns to ", nameOrPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return block()
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) ListenTCP() (net.Listener, error) {
|
func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
||||||
|
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
var tcpListener net.Listener
|
|
||||||
var listenConfig net.ListenConfig
|
var listenConfig net.ListenConfig
|
||||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
@@ -37,20 +40,19 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
}
|
}
|
||||||
setMultiPathTCP(&listenConfig)
|
setMultiPathTCP(&listenConfig)
|
||||||
}
|
}
|
||||||
if l.listenOptions.TCPFastOpen {
|
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
||||||
var tfoConfig tfo.ListenConfig
|
if l.listenOptions.TCPFastOpen {
|
||||||
tfoConfig.ListenConfig = listenConfig
|
var tfoConfig tfo.ListenConfig
|
||||||
tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
tfoConfig.ListenConfig = listenConfig
|
||||||
} else {
|
return tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||||
tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
} else {
|
||||||
}
|
return listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||||
if err == nil {
|
}
|
||||||
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
})
|
||||||
}
|
if err != nil {
|
||||||
//nolint:staticcheck
|
return nil, err
|
||||||
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
|
||||||
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
|
||||||
}
|
}
|
||||||
|
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
||||||
l.tcpListener = tcpListener
|
l.tcpListener = tcpListener
|
||||||
return tcpListener, err
|
return tcpListener, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package listener
|
package listener
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -24,7 +25,9 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
if !udpFragment {
|
if !udpFragment {
|
||||||
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
||||||
}
|
}
|
||||||
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||||
|
return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -34,6 +37,12 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
return udpConn, err
|
return udpConn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
|
||||||
|
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||||
|
return listenConfig.ListenPacket(ctx, network, address)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Listener) UDPAddr() M.Socksaddr {
|
func (l *Listener) UDPAddr() M.Socksaddr {
|
||||||
return l.udpAddr
|
return l.udpAddr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -23,7 +24,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
|||||||
var first byte
|
var first byte
|
||||||
err := binary.Read(reader, binary.BigEndian, &first)
|
err := binary.Read(reader, binary.BigEndian, &first)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if first != 19 {
|
if first != 19 {
|
||||||
@@ -33,7 +34,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
|||||||
var protocol [19]byte
|
var protocol [19]byte
|
||||||
_, err = reader.Read(protocol[:])
|
_, err = reader.Read(protocol[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if string(protocol[:]) != "BitTorrent protocol" {
|
if string(protocol[:]) != "BitTorrent protocol" {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -67,7 +68,9 @@ func UTP(_ context.Context, metadata *adapter.InboundContext, packet []byte) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if extension > 0x04 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
var length byte
|
var length byte
|
||||||
err = binary.Read(reader, binary.BigEndian, &length)
|
err = binary.Read(reader, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -71,3 +71,19 @@ func TestSniffUDPTracker(t *testing.T) {
|
|||||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSniffNotUTP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
packets := []string{
|
||||||
|
"0102736470696e674958d580121500000000000079aaed6717a39c27b07c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
}
|
||||||
|
for _, pkt := range packets {
|
||||||
|
pkt, err := hex.DecodeString(pkt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.UTP(context.TODO(), &metadata, pkt)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,14 +5,11 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"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/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/task"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -21,22 +18,16 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
|||||||
var length uint16
|
var length uint16
|
||||||
err := binary.Read(reader, binary.BigEndian, &length)
|
err := binary.Read(reader, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return os.ErrInvalid
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
buffer := buf.NewSize(int(length))
|
buffer := buf.NewSize(int(length))
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
|
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||||
var readTask task.Group
|
|
||||||
readTask.Append0(func(ctx context.Context) error {
|
|
||||||
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen()))
|
|
||||||
})
|
|
||||||
err = readTask.Run(readCtx)
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
|
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
|
||||||
}
|
}
|
||||||
@@ -47,9 +38,6 @@ func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, pack
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(msg.Question) == 0 || msg.Question[0].Qclass != mDNS.ClassINET || !M.IsDomainName(msg.Question[0].Name) {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
metadata.Protocol = C.ProtocolDNS
|
metadata.Protocol = C.ProtocolDNS
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
23
common/sniff/dns_test.go
Normal file
23
common/sniff/dns_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffDNS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
query, err := hex.DecodeString("740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.DomainNameQuery(context.TODO(), &metadata, query)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
std_bufio "bufio"
|
std_bufio "bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"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"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/protocol/http"
|
"github.com/sagernet/sing/protocol/http"
|
||||||
)
|
)
|
||||||
@@ -14,7 +16,11 @@ import (
|
|||||||
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
request, err := http.ReadRequest(std_bufio.NewReader(reader))
|
request, err := http.ReadRequest(std_bufio.NewReader(reader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolHTTP
|
metadata.Protocol = C.ProtocolHTTP
|
||||||
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import (
|
|||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection")
|
|
||||||
|
|
||||||
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||||
reader := bytes.NewReader(packet)
|
reader := bytes.NewReader(packet)
|
||||||
typeByte, err := reader.ReadByte()
|
typeByte, err := reader.ReadByte()
|
||||||
@@ -308,7 +306,7 @@ find:
|
|||||||
metadata.Protocol = C.ProtocolQUIC
|
metadata.Protocol = C.ProtocolQUIC
|
||||||
metadata.Client = C.ClientChromium
|
metadata.Client = C.ClientChromium
|
||||||
metadata.SniffContext = fragments
|
metadata.SniffContext = fragments
|
||||||
return ErrClientHelloFragmented
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
metadata.Domain = fingerprint.ServerName
|
metadata.Domain = fingerprint.ServerName
|
||||||
for metadata.Client == "" {
|
for metadata.Client == "" {
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ func TestSniffQUICChromeNew(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
|
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
@@ -40,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) {
|
|||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktVersion uint8
|
var tpktVersion uint8
|
||||||
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
|
err := binary.Read(reader, binary.BigEndian, &tpktVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if tpktVersion != 0x03 {
|
if tpktVersion != 0x03 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -24,7 +25,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktReserved uint8
|
var tpktReserved uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
|
err = binary.Read(reader, binary.BigEndian, &tpktReserved)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if tpktReserved != 0x00 {
|
if tpktReserved != 0x00 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -33,7 +34,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var tpktLength uint16
|
var tpktLength uint16
|
||||||
err = binary.Read(reader, binary.BigEndian, &tpktLength)
|
err = binary.Read(reader, binary.BigEndian, &tpktLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tpktLength != 19 {
|
if tpktLength != 19 {
|
||||||
@@ -43,7 +44,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var cotpLength uint8
|
var cotpLength uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &cotpLength)
|
err = binary.Read(reader, binary.BigEndian, &cotpLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cotpLength != 14 {
|
if cotpLength != 14 {
|
||||||
@@ -53,7 +54,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var cotpTpduType uint8
|
var cotpTpduType uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
|
err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if cotpTpduType != 0xE0 {
|
if cotpTpduType != 0xE0 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -61,13 +62,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
|
|
||||||
err = rw.SkipN(reader, 5)
|
err = rw.SkipN(reader, 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rdpType uint8
|
var rdpType uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpType)
|
err = binary.Read(reader, binary.BigEndian, &rdpType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if rdpType != 0x01 {
|
if rdpType != 0x01 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -75,12 +76,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
|||||||
var rdpFlags uint8
|
var rdpFlags uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
|
err = binary.Read(reader, binary.BigEndian, &rdpFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
var rdpLength uint8
|
var rdpLength uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &rdpLength)
|
err = binary.Read(reader, binary.BigEndian, &rdpLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
if rdpLength != 8 {
|
if rdpLength != 8 {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
@@ -19,6 +20,8 @@ type (
|
|||||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNeedMoreData = E.New("need more data")
|
||||||
|
|
||||||
func Skip(metadata *adapter.InboundContext) bool {
|
func Skip(metadata *adapter.InboundContext) bool {
|
||||||
// skip server first protocols
|
// skip server first protocols
|
||||||
switch metadata.Destination.Port {
|
switch metadata.Destination.Port {
|
||||||
@@ -40,7 +43,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
timeout = C.ReadPayloadTimeout
|
timeout = C.ReadPayloadTimeout
|
||||||
}
|
}
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
var errors []error
|
var sniffError error
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
err := conn.SetReadDeadline(deadline)
|
err := conn.SetReadDeadline(deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -54,7 +57,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
}
|
}
|
||||||
return E.Cause(err, "read payload")
|
return E.Cause(err, "read payload")
|
||||||
}
|
}
|
||||||
errors = nil
|
sniffError = nil
|
||||||
for _, sniffer := range sniffers {
|
for _, sniffer := range sniffers {
|
||||||
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
|
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
|
||||||
return bytes.NewReader(it.Bytes())
|
return bytes.NewReader(it.Bytes())
|
||||||
@@ -63,20 +66,23 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errors = append(errors, err)
|
sniffError = E.Errors(sniffError, err)
|
||||||
|
}
|
||||||
|
if !errors.Is(err, ErrNeedMoreData) {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return E.Errors(errors...)
|
return sniffError
|
||||||
}
|
}
|
||||||
|
|
||||||
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
|
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
|
||||||
var errors []error
|
var sniffError []error
|
||||||
for _, sniffer := range sniffers {
|
for _, sniffer := range sniffers {
|
||||||
err := sniffer(ctx, metadata, packet)
|
err := sniffer(ctx, metadata, packet)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errors = append(errors, err)
|
sniffError = append(sniffError, err)
|
||||||
}
|
}
|
||||||
return E.Errors(errors...)
|
return E.Errors(sniffError...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,26 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
scanner := bufio.NewScanner(reader)
|
const sshPrefix = "SSH-2.0-"
|
||||||
if !scanner.Scan() {
|
bReader := bufio.NewReader(reader)
|
||||||
|
prefix, err := bReader.Peek(len(sshPrefix))
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
|
} else if string(prefix) != sshPrefix {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
fistLine := scanner.Text()
|
fistLine, _, err := bReader.ReadLine()
|
||||||
if !strings.HasPrefix(fistLine, "SSH-2.0-") {
|
if err != nil {
|
||||||
return os.ErrInvalid
|
return err
|
||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolSSH
|
metadata.Protocol = C.ProtocolSSH
|
||||||
metadata.Client = fistLine[8:]
|
metadata.Client = string(fistLine)[8:]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package sniff
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
|
||||||
@@ -23,5 +25,9 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
|
|||||||
metadata.Domain = clientHello.ServerName
|
metadata.Domain = clientHello.ServerName
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case "", "letsencrypt":
|
case "", "letsencrypt":
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
|||||||
if response.Rcode != mDNS.RcodeSuccess {
|
if response.Rcode != mDNS.RcodeSuccess {
|
||||||
return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
|
return nil, E.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
|
||||||
}
|
}
|
||||||
|
match:
|
||||||
for _, rr := range response.Answer {
|
for _, rr := range response.Answer {
|
||||||
switch resource := rr.(type) {
|
switch resource := rr.(type) {
|
||||||
case *mDNS.HTTPS:
|
case *mDNS.HTTPS:
|
||||||
@@ -133,11 +134,14 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
|||||||
return nil, E.Cause(err, "decode ECH config")
|
return nil, E.Cause(err, "decode ECH config")
|
||||||
}
|
}
|
||||||
s.config.EncryptedClientHelloConfigList = echConfigList
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
|
break match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, E.New("no ECH config found in DNS records")
|
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||||
|
return nil, E.New("no ECH config found in DNS records")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tlsConn, err := s.Client(conn)
|
tlsConn, err := s.Client(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -89,16 +89,20 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
||||||
|
|
||||||
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
||||||
for i, shortIDString := range options.Reality.ShortID {
|
if len(options.Reality.ShortID) == 0 {
|
||||||
var shortID [8]byte
|
tlsConfig.ShortIds[[8]byte{0}] = true
|
||||||
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
} else {
|
||||||
if err != nil {
|
for i, shortIDString := range options.Reality.ShortID {
|
||||||
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
|
var shortID [8]byte
|
||||||
|
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
|
||||||
|
}
|
||||||
|
if decodedLen > 8 {
|
||||||
|
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
|
||||||
|
}
|
||||||
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
if decodedLen > 8 {
|
|
||||||
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
|
|
||||||
}
|
|
||||||
tlsConfig.ShortIds[shortID] = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -21,7 +22,7 @@ var errInsecureUnused = E.New("tls: insecure unused")
|
|||||||
type STDServerConfig struct {
|
type STDServerConfig struct {
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
acmeService adapter.Service
|
acmeService adapter.SimpleLifecycle
|
||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
certificatePath string
|
certificatePath string
|
||||||
@@ -164,7 +165,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
var acmeService adapter.Service
|
var acmeService adapter.SimpleLifecycle
|
||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
@@ -233,8 +234,12 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
key = content
|
key = content
|
||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
|
timeFunc := ntp.TimeFuncFromContext(ctx)
|
||||||
|
if timeFunc == nil {
|
||||||
|
timeFunc = time.Now
|
||||||
|
}
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
return GenerateKeyPair(nil, nil, timeFunc, info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ const (
|
|||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
|
TypeDERP = "derp"
|
||||||
|
TypeDERPSTUN = "derp-stun"
|
||||||
|
TypeResolved = "resolved"
|
||||||
|
TypeSSMAPI = "ssm-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -243,9 +243,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
domain = FqdnToDomain(domain)
|
domain = FqdnToDomain(domain)
|
||||||
dnsName := dns.Fqdn(domain)
|
dnsName := dns.Fqdn(domain)
|
||||||
if options.Strategy == C.DomainStrategyIPv4Only {
|
var strategy C.DomainStrategy
|
||||||
|
if options.LookupStrategy != C.DomainStrategyAsIS {
|
||||||
|
strategy = options.LookupStrategy
|
||||||
|
} else {
|
||||||
|
strategy = options.Strategy
|
||||||
|
}
|
||||||
|
if strategy == C.DomainStrategyIPv4Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
||||||
} else if options.Strategy == C.DomainStrategyIPv6Only {
|
} else if strategy == C.DomainStrategyIPv6Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
||||||
}
|
}
|
||||||
var response4 []netip.Addr
|
var response4 []netip.Addr
|
||||||
@@ -271,7 +277,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
if len(response4) == 0 && len(response6) == 0 {
|
if len(response4) == 0 && len(response6) == 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return sortAddresses(response4, response6, options.Strategy), nil
|
return sortAddresses(response4, response6, strategy), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ClearCache() {
|
func (c *Client) ClearCache() {
|
||||||
@@ -527,12 +533,26 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
|
|||||||
return value, loaded
|
return value, loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
||||||
|
return &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: rcode,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: message.Question,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
|
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Rcode: dns.RcodeSuccess,
|
Response: true,
|
||||||
Response: true,
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
@@ -565,9 +585,12 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
|
|||||||
func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg {
|
func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Rcode: dns.RcodeSuccess,
|
Response: true,
|
||||||
Response: true,
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
Answer: []dns.RR{
|
Answer: []dns.RR{
|
||||||
@@ -588,9 +611,12 @@ func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToL
|
|||||||
func FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg {
|
func FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Rcode: dns.RcodeSuccess,
|
Response: true,
|
||||||
Response: true,
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
Answer: []dns.RR{
|
Answer: []dns.RR{
|
||||||
@@ -611,9 +637,12 @@ func FixedResponseTXT(id uint16, question dns.Question, records []string, timeTo
|
|||||||
func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg {
|
func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Rcode: dns.RcodeSuccess,
|
Response: true,
|
||||||
Response: true,
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf
|
|||||||
}
|
}
|
||||||
responseLen := response.Len()
|
responseLen := response.Len()
|
||||||
if responseLen > maxLen {
|
if responseLen > maxLen {
|
||||||
|
copyResponse := *response
|
||||||
|
response = ©Response
|
||||||
response.Truncate(maxLen)
|
response.Truncate(maxLen)
|
||||||
}
|
}
|
||||||
buffer := buf.NewSize(headroom*2 + 1 + responseLen)
|
buffer := buf.NewSize(headroom*2 + 1 + responseLen)
|
||||||
|
|||||||
@@ -263,20 +263,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
return nil, tun.ErrDrop
|
return nil, tun.ErrDrop
|
||||||
}
|
}
|
||||||
case *R.RuleActionPredefined:
|
case *R.RuleActionPredefined:
|
||||||
return &mDNS.Msg{
|
return action.Response(message), nil
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Response: true,
|
|
||||||
Authoritative: true,
|
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: action.Rcode,
|
|
||||||
},
|
|
||||||
Question: message.Question,
|
|
||||||
Answer: action.Answer,
|
|
||||||
Ns: action.Ns,
|
|
||||||
Extra: action.Extra,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var responseCheck func(responseAddrs []netip.Addr) bool
|
var responseCheck func(responseAddrs []netip.Addr) bool
|
||||||
@@ -298,7 +285,12 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
} else if errors.Is(err, ErrResponseRejected) {
|
} else if errors.Is(err, ErrResponseRejected) {
|
||||||
rejected = true
|
rejected = true
|
||||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||||
|
/*} else if responseCheck!= nil && errors.Is(err, RcodeError(mDNS.RcodeNameError)) {
|
||||||
|
rejected = true
|
||||||
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||||
|
*/
|
||||||
} else if len(message.Question) > 0 {
|
} else if len(message.Question) > 0 {
|
||||||
|
rejected = true
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||||
@@ -462,6 +454,6 @@ func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) {
|
|||||||
func (r *Router) ResetNetwork() {
|
func (r *Router) ResetNetwork() {
|
||||||
r.ClearCache()
|
r.ClearCache()
|
||||||
for _, transport := range r.transport.Transports() {
|
for _, transport := range r.transport.Transports() {
|
||||||
transport.Reset()
|
transport.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
|
|||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
for _, transport := range t.transports {
|
for _, transport := range t.transports {
|
||||||
transport.Reset()
|
transport.Close()
|
||||||
}
|
}
|
||||||
if t.interfaceCallback != nil {
|
if t.interfaceCallback != nil {
|
||||||
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
||||||
@@ -89,12 +89,6 @@ func (t *Transport) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
|
||||||
for _, transport := range t.transports {
|
|
||||||
transport.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
err := t.fetchServers()
|
err := t.fetchServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -252,7 +246,7 @@ func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.So
|
|||||||
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
||||||
}
|
}
|
||||||
for _, transport := range t.transports {
|
for _, transport := range t.transports {
|
||||||
transport.Reset()
|
transport.Close()
|
||||||
}
|
}
|
||||||
t.transports = transports
|
t.transports = transports
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
@@ -149,9 +150,17 @@ func NewHTTPSRaw(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Reset() {
|
func (t *HTTPSTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dialer.InitializeDetour(t.dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTPSTransport) Close() error {
|
||||||
t.transport.CloseIdleConnections()
|
t.transport.CloseIdleConnections()
|
||||||
t.transport = t.transport.Clone()
|
t.transport = t.transport.Clone()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
|||||||
@@ -35,12 +35,18 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
}
|
}
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||||
|
ctx: ctx,
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
@@ -52,7 +58,7 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
systemConfig := getSystemDNSConfig()
|
systemConfig := getSystemDNSConfig(t.ctx)
|
||||||
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||||
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -23,19 +24,21 @@ type resolverConfig struct {
|
|||||||
|
|
||||||
var resolvConf resolverConfig
|
var resolvConf resolverConfig
|
||||||
|
|
||||||
func getSystemDNSConfig() *dnsConfig {
|
func getSystemDNSConfig(ctx context.Context) *dnsConfig {
|
||||||
resolvConf.tryUpdate("/etc/resolv.conf")
|
resolvConf.tryUpdate(ctx, "/etc/resolv.conf")
|
||||||
return resolvConf.dnsConfig.Load()
|
return resolvConf.dnsConfig.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *resolverConfig) init() {
|
func (conf *resolverConfig) init(ctx context.Context) {
|
||||||
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf"))
|
conf.dnsConfig.Store(dnsReadConfig(ctx, "/etc/resolv.conf"))
|
||||||
conf.lastChecked = time.Now()
|
conf.lastChecked = time.Now()
|
||||||
conf.ch = make(chan struct{}, 1)
|
conf.ch = make(chan struct{}, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *resolverConfig) tryUpdate(name string) {
|
func (conf *resolverConfig) tryUpdate(ctx context.Context, name string) {
|
||||||
conf.initOnce.Do(conf.init)
|
conf.initOnce.Do(func() {
|
||||||
|
conf.init(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
if conf.dnsConfig.Load().noReload {
|
if conf.dnsConfig.Load().noReload {
|
||||||
return
|
return
|
||||||
@@ -59,7 +62,7 @@ func (conf *resolverConfig) tryUpdate(name string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dnsConf := dnsReadConfig(name)
|
dnsConf := dnsReadConfig(ctx, name)
|
||||||
conf.dnsConfig.Store(dnsConf)
|
conf.dnsConfig.Store(dnsConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ package local
|
|||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ string) *dnsConfig {
|
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||||
if C.res_init() != 0 {
|
if C.res_init() != 0 {
|
||||||
return &dnsConfig{
|
return &dnsConfig{
|
||||||
servers: defaultNS,
|
servers: defaultNS,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(name string) *dnsConfig {
|
func dnsReadConfig(_ context.Context, name string) *dnsConfig {
|
||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -8,10 +9,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ string) *dnsConfig {
|
func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
|
||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
@@ -22,35 +26,35 @@ func dnsReadConfig(_ string) *dnsConfig {
|
|||||||
conf.servers = defaultNS
|
conf.servers = defaultNS
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
aas, err := adapterAddresses()
|
addresses, err := adapterAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
var dnsAddresses []struct {
|
||||||
for _, aa := range aas {
|
ifName string
|
||||||
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
|
netip.Addr
|
||||||
if aa.OperStatus != windows.IfOperStatusUp {
|
}
|
||||||
|
for _, address := range addresses {
|
||||||
|
if address.OperStatus != windows.IfOperStatusUp {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if address.IfType == windows.IF_TYPE_TUNNEL {
|
||||||
// Only take interfaces which have at least one gateway
|
|
||||||
if aa.FirstGatewayAddress == nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if address.FirstGatewayAddress == nil {
|
||||||
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
|
continue
|
||||||
sa, err := dns.Address.Sockaddr.Sockaddr()
|
}
|
||||||
|
for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {
|
||||||
|
rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var ip netip.Addr
|
var dnsServerAddr netip.Addr
|
||||||
switch sa := sa.(type) {
|
switch sockaddr := rawSockaddr.(type) {
|
||||||
case *syscall.SockaddrInet4:
|
case *syscall.SockaddrInet4:
|
||||||
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]})
|
dnsServerAddr = netip.AddrFrom4(sockaddr.Addr)
|
||||||
case *syscall.SockaddrInet6:
|
case *syscall.SockaddrInet6:
|
||||||
var addr16 [16]byte
|
if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {
|
||||||
copy(addr16[:], sa.Addr[:])
|
|
||||||
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
|
|
||||||
// fec0/10 IPv6 addresses are site local anycast DNS
|
// fec0/10 IPv6 addresses are site local anycast DNS
|
||||||
// addresses Microsoft sets by default if no other
|
// addresses Microsoft sets by default if no other
|
||||||
// IPv6 DNS address is set. Site local anycast is
|
// IPv6 DNS address is set. Site local anycast is
|
||||||
@@ -58,14 +62,27 @@ func dnsReadConfig(_ string) *dnsConfig {
|
|||||||
// https://datatracker.ietf.org/doc/html/rfc3879
|
// https://datatracker.ietf.org/doc/html/rfc3879
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ip = netip.AddrFrom16(addr16)
|
dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
|
||||||
default:
|
default:
|
||||||
// Unexpected type.
|
// Unexpected type.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53"))
|
dnsAddresses = append(dnsAddresses, struct {
|
||||||
|
ifName string
|
||||||
|
netip.Addr
|
||||||
|
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var myInterface string
|
||||||
|
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
|
||||||
|
myInterface = networkManager.InterfaceMonitor().MyInterface()
|
||||||
|
}
|
||||||
|
for _, address := range dnsAddresses {
|
||||||
|
if address.ifName == myInterface {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
|
||||||
|
}
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,8 +111,12 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTP3Transport) Reset() {
|
func (t *HTTP3Transport) Start(stage adapter.StartStage) error {
|
||||||
t.transport.Close()
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTP3Transport) Close() error {
|
||||||
|
return t.transport.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
|||||||
@@ -68,13 +68,18 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
t.access.Lock()
|
t.access.Lock()
|
||||||
defer t.access.Unlock()
|
defer t.access.Unlock()
|
||||||
connection := t.connection
|
connection := t.connection
|
||||||
if connection != nil {
|
if connection != nil {
|
||||||
connection.CloseWithError(0, "")
|
connection.CloseWithError(0, "")
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
@@ -135,12 +140,12 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer stream.Close()
|
|
||||||
defer stream.CancelRead(0)
|
|
||||||
err = transport.WriteMessage(stream, 0, message)
|
err = transport.WriteMessage(stream, 0, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
stream.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
stream.Close()
|
||||||
return transport.ReadMessage(stream)
|
return transport.ReadMessage(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -46,7 +47,15 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPTransport) Reset() {
|
func (t *TCPTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dialer.InitializeDetour(t.dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCPTransport) Close() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
@@ -56,22 +57,34 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if serverAddr.Port == 0 {
|
if serverAddr.Port == 0 {
|
||||||
serverAddr.Port = 853
|
serverAddr.Port = 853
|
||||||
}
|
}
|
||||||
return &TLSTransport{
|
return NewTLSRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), transportDialer, serverAddr, tlsConfig), nil
|
||||||
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
|
|
||||||
logger: logger,
|
|
||||||
dialer: transportDialer,
|
|
||||||
serverAddr: serverAddr,
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TLSTransport) Reset() {
|
func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr, tlsConfig tls.Config) *TLSTransport {
|
||||||
|
return &TLSTransport{
|
||||||
|
TransportAdapter: adapter,
|
||||||
|
logger: logger,
|
||||||
|
dialer: dialer,
|
||||||
|
serverAddr: serverAddr,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dialer.InitializeDetour(t.dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLSTransport) Close() error {
|
||||||
t.access.Lock()
|
t.access.Lock()
|
||||||
defer t.access.Unlock()
|
defer t.access.Unlock()
|
||||||
for connection := t.connections.Front(); connection != nil; connection = connection.Next() {
|
for connection := t.connections.Front(); connection != nil; connection = connection.Next() {
|
||||||
connection.Value.Close()
|
connection.Value.Close()
|
||||||
}
|
}
|
||||||
t.connections.Init()
|
t.connections.Init()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -64,11 +65,19 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPTransport) Reset() {
|
func (t *UDPTransport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dialer.InitializeDetour(t.dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *UDPTransport) Close() error {
|
||||||
t.access.Lock()
|
t.access.Lock()
|
||||||
defer t.access.Unlock()
|
defer t.access.Unlock()
|
||||||
close(t.done)
|
close(t.done)
|
||||||
t.done = make(chan struct{})
|
t.done = make(chan struct{})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
@@ -203,8 +212,8 @@ type dnsConnection struct {
|
|||||||
|
|
||||||
func (c *dnsConnection) Close(err error) {
|
func (c *dnsConnection) Close(err error) {
|
||||||
c.closeOnce.Do(func() {
|
c.closeOnce.Do(func() {
|
||||||
close(c.done)
|
|
||||||
c.err = err
|
c.err = err
|
||||||
|
close(c.done)
|
||||||
})
|
})
|
||||||
c.Conn.Close()
|
c.Conn.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
|
|||||||
return dialer.NewDefaultOutbound(ctx), nil
|
return dialer.NewDefaultOutbound(ctx), nil
|
||||||
} else {
|
} else {
|
||||||
return dialer.NewWithOptions(dialer.Options{
|
return dialer.NewWithOptions(dialer.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options.DialerOptions,
|
Options: options.DialerOptions,
|
||||||
DirectResolver: true,
|
DirectResolver: true,
|
||||||
|
LegacyDNSDialer: options.Legacy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,10 +44,11 @@ func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions)
|
|||||||
return transportDialer, nil
|
return transportDialer, nil
|
||||||
} else {
|
} else {
|
||||||
return dialer.NewWithOptions(dialer.Options{
|
return dialer.NewWithOptions(dialer.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options.DialerOptions,
|
Options: options.DialerOptions,
|
||||||
RemoteIsDomain: options.ServerIsDomain(),
|
RemoteIsDomain: options.ServerIsDomain(),
|
||||||
DirectResolver: true,
|
DirectResolver: true,
|
||||||
|
LegacyDNSDialer: options.Legacy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ func (m *TransportManager) Start(stage adapter.StartStage) error {
|
|||||||
transports := m.transports
|
transports := m.transports
|
||||||
m.access.Unlock()
|
m.access.Unlock()
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
|
if m.defaultTag != "" && m.defaultTransport == nil {
|
||||||
|
return E.New("default DNS server not found: ", m.defaultTag)
|
||||||
|
}
|
||||||
return m.startTransports(m.transports)
|
return m.startTransports(m.transports)
|
||||||
} else {
|
} else {
|
||||||
for _, outbound := range transports {
|
for _, outbound := range transports {
|
||||||
@@ -225,7 +228,7 @@ func (m *TransportManager) Remove(tag string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if started {
|
if started {
|
||||||
transport.Reset()
|
transport.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,36 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 1.12.0-beta.3
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.11.7
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-beta.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
||||||
|
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||||
|
|
||||||
|
### 1.11.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
#### 1.12.0-alpha.19
|
||||||
|
|
||||||
|
* Update gVisor to 20250319.0
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.0-alpha.18
|
#### 1.12.0-alpha.18
|
||||||
|
|
||||||
* Add wildcard SNI support for ShadowTLS inbound **1**
|
* Add wildcard SNI support for ShadowTLS inbound **1**
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ Default padding scheme:
|
|||||||
|
|
||||||
```
|
```
|
||||||
stop=8
|
stop=8
|
||||||
0=34-120
|
0=30-30
|
||||||
1=100-400
|
1=100-400
|
||||||
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||||
3=500-1000
|
3=9-9,500-1000
|
||||||
4=500-1000
|
4=500-1000
|
||||||
5=500-1000
|
5=500-1000
|
||||||
6=500-1000
|
6=500-1000
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ AnyTLS 填充方案行数组。
|
|||||||
|
|
||||||
```
|
```
|
||||||
stop=8
|
stop=8
|
||||||
0=34-120
|
0=30-30
|
||||||
1=100-400
|
1=100-400
|
||||||
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
|
||||||
3=500-1000
|
3=9-9,500-1000
|
||||||
4=500-1000
|
4=500-1000
|
||||||
5=500-1000
|
5=500-1000
|
||||||
6=500-1000
|
6=500-1000
|
||||||
|
|||||||
@@ -211,6 +211,10 @@ Set the default route to the Tun.
|
|||||||
|
|
||||||
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
|
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
|
||||||
|
|
||||||
|
!!! note "Also enable `auto_redirect`"
|
||||||
|
|
||||||
|
`auto_redirect` is always recommended on Linux, it provides better routing, higher performance (better than tproxy), and avoids conflicts between TUN and Docker bridge networks.
|
||||||
|
|
||||||
#### iproute2_table_index
|
#### iproute2_table_index
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
@@ -235,22 +239,29 @@ Linux iproute2 rule start index generated by `auto_route`.
|
|||||||
|
|
||||||
Only supported on Linux with `auto_route` enabled.
|
Only supported on Linux with `auto_route` enabled.
|
||||||
|
|
||||||
Automatically configure iptables/nftables to redirect connections.
|
Improve TUN routing and performance using nftables.
|
||||||
|
|
||||||
*In Android*:
|
`auto_redirect` is always recommended on Linux, it provides better routing,
|
||||||
|
higher performance (better than tproxy),
|
||||||
|
and avoids conflicts between TUN and Docker bridge networks.
|
||||||
|
|
||||||
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
|
Note that `auto_redirect` also works on Android,
|
||||||
|
but due to the lack of `nftables` and `ip6tables`,
|
||||||
|
only simple IPv4 TCP forwarding is performed.
|
||||||
|
To share your VPN connection over hotspot or repeater on Android,
|
||||||
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||||
|
|
||||||
*In Linux*:
|
`auto_redirect` also automatically inserts compatibility rules
|
||||||
|
into the OpenWrt fw4 table, i.e.
|
||||||
|
it will work on routers without any extra configuration.
|
||||||
|
|
||||||
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
|
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
|
Connection input mark used by `auto_redirect`.
|
||||||
|
|
||||||
`0x2023` is used by default.
|
`0x2023` is used by default.
|
||||||
|
|
||||||
@@ -258,7 +269,7 @@ Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`
|
|||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
Connection input mark used by `route[_exclude]_address_set` with `auto_redirect`.
|
Connection output mark used by `auto_redirect`.
|
||||||
|
|
||||||
`0x2024` is used by default.
|
`0x2024` is used by default.
|
||||||
|
|
||||||
@@ -269,17 +280,15 @@ Enforce strict routing rules when `auto_route` is enabled:
|
|||||||
*In Linux*:
|
*In Linux*:
|
||||||
|
|
||||||
* Let unsupported network unreachable
|
* Let unsupported network unreachable
|
||||||
* Make ICMP traffic route to tun instead of upstream interfaces
|
* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
|
||||||
* Route all connections to tun
|
|
||||||
|
|
||||||
It prevents IP address leaks and makes DNS hijacking work on Android.
|
|
||||||
|
|
||||||
*In Windows*:
|
*In Windows*:
|
||||||
|
|
||||||
* Add firewall rules to prevent DNS leak caused by
|
* Let unsupported network unreachable
|
||||||
|
* prevent DNS leak caused by
|
||||||
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
Windows' [ordinary multihomed DNS resolution behavior](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
||||||
|
|
||||||
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
It may prevent some Windows applications (such as VirtualBox) from working properly in certain situations.
|
||||||
|
|
||||||
#### route_address
|
#### route_address
|
||||||
|
|
||||||
@@ -367,8 +376,6 @@ Exclude custom routes when `auto_route` is enabled.
|
|||||||
|
|
||||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||||
Matched traffic will bypass the sing-box routes.
|
Matched traffic will bypass the sing-box routes.
|
||||||
|
|
||||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
|
||||||
|
|
||||||
=== "Without `auto_redirect` enabled"
|
=== "Without `auto_redirect` enabled"
|
||||||
|
|
||||||
|
|||||||
@@ -215,6 +215,10 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。
|
VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。
|
||||||
|
|
||||||
|
!!! note "也启用 `auto_redirect`"
|
||||||
|
|
||||||
|
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由, 更高的性能(优于 tproxy), 并避免 TUN 与 Docker 桥接网络冲突。
|
||||||
|
|
||||||
#### iproute2_table_index
|
#### iproute2_table_index
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
@@ -237,23 +241,24 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|
||||||
仅支持 Linux,且需要 `auto_route` 已启用。
|
仅支持 Linux,且需要 `auto_route` 已启用。
|
||||||
|
|
||||||
自动配置 iptables/nftables 以重定向连接。
|
通过使用 nftables 改善 TUN 路由和性能。
|
||||||
|
|
||||||
*在 Android 中*:
|
在 Linux 上始终推荐使用 `auto_redirect`,它提供更好的路由、更高的性能(优于 tproxy),并避免了 TUN 和 Docker 桥接网络之间的冲突。
|
||||||
|
|
||||||
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
请注意,`auto_redirect` 也适用于 Android,但由于缺少 `nftables` 和 `ip6tables`,仅执行简单的 IPv4 TCP 转发。
|
||||||
|
若要在 Android 上通过热点或中继器共享 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
||||||
|
|
||||||
*在 Linux 中*:
|
`auto_redirect` 还会自动将兼容性规则插入 OpenWrt 的 fw4 表中,即无需额外配置即可在路由器上工作。
|
||||||
|
|
||||||
带有 `auto_redirect `的 `auto_route` 可以在路由器上按预期工作,**无需干预**。
|
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
`route_address_set` 和 `route_exclude_address_set` 使用的连接输入标记。
|
`auto_redirect` 使用的连接输入标记。
|
||||||
|
|
||||||
默认使用 `0x2023`。
|
默认使用 `0x2023`。
|
||||||
|
|
||||||
@@ -261,29 +266,25 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
`route_address_set` 和 `route_exclude_address_set` 使用的连接输出标记。
|
`auto_redirect` 使用的连接输出标记。
|
||||||
|
|
||||||
默认使用 `0x2024`。
|
默认使用 `0x2024`。
|
||||||
|
|
||||||
#### strict_route
|
#### strict_route
|
||||||
|
|
||||||
启用 `auto_route` 时执行严格的路由规则。
|
当启用 `auto_route` 时,强制执行严格的路由规则:
|
||||||
|
|
||||||
*在 Linux 中*:
|
*在 Linux 中*:
|
||||||
|
|
||||||
* 让不支持的网络无法到达
|
* 使不支持的网络不可达。
|
||||||
* 使 ICMP 流量路由到 tun 而不是上游接口
|
* 出于历史遗留原因,当未启用 `strict_route` 或 `auto_redirect` 时,所有 ICMP 流量将不会通过 TUN。
|
||||||
* 将所有连接路由到 tun
|
|
||||||
|
|
||||||
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
|
*在 Windows 中*:
|
||||||
|
|
||||||
*在 Windows 中*:
|
* 使不支持的网络不可达。
|
||||||
|
* 阻止 Windows 的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) 造成的 DNS 泄露
|
||||||
|
|
||||||
* 添加防火墙规则以阻止 Windows
|
它可能会使某些 Windows 应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
||||||
的 [普通多宿主 DNS 解析行为](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29)
|
|
||||||
造成的 DNS 泄露
|
|
||||||
|
|
||||||
它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
|
||||||
|
|
||||||
#### route_address
|
#### route_address
|
||||||
|
|
||||||
@@ -341,8 +342,6 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||||
不匹配的流量将绕过 sing-box 路由。
|
不匹配的流量将绕过 sing-box 路由。
|
||||||
|
|
||||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
|
||||||
|
|
||||||
=== "`auto_redirect` 未启用"
|
=== "`auto_redirect` 未启用"
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
|
|||||||
|
|
||||||
| QUIC Client | Type |
|
| QUIC Client | Type |
|
||||||
|:------------------------:|:----------:|
|
|:------------------------:|:----------:|
|
||||||
| Chromium/Cronet | `chrimium` |
|
| Chromium/Cronet | `chromium` |
|
||||||
| Safari/Apple Network API | `safari` |
|
| Safari/Apple Network API | `safari` |
|
||||||
| Firefox / uquic firefox | `firefox` |
|
| Firefox / uquic firefox | `firefox` |
|
||||||
| quic-go / uquic chrome | `quic-go` |
|
| quic-go / uquic chrome | `quic-go` |
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ icon: material/new-box
|
|||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [domain_resolver](#domain_resolver)
|
:material-plus: [domain_resolver](#domain_resolver)
|
||||||
:material-delete-clock: [domain_strategy](#domain_strategy)
|
:material-delete-clock: [domain_strategy](#domain_strategy)
|
||||||
|
:material-plus: [netns](#netns)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
@@ -18,24 +19,25 @@ icon: material/new-box
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detour": "upstream-out",
|
"detour": "",
|
||||||
"bind_interface": "en0",
|
"bind_interface": "",
|
||||||
"inet4_bind_address": "0.0.0.0",
|
"inet4_bind_address": "",
|
||||||
"inet6_bind_address": "::",
|
"inet6_bind_address": "",
|
||||||
"routing_mark": 1234,
|
"routing_mark": 0,
|
||||||
"reuse_addr": false,
|
"reuse_addr": false,
|
||||||
"connect_timeout": "5s",
|
"connect_timeout": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
|
"netns": "",
|
||||||
"domain_resolver": "", // or {}
|
"domain_resolver": "", // or {}
|
||||||
"network_strategy": "default",
|
"network_strategy": "",
|
||||||
"network_type": [],
|
"network_type": [],
|
||||||
"fallback_network_type": [],
|
"fallback_network_type": [],
|
||||||
"fallback_delay": "300ms",
|
"fallback_delay": "",
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
"domain_strategy": "prefer_ipv6"
|
"domain_strategy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -75,6 +77,15 @@ Set netfilter routing mark.
|
|||||||
|
|
||||||
Reuse listener address.
|
Reuse listener address.
|
||||||
|
|
||||||
|
#### connect_timeout
|
||||||
|
|
||||||
|
Connect timeout, in golang's Duration format.
|
||||||
|
|
||||||
|
A duration string is a possibly signed sequence of
|
||||||
|
decimal numbers, each with optional fraction and a unit suffix,
|
||||||
|
such as "300ms", "-1.5h" or "2h45m".
|
||||||
|
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||||
|
|
||||||
#### tcp_fast_open
|
#### tcp_fast_open
|
||||||
|
|
||||||
Enable TCP Fast Open.
|
Enable TCP Fast Open.
|
||||||
@@ -91,14 +102,15 @@ Enable TCP Multi Path.
|
|||||||
|
|
||||||
Enable UDP fragmentation.
|
Enable UDP fragmentation.
|
||||||
|
|
||||||
#### connect_timeout
|
#### netns
|
||||||
|
|
||||||
Connect timeout, in golang's Duration format.
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
A duration string is a possibly signed sequence of
|
!!! quote ""
|
||||||
decimal numbers, each with optional fraction and a unit suffix,
|
|
||||||
such as "300ms", "-1.5h" or "2h45m".
|
Only supported on Linux.
|
||||||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
|
||||||
|
Set network namespace, name or path.
|
||||||
|
|
||||||
#### domain_resolver
|
#### domain_resolver
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ icon: material/new-box
|
|||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [domain_resolver](#domain_resolver)
|
:material-plus: [domain_resolver](#domain_resolver)
|
||||||
:material-delete-clock: [domain_strategy](#domain_strategy)
|
:material-delete-clock: [domain_strategy](#domain_strategy)
|
||||||
|
:material-plus: [netns](#netns)
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
@@ -18,25 +19,26 @@ icon: material/new-box
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detour": "upstream-out",
|
"detour": "",
|
||||||
"bind_interface": "en0",
|
"bind_interface": "",
|
||||||
"inet4_bind_address": "0.0.0.0",
|
"inet4_bind_address": "",
|
||||||
"inet6_bind_address": "::",
|
"inet6_bind_address": "",
|
||||||
"routing_mark": 1234,
|
"routing_mark": 0,
|
||||||
"reuse_addr": false,
|
"reuse_addr": false,
|
||||||
"connect_timeout": "5s",
|
"connect_timeout": "",
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
|
"netns": "",
|
||||||
"domain_resolver": "", // 或 {}
|
"domain_resolver": "", // 或 {}
|
||||||
"network_strategy": "",
|
"network_strategy": "",
|
||||||
"network_type": [],
|
"network_type": [],
|
||||||
"fallback_network_type": [],
|
"fallback_network_type": [],
|
||||||
"fallback_delay": "300ms",
|
"fallback_delay": "",
|
||||||
|
|
||||||
// 废弃的
|
// 废弃的
|
||||||
|
|
||||||
"domain_strategy": "prefer_ipv6"
|
"domain_strategy": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -76,6 +78,13 @@ icon: material/new-box
|
|||||||
|
|
||||||
重用监听地址。
|
重用监听地址。
|
||||||
|
|
||||||
|
#### connect_timeout
|
||||||
|
|
||||||
|
连接超时,采用 golang 的 Duration 格式。
|
||||||
|
|
||||||
|
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
|
||||||
|
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
|
||||||
|
|
||||||
#### tcp_fast_open
|
#### tcp_fast_open
|
||||||
|
|
||||||
启用 TCP Fast Open。
|
启用 TCP Fast Open。
|
||||||
@@ -92,12 +101,15 @@ icon: material/new-box
|
|||||||
|
|
||||||
启用 UDP 分段。
|
启用 UDP 分段。
|
||||||
|
|
||||||
#### connect_timeout
|
#### netns
|
||||||
|
|
||||||
连接超时,采用 golang 的 Duration 格式。
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
|
!!! quote ""
|
||||||
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
|
|
||||||
|
仅支持 Linux。
|
||||||
|
|
||||||
|
设置网络命名空间,名称或路径。
|
||||||
|
|
||||||
#### domain_resolver
|
#### domain_resolver
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
---
|
---
|
||||||
icon: material/delete-clock
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
|
:material-plus: [netns](#netns)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-delete-clock: [sniff](#sniff)
|
:material-delete-clock: [sniff](#sniff)
|
||||||
@@ -14,17 +18,18 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"listen": "::",
|
"listen": "",
|
||||||
"listen_port": 5353,
|
"listen_port": 0,
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"udp_timeout": "5m",
|
"udp_timeout": "",
|
||||||
"detour": "another-in",
|
"netns": "",
|
||||||
|
"detour": "",
|
||||||
"sniff": false,
|
"sniff": false,
|
||||||
"sniff_override_destination": false,
|
"sniff_override_destination": false,
|
||||||
"sniff_timeout": "300ms",
|
"sniff_timeout": "",
|
||||||
"domain_strategy": "prefer_ipv6",
|
"domain_strategy": "",
|
||||||
"udp_disable_domain_unmapping": false
|
"udp_disable_domain_unmapping": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -72,6 +77,16 @@ UDP NAT expiration time.
|
|||||||
|
|
||||||
`5m` will be used by default.
|
`5m` will be used by default.
|
||||||
|
|
||||||
|
#### netns
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux.
|
||||||
|
|
||||||
|
Set network namespace, name or path.
|
||||||
|
|
||||||
#### detour
|
#### detour
|
||||||
|
|
||||||
If set, connections will be forwarded to the specified inbound.
|
If set, connections will be forwarded to the specified inbound.
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
---
|
---
|
||||||
icon: material/delete-clock
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
|
:material-plus: [netns](#netns)
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-delete-clock: [sniff](#sniff)
|
:material-delete-clock: [sniff](#sniff)
|
||||||
@@ -14,17 +18,18 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"listen": "::",
|
"listen": "",
|
||||||
"listen_port": 5353,
|
"listen_port": 0,
|
||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"udp_timeout": "5m",
|
"udp_timeout": "",
|
||||||
"detour": "another-in",
|
"netns": "",
|
||||||
|
"detour": "",
|
||||||
"sniff": false,
|
"sniff": false,
|
||||||
"sniff_override_destination": false,
|
"sniff_override_destination": false,
|
||||||
"sniff_timeout": "300ms",
|
"sniff_timeout": "",
|
||||||
"domain_strategy": "prefer_ipv6",
|
"domain_strategy": "",
|
||||||
"udp_disable_domain_unmapping": false
|
"udp_disable_domain_unmapping": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -73,6 +78,16 @@ UDP NAT 过期时间。
|
|||||||
|
|
||||||
默认使用 `5m`。
|
默认使用 `5m`。
|
||||||
|
|
||||||
|
#### netns
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux。
|
||||||
|
|
||||||
|
设置网络命名空间,名称或路径。
|
||||||
|
|
||||||
#### detour
|
#### detour
|
||||||
|
|
||||||
如果设置,连接将被转发到指定的入站。
|
如果设置,连接将被转发到指定的入站。
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func BaseContext(platformInterface PlatformInterface) context.Context {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry)
|
return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||||
@@ -116,6 +116,10 @@ func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network str
|
|||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type interfaceMonitorStub struct{}
|
type interfaceMonitorStub struct{}
|
||||||
|
|
||||||
func (s *interfaceMonitorStub) Start() error {
|
func (s *interfaceMonitorStub) Start() error {
|
||||||
@@ -145,8 +149,11 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
|
|||||||
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error {
|
func (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) {
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
func (s *interfaceMonitorStub) MyInterface() string {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatConfig(configContent string) (*StringBox, error) {
|
func FormatConfig(configContent string) (*StringBox, error) {
|
||||||
|
|||||||
@@ -38,7 +38,12 @@ func newPlatformTransport(iif LocalDNSTransport, tag string, options option.Loca
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *platformTransport) Reset() {
|
func (p *platformTransport) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *platformTransport) Close() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ var (
|
|||||||
|
|
||||||
type platformDefaultInterfaceMonitor struct {
|
type platformDefaultInterfaceMonitor struct {
|
||||||
*platformInterfaceWrapper
|
*platformInterfaceWrapper
|
||||||
element *list.Element[tun.NetworkUpdateCallback]
|
logger logger.Logger
|
||||||
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
|
element *list.Element[tun.NetworkUpdateCallback]
|
||||||
logger logger.Logger
|
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
|
||||||
|
myInterface string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *platformDefaultInterfaceMonitor) Start() error {
|
func (m *platformDefaultInterfaceMonitor) Start() error {
|
||||||
@@ -102,3 +103,15 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s
|
|||||||
callback(newInterface, 0)
|
callback(newInterface, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) {
|
||||||
|
m.defaultInterfaceAccess.Lock()
|
||||||
|
defer m.defaultInterfaceAccess.Unlock()
|
||||||
|
m.myInterface = interfaceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) MyInterface() string {
|
||||||
|
m.defaultInterfaceAccess.Lock()
|
||||||
|
defer m.defaultInterfaceAccess.Unlock()
|
||||||
|
return m.myInterface
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type BoxService struct {
|
|||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
|
|
||||||
servicePauseFields
|
iOSPauseFields
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||||
@@ -164,6 +164,7 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "query tun name")
|
return nil, E.Cause(err, "query tun name")
|
||||||
}
|
}
|
||||||
|
options.InterfaceMonitor.RegisterMyInterface(options.Name)
|
||||||
dupFd, err := dup(int(tunFd))
|
dupFd, err := dup(int(tunFd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "dup tun file descriptor")
|
return nil, E.Cause(err, "dup tun file descriptor")
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type servicePauseFields struct {
|
type iOSPauseFields struct {
|
||||||
pauseAccess sync.Mutex
|
endPauseTimer *time.Timer
|
||||||
pauseTimer *time.Timer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) Pause() {
|
func (s *BoxService) Pause() {
|
||||||
s.pauseAccess.Lock()
|
s.pauseManager.DevicePause()
|
||||||
defer s.pauseAccess.Unlock()
|
if !C.IsIos {
|
||||||
if s.pauseTimer != nil {
|
s.instance.Router().ResetNetwork()
|
||||||
s.pauseTimer.Stop()
|
} else {
|
||||||
|
if s.endPauseTimer == nil {
|
||||||
|
s.endPauseTimer = time.AfterFunc(time.Minute, s.pauseManager.DeviceWake)
|
||||||
|
} else {
|
||||||
|
s.endPauseTimer.Reset(time.Minute)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) Wake() {
|
func (s *BoxService) Wake() {
|
||||||
s.pauseAccess.Lock()
|
if !C.IsIos {
|
||||||
defer s.pauseAccess.Unlock()
|
s.pauseManager.DeviceWake()
|
||||||
if s.pauseTimer != nil {
|
s.instance.Router().ResetNetwork()
|
||||||
s.pauseTimer.Stop()
|
|
||||||
}
|
}
|
||||||
s.pauseTimer = time.AfterFunc(3*time.Minute, s.ResetNetwork)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) ResetNetwork() {
|
func (s *BoxService) ResetNetwork() {
|
||||||
|
|||||||
20
go.mod
20
go.mod
@@ -3,13 +3,15 @@ module github.com/sagernet/sing-box
|
|||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anytls/sing-anytls v0.0.6
|
github.com/anytls/sing-anytls v0.0.8
|
||||||
github.com/caddyserver/certmagic v0.21.7
|
github.com/caddyserver/certmagic v0.21.7
|
||||||
github.com/cloudflare/circl v1.6.0
|
github.com/cloudflare/circl v1.6.0
|
||||||
|
github.com/coder/websocket v1.8.12
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/go-chi/chi/v5 v5.2.1
|
github.com/go-chi/chi/v5 v5.2.1
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/gofrs/uuid/v5 v5.3.1
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
||||||
|
github.com/gofrs/uuid/v5 v5.3.2
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||||
github.com/libdns/alidns v1.0.3
|
github.com/libdns/alidns v1.0.3
|
||||||
github.com/libdns/cloudflare v0.1.1
|
github.com/libdns/cloudflare v0.1.1
|
||||||
@@ -23,24 +25,25 @@ require (
|
|||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gomobile v0.1.4
|
github.com/sagernet/gomobile v0.1.4
|
||||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||||
github.com/sagernet/quic-go v0.49.0-beta.1
|
github.com/sagernet/quic-go v0.49.0-beta.1
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.6.4-0.20250316065121-38f666955109
|
github.com/sagernet/sing v0.6.7-0.20250409030945-77e2a1bb577c
|
||||||
github.com/sagernet/sing-mux v0.3.1
|
github.com/sagernet/sing-mux v0.3.1
|
||||||
github.com/sagernet/sing-quic v0.4.1-beta.1
|
github.com/sagernet/sing-quic v0.4.1
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056
|
||||||
github.com/sagernet/sing-tun v0.6.1
|
github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a
|
||||||
github.com/sagernet/sing-vmess v0.2.0
|
github.com/sagernet/sing-vmess v0.2.0
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.0
|
github.com/sagernet/tailscale v1.80.3-mod.2
|
||||||
github.com/sagernet/utls v1.6.7
|
github.com/sagernet/utls v1.6.7
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.5
|
github.com/sagernet/wireguard-go v0.0.1-beta.5
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/vishvananda/netns v0.0.4
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.33.0
|
golang.org/x/crypto v0.33.0
|
||||||
@@ -65,7 +68,6 @@ require (
|
|||||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||||
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/coder/websocket v1.8.12 // 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/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
|
||||||
@@ -79,7 +81,6 @@ require (
|
|||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
@@ -122,7 +123,6 @@ 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/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
|||||||
28
go.sum
28
go.sum
@@ -8,8 +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/anytls/sing-anytls v0.0.6 h1:UatIjl/OvzWQGXQ1I2bAIkabL9WtihW0fA7G+DXGBUg=
|
github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6Vymc=
|
||||||
github.com/anytls/sing-anytls v0.0.6/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
||||||
@@ -66,8 +66,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
|||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||||
github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0=
|
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
||||||
github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
@@ -167,8 +167,8 @@ github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQ
|
|||||||
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.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY=
|
github.com/sagernet/gomobile v0.1.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY=
|
||||||
github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
||||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs=
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=
|
||||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw=
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
|
||||||
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=
|
||||||
@@ -178,26 +178,26 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W
|
|||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||||
github.com/sagernet/sing v0.6.4-0.20250316065121-38f666955109 h1:clwEzQu0oiapGllEDtbGQjcmQaIAt8DH3EeOHAWyiKs=
|
github.com/sagernet/sing v0.6.7-0.20250409030945-77e2a1bb577c h1:Zi+WR7f9SQ96yNHmyxj42BtaVb3kTouQ8bQLBHReTSI=
|
||||||
github.com/sagernet/sing v0.6.4-0.20250316065121-38f666955109/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.7-0.20250409030945-77e2a1bb577c/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=
|
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=
|
||||||
github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78=
|
github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78=
|
||||||
github.com/sagernet/sing-quic v0.4.1-beta.1 h1:V2VfMckT3EQR3ZdfSzJgZZDsvfZZH42QAZpnOnHKa0s=
|
github.com/sagernet/sing-quic v0.4.1 h1:pxlMa4efZu/M07RgGagNNDDyl6ZUwpmNUjRTpgHOWK4=
|
||||||
github.com/sagernet/sing-quic v0.4.1-beta.1/go.mod h1:c+CytOEyeN20KCTFIP8YQUkNDVFLSzjrEPqP7Hlnxys=
|
github.com/sagernet/sing-quic v0.4.1/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQAHhSXqAfxAw1wDG/QWbdpGH5Na3k8qUynqWnEA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQAHhSXqAfxAw1wDG/QWbdpGH5Na3k8qUynqWnEA=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056/go.mod h1:HyacBPIFiKihJQR8LQp56FM4hBtd/7MZXnRxxQIOPsc=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056/go.mod h1:HyacBPIFiKihJQR8LQp56FM4hBtd/7MZXnRxxQIOPsc=
|
||||||
github.com/sagernet/sing-tun v0.6.1 h1:4l0+gnEKcGjlWfUVTD+W0BRApqIny/lU2ZliurE+VMo=
|
github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a h1:2aLxZFD2HPCLrnFGpH+KBuPqMOk0cuaDE2dgEvANuMk=
|
||||||
github.com/sagernet/sing-tun v0.6.1/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI=
|
github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI=
|
||||||
github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.0 h1:oHIdivbR/yxoiA9d3a2rRlhYn2shY9XVF35Rr8jW508=
|
github.com/sagernet/tailscale v1.80.3-mod.2 h1:hT0CI74q727EuCcgQ+T4pvon8V0aoi4vTAxah7GsNMQ=
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.0/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
github.com/sagernet/tailscale v1.80.3-mod.2/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
||||||
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
||||||
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
|
github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport"
|
"github.com/sagernet/sing-box/dns/transport"
|
||||||
@@ -33,6 +34,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/tun"
|
"github.com/sagernet/sing-box/protocol/tun"
|
||||||
"github.com/sagernet/sing-box/protocol/vless"
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
"github.com/sagernet/sing-box/protocol/vmess"
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
|
"github.com/sagernet/sing-box/service/resolved"
|
||||||
|
"github.com/sagernet/sing-box/service/ssmapi"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,6 +113,7 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
|||||||
hosts.RegisterTransport(registry)
|
hosts.RegisterTransport(registry)
|
||||||
local.RegisterTransport(registry)
|
local.RegisterTransport(registry)
|
||||||
fakeip.RegisterTransport(registry)
|
fakeip.RegisterTransport(registry)
|
||||||
|
resolved.RegisterTransport(registry)
|
||||||
|
|
||||||
registerQUICTransports(registry)
|
registerQUICTransports(registry)
|
||||||
registerDHCPTransport(registry)
|
registerDHCPTransport(registry)
|
||||||
@@ -118,6 +122,17 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
|||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ServiceRegistry() *service.Registry {
|
||||||
|
registry := service.NewRegistry()
|
||||||
|
|
||||||
|
resolved.RegisterService(registry)
|
||||||
|
ssmapi.RegisterService(registry)
|
||||||
|
|
||||||
|
registerDERPService(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
func registerStubForRemovedInbounds(registry *inbound.Registry) {
|
func registerStubForRemovedInbounds(registry *inbound.Registry) {
|
||||||
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
|
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
|
||||||
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")
|
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ package include
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/protocol/tailscale"
|
"github.com/sagernet/sing-box/protocol/tailscale"
|
||||||
|
"github.com/sagernet/sing-box/service/derp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerTailscaleEndpoint(registry *endpoint.Registry) {
|
func registerTailscaleEndpoint(registry *endpoint.Registry) {
|
||||||
@@ -15,3 +17,8 @@ func registerTailscaleEndpoint(registry *endpoint.Registry) {
|
|||||||
func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
||||||
tailscale.RegistryTransport(registry)
|
tailscale.RegistryTransport(registry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerDERPService(registry *service.Registry) {
|
||||||
|
derp.Register(registry)
|
||||||
|
derp.RegisterSTUN(registry)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -25,3 +26,12 @@ func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
|||||||
return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`)
|
return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerDERPService(registry *service.Registry) {
|
||||||
|
service.Register[option.DERPServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) {
|
||||||
|
return nil, E.New(`DERP is not included in this build, rebuild with -tags with_tailscale`)
|
||||||
|
})
|
||||||
|
service.Register[option.DERPSTUNServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPSTUNServiceOptions) (adapter.Service, error) {
|
||||||
|
return nil, E.New(`STUN (DERP) is not included in this build, rebuild with -tags with_tailscale`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ type LegacyDNSFakeIPOptions struct {
|
|||||||
type DNSTransportOptionsRegistry interface {
|
type DNSTransportOptionsRegistry interface {
|
||||||
CreateOptions(transportType string) (any, bool)
|
CreateOptions(transportType string) (any, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DNSServerOptions struct {
|
type _DNSServerOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
@@ -191,34 +190,24 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
serverType = C.DNSTypeUDP
|
serverType = C.DNSTypeUDP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var remoteOptions RemoteDNSServerOptions
|
remoteOptions := RemoteDNSServerOptions{
|
||||||
if options.Detour == "" {
|
LocalDNSServerOptions: LocalDNSServerOptions{
|
||||||
remoteOptions = RemoteDNSServerOptions{
|
DialerOptions: DialerOptions{
|
||||||
LocalDNSServerOptions: LocalDNSServerOptions{
|
Detour: options.Detour,
|
||||||
LegacyStrategy: options.Strategy,
|
DomainResolver: &DomainResolveOptions{
|
||||||
LegacyDefaultDialer: options.Detour == "",
|
Server: options.AddressResolver,
|
||||||
LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
Strategy: options.AddressStrategy,
|
||||||
},
|
|
||||||
LegacyAddressResolver: options.AddressResolver,
|
|
||||||
LegacyAddressStrategy: options.AddressStrategy,
|
|
||||||
LegacyAddressFallbackDelay: options.AddressFallbackDelay,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
remoteOptions = RemoteDNSServerOptions{
|
|
||||||
LocalDNSServerOptions: LocalDNSServerOptions{
|
|
||||||
DialerOptions: DialerOptions{
|
|
||||||
Detour: options.Detour,
|
|
||||||
DomainResolver: &DomainResolveOptions{
|
|
||||||
Server: options.AddressResolver,
|
|
||||||
Strategy: options.AddressStrategy,
|
|
||||||
},
|
|
||||||
FallbackDelay: options.AddressFallbackDelay,
|
|
||||||
},
|
},
|
||||||
LegacyStrategy: options.Strategy,
|
FallbackDelay: options.AddressFallbackDelay,
|
||||||
LegacyDefaultDialer: options.Detour == "",
|
|
||||||
LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
|
||||||
},
|
},
|
||||||
}
|
Legacy: true,
|
||||||
|
LegacyStrategy: options.Strategy,
|
||||||
|
LegacyDefaultDialer: options.Detour == "",
|
||||||
|
LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||||
|
},
|
||||||
|
LegacyAddressResolver: options.AddressResolver,
|
||||||
|
LegacyAddressStrategy: options.AddressStrategy,
|
||||||
|
LegacyAddressFallbackDelay: options.AddressFallbackDelay,
|
||||||
}
|
}
|
||||||
switch serverType {
|
switch serverType {
|
||||||
case C.DNSTypeLocal:
|
case C.DNSTypeLocal:
|
||||||
@@ -362,6 +351,7 @@ type HostsDNSServerOptions struct {
|
|||||||
|
|
||||||
type LocalDNSServerOptions struct {
|
type LocalDNSServerOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
|
Legacy bool `json:"-"`
|
||||||
LegacyStrategy DomainStrategy `json:"-"`
|
LegacyStrategy DomainStrategy `json:"-"`
|
||||||
LegacyDefaultDialer bool `json:"-"`
|
LegacyDefaultDialer bool `json:"-"`
|
||||||
LegacyClientSubnet netip.Prefix `json:"-"`
|
LegacyClientSubnet netip.Prefix `json:"-"`
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ func (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) err
|
|||||||
}
|
}
|
||||||
registry := service.FromContext[EndpointOptionsRegistry](ctx)
|
registry := service.FromContext[EndpointOptionsRegistry](ctx)
|
||||||
if registry == nil {
|
if registry == nil {
|
||||||
return E.New("missing Endpoint fields registry in context")
|
return E.New("missing endpoint fields registry in context")
|
||||||
}
|
}
|
||||||
options, loaded := registry.CreateOptions(h.Type)
|
options, loaded := registry.CreateOptions(h.Type)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return E.New("unknown inbound type: ", h.Type)
|
return E.New("unknown endpoint type: ", h.Type)
|
||||||
}
|
}
|
||||||
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Endpoint)(h), options)
|
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Endpoint)(h), options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro
|
|||||||
}
|
}
|
||||||
registry := service.FromContext[InboundOptionsRegistry](ctx)
|
registry := service.FromContext[InboundOptionsRegistry](ctx)
|
||||||
if registry == nil {
|
if registry == nil {
|
||||||
return E.New("missing Inbound fields registry in context")
|
return E.New("missing inbound fields registry in context")
|
||||||
}
|
}
|
||||||
options, loaded := registry.CreateOptions(h.Type)
|
options, loaded := registry.CreateOptions(h.Type)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
@@ -68,6 +68,7 @@ type ListenOptions struct {
|
|||||||
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
||||||
UDPFragmentDefault bool `json:"-"`
|
UDPFragmentDefault bool `json:"-"`
|
||||||
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
||||||
|
NetNs string `json:"netns,omitempty"`
|
||||||
|
|
||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
|
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ type _Options struct {
|
|||||||
Inbounds []Inbound `json:"inbounds,omitempty"`
|
Inbounds []Inbound `json:"inbounds,omitempty"`
|
||||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||||
Route *RouteOptions `json:"route,omitempty"`
|
Route *RouteOptions `json:"route,omitempty"`
|
||||||
|
Services []Service `json:"services,omitempty"`
|
||||||
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.RawMessage = content
|
o.RawMessage = content
|
||||||
return nil
|
return checkOptions(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogOptions struct {
|
type LogOptions struct {
|
||||||
@@ -43,3 +45,52 @@ type LogOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StubOptions struct{}
|
type StubOptions struct{}
|
||||||
|
|
||||||
|
func checkOptions(options *Options) error {
|
||||||
|
err := checkInbounds(options.Inbounds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = checkOutbounds(options.Outbounds, options.Endpoints)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkInbounds(inbounds []Inbound) error {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
if inbound.Tag == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if seen[inbound.Tag] {
|
||||||
|
return E.New("duplicate inbound tag: ", inbound.Tag)
|
||||||
|
}
|
||||||
|
seen[inbound.Tag] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkOutbounds(outbounds []Outbound, endpoints []Endpoint) error {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
if outbound.Tag == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if seen[outbound.Tag] {
|
||||||
|
return E.New("duplicate outbound/endpoint tag: ", outbound.Tag)
|
||||||
|
}
|
||||||
|
seen[outbound.Tag] = true
|
||||||
|
}
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
if endpoint.Tag == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if seen[endpoint.Tag] {
|
||||||
|
return E.New("duplicate outbound/endpoint tag: ", endpoint.Tag)
|
||||||
|
}
|
||||||
|
seen[endpoint.Tag] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ type DialerOptions struct {
|
|||||||
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
|
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
|
||||||
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
||||||
UDPFragmentDefault bool `json:"-"`
|
UDPFragmentDefault bool `json:"-"`
|
||||||
|
NetNs string `json:"netns,omitempty"`
|
||||||
DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"`
|
DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"`
|
||||||
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
|
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
|
|||||||
49
option/resolved.go
Normal file
49
option/resolved.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _ResolvedServiceOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolvedServiceOptions _ResolvedServiceOptions
|
||||||
|
|
||||||
|
func (r ResolvedServiceOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
|
||||||
|
if r.Listen != nil && netip.Addr(*r.Listen) == (netip.AddrFrom4([4]byte{127, 0, 0, 53})) {
|
||||||
|
r.Listen = nil
|
||||||
|
}
|
||||||
|
if r.ListenPort == 53 {
|
||||||
|
r.ListenPort = 0
|
||||||
|
}
|
||||||
|
return json.MarshalContext(ctx, (*_ResolvedServiceOptions)(&r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResolvedServiceOptions) UnmarshalJSONContext(ctx context.Context, bytes []byte) error {
|
||||||
|
err := json.UnmarshalContextDisallowUnknownFields(ctx, bytes, (*_ResolvedServiceOptions)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.Listen == nil {
|
||||||
|
r.Listen = (*badoption.Addr)(common.Ptr(netip.AddrFrom4([4]byte{127, 0, 0, 53})))
|
||||||
|
}
|
||||||
|
if r.ListenPort == 0 {
|
||||||
|
r.ListenPort = 53
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolvedDNSServerOptions struct {
|
||||||
|
Service string `json:"Service"`
|
||||||
|
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
||||||
|
// NDots int `json:"ndots,omitempty"`
|
||||||
|
// Timeout badoption.Duration `json:"timeout,omitempty"`
|
||||||
|
// Attempts int `json:"attempts,omitempty"`
|
||||||
|
// Rotate bool `json:"rotate,omitempty"`
|
||||||
|
}
|
||||||
@@ -125,10 +125,9 @@ func (r *DefaultRule) UnmarshalJSON(data []byte) error {
|
|||||||
return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction)
|
return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultRule) IsValid() bool {
|
func (r DefaultRule) IsValid() bool {
|
||||||
var defaultValue DefaultRule
|
var defaultValue DefaultRule
|
||||||
defaultValue.Invert = r.Invert
|
defaultValue.Invert = r.Invert
|
||||||
defaultValue.Action = r.Action
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ func (r *DefaultDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte)
|
|||||||
func (r DefaultDNSRule) IsValid() bool {
|
func (r DefaultDNSRule) IsValid() bool {
|
||||||
var defaultValue DefaultDNSRule
|
var defaultValue DefaultDNSRule
|
||||||
defaultValue.Invert = r.Invert
|
defaultValue.Invert = r.Invert
|
||||||
defaultValue.DNSRuleAction = r.DNSRuleAction
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
47
option/service.go
Normal file
47
option/service.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceOptionsRegistry interface {
|
||||||
|
CreateOptions(serviceType string) (any, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type _Service struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Tag string `json:"tag,omitempty"`
|
||||||
|
Options any `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service _Service
|
||||||
|
|
||||||
|
func (h *Service) MarshalJSONContext(ctx context.Context) ([]byte, error) {
|
||||||
|
return badjson.MarshallObjectsContext(ctx, (*_Service)(h), h.Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Service) UnmarshalJSONContext(ctx context.Context, content []byte) error {
|
||||||
|
err := json.UnmarshalContext(ctx, content, (*_Service)(h))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
registry := service.FromContext[ServiceOptionsRegistry](ctx)
|
||||||
|
if registry == nil {
|
||||||
|
return E.New("missing service fields registry in context")
|
||||||
|
}
|
||||||
|
options, loaded := registry.CreateOptions(h.Type)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("unknown inbound type: ", h.Type)
|
||||||
|
}
|
||||||
|
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Service)(h), options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.Options = options
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ type ShadowsocksInboundOptions struct {
|
|||||||
Users []ShadowsocksUser `json:"users,omitempty"`
|
Users []ShadowsocksUser `json:"users,omitempty"`
|
||||||
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
||||||
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
|
Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
|
||||||
|
Managed bool `json:"managed,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShadowsocksUser struct {
|
type ShadowsocksUser struct {
|
||||||
|
|||||||
@@ -4,17 +4,18 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowTLSInboundOptions struct {
|
type ShadowTLSInboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Version int `json:"version,omitempty"`
|
Version int `json:"version,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
Users []ShadowTLSUser `json:"users,omitempty"`
|
Users []ShadowTLSUser `json:"users,omitempty"`
|
||||||
Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"`
|
Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"`
|
||||||
HandshakeForServerName map[string]ShadowTLSHandshakeOptions `json:"handshake_for_server_name,omitempty"`
|
HandshakeForServerName *badjson.TypedMap[string, ShadowTLSHandshakeOptions] `json:"handshake_for_server_name,omitempty"`
|
||||||
StrictMode bool `json:"strict_mode,omitempty"`
|
StrictMode bool `json:"strict_mode,omitempty"`
|
||||||
WildcardSNI WildcardSNI `json:"wildcard_sni,omitempty"`
|
WildcardSNI WildcardSNI `json:"wildcard_sni,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WildcardSNI int
|
type WildcardSNI int
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ import (
|
|||||||
|
|
||||||
type SocksInboundOptions struct {
|
type SocksInboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Users []auth.User `json:"users,omitempty"`
|
Users []auth.User `json:"users,omitempty"`
|
||||||
|
DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPMixedInboundOptions struct {
|
type HTTPMixedInboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
Users []auth.User `json:"users,omitempty"`
|
Users []auth.User `json:"users,omitempty"`
|
||||||
SetSystemProxy bool `json:"set_system_proxy,omitempty"`
|
DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"`
|
||||||
|
SetSystemProxy bool `json:"set_system_proxy,omitempty"`
|
||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user