Compare commits

..

75 Commits

Author SHA1 Message Date
世界
29bdf47e44 Add MITM 2025-03-26 23:02:13 +08:00
世界
9656bc0938 Fix tproxy inbound with netns 2025-03-26 18:23:42 +08:00
世界
4fe48c1a80 documentation: Bump version 2025-03-26 13:27:19 +08:00
Rambling2076
f5ac12870c Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-03-26 13:27:05 +08:00
世界
5de57fad5d Fail when default DNS server not found 2025-03-26 13:21:09 +08:00
世界
3c0fafb0a6 Update gVisor to 20250319.0 2025-03-26 13:21:09 +08:00
世界
7829e2677a release: Do not build tailscale on iOS and tvOS 2025-03-26 13:21:08 +08:00
世界
24f918223f Explicitly reject detour to empty direct outbounds 2025-03-26 13:21:08 +08:00
世界
3041ee9314 Ignore UDP offload error 2025-03-26 13:21:08 +08:00
世界
01d1a7f780 Add netns support 2025-03-26 13:21:08 +08:00
世界
349aecec92 Add wildcard name support for predefined records 2025-03-26 13:21:07 +08:00
世界
95cb46d943 Remove map usage in options 2025-03-26 13:21:07 +08:00
世界
2486fd39e4 Fix unhandled DNS loop 2025-03-26 13:21:07 +08:00
世界
d4b20ec36e Add wildcard-sni support for shadow-tls inbound 2025-03-26 13:21:07 +08:00
世界
f04c976acb Fix Tailscale DNS 2025-03-26 13:21:06 +08:00
k9982874
2994c59c58 Add ntp protocol sniffing 2025-03-26 13:21:06 +08:00
世界
2609a44115 option: Fix marshal legacy DNS options 2025-03-26 13:21:06 +08:00
世界
1c8d12b721 Make domain_resolver optional when only one DNS server is configured 2025-03-26 13:21:05 +08:00
世界
1ac69295c4 Fix DNS lookup context pollution 2025-03-26 13:21:05 +08:00
世界
57164832ad Fix http3 DNS server connecting to wrong address 2025-03-26 13:21:05 +08:00
Restia-Ashbell
130dad1c54 documentation: Fix typo 2025-03-26 13:21:05 +08:00
anytls
8fea9c7ff2 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-03-26 13:21:04 +08:00
k9982874
ca44711cc9 Fix hosts DNS server 2025-03-26 13:21:04 +08:00
世界
9195d1deec Fix UDP DNS server crash 2025-03-26 13:21:04 +08:00
世界
2fccc5e806 documentation: Fix missing ip_accept_any DNS rule option 2025-03-26 13:21:03 +08:00
世界
157eb54c60 Fix anytls dialer usage 2025-03-26 13:21:03 +08:00
世界
1069b7d712 Move predefined DNS server to rule action 2025-03-26 13:21:03 +08:00
世界
d52322112e Fix domain resolver on direct outbound 2025-03-26 13:21:02 +08:00
Zephyruso
59cbe0876e Fix missing AnyTLS display name 2025-03-26 13:21:02 +08:00
anytls
864003dfd5 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-03-26 13:21:02 +08:00
Estel
2cd746bb95 documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-03-26 13:21:02 +08:00
TargetLocked
e3d5afa626 Fix parsing legacy DNS options 2025-03-26 13:21:02 +08:00
世界
e8d9560aa1 Fix DNS fallback 2025-03-26 13:21:01 +08:00
世界
d4fb0321c6 documentation: Fix missing hosts DNS server 2025-03-26 13:21:01 +08:00
anytls
e8c11439d9 Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-03-26 13:21:01 +08:00
ReleTor
342112b600 documentation: Minor fixes 2025-03-26 13:21:00 +08:00
libtry486
cf0bee8fe7 documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-03-26 13:21:00 +08:00
Alireza Ahmadi
cebf8b3f3e Fix Outbound deadlock 2025-03-26 13:21:00 +08:00
世界
3a5267b448 documentation: Fix AnyTLS doc 2025-03-26 13:21:00 +08:00
anytls
3e90cd4a8f Add AnyTLS protocol 2025-03-26 13:20:59 +08:00
世界
42c769d375 Migrate to stdlib ECH support 2025-03-26 13:20:59 +08:00
世界
c8fea4d6fe Add fallback local DNS server for iOS 2025-03-26 13:20:58 +08:00
世界
495bdee0aa Get darwin local DNS server from libresolv 2025-03-26 13:20:58 +08:00
世界
20f245c628 Improve resolve action 2025-03-26 13:20:58 +08:00
世界
8cdb4b7c85 Fix toolchain version 2025-03-26 13:20:58 +08:00
世界
6b8307cf5d Add back port hopping to hysteria 1 2025-03-26 13:20:58 +08:00
世界
b6d4fd4c39 Update dependencies 2025-03-26 13:20:57 +08:00
xchacha20-poly1305
0a052045bf Remove single quotes of raw Moziila certs 2025-03-26 13:20:57 +08:00
世界
df588b84bc Add Tailscale endpoint 2025-03-26 13:20:57 +08:00
世界
9c7e054d54 Build legacy binaries with latest Go 2025-03-26 13:20:56 +08:00
世界
28e01aed6d documentation: Remove outdated icons 2025-03-26 13:20:56 +08:00
世界
d302b018ff documentation: Certificate store 2025-03-26 13:20:55 +08:00
世界
925808dfd0 documentation: TLS fragment 2025-03-26 13:20:55 +08:00
世界
98ee3f467c documentation: Outbound domain resolver 2025-03-26 13:20:55 +08:00
世界
14003c1981 documentation: Refactor DNS 2025-03-26 13:20:55 +08:00
世界
323d5887d6 Add certificate store 2025-03-26 13:20:55 +08:00
世界
766e3a1e8d Add TLS fragment support 2025-03-26 13:20:33 +08:00
世界
291a4f1854 refactor: Outbound domain resolver 2025-03-26 13:19:46 +08:00
世界
dee381c45a refactor: DNS 2025-03-26 13:19:20 +08:00
世界
95e397f898 Bump version 2025-03-26 13:14:08 +08:00
世界
ad1f3216fb Fix udpnat2 handler again 2025-03-26 13:14:08 +08:00
世界
5b5e4903e2 release: Fix workflow 2025-03-26 13:14:08 +08:00
世界
f56131f38e Make linter happy 2025-03-24 20:38:42 +08:00
世界
273a11d550 Fix crash on udpnat2 handler 2025-03-24 18:14:32 +08:00
世界
ae8ce75e41 Fix websocket crash 2025-03-24 17:44:14 +08:00
世界
d6d94b689f release: Replace goreleaser build with scripts 2025-03-24 13:48:37 +08:00
世界
30d785f1ee release: Use fake goreleaser key 2025-03-21 22:25:51 +08:00
世界
db5ec3cdfc Fix connectionCopyEarly 2025-03-21 10:51:16 +08:00
世界
9aca54d039 Fix socks5 UDP 2025-03-16 14:46:44 +08:00
世界
d55d5009c2 Fix processing multiple sniffs 2025-03-16 09:21:54 +08:00
世界
4f3ee61104 Fix copy early conn 2025-03-15 08:09:04 +08:00
世界
96eb98c00a Fix httpupgrade crash 2025-03-14 17:17:28 +08:00
世界
68ce9577c6 Fix context in v2ray http transports 2025-03-14 17:07:17 +08:00
世界
3ae036e997 Downgrade goreleaser to stable since nfpm fixed 2025-03-13 18:53:19 +08:00
世界
5da2d1d470 release: Fix goreleaser version 2025-03-12 16:15:50 +08:00
97 changed files with 3387 additions and 945 deletions

19
.fpm Normal file
View File

@@ -0,0 +1,19 @@
-s dir
--name sing-box
--category net
--license GPLv3-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
release/config/config.json=/etc/sing-box/config.json
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

View File

@@ -1,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

View File

@@ -50,12 +50,12 @@ jobs:
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
echo "version=${{ inputs.version }}" echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version - name: Calculate version
if: github.event_name != 'workflow_dispatch' if: github.event_name != 'workflow_dispatch'
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
- 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: nightly
install-only: true
- name: Extract signing key
run: |-
mkdir -p $HOME/.gnupg
cat > $HOME/.gnupg/sagernet.key <<EOF
${{ secrets.GPG_KEY }}
EOF
echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Set tag - name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api'
if [ ! '${{ matrix.legacy_go }}' = 'true' ]; then
TAGS="${TAGS},with_ech"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build - name: Build
if: matrix.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}"
- name: Package DEB
if: matrix.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y debsigs
fpm -t deb \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${PKG_NAME}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
fpm -t rpm \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${PKG_NAME}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Package Pacman
if: matrix.pacman != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y libarchive-tools
fpm -t pacman \
-v "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${PKG_NAME}.pkg.tar.zst" \
--architecture ${{ matrix.pacman }} \
dist/sing-box=/usr/bin/sing-box
- name: Archive
run: |
set -xeuo pipefail
cd dist
mkdir -p "${DIR_NAME}"
cp ../LICENSE "${DIR_NAME}"
if [ '${{ matrix.os }}' = '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'
@@ -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
@@ -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"
@@ -523,10 +565,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 +589,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: 2.5.1
install-only: true
- name: Cache ghr - name: Cache ghr
uses: actions/cache@v4 uses: actions/cache@v4
id: cache-ghr id: cache-ghr
@@ -577,26 +613,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 }}

View File

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

View File

@@ -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
@@ -17,22 +26,155 @@ jobs:
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24
- 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
- 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: 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 "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/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 "${{ needs.calculate_version.outputs.version }}" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/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: latest 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/

View File

@@ -126,8 +126,8 @@ nfpms:
- deb - deb
- rpm - rpm
- archlinux - archlinux
# - apk # - apk
# - ipk # - ipk
priority: extra priority: extra
contents: contents:
- src: release/config/config.json - src: release/config/config.json

View File

@@ -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

View File

@@ -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)
@@ -20,11 +18,6 @@ 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) && \
@@ -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.5
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4 go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.5
docs: docs:
venv/bin/mkdocs serve venv/bin/mkdocs serve

View File

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

View File

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

View File

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

View File

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

13
adapter/mitm.go Normal file
View File

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

43
box.go
View File

@@ -23,6 +23,7 @@ import (
"github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/mitm"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route" "github.com/sagernet/sing-box/route"
@@ -48,6 +49,7 @@ type Box struct {
dnsRouter *dns.Router dnsRouter *dns.Router
connection *route.ConnectionManager connection *route.ConnectionManager
router *route.Router router *route.Router
mitm adapter.MITMEngine //*mitm.Engine
services []adapter.LifecycleService services []adapter.LifecycleService
done chan struct{} done chan struct{}
} }
@@ -143,18 +145,12 @@ func New(options Options) (*Box, error) {
} }
var services []adapter.LifecycleService var services []adapter.LifecycleService
certificateOptions := common.PtrValueOrDefault(options.Certificate) certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), common.PtrValueOrDefault(options.Certificate))
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem || if err != nil {
len(certificateOptions.Certificate) > 0 || return nil, err
len(certificateOptions.CertificatePath) > 0 ||
len(certificateOptions.CertificateDirectoryPath) > 0 {
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
if err != nil {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
} }
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
services = append(services, certificateStore)
routeOptions := common.PtrValueOrDefault(options.Route) routeOptions := common.PtrValueOrDefault(options.Route)
dnsOptions := common.PtrValueOrDefault(options.DNS) dnsOptions := common.PtrValueOrDefault(options.DNS)
@@ -173,7 +169,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize network manager") return nil, E.Cause(err, "initialize network manager")
} }
service.MustRegister[adapter.NetworkManager](ctx, networkManager) service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection")) connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager) service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions) router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router) service.MustRegister[adapter.Router](ctx, router)
@@ -181,8 +177,8 @@ func New(options Options) (*Box, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize router") return nil, E.Cause(err, "initialize router")
} }
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper var timeService *tls.TimeServiceWrapper
ntpOptions := common.PtrValueOrDefault(options.NTP)
if ntpOptions.Enabled { if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper) timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService) service.MustRegister[ntp.TimeService](ctx, timeService)
@@ -345,6 +341,16 @@ func New(options Options) (*Box, error) {
timeService.TimeService = ntpService timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service")) services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
} }
mitmOptions := common.PtrValueOrDefault(options.MITM)
var mitmEngine adapter.MITMEngine
if mitmOptions.Enabled {
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
if err != nil {
return nil, E.Cause(err, "create MITM engine")
}
service.MustRegister[adapter.MITMEngine](ctx, engine)
mitmEngine = engine
}
return &Box{ return &Box{
network: networkManager, network: networkManager,
endpoint: endpointManager, endpoint: endpointManager,
@@ -354,6 +360,7 @@ func New(options Options) (*Box, error) {
dnsRouter: dnsRouter, dnsRouter: dnsRouter,
connection: connectionManager, connection: connectionManager,
router: router, router: router,
mitm: mitmEngine,
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.Logger(), logger: logFactory.Logger(),
@@ -412,11 +419,11 @@ func (s *Box) preStart() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.mitm, s.outbound, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router) err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.mitm)
if err != nil { if err != nil {
return err return err
} }
@@ -440,7 +447,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.mitm, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
@@ -448,7 +455,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.mitm, s.outbound, s.inbound, s.endpoint)
if err != nil { if err != nil {
return err return err
} }
@@ -467,7 +474,7 @@ func (s *Box) Close() error {
close(s.done) close(s.done)
} }
err := common.Close( err := common.Close(
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network, s.inbound, s.outbound, s.endpoint, s.mitm, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
) )
for _, lifecycleService := range s.services { for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error { err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -45,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...)

View File

@@ -5,40 +5,52 @@ 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.ReadTagVersionRev()
if err == nil {
if version.PreReleaseIdentifier == "" {
version.Patch++
}
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")
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,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
@@ -198,6 +200,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,
@@ -214,19 +217,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 +287,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)
} }

View File

@@ -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) {

View File

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

View File

@@ -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()
}

View File

@@ -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
} }

View File

@@ -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
} }

View File

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

58
common/sniff/ntp.go Normal file
View File

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

33
common/sniff/ntp_test.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -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
@@ -383,7 +370,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
ruleIndex = -1 ruleIndex = -1
for { for {
dnsCtx := adapter.OverrideContext(ctx) dnsCtx := adapter.OverrideContext(ctx)
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &options) dnsOptions := options
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &dnsOptions)
if rule != nil { if rule != nil {
switch action := rule.Action().(type) { switch action := rule.Action().(type) {
case *R.RuleActionReject: case *R.RuleActionReject:
@@ -416,10 +404,10 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
return rule.MatchAddressLimit(metadata) return rule.MatchAddressLimit(metadata)
} }
} }
if options.Strategy == C.DomainStrategyAsIS { if dnsOptions.Strategy == C.DomainStrategyAsIS {
options.Strategy = r.defaultDomainStrategy dnsOptions.Strategy = r.defaultDomainStrategy
} }
responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, options, responseCheck) responseAddrs, err = r.client.Lookup(dnsCtx, transport, domain, dnsOptions, responseCheck)
if responseCheck == nil || err == nil { if responseCheck == nil || err == nil {
break break
} }
@@ -461,6 +449,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()
} }
} }

View File

@@ -81,7 +81,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
func (t *Transport) Close() error { func (t *Transport) Close() error {
for _, transport := range t.transports { for _, transport := range t.transports {
transport.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

View File

@@ -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) {

View File

@@ -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"
@@ -91,7 +92,7 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.ServerOptions.Build() serverAddr := options.DNSServerAddressOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 443 serverAddr.Port = 443
} }
@@ -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) {

View File

@@ -40,7 +40,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) {

View File

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

View File

@@ -54,7 +54,7 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options
if len(tlsConfig.NextProtos()) == 0 { if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"doq"}) tlsConfig.SetNextProtos([]string{"doq"})
} }
serverAddr := options.ServerOptions.Build() serverAddr := options.DNSServerAddressOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 853 serverAddr.Port = 853
} }
@@ -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) {

View File

@@ -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"
@@ -35,7 +36,7 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.ServerOptions.Build() serverAddr := options.DNSServerAddressOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 53 serverAddr.Port = 53
} }
@@ -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) {

View File

@@ -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"
@@ -52,7 +53,7 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.ServerOptions.Build() serverAddr := options.DNSServerAddressOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 853 serverAddr.Port = 853
} }
@@ -65,13 +66,21 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
}, nil }, nil
} }
func (t *TLSTransport) Reset() { 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) {

View File

@@ -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"
@@ -42,7 +43,7 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverAddr := options.ServerOptions.Build() serverAddr := options.DNSServerAddressOptions.Build()
if serverAddr.Port == 0 { if serverAddr.Port == 0 {
serverAddr.Port = 53 serverAddr.Port = 53
} }
@@ -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) {

View File

@@ -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,
}) })
} }
} }

View File

@@ -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
} }

View File

@@ -2,10 +2,44 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.12.0-alpha.14 #### 1.12.0-alpha.20
* Fixes and improvements * Fixes and improvements
#### 1.12.0-alpha.19
* Update gVisor to 20250319.0
* Fixes and improvements
#### 1.12.0-alpha.18
* Add wildcard SNI support for ShadowTLS inbound **1**
* Fixes and improvements
**1**:
See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).
#### 1.12.0-alpha.17
* Add NTP sniffer **1**
* Fixes and improvements
**1**:
See [Protocol Sniff](/configuration/route/sniff/).
#### 1.12.0-alpha.16
* Update `domain_resolver` behavior **1**
* Fixes and improvements
**1**:
`route.default_domain_resolver` or `outbound.domain_resolver` is now optional when only one DNS server is configured.
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
### 1.11.5 ### 1.11.5
* Fixes and improvements * Fixes and improvements

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
@@ -106,6 +118,10 @@ Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
`outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0. `outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0.
!!! info ""
`domain_resolver` or `route.default_domain_resolver` is optional when only one DNS server is configured.
Set domain resolver to use for resolving domain names. Set domain resolver to use for resolving domain names.
This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field. This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field.

View File

@@ -5,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
@@ -105,6 +117,10 @@ icon: material/new-box
`outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项。 `outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项。
!!! info ""
当只有一个 DNS 服务器已配置时,`domain_resolver``route.default_domain_resolver` 是可选的。
用于设置解析域名的域名解析器。 用于设置解析域名的域名解析器。
此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。 此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。

View File

@@ -1,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.

View File

@@ -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
如果设置,连接将被转发到指定的入站。 如果设置,连接将被转发到指定的入站。

View File

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

View File

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

View File

@@ -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) {

37
go.mod
View File

@@ -23,28 +23,29 @@ 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.20250309232452-1c3b777fe509 github.com/sagernet/sing v0.6.6-0.20250326051824-d39c2c2fddfa
github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.1-beta.1 github.com/sagernet/sing-quic v0.4.1-beta.1
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.0 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.2-0.20250319123703-35b5747b44ec
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.79.0-mod.1 github.com/sagernet/tailscale v1.80.3-mod.0
github.com/sagernet/utls v1.6.7 github.com/sagernet/utls v1.6.7
github.com/sagernet/wireguard-go v0.0.1-beta.5 github.com/sagernet/wireguard-go v0.0.1-beta.5
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/vishvananda/netns v0.0.4
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.33.0 golang.org/x/crypto v0.33.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
golang.org/x/mod v0.23.0 golang.org/x/mod v0.23.0
golang.org/x/net v0.35.0 golang.org/x/net v0.35.0
golang.org/x/sys v0.30.0 golang.org/x/sys v0.30.0
@@ -52,6 +53,7 @@ require (
google.golang.org/grpc v1.70.0 google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.5 google.golang.org/protobuf v1.36.5
howett.net/plist v1.0.1 howett.net/plist v1.0.1
software.sslmate.com/src/go-pkcs12 v0.4.0
) )
//replace github.com/sagernet/sing => ../sing //replace github.com/sagernet/sing => ../sing
@@ -72,9 +74,9 @@ require (
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gaissmai/bart v0.11.1 // indirect github.com/gaissmai/bart v0.11.1 // indirect
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
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
@@ -87,20 +89,19 @@ require (
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.2 // indirect github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify/v2 v2.0.3 // indirect github.com/illarion/gonotify/v2 v2.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v0.2.2 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect github.com/mdlayher/socket v0.5.1 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
@@ -120,21 +121,19 @@ require (
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tcnksm/go-httpstat v0.2.0 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/sync v0.11.0 // indirect
golang.org/x/term v0.29.0 // indirect golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.29.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect

76
go.sum
View File

@@ -40,8 +40,8 @@ github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yez
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
@@ -50,8 +50,8 @@ github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -87,8 +87,8 @@ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQN
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
@@ -102,9 +102,6 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
@@ -124,8 +121,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
@@ -146,7 +143,6 @@ github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -171,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=
@@ -182,8 +178,8 @@ 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.20250309232452-1c3b777fe509 h1:rDWToc7O295Xh/uFSLqg67MVPftzXnICH/EUI4NL/a8= github.com/sagernet/sing v0.6.6-0.20250326051824-d39c2c2fddfa h1:18mz8gmh0/EL3Bk+hB0Xf3tGOO1p/tP1sjjhSDeyUtU=
github.com/sagernet/sing v0.6.4-0.20250309232452-1c3b777fe509/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.6-0.20250326051824-d39c2c2fddfa/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-beta.1 h1:V2VfMckT3EQR3ZdfSzJgZZDsvfZZH42QAZpnOnHKa0s=
@@ -192,16 +188,16 @@ github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegE
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.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQAHhSXqAfxAw1wDG/QWbdpGH5Na3k8qUynqWnEA=
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= 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.2-0.20250319123703-35b5747b44ec h1:9/OYGb9qDmUFIhqd3S+3eni62EKRQR1rSmRH18baA/M=
github.com/sagernet/sing-tun v0.6.1/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-tun v0.6.2-0.20250319123703-35b5747b44ec/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.79.0-mod.1 h1:wIuAH7VqBYJNk0h2+bTyk4F0OlSqHvyLDCBrD3i+XNI= github.com/sagernet/tailscale v1.80.3-mod.0 h1:oHIdivbR/yxoiA9d3a2rRlhYn2shY9XVF35Rr8jW508=
github.com/sagernet/tailscale v1.79.0-mod.1/go.mod h1:RKY5WjYLj3JJ7VO/8ZCw8eAFa4+kWU6A1Ftdk84uB14= github.com/sagernet/tailscale v1.80.3-mod.0/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=
@@ -228,16 +224,14 @@ github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29X
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
@@ -267,17 +261,17 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -291,10 +285,8 @@ golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
@@ -306,11 +298,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -20,12 +20,16 @@ type ID struct {
} }
func ContextWithNewID(ctx context.Context) context.Context { func ContextWithNewID(ctx context.Context) context.Context {
return context.WithValue(ctx, (*idKey)(nil), ID{ return ContextWithID(ctx, ID{
ID: rand.Uint32(), ID: rand.Uint32(),
CreatedAt: time.Now(), CreatedAt: time.Now(),
}) })
} }
func ContextWithID(ctx context.Context, id ID) context.Context {
return context.WithValue(ctx, (*idKey)(nil), id)
}
func IDFromContext(ctx context.Context) (ID, bool) { func IDFromContext(ctx context.Context) (ID, bool) {
id, loaded := ctx.Value((*idKey)(nil)).(ID) id, loaded := ctx.Value((*idKey)(nil)).(ID)
return id, loaded return id, loaded

View File

@@ -10,6 +10,10 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
const (
DefaultTimeFormat = "-0700 2006-01-02 15:04:05"
)
type Options struct { type Options struct {
Context context.Context Context context.Context
Options option.LogOptions Options option.LogOptions
@@ -47,7 +51,7 @@ func New(options Options) (Factory, error) {
DisableColors: logOptions.DisableColor || logFilePath != "", DisableColors: logOptions.DisableColor || logFilePath != "",
DisableTimestamp: !logOptions.Timestamp && logFilePath != "", DisableTimestamp: !logOptions.Timestamp && logFilePath != "",
FullTimestamp: logOptions.Timestamp, FullTimestamp: logOptions.Timestamp,
TimestampFormat: "-0700 2006-01-02 15:04:05", TimestampFormat: DefaultTimeFormat,
} }
factory := NewDefaultFactory( factory := NewDefaultFactory(
options.Context, options.Context,

11
mitm/constants.go Normal file
View File

@@ -0,0 +1,11 @@
package mitm
import (
"encoding/base64"
"github.com/sagernet/sing/common"
)
var surgeTinyGif = common.OnceValue(func() []byte {
return common.Must1(base64.StdEncoding.DecodeString("R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA="))
})

811
mitm/engine.go Normal file
View File

@@ -0,0 +1,811 @@
package mitm
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"io"
"math"
"mime"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"unicode"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
sTLS "github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
sHTTP "github.com/sagernet/sing/protocol/http"
"github.com/sagernet/sing/service"
"golang.org/x/net/http2"
)
var _ adapter.MITMEngine = (*Engine)(nil)
type Engine struct {
ctx context.Context
logger logger.ContextLogger
connection adapter.ConnectionManager
certificate adapter.CertificateStore
timeFunc func() time.Time
http2Enabled bool
}
func NewEngine(ctx context.Context, logger logger.ContextLogger, options option.MITMOptions) (*Engine, error) {
engine := &Engine{
ctx: ctx,
logger: logger,
http2Enabled: options.HTTP2Enabled,
}
return engine, nil
}
func (e *Engine) Start(stage adapter.StartStage) error {
switch stage {
case adapter.StartStateInitialize:
e.connection = service.FromContext[adapter.ConnectionManager](e.ctx)
e.certificate = service.FromContext[adapter.CertificateStore](e.ctx)
e.timeFunc = ntp.TimeFuncFromContext(e.ctx)
if e.timeFunc == nil {
e.timeFunc = time.Now
}
}
return nil
}
func (e *Engine) Close() error {
return nil
}
func (e *Engine) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
if e.certificate.TLSDecryptionEnabled() && metadata.ClientHello != nil {
err := e.newTLS(ctx, this, conn, metadata, onClose)
if err != nil {
e.logger.ErrorContext(ctx, err)
} else {
e.logger.DebugContext(ctx, "connection closed")
}
if onClose != nil {
onClose(err)
}
return
} else if metadata.HTTPRequest != nil {
err := e.newHTTP1(ctx, this, conn, nil, metadata)
if err != nil {
e.logger.ErrorContext(ctx, err)
} else {
e.logger.DebugContext(ctx, "connection closed")
}
if onClose != nil {
onClose(err)
}
return
} else {
e.logger.DebugContext(ctx, "HTTP and TLS not detected, skipped")
}
metadata.MITM = nil
e.connection.NewConnection(ctx, this, conn, metadata, onClose)
}
func (e *Engine) newTLS(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
acceptHTTP := len(metadata.ClientHello.SupportedProtos) == 0 || common.Contains(metadata.ClientHello.SupportedProtos, "http/1.1")
acceptH2 := e.http2Enabled && common.Contains(metadata.ClientHello.SupportedProtos, "h2")
if !acceptHTTP && !acceptH2 {
metadata.MITM = nil
e.logger.DebugContext(ctx, "unsupported application protocol: ", strings.Join(metadata.ClientHello.SupportedProtos, ","))
e.connection.NewConnection(ctx, this, conn, metadata, onClose)
return nil
}
var nextProtos []string
if acceptH2 {
nextProtos = append(nextProtos, "h2")
} else if acceptHTTP {
nextProtos = append(nextProtos, "http/1.1")
}
var (
maxVersion uint16
minVersion uint16
)
for _, version := range metadata.ClientHello.SupportedVersions {
maxVersion = common.Max(maxVersion, version)
minVersion = common.Min(minVersion, version)
}
serverName := metadata.ClientHello.ServerName
if serverName == "" && metadata.Destination.IsIP() {
serverName = metadata.Destination.Addr.String()
}
tlsConfig := &tls.Config{
Time: e.timeFunc,
ServerName: serverName,
NextProtos: nextProtos,
MinVersion: minVersion,
MaxVersion: maxVersion,
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return sTLS.GenerateKeyPair(e.certificate.TLSDecryptionCertificate(), e.certificate.TLSDecryptionPrivateKey(), e.timeFunc, serverName)
},
}
tlsConn := tls.Server(conn, tlsConfig)
err := tlsConn.HandshakeContext(ctx)
if err != nil {
return E.Cause(err, "TLS handshake failed for ", metadata.ClientHello.ServerName, ", ", strings.Join(metadata.ClientHello.SupportedProtos, ", "))
}
if tlsConn.ConnectionState().NegotiatedProtocol == "h2" {
return e.newHTTP2(ctx, this, tlsConn, tlsConfig, metadata, onClose)
} else {
return e.newHTTP1(ctx, this, tlsConn, tlsConfig, metadata)
}
}
func (e *Engine) newHTTP1(ctx context.Context, this N.Dialer, conn net.Conn, tlsConfig *tls.Config, metadata adapter.InboundContext) error {
options := metadata.MITM
defer conn.Close()
reader := bufio.NewReader(conn)
request, err := sHTTP.ReadRequest(reader)
if err != nil {
return E.Cause(err, "read HTTP request")
}
rawRequestURL := request.URL
if tlsConfig != nil {
rawRequestURL.Scheme = "https"
} else {
rawRequestURL.Scheme = "http"
}
if rawRequestURL.Host == "" {
rawRequestURL.Host = request.Host
}
requestURL := rawRequestURL.String()
request.RequestURI = ""
var requestMatch bool
var body []byte
if options.Print && request.ContentLength > 0 && request.ContentLength <= 131072 {
body, err = io.ReadAll(request.Body)
if err != nil {
return E.Cause(err, "read HTTP request body")
}
request.Body = io.NopCloser(bytes.NewReader(body))
}
if options.Print {
e.printRequest(ctx, request, body)
}
for i, rule := range options.SurgeURLRewrite {
if !rule.Pattern.MatchString(requestURL) {
continue
}
e.logger.DebugContext(ctx, "match url_rewrite[", i, "] => ", rule.String())
if rule.Reject {
return E.New("request rejected by url_rewrite")
} else if rule.Redirect {
w := new(simpleResponseWriter)
http.Redirect(w, request, rule.Destination.String(), http.StatusFound)
err = w.Build(request).Write(conn)
if err != nil {
return E.Cause(err, "write url_rewrite 302 response")
}
return nil
}
requestMatch = true
request.URL = rule.Destination
newDestination := M.ParseSocksaddrHostPortStr(rule.Destination.Hostname(), rule.Destination.Port())
if newDestination.Port == 0 {
newDestination.Port = metadata.Destination.Port
}
metadata.Destination = newDestination
if tlsConfig != nil {
tlsConfig.ServerName = rule.Destination.Hostname()
}
break
}
for i, rule := range options.SurgeHeaderRewrite {
if rule.Response {
continue
}
if !rule.Pattern.MatchString(requestURL) {
continue
}
requestMatch = true
e.logger.DebugContext(ctx, "match header_rewrite[", i, "] => ", rule.String())
switch {
case rule.Add:
if strings.ToLower(rule.Key) == "host" {
request.Host = rule.Value
continue
}
request.Header.Add(rule.Key, rule.Value)
case rule.Delete:
request.Header.Del(rule.Key)
case rule.Replace:
if request.Header.Get(rule.Key) != "" {
request.Header.Set(rule.Key, rule.Value)
}
case rule.ReplaceRegex:
if value := request.Header.Get(rule.Key); value != "" {
request.Header.Set(rule.Key, rule.Match.ReplaceAllString(value, rule.Value))
}
}
}
for i, rule := range options.SurgeBodyRewrite {
if rule.Response {
continue
}
if !rule.Pattern.MatchString(requestURL) {
continue
}
requestMatch = true
e.logger.DebugContext(ctx, "match body_rewrite[", i, "] => ", rule.String())
if body == nil {
if request.ContentLength <= 0 {
e.logger.WarnContext(ctx, "body replace skipped due to non-fixed content length")
break
} else if request.ContentLength > 131072 {
e.logger.WarnContext(ctx, "body replace skipped due to large content length: ", request.ContentLength)
break
}
body, err = io.ReadAll(request.Body)
if err != nil {
return E.Cause(err, "read HTTP request body")
}
}
for mi := 0; i < len(rule.Match); i++ {
body = rule.Match[mi].ReplaceAll(body, []byte(rule.Replace[i]))
}
request.Body = io.NopCloser(bytes.NewReader(body))
request.ContentLength = int64(len(body))
}
if !requestMatch {
for i, rule := range options.SurgeMapLocal {
if !rule.Pattern.MatchString(requestURL) {
continue
}
requestMatch = true
e.logger.DebugContext(ctx, "match map_local[", i, "] => ", rule.String())
var (
statusCode = http.StatusOK
headers = make(http.Header)
)
if rule.StatusCode > 0 {
statusCode = rule.StatusCode
}
switch {
case rule.File:
resource, err := os.ReadFile(rule.Data)
if err != nil {
return E.Cause(err, "open map local source")
}
mimeType := mime.TypeByExtension(filepath.Ext(rule.Data))
if mimeType == "" {
mimeType = "application/octet-stream"
}
headers.Set("Content-Type", mimeType)
body = resource
case rule.Text:
headers.Set("Content-Type", "text/plain")
body = []byte(rule.Data)
case rule.TinyGif:
headers.Set("Content-Type", "image/gif")
body = surgeTinyGif()
case rule.Base64:
headers.Set("Content-Type", "application/octet-stream")
body = rule.Base64Data
}
response := &http.Response{
StatusCode: statusCode,
Status: http.StatusText(statusCode),
Proto: request.Proto,
ProtoMajor: request.ProtoMajor,
ProtoMinor: request.ProtoMinor,
Header: headers,
Body: io.NopCloser(bytes.NewReader(body)),
}
err = response.Write(conn)
if err != nil {
return E.Cause(err, "write map local response")
}
return nil
}
}
ctx = adapter.WithContext(ctx, &metadata)
var innerErr atomic.TypedValue[error]
httpClient := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
if len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() {
return dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
return this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
}
},
TLSClientConfig: tlsConfig,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
defer httpClient.CloseIdleConnections()
requestCtx, cancel := context.WithCancel(ctx)
defer cancel()
response, err := httpClient.Do(request.WithContext(requestCtx))
if err != nil {
cancel()
return E.Errors(innerErr.Load(), err)
}
var responseMatch bool
var responseBody []byte
if options.Print && response.ContentLength > 0 && response.ContentLength <= 131072 {
responseBody, err = io.ReadAll(response.Body)
if err != nil {
return E.Cause(err, "read HTTP response body")
}
response.Body = io.NopCloser(bytes.NewReader(responseBody))
}
if options.Print {
e.printResponse(ctx, request, response, responseBody)
}
for i, rule := range options.SurgeHeaderRewrite {
if !rule.Response {
continue
}
if !rule.Pattern.MatchString(requestURL) {
continue
}
responseMatch = true
e.logger.DebugContext(ctx, "match header_rewrite[", i, "] => ", rule.String())
switch {
case rule.Add:
response.Header.Add(rule.Key, rule.Value)
case rule.Delete:
response.Header.Del(rule.Key)
case rule.Replace:
if response.Header.Get(rule.Key) != "" {
response.Header.Set(rule.Key, rule.Value)
}
case rule.ReplaceRegex:
if value := response.Header.Get(rule.Key); value != "" {
response.Header.Set(rule.Key, rule.Match.ReplaceAllString(value, rule.Value))
}
}
}
for i, rule := range options.SurgeBodyRewrite {
if !rule.Response {
continue
}
if !rule.Pattern.MatchString(requestURL) {
continue
}
responseMatch = true
e.logger.DebugContext(ctx, "match body_rewrite[", i, "] => ", rule.String())
if responseBody == nil {
if response.ContentLength <= 0 {
e.logger.WarnContext(ctx, "body replace skipped due to non-fixed content length")
break
} else if response.ContentLength > 131072 {
e.logger.WarnContext(ctx, "body replace skipped due to large content length: ", request.ContentLength)
break
}
responseBody, err = io.ReadAll(response.Body)
if err != nil {
return E.Cause(err, "read HTTP request body")
}
}
for mi := 0; i < len(rule.Match); i++ {
responseBody = rule.Match[mi].ReplaceAll(responseBody, []byte(rule.Replace[i]))
}
response.Body = io.NopCloser(bytes.NewReader(responseBody))
response.ContentLength = int64(len(responseBody))
}
if !options.Print && !requestMatch && !responseMatch {
e.logger.WarnContext(ctx, "request not modified")
}
err = response.Write(conn)
if err != nil {
return E.Errors(E.Cause(err, "write HTTP response"), innerErr.Load())
} else if innerErr.Load() != nil {
return E.Cause(innerErr.Load(), "write HTTP response")
}
return nil
}
func (e *Engine) newHTTP2(ctx context.Context, this N.Dialer, conn net.Conn, tlsConfig *tls.Config, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
httpTransport := &http.Transport{
ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
ctx = adapter.WithContext(ctx, &metadata)
if len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() {
return dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
return this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
}
},
TLSClientConfig: tlsConfig,
}
err := http2.ConfigureTransport(httpTransport)
if err != nil {
return E.Cause(err, "configure HTTP/2 transport")
}
handler := &engineHandler{
Engine: e,
conn: conn,
tlsConfig: tlsConfig,
dialer: this,
metadata: metadata,
httpClient: &http.Client{
Transport: httpTransport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
},
onClose: onClose,
}
http2Server := &http2.Server{
MaxReadFrameSize: math.MaxUint32,
}
http2Server.ServeConn(conn, &http2.ServeConnOpts{
Context: ctx,
Handler: handler,
})
return nil
}
type engineHandler struct {
*Engine
conn net.Conn
tlsConfig *tls.Config
dialer N.Dialer
metadata adapter.InboundContext
onClose N.CloseHandlerFunc
httpClient *http.Client
}
func (e *engineHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
err := e.serveHTTP(request.Context(), writer, request)
if err != nil {
if E.IsClosedOrCanceled(err) {
e.logger.DebugContext(request.Context(), E.Cause(err, "connection closed"))
} else {
e.logger.ErrorContext(request.Context(), err)
}
}
}
func (e *engineHandler) serveHTTP(ctx context.Context, writer http.ResponseWriter, request *http.Request) error {
options := e.metadata.MITM
rawRequestURL := request.URL
rawRequestURL.Scheme = "https"
if rawRequestURL.Host == "" {
rawRequestURL.Host = request.Host
}
requestURL := rawRequestURL.String()
request.RequestURI = ""
var requestMatch bool
var (
body []byte
err error
)
if options.Print && request.ContentLength > 0 && request.ContentLength <= 131072 {
body, err = io.ReadAll(request.Body)
if err != nil {
return E.Cause(err, "read HTTP request body")
}
request.Body.Close()
request.Body = io.NopCloser(bytes.NewReader(body))
}
if options.Print {
e.printRequest(ctx, request, body)
}
for i, rule := range options.SurgeURLRewrite {
if !rule.Pattern.MatchString(requestURL) {
continue
}
e.logger.DebugContext(ctx, "match url_rewrite[", i, "] => ", rule.String())
if rule.Reject {
return E.New("request rejected by url_rewrite")
} else if rule.Redirect {
http.Redirect(writer, request, rule.Destination.String(), http.StatusFound)
return nil
}
requestMatch = true
request.URL = rule.Destination
newDestination := M.ParseSocksaddrHostPortStr(rule.Destination.Hostname(), rule.Destination.Port())
if newDestination.Port == 0 {
newDestination.Port = e.metadata.Destination.Port
}
e.metadata.Destination = newDestination
e.tlsConfig.ServerName = rule.Destination.Hostname()
break
}
for i, rule := range options.SurgeHeaderRewrite {
if rule.Response {
continue
}
if !rule.Pattern.MatchString(requestURL) {
continue
}
requestMatch = true
e.logger.DebugContext(ctx, "match header_rewrite[", i, "] => ", rule.String())
switch {
case rule.Add:
if strings.ToLower(rule.Key) == "host" {
request.Host = rule.Value
continue
}
request.Header.Add(rule.Key, rule.Value)
case rule.Delete:
request.Header.Del(rule.Key)
case rule.Replace:
if request.Header.Get(rule.Key) != "" {
request.Header.Set(rule.Key, rule.Value)
}
case rule.ReplaceRegex:
if value := request.Header.Get(rule.Key); value != "" {
request.Header.Set(rule.Key, rule.Match.ReplaceAllString(value, rule.Value))
}
}
}
for i, rule := range options.SurgeBodyRewrite {
if rule.Response {
continue
}
if !rule.Pattern.MatchString(requestURL) {
continue
}
requestMatch = true
e.logger.DebugContext(ctx, "match body_rewrite[", i, "] => ", rule.String())
var body []byte
if request.ContentLength <= 0 {
e.logger.WarnContext(ctx, "body replace skipped due to non-fixed content length")
break
} else if request.ContentLength > 131072 {
e.logger.WarnContext(ctx, "body replace skipped due to large content length: ", request.ContentLength)
break
}
body, err := io.ReadAll(request.Body)
if err != nil {
return E.Cause(err, "read HTTP request body")
}
request.Body.Close()
for mi := 0; i < len(rule.Match); i++ {
body = rule.Match[mi].ReplaceAll(body, []byte(rule.Replace[i]))
}
request.Body = io.NopCloser(bytes.NewReader(body))
request.ContentLength = int64(len(body))
}
if !requestMatch {
for i, rule := range options.SurgeMapLocal {
if !rule.Pattern.MatchString(requestURL) {
continue
}
requestMatch = true
e.logger.DebugContext(ctx, "match map_local[", i, "] => ", rule.String())
go func() {
io.Copy(io.Discard, request.Body)
request.Body.Close()
}()
var (
statusCode = http.StatusOK
headers = make(http.Header)
body []byte
)
if rule.StatusCode > 0 {
statusCode = rule.StatusCode
}
switch {
case rule.File:
resource, err := os.ReadFile(rule.Data)
if err != nil {
return E.Cause(err, "open map local source")
}
mimeType := mime.TypeByExtension(filepath.Ext(rule.Data))
if mimeType == "" {
mimeType = "application/octet-stream"
}
headers.Set("Content-Type", mimeType)
body = resource
case rule.Text:
headers.Set("Content-Type", "text/plain")
body = []byte(rule.Data)
case rule.TinyGif:
headers.Set("Content-Type", "image/gif")
body = surgeTinyGif()
case rule.Base64:
headers.Set("Content-Type", "application/octet-stream")
body = rule.Base64Data
}
for key, values := range headers {
writer.Header()[key] = values
}
writer.WriteHeader(statusCode)
_, err = writer.Write(body)
if err != nil {
return E.Cause(err, "write map local response")
}
return nil
}
}
requestCtx, cancel := context.WithCancel(ctx)
defer cancel()
response, err := e.httpClient.Do(request.WithContext(requestCtx))
if err != nil {
cancel()
return E.Cause(err, "exchange request")
}
var responseMatch bool
var responseBody []byte
if options.Print && response.ContentLength > 0 && response.ContentLength <= 131072 {
responseBody, err = io.ReadAll(response.Body)
if err != nil {
return E.Cause(err, "read HTTP response body")
}
response.Body.Close()
response.Body = io.NopCloser(bytes.NewReader(responseBody))
}
if options.Print {
e.printResponse(ctx, request, response, responseBody)
}
for i, rule := range options.SurgeHeaderRewrite {
if !rule.Response {
continue
}
if !rule.Pattern.MatchString(requestURL) {
continue
}
responseMatch = true
e.logger.DebugContext(ctx, "match header_rewrite[", i, "] => ", rule.String())
switch {
case rule.Add:
response.Header.Add(rule.Key, rule.Value)
case rule.Delete:
response.Header.Del(rule.Key)
case rule.Replace:
if response.Header.Get(rule.Key) != "" {
response.Header.Set(rule.Key, rule.Value)
}
case rule.ReplaceRegex:
if value := response.Header.Get(rule.Key); value != "" {
response.Header.Set(rule.Key, rule.Match.ReplaceAllString(value, rule.Value))
}
}
}
for i, rule := range options.SurgeBodyRewrite {
if !rule.Response {
continue
}
if !rule.Pattern.MatchString(requestURL) {
continue
}
responseMatch = true
e.logger.DebugContext(ctx, "match body_rewrite[", i, "] => ", rule.String())
if responseBody == nil {
if response.ContentLength <= 0 {
e.logger.WarnContext(ctx, "body replace skipped due to non-fixed content length")
break
} else if response.ContentLength > 131072 {
e.logger.WarnContext(ctx, "body replace skipped due to large content length: ", request.ContentLength)
break
}
responseBody, err = io.ReadAll(response.Body)
if err != nil {
return E.Cause(err, "read HTTP request body")
}
response.Body.Close()
}
for mi := 0; i < len(rule.Match); i++ {
responseBody = rule.Match[mi].ReplaceAll(responseBody, []byte(rule.Replace[i]))
}
response.Body = io.NopCloser(bytes.NewReader(responseBody))
response.ContentLength = int64(len(responseBody))
}
if !options.Print && !requestMatch && !responseMatch {
e.logger.WarnContext(ctx, "request not modified")
}
for key, values := range response.Header {
writer.Header()[key] = values
}
writer.WriteHeader(response.StatusCode)
_, err = io.Copy(writer, response.Body)
response.Body.Close()
if err != nil {
return E.Cause(err, "write HTTP response")
}
return nil
}
func (e *Engine) printRequest(ctx context.Context, request *http.Request, body []byte) {
var builder strings.Builder
builder.WriteString(F.ToString(request.Proto, " ", request.Method, " ", request.URL))
builder.WriteString("\n")
if request.URL.Hostname() != "" && request.URL.Hostname() != request.Host {
builder.WriteString("Host: ")
builder.WriteString(request.Host)
builder.WriteString("\n")
}
for key, values := range request.Header {
for _, value := range values {
builder.WriteString(key)
builder.WriteString(": ")
builder.WriteString(value)
builder.WriteString("\n")
}
}
if len(body) > 0 {
builder.WriteString("\n")
if !bytes.ContainsFunc(body, func(r rune) bool {
return !unicode.IsPrint(r) && !unicode.IsSpace(r)
}) {
builder.Write(body)
} else {
builder.WriteString("(body not printable)")
}
}
e.logger.InfoContext(ctx, "request: ", builder.String())
}
func (e *Engine) printResponse(ctx context.Context, request *http.Request, response *http.Response, body []byte) {
var builder strings.Builder
builder.WriteString(F.ToString(response.Proto, " ", response.Status, " ", request.URL))
builder.WriteString("\n")
for key, values := range response.Header {
for _, value := range values {
builder.WriteString(key)
builder.WriteString(": ")
builder.WriteString(value)
builder.WriteString("\n")
}
}
if len(body) > 0 {
builder.WriteString("\n")
if !bytes.ContainsFunc(body, func(r rune) bool {
return !unicode.IsPrint(r) && !unicode.IsSpace(r)
}) {
builder.Write(body)
} else {
builder.WriteString("(body not printable)")
}
}
e.logger.InfoContext(ctx, "response: ", builder.String())
}
type simpleResponseWriter struct {
statusCode int
header http.Header
body bytes.Buffer
}
func (w *simpleResponseWriter) Build(request *http.Request) *http.Response {
return &http.Response{
StatusCode: w.statusCode,
Status: http.StatusText(w.statusCode),
Proto: request.Proto,
ProtoMajor: request.ProtoMajor,
ProtoMinor: request.ProtoMinor,
Header: w.header,
Body: io.NopCloser(&w.body),
}
}
func (w *simpleResponseWriter) Header() http.Header {
if w.header == nil {
w.header = make(http.Header)
}
return w.header
}
func (w *simpleResponseWriter) Write(b []byte) (int, error) {
return w.body.Write(b)
}
func (w *simpleResponseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
}

View File

@@ -11,6 +11,13 @@ type _CertificateOptions struct {
Certificate badoption.Listable[string] `json:"certificate,omitempty"` Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath badoption.Listable[string] `json:"certificate_path,omitempty"` CertificatePath badoption.Listable[string] `json:"certificate_path,omitempty"`
CertificateDirectoryPath badoption.Listable[string] `json:"certificate_directory_path,omitempty"` CertificateDirectoryPath badoption.Listable[string] `json:"certificate_directory_path,omitempty"`
TLSDecryption *TLSDecryptionOptions `json:"tls_decryption,omitempty"`
}
type TLSDecryptionOptions struct {
Enabled bool `json:"enabled,omitempty"`
KeyPair string `json:"key_pair_p12,omitempty"`
KeyPairPassword string `json:"key_pair_p12_password,omitempty"`
} }
type CertificateOptions _CertificateOptions type CertificateOptions _CertificateOptions

View File

@@ -19,10 +19,10 @@ import (
) )
type RawDNSOptions struct { type RawDNSOptions struct {
Servers []NewDNSServerOptions `json:"servers,omitempty"` Servers []DNSServerOptions `json:"servers,omitempty"`
Rules []DNSRule `json:"rules,omitempty"` Rules []DNSRule `json:"rules,omitempty"`
Final string `json:"final,omitempty"` Final string `json:"final,omitempty"`
ReverseMapping bool `json:"reverse_mapping,omitempty"` ReverseMapping bool `json:"reverse_mapping,omitempty"`
DNSClientOptions DNSClientOptions
} }
@@ -35,32 +35,47 @@ type DNSOptions struct {
LegacyDNSOptions LegacyDNSOptions
} }
type contextKeyDontUpgrade struct{}
func ContextWithDontUpgrade(ctx context.Context) context.Context {
return context.WithValue(ctx, (*contextKeyDontUpgrade)(nil), true)
}
func dontUpgradeFromContext(ctx context.Context) bool {
return ctx.Value((*contextKeyDontUpgrade)(nil)) == true
}
func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions) err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions)
if err != nil { if err != nil {
return err return err
} }
if o.FakeIP != nil && o.FakeIP.Enabled { dontUpgrade := dontUpgradeFromContext(ctx)
deprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions)
ctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP)
}
legacyOptions := o.LegacyDNSOptions legacyOptions := o.LegacyDNSOptions
o.LegacyDNSOptions = LegacyDNSOptions{} if !dontUpgrade {
if o.FakeIP != nil && o.FakeIP.Enabled {
deprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions)
ctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP)
}
o.LegacyDNSOptions = LegacyDNSOptions{}
}
err = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions) err = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
if err != nil { if err != nil {
return err return err
} }
rcodeMap := make(map[string]int) if !dontUpgrade {
o.Servers = common.Filter(o.Servers, func(it NewDNSServerOptions) bool { rcodeMap := make(map[string]int)
if it.Type == C.DNSTypeLegacyRcode { o.Servers = common.Filter(o.Servers, func(it DNSServerOptions) bool {
rcodeMap[it.Tag] = it.Options.(int) if it.Type == C.DNSTypeLegacyRcode {
return false rcodeMap[it.Tag] = it.Options.(int)
} return false
return true }
}) return true
if len(rcodeMap) > 0 { })
for i := 0; i < len(o.Rules); i++ { if len(rcodeMap) > 0 {
rewriteRcode(rcodeMap, &o.Rules[i]) for i := 0; i < len(o.Rules); i++ {
rewriteRcode(rcodeMap, &o.Rules[i])
}
} }
} }
return nil return nil
@@ -107,20 +122,24 @@ type DNSTransportOptionsRegistry interface {
CreateOptions(transportType string) (any, bool) CreateOptions(transportType string) (any, bool)
} }
type _NewDNSServerOptions struct { type _DNSServerOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Tag string `json:"tag,omitempty"` Tag string `json:"tag,omitempty"`
Options any `json:"-"` Options any `json:"-"`
} }
type NewDNSServerOptions _NewDNSServerOptions type DNSServerOptions _DNSServerOptions
func (o *NewDNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { func (o *DNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
return badjson.MarshallObjectsContext(ctx, (*_NewDNSServerOptions)(o), o.Options) switch o.Type {
case C.DNSTypeLegacy:
o.Type = ""
}
return badjson.MarshallObjectsContext(ctx, (*_DNSServerOptions)(o), o.Options)
} }
func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
err := json.UnmarshalContext(ctx, content, (*_NewDNSServerOptions)(o)) err := json.UnmarshalContext(ctx, content, (*_DNSServerOptions)(o))
if err != nil { if err != nil {
return err return err
} }
@@ -141,12 +160,12 @@ func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content
return E.New("unknown transport type: ", o.Type) return E.New("unknown transport type: ", o.Type)
} }
} }
err = badjson.UnmarshallExcludedContext(ctx, content, (*_NewDNSServerOptions)(o), options) err = badjson.UnmarshallExcludedContext(ctx, content, (*_DNSServerOptions)(o), options)
if err != nil { if err != nil {
return err return err
} }
o.Options = options o.Options = options
if o.Type == C.DNSTypeLegacy { if o.Type == C.DNSTypeLegacy && !dontUpgradeFromContext(ctx) {
err = o.Upgrade(ctx) err = o.Upgrade(ctx)
if err != nil { if err != nil {
return err return err
@@ -155,7 +174,7 @@ func (o *NewDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content
return nil return nil
} }
func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error { func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
if o.Type != C.DNSTypeLegacy { if o.Type != C.DNSTypeLegacy {
return nil return nil
} }
@@ -172,34 +191,24 @@ func (o *NewDNSServerOptions) 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:
@@ -305,6 +314,27 @@ func (o *NewDNSServerOptions) Upgrade(ctx context.Context) error {
return nil return nil
} }
type DNSServerAddressOptions struct {
Server string `json:"server"`
ServerPort uint16 `json:"server_port,omitempty"`
}
func (o DNSServerAddressOptions) Build() M.Socksaddr {
return M.ParseSocksaddrHostPort(o.Server, o.ServerPort)
}
func (o DNSServerAddressOptions) ServerIsDomain() bool {
return M.IsDomainName(o.Server)
}
func (o *DNSServerAddressOptions) TakeServerOptions() ServerOptions {
return ServerOptions(*o)
}
func (o *DNSServerAddressOptions) ReplaceServerOptions(options ServerOptions) {
*o = DNSServerAddressOptions(options)
}
type LegacyDNSServerOptions struct { type LegacyDNSServerOptions struct {
Address string `json:"address"` Address string `json:"address"`
AddressResolver string `json:"address_resolver,omitempty"` AddressResolver string `json:"address_resolver,omitempty"`
@@ -322,6 +352,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:"-"`
@@ -329,7 +360,7 @@ type LocalDNSServerOptions struct {
type RemoteDNSServerOptions struct { type RemoteDNSServerOptions struct {
LocalDNSServerOptions LocalDNSServerOptions
ServerOptions DNSServerAddressOptions
LegacyAddressResolver string `json:"-"` LegacyAddressResolver string `json:"-"`
LegacyAddressStrategy DomainStrategy `json:"-"` LegacyAddressStrategy DomainStrategy `json:"-"`
LegacyAddressFallbackDelay badoption.Duration `json:"-"` LegacyAddressFallbackDelay badoption.Duration `json:"-"`

View File

@@ -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"`

19
option/mitm.go Normal file
View File

@@ -0,0 +1,19 @@
package option
import (
"github.com/sagernet/sing/common/json/badoption"
)
type MITMOptions struct {
Enabled bool `json:"enabled,omitempty"`
HTTP2Enabled bool `json:"http2_enabled,omitempty"`
}
type MITMRouteOptions struct {
Enabled bool `json:"enabled,omitempty"`
Print bool `json:"print,omitempty"`
SurgeURLRewrite badoption.Listable[SurgeURLRewriteLine] `json:"surge_url_rewrite,omitempty"`
SurgeHeaderRewrite badoption.Listable[SurgeHeaderRewriteLine] `json:"surge_header_rewrite,omitempty"`
SurgeBodyRewrite badoption.Listable[SurgeBodyRewriteLine] `json:"surge_body_rewrite,omitempty"`
SurgeMapLocal badoption.Listable[SurgeMapLocalLine] `json:"surge_map_local,omitempty"`
}

View File

@@ -0,0 +1,449 @@
package option
import (
"encoding/base64"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"unicode"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
)
type SurgeURLRewriteLine struct {
Pattern *regexp.Regexp
Destination *url.URL
Redirect bool
Reject bool
}
func (l SurgeURLRewriteLine) String() string {
var fields []string
fields = append(fields, l.Pattern.String())
if l.Reject {
fields = append(fields, "_")
} else {
fields = append(fields, l.Destination.String())
}
switch {
case l.Redirect:
fields = append(fields, "302")
case l.Reject:
fields = append(fields, "reject")
default:
fields = append(fields, "header")
}
return encodeSurgeKeys(fields)
}
func (l SurgeURLRewriteLine) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}
func (l *SurgeURLRewriteLine) UnmarshalJSON(bytes []byte) error {
var stringValue string
err := json.Unmarshal(bytes, &stringValue)
if err != nil {
return err
}
fields, err := surgeFields(stringValue)
if err != nil {
return E.Cause(err, "invalid surge_url_rewrite line: ", stringValue)
} else if len(fields) < 2 || len(fields) > 3 {
return E.New("invalid surge_url_rewrite line: ", stringValue)
}
pattern, err := regexp.Compile(fields[0].Key)
if err != nil {
return E.Cause(err, "invalid surge_url_rewrite line: invalid pattern: ", stringValue)
}
l.Pattern = pattern
l.Destination, err = url.Parse(fields[1].Key)
if err != nil {
return E.Cause(err, "invalid surge_url_rewrite line: invalid destination: ", stringValue)
}
if len(fields) == 3 {
switch fields[2].Key {
case "header":
case "302":
l.Redirect = true
case "reject":
l.Reject = true
default:
return E.New("invalid surge_url_rewrite line: invalid action: ", stringValue)
}
}
return nil
}
type SurgeHeaderRewriteLine struct {
Response bool
Pattern *regexp.Regexp
Add bool
Delete bool
Replace bool
ReplaceRegex bool
Key string
Match *regexp.Regexp
Value string
}
func (l SurgeHeaderRewriteLine) String() string {
var fields []string
if !l.Response {
fields = append(fields, "http-request")
} else {
fields = append(fields, "http-response")
}
fields = append(fields, l.Pattern.String())
if l.Add {
fields = append(fields, "header-add")
} else if l.Delete {
fields = append(fields, "header-del")
} else if l.Replace {
fields = append(fields, "header-replace")
} else if l.ReplaceRegex {
fields = append(fields, "header-replace-regex")
}
fields = append(fields, l.Key)
if l.Add || l.Replace {
fields = append(fields, l.Value)
} else if l.ReplaceRegex {
fields = append(fields, l.Match.String(), l.Value)
}
return encodeSurgeKeys(fields)
}
func (l SurgeHeaderRewriteLine) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}
func (l *SurgeHeaderRewriteLine) UnmarshalJSON(bytes []byte) error {
var stringValue string
err := json.Unmarshal(bytes, &stringValue)
if err != nil {
return err
}
fields, err := surgeFields(stringValue)
if err != nil {
return E.Cause(err, "invalid surge_header_rewrite line: ", stringValue)
} else if len(fields) < 4 {
return E.New("invalid surge_header_rewrite line: ", stringValue)
}
switch fields[0].Key {
case "http-request":
case "http-response":
l.Response = true
default:
return E.New("invalid surge_header_rewrite line: invalid type: ", stringValue)
}
l.Pattern, err = regexp.Compile(fields[1].Key)
if err != nil {
return E.Cause(err, "invalid surge_header_rewrite line: invalid pattern: ", stringValue)
}
switch fields[2].Key {
case "header-add":
l.Add = true
if len(fields) != 5 {
return E.New("invalid surge_header_rewrite line: " + stringValue)
}
l.Key = fields[3].Key
l.Value = fields[4].Key
case "header-del":
l.Delete = true
l.Key = fields[3].Key
case "header-replace":
l.Replace = true
if len(fields) != 5 {
return E.New("invalid surge_header_rewrite line: " + stringValue)
}
l.Key = fields[3].Key
l.Value = fields[4].Key
case "header-replace-regex":
l.ReplaceRegex = true
if len(fields) != 6 {
return E.New("invalid surge_header_rewrite line: " + stringValue)
}
l.Key = fields[3].Key
l.Match, err = regexp.Compile(fields[4].Key)
if err != nil {
return E.Cause(err, "invalid surge_header_rewrite line: invalid match: ", stringValue)
}
l.Value = fields[5].Key
default:
return E.New("invalid surge_header_rewrite line: invalid action: ", stringValue)
}
return nil
}
type SurgeBodyRewriteLine struct {
Response bool
Pattern *regexp.Regexp
Match []*regexp.Regexp
Replace []string
}
func (l SurgeBodyRewriteLine) String() string {
var fields []string
if !l.Response {
fields = append(fields, "http-request")
} else {
fields = append(fields, "http-response")
}
for i := 0; i < len(l.Match); i += 2 {
fields = append(fields, l.Match[i].String(), l.Replace[i])
}
return strings.Join(fields, " ")
}
func (l SurgeBodyRewriteLine) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}
func (l *SurgeBodyRewriteLine) UnmarshalJSON(bytes []byte) error {
var stringValue string
err := json.Unmarshal(bytes, &stringValue)
if err != nil {
return err
}
fields, err := surgeFields(stringValue)
if err != nil {
return E.Cause(err, "invalid surge_body_rewrite line: ", stringValue)
} else if len(fields) < 4 {
return E.New("invalid surge_body_rewrite line: ", stringValue)
} else if len(fields)%2 != 0 {
return E.New("invalid surge_body_rewrite line: ", stringValue)
}
switch fields[0].Key {
case "http-request":
case "http-response":
l.Response = true
default:
return E.New("invalid surge_body_rewrite line: invalid type: ", stringValue)
}
l.Pattern, err = regexp.Compile(fields[1].Key)
for i := 2; i < len(fields); i += 2 {
var match *regexp.Regexp
match, err = regexp.Compile(fields[i].Key)
if err != nil {
return E.Cause(err, "invalid surge_body_rewrite line: invalid match: ", stringValue)
}
l.Match = append(l.Match, match)
l.Replace = append(l.Replace, fields[i+1].Key)
}
return nil
}
type SurgeMapLocalLine struct {
Pattern *regexp.Regexp
StatusCode int
File bool
Text bool
TinyGif bool
Base64 bool
Data string
Base64Data []byte
Headers http.Header
}
func (l SurgeMapLocalLine) String() string {
var fields []surgeField
fields = append(fields, surgeField{Key: l.Pattern.String()})
if l.File {
fields = append(fields, surgeField{Key: "data-type", Value: "file"})
fields = append(fields, surgeField{Key: "data", Value: l.Data})
} else if l.Text {
fields = append(fields, surgeField{Key: "data-type", Value: "text"})
fields = append(fields, surgeField{Key: "data", Value: l.Data})
} else if l.TinyGif {
fields = append(fields, surgeField{Key: "data-type", Value: "tiny-gif"})
} else if l.Base64 {
fields = append(fields, surgeField{Key: "data-type", Value: "base64"})
fields = append(fields, surgeField{Key: "data-type", Value: base64.StdEncoding.EncodeToString(l.Base64Data)})
}
if l.StatusCode != 0 {
fields = append(fields, surgeField{Key: "status-code", Value: F.ToString(l.StatusCode), ValueSet: true})
}
if len(l.Headers) > 0 {
var headers []string
for key, values := range l.Headers {
for _, value := range values {
headers = append(headers, key+":"+value)
}
}
fields = append(fields, surgeField{Key: "headers", Value: strings.Join(headers, "|")})
}
return encodeSurgeFields(fields)
}
func (l SurgeMapLocalLine) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}
func (l *SurgeMapLocalLine) UnmarshalJSON(bytes []byte) error {
var stringValue string
err := json.Unmarshal(bytes, &stringValue)
if err != nil {
return err
}
fields, err := surgeFields(stringValue)
if err != nil {
return E.Cause(err, "invalid surge_map_local line: ", stringValue)
} else if len(fields) < 1 {
return E.New("invalid surge_map_local line: ", stringValue)
}
l.Pattern, err = regexp.Compile(fields[0].Key)
if err != nil {
return E.Cause(err, "invalid surge_map_local line: invalid pattern: ", stringValue)
}
dataTypeField := common.Find(fields, func(it surgeField) bool {
return it.Key == "data-type"
})
if !dataTypeField.ValueSet {
return E.New("invalid surge_map_local line: missing data-type: ", stringValue)
}
switch dataTypeField.Value {
case "file":
l.File = true
case "text":
l.Text = true
case "tiny-gif":
l.TinyGif = true
case "base64":
l.Base64 = true
default:
return E.New("unsupported data-type ", dataTypeField.Value)
}
for i := 1; i < len(fields); i++ {
switch fields[i].Key {
case "data-type":
continue
case "data":
if l.File {
l.Data = fields[i].Value
} else if l.Text {
l.Data = fields[i].Value
} else if l.Base64 {
l.Base64Data, err = base64.StdEncoding.DecodeString(fields[i].Value)
if err != nil {
return E.New("invalid surge_map_local line: invalid base64 data: ", stringValue)
}
}
case "status-code":
statusCode, err := strconv.ParseInt(fields[i].Value, 10, 16)
if err != nil {
return E.New("invalid surge_map_local line: invalid status code: ", stringValue)
}
l.StatusCode = int(statusCode)
case "header":
headers := make(http.Header)
for _, headerLine := range strings.Split(fields[i].Value, "|") {
if !strings.Contains(headerLine, ":") {
return E.New("invalid surge_map_local line: headers: missing `:` in item: ", stringValue, ": ", headerLine)
}
headers.Add(common.SubstringBefore(headerLine, ":"), common.SubstringAfter(headerLine, ":"))
}
l.Headers = headers
default:
return E.New("invalid surge_map_local line: unknown options: ", fields[i].Key)
}
}
return nil
}
type surgeField struct {
Key string
Value string
ValueSet bool
}
func encodeSurgeKeys(keys []string) string {
keys = common.Map(keys, func(it string) string {
if strings.ContainsFunc(it, unicode.IsSpace) {
return "\"" + it + "\""
} else {
return it
}
})
return strings.Join(keys, " ")
}
func encodeSurgeFields(fields []surgeField) string {
return strings.Join(common.Map(fields, func(it surgeField) string {
if !it.ValueSet {
if strings.ContainsFunc(it.Key, unicode.IsSpace) {
return "\"" + it.Key + "\""
} else {
return it.Key
}
} else {
if strings.ContainsFunc(it.Value, unicode.IsSpace) {
return it.Key + "=\"" + it.Value + "\""
} else {
return it.Key + "=" + it.Value
}
}
}), " ")
}
func surgeFields(s string) ([]surgeField, error) {
var (
fields []surgeField
currentField *surgeField
)
for _, field := range strings.Fields(s) {
if currentField != nil {
field = " " + field
if strings.HasSuffix(field, "\"") {
field = field[:len(field)-1]
if !currentField.ValueSet {
currentField.Key += field
} else {
currentField.Value += field
}
fields = append(fields, *currentField)
currentField = nil
} else {
if !currentField.ValueSet {
currentField.Key += field
} else {
currentField.Value += field
}
}
continue
}
if !strings.Contains(field, "=") {
if strings.HasPrefix(field, "\"") {
field = field[1:]
if strings.HasSuffix(field, "\"") {
field = field[:len(field)-1]
} else {
currentField = &surgeField{Key: field}
continue
}
}
fields = append(fields, surgeField{Key: field})
} else {
key := common.SubstringBefore(field, "=")
value := common.SubstringAfter(field, "=")
if strings.HasPrefix(value, "\"") {
value = value[1:]
if strings.HasSuffix(field, "\"") {
value = value[:len(value)-1]
} else {
currentField = &surgeField{Key: key, Value: value, ValueSet: true}
continue
}
}
fields = append(fields, surgeField{Key: key, Value: value, ValueSet: true})
}
}
if currentField != nil {
return nil, E.New("invalid surge fields line: ", s)
}
return fields, nil
}

View File

@@ -12,13 +12,14 @@ type _Options struct {
Schema string `json:"$schema,omitempty"` Schema string `json:"$schema,omitempty"`
Log *LogOptions `json:"log,omitempty"` Log *LogOptions `json:"log,omitempty"`
DNS *DNSOptions `json:"dns,omitempty"` DNS *DNSOptions `json:"dns,omitempty"`
NTP *NTPOptions `json:"ntp,omitempty"`
Certificate *CertificateOptions `json:"certificate,omitempty"`
Endpoints []Endpoint `json:"endpoints,omitempty"` Endpoints []Endpoint `json:"endpoints,omitempty"`
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"`
Experimental *ExperimentalOptions `json:"experimental,omitempty"` Experimental *ExperimentalOptions `json:"experimental,omitempty"`
NTP *NTPOptions `json:"ntp,omitempty"`
Certificate *CertificateOptions `json:"certificate,omitempty"`
MITM *MITMOptions `json:"mitm,omitempty"`
} }
type Options _Options type Options _Options

View File

@@ -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"`
@@ -99,7 +100,9 @@ type _DomainResolveOptions struct {
type DomainResolveOptions _DomainResolveOptions type DomainResolveOptions _DomainResolveOptions
func (o DomainResolveOptions) MarshalJSON() ([]byte, error) { func (o DomainResolveOptions) MarshalJSON() ([]byte, error) {
if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) && if o.Server == "" {
return []byte("{}"), nil
} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&
!o.DisableCache && !o.DisableCache &&
o.RewriteTTL == nil && o.RewriteTTL == nil &&
o.ClientSubnet == nil { o.ClientSubnet == nil {
@@ -116,7 +119,14 @@ func (o *DomainResolveOptions) UnmarshalJSON(bytes []byte) error {
o.Server = stringValue o.Server = stringValue
return nil return nil
} }
return json.Unmarshal(bytes, (*_DomainResolveOptions)(o)) err = json.Unmarshal(bytes, (*_DomainResolveOptions)(o))
if err != nil {
return err
}
if o.Server == "" {
return E.New("empty domain_resolver.server")
}
return nil
} }
func (o *DialerOptions) TakeDialerOptions() DialerOptions { func (o *DialerOptions) TakeDialerOptions() DialerOptions {

View File

@@ -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)
} }

View File

@@ -158,6 +158,8 @@ type RawRouteOptionsActionOptions struct {
TLSFragment bool `json:"tls_fragment,omitempty"` TLSFragment bool `json:"tls_fragment,omitempty"`
TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"` TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"`
MITM *MITMRouteOptions `json:"mitm,omitempty"`
} }
type RouteOptionsActionOptions RawRouteOptionsActionOptions type RouteOptionsActionOptions RawRouteOptionsActionOptions

View File

@@ -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)
} }

View File

@@ -1,13 +1,65 @@
package option package option
import (
"encoding/json"
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"`
}
type WildcardSNI int
const (
ShadowTLSWildcardSNIOff WildcardSNI = iota
ShadowTLSWildcardSNIAuthed
ShadowTLSWildcardSNIAll
)
func (w WildcardSNI) MarshalJSON() ([]byte, error) {
return json.Marshal(w.String())
}
func (w WildcardSNI) String() string {
switch w {
case ShadowTLSWildcardSNIOff:
return "off"
case ShadowTLSWildcardSNIAuthed:
return "authed"
case ShadowTLSWildcardSNIAll:
return "all"
default:
panic("unknown wildcard SNI value")
}
}
func (w *WildcardSNI) UnmarshalJSON(bytes []byte) error {
var valueString string
err := json.Unmarshal(bytes, &valueString)
if err != nil {
return err
}
switch valueString {
case "off", "":
*w = ShadowTLSWildcardSNIOff
case "authed":
*w = ShadowTLSWildcardSNIAuthed
case "all":
*w = ShadowTLSWildcardSNIAll
default:
return E.New("unknown wildcard SNI value: ", valueString)
}
return nil
} }
type ShadowTLSUser struct { type ShadowTLSUser struct {

View File

@@ -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
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"net" "net"
"net/netip" "net/netip"
"reflect"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -27,6 +28,7 @@ func RegisterOutbound(registry *outbound.Registry) {
var ( var (
_ N.ParallelDialer = (*Outbound)(nil) _ N.ParallelDialer = (*Outbound)(nil)
_ dialer.ParallelNetworkDialer = (*Outbound)(nil) _ dialer.ParallelNetworkDialer = (*Outbound)(nil)
_ dialer.DirectDialer = (*Outbound)(nil)
) )
type Outbound struct { type Outbound struct {
@@ -37,6 +39,7 @@ type Outbound struct {
fallbackDelay time.Duration fallbackDelay time.Duration
overrideOption int overrideOption int
overrideDestination M.Socksaddr overrideDestination M.Socksaddr
isEmpty bool
// loopBack *loopBackDetector // loopBack *loopBackDetector
} }
@@ -56,6 +59,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
domainStrategy: C.DomainStrategy(options.DomainStrategy), domainStrategy: C.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay), fallbackDelay: time.Duration(options.FallbackDelay),
dialer: outboundDialer.(dialer.ParallelInterfaceDialer), dialer: outboundDialer.(dialer.ParallelInterfaceDialer),
//nolint:staticcheck
isEmpty: reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true}) && options.OverrideAddress == "" && options.OverridePort == 0,
// loopBack: newLoopBackDetector(router), // loopBack: newLoopBackDetector(router),
} }
//nolint:staticcheck //nolint:staticcheck
@@ -242,6 +247,10 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.
return conn, newDestination, nil return conn, newDestination, nil
} }
func (h *Outbound) IsEmpty() bool {
return h.isEmpty
}
/*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { /*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) {
return E.New("reject loopback connection to ", metadata.Destination) return E.New("reject loopback connection to ", metadata.Destination)

View File

@@ -85,7 +85,7 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada
} }
switch headerBytes[0] { switch headerBytes[0] {
case socks4.Version, socks5.Version: case socks4.Version, socks5.Version:
return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, metadata.Source, onClose)
default: default:
return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose)
} }

View File

@@ -121,40 +121,48 @@ func (t *TProxy) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
t.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.SocksaddrFromNetIP(destination), nil) t.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.SocksaddrFromNetIP(destination), nil)
} }
type tproxyPacketWriter struct {
ctx context.Context
source netip.AddrPort
destination M.Socksaddr
conn *net.UDPConn
}
func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) {
ctx := log.ContextWithNewID(t.ctx) ctx := log.ContextWithNewID(t.ctx)
writer := &tproxyPacketWriter{ctx: ctx, source: source.AddrPort(), destination: destination} writer := &tproxyPacketWriter{
ctx: ctx,
listener: t.listener,
source: source.AddrPort(),
destination: destination,
}
return true, ctx, writer, func(it error) { return true, ctx, writer, func(it error) {
common.Close(common.PtrOrNil(writer.conn)) common.Close(common.PtrOrNil(writer.conn))
} }
} }
type tproxyPacketWriter struct {
ctx context.Context
listener *listener.Listener
source netip.AddrPort
destination M.Socksaddr
conn *net.UDPConn
}
func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release() defer buffer.Release()
conn := w.conn if w.listener.ListenOptions().NetNs == "" {
if w.destination == destination && conn != nil { conn := w.conn
_, err := conn.WriteToUDPAddrPort(buffer.Bytes(), w.source) if w.destination == destination && conn != nil {
if err != nil { _, err := conn.WriteToUDPAddrPort(buffer.Bytes(), w.source)
w.conn = nil if err != nil {
w.conn = nil
}
return err
} }
return err
} }
var listener net.ListenConfig var listenConfig net.ListenConfig
listener.Control = control.Append(listener.Control, control.ReuseAddr()) listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
listener.Control = control.Append(listener.Control, redir.TProxyWriteBack()) listenConfig.Control = control.Append(listenConfig.Control, redir.TProxyWriteBack())
packetConn, err := listener.ListenPacket(w.ctx, "udp", destination.String()) packetConn, err := w.listener.ListenPacket(listenConfig, w.ctx, "udp", destination.String())
if err != nil { if err != nil {
return err return err
} }
udpConn := packetConn.(*net.UDPConn) udpConn := packetConn.(*net.UDPConn)
if w.destination == destination { if w.listener.ListenOptions().NetNs == "" && w.destination == destination {
w.conn = udpConn w.conn = udpConn
} else { } else {
defer udpConn.Close() defer udpConn.Close()

View File

@@ -46,18 +46,24 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
var handshakeForServerName map[string]shadowtls.HandshakeConfig var handshakeForServerName map[string]shadowtls.HandshakeConfig
if options.Version > 1 { if options.Version > 1 {
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
for serverName, serverOptions := range options.HandshakeForServerName { if options.HandshakeForServerName != nil {
handshakeDialer, err := dialer.New(ctx, serverOptions.DialerOptions, serverOptions.ServerIsDomain()) for _, entry := range options.HandshakeForServerName.Entries() {
if err != nil { handshakeDialer, err := dialer.New(ctx, entry.Value.DialerOptions, entry.Value.ServerIsDomain())
return nil, err if err != nil {
} return nil, err
handshakeForServerName[serverName] = shadowtls.HandshakeConfig{ }
Server: serverOptions.ServerOptions.Build(), handshakeForServerName[entry.Key] = shadowtls.HandshakeConfig{
Dialer: handshakeDialer, Server: entry.Value.ServerOptions.Build(),
Dialer: handshakeDialer,
}
} }
} }
} }
handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions, options.Handshake.ServerIsDomain()) serverIsDomain := options.Handshake.ServerIsDomain()
if options.WildcardSNI != option.ShadowTLSWildcardSNIOff {
serverIsDomain = true
}
handshakeDialer, err := dialer.New(ctx, options.Handshake.DialerOptions, serverIsDomain)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -73,6 +79,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
}, },
HandshakeForServerName: handshakeForServerName, HandshakeForServerName: handshakeForServerName,
StrictMode: options.StrictMode, StrictMode: options.StrictMode,
WildcardSNI: shadowtls.WildcardSNI(options.WildcardSNI),
Handler: (*inboundHandler)(inbound), Handler: (*inboundHandler)(inbound),
Logger: logger, Logger: logger,
}) })

View File

@@ -62,7 +62,7 @@ func (h *Inbound) Close() error {
} }
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, metadata.Source, onClose)
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil { if err != nil {
if E.IsClosedOrCanceled(err) { if E.IsClosedOrCanceled(err) {

View File

@@ -39,6 +39,7 @@ import (
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager" "github.com/sagernet/sing/service/filemanager"
"github.com/sagernet/tailscale/ipn" "github.com/sagernet/tailscale/ipn"
tsDNS "github.com/sagernet/tailscale/net/dns"
"github.com/sagernet/tailscale/net/netmon" "github.com/sagernet/tailscale/net/netmon"
"github.com/sagernet/tailscale/net/tsaddr" "github.com/sagernet/tailscale/net/tsaddr"
"github.com/sagernet/tailscale/tsnet" "github.com/sagernet/tailscale/tsnet"
@@ -145,6 +146,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) { LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) {
return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions()) return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions())
}, },
DNS: &dnsConfigurtor{},
} }
return &Endpoint{ return &Endpoint{
Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil),
@@ -471,3 +473,24 @@ func (d *endpointDialer) ListenPacket(ctx context.Context, destination M.Socksad
d.logger.InfoContext(ctx, "output packet connection") d.logger.InfoContext(ctx, "output packet connection")
return d.Dialer.ListenPacket(ctx, destination) return d.Dialer.ListenPacket(ctx, destination)
} }
type dnsConfigurtor struct {
baseConfig tsDNS.OSConfig
}
func (c *dnsConfigurtor) SetDNS(cfg tsDNS.OSConfig) error {
c.baseConfig = cfg
return nil
}
func (c *dnsConfigurtor) SupportsSplitDNS() bool {
return true
}
func (c *dnsConfigurtor) GetBaseConfig() (tsDNS.OSConfig, error) {
return c.baseConfig, nil
}
func (c *dnsConfigurtor) Close() error {
return nil
}

View File

@@ -99,7 +99,7 @@ func (l *ProxyListener) acceptLoop() {
} }
func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error { func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error {
return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, M.SocksaddrFromNet(conn.RemoteAddr()), nil) return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, nil, M.SocksaddrFromNet(conn.RemoteAddr()), nil)
} }
func (l *ProxyListener) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (l *ProxyListener) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {

View File

@@ -2,9 +2,11 @@ package route
import ( import (
"context" "context"
"errors"
"io" "io"
"net" "net"
"net/netip" "net/netip"
"os"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -14,6 +16,7 @@ import (
"github.com/sagernet/sing-box/common/tlsfragment" "github.com/sagernet/sing-box/common/tlsfragment"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler" "github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@@ -21,23 +24,31 @@ import (
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
) )
var _ adapter.ConnectionManager = (*ConnectionManager)(nil) var _ adapter.ConnectionManager = (*ConnectionManager)(nil)
type ConnectionManager struct { type ConnectionManager struct {
ctx context.Context
logger logger.ContextLogger logger logger.ContextLogger
mitm adapter.MITMEngine
access sync.Mutex access sync.Mutex
connections list.List[io.Closer] connections list.List[io.Closer]
} }
func NewConnectionManager(logger logger.ContextLogger) *ConnectionManager { func NewConnectionManager(ctx context.Context, logger logger.ContextLogger) *ConnectionManager {
return &ConnectionManager{ return &ConnectionManager{
ctx: ctx,
logger: logger, logger: logger,
} }
} }
func (m *ConnectionManager) Start(stage adapter.StartStage) error { func (m *ConnectionManager) Start(stage adapter.StartStage) error {
switch stage {
case adapter.StartStateInitialize:
m.mitm = service.FromContext[adapter.MITMEngine](m.ctx)
}
return nil return nil
} }
@@ -52,6 +63,14 @@ func (m *ConnectionManager) Close() error {
} }
func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
if metadata.MITM != nil && metadata.MITM.Enabled {
if m.mitm == nil {
m.logger.WarnContext(ctx, "MITM disabled")
} else {
m.mitm.NewConnection(ctx, this, conn, metadata, onClose)
return
}
}
ctx = adapter.WithContext(ctx, &metadata) ctx = adapter.WithContext(ctx, &metadata)
var ( var (
remoteConn net.Conn remoteConn net.Conn
@@ -206,14 +225,16 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose)
} }
func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader, destination io.Writer, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
originSource := source var (
originDestination := destination sourceReader io.Reader = source
destinationWriter io.Writer = destination
)
var readCounters, writeCounters []N.CountFunc var readCounters, writeCounters []N.CountFunc
for { for {
source, readCounters = N.UnwrapCountReader(source, readCounters) sourceReader, readCounters = N.UnwrapCountReader(sourceReader, readCounters)
destination, writeCounters = N.UnwrapCountWriter(destination, writeCounters) destinationWriter, writeCounters = N.UnwrapCountWriter(destinationWriter, writeCounters)
if cachedSrc, isCached := source.(N.CachedReader); isCached { if cachedSrc, isCached := sourceReader.(N.CachedReader); isCached {
cachedBuffer := cachedSrc.ReadCached() cachedBuffer := cachedSrc.ReadCached()
if cachedBuffer != nil { if cachedBuffer != nil {
dataLen := cachedBuffer.Len() dataLen := cachedBuffer.Len()
@@ -223,7 +244,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
if done.Swap(true) { if done.Swap(true) {
onClose(err) onClose(err)
} }
common.Close(originSource, originDestination) common.Close(source, destination)
if !direction { if !direction {
m.logger.ErrorContext(ctx, "connection upload payload: ", err) m.logger.ErrorContext(ctx, "connection upload payload: ", err)
} else { } else {
@@ -242,9 +263,13 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
} }
break break
} }
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destination); isEarlyConn && earlyConn.NeedHandshake() { if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destinationWriter); isEarlyConn && earlyConn.NeedHandshake() {
_, err := destination.Write(nil) err := m.connectionCopyEarly(source, destination)
if err != nil { if err != nil {
if done.Swap(true) {
onClose(err)
}
common.Close(source, destination)
if !direction { if !direction {
m.logger.ErrorContext(ctx, "connection upload handshake: ", err) m.logger.ErrorContext(ctx, "connection upload handshake: ", err)
} else { } else {
@@ -253,20 +278,20 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
return return
} }
} }
_, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters) _, err := bufio.CopyWithCounters(destination, sourceReader, source, readCounters, writeCounters)
if err != nil { if err != nil {
common.Close(originDestination) common.Close(source, destination)
} else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex { } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex {
err = duplexDst.CloseWrite() err = duplexDst.CloseWrite()
if err != nil { if err != nil {
common.Close(originSource, originDestination) common.Close(source, destination)
} }
} else { } else {
common.Close(originDestination) destination.Close()
} }
if done.Swap(true) { if done.Swap(true) {
onClose(err) onClose(err)
common.Close(originSource, originDestination) common.Close(source, destination)
} }
if !direction { if !direction {
if err == nil { if err == nil {
@@ -287,6 +312,28 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
} }
} }
func (m *ConnectionManager) connectionCopyEarly(source net.Conn, destination io.Writer) error {
payload := buf.NewPacket()
defer payload.Release()
err := source.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout))
if err != nil {
if err == os.ErrInvalid {
return common.Error(destination.Write(nil))
}
return err
}
_, err = payload.ReadOnceFrom(source)
if err != nil && !(E.IsTimeout(err) || errors.Is(err, io.EOF)) {
return E.Cause(err, "read payload")
}
_ = source.SetReadDeadline(time.Time{})
_, err = destination.Write(payload.Bytes())
if err != nil {
return E.Cause(err, "write payload")
}
return nil
}
func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) {
_, err := bufio.CopyPacket(destination, source) _, err := bufio.CopyPacket(destination, source)
if !direction { if !direction {

View File

@@ -358,7 +358,7 @@ func (r *Router) matchRule(
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{ newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
}, inputConn, inputPacketConn) }, inputConn, inputPacketConn, nil)
if newErr != nil { if newErr != nil {
fatalErr = newErr fatalErr = newErr
return return
@@ -458,11 +458,14 @@ match:
metadata.TLSFragment = true metadata.TLSFragment = true
metadata.TLSFragmentFallbackDelay = routeOptions.TLSFragmentFallbackDelay metadata.TLSFragmentFallbackDelay = routeOptions.TLSFragmentFallbackDelay
} }
if routeOptions.MITM != nil && routeOptions.MITM.Enabled {
metadata.MITM = routeOptions.MITM
}
} }
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *rule.RuleActionSniff: case *rule.RuleActionSniff:
if !preMatch { if !preMatch {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
if newErr != nil { if newErr != nil {
fatalErr = newErr fatalErr = newErr
return return
@@ -493,28 +496,21 @@ match:
break match break match
} }
} }
if !preMatch && inputPacketConn != nil && (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: C.TCPTimeout}, inputConn, inputPacketConn)
if newErr != nil {
fatalErr = newErr
return
}
if newBuffer != nil {
buffers = append(buffers, newBuffer)
} else if len(newPacketBuffers) > 0 {
packetBuffers = append(packetBuffers, newPacketBuffers...)
}
}
return return
} }
func (r *Router) actionSniff( func (r *Router) actionSniff(
ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff, ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff,
inputConn net.Conn, inputPacketConn N.PacketConn, inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { ) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
if sniff.Skip(metadata) { if sniff.Skip(metadata) {
r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first")
return return
} else if inputConn != nil { } else if metadata.Protocol != "" {
r.logger.DebugContext(ctx, "duplicate sniff skipped")
return
}
if inputConn != nil {
sniffBuffer := buf.NewPacket() sniffBuffer := buf.NewPacket()
var streamSniffers []sniff.StreamSniffer var streamSniffers []sniff.StreamSniffer
if len(action.StreamSniffers) > 0 { if len(action.StreamSniffers) > 0 {
@@ -533,6 +529,7 @@ func (r *Router) actionSniff(
ctx, ctx,
metadata, metadata,
inputConn, inputConn,
inputBuffers,
sniffBuffer, sniffBuffer,
action.Timeout, action.Timeout,
streamSniffers..., streamSniffers...,
@@ -559,6 +556,10 @@ func (r *Router) actionSniff(
sniffBuffer.Release() sniffBuffer.Release()
} }
} else if inputPacketConn != nil { } else if inputPacketConn != nil {
if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrClientHelloFragmented) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
return
}
for { for {
var ( var (
sniffBuffer = buf.NewPacket() sniffBuffer = buf.NewPacket()
@@ -590,10 +591,7 @@ func (r *Router) actionSniff(
return return
} }
} else { } else {
if (metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed) && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() && !metadata.RouteOriginalDestination.IsValid() { if len(packetBuffers) > 0 || metadata.PacketSniffError != nil {
metadata.Destination = destination
}
if len(packetBuffers) > 0 {
err = sniff.PeekPacket( err = sniff.PeekPacket(
ctx, ctx,
metadata, metadata,
@@ -612,6 +610,7 @@ func (r *Router) actionSniff(
sniff.UTP, sniff.UTP,
sniff.UDPTracker, sniff.UDPTracker,
sniff.DTLSRecord, sniff.DTLSRecord,
sniff.NTP,
} }
} }
err = sniff.PeekPacket( err = sniff.PeekPacket(
@@ -626,7 +625,8 @@ func (r *Router) actionSniff(
Destination: destination, Destination: destination,
} }
packetBuffers = append(packetBuffers, packetBuffer) packetBuffers = append(packetBuffers, packetBuffer)
if E.IsMulti(err, sniff.ErrClientHelloFragmented) { metadata.PacketSniffError = err
if errors.Is(err, sniff.ErrClientHelloFragmented) {
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue continue
} }

View File

@@ -40,6 +40,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
UDPConnect: action.RouteOptions.UDPConnect, UDPConnect: action.RouteOptions.UDPConnect,
TLSFragment: action.RouteOptions.TLSFragment, TLSFragment: action.RouteOptions.TLSFragment,
TLSFragmentFallbackDelay: time.Duration(action.RouteOptions.TLSFragmentFallbackDelay), TLSFragmentFallbackDelay: time.Duration(action.RouteOptions.TLSFragmentFallbackDelay),
MITM: action.RouteOptions.MITM,
}, },
}, nil }, nil
case C.RuleActionTypeRouteOptions: case C.RuleActionTypeRouteOptions:
@@ -53,6 +54,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout), UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout),
TLSFragment: action.RouteOptionsOptions.TLSFragment, TLSFragment: action.RouteOptionsOptions.TLSFragment,
TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay), TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay),
MITM: action.RouteOptionsOptions.MITM,
}, nil }, nil
case C.RuleActionTypeDirect: case C.RuleActionTypeDirect:
directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false) directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false)
@@ -152,15 +154,7 @@ func (r *RuleActionRoute) Type() string {
func (r *RuleActionRoute) String() string { func (r *RuleActionRoute) String() string {
var descriptions []string var descriptions []string
descriptions = append(descriptions, r.Outbound) descriptions = append(descriptions, r.Outbound)
if r.UDPDisableDomainUnmapping { descriptions = append(descriptions, r.Descriptions()...)
descriptions = append(descriptions, "udp-disable-domain-unmapping")
}
if r.UDPConnect {
descriptions = append(descriptions, "udp-connect")
}
if r.TLSFragment {
descriptions = append(descriptions, "tls-fragment")
}
return F.ToString("route(", strings.Join(descriptions, ","), ")") return F.ToString("route(", strings.Join(descriptions, ","), ")")
} }
@@ -176,13 +170,14 @@ type RuleActionRouteOptions struct {
UDPTimeout time.Duration UDPTimeout time.Duration
TLSFragment bool TLSFragment bool
TLSFragmentFallbackDelay time.Duration TLSFragmentFallbackDelay time.Duration
MITM *option.MITMRouteOptions
} }
func (r *RuleActionRouteOptions) Type() string { func (r *RuleActionRouteOptions) Type() string {
return C.RuleActionTypeRouteOptions return C.RuleActionTypeRouteOptions
} }
func (r *RuleActionRouteOptions) String() string { func (r *RuleActionRouteOptions) Descriptions() []string {
var descriptions []string var descriptions []string
if r.OverrideAddress.IsValid() { if r.OverrideAddress.IsValid() {
descriptions = append(descriptions, F.ToString("override-address=", r.OverrideAddress.AddrString())) descriptions = append(descriptions, F.ToString("override-address=", r.OverrideAddress.AddrString()))
@@ -209,9 +204,22 @@ func (r *RuleActionRouteOptions) String() string {
descriptions = append(descriptions, "udp-connect") descriptions = append(descriptions, "udp-connect")
} }
if r.UDPTimeout > 0 { if r.UDPTimeout > 0 {
descriptions = append(descriptions, "udp-timeout") descriptions = append(descriptions, F.ToString("udp-timeout=", r.UDPTimeout))
} }
return F.ToString("route-options(", strings.Join(descriptions, ","), ")") if r.TLSFragment {
descriptions = append(descriptions, "tls-fragment")
if r.TLSFragmentFallbackDelay > 0 {
descriptions = append(descriptions, F.ToString("tls-fragment-fallbac-delay=", r.TLSFragmentFallbackDelay.String()))
}
}
if r.MITM != nil && r.MITM.Enabled {
descriptions = append(descriptions, "mitm")
}
return descriptions
}
func (r *RuleActionRouteOptions) String() string {
return F.ToString("route-options(", strings.Join(r.Descriptions(), ","), ")")
} }
type RuleActionDNSRoute struct { type RuleActionDNSRoute struct {
@@ -368,6 +376,8 @@ func (r *RuleActionSniff) build() error {
r.StreamSniffers = append(r.StreamSniffers, sniff.SSH) r.StreamSniffers = append(r.StreamSniffers, sniff.SSH)
case C.ProtocolRDP: case C.ProtocolRDP:
r.StreamSniffers = append(r.StreamSniffers, sniff.RDP) r.StreamSniffers = append(r.StreamSniffers, sniff.RDP)
case C.ProtocolNTP:
r.PacketSniffers = append(r.PacketSniffers, sniff.NTP)
default: default:
return E.New("unknown sniffer: ", name) return E.New("unknown sniffer: ", name)
} }
@@ -442,3 +452,32 @@ func (r *RuleActionPredefined) String() string {
options = append(options, common.Map(r.Extra, dns.RR.String)...) options = append(options, common.Map(r.Extra, dns.RR.String)...)
return F.ToString("predefined(", strings.Join(options, ","), ")") return F.ToString("predefined(", strings.Join(options, ","), ")")
} }
func (r *RuleActionPredefined) Response(request *dns.Msg) *dns.Msg {
return &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: request.Id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: r.Rcode,
},
Question: request.Question,
Answer: rewriteRecords(r.Answer, request.Question[0]),
Ns: rewriteRecords(r.Ns, request.Question[0]),
Extra: rewriteRecords(r.Extra, request.Question[0]),
}
}
func rewriteRecords(records []dns.RR, question dns.Question) []dns.RR {
return common.Map(records, func(it dns.RR) dns.RR {
if strings.HasPrefix(it.Header().Name, "*") {
if strings.HasSuffix(question.Name, it.Header().Name[1:]) {
it = dns.Copy(it)
it.Header().Name = question.Name
}
}
return it
})
}

View File

@@ -13,7 +13,7 @@ require (
github.com/docker/go-connections v0.5.0 github.com/docker/go-connections v0.5.0
github.com/gofrs/uuid/v5 v5.3.1 github.com/gofrs/uuid/v5 v5.3.1
github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/quic-go v0.49.0-beta.1
github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff github.com/sagernet/sing v0.6.4-0.20250319121229-11d8838dc56d
github.com/sagernet/sing-quic v0.4.1-beta.1 github.com/sagernet/sing-quic v0.4.1-beta.1
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
@@ -30,7 +30,7 @@ require (
github.com/akutz/memconn v0.1.0 // indirect github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/anytls/sing-anytls v0.0.2 // indirect github.com/anytls/sing-anytls v0.0.6 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/caddyserver/certmagic v0.21.7 // indirect github.com/caddyserver/certmagic v0.21.7 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect
@@ -46,11 +46,11 @@ require (
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gaissmai/bart v0.11.1 // indirect github.com/gaissmai/bart v0.11.1 // indirect
github.com/go-chi/chi/v5 v5.2.1 // indirect github.com/go-chi/chi/v5 v5.2.1 // indirect
github.com/go-chi/render v1.0.3 // indirect github.com/go-chi/render v1.0.3 // indirect
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
@@ -65,13 +65,12 @@ require (
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.2 // indirect github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify/v2 v2.0.3 // indirect github.com/illarion/gonotify/v2 v2.0.3 // indirect
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect
@@ -81,7 +80,7 @@ require (
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v0.2.2 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect github.com/mdlayher/socket v0.5.1 // indirect
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
@@ -109,11 +108,11 @@ require (
github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-mux v0.3.1 // indirect github.com/sagernet/sing-mux v0.3.1 // indirect
github.com/sagernet/sing-shadowtls v0.2.0 // indirect github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 // indirect
github.com/sagernet/sing-tun v0.6.1 // indirect github.com/sagernet/sing-tun v0.6.2-0.20250319123703-35b5747b44ec // indirect
github.com/sagernet/sing-vmess v0.2.0 // indirect github.com/sagernet/sing-vmess v0.2.0 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/tailscale v1.79.0-mod.1 // indirect github.com/sagernet/tailscale v1.80.3-mod.0 // indirect
github.com/sagernet/utls v1.6.7 // indirect github.com/sagernet/utls v1.6.7 // indirect
github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
@@ -123,10 +122,9 @@ require (
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tcnksm/go-httpstat v0.2.0 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
github.com/vishvananda/netns v0.0.4 // 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
@@ -138,17 +136,17 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.33.0 // indirect golang.org/x/crypto v0.33.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/mod v0.23.0 // indirect golang.org/x/mod v0.23.0 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.29.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect

View File

@@ -12,8 +12,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.2 h1:25azSh0o/LMcIkhS4ZutgRTIGwh8O3wuOhsThVM9K9o= github.com/anytls/sing-anytls v0.0.6 h1:UatIjl/OvzWQGXQ1I2bAIkabL9WtihW0fA7G+DXGBUg=
github.com/anytls/sing-anytls v0.0.2/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= github.com/anytls/sing-anytls v0.0.6/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=
@@ -53,8 +53,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
@@ -63,8 +63,8 @@ github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -100,8 +100,8 @@ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQN
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
@@ -114,9 +114,6 @@ github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vy
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -142,8 +139,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
@@ -172,7 +169,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -205,8 +201,8 @@ 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.2-0.20250210072154-8dff604468ff h1:5UGghwx8cI14qFa0ienrLekAYfhdKAiWvJUkY7rHmsI= github.com/sagernet/sing v0.6.4-0.20250319121229-11d8838dc56d h1:8GJnvXlOBdgCa0spumUzPbMamkEbud4sfNTd8+1YaEg=
github.com/sagernet/sing v0.6.2-0.20250210072154-8dff604468ff/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.4-0.20250319121229-11d8838dc56d/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-beta.1 h1:V2VfMckT3EQR3ZdfSzJgZZDsvfZZH42QAZpnOnHKa0s=
@@ -215,16 +211,16 @@ github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegE
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.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk= github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQAHhSXqAfxAw1wDG/QWbdpGH5Na3k8qUynqWnEA=
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo= 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.2-0.20250319123703-35b5747b44ec h1:9/OYGb9qDmUFIhqd3S+3eni62EKRQR1rSmRH18baA/M=
github.com/sagernet/sing-tun v0.6.1/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-tun v0.6.2-0.20250319123703-35b5747b44ec/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.79.0-mod.1 h1:wIuAH7VqBYJNk0h2+bTyk4F0OlSqHvyLDCBrD3i+XNI= github.com/sagernet/tailscale v1.80.3-mod.0 h1:oHIdivbR/yxoiA9d3a2rRlhYn2shY9XVF35Rr8jW508=
github.com/sagernet/tailscale v1.79.0-mod.1/go.mod h1:RKY5WjYLj3JJ7VO/8ZCw8eAFa4+kWU6A1Ftdk84uB14= github.com/sagernet/tailscale v1.80.3-mod.0/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=
@@ -251,16 +247,14 @@ github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29X
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
@@ -300,8 +294,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -310,10 +304,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
@@ -339,10 +333,8 @@ golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
@@ -355,14 +347,14 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"crypto/tls"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
@@ -19,25 +20,43 @@ import (
func TestShadowTLS(t *testing.T) { func TestShadowTLS(t *testing.T) {
t.Run("v1", func(t *testing.T) { t.Run("v1", func(t *testing.T) {
testShadowTLS(t, 1, "", false) testShadowTLS(t, 1, "", false, option.ShadowTLSWildcardSNIOff)
}) })
t.Run("v2", func(t *testing.T) { t.Run("v2", func(t *testing.T) {
testShadowTLS(t, 2, "hello", false) testShadowTLS(t, 2, "hello", false, option.ShadowTLSWildcardSNIOff)
}) })
t.Run("v3", func(t *testing.T) { t.Run("v3", func(t *testing.T) {
testShadowTLS(t, 3, "hello", false) testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIOff)
}) })
t.Run("v2-utls", func(t *testing.T) { t.Run("v2-utls", func(t *testing.T) {
testShadowTLS(t, 2, "hello", true) testShadowTLS(t, 2, "hello", true, option.ShadowTLSWildcardSNIOff)
}) })
t.Run("v3-utls", func(t *testing.T) { t.Run("v3-utls", func(t *testing.T) {
testShadowTLS(t, 3, "hello", true) testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIOff)
})
t.Run("v3-wildcard-sni-authed", func(t *testing.T) {
testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIAuthed)
})
t.Run("v3-wildcard-sni-all", func(t *testing.T) {
testShadowTLS(t, 3, "hello", false, option.ShadowTLSWildcardSNIAll)
})
t.Run("v3-wildcard-sni-authed-utls", func(t *testing.T) {
testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIAll)
})
t.Run("v3-wildcard-sni-all-utls", func(t *testing.T) {
testShadowTLS(t, 3, "hello", true, option.ShadowTLSWildcardSNIAll)
}) })
} }
func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) { func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool, wildcardSNI option.WildcardSNI) {
method := shadowaead_2022.List[0] method := shadowaead_2022.List[0]
ssPassword := mkBase64(t, 16) ssPassword := mkBase64(t, 16)
var clientServerName string
if wildcardSNI != option.ShadowTLSWildcardSNIOff {
clientServerName = "cloudflare.com"
} else {
clientServerName = "google.com"
}
startInstance(t, option.Options{ startInstance(t, option.Options{
Inbounds: []option.Inbound{ Inbounds: []option.Inbound{
{ {
@@ -67,9 +86,10 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool)
ServerPort: 443, ServerPort: 443,
}, },
}, },
Version: version, Version: version,
Password: password, Password: password,
Users: []option.ShadowTLSUser{{Password: password}}, Users: []option.ShadowTLSUser{{Password: password}},
WildcardSNI: wildcardSNI,
}, },
}, },
{ {
@@ -107,7 +127,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool)
OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
TLS: &option.OutboundTLSOptions{ TLS: &option.OutboundTLSOptions{
Enabled: true, Enabled: true,
ServerName: "google.com", ServerName: clientServerName,
UTLS: &option.OutboundUTLSOptions{ UTLS: &option.OutboundUTLSOptions{
Enabled: utlsEanbled, Enabled: utlsEanbled,
}, },
@@ -157,7 +177,7 @@ func TestShadowTLSFallback(t *testing.T) {
}, },
Handshake: option.ShadowTLSHandshakeOptions{ Handshake: option.ShadowTLSHandshakeOptions{
ServerOptions: option.ServerOptions{ ServerOptions: option.ServerOptions{
Server: "google.com", Server: "bing.com",
ServerPort: 443, ServerPort: 443,
}, },
}, },
@@ -177,13 +197,125 @@ func TestShadowTLSFallback(t *testing.T) {
}, },
}, },
} }
response, err := client.Get("https://google.com") response, err := client.Get("https://bing.com")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, response.StatusCode, 200) require.Equal(t, response.StatusCode, 200)
response.Body.Close() response.Body.Close()
client.CloseIdleConnections() client.CloseIdleConnections()
} }
func TestShadowTLSFallbackWildcardAll(t *testing.T) {
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeShadowTLS,
Options: &option.ShadowTLSInboundOptions{
ListenOptions: option.ListenOptions{
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
ListenPort: serverPort,
},
Version: 3,
Users: []option.ShadowTLSUser{
{Password: "hello"},
},
WildcardSNI: option.ShadowTLSWildcardSNIAll,
},
},
},
})
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort))
},
},
}
response, err := client.Get("https://www.bing.com")
require.NoError(t, err)
require.Equal(t, response.StatusCode, 200)
response.Body.Close()
client.CloseIdleConnections()
}
func TestShadowTLSFallbackWildcardAuthedFail(t *testing.T) {
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeShadowTLS,
Options: &option.ShadowTLSInboundOptions{
ListenOptions: option.ListenOptions{
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
ListenPort: serverPort,
},
Handshake: option.ShadowTLSHandshakeOptions{
ServerOptions: option.ServerOptions{
Server: "bing.com",
ServerPort: 443,
},
},
Version: 3,
Users: []option.ShadowTLSUser{
{Password: "hello"},
},
WildcardSNI: option.ShadowTLSWildcardSNIAuthed,
},
},
},
})
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort))
},
},
}
_, err := client.Get("https://baidu.com")
expected := &tls.CertificateVerificationError{}
require.ErrorAs(t, err, &expected)
client.CloseIdleConnections()
}
func TestShadowTLSFallbackWildcardOffFail(t *testing.T) {
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeShadowTLS,
Options: &option.ShadowTLSInboundOptions{
ListenOptions: option.ListenOptions{
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
ListenPort: serverPort,
},
Handshake: option.ShadowTLSHandshakeOptions{
ServerOptions: option.ServerOptions{
Server: "bing.com",
ServerPort: 443,
},
},
Version: 3,
Users: []option.ShadowTLSUser{
{Password: "hello"},
},
WildcardSNI: option.ShadowTLSWildcardSNIOff,
},
},
},
})
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, network, "127.0.0.1:"+F.ToString(serverPort))
},
},
}
_, err := client.Get("https://baidu.com")
expected := &tls.CertificateVerificationError{}
require.ErrorAs(t, err, &expected)
client.CloseIdleConnections()
}
func TestShadowTLSInbound(t *testing.T) { func TestShadowTLSInbound(t *testing.T) {
method := shadowaead_2022.List[0] method := shadowaead_2022.List[0]
password := mkBase64(t, 16) password := mkBase64(t, 16)

View File

@@ -2,6 +2,7 @@ package v2rayhttp
import ( import (
std_bufio "bufio" std_bufio "bufio"
"context"
"io" "io"
"net" "net"
"net/http" "net/http"
@@ -10,6 +11,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/baderror" "github.com/sagernet/sing/common/baderror"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
@@ -255,3 +257,11 @@ func (w *HTTP2ConnWrapper) Close() error {
func (w *HTTP2ConnWrapper) Upstream() any { func (w *HTTP2ConnWrapper) Upstream() any {
return w.ExtendedConn return w.ExtendedConn
} }
func DupContext(ctx context.Context) context.Context {
id, loaded := log.IDFromContext(ctx)
if !loaded {
return context.Background()
}
return log.ContextWithID(context.Background(), id)
}

View File

@@ -132,7 +132,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if requestBody != nil { if requestBody != nil {
conn = bufio.NewCachedConn(conn, requestBody) conn = bufio.NewCachedConn(conn, requestBody)
} }
s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil) s.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
} else { } else {
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
done := make(chan struct{}) done := make(chan struct{})

View File

@@ -12,6 +12,7 @@ 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"
"github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
@@ -37,6 +38,7 @@ type Server struct {
func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { func NewServer(ctx context.Context, logger logger.ContextLogger, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
server := &Server{ server := &Server{
ctx: ctx, ctx: ctx,
logger: logger,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
handler: handler, handler: handler,
host: options.Host, host: options.Host,
@@ -110,7 +112,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed")) s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed"))
return return
} }
s.handler.NewConnectionEx(request.Context(), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil) s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
} }
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {

View File

@@ -74,6 +74,10 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
return return
} }
if header.OpCode.IsControl() { if header.OpCode.IsControl() {
if header.Length > 128 {
err = wsutil.ErrFrameTooLarge
return
}
err = c.controlHandler(header, c.reader) err = c.controlHandler(header, c.reader)
if err != nil { if err != nil {
return return

View File

@@ -13,6 +13,7 @@ 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"
"github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
@@ -114,7 +115,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if len(earlyData) > 0 { if len(earlyData) > 0 {
conn = bufio.NewCachedConn(conn, buf.As(earlyData)) conn = bufio.NewCachedConn(conn, buf.As(earlyData))
} }
s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, nil) s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
} }
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) { func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {